summaryrefslogtreecommitdiffstats
path: root/mailnews/base
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base')
-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.xul251
-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.js186
-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.js1622
-rw-r--r--mailnews/base/prefs/content/AccountManager.xul87
-rw-r--r--mailnews/base/prefs/content/AccountWizard.js992
-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.js259
-rw-r--r--mailnews/base/prefs/content/accountcreation/createInBackend.js333
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.js1959
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.xul493
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchConfig.js240
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchhttp.js267
-rw-r--r--mailnews/base/prefs/content/accountcreation/guessConfig.js1145
-rw-r--r--mailnews/base/prefs/content/accountcreation/readFromXML.js238
-rw-r--r--mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js207
-rw-r--r--mailnews/base/prefs/content/accountcreation/util.js304
-rw-r--r--mailnews/base/prefs/content/accountcreation/verifyConfig.js347
-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.js400
-rw-r--r--mailnews/base/prefs/content/am-server.xul468
-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.js256
-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.js156
-rw-r--r--mailnews/base/prefs/content/removeAccount.xul87
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.js182
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.xul124
-rw-r--r--mailnews/base/public/MailNewsTypes.h39
-rw-r--r--mailnews/base/public/MailNewsTypes2.idl93
-rw-r--r--mailnews/base/public/moz.build75
-rw-r--r--mailnews/base/public/mozINewMailListener.idl22
-rw-r--r--mailnews/base/public/mozINewMailNotificationService.idl58
-rw-r--r--mailnews/base/public/msgCore.h188
-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.idl236
-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.js854
-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.xml738
-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.build82
-rw-r--r--mailnews/base/src/msgAsyncPrompter.js126
-rw-r--r--mailnews/base/src/msgBase.manifest12
-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.mm730
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.cpp762
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerWinIntegration.cpp1183
-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.cpp3708
-rw-r--r--mailnews/base/src/nsMsgAccountManager.h216
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.cpp1183
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.h142
-rw-r--r--mailnews/base/src/nsMsgBiffManager.cpp373
-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.cpp8066
-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.cpp1348
-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.cpp514
-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.cpp251
-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.build78
-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.cpp228
-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.cpp6040
-rw-r--r--mailnews/base/util/nsMsgDBFolder.h297
-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.cpp2292
-rw-r--r--mailnews/base/util/nsMsgIncomingServer.h103
-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.cpp441
-rw-r--r--mailnews/base/util/nsMsgLineBuffer.h107
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.cpp1061
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.h86
-rw-r--r--mailnews/base/util/nsMsgProtocol.cpp1552
-rw-r--r--mailnews/base/util/nsMsgProtocol.h239
-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.h73
-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.h50
-rw-r--r--mailnews/base/util/templateUtils.js90
-rw-r--r--mailnews/base/util/traceHelper.js113
385 files changed, 114893 insertions, 0 deletions
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..70c2209f3
--- /dev/null
+++ b/mailnews/base/content/msgAccountCentral.xul
@@ -0,0 +1,251 @@
+<?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="CreateAccountChat"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&chat.label;"
+ chromedir="&locale.dir;"
+ onclick="openIMAccountWizard();"/>
+ <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..243934092
--- /dev/null
+++ b/mailnews/base/content/newmailalert.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/. */
+
+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 && !folder.getFlag(Ci.nsMsgFolderFlags.Virtual))
+ {
+ 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..2df37494f
--- /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" 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..83117f4ad
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountManager.js
@@ -0,0 +1,1622 @@
+/* -*- 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 = getDefaultAccount();
+ 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(getDefaultAccount(), 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) {
+ if (!Services.prefs.getBoolPref("mail.chat.enabled"))
+ document.getElementById("accountActionsAddIMAccount").hidden = true;
+
+ 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 != getDefaultAccount() &&
+ 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;
+}
+
+/**
+ * Returns the default account without throwing exception if there is none.
+ * The account manager can be opened even if there are no account yet.
+ */
+function getDefaultAccount() {
+ try {
+ return MailServices.accounts.defaultAccount;
+ } catch (e) {
+ return null; // No default account yet.
+ }
+}
+
+/**
+ * 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;
+
+ if (server.type == "im" && !Services.prefs.getBoolPref("mail.chat.enabled"))
+ continue;
+
+ 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(getDefaultAccount(), 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..1903cbc88
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountWizard.js
@@ -0,0 +1,992 @@
+/* -*- 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;
+
+// default account
+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();
+
+ try {
+ gDefaultAccount = MailServices.accounts.defaultAccount;
+ }
+ catch (ex) {
+ // no default account, this is expected the first time you launch mail
+ // on a new profile
+ gDefaultAccount = null;
+ }
+
+ // 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 exists and if that account is alllowed to be
+ // a default account. If no such account, make this one as the default account
+ // and turn on the new mail check at startup for the current account
+ if (!(gDefaultAccount && gDefaultAccount.incomingServer.canBeDefaultServer)) {
+ 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 && gDefaultAccount.incomingServer.canBeDefaultServer)) {
+ 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..3a757d8ee
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/accountConfig.js
@@ -0,0 +1,259 @@
+/* -*- 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,
+ // OAuth2 configuration, if needed.
+ oauthSettings : null,
+ // 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..d959c3ae9
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/createInBackend.js
@@ -0,0 +1,333 @@
+/* -*- 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);
+
+ if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ }
+
+ // 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);
+ }
+
+ 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);
+ }
+
+ 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..b4e6854da
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.js
@@ -0,0 +1,1959 @@
+/* -*- 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");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+
+/**
+ * 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" and kDebug = true
+
+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");
+ setLabelFromStringBundle("in-authMethod-oauth2", "authOAuth2");
+ 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");
+ setLabelFromStringBundle("out-authMethod-oauth2", "authOAuth2");
+
+ 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()
+ {
+ 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
+ {
+ gEmailWizardLogger.info("progress callback host " + hostname +
+ " port " + port + " type " + type);
+ },
+ 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,
+ [ 0, 3, 4, 5, 6, 10 ], 0);
+ 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);
+
+ // 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;
+ }
+
+ // 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);
+ }
+
+ // 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;
+ }
+
+ // 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;
+
+ // We loaded dynamic client registration, fill this data back in to the
+ // config set.
+ if (successfulConfig.oauthSettings)
+ self._currentConfig.oauthSettings = successfulConfig.oauthSettings;
+
+ 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..0777d1651
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
@@ -0,0 +1,493 @@
+<?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"/>
+ <menuitem id="in-authMethod-oauth2" value="10" hidden="true"/>
+ </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"/>
+ <menuitem id="out-authMethod-oauth2" value="10" hidden="true"/>
+ </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..07a9f5586
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
@@ -0,0 +1,240 @@
+/* -*- 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 url1 = "http://autoconfig." + sanitize.hostname(domain) +
+ "/mail/config-v1.1.xml";
+ // .well-known/ <http://tools.ietf.org/html/draft-nottingham-site-meta-04>
+ let url2 = "http://" + sanitize.hostname(domain) +
+ "/.well-known/autoconfig/mail/config-v1.1.xml";
+ let sucAbortable = new SuccessiveAbortable();
+ var time = Date.now();
+ var urlArgs = { emailaddress: emailAddress };
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.fetchFromISP.sendEmailAddress")) {
+ delete urlArgs.emailaddress;
+ }
+ let fetch1 = new FetchHTTP(url1, urlArgs, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ function(e1) // fetch1 failed
+ {
+ ddump("fetchisp 1 <" + url1 + "> took " + (Date.now() - time) +
+ "ms and failed with " + e1);
+ time = Date.now();
+ if (e1 instanceof CancelledException)
+ {
+ errorCallback(e1);
+ return;
+ }
+
+ let fetch2 = new FetchHTTP(url2, urlArgs, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ function(e2)
+ {
+ ddump("fetchisp 2 <" + url2 + "> took " + (Date.now() - time) +
+ "ms and failed with " + e2);
+ // return the error for the primary call,
+ // unless the fetch was cancelled
+ errorCallback(e2 instanceof CancelledException ? e2 : e1);
+ });
+ sucAbortable.current = fetch2;
+ fetch2.start();
+ });
+ sucAbortable.current = fetch1;
+ fetch1.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..04f5272cd
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
@@ -0,0 +1,267 @@
+/* -*- 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;
+ try
+ {
+ errorCode = this._request.status;
+ errorStr = this._request.statusText;
+ } catch (e) {
+ // If we can't resolve the hostname in DNS etc., .statusText throws
+ errorCode = -2;
+ errorStr = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties")
+ .GetStringFromName("cannot_contact_server.error");
+ ddump(errorStr);
+ }
+ }
+
+ // 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..755c499cd
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/guessConfig.js
@@ -0,0 +1,1145 @@
+/* -*- 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");
+
+var TIMEOUT = 10; // in seconds
+
+// 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");
+
+ // Servers that we know enough that they support OAuth2 do not need guessing.
+ if (resultConfig.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ successCallback(resultConfig);
+ return null;
+ }
+
+ 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 = 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()
+ hostTry.hostname = hostname;
+ hostTry.status = kNotTried;
+ 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;
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i]; // {HostTry}
+ if (thisTry.status != kNotTried)
+ continue;
+ this._log.info("poking at " + thisTry.hostname + " port " +
+ thisTry.port + " ssl "+ thisTry.ssl + " protocol " +
+ protocolToString(thisTry.protocol));
+ if (i == 0) // showing 50 servers at once is pointless
+ this.mProgressCallback(thisTry);
+
+ thisTry.abortable = SocketUtil(
+ thisTry.hostname, thisTry.port, thisTry.ssl,
+ thisTry.commands, TIMEOUT,
+ new SSLErrorHandler(thisTry, this._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(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("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("no data");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info("wiredata: " + wiredata.join(""));
+ thisTry.authMethods =
+ this._advertisesAuthMethods(thisTry.protocol, wiredata);
+ if (thisTry.ssl == TLS && !this._hasTLS(thisTry, wiredata))
+ {
+ this._log.info("STARTTLS wanted, but not offered");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info("success with " + thisTry.hostname + ":" +
+ thisTry.port + " " + protocolToString(thisTry.protocol) +
+ " ssl " + thisTry.ssl +
+ (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("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 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,
+ 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, null);
+
+ 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);
+ }
+}
+
diff --git a/mailnews/base/prefs/content/accountcreation/readFromXML.js b/mailnews/base/prefs/content/accountcreation/readFromXML.js
new file mode 100644
index 000000000..c7e796f5f
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/readFromXML.js
@@ -0,0 +1,238 @@
+/* -*- 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,
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2 });
+ 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,
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2,
+ });
+
+ 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..0f95f78d1
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
@@ -0,0 +1,207 @@
+/* -*- 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);
+ if (kDebug)
+ msg += " (bad value: " + new String(uncheckedBadValue) + ")";
+ 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..d867bfbe9
--- /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_NORMAL,
+ 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..a2afbdad8
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
@@ -0,0 +1,347 @@
+/* -*- 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");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+
+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 {
+ // 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)
+ 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..e3c2d2b09
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.js
@@ -0,0 +1,400 @@
+/* -*- 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.
+ document.getElementById("authMethod-oauth2").hidden = (serverType != "imap");
+ 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");
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+ 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..f52aef1cc
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.xul
@@ -0,0 +1,468 @@
+<?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"/>
+ <menuitem id="authMethod-oauth2" value="10"/>
+ <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..f5e9ab38c
--- /dev/null
+++ b/mailnews/base/prefs/content/am-smtp.js
@@ -0,0 +1,256 @@
+/* -*- 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)
+ {
+ 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;
+ case AuthMethod.OAuth2:
+ authStr = "authOAuth2";
+ break;
+ 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..350a0a065
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.js
@@ -0,0 +1,156 @@
+/* -*- 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;
+ }
+
+ if (gServer.type == "im") {
+ let dataCheckbox = document.getElementById("removeData");
+ dataCheckbox.label = dataCheckbox.getAttribute("labelChat");
+ dataCheckbox.accessKey = dataCheckbox.getAttribute("accesskeyChat");
+ }
+
+ 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 if (gServer.type == "im") {
+ document.getElementById("chatAccount").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..371262a11
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.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/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;"
+ labelChat="&removeData.label;"
+ checked="false"
+ accesskey="&removeData.accesskey;"
+ accesskeyChat="&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>
+ <description id="chatAccount" collapsed="true">
+ &removeDataLocalAccount.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..54590e1b2
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.js
@@ -0,0 +1,182 @@
+/* -*- 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");
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+ 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..f78916d2a
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.xul
@@ -0,0 +1,124 @@
+<?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"/>
+ <menuitem id="authMethod-oauth2" value="10"/>
+ <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..aaedcd2bb
--- /dev/null
+++ b/mailnews/base/public/moz.build
@@ -0,0 +1,75 @@
+# 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 += [
+ 'MailNewsTypes2.idl',
+ 'mozINewMailListener.idl',
+ 'mozINewMailNotificationService.idl',
+ 'msgIOAuth2Module.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..fc18fb16f
--- /dev/null
+++ b/mailnews/base/public/msgCore.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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
+
+#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..d5a06d2c8
--- /dev/null
+++ b/mailnews/base/public/nsIMsgAccountManager.idl
@@ -0,0 +1,236 @@
+/* -*- 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 */
+
+ /* defaultAccount should always be set if there are any accounts
+ * in the account manager. 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..18415d801
--- /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 the passed in folder. 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..986185d34
--- /dev/null
+++ b/mailnews/base/search/content/FilterEditor.js
@@ -0,0 +1,854 @@
+/* -*- 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
+ var copiedFilter = args.copiedFilter;
+ var 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);
+
+ var newTerm = newFilter.createTerm();
+ newTerm.attrib = searchTerm.attrib;
+ newTerm.op = searchTerm.op;
+ newTerm.booleanAnd = searchTerm.booleanAnd;
+ newTerm.value = searchTerm.value;
+ newFilter.appendTerm(newTerm);
+ };
+
+ 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..80ebe38c4
--- /dev/null
+++ b/mailnews/base/search/content/searchWidgets.xml
@@ -0,0 +1,738 @@
+<?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,
+ Components.interfaces.nsIMsgIdentity));
+
+ if (!identities.length) // typically if this is Local Folders
+ 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..384436299
--- /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:
+ 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..c65e5c24a
--- /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
+ 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..5b3b2698a
--- /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(pchar.get());
+ }
+ }
+ }
+// else
+// asciiOnly = false; // TODO: enable this line when the condition is not a plain "true" in the if().
+
+ nsAutoString usAsciiCharSet(NS_LITERAL_STRING("us-ascii"));
+ // Get the optional CHARSET parameter, in case we need it.
+ char *csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet.get() : 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.get(): destCharset,
+ asciiOnly ? usAsciiCharSet.get(): 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..ef50ad1ed
--- /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:
+ 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..b84839e9e
--- /dev/null
+++ b/mailnews/base/src/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 += [
+ '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',
+ 'msgBase.manifest',
+ 'msgOAuth2Module.js',
+ 'newMailNotificationService.js',
+ 'nsMailNewsCommandLineHandler.js',
+]
+
+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..e7af18bdf
--- /dev/null
+++ b/mailnews/base/src/msgBase.manifest
@@ -0,0 +1,12 @@
+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}
+component {b63d8e4c-bf60-439b-be0e-7c9f67291042} msgOAuth2Module.js
+contract @mozilla.org/mail/oauth2-module;1 {b63d8e4c-bf60-439b-be0e-7c9f67291042}
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..d12876d89
--- /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
+
+ 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..82fb5b68a
--- /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_NORMAL,
+ 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..286c76044
--- /dev/null
+++ b/mailnews/base/src/nsMessengerOSXIntegration.mm
@@ -0,0 +1,730 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 kChatEnabledPref "mail.chat.enabled"
+#define kBiffAnimateDockIconPref "mail.biff.animate_dock_icon"
+#define kMaxDisplayCount 10
+#define kNewChatMessageTopic "new-directed-incoming-message"
+#define kUnreadImCountChangedTopic "unread-im-count-changed"
+
+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;
+ mUnreadChat = 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");
+
+ bool chatEnabled = false;
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ rv = pref->GetBoolPref(kChatEnabledPref, &chatEnabled);
+ if (NS_SUCCEEDED(rv) && chatEnabled) {
+ observerService->AddObserver(this, kNewChatMessageTopic, false);
+ observerService->AddObserver(this, kUnreadImCountChangedTopic, false);
+ }
+ }
+
+ // 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, kNewChatMessageTopic)) {
+ // We don't have to bother about checking if the window is already focused
+ // before attempting to bounce the dock icon, as BounceDockIcon is
+ // implemented by a getAttention call which won't do anything if the window
+ // requesting attention is already focused.
+ return BounceDockIcon();
+ }
+
+ if (!strcmp(aTopic, kUnreadImCountChangedTopic)) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsPRInt32> unreadCount = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = unreadCount->GetData(&mUnreadChat);
+ 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 + mUnreadChat;
+ // 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..0e4dd1405
--- /dev/null
+++ b/mailnews/base/src/nsMessengerUnixIntegration.cpp
@@ -0,0 +1,762 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+
+ // Unless we're dealing with an Inbox, we don't care
+ // about Drafts, Queue, SentMail, Template, or Junk folders
+ if (!(flags & nsMsgFolderFlags::Inbox) &&
+ (flags & (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Inbox)))
+ 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..98888fcb3
--- /dev/null
+++ b/mailnews/base/src/nsMessengerWinIntegration.cpp
@@ -0,0 +1,1183 @@
+/* -*- 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));
+ if (NS_FAILED(rv)) {
+ // 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..5dd3893b6
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManager.cpp
@@ -0,0 +1,3708 @@
+/* -*- 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 <algorithm>
+#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, clear the default account
+ if (m_defaultAccount.get() == aAccount)
+ SetDefaultAccount(nullptr);
+
+ // 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, pick the first account */
+NS_IMETHODIMP
+nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount **aDefaultAccount)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultAccount);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_defaultAccount) {
+ uint32_t count = m_accounts.Length();
+ if (!count) {
+ *aDefaultAccount = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ 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_FAILED(rv) || !m_defaultAccount) {
+ nsCOMPtr<nsIMsgAccount> firstAccount;
+ uint32_t index;
+ bool foundValidDefaultAccount = false;
+ for (index = 0; index < count; index++) {
+ nsCOMPtr<nsIMsgAccount> account(m_accounts[index]);
+
+ // get incoming server
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ // server could be null if created by an unloaded extension
+ (void) account->GetIncomingServer(getter_AddRefs(server));
+
+ bool canBeDefaultServer = false;
+ if (server)
+ {
+ server->GetCanBeDefaultServer(&canBeDefaultServer);
+ if (!firstAccount)
+ firstAccount = account;
+ }
+
+ // if this can serve as default server, set it as default and
+ // break out of the loop.
+ if (canBeDefaultServer) {
+ SetDefaultAccount(account);
+ foundValidDefaultAccount = true;
+ break;
+ }
+ }
+
+ if (!foundValidDefaultAccount) {
+ // Get the first account and use it.
+ // We need to fix this scenario, e.g. in bug 342632.
+ NS_WARNING("No valid default account found.");
+ if (firstAccount) {
+ NS_WARNING("Just using the first one (FIXME).");
+ SetDefaultAccount(firstAccount);
+ }
+ }
+ }
+ }
+
+ if (!m_defaultAccount) {
+ // Absolutely no usable account found. Error out.
+ NS_ERROR("Default account is null, when not expected!");
+ *aDefaultAccount = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ NS_ADDREF(*aDefaultAccount = m_defaultAccount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount *aDefaultAccount)
+{
+ if (m_defaultAccount != aDefaultAccount)
+ {
+ 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) && defaultServer && (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..d5a116e57
--- /dev/null
+++ b/mailnews/base/src/nsMsgAccountManager.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+
+ // 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..360600147
--- /dev/null
+++ b/mailnews/base/src/nsMsgBiffManager.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 "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"
+#include <algorithm>
+
+#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..dfb9acc7a
--- /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();
+ 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..f7856dd4d
--- /dev/null
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -0,0 +1,8066 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAlgorithm.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"
+#include <algorithm>
+
+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);
+
+ nsCString emailAddress;
+ nsString name;
+ ExtractFirstAddress(EncodedHeader(author, headerCharset.get()), 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('>');
+ }
+ }
+ }
+
+ 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;
+}
+
+bool nsMsgDBView::IsOutgoingMsg(nsIMsgDBHdr* aHdr)
+{
+ nsString author;
+ aHdr->GetMime2DecodedAuthor(author);
+
+ nsCString emailAddress;
+ nsString name;
+ ExtractFirstAddress(DecodedHeader(author), name, 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())
+ mEmails.PutEntry(email);
+
+ identity->GetReplyTo(email);
+ if (!email.IsEmpty())
+ 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)
+{
+ 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..6dcbbea3b
--- /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:
+ 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..4b0dc3ad5
--- /dev/null
+++ b/mailnews/base/src/nsMsgFolderCompactor.cpp
@@ -0,0 +1,1348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <algorithm>
+#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
+ {
+ 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;
+}
+
+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;
+ }
+
+ /**
+ * 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;
+ }
+
+// 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..3d1cb4fb4
--- /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();
+ 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..d2f8157ed
--- /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:
+ 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:
+ 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..cdc4f0b5a
--- /dev/null
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
@@ -0,0 +1,514 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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);
+
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ aFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse));
+
+ 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;
+ nsCString searchUri;
+ if (!m_doingQuickSearch)
+ {
+ 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..49d9bfb52
--- /dev/null
+++ b/mailnews/base/src/nsStatusBarBiffManager.cpp
@@ -0,0 +1,251 @@
+/* -*- 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 CHAT_PREF_BRANCH "mail.chat."
+#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
+#define PREF_CHAT_ENABLED "mail.chat.enabled"
+#define PLAY_CHAT_NOTIFICATION_SOUND "play-chat-notification-sound"
+
+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);
+
+ bool chatEnabled = false;
+ if (NS_SUCCEEDED(rv))
+ rv = pref->GetBoolPref(PREF_CHAT_ENABLED, &chatEnabled);
+ if (NS_SUCCEEDED(rv) && chatEnabled) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->AddObserver(this, PLAY_CHAT_NOTIFICATION_SOUND, false);
+ }
+
+ 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 PlayBiffSound(CHAT_PREF_BRANCH);
+}
+
+// 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..134a46ae1
--- /dev/null
+++ b/mailnews/base/util/moz.build
@@ -0,0 +1,78 @@
+# 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',
+ 'OAuth2.jsm',
+ 'OAuth2Providers.jsm',
+ 'StringBundle.js',
+ 'templateUtils.js',
+ 'traceHelper.js',
+]
+
+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..5b47422f2
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressIStream.cpp
@@ -0,0 +1,228 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAlgorithm.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..3a1a571e2
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -0,0 +1,6040 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+#include <algorithm>
+#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()
+{
+ return NS_OK;
+}
+
+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..6074b45f9
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.h
@@ -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/. */
+
+#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
+ */
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+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;
+};
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
+
+#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..a1e897f12
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.cpp
@@ -0,0 +1,2292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+#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)
+{
+}
+
+nsMsgIncomingServer::~nsMsgIncomingServer()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgIncomingServer, nsIMsgIncomingServer,
+ nsISupportsWeakReference)
+
+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..0c4219cd2
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.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 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"
+
+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.
+ */
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+class NS_MSG_BASE nsMsgIncomingServer : public nsIMsgIncomingServer,
+ public nsSupportsWeakReference
+{
+ public:
+ nsMsgIncomingServer();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGINCOMINGSERVER
+
+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;
+};
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
+
+#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..c33306ad8
--- /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 */);
+ 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..0a88cd840
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.cpp
@@ -0,0 +1,441 @@
+/* -*- 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 "nsAlgorithm.h"
+#include "nsMsgUtils.h"
+#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
+#include <algorithm>
+
+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..0383b2d43
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.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/. */
+#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:
+ 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:
+ // 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....
+ virtual ~nsMsgLineStreamBuffer();
+
+ // 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();
+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..17f95fc30
--- /dev/null
+++ b/mailnews/base/util/nsMsgMailNewsUrl.cpp
@@ -0,0 +1,1061 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <algorithm>
+#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..61fd4827e
--- /dev/null
+++ b/mailnews/base/util/nsMsgMailNewsUrl.h
@@ -0,0 +1,86 @@
+/* -*- 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.
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+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;
+};
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
+
+#endif /* nsMsgMailNewsUrl_h___ */
diff --git a/mailnews/base/util/nsMsgProtocol.cpp b/mailnews/base/util/nsMsgProtocol.cpp
new file mode 100644
index 000000000..f2190cb45
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.cpp
@@ -0,0 +1,1552 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAlgorithm.h"
+#include "mozilla/Services.h"
+#include <algorithm>
+#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..98aa67285
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+// 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;
+};
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
+
+#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..5c34de4d4
--- /dev/null
+++ b/mailnews/base/util/nsMsgTxn.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 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.
+ */
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+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);
+};
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
+
+#endif
diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp
new file mode 100644
index 000000000..0ed04cd47
--- /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 = Substring(spec, queryIndex + 1).FindChar('&');
+ if (queryEnd == kNotFound)
+ queryEnd = Substring(spec, queryIndex + 1).FindChar('?');
+ 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 + 1));
+ }
+ 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..62b1c52b8
--- /dev/null
+++ b/mailnews/base/util/nsStopwatch.h
@@ -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/. */
+
+#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"
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT
+
+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;
+ };
+ }
+ }
+ }
+};