Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/thsmi/sieve.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThomas Schmid <thsmi@users.noreply.github.com>2021-04-24 13:15:45 +0300
committerGitHub <noreply@github.com>2021-04-24 13:15:45 +0300
commit09fac3ff19d63fc6eac67fe930369908da1d9644 (patch)
treecc62723f6dfc6ee6ce6068bb711e5f44a2de8f1f /src
parent84f1046b5562fa65db52a1258792e69b2216b934 (diff)
Validate sha256 fingerprints (#402)
Bad Certs: * Validate sha256 fingerprints * Display SHA256 fingerprint and improve message * Automatic Reconnect when bad cert was accepted. Security: * Switch to webcrypto api * Add SCRAM-SHA512 * Remove CRAM-MD5 * Fix Login with extended ascii character password. * Allow only secure connection in wx * Implement a UTF8 base64 en- and decoder. Backend: * Move sieve sessions to background page so that only sockets are wx experiments. * Revise Request and WebExtension's socket implementation. * Refactor Request Queueing * Unify Logger a moz specific is not needed anymore. * Improve log messages. * Add a trace mode to logger. * Migrate libManageSieve to JavaScript Modules * Migrate Gulp scripts to mjs modules * Rename js files to mjs and tpl to html * Extract Node specific UI elements into separate files * Fix Thunderbird 89 compatibility * Fix loading keytar * Correct main windows scope Misc: * Fix linter and linter warnings * Adds a Reload and open Devtools button to debug dialog. * Upgrade to Bootstrap 5b3 * Fix azure configuration * Upgrade dependencies
Diffstat (limited to 'src')
-rwxr-xr-xsrc/app/app.html4
-rw-r--r--[-rwxr-xr-x]src/app/app.mjs (renamed from src/app/app.js)112
-rwxr-xr-xsrc/app/css/navbar-top-fixed.css30
-rwxr-xr-xsrc/app/index.js147
-rwxr-xr-xsrc/app/libs/libManageSieve/SieveAutoConfig.js111
-rw-r--r--src/app/libs/libManageSieve/SieveAutoConfig.mjs106
-rw-r--r--src/app/libs/libManageSieve/SieveBase64.mjs55
-rw-r--r--src/app/libs/libManageSieve/SieveClient.js351
-rw-r--r--src/app/libs/libManageSieve/SieveClient.mjs278
-rwxr-xr-xsrc/app/libs/libManageSieve/SieveCrypto.js78
-rw-r--r--src/app/libs/libManageSieve/SieveCrypto.mjs115
-rw-r--r--src/app/libs/libManageSieve/SieveLogger.js40
-rwxr-xr-xsrc/app/libs/libManageSieve/SieveNodeRequestBuilder.js45
-rwxr-xr-xsrc/app/libs/libManageSieve/SieveNodeResponseParser.js46
-rw-r--r--src/app/libs/libManageSieve/SieveSession.js59
-rw-r--r--src/app/libs/libManageSieve/SieveSession.mjs34
-rw-r--r--src/app/libs/libManageSieve/SieveSessions.js168
-rw-r--r--src/app/libs/libManageSieve/SieveSessions.mjs146
-rw-r--r--src/app/libs/libManageSieve/SieveTimer.mjs47
-rw-r--r--src/app/libs/managesieve.ui/accounts.html27
-rw-r--r--src/app/libs/managesieve.ui/accounts.js188
-rw-r--r--src/app/libs/managesieve.ui/accounts.mjs183
-rw-r--r--src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.js72
-rw-r--r--src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.mjs63
-rw-r--r--src/app/libs/managesieve.ui/accounts/SieveAccountUI.mjs97
-rw-r--r--src/app/libs/managesieve.ui/accounts/SieveAccounts.js93
-rw-r--r--src/app/libs/managesieve.ui/accounts/SieveAccounts.mjs84
-rw-r--r--src/app/libs/managesieve.ui/accounts/account.dialog.create.html (renamed from src/app/libs/managesieve.ui/accounts/account.dialog.create.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/accounts/account.settings.html (renamed from src/app/libs/managesieve.ui/accounts/account.settings.tpl)8
-rw-r--r--src/app/libs/managesieve.ui/accounts/accounts.html (renamed from src/app/libs/managesieve.ui/accounts/accounts.tpl)0
-rw-r--r--src/app/libs/managesieve.ui/importer/SieveImportUI.js94
-rw-r--r--src/app/libs/managesieve.ui/importer/SieveImportUI.mjs84
-rw-r--r--src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.js221
-rw-r--r--src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.mjs208
-rw-r--r--src/app/libs/managesieve.ui/importer/account.import.html (renamed from src/app/libs/managesieve.ui/importer/account.import.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/importer/account.import.item.html (renamed from src/app/libs/managesieve.ui/importer/account.import.item.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAccount.js45
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAccount.mjs21
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAccounts.js191
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAccounts.mjs180
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs182
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAuthenticationSettings.js247
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs112
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveAuthorizationSettings.js119
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveHost.mjs145
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveHostSettings.js155
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SievePrefManager.js87
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SievePrefManager.mjs79
-rw-r--r--src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs66
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.js364
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs357
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.js287
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.mjs281
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/settings.credentials.html (renamed from src/app/libs/managesieve.ui/settings/ui/settings.credentials.tpl)10
-rw-r--r--src/app/libs/managesieve.ui/settings/ui/settings.server.html (renamed from src/app/libs/managesieve.ui/settings/ui/settings.server.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/tabs/SieveTabsUI.js385
-rw-r--r--src/app/libs/managesieve.ui/tabs/SieveTabsUI.mjs377
-rw-r--r--src/app/libs/managesieve.ui/tabs/editor.content.html (renamed from src/app/libs/managesieve.ui/tabs/editor.content.tpl)0
-rw-r--r--src/app/libs/managesieve.ui/tabs/editor.tab.html (renamed from src/app/libs/managesieve.ui/tabs/editor.tab.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/updater/SieveUpdater.js178
-rw-r--r--src/app/libs/managesieve.ui/updater/SieveUpdater.mjs171
-rw-r--r--src/app/libs/managesieve.ui/updater/SieveUpdaterUI.js48
-rw-r--r--src/app/libs/managesieve.ui/updater/SieveUpdaterUI.mjs39
-rw-r--r--src/app/libs/managesieve.ui/updater/update.html (renamed from src/app/libs/managesieve.ui/updater/update.tpl)2
-rw-r--r--src/app/libs/managesieve.ui/utils/SieveIpcClient.js83
-rw-r--r--src/app/libs/managesieve.ui/utils/SieveIpcClient.mjs66
-rw-r--r--src/app/main_esm.js21
-rw-r--r--src/app/sieve.mjs185
-rwxr-xr-xsrc/app/utils/SievePasswordManager.js145
-rw-r--r--src/common/libManageSieve/SieveAbstractBase64.mjs96
-rwxr-xr-xsrc/common/libManageSieve/SieveAbstractClient.js695
-rw-r--r--src/common/libManageSieve/SieveAbstractClient.mjs1034
-rwxr-xr-xsrc/common/libManageSieve/SieveAbstractCrypto.js192
-rw-r--r--src/common/libManageSieve/SieveAbstractCrypto.mjs245
-rwxr-xr-xsrc/common/libManageSieve/SieveAbstractLogger.js283
-rwxr-xr-xsrc/common/libManageSieve/SieveAbstractRequestBuilder.js174
-rwxr-xr-xsrc/common/libManageSieve/SieveAbstractResponseParser.js463
-rw-r--r--src/common/libManageSieve/SieveAbstractSession.js785
-rw-r--r--src/common/libManageSieve/SieveAbstractSession.mjs841
-rw-r--r--src/common/libManageSieve/SieveAbstractTimer.mjs38
-rw-r--r--src/common/libManageSieve/SieveExceptions.js137
-rw-r--r--src/common/libManageSieve/SieveExceptions.mjs133
-rw-r--r--src/common/libManageSieve/SieveLogger.mjs288
-rwxr-xr-xsrc/common/libManageSieve/SieveRequest.js1446
-rw-r--r--src/common/libManageSieve/SieveRequest.mjs1482
-rw-r--r--src/common/libManageSieve/SieveRequestBuilder.mjs131
-rwxr-xr-xsrc/common/libManageSieve/SieveResponse.js1103
-rw-r--r--src/common/libManageSieve/SieveResponse.mjs1103
-rwxr-xr-xsrc/common/libManageSieve/SieveResponseCodes.js149
-rw-r--r--src/common/libManageSieve/SieveResponseCodes.mjs142
-rw-r--r--src/common/libManageSieve/SieveResponseParser.mjs425
-rw-r--r--src/common/libManageSieve/doc/HMAC/rfc2104.txt619
-rw-r--r--src/common/libManageSieve/doc/HMAC/rfc4086.txt2691
-rw-r--r--src/common/libManageSieve/doc/IMAP4 Referrals/rfc2221.txt284
-rw-r--r--src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-06.txt1237
-rw-r--r--src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-08.txt1359
-rw-r--r--src/common/libManageSieve/doc/Manage Sieve/rfc5804.txt2747
-rw-r--r--src/common/libManageSieve/doc/SASL CRAM/rfc2195.txt283
-rw-r--r--src/common/libManageSieve/doc/SASL LOGIN/draft-murchison-sasl-login-00.txt396
-rw-r--r--src/common/libManageSieve/doc/SASL PLAIN/rfc4616.txt619
-rw-r--r--src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc2898.txt1907
-rw-r--r--src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc6070.txt283
-rw-r--r--src/common/libManageSieve/doc/SASL SCRAM/draft-melnikov-scram-sha-512-01.txt392
-rw-r--r--src/common/libManageSieve/doc/SASL SCRAM/rfc5802.txt1571
-rw-r--r--src/common/libManageSieve/doc/SASL SCRAM/rfc7677.txt451
-rw-r--r--src/common/libManageSieve/doc/rfc6234.txt7115
-rw-r--r--src/common/libManageSieve/tests/SieveBase64Test.mjs273
-rw-r--r--src/common/libManageSieve/tests/SieveCryptoTest.mjs50
-rw-r--r--src/common/libManageSieve/tests/SieveSaslRequestTest.mjs456
-rwxr-xr-xsrc/common/libSieve/SieveGui.html8
-rw-r--r--src/common/libSieve/SieveGui.mjs30
-rw-r--r--src/common/libSieve/extensions/RFC5228/logic/SieveConditions.mjs2
-rw-r--r--src/common/libSieve/extensions/RFC5228/logic/SieveMatchTypes.mjs2
-rw-r--r--src/common/libSieve/extensions/RFC5228/logic/SieveNumbers.mjs4
-rw-r--r--src/common/libSieve/extensions/RFC5228/logic/SieveOperators.mjs4
-rw-r--r--src/common/libSieve/extensions/RFC5228/logic/SieveStrings.mjs4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveAddressTestUI.html8
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveAllOfAnyOfOperator.html4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveBooleanTest.html4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveDiscardActionUI.html2
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveEnvelopeTestUI.html8
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveExistsTestUI.html6
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveFileIntoActionUI.html4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveHeaderTestUI.html8
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveKeepActionUI.html2
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveRedirectActionUI.html4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveSizeTestUI.html4
-rw-r--r--src/common/libSieve/extensions/RFC5228/templates/SieveStopActionUI.html2
-rw-r--r--src/common/libSieve/extensions/RFC5228/widgets/SieveBlocksUI.mjs12
-rw-r--r--src/common/libSieve/extensions/RFC5228/widgets/SieveConditionsUI.mjs24
-rw-r--r--src/common/libSieve/extensions/RFC5228/widgets/SieveOperatorsUI.mjs14
-rw-r--r--src/common/libSieve/extensions/body/templates/SieveBodyTestUI.html8
-rw-r--r--src/common/libSieve/extensions/convert/templates/SieveConvertUI.html6
-rw-r--r--src/common/libSieve/extensions/date/templates/SieveCurrentDateTestUI.html10
-rw-r--r--src/common/libSieve/extensions/date/templates/SieveDateTestUI.html10
-rw-r--r--src/common/libSieve/extensions/duplicate/templates/SieveDuplicateTestUI.html8
-rw-r--r--src/common/libSieve/extensions/editheader/templates/SieveAddHeaderActionUI.html8
-rw-r--r--src/common/libSieve/extensions/editheader/templates/SieveDeleteHeaderActionUI.html16
-rw-r--r--src/common/libSieve/extensions/environment/templates/SieveEnvironmentUI.html8
-rw-r--r--src/common/libSieve/extensions/imapflags/templates/SieveAddFlagActionUI.html6
-rw-r--r--src/common/libSieve/extensions/imapflags/templates/SieveFlagsTag.html2
-rw-r--r--src/common/libSieve/extensions/imapflags/templates/SieveHasFlagTestUI.html8
-rw-r--r--src/common/libSieve/extensions/imapflags/templates/SieveRemoveFlagActionUI.html6
-rw-r--r--src/common/libSieve/extensions/imapflags/templates/SieveSetFlagActionUI.html6
-rw-r--r--src/common/libSieve/extensions/include/template/SieveGlobalActionUI.html4
-rw-r--r--src/common/libSieve/extensions/include/template/SieveIncludeActionUI.html6
-rw-r--r--src/common/libSieve/extensions/include/template/SieveReturnActionUI.html2
-rw-r--r--src/common/libSieve/extensions/mailbox/templates/SieveMailboxExistsTest.html4
-rw-r--r--src/common/libSieve/extensions/mailbox/templates/SieveMetaDataExistsTest.html4
-rw-r--r--src/common/libSieve/extensions/mailbox/templates/SieveMetaDataTest.html6
-rw-r--r--src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataExistsTest.html4
-rw-r--r--src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataTest.html6
-rw-r--r--src/common/libSieve/extensions/notify/templates/SieveNotifyActionUI.html8
-rw-r--r--src/common/libSieve/extensions/notify/templates/SieveNotifyMethodCapabilityTestUI.html10
-rw-r--r--src/common/libSieve/extensions/notify/templates/SieveValidNotifyMethodTestUI.html4
-rw-r--r--src/common/libSieve/extensions/pipe/templates/SieveExecuteUI.html4
-rw-r--r--src/common/libSieve/extensions/pipe/templates/SieveFilterUI.html4
-rw-r--r--src/common/libSieve/extensions/pipe/templates/SievePipeUI.html4
-rw-r--r--src/common/libSieve/extensions/reject/templates/SieveExtendedRejectActionUI.html4
-rw-r--r--src/common/libSieve/extensions/reject/templates/SieveRejectActionUI.html4
-rw-r--r--src/common/libSieve/extensions/spamtest/templates/SieveSpamtestPlusValue.html4
-rw-r--r--src/common/libSieve/extensions/spamtest/templates/SieveSpamtestUI.html6
-rw-r--r--src/common/libSieve/extensions/spamtest/templates/SieveSpamtestValue.html2
-rw-r--r--src/common/libSieve/extensions/spamtest/templates/SieveVirustestUI.html8
-rw-r--r--src/common/libSieve/extensions/spamtest/widgets/SieveSpamtestUI.mjs4
-rw-r--r--src/common/libSieve/extensions/vacation-seconds/template/SieveVacationIntervalSecondsUI.html2
-rw-r--r--src/common/libSieve/extensions/vacation/template/SieveVacationIntervalDaysUI.html2
-rw-r--r--src/common/libSieve/extensions/vacation/template/SieveVacationUI.html14
-rw-r--r--src/common/libSieve/extensions/vacation/widgets/SieveVacationUI.mjs4
-rw-r--r--src/common/libSieve/extensions/variables/templates/SieveSetActionUI.html6
-rw-r--r--src/common/libSieve/extensions/variables/templates/SieveStringTestUI.html6
-rw-r--r--src/common/libSieve/extensions/variables/widgets/SieveVariablesUI.mjs4
-rw-r--r--src/common/libSieve/templates/debug.html6
-rw-r--r--src/common/libSieve/templates/sidebar.html6
-rw-r--r--src/common/libSieve/toolkit/SieveParser.mjs4
-rw-r--r--src/common/libSieve/toolkit/SieveScriptDOM.mjs4
-rw-r--r--src/common/libSieve/toolkit/events/DataTransfer.mjs4
-rw-r--r--src/common/libSieve/toolkit/logic/GenericAtoms.mjs6
-rw-r--r--src/common/libSieve/toolkit/logic/GenericElements.mjs1
-rwxr-xr-xsrc/common/libSieve/toolkit/style/layout.css24
-rwxr-xr-xsrc/common/libSieve/toolkit/style/style.css115
-rw-r--r--src/common/libSieve/toolkit/templates/SieveDropDownWidget.html4
-rw-r--r--src/common/libSieve/toolkit/templates/SieveNumericWidget.html4
-rw-r--r--src/common/libSieve/toolkit/templates/SieveStringListWidget.html2
-rw-r--r--src/common/libSieve/toolkit/templates/SieveStringWidget.html2
-rw-r--r--src/common/libSieve/toolkit/utils/SieveI18n.mjs4
-rw-r--r--src/common/libSieve/toolkit/widgets/Boxes.mjs36
-rw-r--r--src/common/libSieve/toolkit/widgets/Widgets.mjs38
-rw-r--r--src/common/managesieve.ui/accounts/SieveAbstractAccountUI.mjs333
-rw-r--r--src/common/managesieve.ui/accounts/SieveAbstractAccounts.js60
-rw-r--r--src/common/managesieve.ui/accounts/SieveAbstractAccounts.mjs51
-rw-r--r--src/common/managesieve.ui/accounts/SieveAccountUI.js412
-rw-r--r--src/common/managesieve.ui/accounts/SieveCapabilities.js71
-rw-r--r--src/common/managesieve.ui/accounts/SieveCapabilities.mjs62
-rw-r--r--src/common/managesieve.ui/accounts/SieveScriptUI.html (renamed from src/common/managesieve.ui/accounts/SieveScriptUI.tpl)12
-rw-r--r--src/common/managesieve.ui/accounts/SieveScriptUI.js156
-rw-r--r--src/common/managesieve.ui/accounts/SieveScriptUI.mjs146
-rw-r--r--src/common/managesieve.ui/accounts/account.capabilities.html (renamed from src/common/managesieve.ui/accounts/account.capabilities.tpl)2
-rw-r--r--src/common/managesieve.ui/accounts/account.connecting.html (renamed from src/common/managesieve.ui/accounts/account.connecting.tpl)0
-rw-r--r--src/common/managesieve.ui/accounts/account.disconnected.html (renamed from src/common/managesieve.ui/accounts/account.disconnected.tpl)2
-rw-r--r--src/common/managesieve.ui/accounts/account.disconnecting.html (renamed from src/common/managesieve.ui/accounts/account.disconnecting.tpl)0
-rw-r--r--src/common/managesieve.ui/accounts/account.empty.html2
-rw-r--r--src/common/managesieve.ui/accounts/account.html (renamed from src/common/managesieve.ui/accounts/account.tpl)12
-rw-r--r--src/common/managesieve.ui/dialogs/SieveDialogUI.js706
-rw-r--r--src/common/managesieve.ui/dialogs/SieveDialogUI.mjs692
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.account.authorization.html (renamed from src/common/managesieve.ui/dialogs/dialog.account.authorization.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.account.cert.html (renamed from src/common/managesieve.ui/dialogs/dialog.account.cert.tpl)17
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.account.delete.html (renamed from src/common/managesieve.ui/dialogs/dialog.account.delete.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.account.password.html (renamed from src/common/managesieve.ui/dialogs/dialog.account.password.tpl)4
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.error.html (renamed from src/common/managesieve.ui/dialogs/dialog.error.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.script.busy.html (renamed from src/common/managesieve.ui/dialogs/dialog.script.busy.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.script.create.html (renamed from src/common/managesieve.ui/dialogs/dialog.script.create.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.script.delete.html (renamed from src/common/managesieve.ui/dialogs/dialog.script.delete.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.script.rename.html (renamed from src/common/managesieve.ui/dialogs/dialog.script.rename.tpl)2
-rw-r--r--src/common/managesieve.ui/dialogs/dialog.script.save.html (renamed from src/common/managesieve.ui/dialogs/dialog.script.save.tpl)2
-rw-r--r--src/common/managesieve.ui/editor.html20
-rw-r--r--src/common/managesieve.ui/editor.js90
-rw-r--r--src/common/managesieve.ui/editor.mjs88
-rw-r--r--src/common/managesieve.ui/editor/SieveAbstractEditor.js134
-rw-r--r--src/common/managesieve.ui/editor/SieveAbstractEditor.mjs124
-rw-r--r--src/common/managesieve.ui/editor/SieveEditor.js467
-rw-r--r--src/common/managesieve.ui/editor/SieveEditor.mjs459
-rw-r--r--src/common/managesieve.ui/editor/SieveEditorController.js195
-rw-r--r--src/common/managesieve.ui/editor/SieveEditorController.mjs186
-rw-r--r--src/common/managesieve.ui/editor/editor.error.save.html2
-rw-r--r--src/common/managesieve.ui/editor/editor.html (renamed from src/common/managesieve.ui/editor/editor.tpl)14
-rw-r--r--src/common/managesieve.ui/editor/editor.settings.defaults.html (renamed from src/common/managesieve.ui/editor/editor.settings.defaults.tpl)2
-rw-r--r--src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.js69
-rw-r--r--src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.mjs60
-rw-r--r--src/common/managesieve.ui/editor/text/SieveTextEditor.js760
-rw-r--r--src/common/managesieve.ui/editor/text/SieveTextEditor.mjs752
-rw-r--r--src/common/managesieve.ui/editor/text/editor.plaintext.toolbar.html8
-rw-r--r--src/common/managesieve.ui/editor/text/editor.settings.indentation.html (renamed from src/common/managesieve.ui/editor/text/editor.settings.indentation.tpl)0
-rw-r--r--src/common/managesieve.ui/editor/text/editor.settings.syntax.html (renamed from src/common/managesieve.ui/editor/text/editor.settings.syntax.tpl)0
-rw-r--r--src/common/managesieve.ui/i18n/en-US.json18
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAccount.js187
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAccount.mjs154
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.js125
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.mjs114
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.js79
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.mjs55
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.js136
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.mjs121
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractHost.js193
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractHost.mjs107
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.js134
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.mjs125
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.js245
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.mjs234
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs52
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAccountSettings.js67
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveAccountSettings.mjs57
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveEditorSettings.js82
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveEditorSettings.mjs73
-rw-r--r--src/common/managesieve.ui/settings/logic/SieveSecuritySettings.js87
-rw-r--r--src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.js237
-rw-r--r--src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.mjs262
-rw-r--r--src/common/managesieve.ui/settings/ui/settings.debug.html (renamed from src/common/managesieve.ui/settings/ui/settings.debug.tpl)17
-rw-r--r--src/common/managesieve.ui/utils/SieveAbstractIpcClient.js239
-rw-r--r--src/common/managesieve.ui/utils/SieveAbstractIpcClient.mjs229
-rw-r--r--src/common/managesieve.ui/utils/SieveFakeRequire.js126
-rw-r--r--src/common/managesieve.ui/utils/SieveI18n.js205
-rw-r--r--src/common/managesieve.ui/utils/SieveI18n.mjs196
-rw-r--r--src/common/managesieve.ui/utils/SieveLogger.js227
-rw-r--r--src/common/managesieve.ui/utils/SieveLogger.mjs226
-rw-r--r--src/common/managesieve.ui/utils/SieveTemplate.js118
-rw-r--r--src/common/managesieve.ui/utils/SieveTemplate.mjs108
-rw-r--r--src/common/managesieve.ui/utils/SieveUniqueId.js43
-rw-r--r--src/common/managesieve.ui/utils/SieveUniqueId.mjs33
-rw-r--r--src/wx/api/sieve/SieveAccountsApi.js2
-rw-r--r--src/wx/api/sieve/SieveAccountsApi.json18
-rw-r--r--src/wx/api/sieve/SieveMenuApi.js14
-rw-r--r--src/wx/api/sieve/SieveMenuApi.json58
-rw-r--r--src/wx/api/sieve/SieveSessionApi.js310
-rw-r--r--src/wx/api/sieve/SieveSessionApi.json390
-rw-r--r--src/wx/api/sieve/SieveSocketApi.js1030
-rw-r--r--src/wx/api/sieve/SieveSocketApi.json213
-rw-r--r--src/wx/background.html28
-rw-r--r--src/wx/background.mjs (renamed from src/wx/background.js)141
-rw-r--r--src/wx/libs/libManageSieve/SieveBase64.mjs356
-rw-r--r--src/wx/libs/libManageSieve/SieveClient.js539
-rw-r--r--src/wx/libs/libManageSieve/SieveClient.mjs171
-rw-r--r--src/wx/libs/libManageSieve/SieveCrypto.js126
-rw-r--r--src/wx/libs/libManageSieve/SieveCrypto.mjs141
-rw-r--r--src/wx/libs/libManageSieve/SieveLogger.js44
-rw-r--r--src/wx/libs/libManageSieve/SieveRequestBuilder.js70
-rw-r--r--src/wx/libs/libManageSieve/SieveResponseParser.js49
-rw-r--r--src/wx/libs/libManageSieve/SieveSession.mjs80
-rw-r--r--src/wx/libs/libManageSieve/SieveTimer.mjs45
-rw-r--r--src/wx/libs/managesieve.ui/accounts.html22
-rw-r--r--src/wx/libs/managesieve.ui/accounts.js123
-rw-r--r--src/wx/libs/managesieve.ui/accounts.mjs137
-rw-r--r--src/wx/libs/managesieve.ui/accounts/SieveAccountUI.mjs21
-rw-r--r--src/wx/libs/managesieve.ui/accounts/SieveAccounts.js30
-rw-r--r--src/wx/libs/managesieve.ui/accounts/SieveAccounts.mjs22
-rw-r--r--src/wx/libs/managesieve.ui/accounts/account.settings.html (renamed from src/wx/libs/managesieve.ui/accounts/account.settings.tpl)6
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.js61
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.mjs50
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.js96
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs37
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.js75
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs64
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveHost.js100
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveHost.mjs50
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.js58
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.mjs49
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.js44
-rw-r--r--src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.mjs (renamed from src/wx/libs/libManageSieve/SieveSession.js)8
-rw-r--r--src/wx/libs/managesieve.ui/utils/SieveIpcClient.js70
-rw-r--r--src/wx/manifest.json8
-rw-r--r--src/wx/modules/SieveRequire.jsm276
311 files changed, 40727 insertions, 18214 deletions
diff --git a/src/app/app.html b/src/app/app.html
index c591c5ee..a9d87f34 100755
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -24,7 +24,7 @@
<div id="tabs-scroll-box">
<ul id="tabs-items" class="nav nav-tabs list" role="tablist">
<li id="accounts-tab" class="nav-item">
- <a class="nav-link active" data-toggle="tab" href="#accounts" role="tab">Home</a>
+ <a class="nav-link active" data-bs-toggle="tab" href="#accounts" role="tab">Home</a>
</li>
</ul>
</div>
@@ -43,7 +43,7 @@
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="./libs/bootstrap/js/bootstrap.bundle.min.js"></script>
- <script src="./app.js"></script>
+ <script type="module" src="./app.mjs"></script>
</body>
</html> \ No newline at end of file
diff --git a/src/app/app.js b/src/app/app.mjs
index e479edeb..2178af18 100755..100644
--- a/src/app/app.js
+++ b/src/app/app.mjs
@@ -9,35 +9,33 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/
-(async function () {
-
- const DEFAULT_AUTHENTICATION = 0;
- const DEFAULT_AUTHORIZATION = 3;
+const DEFAULT_AUTHORIZATION = 3;
- const FIRST_ELEMENT = 0;
+const FIRST_ELEMENT = 0;
- const { ipcRenderer } = require('electron');
+const { ipcRenderer } = require('electron');
- // Import the node modules into our global namespace...
- const { SieveLogger } = require("./libs/managesieve.ui/utils/SieveLogger.js");
- const { SieveIpcClient} = require("./libs/managesieve.ui/utils/SieveIpcClient.js");
+// Import the node modules into our global namespace...
+import { SieveLogger } from "./libs/managesieve.ui/utils/SieveLogger.mjs";
+import { SieveIpcClient } from "./libs/managesieve.ui/utils/SieveIpcClient.mjs";
- const {
- SieveCertValidationException
- } = require("./libs/libManageSieve/SieveExceptions.js");
+import {
+ SieveCertValidationException
+} from "./libs/libManageSieve/SieveExceptions.mjs";
- const { SieveSessions } = require("./libs/libManageSieve/SieveSessions.js");
+import { SieveSessions } from "./libs/libManageSieve/SieveSessions.mjs";
- const { SieveAccounts } = require("./libs/managesieve.ui/settings/logic/SieveAccounts.js");
+import { SieveAccounts } from "./libs/managesieve.ui/settings/logic/SieveAccounts.mjs";
- const { SieveUpdater } = require("./libs/managesieve.ui/updater/SieveUpdater.js");
- const { SieveTabUI } = require("./libs/managesieve.ui/tabs/SieveTabsUI.js");
+import { SieveUpdater } from "./libs/managesieve.ui/updater/SieveUpdater.mjs";
+import { SieveTabUI } from "./libs/managesieve.ui/tabs/SieveTabsUI.mjs";
- const { SieveThunderbirdImport } = require("./libs/managesieve.ui/importer/SieveThunderbirdImport.js");
- const { SieveAutoConfig } = require("./libs/libManageSieve/SieveAutoConfig.js");
+import { SieveThunderbirdImport } from "./libs/managesieve.ui/importer/SieveThunderbirdImport.mjs";
+import { SieveAutoConfig } from "./libs/libManageSieve/SieveAutoConfig.mjs";
- const { SieveI18n } = require("./libs/managesieve.ui/utils/SieveI18n.js");
+import { SieveI18n } from "./libs/managesieve.ui/utils/SieveI18n.mjs";
+(async function () {
const logger = SieveLogger.getInstance();
// TODO remove me this file should not have any dependency to i18n
@@ -47,6 +45,13 @@
const accounts = await (new SieveAccounts().load());
const sessions = new SieveSessions();
+ let keytar = null;
+ try {
+ keytar = require("./libs/keytar");
+ } catch (ex) {
+ logger.log("Could not initialize keytar " + ex);
+ }
+
const actions = {
"update-check": async () => {
@@ -146,7 +151,7 @@
};
},
- "settings-get-loglevel": async function() {
+ "settings-get-loglevel": async function () {
return await accounts.getLogLevel();
},
@@ -167,8 +172,8 @@
const account = accounts.getAccountById(msg.payload.account);
return {
- "account" : await account.getSettings().getLogLevel(),
- "global" : await accounts.getLogLevel()
+ "account": await account.getSettings().getLogLevel(),
+ "global": await accounts.getLogLevel()
};
},
@@ -184,9 +189,9 @@
sasl: await account.getSecurity().getMechanism()
},
"authentication": {
- type: await (await account.getAuthentication()).getType(),
- username: await (await account.getAuthentication(DEFAULT_AUTHENTICATION)).getUsername(),
- stored: await (await account.getAuthentication(DEFAULT_AUTHENTICATION)).hasStoredPassword()
+
+ username: await (await account.getAuthentication()).getUsername(),
+ stored: await (await account.getAuthentication()).hasStoredPassword()
},
"authorization": {
@@ -196,11 +201,11 @@
};
},
- "account-settings-forget-credentials": async function(msg) {
+ "account-settings-forget-credentials": async function (msg) {
logger.logAction(`Forget credentials for ${msg.payload.account}`);
const account = await accounts.getAccountById(msg.payload.account);
- await (await account.getAuthentication(DEFAULT_AUTHENTICATION)).forget();
+ await (await account.getAuthentication()).forget();
},
"account-settings-set-credentials": async function (msg) {
@@ -212,8 +217,7 @@
await account.getSecurity().setSecure(msg.payload.general.secure);
await account.getSecurity().setMechanism(msg.payload.general.sasl);
- await account.setAuthentication(msg.payload.authentication.mechanism);
- await (await account.getAuthentication(DEFAULT_AUTHENTICATION)).setUsername(msg.payload.authentication.username);
+ await account.getAuthentication().setUsername(msg.payload.authentication.username);
await account.setAuthorization(msg.payload.authorization.mechanism);
await (await account.getAuthorization(DEFAULT_AUTHORIZATION)).setAuthorization(msg.payload.authorization.username);
@@ -234,7 +238,7 @@
await host.setKeepAlive(msg.payload.keepAlive);
},
- "account-import" : async function() {
+ "account-import": async function () {
logger.logAction("Import account settings");
const options = {
@@ -261,7 +265,7 @@
await accounts.import(data);
},
- "account-export" : async function(msg) {
+ "account-export": async function (msg) {
logger.logAction("Export account settings");
const host = await accounts.getAccountById(msg.payload.account).getHost();
@@ -308,6 +312,11 @@
} catch (e) {
+ // As first step we disconnect. Our connection sequence failed.
+ // So ensure the connection is closed. Anyhow we have no chance to recover.
+
+ await (actions["account-disconnect"](response));
+
if (e instanceof SieveCertValidationException) {
const secInfo = e.getSecurityInfo();
@@ -320,21 +329,24 @@
const host = await accounts.getAccountById(account).getHost();
- await host.setFingerprint(secInfo.fingerprint);
+ // Prefer SHA256 if available
+ if ((typeof (secInfo.fingerprint256) !== "undefined") && (secInfo.fingerprint256 !== null))
+ await host.setFingerprint(secInfo.fingerprint256);
+ else
+ await host.setFingerprint(secInfo.fingerprint);
+
await host.setIgnoreCertErrors(secInfo.code);
- await actions["account-connecting"](response);
+ await actions["account-connect"](response);
return;
}
// connecting failed for some reason, which means we
// need to handle the error.
- console.error(e);
+ logger.logAction("Connecting failed due to an error " + e);
await SieveIpcClient.sendMessage(
"accounts", "account-show-error", e.message);
-
- throw e;
}
},
@@ -559,7 +571,7 @@
return value;
},
- "get-default-preference": async(msg) => {
+ "get-default-preference": async (msg) => {
const name = msg.payload.data;
logger.logAction(`Get default value for ${name}`);
@@ -577,13 +589,37 @@
await accounts.getAccountById(account).getEditor().setValue(name, value);
},
- "set-default-preference": async(msg) => {
+ "set-default-preference": async (msg) => {
const name = msg.payload.key;
const value = msg.payload.value;
logger.logAction(`Set default value for ${name}`);
await accounts.getEditor().setValue(name, value);
+ },
+
+ "open-developer-tools": async() => {
+ await ipcRenderer.invoke("open-developer-tools");
+ },
+
+ "reload-ui" : async() => {
+ await ipcRenderer.invoke("reload-ui");
+ },
+
+ "keystore-ready" : async() => {
+ return ((typeof(keytar) !== undefined) && (keytar !== null));
+ },
+
+ "keystore-forget" : async(msg) => {
+ await keytar.deletePassword("Sieve Editor", msg.payload.username);
+ },
+
+ "keystore-store" : async(msg) => {
+ await keytar.setPassword("Sieve Editor", msg.payload.username, msg.payload.password);
+ },
+
+ "keystore-get" : async(msg) => {
+ return await keytar.getPassword("Sieve Editor", msg.payload.username);
}
};
diff --git a/src/app/css/navbar-top-fixed.css b/src/app/css/navbar-top-fixed.css
index ddc05eb7..c69a3077 100755
--- a/src/app/css/navbar-top-fixed.css
+++ b/src/app/css/navbar-top-fixed.css
@@ -3,10 +3,8 @@ html,
body {
height: 100%;
overflow: hidden;
- height: 100%;
}
-
#ctx {
display: inline-flex;
height: 100%;
@@ -14,7 +12,7 @@ body {
flex-direction: column;
}
-#tabs-content>* {
+#tabs-content > * {
height: 100%;
width: 100%;
border: 0;
@@ -28,36 +26,36 @@ body {
#tabs-container {
height: 50px;
- padding: 0px;
+ padding: 0;
}
#tabs-scroll-left {
position: absolute;
- bottom: 0px;
- left: 0px;
+ bottom: 0;
+ left: 0;
width: 20px;
- padding-top: .5rem;
- padding-bottom: .5rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
cursor: pointer;
}
#tabs-scroll-right {
position: absolute;
- bottom:0px;
- right: 0px;
+ bottom: 0;
+ right: 0;
width: 20px;
- padding-top: .5rem;
- padding-bottom: .5rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
cursor: pointer;
}
#tabs-scroll-box {
position: absolute;
- bottom: 0px;
- left:20px;
- right:20px;
+ bottom: 0;
+ left: 20px;
+ right: 20px;
overflow: hidden;
white-space: nowrap;
@@ -70,5 +68,5 @@ body {
}
#tabs-items > li {
- display:inline-block;
+ display: inline-block;
}
diff --git a/src/app/index.js b/src/app/index.js
deleted file mode 100755
index 2fcd0534..00000000
--- a/src/app/index.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function () {
-
- const { app, Menu, BrowserWindow, ipcMain, dialog } = require('electron');
- const path = require('path');
- const url = require('url');
-
- const DEFAULT_WINDOW_WIDTH = 1200;
- const DEFAULT_WINDOW_HEIGHT = 600;
-
- // Keep a global reference of the window object, if you don't, the window will
- // be closed automatically when the JavaScript object is garbage collected.
- let win;
-
- // ensure we are running as a singleton.
- const isLocked = app.requestSingleInstanceLock();
-
- if (!isLocked) {
- app.quit();
- return;
- }
-
- app.on('second-instance', () => {
- // Someone tried to run a second instance, we should focus our window.
- if (!win)
- return;
-
- if (win.isMinimized());
- win.restore();
-
- win.focus();
- });
-
- ipcMain.handle("open-dialog", async(event, options) => {
- return await dialog.showOpenDialog(options);
- });
-
- ipcMain.handle("save-dialog", async(event, options) => {
- return await dialog.showSaveDialog(options);
- });
-
- ipcMain.handle("get-version", async() => {
- return await app.getVersion();
- });
-
- /**
- * Creates the main window
- */
- function createWindow() {
-
- let icon = undefined;
- if (process.platform === "linux")
- icon = path.join(__dirname, 'libs/icons/linux.png');
-
- // Create the browser window.
- win = new BrowserWindow({
- width: DEFAULT_WINDOW_WIDTH,
- height: DEFAULT_WINDOW_HEIGHT,
- icon: icon,
- webPreferences: {
- // nodeIntegrationInSubFrames: true,
- nodeIntegration: true
- }
- });
-
- // Hide the menu bar.
- win.removeMenu();
-
- // and load the index.html of the app.
- win.loadURL(url.format({
- pathname: path.join(__dirname, 'app.html'),
- protocol: 'file:',
- slashes: true
- }));
-
- // Open the DevTools.
- // win.webContents.openDevTools();
-
- // Emitted when the window is closed.
- win.on('closed', () => {
- // Dereference the window object, usually you would store windows
- // in an array if your app supports multi windows, this is the time
- // when you should delete the corresponding element.
- win = null;
- });
-
- // As suggested in https://github.com/electron/electron/issues/4068
- const inputMenu = Menu.buildFromTemplate([
- { role: 'cut' },
- { role: 'copy' },
- { role: 'paste' }
- ]);
-
- win.webContents.on('context-menu', (e, props) => {
- const { isEditable } = props;
- if (isEditable) {
- inputMenu.popup(win);
- }
- });
-
- const handleRedirect = (e, uri) => {
- if (uri !== win.webContents.getURL()) {
- e.preventDefault();
- require('electron').shell.openExternal(uri);
- }
- };
-
- // win.webContents.on('will-navigate', handleRedirect);
- win.webContents.on('new-window', handleRedirect);
- }
-
- // This method will be called when Electron has finished
- // initialization and is ready to create browser windows.
- // Some APIs can only be used after this event occurs.
- app.on('ready', createWindow);
-
- // Quit when all windows are closed.
- app.on('window-all-closed', () => {
- // On macOS it is common for applications and their menu bar
- // to stay active until the user quits explicitly with Cmd + Q
- if (process.platform !== 'darwin') {
- app.quit();
- }
- });
-
- app.on('activate', () => {
- // On macOS it's common to re-create a window in the app when the
- // dock icon is clicked and there are no other windows open.
- if (win === null) {
- createWindow();
- }
- });
-
- // In this file you can include the rest of your app's specific main process
- // code. You can also put them in separate files and require them here.
-
-})();
diff --git a/src/app/libs/libManageSieve/SieveAutoConfig.js b/src/app/libs/libManageSieve/SieveAutoConfig.js
deleted file mode 100755
index 2a693ec5..00000000
--- a/src/app/libs/libManageSieve/SieveAutoConfig.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
-* The content of this file is licensed. You may obtain a copy of
-* the license at https://github.com/thsmi/sieve/ or request it via
-* email from the author.
-*
-* Do not remove or change this comment.
-*
-* The initial author of the code is:
-* Thomas Schmid <schmid-thomas@gmx.net>
-*/
-
-
-(function (exports) {
-
- "use strict";
-
- const SIEVE_PORT_NEW = 4190;
- const SIEVE_PORT_OLD = 2000;
-
- const LOG_LEVEL = 255;
-
- const { SieveLogger } = require("./SieveLogger.js");
- const { Sieve } = require("./SieveClient.js");
- const { SieveInitRequest } = require("./SieveRequest.js");
-
- /**
- * Tries to detect the correct sieve port.
- * It is typically either 2000 or 4190.
- */
- class SieveAutoConfig {
-
- /**
- * Creates a new auto config instance. It tries to automagically detect
- * the correct sieve settings.
- *
- * @param {string} hostname
- * the hostname or ip which should be tested
- */
- constructor(hostname) {
- this.hostname = hostname;
- this.logger = new SieveLogger();
- this.logger.level(LOG_LEVEL);
- }
-
- /**
- * Auto detects the sieve port for all well known sieve ports.
- * In case auto detect fails an exception is thrown.
- *
- * @returns {int}
- * the sieve port in case auto detect succeeds.
- */
- async detect() {
-
- for (const port of [SIEVE_PORT_NEW, SIEVE_PORT_OLD])
- if (await this.probe(port))
- return port;
-
- throw new Error("Could not detect the sieve port");
- }
-
- /**
- * Tries a handshake on the given port.
- * @param {int} port
- * the tcp port which should be challenged
- * @returns {boolean}
- * true in case it is a manage sieve port otherwise false.
- */
- async probe(port) {
-
- const sieve = new Sieve(this.logger);
-
- return await new Promise((resolve) => {
-
- const listener = {
-
- onInitResponse: function () {
- resolve(true);
- sieve.disconnect();
- },
-
- onError: function () {
- resolve(false);
- sieve.disconnect();
- },
-
- onTimeout: function () {
- resolve(false);
- sieve.disconnect();
- },
-
- onDisconnect: function () {
- // we are already disconnected....
- resolve(false);
- }
- };
-
- const request = new SieveInitRequest();
- request.addErrorListener(listener.onError);
- request.addResponseListener(listener.onInitResponse);
- sieve.addRequest(request);
-
- sieve.addListener(listener);
-
- sieve.connect(this.hostname, port, false);
- });
- }
- }
-
- exports.SieveAutoConfig = SieveAutoConfig;
-
-})(exports || this);
diff --git a/src/app/libs/libManageSieve/SieveAutoConfig.mjs b/src/app/libs/libManageSieve/SieveAutoConfig.mjs
new file mode 100644
index 00000000..de647ef9
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveAutoConfig.mjs
@@ -0,0 +1,106 @@
+/*
+* The content of this file is licensed. You may obtain a copy of
+* the license at https://github.com/thsmi/sieve/ or request it via
+* email from the author.
+*
+* Do not remove or change this comment.
+*
+* The initial author of the code is:
+* Thomas Schmid <schmid-thomas@gmx.net>
+*/
+
+
+const SIEVE_PORT_NEW = 4190;
+const SIEVE_PORT_OLD = 2000;
+
+const LOG_LEVEL = 255;
+
+import { SieveLogger } from "./SieveLogger.mjs";
+import { Sieve } from "./SieveClient.mjs";
+import { SieveInitRequest } from "./SieveRequest.mjs";
+
+/**
+ * Tries to detect the correct sieve port.
+ * It is typically either 2000 or 4190.
+ */
+class SieveAutoConfig {
+
+ /**
+ * Creates a new auto config instance. It tries to automagically detect
+ * the correct sieve settings.
+ *
+ * @param {string} hostname
+ * the hostname or ip which should be tested
+ */
+ constructor(hostname) {
+ this.hostname = hostname;
+ this.logger = new SieveLogger();
+ this.logger.level(LOG_LEVEL);
+ }
+
+ /**
+ * Auto detects the sieve port for all well known sieve ports.
+ * In case auto detect fails an exception is thrown.
+ *
+ * @returns {int}
+ * the sieve port in case auto detect succeeds.
+ */
+ async detect() {
+
+ for (const port of [SIEVE_PORT_NEW, SIEVE_PORT_OLD])
+ if (await this.probe(port))
+ return port;
+
+ throw new Error("Could not detect the sieve port");
+ }
+
+ /**
+ * Tries a handshake on the given port.
+ * @param {int} port
+ * the tcp port which should be challenged
+ * @returns {boolean}
+ * true in case it is a manage sieve port otherwise false.
+ */
+ async probe(port) {
+
+ const sieve = new Sieve(this.logger);
+
+ // eslint-disable-next-line no-async-promise-executor
+ return await new Promise(async (resolve) => {
+
+ const listener = {
+
+ onInitResponse: function () {
+ resolve(true);
+ sieve.disconnect();
+ },
+
+ onError: function () {
+ resolve(false);
+ sieve.disconnect();
+ },
+
+ onTimeout: function () {
+ resolve(false);
+ sieve.disconnect();
+ },
+
+ onDisconnected: function () {
+ // we are already disconnected....
+ resolve(false);
+ }
+ };
+
+ const request = new SieveInitRequest();
+ request.addErrorListener(listener.onError);
+ request.addResponseListener(listener.onInitResponse);
+ await sieve.addRequest(request);
+
+ sieve.addListener(listener);
+
+ sieve.connect(this.hostname, port, false);
+ });
+ }
+}
+
+export { SieveAutoConfig };
diff --git a/src/app/libs/libManageSieve/SieveBase64.mjs b/src/app/libs/libManageSieve/SieveBase64.mjs
new file mode 100644
index 00000000..7a275f8c
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveBase64.mjs
@@ -0,0 +1,55 @@
+/*
+* The content of this file is licensed. You may obtain a copy of
+* the license at https://github.com/thsmi/sieve/ or request it via
+* email from the author.
+*
+* Do not remove or change this comment.
+*
+* The initial author of the code is:
+* Thomas Schmid <schmid-thomas@gmx.net>
+*/
+
+import {
+ SieveAbstractBase64Decoder,
+ SieveAbstractBase64Encoder
+} from "./SieveAbstractBase64.mjs";
+
+/**
+ *
+ */
+class SieveNodeBase64Encoder extends SieveAbstractBase64Encoder {
+
+ /**
+ * @inheritdoc
+ */
+ toUtf8() {
+ return Buffer.from(this.decoded).toString('base64');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ toArray() {
+ return (new TextEncoder()).encode(this.toUtf8());
+ }
+}
+
+/**
+ * Node implements a native base64 decoder which supports UTF-8
+ * This simplifies decoding dramatically.
+ */
+class SieveNodeBase64Decoder extends SieveAbstractBase64Decoder {
+
+ /**
+ * @inheritdoc
+ */
+ toArray() {
+ return new Uint8Array(Buffer.from(this.encoded, 'base64'));
+ }
+
+}
+
+export {
+ SieveNodeBase64Decoder as SieveBase64Decoder,
+ SieveNodeBase64Encoder as SieveBase64Encoder
+};
diff --git a/src/app/libs/libManageSieve/SieveClient.js b/src/app/libs/libManageSieve/SieveClient.js
deleted file mode 100644
index df0140cd..00000000
--- a/src/app/libs/libManageSieve/SieveClient.js
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractClient } = require("./SieveAbstractClient.js");
- const { SieveNodeResponseParser } = require("./SieveNodeResponseParser.js");
- const { SieveNodeRequestBuilder } = require("./SieveNodeRequestBuilder.js");
-
- const { SieveCertValidationException } = require("./SieveExceptions.js");
-
- const net = require('net');
- const tls = require('tls');
- const timers = require('timers');
-
- const NOT_FOUND = -1;
-
- /**
- * Uses Node networking to realize a sieve client.
- */
- class SieveNodeClient extends SieveAbstractClient {
-
-
- /**
- * Creates a new instance
- * @param {AbstractLogger} logger
- * the logger instance to use
- **/
- constructor(logger) {
- super();
-
- this.tlsSocket = null;
- this._logger = logger;
- this.secure = true;
- }
-
- /**
- * @inheritdoc
- */
- isSecure() {
- return this.secure;
- }
-
-
- /**
- * @inheritdoc
- */
- onStartTimeout() {
-
- // Clear any existing timeouts
- if (this.timeoutTimer) {
- timers.clearTimeout(this.timeoutTimer);
- this.timeoutTimer = null;
- }
-
- // ensure the idle timer is stopped
- this.onStopIdle();
-
- // then restart the timeout timer
- this.timeoutTimer = timers.setTimeout(
- () => { this.onTimeout(); },
- this.getTimeoutWait());
- }
-
- /**
- * @inheritdoc
- */
- onStopTimeout() {
-
- // clear any existing timeout
- if (this.timeoutTimer) {
- timers.clearTimeout(this.timeoutTimer);
- this.timeoutTimer = null;
- }
-
- // and start the idle timer.
- this.onStartIdle();
-
- return;
- }
-
- /**
- * @inheritdoc
- */
- onStartIdle() {
- // first ensure the timer is stopped..
- this.onStopIdle();
-
- // ... then configure the timer.
- const delay = this.getIdleWait();
-
- if (!delay)
- return;
-
- this.idleTimer
- = timers.setTimeout(async () => { await this.onIdle(); }, delay);
- }
-
- /**
- * @inheritdoc
- */
- onStopIdle() {
-
- if (!this.idleTimer)
- return;
-
- timers.clearTimeout(this.idleTimer);
- this.idleTimer = null;
- }
-
- /**
- * @inheritdoc
- */
- createParser(data) {
- return new SieveNodeResponseParser(data);
- }
-
- /**
- * @inheritdoc
- */
- createRequestBuilder() {
- return new SieveNodeRequestBuilder();
- }
-
- /**
- * @inheritdoc
- */
- getLogger() {
- return this._logger;
- }
-
- /**
- * Connects to a ManageSieve server.
- * @param {string} host
- * The target hostname or IP address as String
- * @param {int} port
- * The target port as integer
- * @param {boolean} secure
- * If true, a secure socket will be created. This allows switching to a secure
- * connection.
- *
- * @returns {SieveAbstractClient}
- * a self reference
- */
- connect(host, port, secure) {
-
- if (this.socket !== null)
- return this;
-
- this.host = host;
- this.port = port;
- this.secure = secure;
-
- this.socket = net.connect(this.port, this.host);
-
- this.socket.on('data', (data) => { this.onReceive(data); });
- this.socket.on('error', (error) => {
- // Node guarantees that close is called after error.
- if ((this.listener) && (this.listener.onError))
- (async () => { await this.listener.onError(error); })();
- });
- this.socket.on('close', () => {
- this.disconnect();
-
- if ((this.listener) && (this.listener.onDisconnect))
- (async () => { await this.listener.onDisconnect(); })();
- });
-
- return this;
- }
-
- /**
- * @inheritdoc
- */
- async startTLS(options) {
-
- if (options === undefined || options === null)
- options = {};
-
- if (options.fingerprints === undefined || options.fingerprints === null || options.fingerprints === "")
- options.fingerprints = [];
-
- if (Array.isArray(options.fingerprints) === false)
- options.fingerprints = [options.fingerprints];
-
- if (options.ignoreCertErrors === undefined || options.ignoreCertErrors === null || options.ignoreCertErrors === "")
- options.ignoreCertErrors = [];
-
- if (Array.isArray(options.ignoreCertErrors) === false)
- options.ignoreCertErrors = [options.ignoreCertErrors];
-
- await super.startTLS();
-
- return await new Promise((resolve, reject) => {
- // Upgrade the current socket.
- // this.tlsSocket = tls.TLSSocket(socket, options).connect();
- this.tlsSocket = tls.connect({
- socket: this.socket,
- rejectUnauthorized: false
- });
-
- this.tlsSocket.on('secureConnect', () => {
-
- const cert = this.tlsSocket.getPeerCertificate(true);
-
- if (this.tlsSocket.authorized === true) {
-
- // in case the fingerprint is not pinned we can skip right here.
- if (!options.fingerprints.length) {
- resolve();
- this.getLogger().logState('Socket upgraded! (Chain of Trust)');
- return;
- }
-
- // so let's check the if the server's fingerprint matches the pinned one.
- if (options.fingerprints.indexOf(cert.fingerprint) !== NOT_FOUND) {
- resolve();
- this.getLogger().logState('Socket upgraded! (Chain of Trust and pinned fingerprint)');
- return;
- }
-
- const secInfo = {
- host: this.host,
- port: this.port,
-
- fingerprint: cert.fingerprint,
- fingerprint256 : cert.fingerprint256,
-
- message: "Server fingerprint does not match pinned fingerprint"
- };
-
- // If not we need to fail right here...
- reject(new SieveCertValidationException(secInfo));
- return;
- }
-
- const error = this.tlsSocket.ssl.verifyError();
-
- // dealing with self signed certificates
- if (options.ignoreCertErrors.indexOf(error.code) !== NOT_FOUND) {
-
- // Check if the fingerprint is well known...
- if (options.fingerprints.indexOf(cert.fingerprint) !== NOT_FOUND) {
- resolve();
-
- this.getLogger().logState('Socket upgraded! (Trusted Finger Print)');
- return;
- }
- }
-
- const secInfo = {
- host: this.host,
- port: this.port,
-
- fingerprint : cert.fingerprint,
- fingerprint256 : cert.fingerprint256,
-
- code : error.code,
- message : error.message
- };
-
- reject(new SieveCertValidationException(secInfo));
-
- this.tlsSocket.destroy();
- });
-
- this.tlsSocket.on('data', (data) => { this.onReceive(data); });
- });
- }
-
- /**
- * @inheritdoc
- */
- disconnect() {
-
- super.disconnect();
-
- this.getLogger().logState("Disconnecting...");
- if (this.socket) {
- this.socket.destroy();
- this.socket.unref();
- this.socket = null;
- }
-
- if (this.tlsSocket) {
- this.tlsSocket.destroy();
- this.tlsSocket.unref();
- this.tlsSocket = null;
- }
-
- this.idleTimer = null;
- this.timeoutTimer = null;
-
- this.getLogger().logState("Disconnected ...");
- }
-
- /**
- * Called when data was received and is ready to be processed.
- * @param {object} buffer
- * the received data.
- */
- onReceive(buffer) {
-
- this.getLogger().logState(`onDataRead (${buffer.length})`);
-
- const data = [];
-
- for (let i = 0; i < buffer.length; i++) {
- data[i] = buffer.readUInt8(i);
- }
-
- super.onReceive(data);
- }
-
- /**
- * @inheritdoc
- */
- onSend(data) {
-
- if (this.getLogger().isLevelStream()) {
- // Force String to UTF-8...
- const output = Array.prototype.slice.call(
- new Uint8Array(new TextEncoder("UTF-8").encode(data)));
-
- this.getLogger().logStream(`Client -> Server [Byte Array]:\n${output}`);
- }
-
- if (this.tlsSocket !== null) {
- this.tlsSocket.write(data, "utf8");
- return;
- }
-
- this.socket.write(data, "utf8");
- }
- }
-
-
- exports.Sieve = SieveNodeClient;
-
-})(this);
diff --git a/src/app/libs/libManageSieve/SieveClient.mjs b/src/app/libs/libManageSieve/SieveClient.mjs
new file mode 100644
index 00000000..5eb7490d
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveClient.mjs
@@ -0,0 +1,278 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractClient } from "./SieveAbstractClient.mjs";
+
+import { SieveCertValidationException } from "./SieveExceptions.mjs";
+
+const net = require('net');
+const tls = require('tls');
+
+/**
+ * Uses Node networking to realize a sieve client.
+ */
+class SieveNodeClient extends SieveAbstractClient {
+
+
+ /**
+ * Creates a new instance
+ * @param {AbstractLogger} logger
+ * the logger instance to use
+ **/
+ constructor(logger) {
+ super();
+
+ this.tlsSocket = null;
+ this._logger = logger;
+ this.secure = true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ isSecure() {
+ return this.secure;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getLogger() {
+ return this._logger;
+ }
+
+ /**
+ * Connects to a ManageSieve server.
+ * @param {string} host
+ * The target hostname or IP address as String
+ * @param {int} port
+ * The target port as integer
+ * @param {boolean} secure
+ * If true, a secure socket will be created. This allows switching to a secure
+ * connection.
+ *
+ * @returns {SieveAbstractClient}
+ * a self reference
+ */
+ connect(host, port, secure) {
+
+ if (this.socket !== null)
+ return this;
+
+ this.host = host;
+ this.port = port;
+ this.secure = secure;
+
+ this.socket = net.connect(this.port, this.host);
+
+ this.socket.on('data', async (data) => { await this.onReceive(data); });
+ this.socket.on('error', (error) => {
+ this.getLogger().logState(`SieveClient: OnError (Connection ${this.host}:${this.port})`);
+ // Node guarantees that close is called after error.
+ if ((this.listener) && (this.listener.onError))
+ (async () => { await this.listener.onError(error); })();
+ });
+ this.socket.on('close', async () => {
+ this.getLogger().logState(`SieveClient: OnClose (Connection ${this.host}:${this.port})`);
+ await this.disconnect();
+ });
+
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async startTLS(options) {
+
+ if (options === undefined || options === null)
+ options = {};
+
+ if (options.fingerprints === undefined || options.fingerprints === null || options.fingerprints === "")
+ options.fingerprints = [];
+
+ if (Array.isArray(options.fingerprints) === false)
+ options.fingerprints = [options.fingerprints];
+
+ if (options.ignoreCertErrors === undefined || options.ignoreCertErrors === null || options.ignoreCertErrors === "")
+ options.ignoreCertErrors = [];
+
+ if (Array.isArray(options.ignoreCertErrors) === false)
+ options.ignoreCertErrors = [options.ignoreCertErrors];
+
+ await super.startTLS();
+
+ return await new Promise((resolve, reject) => {
+ // Upgrade the current socket.
+ // this.tlsSocket = tls.TLSSocket(socket, options).connect();
+ this.tlsSocket = tls.connect({
+ socket: this.socket,
+ rejectUnauthorized: false
+ });
+
+ this.tlsSocket.on('secureConnect', () => {
+
+ const cert = this.tlsSocket.getPeerCertificate(true);
+
+ if (this.tlsSocket.authorized === true) {
+
+ // in case the fingerprint is not pinned we can skip right here.
+ if (!options.fingerprints.length) {
+ resolve();
+ this.getLogger().logState('Socket upgraded! (Chain of Trust)');
+ return;
+ }
+
+ // so let's check the if the server's sha1 fingerprint matches the pinned one.
+ if (options.fingerprints.includes(cert.fingerprint)) {
+ resolve();
+ this.getLogger().logState('Socket upgraded! (Chain of Trust and pinned SHA1 fingerprint)');
+ return;
+ }
+
+ // then check the sha256 fingerprint.
+ if (options.fingerprints.includes(cert.fingerprint256)) {
+ resolve();
+ this.getLogger().logState('Socket upgraded! (Chain of Trust and pinned SHA256 fingerprint)');
+ return;
+ }
+
+ const secInfo = {
+ host: this.host,
+ port: this.port,
+
+ fingerprint: cert.fingerprint,
+ fingerprint256: cert.fingerprint256,
+
+ message: "Server fingerprint does not match pinned fingerprint"
+ };
+
+ // If not we need to fail right here...
+ reject(new SieveCertValidationException(secInfo));
+ return;
+ }
+
+ const error = this.tlsSocket.ssl.verifyError();
+
+ // dealing with self signed certificates
+ if (options.ignoreCertErrors.includes(error.code)) {
+
+ // Check if the fingerprint is well known...
+ if (options.fingerprints.includes(cert.fingerprint)) {
+ resolve();
+
+ this.getLogger().logState('Socket upgraded! (Trusted SHA1 Finger Print)');
+ return;
+ }
+
+ // Check if the fingerprint is well known...
+ if (options.fingerprints.includes(cert.fingerprint256)) {
+ resolve();
+
+ this.getLogger().logState('Socket upgraded! (Trusted SHA256 Finger Print)');
+ return;
+ }
+ }
+
+ const secInfo = {
+ host: this.host,
+ port: this.port,
+
+ fingerprint: cert.fingerprint,
+ fingerprint256: cert.fingerprint256,
+
+ code: error.code,
+ message: error.message
+ };
+
+ reject(new SieveCertValidationException(secInfo));
+
+ this.tlsSocket.destroy();
+ });
+
+ this.tlsSocket.on('data', async (data) => { await this.onReceive(data); });
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async disconnect() {
+
+ this.getLogger().logState(`SieveClient: Disconnecting ${this.host}:${this.port}...`);
+
+ // Just a precaution ensures all timers are stopped.
+ await super.disconnect();
+
+ // In case the socket is gone we can skip right here
+ if (!this.socket)
+ return;
+
+ if (this.socket) {
+ this.socket.destroy();
+ if (this.socket && this.socket.unref)
+ this.socket.unref();
+ this.socket = null;
+ }
+
+ if (this.tlsSocket) {
+ this.tlsSocket.destroy();
+ if (this.socket && this.socket.unref)
+ this.tlsSocket.unref();
+ this.tlsSocket = null;
+ }
+
+ if ((this.listener) && (this.listener.onDisconnected))
+ await this.listener.onDisconnected();
+
+ this.getLogger().logState("SieveClient: ... client disconnected.");
+ }
+
+ /**
+ * Called when data was received and is ready to be processed.
+ * @param {object} buffer
+ * the received data.
+ */
+ async onReceive(buffer) {
+
+ this.getLogger().logState(`onDataRead (${buffer.length})`);
+
+ const data = [];
+
+ for (let i = 0; i < buffer.length; i++) {
+ data[i] = buffer.readUInt8(i);
+ }
+
+ await (super.onReceive(data));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onSend(data) {
+
+ if (this.getLogger().isLevelStream()) {
+ // Force String to UTF-8...
+ const output = Array.prototype.slice.call((new TextEncoder()).encode(data));
+
+ this.getLogger().logStream(`Client -> Server [Byte Array]:\n${output}`);
+ }
+
+ if (this.tlsSocket !== null) {
+ this.tlsSocket.write(data, "utf8");
+ return;
+ }
+
+ this.socket.write(data, "utf8");
+ }
+}
+
+export { SieveNodeClient as Sieve };
diff --git a/src/app/libs/libManageSieve/SieveCrypto.js b/src/app/libs/libManageSieve/SieveCrypto.js
deleted file mode 100755
index 35af38fc..00000000
--- a/src/app/libs/libManageSieve/SieveCrypto.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractCrypto } = require("./SieveAbstractCrypto.js");
-
- const crypto = require('crypto');
-
- /**
- * A Electron specific crypto implementation.
- */
- class SieveCrypto extends SieveAbstractCrypto {
-
- /**
- * @inheritdoc
- */
- HMAC(key, bytes, output) {
-
- if (typeof(key) === "undefined" || key === null)
- throw new Error("Invalid key");
-
- if (Array.isArray(key))
- key = Buffer.from(key);
-
- if (Array.isArray(bytes))
- bytes = Buffer.from(bytes);
-
- if (typeof(output) === "undefined" || output === null)
- output = "latin1";
-
- const rv = crypto
- .createHmac(this.name, key)
- .update(bytes)
- .digest(output);
-
- if (output === "hex")
- return rv;
-
- return this.strToByteArray(rv);
- }
-
- /**
- * @inheritdoc
- */
- H(bytes, output) {
-
- if (typeof(output) === "undefined" || output === null)
- output = "latin1";
-
- if (Array.isArray(bytes))
- bytes = Buffer.from(bytes);
-
- const rv = crypto.createHash(this.name)
- .update(bytes)
- .digest(output);
-
- if (output === "hex")
- return rv;
-
- return this.strToByteArray(rv);
- }
-
- }
-
- exports.SieveCrypto = SieveCrypto;
-
-})(module.exports);
diff --git a/src/app/libs/libManageSieve/SieveCrypto.mjs b/src/app/libs/libManageSieve/SieveCrypto.mjs
new file mode 100644
index 00000000..147ab0e5
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveCrypto.mjs
@@ -0,0 +1,115 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractCrypto } from "./SieveAbstractCrypto.mjs";
+
+// The hash names as they are used the web crypto api.
+const HASH_SHA1 = "SHA-1";
+const HASH_SHA256 = "SHA-256";
+const HASH_SHA512 = "SHA-512";
+
+// Node sadly node uses a slightly different naming scheme.
+const NODE_HASH_SHA1 = "SHA1";
+const NODE_HASH_SHA256 = "SHA256";
+const NODE_HASH_SHA512 = "SHA512";
+
+/**
+ * A Electron specific crypto implementation.
+ *
+ * @deprecated
+ * Node implements since version 15.x the web crypto api which makes
+ * this class obsolete as soon as the new api is marked as mature.
+ * See https://nodejs.org/api/webcrypto.html for more details.
+ *
+ */
+class SieveNodeCrypto extends SieveAbstractCrypto {
+
+ /**
+ * @inheritdoc
+ */
+ getCryptoHash() {
+
+ if (this.name === HASH_SHA1)
+ return NODE_HASH_SHA1;
+
+ if (this.name === HASH_SHA256)
+ return NODE_HASH_SHA256;
+
+ if (this.name === HASH_SHA512)
+ return NODE_HASH_SHA512;
+
+ throw new Error(`Unknown Hash algorithm ${name}`);
+ }
+
+ /**
+ * Loads the crypto module either as classic commonsjs or as ecma module.
+ * @returns {Crypto}
+ * the crypto module.
+ */
+ async getCrypto() {
+ if (typeof(require) !== "undefined")
+ return require("crypto");
+
+ return await import("crypto");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async HMAC(key, bytes, output) {
+
+ if (typeof (key) === "undefined" || key === null)
+ throw new Error("Invalid key");
+
+ if (Array.isArray(key))
+ key = Buffer.from(key);
+
+ if (Array.isArray(bytes))
+ bytes = Buffer.from(bytes);
+
+ if (typeof (output) === "undefined" || output === null)
+ output = "latin1";
+
+ const rv =
+ (await this.getCrypto()).createHmac(this.getCryptoHash(), key)
+ .update(bytes)
+ .digest(output);
+
+ if (output === "hex")
+ return rv;
+
+ return this.strToByteArray(rv);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async H(bytes, output) {
+
+ if (typeof (output) === "undefined" || output === null)
+ output = "latin1";
+
+ if (Array.isArray(bytes))
+ bytes = Buffer.from(bytes);
+
+ const rv = (await this.getCrypto()).createHash(this.getCryptoHash())
+ .update(bytes)
+ .digest(output);
+
+ if (output === "hex")
+ return rv;
+
+ return this.strToByteArray(rv);
+ }
+
+}
+
+export { SieveNodeCrypto as SieveCrypto };
diff --git a/src/app/libs/libManageSieve/SieveLogger.js b/src/app/libs/libManageSieve/SieveLogger.js
deleted file mode 100644
index d2fb06f4..00000000
--- a/src/app/libs/libManageSieve/SieveLogger.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractLogger } = require("./SieveAbstractLogger.js");
-
- /**
- * Implements a sieve compatible logger instance for node
- */
- class SieveNodeLogger extends SieveAbstractLogger {
-
- /**
- * @inheritdoc
- */
- log(message, level) {
-
- if (!this.isLoggable(level))
- return this;
-
- // eslint-disable-next-line no-console
- console.log(`[${this.getTimestamp()} ${this.prefix()} ] ${message}`);
- return this;
- }
- }
-
- exports.SieveLogger = SieveNodeLogger;
-
-})(this);
-
diff --git a/src/app/libs/libManageSieve/SieveNodeRequestBuilder.js b/src/app/libs/libManageSieve/SieveNodeRequestBuilder.js
deleted file mode 100755
index 7fc53659..00000000
--- a/src/app/libs/libManageSieve/SieveNodeRequestBuilder.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractRequestBuilder } = require("./SieveAbstractRequestBuilder.js");
-
- /**
- * Realizes a Request builder which uses native node commands
- */
- class SieveNodeRequestBuilder extends SieveAbstractRequestBuilder {
-
- /**
- * @inheritdoc
- */
- calculateByteLength(data) {
- return Buffer.byteLength(data, 'utf8');
- }
-
- /**
- * @inheritdoc
- */
- convertToBase64(decoded) {
- return Buffer.from(decoded).toString('base64');
- }
-
- /**
- * @inheritdoc
- */
- convertFromBase64(encoded) {
- return Buffer.from(encoded, 'base64').toString("latin1");
- }
- }
-
- exports.SieveNodeRequestBuilder = SieveNodeRequestBuilder;
-
-})(this);
diff --git a/src/app/libs/libManageSieve/SieveNodeResponseParser.js b/src/app/libs/libManageSieve/SieveNodeResponseParser.js
deleted file mode 100755
index a02b16cd..00000000
--- a/src/app/libs/libManageSieve/SieveNodeResponseParser.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractResponseParser } = require("./SieveAbstractResponseParser.js");
- const { StringDecoder } = require('string_decoder');
-
- /**
- * Implements a node specific response parser
- */
- class SieveNodeResponseParser extends SieveAbstractResponseParser {
-
- /**
- * @inheritdoc
- **/
- convertToString(byteArray) {
- return new StringDecoder('utf8').end(Buffer.from(byteArray)).toString();
- }
-
- /**
- * @inheritdoc
- **/
- convertToBase64(decoded) {
- return Buffer.from(decoded).toString('base64');
- }
-
- /**
- * @inheritdoc
- */
- convertFromBase64(encoded) {
- return Buffer.from(encoded, 'base64').toString("latin1");
- }
- }
-
- exports.SieveNodeResponseParser = SieveNodeResponseParser;
-
-})(this);
diff --git a/src/app/libs/libManageSieve/SieveSession.js b/src/app/libs/libManageSieve/SieveSession.js
deleted file mode 100644
index 4cbc0035..00000000
--- a/src/app/libs/libManageSieve/SieveSession.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const {
- SieveAbstractSession
- } = require("./SieveAbstractSession.js");
-
- /**
- * @inheritdoc
- */
- class SieveNodeSession extends SieveAbstractSession {
-
- /**
- * @inheritdoc
- */
- async startTLS() {
-
- const options = {
- fingerprints : this.getOption("certFingerprints"),
- ignoreCertErrors : this.getOption("certIgnoreError")
- };
-
- await super.startTLS(options);
- }
-
- /**
- * The default error handler called upon any unhandled error or exception.
- * Called e.g. when the connection to the server was terminated unexpectedly.
- *
- * The default behaviour is to disconnect.
- *
- * @param {Error} error
- * the error message which causes this exceptional state.
- */
- async onError(error) {
-
- this.getLogger().logSession(`OnError: ${error.message}`);
-
- await this.disconnect(true);
- }
-
- }
-
- exports.SieveSession = SieveNodeSession;
-
-})(exports || this);
diff --git a/src/app/libs/libManageSieve/SieveSession.mjs b/src/app/libs/libManageSieve/SieveSession.mjs
new file mode 100644
index 00000000..dc371bfc
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveSession.mjs
@@ -0,0 +1,34 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractSession } from "./SieveAbstractSession.mjs";
+
+/**
+ * @inheritdoc
+ */
+class SieveNodeSession extends SieveAbstractSession {
+
+ /**
+ * @inheritdoc
+ */
+ async startTLS() {
+
+ const options = {
+ fingerprints: this.getOption("certFingerprints"),
+ ignoreCertErrors: this.getOption("certIgnoreError")
+ };
+
+ await super.startTLS(options);
+ }
+
+}
+
+export { SieveNodeSession as SieveSession };
diff --git a/src/app/libs/libManageSieve/SieveSessions.js b/src/app/libs/libManageSieve/SieveSessions.js
deleted file mode 100644
index 94ccb4fa..00000000
--- a/src/app/libs/libManageSieve/SieveSessions.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveSession } = require("./SieveSession.js");
-
- /**
- * Manages Sieve session.
- *
- * Sessions are identified by a unique id.
- * As the account id unique, it is typically
- * used as session id.
- */
- class SieveSessions {
-
- /**
- * creates a new instance
- */
- constructor() {
- this.sessions = new Map();
- }
-
- /**
- * Check if the id it a known session.
- *
- * @param {string} id
- * the session id
- * @returns {boolean}
- * true in case the id is a known session otherwise false.
- */
- has(id) {
- return this.sessions.has(id);
- }
-
- /**
- * Returns the session with the given id.
- * In case the id is unknown an exception is thrown.
- *
- * @param {string} id
- * the session id
- * @returns {SieveSession}
- * the session or an exception.
- */
- get(id) {
- if (!this.has(id))
- throw new Error(`Unknown session id ${id}`);
-
- return this.sessions.get(id);
- }
-
- /**
- * Called when an authentication is needed
- *
- * @param {SieveAccount} account
- * the account which should be authenticated.
- * @param {boolean} hasPassword
- * true if the password is needed otherwise false.
- * @returns {object}
- * an object the the username and optionally the password.
- */
- async onAuthenticate(account, hasPassword) {
-
- const authentication = {};
-
- authentication.username = await (await account.getAuthentication()).getUsername();
-
- if (hasPassword)
- authentication.password = await (await account.getAuthentication()).getPassword();
-
- return authentication;
- }
-
- /**
- * Called when an authorization is needed.
- *
- * @param {SieveAccount} account
- * the account which should be authorized.
- * @returns {string}
- * the user name to be authorized as or an empty string.
- */
- async onAuthorize(account) {
- return await (await account.getAuthorization()).getAuthorization();
- }
-
- /**
- * Called when a proxy lookup is needed.
- *
- * @param {SieveAccount} account
- * the current account.
- * @returns {object}
- * the proxy information.
- */
- async onProxyLookup(account) {
- return await account.getProxy().getProxyInfo();
- }
-
-
- /**
- * Creates a new session for the given id.
- * In case the session id is in use. It will
- * terminate the connection, and recreate a
- * new session.
- *
- * @param {string} id
- * the unique session id
- * @param {SieveAccount} account
- * the account with the session's configuration
- */
- async create(id, account) {
-
- await this.destroy(id);
-
- const host = await account.getHost();
- const security = await account.getSecurity();
- const settings = await account.getSettings();
-
- const options = {
- secure : await security.isSecure(),
- sasl : await security.getMechanism(),
- keepAlive : await host.getKeepAlive(),
- logLevel : await settings.getLogLevel(),
- certFingerprints : await host.getFingerprint(),
- certIgnoreError : await host.getIgnoreCertErrors()
- };
-
- const session = new SieveSession(id, options);
-
- // TODO move to app so that it can be shared with the wx implementation.
- session.on("authenticate", async (hasPassword) => { return await this.onAuthenticate(account, hasPassword); });
- session.on("authorize", async () => { return await this.onAuthorize(account); });
- session.on("proxy", async (hostname, port) => { return await this.onProxyLookup(account, hostname, port); });
-
- this.sessions.set(id, session);
- }
-
- /**
- * Destroy the session for the given id.
- * If active it will disconnect from the server.
- *
- * @param {string} id
- * the unique session id
- */
- async destroy(id) {
- if (this.has(id))
- await this.get(id).disconnect();
-
- this.sessions.delete(id);
- }
-
- }
-
- if (module.exports)
- module.exports.SieveSessions = SieveSessions;
- else
- exports.SieveSessions = SieveSessions;
-
-})(this);
diff --git a/src/app/libs/libManageSieve/SieveSessions.mjs b/src/app/libs/libManageSieve/SieveSessions.mjs
new file mode 100644
index 00000000..7b9bd5f7
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveSessions.mjs
@@ -0,0 +1,146 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveSession } from "./SieveSession.mjs";
+
+/**
+ * Manages Sieve session.
+ *
+ * Sessions are identified by a unique id.
+ * As the account id unique, it is typically
+ * used as session id.
+ */
+class SieveNodeSessions {
+
+ /**
+ * creates a new instance
+ */
+ constructor() {
+ this.sessions = new Map();
+ }
+
+ /**
+ * Check if the id it a known session.
+ *
+ * @param {string} id
+ * the session id
+ * @returns {boolean}
+ * true in case the id is a known session otherwise false.
+ */
+ has(id) {
+ return this.sessions.has(id);
+ }
+
+ /**
+ * Returns the session with the given id.
+ * In case the id is unknown an exception is thrown.
+ *
+ * @param {string} id
+ * the session id
+ * @returns {SieveSession}
+ * the session or an exception.
+ */
+ get(id) {
+ if (!this.has(id))
+ throw new Error(`Unknown session id ${id}`);
+
+ return this.sessions.get(id);
+ }
+
+ /**
+ * Called when an authentication is needed
+ *
+ * @param {SieveAccount} account
+ * the account which should be authenticated.
+ * @param {boolean} hasPassword
+ * true if the password is needed otherwise false.
+ * @returns {object}
+ * an object the the username and optionally the password.
+ */
+ async onAuthenticate(account, hasPassword) {
+
+ const authentication = {};
+
+ authentication.username = await (await account.getAuthentication()).getUsername();
+
+ if (hasPassword)
+ authentication.password = await (await account.getAuthentication()).getPassword();
+
+ return authentication;
+ }
+
+ /**
+ * Called when an authorization is needed.
+ *
+ * @param {SieveAccount} account
+ * the account which should be authorized.
+ * @returns {string}
+ * the user name to be authorized as or an empty string.
+ */
+ async onAuthorize(account) {
+ return await (await account.getAuthorization()).getAuthorization();
+ }
+
+
+
+ /**
+ * Creates a new session for the given id.
+ * In case the session id is in use. It will
+ * terminate the connection, and recreate a
+ * new session.
+ *
+ * @param {string} id
+ * the unique session id
+ * @param {SieveAccount} account
+ * the account with the session's configuration
+ */
+ async create(id, account) {
+
+ await this.destroy(id);
+
+ const host = await account.getHost();
+ const security = await account.getSecurity();
+ const settings = await account.getSettings();
+
+ const options = {
+ secure: await security.isSecure(),
+ sasl: await security.getMechanism(),
+ keepAlive: await host.getKeepAlive(),
+ logLevel: await settings.getLogLevel(),
+ certFingerprints: await host.getFingerprint(),
+ certIgnoreError: await host.getIgnoreCertErrors()
+ };
+
+ const session = new SieveSession(id, options);
+
+ session.on("authenticate", async (hasPassword) => { return await this.onAuthenticate(account, hasPassword); });
+ session.on("authorize", async () => { return await this.onAuthorize(account); });
+
+ this.sessions.set(id, session);
+ }
+
+ /**
+ * Destroy the session for the given id.
+ * If active it will disconnect from the server.
+ *
+ * @param {string} id
+ * the unique session id
+ */
+ async destroy(id) {
+ if (this.has(id))
+ await (this.get(id).disconnect());
+
+ this.sessions.delete(id);
+ }
+
+}
+
+export { SieveNodeSessions as SieveSessions };
diff --git a/src/app/libs/libManageSieve/SieveTimer.mjs b/src/app/libs/libManageSieve/SieveTimer.mjs
new file mode 100644
index 00000000..e68b57b8
--- /dev/null
+++ b/src/app/libs/libManageSieve/SieveTimer.mjs
@@ -0,0 +1,47 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractTimer } from "./SieveAbstractTimer.mjs";
+
+/**
+ * By default node does not inject a timer into the standard context.
+ *
+ * Your need to include it via require. All in all it is almost identical
+ * with the timer used by a javascript window object. But it lives in a
+ * different namespace.
+ */
+class SieveNodeTimer extends SieveAbstractTimer {
+
+ /**
+ * @inheritdoc
+ */
+ start(callback, ms) {
+ this.cancel();
+
+ if (ms === 0)
+ return;
+
+ this.timer = setTimeout(callback, ms);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ cancel() {
+ if (!this.timer)
+ return;
+
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+}
+
+export { SieveNodeTimer as SieveTimer };
diff --git a/src/app/libs/managesieve.ui/accounts.html b/src/app/libs/managesieve.ui/accounts.html
index fa38ebbf..941a7ef2 100644
--- a/src/app/libs/managesieve.ui/accounts.html
+++ b/src/app/libs/managesieve.ui/accounts.html
@@ -21,32 +21,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="./../bootstrap/js/bootstrap.bundle.min.js"></script>
- <script src="./utils/SieveFakeRequire.js"></script>
-
- <script src="./utils/SieveLogger.js"></script>
- <script src="./utils/SieveUniqueId.js"></script>
- <script src="./utils/SieveAbstractIpcClient.js"></script>
- <script src="./utils/SieveIpcClient.js"></script>
- <script src="./utils/SieveI18n.js"></script>
- <script src="./utils/SieveTemplate.js"></script>
-
- <script src="./settings/ui/SieveServerSettingsUI.js"></script>
- <script src="./settings/ui/SieveCredentialSettingsUI.js"></script>
- <script src="./settings/ui/SieveDebugSettingsUI.js"></script>
-
- <script src="./accounts/SieveCapabilities.js"></script>
- <script src="./accounts/SieveScriptUI.js"></script>
- <script src="./accounts/SieveAccountUI.js"></script>
- <script src="./accounts/SieveAbstractAccounts.js"></script>
- <script src="./accounts/SieveAccounts.js"></script>
- <script src="./accounts/SieveAccountCreateUI.js"></script>
-
- <script src="./dialogs/SieveDialogUI.js"></script>
-
- <script src="./importer/SieveImportUI.js"></script>
- <script src="./updater/SieveUpdaterUI.js"></script>
-
- <script src="accounts.js"></script>
+ <script type="module" src="accounts.mjs"></script>
</div>
</body>
diff --git a/src/app/libs/managesieve.ui/accounts.js b/src/app/libs/managesieve.ui/accounts.js
deleted file mode 100644
index d1c85dcb..00000000
--- a/src/app/libs/managesieve.ui/accounts.js
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function () {
-
- "use strict";
-
- /* global SieveAccounts */
- /* global SieveUpdaterUI */
- /* global SieveIpcClient */
- /* global SieveLogger */
- /* global SieveI18n */
-
- /* global SieveCreateScriptDialog */
- /* global SieveDeleteScriptDialog */
- /* global SieveRenameScriptDialog */
- /* global SieveFingerprintDialog */
- /* global SieveScriptBusyDialog */
- /* global SieveDeleteAccountDialog */
- /* global SieveErrorDialog */
-
- /* global SievePasswordDialog */
- /* global SieveAuthorizationDialog */
-
- /**
- * Shows a prompt which asks the user for the new script name.
- *
- * @returns {string}
- * the script name or an empty string in case the dialog was canceled.
- */
- async function onCreateScript() {
- return await (new SieveCreateScriptDialog()).show();
- }
-
- /**
- * Shows a prompt which asks the user if the script should be deleted.
- *
- * @param {string} name
- * the script name which should be deleted
- *
- * @returns {boolean}
- * true in case the script shall be deleted otherwise false.
- */
- async function onDeleteScript(name) {
- return await (new SieveDeleteScriptDialog(name)).show();
- }
-
- /**
- * Shows a prompt which asks the user if the script should be renamed.
- *
- * @param {string} name
- * the name which should be renamed
- *
- * @returns {string}
- * the script name in case the dialog. In case the dialog was
- * canceled the original name otherwise the new name.
- */
- async function onRenameScript(name) {
- return await (new SieveRenameScriptDialog(name)).show();
- }
-
- /**
- * Informs the user that the action can't be performed because
- * the script is currently in use.
- *
- * @param {string} name
- * the name of the script which was busy
- */
- async function onBusy(name) {
- await (new SieveScriptBusyDialog(name)).show();
- }
-
- /**
- * Shows a prompt which asks the user if the given account
- * should be removed.
- *
- * @param {string} name
- * the account name
- * @returns {boolean}
- * true in case the account should be deleted otherwise false.
- */
- async function onDeleteAccount(name) {
- return await (new SieveDeleteAccountDialog(name)).show();
- }
-
- /**
- * Informs the user about a failed certificate validation.
- *
- * @param {object} secInfo
- * the security information with more details about the validation error.
- *
- * @returns {boolean}
- * true in case the certificate should be overwritten otherwise false.
- */
- async function onCertError(secInfo) {
- return await (new SieveFingerprintDialog(secInfo)).show();
- }
-
- /**
- * Informs the user about a connection error.
- *
- * @param {string} message
- * the detailed connection error.
- */
- async function onError(message) {
- await (new SieveErrorDialog(message)).show();
- }
-
- /**
- * Requests the password from the user.
- *
- * @param {string} username
- * the username for which the password is requested.
- * @param {string} account
- * the account's display name.
- * @param {boolean} remember
- * show the "remember password" field.
- * @returns {string}
- * the password as string.
- */
- async function onAuthenticate(username, account, remember) {
- return await (new SievePasswordDialog(username, account, {remember : remember})).show();
- }
-
- /**
- * Prompts for the username to be authorized.
- *
- * @param {string} account
- * the accounts displayname.
- * @returns {string}
- * the username to be authorized.
- */
- async function onAuthorize(account) {
- return await (new SieveAuthorizationDialog(account)).show();
- }
-
- /**
- * The main entry point for the account view
- * Called as soon as the DOM is ready.
- */
- async function main() {
-
- SieveLogger.getInstance().level(
- await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
-
- await (SieveI18n.getInstance()).load();
-
- const accounts = new SieveAccounts();
-
- SieveIpcClient.setRequestHandler("accounts", "script-show-create",
- async () => { return await onCreateScript(); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-delete",
- async (msg) => { return await onDeleteScript(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-rename",
- async (msg) => { return await onRenameScript(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-busy",
- async (msg) => { await onBusy(msg.payload); });
-
- SieveIpcClient.setRequestHandler("accounts", "account-show-delete",
- async (msg) => { return await onDeleteAccount(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "account-show-certerror",
- async (msg) => { return await onCertError(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "account-show-error",
- async (msg) => { return await onError(msg.payload); });
-
- SieveIpcClient.setRequestHandler("accounts", "account-show-authentication",
- async (msg) => { return await onAuthenticate(msg.payload.username, msg.payload.displayname, msg.payload.remember); });
- SieveIpcClient.setRequestHandler("accounts", "account-show-authorization",
- async (msg) => { return await onAuthorize(msg.payload.displayname); });
-
- accounts.render();
- (new SieveUpdaterUI()).check();
- }
-
- if (document.readyState !== 'loading')
- main();
- else
- document.addEventListener('DOMContentLoaded', () => { main(); }, { once: true });
-
-})();
diff --git a/src/app/libs/managesieve.ui/accounts.mjs b/src/app/libs/managesieve.ui/accounts.mjs
new file mode 100644
index 00000000..24f29fc5
--- /dev/null
+++ b/src/app/libs/managesieve.ui/accounts.mjs
@@ -0,0 +1,183 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveLogger } from "./utils/SieveLogger.mjs";
+import { SieveIpcClient } from "./utils/SieveIpcClient.mjs";
+import { SieveI18n } from "./utils/SieveI18n.mjs";
+
+import { SieveAccounts } from "./accounts/SieveAccounts.mjs";
+import { SieveUpdaterUI } from "./updater/SieveUpdaterUI.mjs";
+import {
+ SieveCreateScriptDialog,
+ SieveDeleteScriptDialog,
+ SieveRenameScriptDialog,
+ SieveFingerprintDialog,
+ SieveScriptBusyDialog,
+ SieveDeleteAccountDialog,
+ SieveErrorDialog,
+ SievePasswordDialog,
+ SieveAuthorizationDialog
+} from "./dialogs/SieveDialogUI.mjs";
+
+/**
+ * Shows a prompt which asks the user for the new script name.
+ *
+ * @returns {string}
+ * the script name or an empty string in case the dialog was canceled.
+ */
+async function onCreateScript() {
+ return await (new SieveCreateScriptDialog()).show();
+}
+
+/**
+ * Shows a prompt which asks the user if the script should be deleted.
+ *
+ * @param {string} name
+ * the script name which should be deleted
+ *
+ * @returns {boolean}
+ * true in case the script shall be deleted otherwise false.
+ */
+async function onDeleteScript(name) {
+ return await (new SieveDeleteScriptDialog(name)).show();
+}
+
+/**
+ * Shows a prompt which asks the user if the script should be renamed.
+ *
+ * @param {string} name
+ * the name which should be renamed
+ *
+ * @returns {string}
+ * the script name in case the dialog. In case the dialog was
+ * canceled the original name otherwise the new name.
+ */
+async function onRenameScript(name) {
+ return await (new SieveRenameScriptDialog(name)).show();
+}
+
+/**
+ * Informs the user that the action can't be performed because
+ * the script is currently in use.
+ *
+ * @param {string} name
+ * the name of the script which was busy
+ */
+async function onBusy(name) {
+ await (new SieveScriptBusyDialog(name)).show();
+}
+
+/**
+ * Shows a prompt which asks the user if the given account
+ * should be removed.
+ *
+ * @param {string} name
+ * the account name
+ * @returns {boolean}
+ * true in case the account should be deleted otherwise false.
+ */
+async function onDeleteAccount(name) {
+ return await (new SieveDeleteAccountDialog(name)).show();
+}
+
+/**
+ * Informs the user about a failed certificate validation.
+ *
+ * @param {object} secInfo
+ * the security information with more details about the validation error.
+ *
+ * @returns {boolean}
+ * true in case the certificate should be overwritten otherwise false.
+ */
+async function onCertError(secInfo) {
+ return await (new SieveFingerprintDialog(secInfo)).show();
+}
+
+/**
+ * Informs the user about a connection error.
+ *
+ * @param {string} message
+ * the detailed connection error.
+ */
+async function onError(message) {
+ await (new SieveErrorDialog(message)).show();
+}
+
+/**
+ * Requests the password from the user.
+ *
+ * @param {string} username
+ * the username for which the password is requested.
+ * @param {string} account
+ * the account's display name.
+ * @param {boolean} remember
+ * show the "remember password" field.
+ * @returns {string}
+ * the password as string.
+ */
+async function onAuthenticate(username, account, remember) {
+ return await (new SievePasswordDialog(username, account, { remember: remember })).show();
+}
+
+/**
+ * Prompts for the username to be authorized.
+ *
+ * @param {string} account
+ * the accounts displayname.
+ * @returns {string}
+ * the username to be authorized.
+ */
+async function onAuthorize(account) {
+ return await (new SieveAuthorizationDialog(account)).show();
+}
+
+/**
+ * The main entry point for the account view
+ * Called as soon as the DOM is ready.
+ */
+async function main() {
+
+ SieveLogger.getInstance().level(
+ await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
+
+ await (SieveI18n.getInstance()).load();
+
+ const accounts = new SieveAccounts();
+
+ SieveIpcClient.setRequestHandler("accounts", "script-show-create",
+ async () => { return await onCreateScript(); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-delete",
+ async (msg) => { return await onDeleteScript(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-rename",
+ async (msg) => { return await onRenameScript(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-busy",
+ async (msg) => { await onBusy(msg.payload); });
+
+ SieveIpcClient.setRequestHandler("accounts", "account-show-delete",
+ async (msg) => { return await onDeleteAccount(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "account-show-certerror",
+ async (msg) => { return await onCertError(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "account-show-error",
+ async (msg) => { return await onError(msg.payload); });
+
+ SieveIpcClient.setRequestHandler("accounts", "account-show-authentication",
+ async (msg) => { return await onAuthenticate(msg.payload.username, msg.payload.displayname, msg.payload.remember); });
+ SieveIpcClient.setRequestHandler("accounts", "account-show-authorization",
+ async (msg) => { return await onAuthorize(msg.payload.displayname); });
+
+ accounts.render();
+ (new SieveUpdaterUI()).check();
+}
+
+if (document.readyState !== 'loading')
+ main();
+else
+ document.addEventListener('DOMContentLoaded', () => { main(); }, { once: true });
diff --git a/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.js b/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.js
deleted file mode 100644
index 6f995a23..00000000
--- a/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveTemplate */
- /* global SieveIpcClient */
-
- /**
- * Imports sieve settings from mailers.
- */
- class SieveAccountCreateUI {
-
- /**
- * Shows the import account dialog.
- *
- * @returns {boolean}
- * true in case the dialog as accepted otherwise false.
- */
- async show() {
-
- const dialog = await (new SieveTemplate())
- .load("./accounts/account.dialog.create.tpl");
- document.querySelector("#ctx").appendChild(dialog);
-
- return await new Promise((resolve) => {
-
- const modal = new bootstrap.Modal(dialog);
-
- dialog
- .querySelector(".sieve-create-account-btn")
- .addEventListener("click", async () => {
-
- const account = {
- name: dialog.querySelector(".sieve-create-account-displayname").value,
- hostname: dialog.querySelector(".sieve-create-account-hostname").value,
- port: dialog.querySelector(".sieve-create-account-port").value,
- username: dialog.querySelector(".sieve-create-account-username").value
- };
-
- // fix me remove modal2 from dom.
- await SieveIpcClient.sendMessage("core", "account-create", account);
- modal.hide();
- resolve(true);
- });
-
- modal.show();
- dialog.addEventListener('hidden.bs.modal', () => {
- dialog.parentNode.removeChild(dialog);
- resolve(false);
- });
- });
- }
- }
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveAccountCreateUI;
- else
- exports.SieveAccountCreateUI = SieveAccountCreateUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.mjs b/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.mjs
new file mode 100644
index 00000000..08a1dfbc
--- /dev/null
+++ b/src/app/libs/managesieve.ui/accounts/SieveAccountCreateUI.mjs
@@ -0,0 +1,63 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+/**
+ * Imports sieve settings from mailers.
+ */
+class SieveAccountCreateUI {
+
+ /**
+ * Shows the import account dialog.
+ *
+ * @returns {boolean}
+ * true in case the dialog as accepted otherwise false.
+ */
+ async show() {
+
+ const dialog = await (new SieveTemplate())
+ .load("./accounts/account.dialog.create.html");
+ document.querySelector("#ctx").append(dialog);
+
+ return await new Promise((resolve) => {
+
+ const modal = new bootstrap.Modal(dialog);
+
+ dialog
+ .querySelector(".sieve-create-account-btn")
+ .addEventListener("click", async () => {
+
+ const account = {
+ name: dialog.querySelector(".sieve-create-account-displayname").value,
+ hostname: dialog.querySelector(".sieve-create-account-hostname").value,
+ port: dialog.querySelector(".sieve-create-account-port").value,
+ username: dialog.querySelector(".sieve-create-account-username").value
+ };
+
+ // fix me remove modal2 from dom.
+ await SieveIpcClient.sendMessage("core", "account-create", account);
+ modal.hide();
+ resolve(true);
+ });
+
+ modal.show();
+ dialog.addEventListener('hidden.bs.modal', () => {
+ dialog.remove();
+ resolve(false);
+ });
+ });
+ }
+}
+
+export { SieveAccountCreateUI };
diff --git a/src/app/libs/managesieve.ui/accounts/SieveAccountUI.mjs b/src/app/libs/managesieve.ui/accounts/SieveAccountUI.mjs
new file mode 100644
index 00000000..5afea0c8
--- /dev/null
+++ b/src/app/libs/managesieve.ui/accounts/SieveAccountUI.mjs
@@ -0,0 +1,97 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractAccountUI } from "./SieveAbstractAccountUI.mjs";
+
+import { SieveCredentialsSettingsUI } from "./../settings/ui/SieveCredentialSettingsUI.mjs";
+import { SieveServerSettingsUI } from "./../settings/ui/SieveServerSettingsUI.mjs";
+
+/**
+ * A UI renderer for a sieve account
+ */
+class SieveNodeAccountUI extends SieveAbstractAccountUI{
+
+ /**
+ * Renders the settings pane
+ *
+ */
+ async renderSettings() {
+
+ await super.renderSettings();
+
+ const elm = document.querySelector(`#siv-account-${this.id} .sieve-settings-content`);
+
+ // ... finally connect the listeners.
+ if (elm.querySelector(".sieve-account-delete-server")) {
+ elm.querySelector(".sieve-account-delete-server")
+ .addEventListener("click", () => { this.remove(); });
+ }
+
+ if (elm.querySelector(".sieve-account-edit-server")) {
+ elm.querySelector(".sieve-account-edit-server")
+ .addEventListener("click", () => { this.showServerSettings(); });
+ }
+
+ if (elm.querySelector(".sieve-account-edit-credentials")) {
+ elm.querySelector(".sieve-account-edit-credentials")
+ .addEventListener("click", () => { this.showCredentialSettings(); });
+ }
+
+ if (elm.querySelector(".sieve-account-export")) {
+ elm.querySelector(".sieve-account-export")
+ .addEventListener("click", () => { this.exportSettings(); });
+ }
+
+ }
+
+
+
+ /**
+ * Asks the user if he is sure to delete the account.
+ * If yes it triggers expunging the account settings.
+ * This can not be undone.
+ */
+ async remove() {
+ await this.accounts.remove(this);
+ }
+
+ /**
+ * Shows the server settings dialog.
+ */
+ async showServerSettings() {
+
+ await (new SieveServerSettingsUI(this)).show();
+
+ this.renderSettings();
+
+ // Update the account name it may have changed.
+ document
+ .querySelector(`#siv-account-${this.id} .siv-account-name`)
+ .textContent = await this.send("account-get-displayname");
+ }
+
+ /**
+ * Shows the credential settings dialog.
+ **/
+ showCredentialSettings() {
+ (new SieveCredentialsSettingsUI(this)).show();
+ }
+
+ /**
+ * Exports the account's settings to a file.
+ */
+ async exportSettings() {
+ await this.send("account-export");
+ }
+
+}
+
+export { SieveNodeAccountUI as SieveAccountUI };
diff --git a/src/app/libs/managesieve.ui/accounts/SieveAccounts.js b/src/app/libs/managesieve.ui/accounts/SieveAccounts.js
deleted file mode 100644
index b73f140e..00000000
--- a/src/app/libs/managesieve.ui/accounts/SieveAccounts.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveAbstractAccounts */
- /* global SieveIpcClient */
- /* global SieveTemplate */
-
- /* global SieveImportUI */
- /* global SieveAccountCreateUI */
-
- /**
- * A UI renderer for a list of sieve accounts
- **/
- class SieveAppAccounts extends SieveAbstractAccounts {
-
- /**
- * @inheritdoc
- */
- async render() {
-
- if (!document.querySelector(".siv-accounts-items")) {
-
- document.querySelector(".siv-accounts").appendChild(
- (await (new SieveTemplate()).load("./accounts/accounts.tpl")));
-
- document.querySelector("#sieve-account-import-file")
- .addEventListener("click", async () => {
- await SieveIpcClient.sendMessage("core", "account-import");
- this.render();
- });
-
- document.querySelector("#sieve-account-import-thunderbird")
- .addEventListener("click", async () => {
- await (new SieveImportUI()).show();
- this.render();
- });
-
- document.querySelector("#sieve-account-create")
- .addEventListener("click", async () => {
- await (new SieveAccountCreateUI().show());
- this.render();
- });
- }
-
- super.render();
- }
-
- /**
- * Removes the account including all settings.
- *
- * @param {SieveAccountUI} account
- * the account which should be removed.
- */
- async remove(account) {
-
- const rv = await account.send("account-delete", account.id);
-
- if (rv)
- await this.render();
- }
-
- /**
- * Create a new account, and initializes it with default settings.
- *
- * @returns {string}
- * the accounts unique id.
- */
- async create() {
- const id = await SieveIpcClient.sendMessage("core", "account-create");
- await this.render();
-
- return id;
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAccounts = SieveAppAccounts;
- else
- exports.SieveAccounts = SieveAppAccounts;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/accounts/SieveAccounts.mjs b/src/app/libs/managesieve.ui/accounts/SieveAccounts.mjs
new file mode 100644
index 00000000..43ec34fa
--- /dev/null
+++ b/src/app/libs/managesieve.ui/accounts/SieveAccounts.mjs
@@ -0,0 +1,84 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractAccounts } from "./SieveAbstractAccounts.mjs";
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+import { SieveImportUI } from "./../importer/SieveImportUI.mjs";
+import { SieveAccountCreateUI } from "./SieveAccountCreateUI.mjs";
+
+/**
+ * A UI renderer for a list of sieve accounts
+ **/
+class SieveNodeAccounts extends SieveAbstractAccounts {
+
+ /**
+ * @inheritdoc
+ */
+ async render() {
+
+ if (!document.querySelector(".siv-accounts-items")) {
+
+ document.querySelector(".siv-accounts").append(
+ (await (new SieveTemplate()).load("./accounts/accounts.html")));
+
+ document.querySelector("#sieve-account-import-file")
+ .addEventListener("click", async () => {
+ await SieveIpcClient.sendMessage("core", "account-import");
+ this.render();
+ });
+
+ document.querySelector("#sieve-account-import-thunderbird")
+ .addEventListener("click", async () => {
+ await (new SieveImportUI()).show();
+ this.render();
+ });
+
+ document.querySelector("#sieve-account-create")
+ .addEventListener("click", async () => {
+ await (new SieveAccountCreateUI().show());
+ this.render();
+ });
+ }
+
+ super.render();
+ }
+
+ /**
+ * Removes the account including all settings.
+ *
+ * @param {SieveAccountUI} account
+ * the account which should be removed.
+ */
+ async remove(account) {
+
+ const rv = await account.send("account-delete", account.id);
+
+ if (rv)
+ await this.render();
+ }
+
+ /**
+ * Create a new account, and initializes it with default settings.
+ *
+ * @returns {string}
+ * the accounts unique id.
+ */
+ async create() {
+ const id = await SieveIpcClient.sendMessage("core", "account-create");
+ await this.render();
+
+ return id;
+ }
+}
+
+export { SieveNodeAccounts as SieveAccounts };
diff --git a/src/app/libs/managesieve.ui/accounts/account.dialog.create.tpl b/src/app/libs/managesieve.ui/accounts/account.dialog.create.html
index 81982c22..70677b8d 100644
--- a/src/app/libs/managesieve.ui/accounts/account.dialog.create.tpl
+++ b/src/app/libs/managesieve.ui/accounts/account.dialog.create.html
@@ -5,7 +5,7 @@
<div class="modal-header">
<h5 class="modal-title" data-i18n="account.create.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
diff --git a/src/app/libs/managesieve.ui/accounts/account.settings.tpl b/src/app/libs/managesieve.ui/accounts/account.settings.html
index 2ff4ce3c..f3098448 100644
--- a/src/app/libs/managesieve.ui/accounts/account.settings.tpl
+++ b/src/app/libs/managesieve.ui/accounts/account.settings.html
@@ -4,12 +4,12 @@
<div style="min-width:8em" data-i18n="account.details.server"></div>
<span class="sieve-settings-hostname"></span>:
<span class="sieve-settings-port"></span>
- <span class="sieve-settings-secure ml-1" data-i18n="account.details.secure"></span>
+ <span class="sieve-settings-secure ms-1" data-i18n="account.details.secure"></span>
</div>
<div class="sieve-settings-fingerprint-item d-none">
<div class="d-flex flex-row form-label ">
<div style="min-width:8em" data-i18n="account.details.fingerprint"></div>
- <div class="sieve-settings-fingerprint"></div>
+ <div class="sieve-settings-fingerprint text-break"></div>
</div>
</div>
<div class="d-flex flex-row form-label ">
@@ -23,10 +23,10 @@
<div class="mt-3">
<button type="button"
data-i18n="account.details.server.edit"
- class="sieve-account-edit-server btn btn-sm btn-outline-secondary mr-1"></button>
+ class="sieve-account-edit-server btn btn-sm btn-outline-secondary me-1"></button>
<button type="button"
data-i18n="account.details.credentials.edit"
- class="sieve-account-edit-credentials btn btn-sm btn-outline-secondary mr-1"></button>
+ class="sieve-account-edit-credentials btn btn-sm btn-outline-secondary me-1"></button>
<button type="button"
data-i18n="account.details.debugging.edit"
class="sieve-account-edit-debug btn btn-sm btn-outline-secondary"></button>
diff --git a/src/app/libs/managesieve.ui/accounts/accounts.tpl b/src/app/libs/managesieve.ui/accounts/accounts.html
index 4e56b81c..4e56b81c 100644
--- a/src/app/libs/managesieve.ui/accounts/accounts.tpl
+++ b/src/app/libs/managesieve.ui/accounts/accounts.html
diff --git a/src/app/libs/managesieve.ui/importer/SieveImportUI.js b/src/app/libs/managesieve.ui/importer/SieveImportUI.js
deleted file mode 100644
index 8bfaeeaa..00000000
--- a/src/app/libs/managesieve.ui/importer/SieveImportUI.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveTemplate */
- /* global SieveIpcClient */
- /* global bootstrap */
-
- /**
- * Imports sieve settings from mailers.
- */
- class SieveImportUI {
-
- /**
- * Shows the import account dialog.
- */
- async show() {
-
- const dialog = await (new SieveTemplate()).load("./importer/account.import.tpl");
- dialog.querySelector(".sieve-import-progress").classList.add("d-none");
- document.querySelector("#ctx").appendChild(dialog);
-
- // we need to call it on the main thread because we don't have
- // to all the libraries we need right here.
- const accounts = await SieveIpcClient.sendMessage("core", "import-thunderbird");
-
- const modal = new bootstrap.Modal(dialog);
-
- await new Promise((resolve) => {
-
- accounts.forEach(async (account) => {
- const item = await (new SieveTemplate()).load("./importer/account.import.item.tpl");
-
- item.querySelector(".sieve-import-username").textContent = account["username"];
- item.querySelector(".sieve-import-hostname").textContent = account["hostname"];
- item.querySelector(".sieve-import-name").textContent = account["name"];
-
- item.querySelector(".sieve-import-source").textContent = "Thunderbird";
-
- item.querySelector(".sieve-import-btn").addEventListener("click", async () => {
-
- dialog.querySelector(".sieve-import-items").classList.add("d-none");
- dialog.querySelector(".sieve-import-progress").classList.remove("d-none");
-
- let account2;
- try {
- account2 = await SieveIpcClient.sendMessage("core", "account-probe", account);
- } catch (ex) {
- alert(`Failed to import ${ex}`);
- resolve(false);
-
- dialog.querySelector(".sieve-import-items").classList.remove("d-none");
- dialog.querySelector(".sieve-import-progress").classList.add("d-none");
- return;
- }
-
- // fix me remove modal2 from dom.
- await SieveIpcClient.sendMessage("core", "account-create", account2);
- modal.hide();
- resolve(true);
- });
-
- dialog.querySelector(".sieve-import-items").appendChild(item);
-
- });
-
- modal.show();
-
- dialog.addEventListener('hidden.bs.modal', () => {
- dialog.parentNode.removeChild(dialog);
- resolve(false);
- });
- });
- }
- }
-
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveImportUI;
- else
- exports.SieveImportUI = SieveImportUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/importer/SieveImportUI.mjs b/src/app/libs/managesieve.ui/importer/SieveImportUI.mjs
new file mode 100644
index 00000000..d0a1b2aa
--- /dev/null
+++ b/src/app/libs/managesieve.ui/importer/SieveImportUI.mjs
@@ -0,0 +1,84 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+
+/**
+ * Imports sieve settings from mailers.
+ */
+class SieveImportUI {
+
+ /**
+ * Shows the import account dialog.
+ */
+ async show() {
+
+ const dialog = await (new SieveTemplate()).load("./importer/account.import.html");
+ dialog.querySelector(".sieve-import-progress").classList.add("d-none");
+ document.querySelector("#ctx").append(dialog);
+
+ // we need to call it on the main thread because we don't have
+ // to all the libraries we need right here.
+ const accounts = await SieveIpcClient.sendMessage("core", "import-thunderbird");
+
+ const modal = new bootstrap.Modal(dialog);
+
+ await new Promise((resolve) => {
+
+ accounts.forEach(async (account) => {
+ const item = await (new SieveTemplate()).load("./importer/account.import.item.html");
+
+ item.querySelector(".sieve-import-username").textContent = account["username"];
+ item.querySelector(".sieve-import-hostname").textContent = account["hostname"];
+ item.querySelector(".sieve-import-name").textContent = account["name"];
+
+ item.querySelector(".sieve-import-source").textContent = "Thunderbird";
+
+ item.querySelector(".sieve-import-btn").addEventListener("click", async () => {
+
+ dialog.querySelector(".sieve-import-items").classList.add("d-none");
+ dialog.querySelector(".sieve-import-progress").classList.remove("d-none");
+
+ let account2;
+ try {
+ account2 = await SieveIpcClient.sendMessage("core", "account-probe", account);
+ } catch (ex) {
+ alert(`Failed to import ${ex}`);
+ resolve(false);
+
+ dialog.querySelector(".sieve-import-items").classList.remove("d-none");
+ dialog.querySelector(".sieve-import-progress").classList.add("d-none");
+ return;
+ }
+
+ // fix me remove modal2 from dom.
+ await SieveIpcClient.sendMessage("core", "account-create", account2);
+ modal.hide();
+ resolve(true);
+ });
+
+ dialog.querySelector(".sieve-import-items").append(item);
+
+ });
+
+ modal.show();
+
+ dialog.addEventListener('hidden.bs.modal', () => {
+ dialog.remove();
+ resolve(false);
+ });
+ });
+ }
+}
+
+export { SieveImportUI };
diff --git a/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.js b/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.js
deleted file mode 100644
index 5a367bda..00000000
--- a/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.js
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const PREF_KEY_ACCOUNTS = '^.*user_pref\\(.*"mail.accountmanager.accounts".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER = '^.*user_pref\\(.*"mail.account.%account%.server".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_TYPE = '^.*user_pref\\(.*"mail.server.%server%.type".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_USERNAME = '^.*user_pref\\(.*"mail.server.%server%.userName".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_HOSTNAME = '^.*user_pref\\(.*"mail.server.%server%.hostname".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_REALUSERNAME = '^.*user_pref\\(.*"mail.server.%server%.realuserName".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_REALHOSTNAME = '^.*user_pref\\(.*"mail.server.%server%.realhostname".*,.*"(.*)"\\);.*$';
- const PREF_KEY_SERVER_NAME = '^.*user_pref\\(.*"mail.server.%server%.name".*,.*"(.*)"\\);.*$';
-
- const FIRST_MATCH = 1;
-
- /**
- * Imports Account settings from thunderbird's profile directory.
- */
- class SieveThunderbirdImport {
-
- /**
- * Parses a section from thunderbird's profile.ini
- * @param {string} section
- * the section which should be parsed.
- * @returns {Struct}
- * an object containing the path as well as the information
- * if the profile is default and the path is relative.
- */
- parseProfileSection(section) {
- const lines = section.split(/\r?\n/g);
-
- let path = "";
- let isRelative = false;
- let isDefault = false;
-
- for (let line of lines) {
- line = line.trim();
-
- if (line.toLocaleLowerCase() === "default=1")
- isDefault = true;
-
- if (line.toLocaleLowerCase().startsWith("path="))
- path = line.split("=")[FIRST_MATCH];
-
- if (line.toLocaleLowerCase() === "isrelative=1")
- isRelative = true;
- }
-
- return {
- "path": path,
- "isRelative": isRelative,
- "isDefault": isDefault
- };
- }
-
- /**
- * Parses Thunderbird's profile.ini and returns the path to the profile.
- * @param {string} [directory]
- * the directory to thunderbird's app data directory.
- * if omitted the directory will be guessed.
- * @returns {string}
- * the path to the default user Profile.
- */
- getDefaultUserProfile(directory) {
- const path = require('path');
- const fs = require('fs');
-
- if (typeof (directory) === "undefined" || directory === null)
- directory = this.getProfileDirectory();
-
- const file = fs.readFileSync(
- path.join(directory, "profiles.ini"), "utf-8");
-
- const sections = file.split(/\[\w*\]/gm);
-
- for (let section of sections) {
-
- section = this.parseProfileSection(section);
-
- if (!section.isDefault)
- continue;
-
- if (section.isRelative)
- return path.join(directory, section.path);
-
- return section.path;
- }
-
- throw new Error("Failed to parse profile.ini");
-
- }
-
- /**
- * Tries to get thunderbird's profile directory.
- *
- * @returns {string}
- * the profile directory
- */
- getProfileDirectory() {
- const path = require('path');
- const fs = require('fs');
-
- let directory;
-
- if (process.platform === "linux")
- directory = path.join(process.env.HOME, ".thunderbird");
- else if (process.platform === "win32")
- directory = path.join(process.env.APPDATA, "Thunderbird");
- else
- throw new Error("Unsupported Platform");
-
- if (!fs.existsSync(directory))
- throw new Error("No file path");
-
- return directory;
- }
-
- /**
- * Tries to read thunderbird's preference file.
- *
- * @returns {string}
- * the current user's preference.js
- */
- getProfile() {
- const path = require('path');
- const fs = require('fs');
-
- const profile = this.getProfileDirectory();
-
- this.getDefaultUserProfile(profile);
-
- return fs.readFileSync(
- path.join(this.getDefaultUserProfile(profile), "prefs.js"), "utf-8");
- }
-
- /**
- * Extracts the given key from the server settings.
- *
- * @param {string} profile
- * the profile data
- * @param {string} server
- * the server's unique name
- * @param {string} key
- * the preference key to be retrieved
- *
- * @returns {string}
- * the key's value or null in case it does not exist.
- */
- getServerKey(profile, server, key) {
- const value = (new RegExp(key.replace("%server%", server), "gm")).exec(profile);
-
- if (!value)
- return null;
-
- return value[FIRST_MATCH];
- }
-
- /**
- * Reads the accounts from thunderbird's preferences file.
- *
- * @returns {object}
- * username and the host for each imap account.
- */
- getAccounts() {
-
- const results = [];
- const profile = this.getProfile();
-
- const accounts = (new RegExp(PREF_KEY_ACCOUNTS, "gm")).exec(profile)[FIRST_MATCH].split(",");
-
- for (const account of accounts) {
-
- const server = (new RegExp(PREF_KEY_SERVER.replace("%account%", account), "gm")).exec(profile)[FIRST_MATCH];
- const type = this.getServerKey(profile, server, PREF_KEY_SERVER_TYPE);
-
- if (type !== "imap")
- continue;
-
- let username = this.getServerKey(profile, server, PREF_KEY_SERVER_REALUSERNAME);
-
- if (!username)
- username = this.getServerKey(profile, server, PREF_KEY_SERVER_USERNAME);
-
- let hostname = this.getServerKey(profile, server, PREF_KEY_SERVER_REALHOSTNAME);
- if (!hostname)
- hostname = this.getServerKey(profile, server, PREF_KEY_SERVER_HOSTNAME);
-
- const name = this.getServerKey(profile, server, PREF_KEY_SERVER_NAME);
-
- const result = {};
-
- result["username"] = username;
- result["hostname"] = hostname;
- result["name"] = name;
-
- results.push(result);
- }
-
- return results;
- }
-
- }
-
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveThunderbirdImport = SieveThunderbirdImport;
- else
- exports.SieveThunderbirdImport = SieveThunderbirdImport;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.mjs b/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.mjs
new file mode 100644
index 00000000..84798411
--- /dev/null
+++ b/src/app/libs/managesieve.ui/importer/SieveThunderbirdImport.mjs
@@ -0,0 +1,208 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const PREF_KEY_ACCOUNTS = '^.*user_pref\\(.*"mail.accountmanager.accounts".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER = '^.*user_pref\\(.*"mail.account.%account%.server".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_TYPE = '^.*user_pref\\(.*"mail.server.%server%.type".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_USERNAME = '^.*user_pref\\(.*"mail.server.%server%.userName".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_HOSTNAME = '^.*user_pref\\(.*"mail.server.%server%.hostname".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_REALUSERNAME = '^.*user_pref\\(.*"mail.server.%server%.realuserName".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_REALHOSTNAME = '^.*user_pref\\(.*"mail.server.%server%.realhostname".*,.*"(.*)"\\);.*$';
+const PREF_KEY_SERVER_NAME = '^.*user_pref\\(.*"mail.server.%server%.name".*,.*"(.*)"\\);.*$';
+
+const FIRST_MATCH = 1;
+
+const path = require('path');
+const fs = require('fs');
+
+/**
+ * Imports Account settings from thunderbird's profile directory.
+ */
+class SieveThunderbirdImport {
+
+ /**
+ * Parses a section from thunderbird's profile.ini
+ * @param {string} section
+ * the section which should be parsed.
+ * @returns {Struct}
+ * an object containing the path as well as the information
+ * if the profile is default and the path is relative.
+ */
+ parseProfileSection(section) {
+ const lines = section.split(/\r?\n/g);
+
+ let profileDir = "";
+ let isRelative = false;
+ let isDefault = false;
+
+ for (let line of lines) {
+ line = line.trim();
+
+ if (line.toLocaleLowerCase() === "default=1")
+ isDefault = true;
+
+ if (line.toLocaleLowerCase().startsWith("path="))
+ profileDir = line.split("=")[FIRST_MATCH];
+
+ if (line.toLocaleLowerCase() === "isrelative=1")
+ isRelative = true;
+ }
+
+ return {
+ "path": profileDir,
+ "isRelative": isRelative,
+ "isDefault": isDefault
+ };
+ }
+
+ /**
+ * Parses Thunderbird's profile.ini and returns the path to the profile.
+ * @param {string} [directory]
+ * the directory to thunderbird's app data directory.
+ * if omitted the directory will be guessed.
+ * @returns {string}
+ * the path to the default user Profile.
+ */
+ getDefaultUserProfile(directory) {
+
+ if (typeof (directory) === "undefined" || directory === null)
+ directory = this.getProfileDirectory();
+
+ const file = fs.readFileSync(
+ path.join(directory, "profiles.ini"), "utf-8");
+
+ const sections = file.split(/\[\w*]/gm);
+
+ for (let section of sections) {
+
+ section = this.parseProfileSection(section);
+
+ if (!section.isDefault)
+ continue;
+
+ if (section.isRelative)
+ return path.join(directory, section.path);
+
+ return section.path;
+ }
+
+ throw new Error("Failed to parse profile.ini");
+
+ }
+
+ /**
+ * Tries to get thunderbird's profile directory.
+ *
+ * @returns {string}
+ * the profile directory
+ */
+ getProfileDirectory() {
+
+ let directory;
+
+ if (process.platform === "linux")
+ directory = path.join(process.env.HOME, ".thunderbird");
+ else if (process.platform === "win32")
+ directory = path.join(process.env.APPDATA, "Thunderbird");
+ else
+ throw new Error("Unsupported Platform");
+
+ if (!fs.existsSync(directory))
+ throw new Error("No file path");
+
+ return directory;
+ }
+
+ /**
+ * Tries to read thunderbird's preference file.
+ *
+ * @returns {string}
+ * the current user's preference.js
+ */
+ getProfile() {
+
+ const profile = this.getProfileDirectory();
+
+ this.getDefaultUserProfile(profile);
+
+ return fs.readFileSync(
+ path.join(this.getDefaultUserProfile(profile), "prefs.js"), "utf-8");
+ }
+
+ /**
+ * Extracts the given key from the server settings.
+ *
+ * @param {string} profile
+ * the profile data
+ * @param {string} server
+ * the server's unique name
+ * @param {string} key
+ * the preference key to be retrieved
+ *
+ * @returns {string}
+ * the key's value or null in case it does not exist.
+ */
+ getServerKey(profile, server, key) {
+ const value = (new RegExp(key.replace("%server%", server), "gm")).exec(profile);
+
+ if (!value)
+ return null;
+
+ return value[FIRST_MATCH];
+ }
+
+ /**
+ * Reads the accounts from thunderbird's preferences file.
+ *
+ * @returns {object}
+ * username and the host for each imap account.
+ */
+ getAccounts() {
+
+ const results = [];
+ const profile = this.getProfile();
+
+ const accounts = (new RegExp(PREF_KEY_ACCOUNTS, "gm")).exec(profile)[FIRST_MATCH].split(",");
+
+ for (const account of accounts) {
+
+ const server = (new RegExp(PREF_KEY_SERVER.replace("%account%", account), "gm")).exec(profile)[FIRST_MATCH];
+ const type = this.getServerKey(profile, server, PREF_KEY_SERVER_TYPE);
+
+ if (type !== "imap")
+ continue;
+
+ let username = this.getServerKey(profile, server, PREF_KEY_SERVER_REALUSERNAME);
+
+ if (!username)
+ username = this.getServerKey(profile, server, PREF_KEY_SERVER_USERNAME);
+
+ let hostname = this.getServerKey(profile, server, PREF_KEY_SERVER_REALHOSTNAME);
+ if (!hostname)
+ hostname = this.getServerKey(profile, server, PREF_KEY_SERVER_HOSTNAME);
+
+ const name = this.getServerKey(profile, server, PREF_KEY_SERVER_NAME);
+
+ const result = {};
+
+ result["username"] = username;
+ result["hostname"] = hostname;
+ result["name"] = name;
+
+ results.push(result);
+ }
+
+ return results;
+ }
+
+}
+
+export { SieveThunderbirdImport };
diff --git a/src/app/libs/managesieve.ui/importer/account.import.tpl b/src/app/libs/managesieve.ui/importer/account.import.html
index b789a59c..4712cbdd 100644
--- a/src/app/libs/managesieve.ui/importer/account.import.tpl
+++ b/src/app/libs/managesieve.ui/importer/account.import.html
@@ -5,7 +5,7 @@
<div class="modal-header">
<h5 class="modal-title" data-i18n="import.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
diff --git a/src/app/libs/managesieve.ui/importer/account.import.item.tpl b/src/app/libs/managesieve.ui/importer/account.import.item.html
index 1231c22a..b652f171 100644
--- a/src/app/libs/managesieve.ui/importer/account.import.item.tpl
+++ b/src/app/libs/managesieve.ui/importer/account.import.item.html
@@ -12,7 +12,7 @@
<label class="col-sm-3 col-form-label" data-i18n="import.username"></label>
<div class="sieve-import-username col-sm-8 col-form-label"></div>
</div>
- <div class="text-right">
+ <div class="text-end">
<button type="button" class="sieve-import-btn btn btn-primary" data-i18n="import.accept"></button>
</div>
</div>
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAccount.js b/src/app/libs/managesieve.ui/settings/logic/SieveAccount.js
deleted file mode 100644
index 8327b6b9..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SieveAccount.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractAccount } = require("./SieveAbstractAccount.js");
-
- // const SievePasswordManager = require('./utils/SievePasswordManager.js');
-
- /**
- * Manages the account specific settings
- */
- class SieveAccount extends SieveAbstractAccount {
-
- /**
- *
- */
- getProxy() {
- return {
- getProxyInfo: function () {
- return null;
- }
- };
- }
-
-
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAccount = SieveAccount;
- else
- exports.SieveAccount = SieveAccount;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAccount.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveAccount.mjs
new file mode 100644
index 00000000..8d2d0012
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveAccount.mjs
@@ -0,0 +1,21 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import { SieveAbstractAccount } from "./SieveAbstractAccount.mjs";
+
+/**
+ * Manages the account specific settings
+ */
+class SieveAccount extends SieveAbstractAccount {
+}
+
+export { SieveAccount };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.js b/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.js
deleted file mode 100644
index 51128584..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.js
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-
-(function (exports) {
-
- "use strict";
-
- const JSON_INDENTATION = 2;
-
- const CONFIG_ID_GLOBAL = "global";
- const CONFIG_KEY_ACCOUNTS = "accounts";
-
- const DEFAULT_AUTHENTICATION = 1;
-
- const SETTINGS_VERSION_I = 1;
-
- const { SieveLogger } = require("./../../utils/SieveLogger.js");
-
- const { SievePrefManager } = require('./SievePrefManager.js');
-
- const { SieveAccount } = require("./SieveAccount.js");
- const { SieveAbstractAccounts } = require("./SieveAbstractAccounts.js");
-
- /**
- * Manages the configuration for sieve accounts.
- * It behaves like a directory. Ist just lists the accounts.
- * The individual settings are managed by the SieveAccount object
- *
- * It uses the DOM's local store to persist the configuration data.
- */
- class SieveAccounts extends SieveAbstractAccounts {
-
- /**
- * @inheritdoc
- */
- async load() {
-
- const items = await (new SievePrefManager(CONFIG_ID_GLOBAL)).getComplexValue(CONFIG_KEY_ACCOUNTS, []);
-
- const accounts = {};
-
- SieveLogger.getInstance().level(await this.getLogLevel());
-
- if (!items)
- return this;
-
- for (const item of items) {
- // Recreate the accounts only when needed...
- if (this.accounts[item])
- accounts[item] = this.accounts[item];
- else
- accounts[item] = new SieveAccount(item);
- }
-
- this.accounts = accounts;
- return this;
- }
-
- /**
- * Saves the list of account configurations.
- *
- * @returns {SieveAccounts}
- * a self reference.
- */
- async save() {
- await (new SievePrefManager(CONFIG_ID_GLOBAL)).setComplexValue(CONFIG_KEY_ACCOUNTS, [...Object.keys(this.accounts)]);
- return this;
- }
-
- /**
- * Creates a new account.
- * The new account will be initialized with default and then added to the list of accounts
- *
- * @param {object} [details]
- * the accounts details like the name, hostname, port and username as key value pairs.
- *
- * @returns {SieveAccounts}
- * a self reference.
- */
- async create(details) {
-
- // create a unique id;
-
- const id = this.generateId();
-
- this.accounts[id] = new SieveAccount(id);
-
- await this.save();
-
- if (typeof (details) === "undefined" || details === null)
- return this;
-
- if ((details.hostname !== null) && (details.hostname !== undefined))
- await (await this.accounts[id].getHost()).setHostname(details.hostname);
-
- if ((details.port !== null) && (details.port !== undefined))
- await (await this.accounts[id].getHost()).setPort(details.port);
-
- if ((details.username !== null) && (details.username !== undefined))
- await (await this.accounts[id].getAuthentication(DEFAULT_AUTHENTICATION)).setUsername(details.username);
-
- if ((details.name !== null) && (details.name !== undefined))
- await (await this.accounts[id].getHost()).setDisplayName(details.name);
-
- return this;
- }
-
- /**
- * Removes the account including all settings.
- *
- * @param {AccountId} id
- * the unique id which identifies the account.
- * @returns {SieveAccounts}
- * a self reference
- */
- async remove(id) {
- // remove the accounts...
- delete this.accounts[id];
- // ... an persist it.
- await this.save();
-
- // remove the account's settings.
- (new SievePrefManager(`@${id}`)).clear();
-
- return this;
- }
-
- /**
- * Imports previously exported account settings.
- *
- * @param {string} data
- * the settings to be imported.
- */
- async import(data) {
- data = JSON.parse(data);
-
- if (data.version !== SETTINGS_VERSION_I)
- throw new Error(`Unknown version ${data.version}`);
-
- const details = {
- name: data.settings["host.displayName"],
- hostname: data.settings["hostname"],
- port: data.settings["port"],
- username: data.settings["authentication.username"]
- };
-
- await this.create(details);
- }
-
- /**
- * Exports the account's settings.
- *
- * @param {string} id
- * the unique account id.
- *
- * @returns {string}
- * the account settings as json string.
- */
- async export(id) {
- const config = new SievePrefManager(`@${id}`);
-
- let data = {};
- for (const key of config.getKeys())
- data[key] = await (config.getValue(key));
-
- data = {
- "version": SETTINGS_VERSION_I,
- "settings": data
- };
-
- return JSON.stringify(
- data, null, JSON_INDENTATION);
- }
- }
-
- // Require modules need to use export.module
- if (module.exports)
- module.exports.SieveAccounts = SieveAccounts;
- else
- exports.SieveAccounts = SieveAccounts;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.mjs
new file mode 100644
index 00000000..789c7984
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveAccounts.mjs
@@ -0,0 +1,180 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+const JSON_INDENTATION = 2;
+
+const CONFIG_ID_GLOBAL = "global";
+const CONFIG_KEY_ACCOUNTS = "accounts";
+
+
+const SETTINGS_VERSION_I = 1;
+
+import { SieveLogger } from "./../../utils/SieveLogger.mjs";
+
+import { SievePrefManager } from "./SievePrefManager.mjs";
+
+import { SieveAccount } from "./SieveAccount.mjs";
+import { SieveAbstractAccounts } from "./SieveAbstractAccounts.mjs";
+
+/**
+ * Manages the configuration for sieve accounts.
+ * It behaves like a directory. Ist just lists the accounts.
+ * The individual settings are managed by the SieveAccount object
+ *
+ * It uses the DOM's local store to persist the configuration data.
+ */
+class SieveAccounts extends SieveAbstractAccounts {
+
+ /**
+ * @inheritdoc
+ */
+ async load() {
+
+ const items = await (new SievePrefManager(CONFIG_ID_GLOBAL)).getComplexValue(CONFIG_KEY_ACCOUNTS, []);
+
+ const accounts = {};
+
+ SieveLogger.getInstance().level(await this.getLogLevel());
+
+ if (!items)
+ return this;
+
+ for (const item of items) {
+ // Recreate the accounts only when needed...
+ if (this.accounts[item])
+ accounts[item] = this.accounts[item];
+ else
+ accounts[item] = new SieveAccount(item);
+ }
+
+ this.accounts = accounts;
+ return this;
+ }
+
+ /**
+ * Saves the list of account configurations.
+ *
+ * @returns {SieveAccounts}
+ * a self reference.
+ */
+ async save() {
+ await (new SievePrefManager(CONFIG_ID_GLOBAL)).setComplexValue(CONFIG_KEY_ACCOUNTS, [...Object.keys(this.accounts)]);
+ return this;
+ }
+
+ /**
+ * Creates a new account.
+ * The new account will be initialized with default and then added to the list of accounts
+ *
+ * @param {object} [details]
+ * the accounts details like the name, hostname, port and username as key value pairs.
+ *
+ * @returns {SieveAccounts}
+ * a self reference.
+ */
+ async create(details) {
+
+ // create a unique id;
+
+ const id = this.generateId();
+
+ this.accounts[id] = new SieveAccount(id);
+
+ await this.save();
+
+ if (typeof (details) === "undefined" || details === null)
+ return this;
+
+ if ((details.hostname !== null) && (details.hostname !== undefined))
+ await (await this.accounts[id].getHost()).setHostname(details.hostname);
+
+ if ((details.port !== null) && (details.port !== undefined))
+ await (await this.accounts[id].getHost()).setPort(details.port);
+
+ if ((details.username !== null) && (details.username !== undefined))
+ await (await this.accounts[id].getAuthentication()).setUsername(details.username);
+
+ if ((details.name !== null) && (details.name !== undefined))
+ await (await this.accounts[id].getHost()).setDisplayName(details.name);
+
+ return this;
+ }
+
+ /**
+ * Removes the account including all settings.
+ *
+ * @param {AccountId} id
+ * the unique id which identifies the account.
+ * @returns {SieveAccounts}
+ * a self reference
+ */
+ async remove(id) {
+ // remove the accounts...
+ delete this.accounts[id];
+ // ... an persist it.
+ await this.save();
+
+ // remove the account's settings.
+ (new SievePrefManager(`@${id}`)).clear();
+
+ return this;
+ }
+
+ /**
+ * Imports previously exported account settings.
+ *
+ * @param {string} data
+ * the settings to be imported.
+ */
+ async import(data) {
+ data = JSON.parse(data);
+
+ if (data.version !== SETTINGS_VERSION_I)
+ throw new Error(`Unknown version ${data.version}`);
+
+ const details = {
+ name: data.settings["host.displayName"],
+ hostname: data.settings["hostname"],
+ port: data.settings["port"],
+ username: data.settings["authentication.username"]
+ };
+
+ await this.create(details);
+ }
+
+ /**
+ * Exports the account's settings.
+ *
+ * @param {string} id
+ * the unique account id.
+ *
+ * @returns {string}
+ * the account settings as json string.
+ */
+ async export(id) {
+ const config = new SievePrefManager(`@${id}`);
+
+ let data = {};
+ for (const key of config.getKeys())
+ data[key] = await (config.getValue(key));
+
+ data = {
+ "version": SETTINGS_VERSION_I,
+ "settings": data
+ };
+
+ return JSON.stringify(
+ data, null, JSON_INDENTATION);
+ }
+}
+
+export { SieveAccounts };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs
new file mode 100644
index 00000000..63c2267f
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs
@@ -0,0 +1,182 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import { SieveAbstractAuthentication } from "./SieveAbstractAuthentication.mjs";
+
+import { SieveIpcClient } from "./../../utils/SieveIpcClient.mjs";
+
+const KEY_USERNAME = "authentication.username";
+
+/**
+ * Prompts for a password.
+ */
+class SieveElectronAuthentication extends SieveAbstractAuthentication {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(account) {
+ super(account);
+ this.canStore = null;
+ }
+ /**
+ * Sets the username.
+ *
+ * @param {string} username
+ * the username as string, can not be null.
+ *
+ */
+ async setUsername(username) {
+ await this.account.getConfig().setString(KEY_USERNAME, username);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getUsername() {
+ return await this.account.getConfig().getString(KEY_USERNAME);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ hasPassword() {
+ return true;
+ }
+
+ /**
+ * Checks if the system key store can be accessed from this app.
+ * It could be unavailable due to missing libraries or because
+ * it has been disabled by the IT administrator.
+ *
+ * @returns {boolean}
+ * true in case a password can be stored otherwise false.
+ */
+ async canStorePassword() {
+ if (typeof (this.canStore) === "undefined" || this.canStore === null) {
+ this.canStore = await SieveIpcClient.sendMessage("core", "keystore-ready", "", window);
+ }
+
+ return (this.canStore === true);
+ }
+
+ /**
+ * Forgets any passwords remembered for this username.
+ * It will fail silently.
+ */
+ async forget() {
+ try {
+ if (!await this.canStorePassword())
+ return;
+
+ const username = await this.getUsername();
+
+ await SieveIpcClient.sendMessage(
+ "core", "keystore-forget", { "username": username }, window);
+
+ } catch (ex) {
+ this.account.getLogger().logAction("Forgetting password failed " + ex);
+ }
+ }
+
+ /**
+ * Stores a password in the system wide key store.
+ * It will fail silently in case storing the password failed.
+ *
+ * @param {string} password
+ * the password to be stored.
+ */
+ async setStoredPassword(password) {
+ try {
+ if (! await this.canStorePassword())
+ return;
+
+ const username = await this.getUsername();
+
+ await SieveIpcClient.sendMessage(
+ "core", "keystore-store", { "username": username, "password": password }, window);
+
+ } catch (ex) {
+ this.account.getLogger().logAction("Storing password failed " + ex);
+ }
+ }
+
+ /**
+ * Gets the stored password
+ *
+ * @returns {string}
+ * the password or null in case it does not exist.
+ */
+ async getStoredPassword() {
+ try {
+ if (!await this.canStorePassword())
+ return null;
+
+ const username = await this.getUsername();
+
+ return await SieveIpcClient.sendMessage(
+ "core", "keystore-get", { "username": username }, window);
+
+ } catch (ex) {
+ this.account.getLogger().logAction("Getting password failed " + ex);
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if the password is stored in the cert store.
+ *
+ * @returns {boolean}
+ * true in case a password is stored.
+ */
+ async hasStoredPassword() {
+ try {
+ if (!await this.canStorePassword())
+ return false;
+
+ if (await this.getStoredPassword() === null)
+ return false;
+
+ return true;
+ } catch (ex) {
+ this.account.getLogger().logAction("Checking for stored password failed " + ex);
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getPassword() {
+
+ if (await this.hasStoredPassword())
+ return await this.getStoredPassword();
+
+ const request = {
+ "username": await this.getUsername(),
+ "displayname": await (await this.account.getHost()).getDisplayName(),
+ "remember": await this.canStorePassword()
+ };
+
+ const credentials = await SieveIpcClient.sendMessage(
+ "accounts", "account-show-authentication", request);
+
+ if (credentials.remember)
+ await this.setStoredPassword(credentials.password);
+
+ return credentials.password;
+ }
+}
+
+export { SieveElectronAuthentication as SieveAuthentication };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAuthenticationSettings.js b/src/app/libs/managesieve.ui/settings/logic/SieveAuthenticationSettings.js
deleted file mode 100644
index d5bd53a7..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SieveAuthenticationSettings.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const AUTH_TYPE_PROMPT = 0;
- const DEFAULT_AUTH_TYPE = AUTH_TYPE_PROMPT;
-
- const CONFIG_AUTHENTICATION_TYPE = "activeLogin";
-
- const { SieveAbstractAuthentication } = require("./SieveAbstractAuthentication.js");
- const { SieveAbstractMechanism } = require("./SieveAbstractMechanism.js");
-
- const { SieveIpcClient } = require("./../../utils/SieveIpcClient.js");
-
- let keytar = null;
-
- /**
- * Prompts for a password.
- */
- class SievePromptAuthentication extends SieveAbstractAuthentication {
-
- /**
- * Sets the username.
- *
- * @param {string} username
- * the username as string, can not be null.
- *
- */
- async setUsername(username) {
- await this.account.getConfig().setString("authentication.username", username);
- }
-
- /**
- * @inheritdoc
- */
- async getUsername() {
- return await this.account.getConfig().getString("authentication.username");
- }
-
- /**
- * @inheritdoc
- */
- hasPassword() {
- return true;
- }
-
- /**
- * Checks if the system key store can be accessed from this app.
- * It could be unavailable due to missing libraries or because
- * it has been disabled by the IT administrator.
- *
- * @returns {boolean}
- * true in case a password can be stored otherwise false.
- */
- canStorePassword() {
- const keystore = this.getKeyStore();
-
- if (typeof (keystore) === "undefined" || keystore === null)
- return false;
-
- return true;
- }
-
- /**
- * Returns the system wide keystore.
- * There is no guarantee that it exists.
- * It may fail due to missing external dependencies or because it was
- * just disabled by the administrator.
- *
- * @returns {Keytar}
- * the keytar object.
- */
- getKeyStore() {
- if (typeof (keytar) !== "undefined" && keytar !== null)
- return keytar;
-
- try {
- keytar = require("./../../../keytar");
- } catch (ex) {
- this.account.getLogger().logAction("Could not initialize keystore: " + ex);
- }
-
- return keytar;
- }
-
- /**
- * Forgets any passwords remembered for this username.
- * It will fail silently.
- */
- async forget() {
- try {
- if (!this.canStorePassword())
- return;
-
- const username = await this.getUsername();
- await this.getKeyStore().deletePassword("Sieve Editor", username);
- } catch (ex) {
- this.account.getLogger().logAction("Forgetting password failed " + ex);
- }
- }
-
- /**
- * Stores a password in the system wide key store.
- * It will fail silently in case storing the password failed.
- *
- * @param {string} password
- * the password to be stored.
- */
- async setStoredPassword(password) {
- try {
- if (!this.canStorePassword())
- return;
-
- const username = await this.getUsername();
- await this.getKeyStore().setPassword("Sieve Editor", username, password);
- } catch (ex) {
- this.account.getLogger().logAction("Storing password failed " + ex);
- }
- }
-
- /**
- * Gets the stored password
- *
- * @returns {string}
- * the password or null in case it does not exist.
- */
- async getStoredPassword() {
- try {
- if (!this.canStorePassword())
- return null;
-
- const username = await this.getUsername();
- return await this.getKeyStore().getPassword("Sieve Editor", username);
- } catch (ex) {
- this.account.getLogger().logAction("Getting password failed " + ex);
- }
-
- return null;
- }
-
- /**
- * Checks if the password is stored in the cert store.
- *
- * @returns {boolean}
- * true in case a password is stored.
- */
- async hasStoredPassword() {
- try {
- if (!this.canStorePassword())
- return false;
-
- const username = await this.getUsername();
-
- if (await this.getKeyStore().getPassword("Sieve Editor", username) === null)
- return false;
-
- return true;
- } catch (ex) {
- this.account.getLogger().logAction("Checking for stored password failed " + ex);
- }
-
- return false;
- }
-
- /**
- * @inheritdoc
- */
- async getPassword() {
-
- if (await this.hasStoredPassword())
- return await this.getStoredPassword();
-
- const request = {
- "username": await this.getUsername(),
- "displayname": await (await this.account.getHost()).getDisplayName(),
- "remember": this.canStorePassword()
- };
-
- const credentials = await SieveIpcClient.sendMessage(
- "accounts", "account-show-authentication", request);
-
- if (credentials.remember)
- await this.setStoredPassword(credentials.password);
-
- return credentials.password;
- }
- }
-
- /**
- * Manages the authorization settings.
- */
- class SieveAuthentication extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return DEFAULT_AUTH_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_AUTHENTICATION_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- hasMechanism(type) {
- switch (type) {
- case AUTH_TYPE_PROMPT:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- **/
- getMechanismById(type) {
- switch (type) {
- case AUTH_TYPE_PROMPT:
- // fall through we just implement prompt authentication
- default:
- return new SievePromptAuthentication(AUTH_TYPE_PROMPT, this.account);
- }
- }
- }
-
- exports.SieveAuthentication = SieveAuthentication;
-
-})(module.exports);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs
new file mode 100644
index 00000000..aafe83c9
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs
@@ -0,0 +1,112 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const CONFIG_AUTHORIZATION_TYPE_NONE = 0;
+const CONFIG_AUTHORIZATION_TYPE_SIEVE = 1;
+const CONFIG_AUTHORIZATION_TYPE_PROMPT = 2;
+const CONFIG_AUTHORIZATION_TYPE_CUSTOM = 3;
+
+const DEFAULT_AUTHORIZATION_TYPE = CONFIG_AUTHORIZATION_TYPE_SIEVE;
+
+const CONFIG_AUTHORIZATION_TYPE = "authorization.type";
+
+import {
+ SieveNoAuthorization,
+ SieveCustomAuthorization,
+ SieveDefaultAuthorization,
+ SieveAbstractAuthorization
+} from "./SieveAbstractAuthorization.mjs";
+
+import { SieveAbstractMechanism } from "./SieveAbstractMechanism.mjs";
+import { SieveIpcClient } from "./../../utils/SieveIpcClient.mjs";
+
+/**
+ * Shows a dialog and prompts for the authorization.
+ */
+class SievePromptAuthorization extends SieveAbstractAuthorization {
+
+ /**
+ * @inheritdoc
+ */
+ getType() {
+ return CONFIG_AUTHORIZATION_TYPE_PROMPT;
+ }
+
+ /**
+ * Shows a dialog asking for the authorization.
+ * @returns {string}
+ * the authorization string or null in case the dialog was canceled.
+ */
+ async getAuthorization() {
+ const name = await (await this.account.getHost()).getDisplayName();
+
+ return await SieveIpcClient.sendMessage(
+ "accounts", "account-show-authorization",
+ { "displayname": name });
+ }
+}
+
+/**
+ * Manages the authorization settings.
+ */
+class SieveAuthorization extends SieveAbstractMechanism {
+
+ /**
+ * @inheritdoc
+ **/
+ getDefault() {
+ return DEFAULT_AUTHORIZATION_TYPE;
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ getKey() {
+ return CONFIG_AUTHORIZATION_TYPE;
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ hasMechanism(type) {
+ switch (type) {
+ case CONFIG_AUTHORIZATION_TYPE_NONE:
+ case CONFIG_AUTHORIZATION_TYPE_SIEVE:
+ case CONFIG_AUTHORIZATION_TYPE_PROMPT:
+ case CONFIG_AUTHORIZATION_TYPE_CUSTOM:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ getMechanismById(type) {
+ switch (type) {
+ case CONFIG_AUTHORIZATION_TYPE_NONE:
+ return new SieveNoAuthorization(CONFIG_AUTHORIZATION_TYPE_NONE, this.account);
+ case CONFIG_AUTHORIZATION_TYPE_SIEVE:
+ return new SieveDefaultAuthorization(CONFIG_AUTHORIZATION_TYPE_SIEVE, this.account);
+ case CONFIG_AUTHORIZATION_TYPE_PROMPT:
+ return new SievePromptAuthorization(CONFIG_AUTHORIZATION_TYPE_PROMPT, this.account);
+ case CONFIG_AUTHORIZATION_TYPE_CUSTOM:
+ return new SieveCustomAuthorization(CONFIG_AUTHORIZATION_TYPE_CUSTOM, this.account);
+
+ default:
+ throw new Error("Unknown authorization mechanism");
+ }
+ }
+}
+
+export { SieveAuthorization };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveAuthorizationSettings.js b/src/app/libs/managesieve.ui/settings/logic/SieveAuthorizationSettings.js
deleted file mode 100644
index c77ddc80..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SieveAuthorizationSettings.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const CONFIG_AUTHORIZATION_TYPE_NONE = 0;
- const CONFIG_AUTHORIZATION_TYPE_SIEVE = 1;
- const CONFIG_AUTHORIZATION_TYPE_PROMPT = 2;
- const CONFIG_AUTHORIZATION_TYPE_CUSTOM = 3;
-
- const DEFAULT_AUTHORIZATION_TYPE = CONFIG_AUTHORIZATION_TYPE_SIEVE;
-
- const CONFIG_AUTHORIZATION_TYPE = "authorization.type";
-
- const {
- SieveNoAuthorization,
- SieveCustomAuthorization,
- SieveDefaultAuthorization,
- SieveAbstractAuthorization
- } = require("./SieveAbstractAuthorization.js");
-
- const { SieveAbstractMechanism } = require("./SieveAbstractMechanism.js");
-
- const { SieveIpcClient} = require("./../../utils/SieveIpcClient.js");
-
- /**
- * Shows a dialog and prompts for the authorization.
- */
- class SievePromptAuthorization extends SieveAbstractAuthorization {
-
- /**
- * @inheritdoc
- */
- getType() {
- return CONFIG_AUTHORIZATION_TYPE_PROMPT;
- }
-
- /**
- * Shows a dialog asking for the authorization.
- * @returns {string}
- * the authorization string or null in case the dialog was canceled.
- */
- async getAuthorization() {
- const name = await (await this.account.getHost()).getDisplayName();
-
- return await SieveIpcClient.sendMessage(
- "accounts", "account-show-authorization",
- { "displayname": name });
- }
- }
-
- /**
- * Manages the authorization settings.
- */
- class SieveAuthorization extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return DEFAULT_AUTHORIZATION_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_AUTHORIZATION_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- hasMechanism(type) {
- switch (type) {
- case CONFIG_AUTHORIZATION_TYPE_NONE:
- case CONFIG_AUTHORIZATION_TYPE_SIEVE:
- case CONFIG_AUTHORIZATION_TYPE_PROMPT:
- case CONFIG_AUTHORIZATION_TYPE_CUSTOM:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- **/
- getMechanismById(type) {
- switch (type) {
- case CONFIG_AUTHORIZATION_TYPE_NONE:
- return new SieveNoAuthorization(CONFIG_AUTHORIZATION_TYPE_NONE, this.account);
- case CONFIG_AUTHORIZATION_TYPE_SIEVE:
- return new SieveDefaultAuthorization(CONFIG_AUTHORIZATION_TYPE_SIEVE, this.account);
- case CONFIG_AUTHORIZATION_TYPE_PROMPT:
- return new SievePromptAuthorization(CONFIG_AUTHORIZATION_TYPE_PROMPT, this.account);
- case CONFIG_AUTHORIZATION_TYPE_CUSTOM:
- return new SieveCustomAuthorization(CONFIG_AUTHORIZATION_TYPE_CUSTOM, this.account);
-
- default:
- throw new Error("Unknown authorization mechanism");
- }
- }
- }
-
- exports.SieveAuthorization = SieveAuthorization;
-
-})(module.exports);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveHost.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveHost.mjs
new file mode 100644
index 00000000..4b10db7d
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveHost.mjs
@@ -0,0 +1,145 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const CONFIG_KEEP_ALIVE_INTERVAL = "keepalive";
+// eslint-disable-next-line no-magic-numbers
+const ONE_MINUTE = 60 * 1000;
+// eslint-disable-next-line no-magic-numbers
+const FIVE_MINUTES = 5 * ONE_MINUTE;
+
+import { SieveCustomHost } from "./SieveAbstractHost.mjs";
+
+/**
+ * Extends the CustomHost implementation by a display name and fingerprint setting
+ **/
+class SieveElectronHost extends SieveCustomHost {
+
+ /**
+ * @inheritdoc
+ **/
+ async getHostname() {
+ return await this.account.getConfig().getString("hostname", "");
+ }
+
+ /**
+ * Sets the custom hostname which shall be used.
+ *
+ * @param {string} hostname
+ * the hostname or ip as string.
+ *
+ * @returns {SieveElectronHost}
+ * a self reference
+ */
+ async setHostname(hostname) {
+ await this.account.getConfig().setString("hostname", hostname);
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ async getDisplayName() {
+ return await this.account.getConfig().getString("host.displayName", "Unnamed Account");
+ }
+
+ /**
+ * Sets the account display name.
+ * @param {string} value
+ * sets the account's display name
+ * @returns {SieveCustomHostEx}
+ * a self reference
+ */
+ async setDisplayName(value) {
+ await this.account.getConfig().setString("host.displayName", value);
+ return this;
+ }
+
+ /**
+ * Configures the maximum idle time after a message is send.
+ * In case the time span elapsed an keep alive message will be
+ * send to the server.
+ *
+ * @param {int} value
+ * the maximal time in seconds. zero disables keep alive messages
+ *
+ * @returns {SieveAbstractHost}
+ * a self reference
+ */
+ async setKeepAlive(value) {
+ await this.account.getConfig().setInteger(CONFIG_KEEP_ALIVE_INTERVAL, value);
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getKeepAlive() {
+ return await this.account.getConfig().getInteger(CONFIG_KEEP_ALIVE_INTERVAL, FIVE_MINUTES);
+ }
+
+ /**
+ * Each certificate has a unique fingerprint.
+ *
+ * Normally this fingerprint is not used directly.
+ * But in case no chain of trust can be established,
+ * the typical fallback is to verify the fingerprint.
+ *
+ * This is normal case for a self signed certificate.
+ *
+ * @returns {string}
+ * the accounts fingerprint or an empty string in case no fingerprint is stored.
+ **/
+ async getFingerprint() {
+ return await this.account.getConfig().getString("host.fingerprint", "");
+ }
+
+ /**
+ * Sets the account's fingerprint.
+ *
+ * @param {string} value
+ * the accounts fingerprint, or pass an empty string to disable.
+ * @returns {SieveCustomHostEx}
+ * a self reference
+ */
+ async setFingerprint(value) {
+ await this.account.getConfig().setString("host.fingerprint", value);
+ return this;
+ }
+
+ /**
+ * Gets the certificate errors to ignore.
+ *
+ * @returns {string}
+ * the node js error code to ignore as string or an empty string.
+ */
+ async getIgnoreCertErrors() {
+ return await this.account.getConfig().getString("host.ignoreCertErrors", "");
+ }
+
+ /**
+ * Defines which certificate error code should be ignored.
+ *
+ * In general it is not a good idea to ignore certificate errors.
+ * But there are some exception: e.g. a self signed error
+ * after you verified the certificates fingerprint.
+ *
+ * @param {string} errorCode
+ * the node js error code or an empty string to disable.
+ * @returns {SieveCustomHostEx}
+ * a self reference
+ */
+ async setIgnoreCertErrors(errorCode) {
+ await this.account.getConfig().setString("host.ignoreCertErrors", errorCode);
+ return this;
+ }
+}
+
+export { SieveElectronHost as SieveHost };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveHostSettings.js b/src/app/libs/managesieve.ui/settings/logic/SieveHostSettings.js
deleted file mode 100644
index bff61c1f..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SieveHostSettings.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const HOST_TYPE_CUSTOM = 1;
-
- const CONFIG_HOST_TYPE = "activeHost";
-
- const { SieveAbstractMechanism } = require("./SieveAbstractMechanism.js");
- const { SieveCustomHost } = require("./SieveAbstractHost.js");
-
- /**
- * Extends the CustomHost implementation by a display name and fingerprint setting
- **/
- class SieveCustomHostEx extends SieveCustomHost {
-
- /**
- * The human readable display name for this account.
- * It can be any valid javascript string.
- *
- * @returns {string}
- * the display name
- **/
- async getDisplayName() {
- return await this.account.getConfig().getString("host.displayName", "Unnamed Account");
- }
-
- /**
- * Sets the account display name.
- * @param {string} value
- * sets the account's display name
- * @returns {SieveCustomHostEx}
- * a self reference
- */
- async setDisplayName(value) {
- await this.account.getConfig().setString("host.displayName", value);
- return this;
- }
-
- /**
- * Each certificate has a unique fingerprint.
- *
- * Normally this fingerprint is not used directly.
- * But in case no chain of trust can be established,
- * the typical fallback is to verify the fingerprint.
- *
- * This is normal case for a self signed certificate.
- *
- * @returns {string}
- * the accounts fingerprint or an empty string in case no fingerprint is stored.
- **/
- async getFingerprint() {
- return await this.account.getConfig().getString("host.fingerprint", "");
- }
-
- /**
- * Sets the account's fingerprint.
- *
- * @param {string} value
- * the accounts fingerprint, or pass an empty string to disable.
- * @returns {SieveCustomHostEx}
- * a self reference
- */
- async setFingerprint(value) {
- await this.account.getConfig().setString("host.fingerprint", value);
- return this;
- }
-
- /**
- * Gets the certificate errors to ignore.
- *
- * @returns {string}
- * the node js error code to ignore as string or an empty string.
- */
- async getIgnoreCertErrors() {
- return await this.account.getConfig().getString("host.ignoreCertErrors", "");
- }
-
- /**
- * Defines which certificate error code should be ignored.
- *
- * In general it is not a good idea to ignore certificate errors.
- * But there are some exception: e.g. a self signed error
- * after you verified the certificates fingerprint.
- *
- * @param {string} errorCode
- * the node js error code or an empty string to disable.
- * @returns {SieveCustomHostEx}
- * a self reference
- */
- async setIgnoreCertErrors(errorCode) {
- await this.account.getConfig().setString("host.ignoreCertErrors", errorCode);
- return this;
- }
- }
-
- /**
- * A transparent wrapper needed to deal with the different
- * host mechanism which are provided by electron and thunderbird.
- **/
- class SieveHost extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_HOST_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return HOST_TYPE_CUSTOM;
- }
-
- /**
- * @inheritdoc
- */
- hasMechanism(type) {
- switch (type) {
- case HOST_TYPE_CUSTOM:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- */
- getMechanismById(type) {
-
- switch (type) {
- default:
- return new SieveCustomHostEx(HOST_TYPE_CUSTOM, this.account);
- }
- }
- }
-
- exports.SieveHost = SieveHost;
-
-})(module.exports);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.js b/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.js
deleted file mode 100644
index b01f05ce..00000000
--- a/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractPrefManager } = require("./SieveAbstractPrefManager.js");
-
- /**
- * Manages preferences.
- * It uses the DOM's local storage interface
- */
- class SievePrefManager extends SieveAbstractPrefManager {
-
- /**
- * Returns all the keys contained by this namespace.
- * The keys are returned without any namespace prefix.
- *
- * @returns {Set}
- * a set with all key names.
- */
- getKeys() {
- const keys = new Set();
-
- const namespace = `${this.getNamespace()}.`;
-
- for (let idx = 0; idx < localStorage.length; idx++) {
- const key = localStorage.key(idx);
- if (key.startsWith(namespace))
- keys.add(key.substring(namespace.length));
- }
-
- return keys;
- }
-
- /**
- * Clears the complete name space.
- */
- clear() {
- const keys = this.getKeys();
-
- for (const key of keys)
- localStorage.removeItem(`${this.getNamespace()}.${key}`);
- }
-
- /**
- * Returns a specific value.
- * @param {string} key
- * the key which should be returned.
- * @returns {object}
- * the value or undefined in case it does not exist.
- */
- async getValue(key) {
- return await localStorage.getItem(`${this.getNamespace()}.${key}`);
- }
-
- /**
- * Sets and persists the given preference.
- *
- * @param {string} key
- * the preference key which should be written.
- * @param {object} value
- * the key's value.
- * @returns {SievePrefManager}
- * a self reference.
- */
- async setValue(key, value) {
- await localStorage.setItem(`${this.getNamespace()}.${key}`, value);
- return this;
- }
- }
-
- if (typeof(module) !== "undefined" && module && module.exports)
- module.exports.SievePrefManager = SievePrefManager;
- else
- exports.SievePrefManager = SievePrefManager;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.mjs b/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.mjs
new file mode 100644
index 00000000..81255305
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SievePrefManager.mjs
@@ -0,0 +1,79 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import { SieveAbstractPrefManager } from "./SieveAbstractPrefManager.mjs";
+
+/**
+ * Manages preferences.
+ * It uses the DOM's local storage interface
+ */
+class SieveElectronPrefManager extends SieveAbstractPrefManager {
+
+ /**
+ * Returns all the keys contained by this namespace.
+ * The keys are returned without any namespace prefix.
+ *
+ * @returns {Set}
+ * a set with all key names.
+ */
+ getKeys() {
+ const keys = new Set();
+
+ const namespace = `${this.getNamespace()}.`;
+
+ for (let idx = 0; idx < localStorage.length; idx++) {
+ const key = localStorage.key(idx);
+ if (key.startsWith(namespace))
+ keys.add(key.substring(namespace.length));
+ }
+
+ return keys;
+ }
+
+ /**
+ * Clears the complete name space.
+ */
+ clear() {
+ const keys = this.getKeys();
+
+ for (const key of keys)
+ localStorage.removeItem(`${this.getNamespace()}.${key}`);
+ }
+
+ /**
+ * Returns a specific value.
+ * @param {string} key
+ * the key which should be returned.
+ * @returns {object}
+ * the value or undefined in case it does not exist.
+ */
+ async getValue(key) {
+ return await localStorage.getItem(`${this.getNamespace()}.${key}`);
+ }
+
+ /**
+ * Sets and persists the given preference.
+ *
+ * @param {string} key
+ * the preference key which should be written.
+ * @param {object} value
+ * the key's value.
+ * @returns {SievePrefManager}
+ * a self reference.
+ */
+ async setValue(key, value) {
+ await localStorage.setItem(`${this.getNamespace()}.${key}`, value);
+ return this;
+ }
+}
+
+export { SieveElectronPrefManager as SievePrefManager };
diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs
new file mode 100644
index 00000000..46e43ec2
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs
@@ -0,0 +1,66 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const PREF_MECHANISM = "security.mechanism";
+const PREF_TLS = "security.tls";
+
+import { SieveAbstractSecurity } from "./SieveAbstractSecurity.mjs";
+
+/**
+ * Manages the account's security related settings
+ */
+class SieveSecurity extends SieveAbstractSecurity {
+
+ /**
+ * @inheritdoc
+ */
+ async getMechanism() {
+ return await this.account.getConfig().getString(PREF_MECHANISM, "default");
+ }
+
+ /**
+ * Sets the sasl mechanism.
+ *
+ * @param {string} mechanism
+ * the sasl mechanism which should be used.
+ *
+ * @returns {SieveSecurity}
+ * a self reference
+ */
+ async setMechanism(mechanism) {
+ await this.account.getConfig().setString(PREF_MECHANISM, mechanism);
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async isSecure() {
+ return await this.account.getConfig().getBoolean(PREF_TLS, true);
+ }
+
+ /**
+ * Defines if a secure connections shall be used.
+ *
+ * @param {boolean} value
+ * set to true for a secure connection.
+ *
+ * @returns {SieveSecurity}
+ * a self reference
+ */
+ async setSecure(value) {
+ await this.account.getConfig().setBoolean(PREF_TLS, value);
+ return this;
+ }
+
+}
+
+export { SieveSecurity };
diff --git a/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.js b/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.js
deleted file mode 100644
index 04890b65..00000000
--- a/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.js
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveTemplate */
-
- /**
- * A UI renderer for the sieve settings dialog
- */
- class SieveCredentialsSettingsUI {
-
- /**
- * Initializes the settings
- * @param {SieveAccount} account
- * the account for which the settings edited.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * Sets the authentication type
- *
- * @param {string} type
- * the authentication type which should be used
- * @returns {SieveCredentialsUI}
- * a self reference
- */
- setSaslMechanism(type) {
-
- const parent = this.getDialog();
-
- const text = parent
- .querySelector(".sieve-settings-authentication")
- .querySelector(`.dropdown-item[data-sieve-authentication="${type}"]`)
- .textContent;
-
- parent
- .querySelector(".sieve-settings-authentication button")
- .dataset.sieveAuthentication = type;
-
- parent
- .querySelector(".sieve-settings-authentication button")
- .textContent = text;
-
- return this;
- }
-
- /**
- * The authentication type which was selected
- *
- * @returns {string}
- * authentication type
- */
- getSaslMechanism() {
- return this.getDialog()
- .querySelector(".sieve-settings-authentication button")
- .dataset.sieveAuthentication;
- }
-
- /**
- * Sets the username in the ui
- *
- * @param {string} username
- * the username which should be set
- * @returns {SieveSettingsUI}
- * a self reference
- */
- setAuthentication(username) {
- this.getDialog()
- .querySelector(".sieve-settings-username").value = username;
-
- return this;
- }
-
- /**
- * Gets the username from the ui.
- *
- * @returns {string}
- * the username as string.
- */
- getAuthentication() {
- return this.getDialog()
- .querySelector(".sieve-settings-username").value;
- }
-
- /**
- * Selects the given authorization type in the dropdown control.
- *
- * @param {int|string} type
- * the authorization type to be activated.
- * @returns {SieveCredentialsSettingsUI}
- * a self reference.
- */
- setAuthorizationType(type) {
- const dialog = this.getDialog();
-
- const text = dialog
- .querySelector(".sieve-settings-authorization")
- .querySelector(`.dropdown-item[data-sieve-authorization="${type}"]`)
- .textContent;
-
- dialog
- .querySelector(".sieve-settings-authorization button")
- .dataset.sieveAuthorization = type;
-
- dialog
- .querySelector(".sieve-settings-authorization button")
- .textContent = text;
-
- if (`${type}` === "3")
- dialog.querySelector(".sieve-settings-authorization-username").classList.remove("d-none");
- else
- dialog.querySelector(".sieve-settings-authorization-username").classList.add("d-none");
-
- return this;
- }
-
- /**
- * Gets the current authorization type set in the dropdown menu.
- *
- * @returns {string}
- * the authorization type as string.
- */
- getAuthorizationType() {
- return this.getDialog()
- .querySelector(".sieve-settings-authorization button")
- .dataset.sieveAuthorization;
- }
-
- /**
- * Sets the authorization name.
- * Authorization allows an authenticated user (normally admin)
- * to access an other users sieve account.
- *
- * @param {string} username
- * the username as which the current user should authorized
- * @returns {SieveSettingsUI}
- * a self reference
- */
- setAuthorization(username) {
- this.getDialog()
- .querySelector(".sieve-settings-text-authorization-username").value = username;
-
- return this;
- }
-
- /**
- * Gets the authorization name from the ui.
- *
- * @returns {string}
- * the authorized username.
- */
- getAuthorization() {
- return this.getDialog()
- .querySelector(".sieve-settings-text-authorization-username").value;
- }
-
- /**
- * Gets the current dialogs encryption settings.
- *
- * @returns {boolean}
- * true in case an encrypted connection should be used otherwise false.
- */
- isEncrypted() {
-
- if (this.getDialog().querySelector("#sieve-settings-encryption-off").checked)
- return false;
-
- return true;
- }
-
- /**
- * Sets the encryption settings in the current dialog.
- *
- * @param {boolean} encrypted
- * the encryption status to set. False in case encryption is disabled
- * otherwise it will be enabled
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setEncrypted(encrypted) {
- const parent = this.getDialog();
-
- if (encrypted === false)
- parent.querySelector("#sieve-settings-encryption-off").checked = true;
- else
- parent.querySelector("#sieve-settings-encryption-on").checked = true;
-
- return this;
- }
-
- /**
- * Shows the advanced setting
- */
- showAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").classList.remove("d-none");
- parent.querySelector(".siv-settings-show-advanced").classList.add("d-none");
- parent.querySelector(".siv-settings-hide-advanced").classList.remove("d-none");
- }
-
- /**
- * Hides the advanced settings
- */
- hideAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").classList.add("d-none");
- parent.querySelector(".siv-settings-show-advanced").classList.remove("d-none");
- parent.querySelector(".siv-settings-hide-advanced").classList.add("d-none");
- }
-
- /**
- * Shows the settings dialog
- *
- * @returns {boolean}
- * true in case new settings where applied.
- * false in case the dialog was canceled.
- */
- async show() {
-
- document.querySelector("#ctx").appendChild(
- await (new SieveTemplate()).load("./settings/ui/settings.credentials.tpl"));
-
- await this.render();
-
- const dialog = this.getDialog();
- const modal = new bootstrap.Modal(dialog);
-
- modal.show();
-
- dialog
- .querySelector(".sieve-settings-apply")
- .addEventListener("click", async () => {
- await this.save();
- modal.hide();
- });
-
- return await new Promise((resolve) => {
-
- dialog.addEventListener('hidden.bs.modal', () => {
- modal.dispose();
- dialog.parentElement.removeChild(dialog);
-
- resolve();
- });
-
- });
- }
-
- /**
- * Validates and saves the setting before closing the dialog.
- * In case the settings are invalid an error message is displayed.
- */
- async save() {
-
- const settings = {
- general: {
- secure: this.isEncrypted(),
- sasl: this.getSaslMechanism()
- },
- authentication: {
- username: this.getAuthentication(),
- mechanism: 0
- },
- authorization: {
- username: this.getAuthorization(),
- mechanism: this.getAuthorizationType()
- }
- };
-
- await this.account.send("account-settings-set-credentials", settings);
- }
-
- /**
- * Returns the currents dialogs UI Element.
- *
- * @returns {object}
- * the dialogs UI elements.
- */
- getDialog() {
- return document.querySelector("#dialog-settings-credentials");
- }
-
- /**
- * Renders the UI element into the dom.
- */
- async render() {
- const parent = this.getDialog();
-
- const credentials = await this.account.send("account-setting-get-credentials");
-
- // Authentication settings
- this.setEncrypted(credentials.general.secure);
- this.setSaslMechanism(credentials.general.sasl);
-
- this.setAuthentication(credentials.authentication.username);
-
- parent
- .querySelectorAll(".sieve-settings-authentication .dropdown-item")
- .forEach((item) => {
- item.addEventListener("click", (event) => {
- this.setSaslMechanism(event.target.dataset.sieveAuthentication);
- });
- });
-
- // Show the forget password button only when a password is stored.
- if (credentials.authentication.stored)
- parent.querySelector(".sieve-settings-forget-password").classList.remove("d-none");
- else
- parent.querySelector(".sieve-settings-forget-password").classList.add("d-none");
-
- parent
- .querySelector(".sieve-settings-forget-password button")
- .addEventListener("click", async () => {
- await this.account.send("account-settings-forget-credentials");
-
- this.getDialog()
- .querySelector(".sieve-settings-forget-password").classList.add("d-none");
- });
-
- // Authorization settings....
- this.setAuthorizationType(credentials.authorization.type);
- this.setAuthorization(credentials.authorization.username);
-
- parent
- .querySelectorAll(".sieve-settings-authorization .dropdown-item")
- .forEach((item) => {
- item.addEventListener("click", (event) => {
- this.setAuthorizationType(event.target.dataset.sieveAuthorization);
- });
- });
-
- parent
- .querySelector(".siv-settings-show-advanced")
- .addEventListener("click", () => { this.showAdvanced(); });
-
- parent
- .querySelector(".siv-settings-hide-advanced")
- .addEventListener("click", () => { this.hideAdvanced(); });
-
- this.hideAdvanced();
- }
- }
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveCredentialsSettingsUI;
- else
- exports.SieveCredentialsSettingsUI = SieveCredentialsSettingsUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs b/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs
new file mode 100644
index 00000000..18ab58d8
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs
@@ -0,0 +1,357 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+
+import { SieveTemplate } from "./../../utils/SieveTemplate.mjs";
+
+/**
+ * A UI renderer for the sieve settings dialog
+ */
+class SieveCredentialsSettingsUI {
+
+ /**
+ * Initializes the settings
+ * @param {SieveAccount} account
+ * the account for which the settings edited.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Sets the authentication type
+ *
+ * @param {string} type
+ * the authentication type which should be used
+ * @returns {SieveCredentialsUI}
+ * a self reference
+ */
+ setSaslMechanism(type) {
+
+ const parent = this.getDialog();
+
+ const text = parent
+ .querySelector(".sieve-settings-authentication")
+ .querySelector(`.dropdown-item[data-sieve-authentication="${type}"]`)
+ .textContent;
+
+ parent
+ .querySelector(".sieve-settings-authentication button")
+ .dataset.sieveAuthentication = type;
+
+ parent
+ .querySelector(".sieve-settings-authentication button")
+ .textContent = text;
+
+ return this;
+ }
+
+ /**
+ * The authentication type which was selected
+ *
+ * @returns {string}
+ * authentication type
+ */
+ getSaslMechanism() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-authentication button")
+ .dataset.sieveAuthentication;
+ }
+
+ /**
+ * Sets the username in the ui
+ *
+ * @param {string} username
+ * the username which should be set
+ * @returns {SieveSettingsUI}
+ * a self reference
+ */
+ setAuthentication(username) {
+ this.getDialog()
+ .querySelector(".sieve-settings-username").value = username;
+
+ return this;
+ }
+
+ /**
+ * Gets the username from the ui.
+ *
+ * @returns {string}
+ * the username as string.
+ */
+ getAuthentication() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-username").value;
+ }
+
+ /**
+ * Selects the given authorization type in the dropdown control.
+ *
+ * @param {int|string} type
+ * the authorization type to be activated.
+ * @returns {SieveCredentialsSettingsUI}
+ * a self reference.
+ */
+ setAuthorizationType(type) {
+ const dialog = this.getDialog();
+
+ const text = dialog
+ .querySelector(".sieve-settings-authorization")
+ .querySelector(`.dropdown-item[data-sieve-authorization="${type}"]`)
+ .textContent;
+
+ dialog
+ .querySelector(".sieve-settings-authorization button")
+ .dataset.sieveAuthorization = type;
+
+ dialog
+ .querySelector(".sieve-settings-authorization button")
+ .textContent = text;
+
+ if (`${type}` === "3")
+ dialog.querySelector(".sieve-settings-authorization-username").classList.remove("d-none");
+ else
+ dialog.querySelector(".sieve-settings-authorization-username").classList.add("d-none");
+
+ return this;
+ }
+
+ /**
+ * Gets the current authorization type set in the dropdown menu.
+ *
+ * @returns {string}
+ * the authorization type as string.
+ */
+ getAuthorizationType() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-authorization button")
+ .dataset.sieveAuthorization;
+ }
+
+ /**
+ * Sets the authorization name.
+ * Authorization allows an authenticated user (normally admin)
+ * to access an other users sieve account.
+ *
+ * @param {string} username
+ * the username as which the current user should authorized
+ * @returns {SieveSettingsUI}
+ * a self reference
+ */
+ setAuthorization(username) {
+ this.getDialog()
+ .querySelector(".sieve-settings-text-authorization-username").value = username;
+
+ return this;
+ }
+
+ /**
+ * Gets the authorization name from the ui.
+ *
+ * @returns {string}
+ * the authorized username.
+ */
+ getAuthorization() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-text-authorization-username").value;
+ }
+
+ /**
+ * Gets the current dialogs encryption settings.
+ *
+ * @returns {boolean}
+ * true in case an encrypted connection should be used otherwise false.
+ */
+ isEncrypted() {
+
+ if (this.getDialog().querySelector("#sieve-settings-encryption-off").checked)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Sets the encryption settings in the current dialog.
+ *
+ * @param {boolean} encrypted
+ * the encryption status to set. False in case encryption is disabled
+ * otherwise it will be enabled
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setEncrypted(encrypted) {
+ const parent = this.getDialog();
+
+ if (encrypted === false)
+ parent.querySelector("#sieve-settings-encryption-off").checked = true;
+ else
+ parent.querySelector("#sieve-settings-encryption-on").checked = true;
+
+ return this;
+ }
+
+ /**
+ * Shows the advanced setting
+ */
+ showAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").classList.remove("d-none");
+ parent.querySelector(".siv-settings-show-advanced").classList.add("d-none");
+ parent.querySelector(".siv-settings-hide-advanced").classList.remove("d-none");
+ }
+
+ /**
+ * Hides the advanced settings
+ */
+ hideAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").classList.add("d-none");
+ parent.querySelector(".siv-settings-show-advanced").classList.remove("d-none");
+ parent.querySelector(".siv-settings-hide-advanced").classList.add("d-none");
+ }
+
+ /**
+ * Shows the settings dialog
+ *
+ * @returns {boolean}
+ * true in case new settings where applied.
+ * false in case the dialog was canceled.
+ */
+ async show() {
+
+ document.querySelector("#ctx").append(
+ await (new SieveTemplate()).load("./settings/ui/settings.credentials.html"));
+
+ await this.render();
+
+ const dialog = this.getDialog();
+ const modal = new bootstrap.Modal(dialog);
+
+ modal.show();
+
+ dialog
+ .querySelector(".sieve-settings-apply")
+ .addEventListener("click", async () => {
+ await this.save();
+ modal.hide();
+ });
+
+ return await new Promise((resolve) => {
+
+ dialog.addEventListener('hidden.bs.modal', () => {
+ modal.dispose();
+ dialog.remove();
+
+ resolve();
+ });
+
+ });
+ }
+
+ /**
+ * Validates and saves the setting before closing the dialog.
+ * In case the settings are invalid an error message is displayed.
+ */
+ async save() {
+
+ const settings = {
+ general: {
+ secure: this.isEncrypted(),
+ sasl: this.getSaslMechanism()
+ },
+ authentication: {
+ username: this.getAuthentication(),
+ mechanism: 0
+ },
+ authorization: {
+ username: this.getAuthorization(),
+ mechanism: this.getAuthorizationType()
+ }
+ };
+
+ await this.account.send("account-settings-set-credentials", settings);
+ }
+
+ /**
+ * Returns the currents dialogs UI Element.
+ *
+ * @returns {object}
+ * the dialogs UI elements.
+ */
+ getDialog() {
+ return document.querySelector("#dialog-settings-credentials");
+ }
+
+ /**
+ * Renders the UI element into the dom.
+ */
+ async render() {
+ const parent = this.getDialog();
+
+ const credentials = await this.account.send("account-setting-get-credentials");
+
+ // Authentication settings
+ this.setEncrypted(credentials.general.secure);
+ this.setSaslMechanism(credentials.general.sasl);
+
+ this.setAuthentication(credentials.authentication.username);
+
+ parent
+ .querySelectorAll(".sieve-settings-authentication .dropdown-item")
+ .forEach((item) => {
+ item.addEventListener("click", (event) => {
+ this.setSaslMechanism(event.target.dataset.sieveAuthentication);
+ });
+ });
+
+ // Show the forget password button only when a password is stored.
+ if (credentials.authentication.stored)
+ parent.querySelector(".sieve-settings-forget-password").classList.remove("d-none");
+ else
+ parent.querySelector(".sieve-settings-forget-password").classList.add("d-none");
+
+ parent
+ .querySelector(".sieve-settings-forget-password button")
+ .addEventListener("click", async () => {
+ await this.account.send("account-settings-forget-credentials");
+
+ this.getDialog()
+ .querySelector(".sieve-settings-forget-password").classList.add("d-none");
+ });
+
+ // Authorization settings....
+ this.setAuthorizationType(credentials.authorization.type);
+ this.setAuthorization(credentials.authorization.username);
+
+ parent
+ .querySelectorAll(".sieve-settings-authorization .dropdown-item")
+ .forEach((item) => {
+ item.addEventListener("click", (event) => {
+ this.setAuthorizationType(event.target.dataset.sieveAuthorization);
+ });
+ });
+
+ parent
+ .querySelector(".siv-settings-show-advanced")
+ .addEventListener("click", () => { this.showAdvanced(); });
+
+ parent
+ .querySelector(".siv-settings-hide-advanced")
+ .addEventListener("click", () => { this.hideAdvanced(); });
+
+ this.hideAdvanced();
+ }
+}
+
+export { SieveCredentialsSettingsUI };
diff --git a/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.js b/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.js
deleted file mode 100644
index 08e3cc6a..00000000
--- a/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveTemplate */
-
- // eslint-disable-next-line no-magic-numbers
- const ONE_MINUTE = 60 * 1000;
-
- /**
- * A UI renderer for the sieve settings dialog
- */
- class SieveServerSettingsUI {
-
- /**
- * Initializes the settings
- * @param {SieveAccount} account
- * the account for which the settings edited.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * Sets the account's human readable display name
- * @param {string} name
- * the name which should be set.
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setDisplayName(name) {
- this.getDialog()
- .querySelector(".sieve-settings-displayname").value = name;
-
- return this;
- }
-
- /**
- * Gets the account's human readable display name
- * @returns {string}
- * the display name
- */
- getDisplayName() {
- return this.getDialog()
- .querySelector(".sieve-settings-displayname").value;
- }
-
- /**
- * Sets the server's hostname.
- *
- * @param {string} hostname
- * the hostname as string.
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setHostname(hostname) {
- this.getDialog()
- .querySelector(".sieve-settings-hostname").value = hostname;
-
- return this;
- }
-
- /**
- * Gets the server's hostname.
- *
- * @returns {string}
- * the hostname
- */
- getHostname() {
- return this.getDialog()
- .querySelector(".sieve-settings-hostname").value;
- }
-
- /**
- * Populates the server's port in the dialog
- *
- * @param {string} port
- * the port
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setPort(port) {
- this.getDialog()
- .querySelector(".sieve-settings-port").value = port;
-
- return this;
- }
-
- /**
- * Gets the server's port.
- *
- * @returns {string}
- * the port as string.
- */
- getPort() {
- return this.getDialog()
- .querySelector(".sieve-settings-port").value;
- }
-
- /**
- * Sets the server's certificate fingerprint in the ui.
- * The fingerprint is normally a sha checksum.
- *
- * @param {string} fingerprint
- * the fingerprint.
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setFingerprint(fingerprint) {
- this.getDialog()
- .querySelector(".sieve-settings-fingerprint").value = fingerprint;
-
- return this;
- }
-
- /**
- * Gets the server's fingerprint from the setting ui.
- *
- * @returns {string}
- * the certificate fingerprint
- */
- getFingerprint() {
- return this.getDialog()
- .querySelector(".sieve-settings-fingerprint").value;
- }
-
-
- /**
- * Sets the keep alive interval.
- *
- * @param {int} interval
- * the keep alive interval in ms
- * @returns {SieveServerSettingsUI}
- * a self reference
- */
- setKeepAlive(interval) {
- // convert to seconds
- interval = interval / ONE_MINUTE;
- this.getDialog()
- .querySelector(".sieve-settings-keepalive-interval").value = interval;
-
- return this;
- }
-
- /**
- * Gets the keep alive interval
- *
- * @returns {int}
- * the keep alive interval
- */
- getKeepAlive() {
- const interval = this.getDialog()
- .querySelector(".sieve-settings-keepalive-interval").value;
-
- return interval * ONE_MINUTE;
- }
-
- /**
- * Shows the advanced setting
- *
- */
- showAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").classList.remove("d-none");
- parent.querySelector(".siv-settings-show-advanced").classList.add("d-none");
- parent.querySelector(".siv-settings-hide-advanced").classList.remove("d-none");
- }
-
- /**
- * Hides the advanced settings
- *
- */
- hideAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").classList.add("d-none");
- parent.querySelector(".siv-settings-show-advanced").classList.remove("d-none");
- parent.querySelector(".siv-settings-hide-advanced").classList.add("d-none");
- }
-
-
- /**
- * Renders the UI element into the dom.
- */
- async render() {
- const parent = this.getDialog();
-
- const server = await this.account.send("account-get-server");
-
- this.setDisplayName(server.displayName);
- this.setHostname(server.hostname);
- this.setPort(server.port);
- this.setFingerprint(server.fingerprint);
-
- this.setKeepAlive(server.keepAlive);
-
- parent.querySelector(".siv-settings-show-advanced")
- .addEventListener("click", () => { this.showAdvanced(); });
- parent.querySelector(".siv-settings-hide-advanced")
- .addEventListener("click", () => { this.hideAdvanced(); });
-
- this.hideAdvanced();
- }
-
- /**
- * Shows the settings dialog
- * @returns {Promise<boolean>}
- * false in case the dialog was dismissed otherwise true.
- */
- async show() {
-
- document.querySelector("#ctx").appendChild(
- await (new SieveTemplate()).load("./settings/ui/settings.server.tpl"));
-
- await this.render();
-
- const dialog = document.querySelector("#dialog-settings-server");
- const modal = new bootstrap.Modal(dialog);
-
- modal.show();
-
- dialog
- .querySelector(".sieve-settings-apply")
- .addEventListener("click", async () => {
- await this.save();
- modal.hide();
- });
-
- return await new Promise((resolve) => {
-
- dialog.addEventListener('hidden.bs.modal', () => {
- modal.dispose();
- dialog.parentNode.removeChild(dialog);
-
- resolve();
- });
- });
- }
-
- /**
- * Validates and saves the setting before closing the dialog.
- * In case the settings are invalid an error message is displayed.
- *
- */
- async save() {
-
- const server = {
- displayName: await this.getDisplayName(),
- hostname: await this.getHostname(),
- port: await this.getPort(),
- fingerprint: await this.getFingerprint(),
- keepAlive: await this.getKeepAlive()
- };
-
- await this.account.send("account-set-server", server);
- }
-
- /**
- * Returns the currents dialogs UI Element.
- *
- * @returns {HTMLElement}
- * the dialogs UI elements.
- */
- getDialog() {
- return document.querySelector("#dialog-settings-server");
- }
-
- }
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveServerSettingsUI;
- else
- exports.SieveServerSettingsUI = SieveServerSettingsUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.mjs b/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.mjs
new file mode 100644
index 00000000..53323dec
--- /dev/null
+++ b/src/app/libs/managesieve.ui/settings/ui/SieveServerSettingsUI.mjs
@@ -0,0 +1,281 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+/* global bootstrap */
+
+import { SieveTemplate } from "./../../utils/SieveTemplate.mjs";
+
+// eslint-disable-next-line no-magic-numbers
+const ONE_MINUTE = 60 * 1000;
+
+/**
+ * A UI renderer for the sieve settings dialog
+ */
+class SieveServerSettingsUI {
+
+ /**
+ * Initializes the settings
+ * @param {SieveAccount} account
+ * the account for which the settings edited.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Sets the account's human readable display name
+ * @param {string} name
+ * the name which should be set.
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setDisplayName(name) {
+ this.getDialog()
+ .querySelector(".sieve-settings-displayname").value = name;
+
+ return this;
+ }
+
+ /**
+ * Gets the account's human readable display name
+ * @returns {string}
+ * the display name
+ */
+ getDisplayName() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-displayname").value;
+ }
+
+ /**
+ * Sets the server's hostname.
+ *
+ * @param {string} hostname
+ * the hostname as string.
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setHostname(hostname) {
+ this.getDialog()
+ .querySelector(".sieve-settings-hostname").value = hostname;
+
+ return this;
+ }
+
+ /**
+ * Gets the server's hostname.
+ *
+ * @returns {string}
+ * the hostname
+ */
+ getHostname() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-hostname").value;
+ }
+
+ /**
+ * Populates the server's port in the dialog
+ *
+ * @param {string} port
+ * the port
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setPort(port) {
+ this.getDialog()
+ .querySelector(".sieve-settings-port").value = port;
+
+ return this;
+ }
+
+ /**
+ * Gets the server's port.
+ *
+ * @returns {string}
+ * the port as string.
+ */
+ getPort() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-port").value;
+ }
+
+ /**
+ * Sets the server's certificate fingerprint in the ui.
+ * The fingerprint is normally a sha checksum.
+ *
+ * @param {string} fingerprint
+ * the fingerprint.
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setFingerprint(fingerprint) {
+ this.getDialog()
+ .querySelector(".sieve-settings-fingerprint").value = fingerprint;
+
+ return this;
+ }
+
+ /**
+ * Gets the server's fingerprint from the setting ui.
+ *
+ * @returns {string}
+ * the certificate fingerprint
+ */
+ getFingerprint() {
+ return this.getDialog()
+ .querySelector(".sieve-settings-fingerprint").value;
+ }
+
+
+ /**
+ * Sets the keep alive interval.
+ *
+ * @param {int} interval
+ * the keep alive interval in ms
+ * @returns {SieveServerSettingsUI}
+ * a self reference
+ */
+ setKeepAlive(interval) {
+ // convert to seconds
+ interval = interval / ONE_MINUTE;
+ this.getDialog()
+ .querySelector(".sieve-settings-keepalive-interval").value = interval;
+
+ return this;
+ }
+
+ /**
+ * Gets the keep alive interval
+ *
+ * @returns {int}
+ * the keep alive interval
+ */
+ getKeepAlive() {
+ const interval = this.getDialog()
+ .querySelector(".sieve-settings-keepalive-interval").value;
+
+ return interval * ONE_MINUTE;
+ }
+
+ /**
+ * Shows the advanced setting
+ *
+ */
+ showAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").classList.remove("d-none");
+ parent.querySelector(".siv-settings-show-advanced").classList.add("d-none");
+ parent.querySelector(".siv-settings-hide-advanced").classList.remove("d-none");
+ }
+
+ /**
+ * Hides the advanced settings
+ *
+ */
+ hideAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").classList.add("d-none");
+ parent.querySelector(".siv-settings-show-advanced").classList.remove("d-none");
+ parent.querySelector(".siv-settings-hide-advanced").classList.add("d-none");
+ }
+
+
+ /**
+ * Renders the UI element into the dom.
+ */
+ async render() {
+ const parent = this.getDialog();
+
+ const server = await this.account.send("account-get-server");
+
+ this.setDisplayName(server.displayName);
+ this.setHostname(server.hostname);
+ this.setPort(server.port);
+ this.setFingerprint(server.fingerprint);
+
+ this.setKeepAlive(server.keepAlive);
+
+ parent.querySelector(".siv-settings-show-advanced")
+ .addEventListener("click", () => { this.showAdvanced(); });
+ parent.querySelector(".siv-settings-hide-advanced")
+ .addEventListener("click", () => { this.hideAdvanced(); });
+
+ this.hideAdvanced();
+ }
+
+ /**
+ * Shows the settings dialog
+ * @returns {Promise<boolean>}
+ * false in case the dialog was dismissed otherwise true.
+ */
+ async show() {
+
+ document.querySelector("#ctx").append(
+ await (new SieveTemplate()).load("./settings/ui/settings.server.html"));
+
+ await this.render();
+
+ const dialog = document.querySelector("#dialog-settings-server");
+ const modal = new bootstrap.Modal(dialog);
+
+ modal.show();
+
+ dialog
+ .querySelector(".sieve-settings-apply")
+ .addEventListener("click", async () => {
+ await this.save();
+ modal.hide();
+ });
+
+ return await new Promise((resolve) => {
+
+ dialog.addEventListener('hidden.bs.modal', () => {
+ modal.dispose();
+ dialog.remove();
+
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * Validates and saves the setting before closing the dialog.
+ * In case the settings are invalid an error message is displayed.
+ *
+ */
+ async save() {
+
+ const server = {
+ displayName: await this.getDisplayName(),
+ hostname: await this.getHostname(),
+ port: await this.getPort(),
+ fingerprint: await this.getFingerprint(),
+ keepAlive: await this.getKeepAlive()
+ };
+
+ await this.account.send("account-set-server", server);
+ }
+
+ /**
+ * Returns the currents dialogs UI Element.
+ *
+ * @returns {HTMLElement}
+ * the dialogs UI elements.
+ */
+ getDialog() {
+ return document.querySelector("#dialog-settings-server");
+ }
+
+}
+
+export { SieveServerSettingsUI };
diff --git a/src/app/libs/managesieve.ui/settings/ui/settings.credentials.tpl b/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html
index 37d37067..f3cd152a 100644
--- a/src/app/libs/managesieve.ui/settings/ui/settings.credentials.tpl
+++ b/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="credentials.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
@@ -28,7 +28,7 @@
<label class="col-sm-3 col-form-label" data-i18n="credentials.authentication"></label>
<div class="col-sm-8">
<div class="sieve-settings-authentication dropdown">
- <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
+ <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
@@ -39,12 +39,12 @@
href="#"></a>
<a class="dropdown-item" data-i18n="credentials.sasl.login" data-sieve-authentication="LOGIN"
href="#"></a>
- <a class="dropdown-item" data-i18n="credentials.sasl.crammd5" data-sieve-authentication="CRAM-MD5"
- href="#"></a>
<a class="dropdown-item" data-i18n="credentials.sasl.scramsha1" data-sieve-authentication="SCRAM-SHA-1"
href="#"></a>
<a class="dropdown-item" data-i18n="credentials.sasl.scramsha256"
data-sieve-authentication="SCRAM-SHA-256" href="#"></a>
+ <a class="dropdown-item" data-i18n="credentials.sasl.scramsha512"
+ data-sieve-authentication="SCRAM-SHA-512" href="#"></a>
<a class="dropdown-item" data-i18n="credentials.sasl.external" data-sieve-authentication="EXTERNAL"
href="#"></a>
<div class="dropdown-divider"></div>
@@ -84,7 +84,7 @@
<div class="col-sm-8">
<div class="sieve-settings-authorization dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton"
- data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-i18n="credentials.authorization.implicit" class="dropdown-item" data-sieve-authorization="0"
diff --git a/src/app/libs/managesieve.ui/settings/ui/settings.server.tpl b/src/app/libs/managesieve.ui/settings/ui/settings.server.html
index 1bd314d6..d36aec05 100644
--- a/src/app/libs/managesieve.ui/settings/ui/settings.server.tpl
+++ b/src/app/libs/managesieve.ui/settings/ui/settings.server.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="server.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
diff --git a/src/app/libs/managesieve.ui/tabs/SieveTabsUI.js b/src/app/libs/managesieve.ui/tabs/SieveTabsUI.js
deleted file mode 100644
index f6cf5509..00000000
--- a/src/app/libs/managesieve.ui/tabs/SieveTabsUI.js
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- const { SieveTemplate } = require("./../utils/SieveTemplate.js");
- const { SieveIpcClient } = require("./../utils/SieveIpcClient.js");
- const { SieveUniqueId } = require("./../utils/SieveUniqueId.js");
-
- const SCROLL_INCREMENT = 100;
-
- /**
- * Implements a single tab ui element.
- */
- class SieveTab {
-
- /**
- * Creates a new instance
- * @param {SieveTabUI} tabs
- * the parent tab ui object
- * @param {string} account
- * the accounts name
- * @param {string} name
- * the script'S name
- */
- constructor(tabs, account, name) {
- this.tabs = tabs;
- this.account = account;
- this.name = name;
- }
-
- /**
- * Gets the current tab. The dom element is used to store meta data.
- *
- * @returns {HTMLElement}
- * the tab's dom element
- */
- getTab() {
- return document
- .querySelector(`#tabs-items [data-sieve-account='${this.account}'][data-sieve-name='${this.name}']`);
- }
-
- /**
- * Gets the tab unique id.
- * @returns {string}
- * the unique tab id.
- */
- getId() {
- return this.getTab().dataset.sieveId;
- }
-
- /**
- * Ensures the tab's content is shown.
- */
- show() {
- const tab = this.getTab().querySelector(".nav-link");
- (new bootstrap.Tab(tab)).show();
-
- // On Tab show is not fired when the tab is already visible.
- // so we need to emulate this. In worst case we end up with a
- // duplicated shown message...
- this.onTabShown();
- }
-
- /**
- * Returns the iframe associated with this tab
- *
- * @returns {IFrame}
- * the iframe which hosts the content
- */
- getContent() {
- return document.querySelector(`#${this.getId()}-content`).contentWindow;
- }
-
- /**
- * Called whenever the tab got activated and the tab's content was
- * shown to the user.
- */
- onTabShown() {
- SieveIpcClient.sendMessage("editor", "editor-shown", null, this.getContent());
- }
-
- /**
- * Closes the tab and removes the tab content frame.
- * It may be vetoed, e.g. if an editor contains unsaved changes.
- *
- * @returns {boolean}
- * true in case the tab could be closed. In case it was canceled false.
- */
- async close() {
-
- if (await this.hasChanges()) {
- this.show();
- const rv = await SieveIpcClient.sendMessage("editor", "editor-close", this.name, this.getContent());
-
- // Closing was canceled?
- if (!rv)
- return false;
- }
-
- // we need to delete first the content...
- const content = document.querySelector(`#${this.getId()}-content`);
- content.parentNode.removeChild(content);
-
- // and then the tab, otherwise getId fails...
- const tab = document.querySelector(`#${this.getId()}-tab`);
- const elm = bootstrap.Tab.getInstance(tab);
-
- if (elm)
- elm.dispose();
-
- tab.parentNode.removeChild(tab);
-
- return true;
- }
-
- /**
- * Checks if the tab has unsaved changes.
- *
- * @returns {boolean}
- * true in case of unsaved changes
- */
- async hasChanges() {
- return await SieveIpcClient.sendMessage("editor", "editor-hasChanged", null, this.getContent());
- }
-
- }
-
- /**
- * Implements a tab ui.
- */
- class SieveTabUI {
-
- /**
- * Scrolls the tab bar to the right
- */
- scrollRight() {
- const lastTab = document.querySelector("#tabs-items").lastElementChild;
- const tabs = document.querySelector("#tabs-items");
-
- const lastTabRight = lastTab.offsetLeft + lastTab.offsetWidth;
-
- if (lastTabRight < tabs.clientWidth) {
- tabs.style.left = `${0}px`;
- return;
- }
-
- let left = tabs.offsetLeft - SCROLL_INCREMENT;
-
- const max = tabs.offsetWidth - lastTabRight;
-
- if (left < max)
- left = max;
-
- tabs.style.left = `${left}px`;
- }
-
- /**
- * Scrolls the tab bar to the left
- */
- scrollLeft() {
- const lastTab = document.querySelector("#tabs-items").lastElementChild;
- const tabs = document.querySelector("#tabs-items");
-
- const lastTabRight = lastTab.offsetLeft + lastTab.offsetWidth;
-
-
- if (lastTabRight < tabs.clientWidth) {
- tabs.style.left = `${0}px`;
- return;
- }
-
- let left = tabs.offsetLeft + SCROLL_INCREMENT;
-
- if (left > 0)
- left = 0;
-
- tabs.style.left = `${left}px`;
- }
-
- /**
- * Initializes the TabUI
- */
- init() {
-
- document
- .querySelector("#tabs-items")
- .style.transitionProperty = "left";
-
- document
- .querySelector("#tabs-items")
- .style.transitionDuration = "0.5s";
-
- // Add event listeners...
- document
- .querySelector("#tabs-scroll-left")
- .addEventListener("click", () => { this.scrollLeft(); });
-
- document
- .querySelector("#tabs-scroll-right")
- .addEventListener("click", () => { this.scrollRight(); });
- }
-
- /**
- * Gets the tab by the account name and script name.
- *
- * @param {string} account
- * the unique account name.
- * @param {string} name
- * the script name.
- *
- * @returns {SieveTab|null}
- * the tab or null in case no such tab exists.
- */
- getTab(account, name) {
- const tab = new SieveTab(this, account, name);
-
- if (!tab.getTab())
- return null;
-
- return tab;
- }
-
- /**
- * Checks if a tab for the given script exists.
- *
- * @param {string} account
- * the accounts unique id.
- * @param {string} name
- * the script name.
- *
- * @returns {boolean}
- * true in case the tab exists otherwise false.
- */
- has(account, name) {
- return (this.getTab(account, name) !== null);
- }
-
- /**
- * Called whenever tab is shown when switching to it.
- *
- * @param {string} account
- * the unique account id.
- * @param {string} name
- * the script name.
- */
- onTabShown(account, name) {
- this.getTab(account, name).onTabShown();
- }
-
- /**
- * Closes the given tab.
- * Tabs are identified by the unique account id plus the script name.
- * @param {string} account
- * the unique account id.
- * @param {string} name
- * the script name.
- */
- async close(account, name) {
-
- const tab = this.getTab(account, name);
-
- if (!tab)
- return;
-
- if (!await tab.close()) {
- return;
- }
-
- const newTab = document.querySelector("#accounts-tab .nav-link");
- (new bootstrap.Tab(newTab)).show();
- }
-
- /**
- * Creates a unique id
- * @returns {string}
- * the unique id
- */
- generateId() {
- return (new SieveUniqueId()).generate();
- }
-
- /**
- * Creates a new tab for the script.
- * In case the tab exists it will switch to the tab.
- *
- * Tabs are identified by the account id and the script name.
- *
- * @param {string} account
- * the unique account id
- * @param {string} name
- * the script name
- */
- async create(account, name) {
-
- if (this.has(account, name)) {
- this.open(account, name);
- return;
- }
-
- // Create a random id.
- const id = this.generateId();
-
- const tabId = `${id}-tab`;
- const contentId = `${id}-content`;
-
- // create a new tab.
- const content = await (
- new SieveTemplate()).load("./libs/managesieve.ui/tabs/editor.content.tpl");
- const tab = await (
- new SieveTemplate()).load("./libs/managesieve.ui/tabs/editor.tab.tpl");
-
- tab.querySelector(".nav-link").href = `#${contentId}`;
- tab.querySelector(".siv-tab-name").textContent = name;
-
- tab.querySelector(".tab-close").addEventListener("click", async () => {
- await this.close(account, name);
- });
-
- content.id = contentId;
- tab.id = tabId;
- tab.dataset.sieveAccount = account;
- tab.dataset.sieveName = name;
- tab.dataset.sieveId = id;
-
- // Update the iframe's url.
- const url = new URL(content.src, window.location);
-
- url.searchParams.append("account", account);
- url.searchParams.append("script", name);
-
- content.src = url.toString();
-
- document.querySelector(`#tabs-content`).appendChild(content);
- document.querySelector(`#tabs-items`).appendChild(tab);
-
- tab.addEventListener('shown.bs.tab', () => { this.onTabShown(account, name); });
-
- this.getTab(account, name).show();
- }
-
- /**
- * Opens a script in a tab.
- *
- * In case the script is already open it will switch
- * to this tab.
- *
- * Tabs are identified by the account id and the script name.
- *
- * @param {string} account
- * the account id
- * @param {string} name
- * the script name
- */
- async open(account, name) {
-
- const tab = this.getTab(account, name);
-
- if (tab) {
- tab.show();
- return;
- }
-
- await this.create(account, name);
- }
-
- }
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports.SieveTabUI = SieveTabUI;
- else
- exports.SieveTabUI = SieveTabUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/tabs/SieveTabsUI.mjs b/src/app/libs/managesieve.ui/tabs/SieveTabsUI.mjs
new file mode 100644
index 00000000..dd163088
--- /dev/null
+++ b/src/app/libs/managesieve.ui/tabs/SieveTabsUI.mjs
@@ -0,0 +1,377 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+import { SieveUniqueId } from "./../utils/SieveUniqueId.mjs";
+
+const SCROLL_INCREMENT = 100;
+
+/**
+ * Implements a single tab ui element.
+ */
+class SieveTab {
+
+ /**
+ * Creates a new instance
+ * @param {SieveTabUI} tabs
+ * the parent tab ui object
+ * @param {string} account
+ * the accounts name
+ * @param {string} name
+ * the script'S name
+ */
+ constructor(tabs, account, name) {
+ this.tabs = tabs;
+ this.account = account;
+ this.name = name;
+ }
+
+ /**
+ * Gets the current tab. The dom element is used to store meta data.
+ *
+ * @returns {HTMLElement}
+ * the tab's dom element
+ */
+ getTab() {
+ return document
+ .querySelector(`#tabs-items [data-sieve-account='${this.account}'][data-sieve-name='${this.name}']`);
+ }
+
+ /**
+ * Gets the tab unique id.
+ * @returns {string}
+ * the unique tab id.
+ */
+ getId() {
+ return this.getTab().dataset.sieveId;
+ }
+
+ /**
+ * Ensures the tab's content is shown.
+ */
+ show() {
+ const tab = this.getTab().querySelector(".nav-link");
+ (new bootstrap.Tab(tab)).show();
+
+ // On Tab show is not fired when the tab is already visible.
+ // so we need to emulate this. In worst case we end up with a
+ // duplicated shown message...
+ this.onTabShown();
+ }
+
+ /**
+ * Returns the iframe associated with this tab
+ *
+ * @returns {IFrame}
+ * the iframe which hosts the content
+ */
+ getContent() {
+ return document.querySelector(`#${this.getId()}-content`).contentWindow;
+ }
+
+ /**
+ * Called whenever the tab got activated and the tab's content was
+ * shown to the user.
+ */
+ onTabShown() {
+ SieveIpcClient.sendMessage("editor", "editor-shown", null, this.getContent());
+ }
+
+ /**
+ * Closes the tab and removes the tab content frame.
+ * It may be vetoed, e.g. if an editor contains unsaved changes.
+ *
+ * @returns {boolean}
+ * true in case the tab could be closed. In case it was canceled false.
+ */
+ async close() {
+
+ if (await this.hasChanges()) {
+ this.show();
+ const rv = await SieveIpcClient.sendMessage("editor", "editor-close", this.name, this.getContent());
+
+ // Closing was canceled?
+ if (!rv)
+ return false;
+ }
+
+ // we need to delete first the content...
+ const content = document.querySelector(`#${this.getId()}-content`);
+ content.remove();
+
+ // and then the tab, otherwise getId fails...
+ const tab = document.querySelector(`#${this.getId()}-tab`);
+ const elm = bootstrap.Tab.getInstance(tab);
+
+ if (elm)
+ elm.dispose();
+
+ tab.remove();
+
+ return true;
+ }
+
+ /**
+ * Checks if the tab has unsaved changes.
+ *
+ * @returns {boolean}
+ * true in case of unsaved changes
+ */
+ async hasChanges() {
+ return await SieveIpcClient.sendMessage("editor", "editor-hasChanged", null, this.getContent());
+ }
+
+}
+
+/**
+ * Implements a tab ui.
+ */
+class SieveTabUI {
+
+ /**
+ * Scrolls the tab bar to the right
+ */
+ scrollRight() {
+ const lastTab = document.querySelector("#tabs-items").lastElementChild;
+ const tabs = document.querySelector("#tabs-items");
+
+ const lastTabRight = lastTab.offsetLeft + lastTab.offsetWidth;
+
+ if (lastTabRight < tabs.clientWidth) {
+ tabs.style.left = `${0}px`;
+ return;
+ }
+
+ let left = tabs.offsetLeft - SCROLL_INCREMENT;
+
+ const max = tabs.offsetWidth - lastTabRight;
+
+ if (left < max)
+ left = max;
+
+ tabs.style.left = `${left}px`;
+ }
+
+ /**
+ * Scrolls the tab bar to the left
+ */
+ scrollLeft() {
+ const lastTab = document.querySelector("#tabs-items").lastElementChild;
+ const tabs = document.querySelector("#tabs-items");
+
+ const lastTabRight = lastTab.offsetLeft + lastTab.offsetWidth;
+
+
+ if (lastTabRight < tabs.clientWidth) {
+ tabs.style.left = `${0}px`;
+ return;
+ }
+
+ let left = tabs.offsetLeft + SCROLL_INCREMENT;
+
+ if (left > 0)
+ left = 0;
+
+ tabs.style.left = `${left}px`;
+ }
+
+ /**
+ * Initializes the TabUI
+ */
+ init() {
+
+ document
+ .querySelector("#tabs-items")
+ .style.transitionProperty = "left";
+
+ document
+ .querySelector("#tabs-items")
+ .style.transitionDuration = "0.5s";
+
+ // Add event listeners...
+ document
+ .querySelector("#tabs-scroll-left")
+ .addEventListener("click", () => { this.scrollLeft(); });
+
+ document
+ .querySelector("#tabs-scroll-right")
+ .addEventListener("click", () => { this.scrollRight(); });
+ }
+
+ /**
+ * Gets the tab by the account name and script name.
+ *
+ * @param {string} account
+ * the unique account name.
+ * @param {string} name
+ * the script name.
+ *
+ * @returns {SieveTab|null}
+ * the tab or null in case no such tab exists.
+ */
+ getTab(account, name) {
+ const tab = new SieveTab(this, account, name);
+
+ if (!tab.getTab())
+ return null;
+
+ return tab;
+ }
+
+ /**
+ * Checks if a tab for the given script exists.
+ *
+ * @param {string} account
+ * the accounts unique id.
+ * @param {string} name
+ * the script name.
+ *
+ * @returns {boolean}
+ * true in case the tab exists otherwise false.
+ */
+ has(account, name) {
+ return (this.getTab(account, name) !== null);
+ }
+
+ /**
+ * Called whenever tab is shown when switching to it.
+ *
+ * @param {string} account
+ * the unique account id.
+ * @param {string} name
+ * the script name.
+ */
+ onTabShown(account, name) {
+ this.getTab(account, name).onTabShown();
+ }
+
+ /**
+ * Closes the given tab.
+ * Tabs are identified by the unique account id plus the script name.
+ * @param {string} account
+ * the unique account id.
+ * @param {string} name
+ * the script name.
+ */
+ async close(account, name) {
+
+ const tab = this.getTab(account, name);
+
+ if (!tab)
+ return;
+
+ if (!await tab.close()) {
+ return;
+ }
+
+ const newTab = document.querySelector("#accounts-tab .nav-link");
+ (new bootstrap.Tab(newTab)).show();
+ }
+
+ /**
+ * Creates a unique id
+ * @returns {string}
+ * the unique id
+ */
+ generateId() {
+ return (new SieveUniqueId()).generate();
+ }
+
+ /**
+ * Creates a new tab for the script.
+ * In case the tab exists it will switch to the tab.
+ *
+ * Tabs are identified by the account id and the script name.
+ *
+ * @param {string} account
+ * the unique account id
+ * @param {string} name
+ * the script name
+ */
+ async create(account, name) {
+
+ if (this.has(account, name)) {
+ this.open(account, name);
+ return;
+ }
+
+ // Create a random id.
+ const id = this.generateId();
+
+ const tabId = `${id}-tab`;
+ const contentId = `${id}-content`;
+
+ // create a new tab.
+ const content = await (
+ new SieveTemplate()).load("./libs/managesieve.ui/tabs/editor.content.html");
+ const tab = await (
+ new SieveTemplate()).load("./libs/managesieve.ui/tabs/editor.tab.html");
+
+ tab.querySelector(".nav-link").href = `#${contentId}`;
+ tab.querySelector(".siv-tab-name").textContent = name;
+
+ tab.querySelector(".tab-close").addEventListener("click", async () => {
+ await this.close(account, name);
+ });
+
+ content.id = contentId;
+ tab.id = tabId;
+ tab.dataset.sieveAccount = account;
+ tab.dataset.sieveName = name;
+ tab.dataset.sieveId = id;
+
+ // Update the iframe's url.
+ const url = new URL(content.src, window.location);
+
+ url.searchParams.append("account", account);
+ url.searchParams.append("script", name);
+
+ content.src = url.toString();
+
+ document.querySelector(`#tabs-content`).append(content);
+ document.querySelector(`#tabs-items`).append(tab);
+
+ tab.addEventListener('shown.bs.tab', () => { this.onTabShown(account, name); });
+
+ this.getTab(account, name).show();
+ }
+
+ /**
+ * Opens a script in a tab.
+ *
+ * In case the script is already open it will switch
+ * to this tab.
+ *
+ * Tabs are identified by the account id and the script name.
+ *
+ * @param {string} account
+ * the account id
+ * @param {string} name
+ * the script name
+ */
+ async open(account, name) {
+
+ const tab = this.getTab(account, name);
+
+ if (tab) {
+ tab.show();
+ return;
+ }
+
+ await this.create(account, name);
+ }
+
+}
+
+
+export { SieveTabUI };
diff --git a/src/app/libs/managesieve.ui/tabs/editor.content.tpl b/src/app/libs/managesieve.ui/tabs/editor.content.html
index a35ccabf..a35ccabf 100644
--- a/src/app/libs/managesieve.ui/tabs/editor.content.tpl
+++ b/src/app/libs/managesieve.ui/tabs/editor.content.html
diff --git a/src/app/libs/managesieve.ui/tabs/editor.tab.tpl b/src/app/libs/managesieve.ui/tabs/editor.tab.html
index c1ad9acf..5aeb14cc 100644
--- a/src/app/libs/managesieve.ui/tabs/editor.tab.tpl
+++ b/src/app/libs/managesieve.ui/tabs/editor.tab.html
@@ -1,5 +1,5 @@
<li class="nav-item">
- <a class="nav-link" id="profile-tab" data-toggle="tab" role="tab">
+ <a class="nav-link" id="profile-tab" data-bs-toggle="tab" role="tab">
<span class="siv-tab-name">NAME</span>
<span class="tab-close" style="padding-left: 5px">✕</span>
</a>
diff --git a/src/app/libs/managesieve.ui/updater/SieveUpdater.js b/src/app/libs/managesieve.ui/updater/SieveUpdater.js
deleted file mode 100644
index 519fe3bf..00000000
--- a/src/app/libs/managesieve.ui/updater/SieveUpdater.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- const SIEVE_GITHUB_UPDATE_URL = "https://thsmi.github.io/sieve/update.json";
- const MAJOR_VERSION = 0;
- const MINOR_VERSION = 1;
- const PATCH_VERSION = 2;
-
- /**
- * Checks for Updates on github.
- */
- class SieveUpdater {
-
- /**
- * Converts the given string into an integer
- * @param {string} version
- * the version number as string which should be converted to integer.
- * @returns {Integer|NaN}
- * returns the integer value or NaN in case the string is no integer.
- */
- getInt(version) {
- const value = parseInt(version, 10);
-
- if (Number.isInteger(value))
- return value;
-
- return Number.NaN;
- }
-
-
- /**
- * Checks if the current version is less than the new version.
- *
- * For comparison the string values are converted to an integer.
- * In case no integer comparison is possible a string comparison will be performed.
- *
- * @param {string} newVersion
- * the new version as string
- * @param {string} currentVersion
- * the current version as string
- *
- * @returns {boolean}
- * false in case the current version is the latest
- * true in case there is a newer version
- */
- isLessThan(newVersion, currentVersion) {
-
- const newValue = this.getInt(newVersion);
- const currentValue = this.getInt(currentVersion);
-
- // in case conversion failed we use string comparison
- if (newValue === Number.NaN || currentValue === Number.NaN)
- return (newVersion < currentVersion);
-
- return newValue < currentValue;
- }
-
- /**
- * Checks if the current version is greater than the new version.
- *
- * For comparison the string values are converted to an integer.
- * In case no integer comparison is possible a string comparison will be performed.
- *
- * @param {string} newVersion
- * the new version as string
- * @param {string} currentVersion
- * the current version as string
- *
- * @returns {boolean}
- * true in case the new version is larger than the current
- * false in the new version is smaller than the current.
- */
- isGreaterThan(newVersion, currentVersion) {
- const newValue = this.getInt(newVersion);
- const currentValue = this.getInt(currentVersion);
-
- // in case conversion failed we use string comparison
- if (newValue === Number.NaN || currentValue === Number.NaN)
- return (newVersion > currentVersion);
-
- return newValue > currentValue;
- }
-
- /**
- * Compares if the next version is older than the current version.
- *
- * @param {string} next
- * the next version as dot separated string.
- * @param {string} current
- * the current version as dot separated string.
- * @returns {boolean}
- * true in case the current version is older than the next version otherwise false.
- */
- isOlder(next, current) {
- current = current.split(".");
- next = next.split(".");
-
- // In case the new major is larger, then this version is definitely older.
- if (this.isGreaterThan(next[MAJOR_VERSION], current[MAJOR_VERSION]))
- return false;
- // In case the new major is smaller, then this version is definitely newer.
- if (this.isLessThan(next[MAJOR_VERSION], current[MAJOR_VERSION]))
- return true;
-
- // In case it is equal we need to check at the minor version
- // In case the new minor is larger, then this version is definitely older.
- if (this.isGreaterThan(next[MINOR_VERSION], current[MINOR_VERSION]))
- return false;
- // In case the new minor is smaller, then this version is definitely newer.
- if (this.isLessThan(next[MINOR_VERSION], current[MINOR_VERSION]))
- return true;
-
- // In case it is equal we need to check the patch level.
- // It is newer if it is larger
- if (this.isGreaterThan(next[PATCH_VERSION], current[PATCH_VERSION]))
- return false;
-
- // Otherwise in case it is less or equal, the version is older or the same.
- return true;
- }
-
- /**
- * Compares the current version against the manifest.
- * @param {object} manifest
- * the manifest with the version information
- * @param {string} currentVersion
- * the apps current version.
- * @returns {boolean}
- * false if the current version is the latest.
- * true in case the manifest contains a newer version definition.
- */
- compare(manifest, currentVersion) {
- const items = manifest["addons"]["sieve@mozdev.org"]["updates"];
-
- // There are no updates if all entries are less or equal to the current version
- for (const item of items) {
-
- if (this.isOlder(item.version, currentVersion))
- continue;
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Checks the if any updates are published at github.
- * @returns {boolean}
- * true if newer version are available, otherwise false.
- */
- async check() {
-
- const currentVersion = await (require('electron').ipcRenderer.invoke("get-version"));
-
- return this.compare(
- await (await fetch(SIEVE_GITHUB_UPDATE_URL)).json(),
- currentVersion);
- }
- }
-
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveUpdater = SieveUpdater;
- else
- exports.SieveUpdater = SieveUpdater;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/updater/SieveUpdater.mjs b/src/app/libs/managesieve.ui/updater/SieveUpdater.mjs
new file mode 100644
index 00000000..13fba281
--- /dev/null
+++ b/src/app/libs/managesieve.ui/updater/SieveUpdater.mjs
@@ -0,0 +1,171 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+const SIEVE_GITHUB_UPDATE_URL = "https://thsmi.github.io/sieve/update.json";
+const MAJOR_VERSION = 0;
+const MINOR_VERSION = 1;
+const PATCH_VERSION = 2;
+
+/**
+ * Checks for Updates on github.
+ */
+class SieveUpdater {
+
+ /**
+ * Converts the given string into an integer
+ * @param {string} version
+ * the version number as string which should be converted to integer.
+ * @returns {Integer|NaN}
+ * returns the integer value or NaN in case the string is no integer.
+ */
+ getInt(version) {
+ const value = Number.parseInt(version, 10);
+
+ if (Number.isInteger(value))
+ return value;
+
+ return Number.NaN;
+ }
+
+
+ /**
+ * Checks if the current version is less than the new version.
+ *
+ * For comparison the string values are converted to an integer.
+ * In case no integer comparison is possible a string comparison will be performed.
+ *
+ * @param {string} newVersion
+ * the new version as string
+ * @param {string} currentVersion
+ * the current version as string
+ *
+ * @returns {boolean}
+ * false in case the current version is the latest
+ * true in case there is a newer version
+ */
+ isLessThan(newVersion, currentVersion) {
+
+ const newValue = this.getInt(newVersion);
+ const currentValue = this.getInt(currentVersion);
+
+ // in case conversion failed we use string comparison
+ if (newValue === Number.NaN || currentValue === Number.NaN)
+ return (newVersion < currentVersion);
+
+ return newValue < currentValue;
+ }
+
+ /**
+ * Checks if the current version is greater than the new version.
+ *
+ * For comparison the string values are converted to an integer.
+ * In case no integer comparison is possible a string comparison will be performed.
+ *
+ * @param {string} newVersion
+ * the new version as string
+ * @param {string} currentVersion
+ * the current version as string
+ *
+ * @returns {boolean}
+ * true in case the new version is larger than the current
+ * false in the new version is smaller than the current.
+ */
+ isGreaterThan(newVersion, currentVersion) {
+ const newValue = this.getInt(newVersion);
+ const currentValue = this.getInt(currentVersion);
+
+ // in case conversion failed we use string comparison
+ if (newValue === Number.NaN || currentValue === Number.NaN)
+ return (newVersion > currentVersion);
+
+ return newValue > currentValue;
+ }
+
+ /**
+ * Compares if the next version is older than the current version.
+ *
+ * @param {string} next
+ * the next version as dot separated string.
+ * @param {string} current
+ * the current version as dot separated string.
+ * @returns {boolean}
+ * true in case the current version is older than the next version otherwise false.
+ */
+ isOlder(next, current) {
+ current = current.split(".");
+ next = next.split(".");
+
+ // In case the new major is larger, then this version is definitely older.
+ if (this.isGreaterThan(next[MAJOR_VERSION], current[MAJOR_VERSION]))
+ return false;
+ // In case the new major is smaller, then this version is definitely newer.
+ if (this.isLessThan(next[MAJOR_VERSION], current[MAJOR_VERSION]))
+ return true;
+
+ // In case it is equal we need to check at the minor version
+ // In case the new minor is larger, then this version is definitely older.
+ if (this.isGreaterThan(next[MINOR_VERSION], current[MINOR_VERSION]))
+ return false;
+ // In case the new minor is smaller, then this version is definitely newer.
+ if (this.isLessThan(next[MINOR_VERSION], current[MINOR_VERSION]))
+ return true;
+
+ // In case it is equal we need to check the patch level.
+ // It is newer if it is larger
+ if (this.isGreaterThan(next[PATCH_VERSION], current[PATCH_VERSION]))
+ return false;
+
+ // Otherwise in case it is less or equal, the version is older or the same.
+ return true;
+ }
+
+ /**
+ * Compares the current version against the manifest.
+ * @param {object} manifest
+ * the manifest with the version information
+ * @param {string} currentVersion
+ * the apps current version.
+ * @returns {boolean}
+ * false if the current version is the latest.
+ * true in case the manifest contains a newer version definition.
+ */
+ compare(manifest, currentVersion) {
+ const items = manifest["addons"]["sieve@mozdev.org"]["updates"];
+
+ // There are no updates if all entries are less or equal to the current version
+ for (const item of items) {
+
+ if (this.isOlder(item.version, currentVersion))
+ continue;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks the if any updates are published at github.
+ * @returns {boolean}
+ * true if newer version are available, otherwise false.
+ */
+ async check() {
+
+ const currentVersion = await (require('electron').ipcRenderer.invoke("get-version"));
+
+ return this.compare(
+ await (await fetch(SIEVE_GITHUB_UPDATE_URL)).json(),
+ currentVersion);
+ }
+}
+
+export { SieveUpdater };
diff --git a/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.js b/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.js
deleted file mode 100644
index 47620065..00000000
--- a/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveTemplate */
- /* global SieveIpcClient */
-
- /**
- * Checks for new updates and display a new message if a newer version is available
- **/
- class SieveUpdaterUI {
-
- /**
- * Checks for new updates and display a new message if a newer version is available
- */
- async check() {
- const status = await SieveIpcClient.sendMessage("core", "update-check");
-
- if (status !== true)
- return;
-
- const template = await (new SieveTemplate()).load("./updater/update.tpl");
- template.querySelector(".sieve-update-msg").addEventListener("click", () => {
- SieveIpcClient.sendMessage("core", "update-goto-url");
- });
-
- const parent = document.querySelector("#ctx");
- parent.insertBefore(template, parent.firstChild);
- }
- }
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveUpdaterUI;
- else
- exports.SieveUpdaterUI = SieveUpdaterUI;
-
-})(this);
diff --git a/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.mjs b/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.mjs
new file mode 100644
index 00000000..5c2c459e
--- /dev/null
+++ b/src/app/libs/managesieve.ui/updater/SieveUpdaterUI.mjs
@@ -0,0 +1,39 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+
+/**
+ * Checks for new updates and display a new message if a newer version is available
+ **/
+class SieveUpdaterUI {
+
+ /**
+ * Checks for new updates and display a new message if a newer version is available
+ */
+ async check() {
+ const status = await SieveIpcClient.sendMessage("core", "update-check");
+
+ if (status !== true)
+ return;
+
+ const template = await (new SieveTemplate()).load("./updater/update.html");
+ template.querySelector(".sieve-update-msg").addEventListener("click", () => {
+ SieveIpcClient.sendMessage("core", "update-goto-url");
+ });
+
+ const parent = document.querySelector("#ctx");
+ parent.insertBefore(template, parent.firstChild);
+ }
+}
+
+export { SieveUpdaterUI };
diff --git a/src/app/libs/managesieve.ui/updater/update.tpl b/src/app/libs/managesieve.ui/updater/update.html
index 3e184b24..1cb98c92 100644
--- a/src/app/libs/managesieve.ui/updater/update.tpl
+++ b/src/app/libs/managesieve.ui/updater/update.html
@@ -1,5 +1,5 @@
<div class="alert alert-info alert-dismissible fade show mt-4" role="alert">
<strong data-i18n="updater.title"></strong>
<span class="sieve-update-msg" style="cursor: pointer" data-i18n="updater.message"></span>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> \ No newline at end of file
diff --git a/src/app/libs/managesieve.ui/utils/SieveIpcClient.js b/src/app/libs/managesieve.ui/utils/SieveIpcClient.js
deleted file mode 100644
index ca0e07ab..00000000
--- a/src/app/libs/managesieve.ui/utils/SieveIpcClient.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveLogger } = require("./SieveLogger.js");
- const { SieveAbstractIpcClient } = require("./SieveAbstractIpcClient.js");
-
- /**
- * Implements a IPC based on the postMessage interface.
- */
- class SieveIpcClient extends SieveAbstractIpcClient {
-
- /**
- * @inheritdoc
- */
- static getLogger() {
- return SieveLogger.getInstance();
- }
-
- /**
- * @inheritdoc
- */
- static parseMessageFromEvent(e) {
- return JSON.parse(e.data);
- }
-
- /**
- * @inheritdoc
- */
- static dispatch(message, target, origin) {
- if (origin === undefined)
- origin = "*";
-
- if (target === undefined)
- target = parent;
-
- if (typeof (message) !== 'string') {
- message = JSON.stringify(message);
- }
-
- this.getLogger().logIpcMessage(`Sending message ${message}`);
-
- if (target !== window) {
- target.postMessage(message, origin);
- return;
- }
-
- for (let idx = 0; idx < frames.length; idx++)
- frames[idx].postMessage(message, origin);
- }
-
- /**
- * @inheritdoc
- */
- static onMessage(e) {
- if (e.source === window)
- return;
-
- super.onMessage(e);
- }
- }
-
- window.addEventListener("message", (ev) => { SieveIpcClient.onMessage(ev); }, false);
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveIpcClient = SieveIpcClient;
- else
- exports.SieveIpcClient = SieveIpcClient;
-
-})(this);
-
diff --git a/src/app/libs/managesieve.ui/utils/SieveIpcClient.mjs b/src/app/libs/managesieve.ui/utils/SieveIpcClient.mjs
new file mode 100644
index 00000000..efa2f850
--- /dev/null
+++ b/src/app/libs/managesieve.ui/utils/SieveIpcClient.mjs
@@ -0,0 +1,66 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveLogger } from "./SieveLogger.mjs";
+import { SieveAbstractIpcClient } from "./SieveAbstractIpcClient.mjs";
+
+/**
+ * Implements a IPC based on the postMessage interface.
+ */
+class SieveIpcClient extends SieveAbstractIpcClient {
+
+ /**
+ * @inheritdoc
+ */
+ static getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ static parseMessageFromEvent(e) {
+ return JSON.parse(e.data);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ static dispatch(message, target, origin) {
+ if (origin === undefined)
+ origin = "*";
+
+ if (target === undefined)
+ target = parent;
+
+ if (typeof (message) !== 'string') {
+ message = JSON.stringify(message);
+ }
+
+ this.getLogger().logIpcMessage(`Sending message ${message}`);
+
+ target.postMessage(message, origin);
+
+ // In case the target is the current window, we also notify
+ // all child frames.
+ if (target !== window)
+ return;
+
+ for (let idx = 0; idx < frames.length; idx++)
+ frames[idx].postMessage(message, origin);
+ }
+
+}
+
+window.addEventListener("message", (ev) => { SieveIpcClient.onMessage(ev); }, false);
+
+export { SieveIpcClient };
+
diff --git a/src/app/main_esm.js b/src/app/main_esm.js
new file mode 100644
index 00000000..111912c9
--- /dev/null
+++ b/src/app/main_esm.js
@@ -0,0 +1,21 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+(async () => {
+ const electron = await require('electron');
+
+ try {
+ await (await import('./sieve.mjs')).main(electron);
+ } catch (ex) {
+ // eslint-disable-next-line no-console
+ console.log(ex);
+ }
+})();
diff --git a/src/app/sieve.mjs b/src/app/sieve.mjs
new file mode 100644
index 00000000..49ae6b62
--- /dev/null
+++ b/src/app/sieve.mjs
@@ -0,0 +1,185 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import path from 'path';
+import url from 'url';
+
+const DEFAULT_WINDOW_WIDTH = 1200;
+const DEFAULT_WINDOW_HEIGHT = 600;
+
+// Out main window, it defines the lifecycle of your application
+// so we need to protect it from the garbage collector and keep a
+// global reference active. Otherwise the window will be automatically
+// cleaned up and thus closed.
+
+let win = null;
+
+/**
+ * Creates the main window
+ *
+ * @param {Electron} electron
+ * a reference to the electron's root context object.
+ */
+function createWindow(electron) {
+
+ const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
+
+ let icon = undefined;
+ if (process.platform === "linux")
+ icon = path.join(__dirname, 'libs/icons/linux.png');
+
+ // Create the browser window.
+ win = new electron.BrowserWindow({
+ width: DEFAULT_WINDOW_WIDTH,
+ height: DEFAULT_WINDOW_HEIGHT,
+ icon: icon,
+ webPreferences: {
+ // nodeIntegrationInSubFrames: true,
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ // Hide the menu bar.
+ win.removeMenu();
+
+ // and load the index.html of the app.
+ win.loadURL(url.format({
+ pathname: path.join(__dirname, 'app.html'),
+ protocol: 'file:',
+ slashes: true
+ }));
+
+ // Open the DevTools.
+ win.webContents.openDevTools();
+
+ // Emitted when the window is closed.
+ win.on('closed', () => {
+ // Dereference the window object, usually you would store windows
+ // in an array if your app supports multi windows, this is the time
+ // when you should delete the corresponding element.
+ win = null;
+ });
+
+ // As suggested in https://github.com/electron/electron/issues/4068
+ const inputMenu = electron.Menu.buildFromTemplate([
+ { role: 'cut' },
+ { role: 'copy' },
+ { role: 'paste' }
+ ]);
+
+ win.webContents.on('context-menu', (e, props) => {
+ const { isEditable } = props;
+ if (isEditable) {
+ inputMenu.popup(win);
+ }
+ });
+
+ const handleRedirect = (e, uri) => {
+ if (uri !== win.webContents.getURL()) {
+ e.preventDefault();
+ electron.shell.openExternal(uri);
+ }
+ };
+
+ // win.webContents.on('will-navigate', handleRedirect);
+ win.webContents.on('new-window', handleRedirect);
+}
+
+/**
+ * The main entry point into this application.
+ *
+ * @param {Electron} electron
+ * a reference to the electron's root context object.
+ */
+async function main(electron) {
+
+ // ensure we are running as a singleton.
+ const isLocked = electron.app.requestSingleInstanceLock();
+
+ if (!isLocked) {
+ // eslint-disable-next-line no-console
+ console.log("Exiting app is locked");
+ electron.app.quit();
+ return;
+ }
+
+ electron.app.on('second-instance', () => {
+ // Someone tried to run a second instance, we should focus our window.
+ if (!win)
+ return;
+
+ if (win.isMinimized());
+ win.restore();
+
+ win.focus();
+ });
+
+ electron.ipcMain.handle("open-dialog", async(event, options) => {
+ return await electron.dialog.showOpenDialog(options);
+ });
+
+ electron.ipcMain.handle("save-dialog", async(event, options) => {
+ return await electron.dialog.showSaveDialog(options);
+ });
+
+ electron.ipcMain.handle("get-version", async() => {
+ return await electron.app.getVersion();
+ });
+
+ electron.ipcMain.handle("open-developer-tools", () => {
+ // Open the DevTools.
+ win.webContents.openDevTools({
+ "mode": "detach",
+ "activate" : true
+ });
+ });
+
+ electron.ipcMain.handle("reload-ui", () => {
+ // Force reload...
+ win.webContents.reloadIgnoringCache();
+ });
+
+
+ await electron.app.whenReady();
+
+ createWindow(electron);
+
+ // This method will be called when Electron has finished
+ // initialization and is ready to create browser windows.
+ // Some APIs can only be used after this event occurs.
+ electron.app.on('ready', createWindow);
+
+ // Quit when all windows are closed.
+ electron.app.on('window-all-closed', () => {
+ // On macOS it is common for applications and their menu bar
+ // to stay active until the user quits explicitly with Cmd + Q
+ if (process.platform !== 'darwin') {
+ electron.app.quit();
+ }
+ });
+
+ electron.app.on('activate', () => {
+ // On macOS it's common to re-create a window in the app when the
+ // dock icon is clicked and there are no other windows open.
+ if (win === null) {
+ createWindow(electron);
+ }
+ });
+
+ // In this file you can include the rest of your app's specific main process
+ // code. You can also put them in separate files and require them here.
+
+}
+
+export {
+ main
+};
diff --git a/src/app/utils/SievePasswordManager.js b/src/app/utils/SievePasswordManager.js
deleted file mode 100755
index 07793509..00000000
--- a/src/app/utils/SievePasswordManager.js
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const crypto = require('crypto');
-
- /**
- * Implements a very simplictic password manager.
- * Which uses AES encryption to protect the saved passwords
- */
- class SievePasswordManager {
-
- /**
- * Initializes the password manager.
- *
- * @param {Function} callback
- * a callback function which implements a password prompt
- */
- constructor(callback) {
- this.algorithm = 'aes-256-ctr';
- this.master = null;
- this.callback = callback;
- this.data = {};
- }
-
- /**
- * Locks the password manager.
- * This means it drops the cached master password.
- *
- *
- */
- lock() {
- this.master = null;
- }
-
- /**
- * Unlocks the password manager.
- * In case it the password manager is locked the callback is invoked
- * in order to retieve the master password.
- *
- *
- */
- async unlock() {
- if (this.isLocked())
- this.master = await this.callback();
- }
-
- /**
- * Checks if the password manager is locked.
- * Locked means the user has not provided the master password.
- *
- * @returns {boolean}
- * true in case the passwor managerd is locked otherwise false
- */
- isLocked() {
- return this.master === null;
- }
-
- /**
- * Encrypts the given text
- * @param {string} text
- * the plain text
- * @returns {string}
- * the encrypted text as hex string
- */
- encrypt(text) {
- const cipher = crypto.createCipher(this.algorithm, this.master);
- let crypted = cipher.update(text, 'utf8', 'hex');
- crypted += cipher.final('hex');
-
- return crypted;
- }
-
- /**
- * Decrypts the given text
- * @param {string} text
- * the encrypted text as hex string
- * @returns {string}
- * the plain text
- */
- decrypt(text) {
- const decipher = crypto.createDecipher(this.algorithm, this.master);
- let dec = decipher.update(text, 'hex', 'utf8');
- dec += decipher.final('utf8');
-
- return dec;
- }
-
- /**
- * Adds or updates a password entry
- * @param {string} id
- * the unique id, which is used to identify the password.
- * Typically the account id.
- * @param {string} password
- * the password which should be stored.
- *
- */
- set(id, password) {
- this.unlock();
- this.data[id] = this.encrypt(password);
- }
-
- /**
- * Gets and decrypts a password from the the password store
- * @param {string} id
- * the unique id, which is used to identify the password.
- * Typically the account id.
- * @returns {Promise<string>}
- * the decrypted string.
- */
- get(id) {
- this.unlock();
- return this.decrypt(this.data[id]);
- }
-
- /**
- * Removes the password
- * @param {string} id
- * the unique id, which is used to identify the password.
- * Typically the account id.
- *
- */
- forget(id) {
- delete this.data[id];
- }
- }
-
- // Require modules need to use export.module
- if (typeof(module) !== "undefined" && module && module.exports)
- module.exports.SievePasswordManager = SievePasswordManager;
- else
- exports.SievePasswordManager = SievePasswordManager;
-
-})(this);
diff --git a/src/common/libManageSieve/SieveAbstractBase64.mjs b/src/common/libManageSieve/SieveAbstractBase64.mjs
new file mode 100644
index 00000000..039b7d55
--- /dev/null
+++ b/src/common/libManageSieve/SieveAbstractBase64.mjs
@@ -0,0 +1,96 @@
+/*
+* The content of this file is licensed. You may obtain a copy of
+* the license at https://github.com/thsmi/sieve/ or request it via
+* email from the author.
+*
+* Do not remove or change this comment.
+*
+* The initial author of the code is:
+* Thomas Schmid <schmid-thomas@gmx.net>
+*/
+
+/**
+ * Implements a binary base64 encoder.
+ * We can not use btoa as is does not support UTF-8
+ */
+class SieveAbstractBase64Encoder {
+
+ /**
+ * Creates a new Encoder instance.
+ *
+ * @param {Uint8Array|string} decoded
+ * the data to be encoded. Either an byte array or an UTF8 String
+ */
+ constructor(decoded) {
+ this.decoded = decoded;
+ }
+
+ /**
+ * Encodes the data into base 64 and returns a byte array.
+ *
+ * @returns {Uint8Array}
+ * the encoded data as byte array
+ *
+ * @abstract
+ */
+ toArray() {
+ throw new Error("Implement SieveAbstractBase64Encoder");
+ }
+
+ /**
+ * Encodes the data into base64 and returns a string.
+ *
+ * @returns {string}
+ * the encoded data as string
+ *
+ * @abstract
+ */
+ toUtf8() {
+ throw new Error("Implement SieveAbstractBase64Encoder");
+ }
+}
+
+/**
+ * Implements a binary base64 decoded, due to historic reasons
+ * browser only support decoding UTF-16 and not UTF-8.
+ *
+ * Node uses a Buffer for decoding.
+ */
+class SieveAbstractBase64Decoder {
+ /**
+ * Creates a new decoder instance
+ *
+ * @param {string | Uint8Array} encoded
+ * the encoded string which should be decoded
+ */
+ constructor(encoded) {
+ this.encoded = encoded;
+ }
+
+ /**
+ * Decodes the base64 data into a raw array
+ *
+ * @returns {Uint8Array}
+ * the output as raw array.
+ *
+ * @abstract
+ */
+ toArray() {
+ throw new Error(`Implement SieveAbstractBase64Decoder::raw()`);
+ }
+
+ /**
+ * Decodes the base64 array into a UTF8 String
+ *
+ * @returns {string}
+ * the decoded array as UTF8 string
+ */
+ toUtf8() {
+ return (new TextDecoder("UTF-8")).decode(this.toArray());
+ }
+}
+
+export {
+ SieveAbstractBase64Decoder,
+ SieveAbstractBase64Encoder
+};
diff --git a/src/common/libManageSieve/SieveAbstractClient.js b/src/common/libManageSieve/SieveAbstractClient.js
deleted file mode 100755
index 8d793f25..00000000
--- a/src/common/libManageSieve/SieveAbstractClient.js
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-/*
- * This class is a simple socket implementation for the manage sieve protocol.
- * Due to the asymmetric nature of the Mozilla sockets we need message queue.
- * <p>
- * New requests are added via the "addRequest" method. In case of a response,
- * the corresponding request will be automatically called back via its
- * "addResponse" method.
- * <p>
- * If you need a secure connection, set the flag secure in the constructor.
- * Then connect to the host. And invoke the "startTLS" Method as soon as you
- * negotiated the switch to a encrypted connection. After calling startTLS
- * Mozilla will immediately switch to an encrypted connection.
- * <p>
- */
-
-(function (exports) {
-
- "use strict";
-
- const DEFAULT_TIMEOUT = 20000;
- const NO_IDLE = 0;
-
- /**
- * An abstract implementation for the manage sieve protocol.
- *
- * It implements a message pump and parsing facility.
- * Only the connections to the transport are needed to be implemented.
- *
- * The javascript syntax for this code is extremely limited.
- * As this code is used for a mozilla module as well as in node js.
- *
- * Due to various limitation there is no window object and also no toSource().
- * Same applies to timeouts. They need to be implemented with platform
- * specific code.
- *
- * In general you should avoid the "new" operator as this makes imports difficult.
- * Mozilla's Modules, Node's Require and the new ES imports are mostly
- * incompatible to each other.
- *
- */
- class SieveAbstractClient {
-
- /**
- * Creates a new instance
- */
- constructor() {
- this.host = null;
- this.port = null;
-
- this.socket = null;
- this.data = null;
-
- this.queueLocked = false;
-
- this.requests = [];
-
- this.idleDelay = 0;
-
-
- // out of the box we support the following manage sieve commands...
- // ... the server might advertise additional commands they are added ...
- // ... or removed by the set compatibility method
- this.compatibility = {
- authenticate: true,
- starttls: true,
- logout: true,
- capability: true,
- // until now we do not support havespace...
- // havespace : false,
- putscript: true,
- listscripts: true,
- setactive: true,
- getscript: true,
- deletescript: true
- };
- }
-
- /**
- * Gives this socket a hint, whether a sieve commands is supported or not.
- *
- * Setting the corresponding attribute to false, indicates, that a sieve command
- * should not be used. As this is only an advice, such command will still be
- * processed by this sieve socket.
- *
- * By default the socket seek maximal compatibility.
- *
- * @param {object} capabilities commands
- * the supported sieve commands as an associative array. Attribute names have
- * to be in lower case, the values can be either null, undefined, true or false.
- *
- * @example
- * sieve.setCompatibility({checkscript:true, rename:true, starttls:false});
- */
- setCompatibility(capabilities) {
- for (const capability in capabilities)
- this.compatibility[capability] = capabilities[capability];
- }
-
- /**
- * Returns a list of supported sieve commands. As the socket seeks
- * maximal compatibility, it always suggest the absolute minimal sieve
- * command set defined in the rfc. This value is only a hint, and does
- * not represent the server's capabilities!
- *
- * A command is most likely unsupported if the corresponding attribute is null and
- * disabled if the the attribute is false
- *
- * You should override these defaults as soon as possible.
- *
- * @returns {Struct}
- * an associative array structure indicating supported sieve command.
- * Unsupported commands are indicated by a null, disabled by false value...
- *
- * @example
- * if (sieve.getCompatibility().putscript) {
- * // put script command supported...
- * }
- */
- getCompatibility() {
- return this.compatibility;
- }
-
-
- /**
- * Gets a reference to the current logger
- * @returns {SieveAbstractLogger}
- * the current logger
- *
- * @abstract
- */
- getLogger() {
- throw new Error("Implement getLogger()");
- }
-
- /**
- * Checks if the connection to the server is still alive and can be used to send
- * and receive messages
- * @returns {boolean}
- * true in case the connection is alive otherwise false
- */
- isAlive() {
- if (!this.socket)
- return false;
-
- return true;
- }
-
- /**
- * Check is the connection supports any connection security.
- * It could be either disabled by the client or the server.
- *
- * @abstract
- *
- * @returns {boolean}
- * true in case the connection can be or is secure otherwise false
- */
- isSecure() {
- throw new Error("Implement isSecure()");
- }
-
- /**
- * This method secures the connection to the sieve server. By activating
- * Transport Layer Security all Data exchanged is encrypted.
- *
- * Before calling this method you need to request a encrypted connection by
- * sending a startTLSRequest. Invoke this method immediately after the server
- * confirms switching to TLS.
- *
- * @returns {SieveAbstractClient}
- * a self reference
- **/
- startTLS() {
- if (!this.isSecure())
- throw new Error("TLS can't be started no secure socket");
-
- if (!this.socket)
- throw new Error(`Can't start TLS, your are not connected to ${this.host}`);
-
- // Need to be overwritten in a subclass....
- return this;
- }
-
- /**
- * An internal callback which is triggered when the request timeout timer
- * should be started. This is typically whenever a new request is about to
- * be send to the server.
- *
- * @abstract
- */
- onStartTimeout() {
- throw new Error("Implement onStartTimeout()");
- }
-
- /**
- * An internal callback which is triggered when the request timeout timer
- * should be stopped. This is typically whenever a response was received and
- * the request was completed.
- *
- * @abstract
- */
- onStopTimeout() {
- throw new Error("Implement onStopTimeout()");
- }
-
-
- /**
- * Returns the maximal interval in ms between a request and a response.
- * The default timeout is 20 seconds
- * @returns {int}
- * the maximal number of milliseconds
- */
- getTimeoutWait() {
-
- // Apply some self healing magic...
- if (!this.timeoutDelay)
- return DEFAULT_TIMEOUT;
-
- return this.timeoutDelay;
- }
-
- /**
- * Specifies the maximal interval between a request and a response. If the
- * timeout elapsed, all pending request will be canceled and the event queue
- * will be cleared. Either the onTimeout() method of the most recent request
- * will invoked or in case the request does not support onTimeout() the
- * default's listener will be called.
- *
- * @param {int} interval
- * the number of milliseconds before the timeout is triggered.
- * Pass null to set the default timeout.
- * @returns {SieveAbstractClient}
- * a self reference
- */
- setTimeoutWait(interval) {
-
- this.timeoutDelay = interval;
- return this;
- }
-
-
- /**
- * Internal method trigged after a request was completely processed.
- * @abstract
- */
- onStartIdle() {
- throw new Error("Implement onStartIdle()");
- }
-
- /**
- * Internal method triggered when a new request is processed.
- * @abstract
- */
- onStopIdle() {
- throw new Error("Implement onStopIdle()");
- }
-
- /**
- * Gets the maximal number of idle time between two subsequent requests.
- * A value of zero indicates idle detection is disabled.
- *
- * @returns {int}
- * the number of ms to wait or null in case idle detection is disabled.
- */
- getIdleWait() {
- if (!this.idleDelay)
- return NO_IDLE;
-
- return this.idleDelay;
- }
-
- /**
- * Specifies the maximal interval between a response and a request.
- * If the max time elapsed, the listener's OnIdle() event will be called.
- * Thus it can be used for sending "Keep alive" packets.
- *
- * @param {int} ms
- * the maximal number of milliseconds between a response and a request,
- * pass null to deactivate.
- * @returns {SieveAbstractClient}
- * a self reference
- */
- setIdleWait(ms) {
- if (ms) {
- this.idleDelay = ms;
- return this;
- }
-
- // No keep alive Packets should be sent, so null the timer and the delay.
- this.idleDelay = 0;
- this.onStopIdle();
-
- return this;
- }
-
- /**
- *
- * @param {*} listener
- */
- addListener(listener) {
- this.listener = listener;
- }
-
- /**
- * Adds a request to the send queue.
- *
- * Normal request runs to completion, so they are blocking the queue
- * until they are fully processed. If the request fails, the error
- * handler is triggered and the request is dequeued.
- *
- * A greedy request in contrast accepts whatever it can get. Upon an
- * error greedy request are not dequeued. They fail silently and the next
- * requests is processed. This continues until a request succeeds, a non
- * greedy request fails or the queue has no more requests.
- *
- * @param {SieveAbstractRequest} request
- * the request object which should be added to the queue
- *
- * @param {boolean} [greedy]
- * if true requests fail silently
- *
- * @returns {SieveAbstractClient}
- * a self reference
- */
- addRequest(request, greedy) {
-
- // Attach the global bye listener only when needed.
- if (!request.byeListener)
- if (this.listener && this.listener.onByeResponse)
- request.addByeListener(this.listener.onByeResponse);
-
- // TODO: we should really store this internally, instead of tagging objects
- if (greedy)
- request.isGreedy = true;
-
- // Add the request to the message queue
- this.requests.push(request);
-
- // If the message queue was empty, we might have to reinitialize the...
- // ... request pump.
-
- // We can skip this if queue is locked...
- if (this.queueLocked)
- return this;
-
- let idx;
- // ... or it contains more than one full request
- for (idx = 0; idx < this.requests.length; idx++)
- if (this.requests[idx].isUnsolicited())
- break;
-
- if (idx === this.requests.length)
- return this;
-
- if (this.requests[idx] !== request)
- return this;
-
- this._sendRequest();
-
- return this;
- }
-
-
- /**
- * Connects to a ManageSieve server.
- * @abstract
- *
- * @param {string} host
- * The target hostname or IP address as String
- * @param {int} port
- * The target port as Integer
- * @param {boolean} secure
- * If true, a secure socket will be created. This allows switching to a secure
- * connection.
- * @param {nsIProxyInfo[]} proxy
- * An Array of nsIProxyInfo Objects which specifies the proxy to use.
- * Pass an empty array for no proxy.
- * Set to null if the default proxy should be resolved. Resolving proxy info is
- * done asynchronous. The connect method returns immediately, without any
- * information on the connection status...
- * Currently only the first array entry is evaluated.
- *
- * @returns {SieveAbstractClient}
- * a self reference
- */
- // eslint-disable-next-line no-unused-vars
- connect(host, port, secure, proxy) {
- throw new Error("Implement me SieveAbstractClient ");
- }
-
- /**
- * Cancels all pending request.
- *
- * @param {Error} [reason]
- * the optional reason why the request was canceled.
- */
- cancel(reason) {
-
- while (this.requests.length)
- this.requests.shift().cancel(reason);
- }
-
- /**
- * Disconnects from the server.
- *
- * Need to be overwritten. The current implementation is a stub
- * which takes care about stopping the timeouts.
- *
- * @param {Error} [reason]
- * the optional reason why the client was disconnected.
- */
- disconnect(reason) {
-
- this.getLogger().logState(`Disconnecting ${this.host}:${this.port}...`);
-
- this.cancel(reason);
-
- // free requests...
- // this.requests = new Array();
- this.onStopTimeout();
- this.onStopIdle();
- }
-
- /**
- * Called whenever the client enters idle state.
- * Which means no request where send for the given idle time.
- *
- * It emits a signal to external idle listeners.
- */
- async onIdle() {
-
- this.onStopIdle();
-
- this.getLogger().logState("libManageSieve/Sieve.js:\nOnIdle");
-
- if (this.listener && this.listener.onIdle)
- await this.listener.onIdle();
- }
-
- /**
- * Called whenever a request was not responded in a reasonable timeframe.
- * It cancel all pending requests and emits a timeout signal to the listeners.
- */
- onTimeout() {
-
- this.onStopTimeout();
-
- this.getLogger().logState("libManageSieve/Sieve.js:\nOnTimeout");
-
- // clear receive buffer and any pending request...
- this.data = null;
-
- let idx = 0;
- while ((idx < this.requests.length) && (this.requests[idx].isGreedy))
- idx++;
-
- // ... and cancel the active request. It will automatically invoke the ...
- // ... request's onTimeout() listener.
- if (idx < this.requests.length) {
- const request = this.requests[idx];
- this.requests.splice(0, idx + 1);
-
- request.cancel(new Error("Timeout"));
- return;
- }
-
- // in case no request is active, we call the global listener
- this.requests = [];
-
- if (this.listener && this.listener.onTimeout)
- this.listener.onTimeout();
-
- }
-
- /**
- * Creates a new request parser instance
- * @abstract
- *
- * @param {byte[]} data
- * the data to be parsed
- * @returns {SieveNodeResponseParser}
- * the request parser
- */
- createParser(data) {
- throw new Error(`Implement SieveAbstractClient::createParser(${data})`);
- }
-
- /**
- * Creates a new response builder instance
- * @abstract
- *
- * @returns {SieveAbstractRequestBuilder}
- * the response builder.
- */
- createRequestBuilder() {
- throw new Error("Implement SieveAbstractClient::createRequestBuilder");
- }
-
- /**
- * Called when data was received on the socket.
- *
- * @param {byte[]} data
- * the data received.
- */
- onReceive(data) {
-
- if (this.getLogger().isLevelStream())
- this.getLogger().logStream(`Server -> Client [Byte Array]\n ${data}`);
-
- if (this.getLogger().isLevelResponse())
- this.getLogger().logResponse(data);
-
- // responses packets could be fragmented...
- if ((this.data === null) || (this.data.length === 0))
- this.data = data;
- else
- this.data = this.data.concat(data);
-
- // is a request handler waiting?
- if (this.requests.length === 0)
- return;
-
- // first clear the timeout, parsing starts...
- this.onStopTimeout();
-
- // As we are callback driven, we need to lock the event queue. Otherwise our
- // callbacks could manipulate the event queue while we are working on it.
- let requests = this._lockMessageQueue();
-
- // greedy request take might have an response but do not have to have one.
- // They munch what they get. If there's a request they are fine,
- // if there's no matching request it's also ok.
- let idx = -1;
-
- while (idx + 1 < requests.length) {
- idx++;
- const parser = this.createParser(this.data);
-
- try {
- requests[idx].addResponse(parser);
-
- // We do some cleanup as we don't need the parsed data anymore...
- this.data = parser.getByteArray();
-
- // parsing was successful, so drop every previous request...
- // ... keep in mid previous greedy request continue on exceptions.
- requests = requests.slice(idx);
-
- // we started to parse the request, so it can't be greedy anymore.
- }
- catch (ex) {
- // request could be fragmented or something else, as it's greedy,
- // we don't care about any exception. We just log them in oder
- // to make debugging easier....
- if (this.getLogger().isLevelState()) {
- this.getLogger().logState(`Parsing Warning in libManageSieve/Sieve.js:\\n${ex.toString()}`);
- this.getLogger().logState(ex.stack);
- }
-
- // a greedy request might or might not get an request, thus
- // it's ok if it fails
- if (requests[idx].isGreedy)
- continue;
-
- // ... but a non greedy response must parse without an error. Otherwise...
- // ... something is broken. this is most likely caused by a fragmented ...
- // ... packet, but could be also a broken response. So skip processing...
- // ... and restart the timeout. Either way the next packet or the ...
- // ... timeout will resolve this situation for us.
-
- this._unlockMessageQueue(requests);
- this.onStartTimeout();
-
- return;
- }
-
- // Request is completed...
- if (!requests[0].hasNextRequest()) {
- // so remove it from the event queue.
- const request = requests.shift();
- // and update the index
- idx--;
-
- // ... if it was greedy, we munched an unexpected packet...
- // ... so there is still a valid request dangling around.
- if (request.isGreedy)
- continue;
- }
-
- if (!parser.isEmpty())
- continue;
-
- this._unlockMessageQueue(requests);
-
-
- // Are there any other requests waiting in the queue.
-
- // TODO FIX ME should always be dispatched, to relax the main thread.
- // But in mozilla modules we don't have access to a window object and
- // timeouts are more complicated.
-
- // var that = this;
- // window.setTimeout(function () {that._sendRequest()}, 0);
-
- this._sendRequest();
-
- return;
- }
-
-
- // we end up here only if all responses where greedy or there were no...
- // ... response parser at all. Thus all we can do is release the message...
- // ... queue, cache the data and wait for a new response to be added.
-
- this._unlockMessageQueue(requests);
-
- this.getLogger().logState("Skipping Event Queue");
- }
-
- /**
- *
- */
- _sendRequest() {
-
- let idx = 0;
- while (idx < this.requests.length) {
- if (this.requests[idx].isUnsolicited())
- break;
-
- idx++;
- }
-
- if (idx >= this.requests.length)
- return;
-
- // start the timeout, before sending anything. So that we will timeout...
- // ... in case the socket is jammed...
- this.onStartTimeout();
-
- const output = this.requests[idx].getNextRequest(this.createRequestBuilder()).getBytes();
-
- if (this.getLogger().isLevelRequest())
- this.getLogger().logRequest(`Client -> Server:\n${output}`);
-
- this.onSend(output);
-
- return;
- }
-
- /**
- * Called everytime data is ready to send.
- * @abstract
- *
- * @param {object} data
- * the data to send to the server.
- */
- onSend(data) {
- throw new Error(`Implement SieveAbstractClient::onSend(${data})`);
- }
-
- /**
- *
- */
- _lockMessageQueue() {
- this.queueLocked = true;
- const requests = this.requests.concat();
-
- this.requests = [];
-
- return requests;
- }
-
- /**
- *
- * @param {*} requests
- */
- _unlockMessageQueue(requests) {
- this.requests = requests.concat(this.requests);
- this.queueLocked = false;
- }
- }
-
- exports.SieveAbstractClient = SieveAbstractClient;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractClient.mjs b/src/common/libManageSieve/SieveAbstractClient.mjs
new file mode 100644
index 00000000..adee86d5
--- /dev/null
+++ b/src/common/libManageSieve/SieveAbstractClient.mjs
@@ -0,0 +1,1034 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/*
+ * This class is a simple socket implementation for the manage sieve protocol.
+ * Due to the asymmetric nature of the Mozilla sockets we need message queue.
+ * <p>
+ * New requests are added via the "addRequest" method. In case of a response,
+ * the corresponding request will be automatically called back via its
+ * "addResponse" method.
+ * <p>
+ * If you need a secure connection, set the flag secure in the constructor.
+ * Then connect to the host. And invoke the "startTLS" Method as soon as you
+ * negotiated the switch to a encrypted connection. After calling startTLS
+ * Mozilla will immediately switch to an encrypted connection.
+ * <p>
+ */
+
+import { SieveTimer } from "./SieveTimer.mjs";
+import { SieveResponseParser } from "./SieveResponseParser.mjs";
+import { SieveRequestBuilder } from "./SieveRequestBuilder.mjs";
+
+const DEFAULT_TIMEOUT = 20000;
+const NO_IDLE = 0;
+
+
+const NOT_STARTED = -1;
+
+/**
+ * Implements a locked message queue.
+ */
+class LockedMessageQueue {
+
+ /**
+ * Creates a new instance and moves all of the given requests
+ * into the locked queue.
+ *
+ * @param {SieveAbstractRequest[]} items
+ * the data which should be moved.
+ */
+ constructor(items) {
+ this.offset = NOT_STARTED;
+ this.items = [...items.splice(0, items.length)];
+ }
+
+ /**
+ * Removes all remaining element from the queue and returns them
+ * as array
+ *
+ * @returns {SieveAbstractRequest[]}
+ * all remaining items.
+ */
+ dequeue() {
+ let offset = this.offset;
+
+ if (offset === NOT_STARTED)
+ offset = 0;
+
+ this.offset = NOT_STARTED;
+ return this.items.splice(offset, this.items.length);
+ }
+
+ /**
+ * Used to drain the message queue. Marks all all pending requests as abandoned
+ * This typically happens when the connection was lost or upon an
+ * forced disconnect.
+ *
+ * @param {object} reason
+ * the reason why the message queue was abandoned. Typically an exception.
+ */
+ shutdown(reason) {
+ for (const item of this.items)
+ item.abandon(reason);
+ }
+
+ /**
+ * Truncates the queue on the current position.
+ * The current element plus all previous elements will be removed.
+ * The queue iterator will be reset to not started;
+ */
+ trunc() {
+ if (this.offset === NOT_STARTED)
+ return;
+
+ this.items.splice(0, this.offset + 1);
+ this.reset();
+ }
+
+ /**
+ * Returns the remaining number of elements contained inside this queue.
+ *
+ * @returns {int}
+ * the number of remaining elements.
+ */
+ length() {
+ if (this.offset === NOT_STARTED)
+ return this.items.length;
+
+ return this.items.length - this.offset;
+ }
+
+ /**
+ * Resets the iterator to not started.
+ */
+ reset() {
+ this.offset = NOT_STARTED;
+ }
+
+ /**
+ * Checks if the queue has remaining elements.
+ *
+ * @returns {boolean}
+ * true in case there are more elements. False in case end was reached.
+ */
+ hasNext() {
+ return (this.offset + 1 < this.items.length);
+ }
+
+ /**
+ * Returns the queues next element.
+ *
+ * @returns {SieveAbstractRequest}
+ * the next queued request.
+ */
+ getNext() {
+ this.offset++;
+ return this.items[this.offset];
+ }
+}
+
+/**
+ * Implements a simple message queue logic.
+ */
+class MessageQueue {
+
+ /**
+ * Creates a new message queue instance.
+ */
+ constructor() {
+ this.queued = [];
+ this.locked = null;
+ this.canceled = false;
+ }
+
+ /**
+ * Adds a new request to the end of the message queue
+ *
+ * @param {SieveAbstractRequest} request
+ * the request to be added.
+ *
+ * @returns {MessageQueue}
+ * a self reference.
+ */
+ enqueue(request) {
+ this.queued.push(request);
+ return this;
+ }
+
+ /**
+ * Used to drain the message queue. Marks all all enqueued requests as abandoned.
+ *
+ * @param {string} reason
+ * the human readable string why the message queue was abandoned.
+ *
+ * @returns {MessageQueue}
+ * a self reference.
+ */
+ shutdown(reason) {
+
+ if (this.isLocked())
+ this.getLock().shutdown(reason);
+
+ while (this.queued.length)
+ this.queued.shift().abandon(reason);
+
+ return this;
+ }
+
+ /**
+ * Checks if the message queue has non completed request.
+ *
+ * @returns {boolean}
+ * true in case the queue is locked otherwise false.
+ */
+ isEmpty() {
+ if (this.isLocked() && this.locked.length())
+ return false;
+
+ return (this.queued.length === 0);
+ }
+
+ /**
+ * Checks if the message queue is currently busy.
+ *
+ * @returns {boolean}
+ * true in case the message queue is busy otherwise false.
+ */
+ isLocked() {
+ return (this.locked !== null);
+ }
+
+ /**
+ * Returns the locked and protected message queue.
+ * It will work only in case the queue is locked.
+ * Otherwise and exception will be thrown.
+ *
+ * @returns {LockedMessageQueue}
+ * the currently locked message queue.
+ */
+ getLock() {
+ if (!this.locked)
+ throw new Error("Message queue is not locked");
+
+ return this.locked;
+ }
+
+ /**
+ * Locks and protects the current message queue from changes.
+ *
+ * @returns {MessageQueue}
+ * a self reference
+ */
+ lock() {
+
+ if (this.isLocked())
+ throw new Error("Message queue is already locked");
+
+ this.locked = (new LockedMessageQueue(this.queued));
+ return this;
+ }
+
+ /**
+ * Unlocks the message queue, and allows changes.
+ *
+ * @returns {MessageQueue}
+ * a self reference
+ */
+ unlock() {
+ if (!this.isLocked())
+ return this;
+
+ // Copy all entries from the locked queue back into our normal queue.
+ this.queued = [...this.locked.dequeue(), ...this.queued];
+ this.locked = null;
+
+ return this;
+ }
+
+ /**
+ * Checks if the queue is unlocked and if there are any
+ * queued request which are ready to be processed.
+ *
+ * @returns {SieveAbstractRequest}
+ * the next element or null in case the queue is locked or no
+ * request is enqueued.
+ */
+ peek() {
+ // The queue is locked this means we should not send anything.
+ if (this.isLocked())
+ return null;
+
+ // Check if there is any request which can be send out.
+ for (const item of this.queued) {
+ if (!item.hasRequest())
+ continue;
+
+ return item;
+ }
+
+ return null;
+ }
+}
+
+/**
+ * A simple double buffer implementation.
+ *
+ * JavaScript is single threaded but async. Which means when deferring
+ * a call it can happen that some one else modifies the buffer.
+ *
+ * In order to prevent this we use two buffers one buffer which can be
+ * always safely written and an other buffer which can be always read.
+ *
+ * When calling flush the write buffer will be transferred into the read buffer
+ * Calling trunc cleans the read buffer.
+ */
+class DoubleBuffer {
+
+ /**
+ * Creates a new instance.
+ */
+ constructor() {
+ this.writer = [];
+ this.reader = [];
+ }
+
+ /**
+ * Stores data inside the double buffer.
+ *
+ * @param {byte[]} data
+ * the data to be stored inside the writer
+ * @returns {DoubleBuffer}
+ * a self reference.
+ */
+ write(data) {
+ this.writer.push(...data);
+ return this;
+ }
+
+ /**
+ * Returns a reference to the data stored inside the reader.
+ * It will update and sync the reader buffer with the writer buffer
+ * before returning.
+ *
+ * It does not change or consume any reader buffer data.
+ * To shrink or truncate the reader buffer call truncate.
+ *
+ * @returns {byte[]}
+ * the data stored inside the reader.
+ */
+ read() {
+ // We need the reader length to determine the number of new bytes.
+ const offset = this.reader.length;
+
+ // Copy the writer into the reader.
+ this.reader.push(...this.writer);
+ // an then shrink the writer.
+ this.writer.splice(0, this.reader.length - offset);
+
+ return this.reader;
+ }
+
+ /**
+ * Truncates the read buffer
+ *
+ * @param {int} count
+ * the number of bytes to be truncated
+ * @returns {DoubleBuffer}
+ * a self reference
+ */
+ trunc(count) {
+ this.reader.splice(0, count);
+ return this;
+ }
+
+ /**
+ * Checks if the write buffer is dirty.
+ * This means new data has arrived and wait to be processed.
+ *
+ * @returns {boolean}
+ * true in case the write buffer is dirty otherwise false;
+ */
+ isDirty() {
+ return (this.writer.length !== 0);
+ }
+
+ /**
+ * Clears the read as well as the write buffer.
+ *
+ * @returns {DoubleBuffer}
+ * a self reference
+ */
+ clear() {
+ this.reader = [];
+ this.writer = [];
+
+ return this;
+ }
+
+ /**
+ * Returns the total buffer length, the sum of the read plus the write buffer.
+ *
+ * @returns {int}
+ * the total buffer length
+ */
+ length() {
+ return this.reader.length + this.writer.length;
+ }
+}
+
+/**
+ * An abstract implementation for the manage sieve protocol.
+ *
+ * It implements a message pump and parsing facility.
+ * Only the connections to the transport are needed to be implemented.
+ *
+ * The javascript syntax for this code is extremely limited.
+ * As this code is used for a mozilla module as well as in node js.
+ *
+ * Due to various limitation there is no window object and also no toSource().
+ * Same applies to timeouts. They need to be implemented with platform
+ * specific code.
+ *
+ * In general you should avoid the "new" operator as this makes imports difficult.
+ * Mozilla's Modules, Node's Require and the new ES imports are mostly
+ * incompatible to each other.
+ *
+ */
+class SieveAbstractClient {
+
+ /**
+ * Creates a new instance
+ */
+ constructor() {
+ this.host = null;
+ this.port = null;
+
+ this.socket = null;
+ this.buffer = new DoubleBuffer();
+ this.queue = new MessageQueue();
+
+ this.requests = [];
+
+ this.idleDelay = 0;
+
+ this.timeoutTimer = new SieveTimer();
+ this.idleTimer = new SieveTimer();
+
+
+ // out of the box we support the following manage sieve commands...
+ // ... the server might advertise additional commands they are added ...
+ // ... or removed by the set compatibility method
+ this.compatibility = {
+ authenticate: true,
+ starttls: true,
+ logout: true,
+ capability: true,
+ // until now we do not support havespace...
+ // havespace : false,
+ putscript: true,
+ listscripts: true,
+ setactive: true,
+ getscript: true,
+ deletescript: true
+ };
+ }
+
+ /**
+ * Gives this socket a hint, whether a sieve commands is supported or not.
+ *
+ * Setting the corresponding attribute to false, indicates, that a sieve command
+ * should not be used. As this is only an advice, such command will still be
+ * processed by this sieve socket.
+ *
+ * By default the socket seek maximal compatibility.
+ *
+ * @param {object} capabilities commands
+ * the supported sieve commands as an associative array. Attribute names have
+ * to be in lower case, the values can be either null, undefined, true or false.
+ *
+ * @example
+ * sieve.setCompatibility({checkscript:true, rename:true, starttls:false});
+ */
+ setCompatibility(capabilities) {
+ for (const capability in capabilities)
+ this.compatibility[capability] = capabilities[capability];
+ }
+
+ /**
+ * Returns a list of supported sieve commands. As the socket seeks
+ * maximal compatibility, it always suggest the absolute minimal sieve
+ * command set defined in the rfc. This value is only a hint, and does
+ * not represent the server's capabilities!
+ *
+ * A command is most likely unsupported if the corresponding attribute is null and
+ * disabled if the the attribute is false
+ *
+ * You should override these defaults as soon as possible.
+ *
+ * @returns {Struct}
+ * an associative array structure indicating supported sieve command.
+ * Unsupported commands are indicated by a null, disabled by false value...
+ *
+ * @example
+ * if (sieve.getCompatibility().putscript) {
+ * // put script command supported...
+ * }
+ */
+ getCompatibility() {
+ return this.compatibility;
+ }
+
+
+ /**
+ * Gets a reference to the current logger
+ * @returns {SieveLogger}
+ * the current logger
+ *
+ * @abstract
+ */
+ getLogger() {
+ throw new Error("Implement getLogger()");
+ }
+
+ /**
+ * Checks if the connection to the server is still alive and can be used to send
+ * and receive messages
+ * @returns {boolean}
+ * true in case the connection is alive otherwise false
+ */
+ isAlive() {
+ if (!this.socket)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Check is the connection supports any connection security.
+ * It could be either disabled by the client or the server.
+ *
+ * @abstract
+ *
+ * @returns {boolean}
+ * true in case the connection can be or is secure otherwise false
+ */
+ isSecure() {
+ throw new Error("Implement isSecure()");
+ }
+
+ /**
+ * This method secures the connection to the sieve server. By activating
+ * Transport Layer Security all Data exchanged is encrypted.
+ *
+ * Before calling this method you need to request a encrypted connection by
+ * sending a startTLSRequest. Invoke this method immediately after the server
+ * confirms switching to TLS.
+ *
+ * @returns {SieveAbstractClient}
+ * a self reference
+ **/
+ startTLS() {
+ if (!this.isSecure())
+ throw new Error("TLS can't be started no secure socket");
+
+ if (!this.socket)
+ throw new Error(`Can't start TLS, your are not connected to ${this.host}`);
+
+ // Need to be overwritten in a subclass....
+ return this;
+ }
+
+ /**
+ * An internal callback which is triggered when the request timeout timer
+ * should be started. This is typically whenever a new request is about to
+ * be send to the server.
+ *
+ * @abstract
+ */
+ onStartTimeout() {
+
+ this.getLogger().logState("[SieveAbstractClient:onStartTimeout()] Starting/Restarting timeout");
+ // clear any existing timeouts
+ this.getTimeoutTimer().cancel();
+
+ // ensure the idle timer is stopped
+ this.onStopIdle();
+
+ // then restart the timeout timer.
+ this.getTimeoutTimer().start(
+ () => { this.onTimeout(); },
+ this.getTimeoutWait());
+ }
+
+ /**
+ * An internal callback which is triggered when the request timeout timer
+ * should be stopped. This is typically whenever a response was received and
+ * the request was completed.
+ *
+ * @abstract
+ */
+ onStopTimeout() {
+ this.getLogger().logState("[SieveAbstractClient:onStopTimeout()] Stopping timeout");
+
+ // clear any existing timeouts.
+ this.getTimeoutTimer().cancel();
+
+ // and start the idle timer
+ this.onStartIdle();
+ }
+
+
+ /**
+ * Returns the maximal interval in ms between a request and a response.
+ * The default timeout is 20 seconds
+ * @returns {int}
+ * the maximal number of milliseconds
+ */
+ getTimeoutWait() {
+
+ // Apply some self healing magic...
+ if (!this.timeoutDelay)
+ return DEFAULT_TIMEOUT;
+
+ return this.timeoutDelay;
+ }
+
+ /**
+ * Specifies the maximal interval between a request and a response. If the
+ * timeout elapsed, all pending request will be canceled and the event queue
+ * will be cleared. Either the onTimeout() method of the most recent request
+ * will invoked or in case the request does not support onTimeout() the
+ * default's listener will be called.
+ *
+ * @param {int} interval
+ * the number of milliseconds before the timeout is triggered.
+ * Pass null to set the default timeout.
+ * @returns {SieveAbstractClient}
+ * a self reference
+ */
+ setTimeoutWait(interval) {
+
+ this.timeoutDelay = interval;
+ return this;
+ }
+
+ /**
+ * Returns the timer used to track timeouts.
+ * It is guaranteed to be non null.
+ *
+ * @returns {SieveTimer}
+ * the current timeout timer
+ */
+ getTimeoutTimer() {
+ return this.timeoutTimer;
+ }
+
+ /**
+ * Returns the timer used to track idle.
+ * It is guaranteed to be non null.
+ *
+ * @returns {SieveTimer}
+ * the current idle timer.
+ */
+ getIdleTimer() {
+ return this.idleTimer;
+ }
+
+ /**
+ * Internal method trigged after a request was completely processed.
+ * @abstract
+ */
+ onStartIdle() {
+ // first ensure the timer is stopped..
+ this.onStopIdle();
+
+ // ... then configure the timer.
+ const delay = this.getIdleWait();
+
+ if (!delay)
+ return;
+
+ this.getIdleTimer().start(() => { this.onIdle(); }, delay);
+ }
+
+ /**
+ * Internal method triggered when a new request is processed.
+ */
+ onStopIdle() {
+ this.getIdleTimer().cancel();
+ }
+
+ /**
+ * Gets the maximal number of idle time between two subsequent requests.
+ * A value of zero indicates idle detection is disabled.
+ *
+ * @returns {int}
+ * the number of ms to wait or null in case idle detection is disabled.
+ */
+ getIdleWait() {
+ if (!this.idleDelay)
+ return NO_IDLE;
+
+ return this.idleDelay;
+ }
+
+ /**
+ * Specifies the maximal interval between a response and a request.
+ * If the max time elapsed, the listener's OnIdle() event will be called.
+ * Thus it can be used for sending "Keep alive" packets.
+ *
+ * @param {int} ms
+ * the maximal number of milliseconds between a response and a request,
+ * pass null to deactivate.
+ * @returns {SieveAbstractClient}
+ * a self reference
+ */
+ setIdleWait(ms) {
+ if (ms) {
+ this.idleDelay = ms;
+ return this;
+ }
+
+ // No keep alive Packets should be sent, so null the timer and the delay.
+ this.idleDelay = 0;
+ this.onStopIdle();
+
+ return this;
+ }
+
+ /**
+ * Sets the callback listener.
+ * @param {*} listener
+ */
+ addListener(listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * Adds a request to the send queue.
+ *
+ * Normal request runs to completion, so they are blocking the queue
+ * until they are fully processed. If the request fails, the error
+ * handler is triggered and the request is dequeued.
+ *
+ * @param {SieveAbstractRequest} request
+ * the request object which should be added to the queue
+ *
+ * @returns {SieveAbstractClient}
+ * a self reference
+ */
+ async addRequest(request) {
+
+ // Attach the global bye listener only when needed.
+ if (!request.byeListener)
+ if (this.listener && this.listener.onByeResponse)
+ request.addByeListener(this.listener.onByeResponse);
+
+ // Add the request to the message queue
+ this.queue.enqueue(request);
+ await this._sendRequest();
+
+ return this;
+ }
+
+
+ /**
+ * Connects to a ManageSieve server.
+ * @abstract
+ *
+ * @param {string} host
+ * The target hostname or IP address as String
+ * @param {int} port
+ * The target port as Integer
+ * @param {boolean} secure
+ * If true, a secure socket will be created. This allows switching to a secure
+ * connection.
+ *
+ * @returns {SieveAbstractClient}
+ * a self reference
+ */
+ // eslint-disable-next-line no-unused-vars
+ connect(host, port, secure) {
+ throw new Error("Implement me SieveAbstractClient ");
+ }
+
+ /**
+ * Cancels all pending request.
+ *
+ * @param {Error} [reason]
+ * the optional reason why the request was canceled.
+ */
+ cancel(reason) {
+
+ this.getLogger().logState(`[SieveAbstractClient:cancel()] Shutting down message queue ${reason}`);
+
+ // Mark the queue as canceled...
+ this.queue.shutdown(reason);
+
+ // ... and then retrigger the queue with an empty message.
+ // does not hurt...
+ this.onReceive([]);
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * Need to be overwritten. The current implementation is a stub
+ * which takes care about stopping the timeouts.
+ *
+ * @param {Error} [reason]
+ * the optional reason why the client was disconnected.
+ */
+ async disconnect(reason) {
+
+ this.getLogger().logState(`SieveAbstractClient: Disconnecting ${this.host}:${this.port}...`);
+
+ this.getIdleTimer().cancel();
+ this.getTimeoutTimer().cancel();
+
+ this.cancel(reason);
+ }
+
+ /**
+ * Called whenever the client enters idle state.
+ * Which means no request where send for the given idle time.
+ *
+ * It emits a signal to external idle listeners.
+ */
+ async onIdle() {
+
+ this.onStopIdle();
+
+ this.getLogger().logState("libManageSieve/Sieve.js:\nOnIdle");
+
+ if (this.listener && this.listener.onIdle)
+ await this.listener.onIdle();
+ }
+
+ /**
+ * Called whenever a request was not responded in a reasonable time frame.
+ * It cancel all pending requests and emits a timeout signal to the listeners.
+ */
+ onTimeout() {
+ this.getLogger().logState("[SieveAbstractClient:onTimeout()] Timeout fired");
+
+ this.onStopTimeout();
+
+ this.queue.shutdown(new Error("Timeout"));
+
+ return;
+ }
+
+ /**
+ * Creates a new request parser instance
+ *
+ * @param {byte[]} data
+ * the data to be parsed
+ * @returns {SieveResponseParser}
+ * the request parser
+ */
+ createParser(data) {
+ return new SieveResponseParser(data);
+ }
+
+ /**
+ * Creates a new response builder instance
+ *
+ * @returns {SieveRequestBuilder}
+ * the response builder.
+ */
+ createRequestBuilder() {
+ return new SieveRequestBuilder();
+ }
+
+
+ /**
+ * Called when data was received on the socket.
+ *
+ * @param {byte[]} data
+ * the data received.
+ */
+ async onReceive(data) {
+
+ this.getLogger().logState("[SieveAbstractClient:onReceive] Starting processing received data...");
+
+ if (this.getLogger().isLevelStream())
+ this.getLogger().logStream(`[SieveAbstractClient:onReceive] Server -> Client [Byte Array]\n ${data}`);
+
+ if (this.getLogger().isLevelResponse())
+ this.getLogger().logResponse(data);
+
+ // responses packets could be fragmented...
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... add data to buffer...");
+ this.buffer.write(data);
+
+ // is a request handler waiting?
+ if (this.queue.isEmpty()) {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... skipping, no request handler ready.");
+ return;
+ }
+
+ if (this.queue.isLocked()) {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... skipping queue is locked.");
+ return;
+ }
+
+ // first clear the timeout, parsing starts...
+ this.onStopTimeout();
+
+ // Sound strange but as we are async, we need to lock the event queue.
+ // Otherwise an async function may manipulate the event queue while
+ // we are waiting for a callback.
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... locking Message Queue ...");
+
+ try {
+ const lock = this.queue.lock().getLock();
+
+ while (lock.hasNext()) {
+
+ const request = lock.getNext();
+
+ if (request.isAbandoned()) {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... skipping abandoned request ...");
+ await request.onAbandoned();
+ this.buffer.clear();
+ lock.trunc();
+ continue;
+ }
+
+ // We can bail out in case we ran out of data
+ if (!this.buffer.length()) {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... ran out of data, waiting for more");
+ this.onStartTimeout();
+ return;
+ }
+
+ this.getLogger().logState(`[SieveAbstractClient:onReceive] `
+ + `Start parsing ${this.buffer.length()}`);
+
+ try {
+ const parser = this.createParser(this.buffer.read());
+
+ await (request.onResponse(parser));
+
+ // We do some cleanup as we don't need the parsed data anymore...
+ this.buffer.trunc(parser.getPosition());
+
+ this.getLogger().logState(`[SieveAbstractClient:onReceive] `
+ + `Parsing successful, remaining ${this.buffer.length()} bytes`);
+
+ } catch (ex) {
+
+ if (request.isOptional()) {
+ this.getLogger().logState(`[SieveAbstractClient:onReceive] `
+ + `... failed but is optional, skipping to next request`);
+
+ continue;
+ }
+
+ // Parsing the response failed. This is most likely caused by fragmentation
+ // and will be resolved as soon as the remaining bytes arrive.
+ //
+ // In case it is really a syntax error the we will run into a timeout.
+ //
+ // So in either way the next packet or the timeout will resolve this
+ // situation for us.
+
+ if (this.getLogger().isLevelState()) {
+ this.getLogger().logState(`Parsing Warning in libManageSieve/Sieve.js:\\n${ex.toString()}`);
+ this.getLogger().logState(ex.stack);
+ }
+
+ // In case the buffer is dirty new data arrived while we were parsing
+ // and we are ready to try it again.
+ if (this.buffer.isDirty()) {
+ this.getLogger().logState(`[SieveAbstractClient:onReceive] `
+ + `... buffers are dirty, restarting parsing.`);
+
+ lock.reset();
+ continue;
+ }
+
+ // Restore the message queue and restart the timer.
+ this.onStartTimeout();
+ this.getLogger().logState("[SieveAbstractClient:onReceive] Waiting for more data to continue");
+ return;
+ }
+
+ // The request was processed but it is not yet completed because
+ // is needs to send a new response.
+ //
+ // This means we need to stop here so that the message queue restarts.
+ if (request.hasNextRequest()) {
+ // First drop all processed request and then restart the processing
+ lock.reset();
+ break;
+ }
+
+ // The request was processed and is completed. So let's get rid of it.
+ lock.trunc();
+
+ this.getLogger().logState(`[SieveAbstractClient:onReceive] `
+ + `Removing request from queue ${lock.length()}, ${this.buffer.length()}`);
+ }
+ } finally {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] ... unlocking Message Queue ...");
+ this.queue.unlock();
+ }
+
+ // Finally we need to check if a new request arrived while we were
+ // parsing the response and restart the message processing
+ if (!this.queue.isEmpty()) {
+ this.getLogger().logState("[SieveAbstractClient:onReceive] Restarting request processing");
+ await this._sendRequest();
+ }
+
+ this.getLogger().logState("[SieveAbstractClient:onReceive] Finished processing received data");
+ }
+
+ /**
+ * Send the next request, if available.
+ */
+ async _sendRequest() {
+
+ const request = this.queue.peek();
+
+ if (!request)
+ return;
+
+ // start the timeout, before sending anything. So that we will timeout...
+ // ... in case the socket is jammed...
+ this.onStartTimeout();
+
+ const output = (await request.getNextRequest(this.createRequestBuilder())).getBytes();
+
+ if (this.getLogger().isLevelRequest())
+ this.getLogger().logRequest(`Client -> Server:\n${output}`);
+
+ this.onSend(output);
+
+ return;
+ }
+
+ /**
+ * Called everytime data is ready to send.
+ * @abstract
+ *
+ * @param {object} data
+ * the data to send to the server.
+ */
+ onSend(data) {
+ throw new Error(`Implement SieveAbstractClient::onSend(${data})`);
+ }
+}
+
+export { SieveAbstractClient };
diff --git a/src/common/libManageSieve/SieveAbstractCrypto.js b/src/common/libManageSieve/SieveAbstractCrypto.js
deleted file mode 100755
index 76758b88..00000000
--- a/src/common/libManageSieve/SieveAbstractCrypto.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const MIN_SALT_LENGTH = 2;
- const MIN_ITERATION_COUNT = 0;
-
- const MAX_CHAR_CODE = 255;
-
- const HEX_STRING = 16;
-
- /**
- * Crypto implementations are very browser specific.
- * Which means we need a separate wrapper for each browser.
- */
- class SieveAbstractCrypto {
-
- /**
- * Creates a new crypto wrapper.
- * @param {string} name
- * the crypto algorithms name.
- */
- constructor(name) {
- this.name = name;
- }
-
- /**
- * Converts a byte array into a hex string
- * @param {byte[]} tmp
- * the byte array which should be converted.
- * @returns {string}
- * the hex string
- * @abstract
- */
- byteArrayToHexString(tmp) {
- let str = "";
- for (let i = 0; i < tmp.length; i++)
- str += ("0" + tmp[i].toString(HEX_STRING)).slice(-2);
-
- return str;
- }
-
- /**
- * Converts a byte array into a string
- *
- * @param {byte[]} bytes
- * the byte array which should be converted.
- * @returns {string}
- * the converted string
- */
- byteArrayToStr(bytes) {
- let result = "";
-
- if (Array.isArray(bytes) === false)
- throw new Error("Parameter bytes is not a byte array");
-
- for (let i = 0; i < bytes.length; i++) {
- const byte = String.fromCharCode(bytes[i]);
- if (byte > MAX_CHAR_CODE)
- throw new Error(`Byte Array Invalid: ${byte}`);
-
- result += byte;
- }
-
- return result;
- }
-
- /**
- * Converts a binary string into a byte array.
- *
- * @param {string} str
- * the string which should be converted.
- * @returns {byte[]}
- * the converted string in byte array representation
- */
- strToByteArray(str) {
- const result = [];
-
- for (let i = 0; i < str.length; i++) {
- if (str.charCodeAt(i) > MAX_CHAR_CODE)
- throw new Error(`Invalid Characters for Binary String: ${str.charCodeAt(i)}`);
-
- result.push(str.charCodeAt(i));
- }
-
- return result;
- }
-
- /**
- * Calculates the HMAC keyed hash for the given algorithm.
- * @abstract
- *
- * @param {byte[]} key
- * The key as octet string
- * @param {byte[]|string} bytes
- * The input string as byte array or string
- * @returns {byte[]}
- * the calculated HMAC keyed hash for the given input string. E.g. HMAC-SHA-1 hashes are
- * always always 20 octets long.
- */
- HMAC(key, bytes) {
- throw new Error(`Implement HMAC Algorithm for ${this.name} with key ${key} and data ${bytes}`);
- }
-
-
- /**
- * Calculates the Hash for the given algorithm.
- * @abstract
- *
- * @param {bytes[]|string} bytes
- * The input string as byte array or string
- * @returns {byte[]}
- * the calculated hash value for the input string.s
- */
- H(bytes) {
- throw new Error(`Implement Hashing Algorithm for ${this.name} with data ${bytes}`);
- }
-
- /**
- * Hi(str, salt, i) is a PBKDF2 [RFC2898] implementation with HMAC() as the
- * pseudorandom function (PRF) and with dkLen == output length of HMAC() == output
- * length of H().
- *
- * "str" is an octet input string while salt is a random octet string.
- * "i" is the iteration count, "+" is the string concatenation operator,
- * and INT(1) is a 4-octet encoding of the integer with the value 1.
- *
- * Hi(str, salt, i):
- *
- * U1 := HMAC(str, salt + INT(1))
- * U2 := HMAC(str, U1)
- * ...
- * Ui-1 := HMAC(str, Ui-2)
- * Ui := HMAC(str, Ui-1)
- *
- * Hi := U1 XOR U2 XOR ... XOR Ui
- *
- * @param {byte[]|string} str
- * an octet input string
- * @param {byte[]|string} salt
- * random octet string
- * @param {int} i
- * iteration count a positive number (>= 1), suggested to be at least 4096
- *
- * @returns {byte[]}
- * the pseudorandom value as byte string
- */
- Hi(str, salt, i) {
-
- if (Array.isArray(str) === false)
- str = this.strToByteArray(str);
-
- if (Array.isArray(salt) === false)
- salt = this.strToByteArray(salt);
-
- if (salt.length < MIN_SALT_LENGTH)
- throw new Error("Insufficient salt");
-
- if (i <= MIN_ITERATION_COUNT)
- throw new Error("Invalid Iteration counter");
-
- salt.push(0, 0, 0, 1);
-
- salt = this.HMAC(str, salt);
-
- const hi = salt;
-
- while (--i) {
- salt = this.HMAC(str, salt);
-
- for (let j = 0; j < hi.length; j++)
- hi[j] ^= salt[j];
- }
-
- return hi;
- }
- }
-
- exports.SieveAbstractCrypto = SieveAbstractCrypto;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractCrypto.mjs b/src/common/libManageSieve/SieveAbstractCrypto.mjs
new file mode 100644
index 00000000..08c77feb
--- /dev/null
+++ b/src/common/libManageSieve/SieveAbstractCrypto.mjs
@@ -0,0 +1,245 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const MIN_SALT_LENGTH = 2;
+const MIN_ITERATION_COUNT = 0;
+
+const MAX_CHAR_CODE = 255;
+
+const HEX_STRING = 16;
+
+const HASH_SHA1 = "SHA-1";
+const HASH_SHA256 = "SHA-256";
+const HASH_SHA512 = "SHA-512";
+
+// eslint-disable-next-line no-magic-numbers
+const MAGIC_SALT = [0, 0, 0, 1];
+
+
+/**
+ * Crypto implementations are very browser specific.
+ * Which means we need a separate wrapper for each browser.
+ */
+class SieveAbstractCrypto {
+
+ /**
+ * Creates a new crypto wrapper.
+ * @param {string} name
+ * the crypto algorithms name.
+ */
+ constructor(name) {
+
+ if ((name !== HASH_SHA1) && (name !== HASH_SHA256) && (this.name !== HASH_SHA512))
+ throw new Error(`Unknown Hash algorithm ${name}`);
+
+ this.name = name;
+ }
+
+ /**
+ * Returns the hashing algorithm.
+ * In case the algorithm is unknown an exception is thrown.
+ *
+ * @returns {string}
+ * the hash algorithm
+ */
+ getCryptoHash() {
+ return this.name;
+ }
+
+ /**
+ * Converts a byte array into a hex string
+ * @param {byte[]} tmp
+ * the byte array which should be converted.
+ * @returns {string}
+ * the hex string
+ * @abstract
+ */
+ byteArrayToHexString(tmp) {
+ let str = "";
+ for (let i = 0; i < tmp.length; i++)
+ str += ("0" + tmp[i].toString(HEX_STRING)).slice(-2);
+
+ return str;
+ }
+
+ /**
+ * Converts a byte array into a string
+ *
+ * @param {byte[]} bytes
+ * the byte array which should be converted.
+ * @returns {string}
+ * the converted string
+ */
+ byteArrayToStr(bytes) {
+ let result = "";
+
+ if (Array.isArray(bytes) === false)
+ throw new Error("Parameter bytes is not a byte array");
+
+ for (let i = 0; i < bytes.length; i++) {
+ const byte = String.fromCharCode(bytes[i]);
+ if (byte > MAX_CHAR_CODE)
+ throw new Error(`Byte Array Invalid: ${byte}`);
+
+ result += byte;
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts a binary string into a byte array.
+ *
+ * @param {string} str
+ * the string which should be converted.
+ * @returns {byte[]}
+ * the converted string in byte array representation
+ */
+ strToByteArray(str) {
+ const result = [];
+
+ for (let i = 0; i < str.length; i++) {
+ if (str.charCodeAt(i) > MAX_CHAR_CODE)
+ throw new Error(`Invalid Characters for Binary String: ${str.charCodeAt(i)}`);
+
+ result.push(str.charCodeAt(i));
+ }
+
+ return result;
+ }
+
+ /**
+ * Calculates the HMAC keyed hash for the given algorithm.
+ * @abstract
+ *
+ * @param {byte[]} key
+ * The key as octet string
+ * @param {byte[]|string} bytes
+ * The input string as byte array or string
+ * @returns {byte[]}
+ * the calculated HMAC keyed hash for the given input string. E.g. HMAC-SHA-1 hashes are
+ * always always 20 octets long.
+ */
+ async HMAC(key, bytes) {
+ throw new Error(`Implement HMAC Algorithm for ${this.name} with key ${key} and data ${bytes}`);
+ }
+
+
+ /**
+ * Calculates the Hash for the given algorithm.
+ * @abstract
+ *
+ * @param {bytes[]|string} bytes
+ * The input string as byte array or string
+ * @returns {byte[]}
+ * the calculated hash value for the input string.s
+ */
+ async H(bytes) {
+ throw new Error(`Implement Hashing Algorithm for ${this.name} with data ${bytes}`);
+ }
+
+ /**
+ * Hi(str, salt, i) is a PBKDF2 [RFC2898] implementation with HMAC() as the
+ * pseudorandom function (PRF) and with dkLen == output length of HMAC() == output
+ * length of H().
+ *
+ * "str" is an octet input string while salt is a random octet string.
+ * "i" is the iteration count, "+" is the string concatenation operator,
+ * and INT(1) is a 4-octet encoding of the integer with the value 1.
+ *
+ * Hi(str, salt, i):
+ *
+ * U1 := HMAC(str, salt + INT(1))
+ * U2 := HMAC(str, U1)
+ * ...
+ * Ui-1 := HMAC(str, Ui-2)
+ * Ui := HMAC(str, Ui-1)
+ *
+ * Hi := U1 XOR U2 XOR ... XOR Ui
+ *
+ * @param {Uint8Array} key
+ * an octet input string
+ * @param {Uint8Array} salt
+ * random octet string
+ * @param {int} iterations
+ * iteration count a positive number (>= 1), suggested to be at least 4096
+ *
+ * @returns {Uint8Array}
+ * the pseudorandom value as byte string
+ */
+ async Hi(key, salt, iterations) {
+
+ if (!(key instanceof Uint8Array))
+ throw new Error("Key not an Uint8Array");
+
+ if (!(salt instanceof Uint8Array))
+ throw new Error("Salt not an Uint8Array");
+
+ if (salt.length < MIN_SALT_LENGTH)
+ throw new Error("Insufficient salt");
+
+ if (iterations <= MIN_ITERATION_COUNT)
+ throw new Error("Invalid Iteration counter");
+
+ salt = new Uint8Array([...salt, ...MAGIC_SALT]);
+
+ salt = await this.HMAC(key, salt);
+
+ const hi = salt;
+
+ while (--iterations) {
+ salt = await this.HMAC(key, salt);
+
+ for (let j = 0; j < hi.length; j++)
+ hi[j] ^= salt[j];
+ }
+
+ return new Uint8Array(hi);
+ }
+
+ /**
+ * Applies the SASLprep profile [RFC4013] of the"stringprep" algorithm
+ * [RFC3454] to the given UTF-8 encoded byte array.
+ *
+ * The resulting byte array is also in UTF-8.
+ *
+ * When applying SASLprep, "str" is treated as a "stored strings", which
+ * means that unassigned Unicode codepoints are prohibited.
+ *
+ * @param {Uint8Array} data
+ * the string to be normalized
+ * @returns {Uint8Array}
+ * the normalized string
+ */
+ normalize(data) {
+ return data;
+ /*
+ // RFC 4013 and 3454
+ C12 -> ""
+ B1 -> ""
+
+ str = str.normalize("NFKC");
+
+ // Illegal code points
+ C12, C21, C22, C3, C4, C5, C6, C7, C8, C9, A1
+ -> throw
+
+ // D1 && D2
+ -> throw
+
+ // D1 is allowed only one as first or last character
+ */
+ }
+}
+
+export {
+ SieveAbstractCrypto
+};
diff --git a/src/common/libManageSieve/SieveAbstractLogger.js b/src/common/libManageSieve/SieveAbstractLogger.js
deleted file mode 100755
index 26c77f6e..00000000
--- a/src/common/libManageSieve/SieveAbstractLogger.js
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const TWO_CHARS = 2;
- const THREE_CHARS = 3;
-
- const BASE_10 = 10;
-
- // eslint-disable-next-line no-magic-numbers
- const LOG_REQUEST = (1 << 0);
- // eslint-disable-next-line no-magic-numbers
- const LOG_RESPONSE = (1 << 1);
- // eslint-disable-next-line no-magic-numbers
- const LOG_STATE = (1 << 2);
- // eslint-disable-next-line no-magic-numbers
- const LOG_STREAM = (1 << 3);
- // eslint-disable-next-line no-magic-numbers
- const LOG_SESSION_INFO = (1 << 4);
-
- const DEFAULT_LEVEL = 0;
-
- /**
- * Implements a common and platform independent logging interface.
- * The log level is interpreted as a bit filed with turns logging
- * for the specified scope on and of.
- *
- * The level is concerning scopes and does not differentiate between
- * warning, error and info.
- */
- class SieveAbstractLogger {
-
- /**
- * Creates a new instance
- * @param {string} [prefix]
- * an optional prefix for this logger.
- * @param {int} [level]
- * the logger level
- *
- */
- constructor(prefix, level) {
- if (typeof (prefix) === "undefined")
- prefix = "";
-
- if (typeof(level) === "undefined")
- level = DEFAULT_LEVEL;
-
- this._level = level;
- this._prefix = prefix;
- }
-
- /**
- * Logs a request related information
- * @param {string} message
- * the request status to log.
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- logRequest(message) {
- return this.log(message, LOG_REQUEST);
- }
-
- /**
- * Logs response related information
- * @param {byte[]} data
- * the response status to log
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- logResponse(data) {
- const byteArray = new Uint8Array(data.slice(0, data.length));
-
- return this.log(
- "Server -> Client\n" + (new TextDecoder("UTF-8")).decode(byteArray),
- LOG_RESPONSE);
- }
-
- /**
- * Logs state machine information.
- * @param {string} message
- * the stat information to log.
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- logState(message) {
- return this.log(message, LOG_STATE);
- }
-
- /**
- * Dumps raw stream data to the log
- * @param {string} message
- * the stream information to log.
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- logStream(message) {
- return this.log(message, LOG_STREAM);
- }
-
- /**
- * Logs information about the session.
- * @param {string} message
- * the message to log.
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- logSession(message) {
- return this.log(message, LOG_SESSION_INFO);
- }
-
- /**
- * Logs the given message to the browser console.
- *
- * @abstract
- *
- * @param {string} message
- * The message which should be logged
- * @param {int} [level]
- * the log level. If omitted the message will be always logged.
- * @returns {SieveAbstractLogger}
- * a self reference
- */
- log(message, level) {
- throw new Error(`Implement log(${message},${level})`);
- }
-
- /**
- * Checks if state information should be logged.
- *
- * @returns {boolean}
- * true in case state information should be logged otherwise false.
- */
- isLevelState() {
- return this.isLoggable(LOG_STATE);
- }
-
- /**
- * Checks if session information should be logged.
- *
- * @returns {boolean}
- * true in case session information should be logged otherwise false.
- */
- isLevelSession() {
- return this.isLoggable(LOG_SESSION_INFO);
- }
-
- /**
- * Tests if the log level should log.
- *
- * @param {int} level
- * the level which should be checked.
- * @returns {boolean}
- * true in case the log level is activated otherwise false
- */
- isLoggable(level) {
- if (typeof (level) === "undefined")
- return true;
-
- return !!(this.level() & level);
- }
-
- /**
- * Checks if stream data should be logged.
- *
- * @returns {boolean}
- * true in case the stream data should be logged otherwise false
- */
- isLevelStream() {
- return this.isLoggable(LOG_STREAM);
- }
-
- /**
- * Checks if request data should be logged.
- *
- * @returns {boolean}
- * true in case the request data should be logged otherwise false
- */
- isLevelRequest() {
- return this.isLoggable(LOG_REQUEST);
- }
-
- /**
- * Checks if response data should be logged.
- *
- * @returns {boolean}
- * true in case the response data should be logged otherwise false
- */
- isLevelResponse() {
- return this.isLoggable(LOG_RESPONSE);
- }
-
- /**
- * Gets and sets the log level to the given bit mask.
- * Note that the log level is a bit mask, every bit in the
- * bit mask corresponds to a special logger.
- *
- * In order to activate or deactivate a logger you need to
- * get the level toggle the desired bits and set the new level.
- *
- * @param {int} [level]
- * the desired log level as bit mask.
- * @returns {int}
- * the current log level
- */
- level(level) {
- if (typeof (level) !== "undefined")
- this._level = level;
-
- return this._level;
- }
-
-
- /**
- * Pads the given string with leading zeros
- * @private
- *
- * @param {string} n
- * the string which should be padded
- * @param {int} m
- * the maximum padding.
- *
- * @returns {string}
- * the padded string
- */
- _pad(n, m) {
-
- let str = n;
-
- for (let i = 0; i < m; i++)
- if (n < Math.pow(BASE_10, i))
- str = '0' + str;
-
- return str;
- }
-
- /**
- * Gets the current time in iso format (hh:mm:ss.SSS)
- *
- * @returns {string}
- * the current timestamp as string.
- */
- getTimestamp() {
-
- const date = new Date();
- return this._pad(date.getHours(), TWO_CHARS)
- + ":" + this._pad(date.getMinutes(), TWO_CHARS)
- + ":" + this._pad(date.getSeconds(), TWO_CHARS)
- + "." + this._pad(date.getMilliseconds(), THREE_CHARS);
- }
-
-
- /**
- * Gets and sets the loggers prefix. The prefix is appended to
- * every logger message
- *
- * @param {string} [prefix]
- * the new prefix.
- * @returns {string}
- * the current prefix.
- */
- prefix(prefix) {
-
- if (typeof (prefix) !== "undefined")
- this._prefix = prefix;
-
- return this._prefix;
- }
- }
-
- exports.SieveAbstractLogger = SieveAbstractLogger;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractRequestBuilder.js b/src/common/libManageSieve/SieveAbstractRequestBuilder.js
deleted file mode 100755
index 7c56d024..00000000
--- a/src/common/libManageSieve/SieveAbstractRequestBuilder.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- /**
- * A helper class used to build standard compliant sieve requests.
- */
- class SieveAbstractRequestBuilder {
-
- /**
- * Creates a new instance
- */
- constructor() {
- this.data = "";
- }
-
- /**
- * Adds a string as quoted base 64 encoded literal to the request.
- *
- * This is typically needed for SASL requests as they have to be
- * base64 encoded by definition.
- *
- * @param {string} token
- * the string which should be added to the request.
- * @returns {SieveAbstractRequestBuilder}
- * a self reference
- */
- addQuotedBase64(token) {
- if (token === undefined || token === null)
- throw new Error("Invalid token");
-
- this.addLiteral('"' + this.convertToBase64(token) + '"');
- return this;
- }
-
- /**
- * Adds a string as quoted literal to the request.
- *
- * This is typically used for string without a line break.
- * In case you know you'll have a line break use the multiline
- * version for better readability.
- *
- * Do not use this for any sasl method. All sasl strings
- * have to be base 64 encoded. Refer to addQuotedBase64String instead.
- *
- * @param {string} [token]
- * the string which should be added to the request.
- * if omitted an empty string is sent.
- * @returns {SieveAbstractRequestBuilder}
- * a self reference
- */
- addQuotedString(token) {
- if (typeof (token) === "undefined" || token === null)
- token = "";
-
- this.addLiteral('"' + this.escapeString(token) + '"');
- return this;
- }
-
- /**
- * Adds a string as multiline literal to the request.
- *
- * It improves the requests readability in case you need to send a
- * string containing a line break.
- *
- * @param {string} token
- * the string which should be added to the request.
- * @returns {SieveAbstractRequestBuilder}
- * a self reference
- */
- addMultiLineString(token) {
- this.addLiteral('{' + this.calculateByteLength(token) + '+}\r\n' + token);
- return this;
- }
-
- /**
- * Adds a literal to the request.
- * The literal will used as it is. It will not be wrapped in a string or escaped.
- * In case you need this use the specialized methods.
- *
- * @param {string} token
- * the literal which should be added.
- * @returns {SieveAbstractRequestBuilder}
- * a self reference
- */
- addLiteral(token) {
-
- if (this.data !== "")
- this.data += " ";
-
- this.data += token;
- return this;
- }
-
- /**
- * Returns the current request as it was cached and build up to the call.
- *
- * @returns {string}
- * the current request including a tailing line break
- */
- getBytes() {
- return this.data + "\r\n";
- }
-
- /**
- * Calculates a strings length in bytes.
- *
- * UTF uses variable length characters. Which means the length in bytes
- * in not necessarily equivalent to the number of characters.
- *
- * @param {string} data
- * the string for which the byte length should be calculated.
- * @returns {int}
- * the string's length in bytes.
- *
- * @abstract
- */
- calculateByteLength(data) {
- throw new Error(`Implement SieveAbstractRequestBuilder::calculateByteLength(${data})`);
- }
-
- /**
- * Escapes a string. All Backslashes are converted to \\ while
- * all quotes are escaped as \"
- *
- * @param {string} str
- * the string which should be escaped
- * @returns {string}
- * the escaped string.
- */
- escapeString(str) {
- return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
- }
-
- /**
- * Encodes a string into base64
- * @param {string|byte[]} decoded
- * the string or byte array which shall be converted to base64
- * @returns {string}
- * the encoded string.
- *
- * @abstract
- */
- convertToBase64(decoded) {
- throw new Error(`Implement SieveAbstractRequestBuilder::convertToBase64(${decoded})`);
- }
-
- /**
- * Decodes a base64 encoded string
- * @param {string} encoded
- * the base64 encoded string which should be decoded
- * @returns {string}
- * the decoded string
- *
- * @abstract
- */
- convertFromBase64(encoded) {
- throw new Error(`Implement SieveAbstractRequestBuilder::convertFromBase64(${encoded})`);
- }
- }
-
- exports.SieveAbstractRequestBuilder = SieveAbstractRequestBuilder;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractResponseParser.js b/src/common/libManageSieve/SieveAbstractResponseParser.js
deleted file mode 100755
index 0ba0c50c..00000000
--- a/src/common/libManageSieve/SieveAbstractResponseParser.js
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const CHAR_LF = 10;
- const CHAR_CR = 13;
- const CHAR_SPACE = 32;
- const CHAR_QUOTE = 34;
- const CHAR_BACKSLASH = 92;
- const CHAR_LEFT_BRACES = 123;
- const CHAR_RIGHT_BRACES = 125;
-
- const NOT_FOUND = -1;
- const CHAR_LEN = 1;
-
- /**
- * The manage sieve protocol syntax uses a fixed grammar which is based on atomic tokens.
- * This class offers an interface to test for and extract these predefined tokens. It supports
- * Strings (Quoted and Literal), White Space (Line Break, Space ...) as well as arbitrary tokens.
- *
- * The parser does not change or alter the byte array's content. So extracting data does not shrink
- * the array free any bytes. This parser is just some kind of a view to this array.
- *
- * Tokens are automatically converted from UTF-8 encoded byte arrays to JavaScript Unicode Strings
- * during extraction.
- */
- class SieveAbstractResponseParser {
-
- /**
- * Expects as input a byte array using UTF-8 encoding. It's because the manage sieve
- * protocol is defined to uses UTF-8 encoding and Mozilla sockets return byte based incoming messages streams.
- * @param {byte[]} data
- * the response which should be parsed as a byte array encoded in UTF-8
- */
- constructor(data) {
- if ((typeof (data) === 'undefined') || (data === null))
- throw new Error("Error Parsing Response...\nData is null");
-
- this.pos = 0;
- this.data = data;
- }
-
- /**
- * Extracts the given number of bytes from the buffer.
- *
- * @param {int} size
- * The number of bytes as integer which should be extracted
- *
- */
- extract(size) {
- this.pos += size;
- }
-
- /**
- * Tests if the array starts with a line break (#13#10)
- *
- * @returns {boolean}
- * true if the buffer with a line break, otherwise false
- */
- isLineBreak() {
- // Are we out of bounds?
- if (this.data.length < this.pos + CHAR_LEN)
- return false;
-
- // Test for a line break #13#10
- if (this.data[this.pos] !== CHAR_CR)
- return false;
-
- if (this.data[this.pos + CHAR_LEN] !== CHAR_LF)
- return false;
-
- return true;
- }
-
- /**
- * Extracts a line break (#13#10) for the buffer
- *
- * If it does not start with a line break an exception is thrown.
- *
- * @returns {SieveAbstractResponseParser}
- * a self reference
- */
- extractLineBreak() {
- if (this.isLineBreak() === false)
- throw new Error(`Line break expected but found:\n${this.getData()}`);
-
- this.pos += "\r\n".length;
-
- return this;
- }
-
- /**
- * Test if the buffer starts with a space character (#32)
- * @returns {boolean}
- * true if buffer starts with a space character, otherwise false
- */
- isSpace() {
- if (this.data[this.pos] === CHAR_SPACE)
- return true;
-
- return false;
- }
-
- /**
- * Extracts a space character (#32) form the buffer
- *
- * If it does not start with a space character an exception is thrown.
- *
- */
- extractSpace() {
- if (this.isSpace() === false)
- throw new Error(`Space expected but found:\n${this.getData()}`);
-
- this.pos++;
- }
-
- /**
- * Tests if the current buffer position is a literal string.
- * Literals strings are defined as:
- *
- * literal = "{" number "+}" CRLF *OCTET
- *
- * @returns {boolean}
- * true in case it is a literal otherwise false.
- */
- isLiteral() {
- if (this.data[this.pos] === CHAR_LEFT_BRACES)
- return true;
-
- return false;
- }
-
- /**
- * Extracts a literal string from the current position.
- * Literals strings are defined as:
- *
- * literal = "{" number "+}" CRLF *OCTET
- *
- * Please not it is perfectly fine to have a literal with a zero byte length.
- *
- * @returns {string}
- * the string or an exception in case the literal could not be extracted.
- */
- extractLiteral() {
- if (this.isLiteral() === false)
- throw new Error(`Literal Expected but found\n ${this.getData()}`);
-
- // remove the "{"
- this.pos++;
-
- // some sieve implementations are broken, this means ....
- // ... we can get "{4+}\r\n1234" or "{4}\r\n1234"
-
- const nextBracket = this.indexOf(CHAR_RIGHT_BRACES);
- if (nextBracket === NOT_FOUND)
- throw new Error(`Error unbalanced parentheses "{" in \n ${this.getData()}`);
-
- // extract the size, and ignore "+"
- const size = parseInt(this.getData(this.pos, nextBracket).replace(/\+/, ""), 10);
-
- this.pos = nextBracket + CHAR_LEN;
-
- this.extractLineBreak();
-
- // extract the literal...
- const literal = this.getData(this.pos, this.pos + size);
- this.pos += size;
-
- return literal;
- }
-
- /**
- * Searches the buffer for a character.
- *
- * @param {byte} character
- * the character which should be found
- * @param {int} [offset]
- * an absolute offset, from which to start searching
- * @returns {int} character
- * the characters absolute position within the buffer otherwise -1 if not found
- */
- indexOf(character, offset) {
- if (typeof (offset) === "undefined")
- offset = this.pos;
-
- for (let i = offset; i < this.data.length; i++)
- if (this.data[i] === character)
- return i;
-
- return NOT_FOUND;
- }
-
- /**
- * Test if the buffer starts with a quote character (#34)
- * @returns {boolean}
- * true if buffer starts with a quote character, otherwise false
- */
- isQuoted() {
- if (this.data[this.pos] === CHAR_QUOTE)
- return true;
-
- return false;
- }
-
- /**
- * Extracts a quoted string form the buffer. It is aware of escape sequences.
- *
- * If it does not start with a valid string an exception is thrown.
- *
- * @returns {string}
- * the quoted string extracted, it is guaranteed to be free of escape sequences
- */
- extractQuoted() {
- if (this.isQuoted() === false)
- throw new Error(`Quoted string expected but found\n${this.getData()}`);
-
- // now search for the end. But we need to be aware of escape sequences.
- let nextQuote = this.pos + CHAR_LEN;
-
- while (this.data[nextQuote] !== CHAR_QUOTE) {
-
- // Quoted stings can not contain line breaks...
- if (this.data[nextQuote] === CHAR_LF)
- throw new Error("Line break (LF) in Quoted String detected");
-
- if (this.data[nextQuote] === CHAR_CR)
- throw new Error("Line break (CR) in Quoted String detected");
-
- // is it an escape sequence?
- if (this.data[nextQuote] === CHAR_BACKSLASH) {
- // Yes, it's a backslash so get the next char...
- nextQuote++;
-
- // ... only \\ and \" are valid escape sequences
- if ((this.data[nextQuote] !== CHAR_BACKSLASH) && (this.data[nextQuote] !== CHAR_QUOTE))
- throw new Error("Invalid Escape Sequence");
- }
-
- // move to the next character
- nextQuote++;
-
- if (this.nextQuote >= this.data.length)
- throw new Error("Unterminated Quoted string");
- }
-
- let quoted = this.getData(this.pos + CHAR_LEN, nextQuote);
-
- this.pos = nextQuote + CHAR_LEN;
-
- // Cleanup escape sequences
- quoted = quoted.replace(/\\"/g, '"');
- quoted = quoted.replace(/\\\\/g, '\\');
-
- return quoted;
- }
-
- /**
- * Tests if the a quoted or literal string starts at the current position.
- *
- * @returns {boolean}
- * true if a strings starts, otherwise false
- */
- isString() {
- if (this.isQuoted())
- return true;
-
- if (this.isLiteral())
- return true;
-
- return false;
- }
-
- /**
- * Extracts a quoted or literal string from the current position
- *
- * @returns {string}
- * the quote or literal string or an exception in case no string could be extracted.
- */
- extractString() {
- if (this.isQuoted())
- return this.extractQuoted();
- if (this.isLiteral())
- return this.extractLiteral();
-
- throw new Error(`String expected but found\n${this.getData()}`);
- }
-
- /**
- * Extracts a token form a response. The token is being delimited by any
- * separator. The extracted token does not include the separator.
- *
- * Throws an exception if none of the separators is found.
- *
- * @param {byte[]} separators
- * an array containing possible token separators. The first match always wins.
- * @returns {string}
- * the extracted token.
- */
- extractToken(separators) {
- // Search for the separators, the one with the lowest index which is not...
- // ... equal to -1 wins. The -2 indicates not initialized...
- let index = NOT_FOUND;
-
- for (let i = 0; i < separators.length; i++) {
- const idx = this.indexOf(separators[i], this.pos);
-
- if (idx === NOT_FOUND)
- continue;
-
- if (index === NOT_FOUND)
- index = idx;
- else
- index = Math.min(index, idx);
- }
-
- if (index === NOT_FOUND)
- throw new Error(`Delimiter >>${separators}<< not found in: ${this.getData()}`);
-
- const token = this.getData(this.pos, index);
- this.pos = index;
-
- return token;
- }
-
- /**
- * Tests if the buffer starts with the specified bytes.
- *
- * As the buffer is encoded in UTF-8, the specified bytes have to be
- * encoded in UTF-8, otherwise the result is unpredictable.
- *
- * @param {Byte[]} bytes
- * the bytes to compare as byte array encoded in UTF-8
- *
- * @returns {boolean}
- * true if bytes match the beginning of the buffer, otherwise false
- */
- startsWith(bytes) {
- if (!bytes.length)
- return false;
-
- for (let i = 0; i < bytes.length; i++) {
- let result = false;
-
- for (let ii = 0; ii < bytes[i].length; ii++)
- if (bytes[i][ii] === this.data[this.pos + i])
- result = true;
-
- if (result === false)
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns a copy of the current buffer.
- *
- * @returns {byte[]}
- * an a copy of the array's current view. It is encoded in UTF-8
- */
- getByteArray() {
- return this.data.slice(this.pos, this.data.length);
- }
-
- /**
- * Returns a copy of the response parsers buffer as JavaScript Unicode string.
- *
- * Manage Sieve encodes literals in UTF-8 while network sockets are usually
- * binary. So we can't use java scripts build in string functions as they expect
- * pure unicode.
- *
- * @param {int} [startIndex]
- * Optional zero-based index at which to begin.
- * @param {int} [endIndex]
- * Optional Zero-based index at which to end.
- * @returns {string} the copy buffers content
- */
- getData(startIndex, endIndex) {
-
- if (typeof (endIndex) === "undefined" || endIndex === null)
- endIndex = this.data.length;
-
- if (typeof (startIndex) === "undefined" || startIndex === null)
- startIndex = this.pos;
-
- const byteArray = this.data.slice(startIndex, endIndex);
- return this.convertToString(byteArray);
- }
-
-
- /**
- * Check if the buffer is empty. This means the buffer does not contain any
- * extractable bytes or tokens.
- *
- * @returns {boolean}
- * true if the buffer is empty, otherwise false
- */
- isEmpty() {
- if (this.data.length >= this.pos)
- return true;
-
- return false;
- }
-
- /**
- * Converts a byte array into an UTF8 encoded string
- *
- * @param {byte[]} byteArray
- * the byte array which should be converted.
- *
- * @returns {string}
- * the UT8 encoded string.
- *
- * @abstract
- */
- convertToString(byteArray) {
- throw new Error(`convertToString(${byteArray})`);
- }
-
- /**
- * Encodes a clear text string to a base64 encoded string.
- *
- * @param {string} decoded
- * the clear text string which should be encoded.
- * @returns {string}
- * the base64 encoded string.
- *
- * @abstract
- */
- convertToBase64(decoded) {
- throw new Error(`Implement convertToBase64(${decoded})`);
- }
-
-
- /**
- * Decodes an base64 encoded string into a cleat text string.
- *
- * @param {string} encoded
- * the base64 encoded string which should be decoded.
- * @returns {string}
- * the decoded string.
- *
- * @abstract
- */
- convertFromBase64(encoded) {
- throw new Error(`Implement convertFromBase64(${encoded})`);
- }
- }
-
- exports.SieveAbstractResponseParser = SieveAbstractResponseParser;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractSession.js b/src/common/libManageSieve/SieveAbstractSession.js
deleted file mode 100644
index b9a0ea12..00000000
--- a/src/common/libManageSieve/SieveAbstractSession.js
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveLogger } = require("./SieveLogger.js");
-
- const { Sieve } = require("./SieveClient.js");
-
- const {
- SieveSaslPlainRequest,
- SieveSaslLoginRequest,
- SieveSaslCramMd5Request,
- SieveSaslScramSha1Request,
- SieveSaslScramSha256Request,
- SieveSaslExternalRequest,
-
- SieveInitRequest,
- SieveListScriptsRequest,
- SieveRenameScriptRequest,
- SievePutScriptRequest,
- SieveDeleteScriptRequest,
- SieveGetScriptRequest,
- SieveSetActiveRequest,
- SieveCheckScriptRequest,
- SieveLogoutRequest,
- SieveNoopRequest,
- SieveCapabilitiesRequest,
- SieveStartTLSRequest
- } = require("./SieveRequest.js");
-
- const {
- SieveClientException,
- SieveServerException,
- SieveReferralException,
- SieveTimeOutException,
- SieveCertValidationException
- } = require("./SieveExceptions.js");
-
- const FIRST_ELEMENT = 0;
- const SIEVE_PORT = 4190;
-
-
- /**
- * This class realizes a manage sieve connection to a remote server.
- * It provides the logic for login, logout, heartbeats, watchdogs and
- * much more.
- *
- * It is save to have concurrent call within a session. The sieve backend
- * uses a queue to process them. So you don't need to worry about using
- * stuff in parallel.
- *
- * It is highly async but uses the ES6 await syntax, which makes it behave
- * like a synchronous api.
- */
- class SieveAbstractSession {
-
- /**
- * Creates a new Session instance.
- *
- * @param {string} id
- * the unique session id.
- * @param {object.<string, object>} options
- * a dictionary with options as key/value pairs.
- */
- constructor(id, options) {
- this.id = id;
- this.options = options;
- this.listeners = {};
- this.sieve = null;
- }
-
- /**
- * Returns the logger bound to this session.
- * @abstract
- *
- * @returns {SieveLogger}
- * a reference to the current logger
- */
- getLogger() {
- if (!this.logger)
- this.logger = new SieveLogger(this.id, this.getOption("logLevel"));
-
- return this.logger;
- }
-
- /**
- * Returns the sieve client bound to this session.
- *
- * @returns {SieveAbstractClient}
- * a reference to the sieve client.
- */
- getSieve() {
- return this.sieve;
- }
-
- /**
- * Creates a new sieve client for this session.
- */
- createSieve() {
- if (this.sieve !== undefined && this.sieve !== null)
- throw new SieveClientException("Sieve Connection Active");
-
- this.sieve = new Sieve(this.getLogger());
- }
-
- /**
- * The server may close our connection after being idle for too long.
- * This can be prevented by sending regular keep alive packets.
- *
- * If supported the noop command is used otherwise a capability
- * request is used.
- */
- async onIdle() {
- this.getLogger().logSession("Sending keep alive packet...");
- await this.noop();
- }
-
- /**
- * Binds the capabilities to the sieve object.
- *
- * @param {object} capabilities
- * a struct containing the capabilities.
- */
- setCapabilities(capabilities) {
-
- this.getSieve().setCompatibility(capabilities.getCompatibility());
-
- // FIXME we should use a getter and setter...
- this.getSieve().capabilities = {
- tls: capabilities.getTLS(),
- extensions: capabilities.getExtensions(),
- sasl: capabilities.getSasl(),
- implementation: capabilities.getImplementation(),
- version: capabilities.getVersion()
- };
- }
-
- /**
- * Checks if the current session is connected to the server
- *
- * @returns {boolean}
- * true in case the session is connected otherwise false.
- */
- isConnected() {
- if (!this.getSieve())
- return false;
-
- return this.getSieve().isAlive();
- }
-
- /**
- * Normally the server returns more than one SASL Mechanism.
- * The list is sorted by the server. It starts with the most
- * preferred mechanism and ends with the least preferred one.
- *
- * This means in case the user has forced a preferred mechanism.
- * We try to use this first. In case is is not supported by the server
- * or the user has no preference we start iterating though the advertised
- * mechanism until we find a matching one.
- *
- * The is one exception to this rule. As suggested in the RFC,
- * the SASL Login is only used as very last resort.
- *
- * Note: In case we do not support any of the server's advertised
- * mechanism an exception is thrown.
- *
- * Note: LOGIN is deprecated, it is only used as very last resort.
- *
- * @param {string} [mechanism]
- * the sasl mechanism which shall be used.
- * If omitted or set to "default" the most preferred which is supported
- * by client ans server is chosen.
- *
- * @returns {SieveAbstractSaslRequest}
- * the sasl request which implements the most preferred compatible mechanism.
- */
- getSaslMechanism(mechanism) {
-
- if (mechanism === undefined || mechanism === null)
- mechanism = "default";
-
- if (mechanism === "default")
- mechanism = [...this.getSieve().capabilities.sasl];
- else
- mechanism = [mechanism];
-
- // ... translate the SASL Mechanism into an SieveSaslLogin Object ...
- while (mechanism.length > 0) {
- // remove and test the first element...
- switch (mechanism.shift().toUpperCase()) {
- case "PLAIN":
- return new SieveSaslPlainRequest();
-
- case "CRAM-MD5":
- return new SieveSaslCramMd5Request();
-
- case "SCRAM-SHA-1":
- return new SieveSaslScramSha1Request();
-
- case "SCRAM-SHA-256":
- return new SieveSaslScramSha256Request();
-
- case "EXTERNAL":
- return new SieveSaslExternalRequest();
-
- case "LOGIN":
- // as suggested in the RFC, we use SASL LOGIN
- // only as last resort...
-
- // this means in case it is the only mechanism
- // we have no options
- if (!mechanism.length)
- return new SieveSaslLoginRequest();
-
- // otherwise be move it to the end of the
- // mechanism list.
- mechanism.push("LOGIN");
- break;
- }
- }
-
- throw new SieveClientException("No compatible SASL Mechanism (error.sasl)");
- }
-
- /**
- * Gets an configuration parameter from the session's options.
- *
- * @param {string} name
- * the option name.
- * @param {Function} [fallback]
- * the optional fallback value, if not undefined is returned.
- *
- * @returns {object}
- * the option's value.
- */
- getOption(name, fallback) {
- if (this.options[name] === null || this.options[name] === undefined)
- return fallback;
-
- return this.options[name];
- }
-
- /**
- * Registers the callback listener for the given name.
- * There can be at most one listener per name.
- *
- * To disable a listener just set the callback handler to null
- * or undefined
- *
- * @param {string} name
- * the callback event name.
- * @param {Function} [callback]
- * the callback function, if omitted the handler will be removed.
- */
- on(name, callback) {
-
- if (name === "authenticate") {
- this.listeners.onAuthenticate = callback;
- return;
- }
-
- if (name === "authorize") {
- this.listeners.onAuthorize = callback;
- return;
- }
-
- if (name === "proxy") {
- this.listeners.onProxyLookup = callback;
- return;
- }
-
- if (name === "certerror") {
- this.listeners.onCertError = callback;
- return;
- }
-
- throw new SieveClientException(`Unknown callback handler ${name}`);
- }
-
- /**
- * The authentication starts for secure connections after
- * the tls handshake and for unencrypted connection directly
- * after the initial server response.
- *
- * Please note after a successful tls handshake the server
- * may update the SASL Mechanism. The server may force the user
- * to use TLS by providing initially an empty list of SASL
- * Mechanisms. After a successful tls handshake it then upgrades
- * the SASL Mechanisms.
- */
- async authenticate() {
-
- const mechanism = this.getOption("sasl", "default");
-
- if (mechanism === "none")
- return;
-
- const request = this.getSaslMechanism(mechanism);
-
- if (!this.listeners.onAuthenticate)
- throw new SieveClientException("No Authentication handler registered");
-
- const authentication = await this.listeners.onAuthenticate(request.hasPassword());
-
- // SASL External has no password it relies completely on SSL...
- if (request.hasPassword()) {
- const password = authentication.password;
-
- if (typeof (password) === "undefined" || password === null)
- throw new SieveClientException("error.authentication");
-
- request.setPassword(password);
- }
-
- request.setUsername(authentication.username);
-
- // check if the authentication method supports proxy authorization...
- if (request.isAuthorizable()) {
-
- if (!this.listeners.onAuthorize)
- throw new SieveClientException("No Authorization handler registered");
-
- // ... if so retrieve the authorization identity
- const authorization = await this.listeners.onAuthorize();
-
- if (typeof (authorization) === "undefined" || authorization === null)
- throw new SieveClientException("error.authorization");
-
- if (authorization !== "")
- request.setAuthorization(authorization);
- }
-
- await this.sendRequest(request, false);
- }
-
- /**
- * Starts a new TLS connections.
- * It throws an exception in case the tls handshake failed for some reason.
- *
- * Old Cyrus servers do not advertise their capabilities
- * after the tls handshake. So we need some magic here.
- * In addition the implicit request we send an explicit
- * capability request.
- *
- * A bug free server will return with two capability responses
- * while a buggy implementation returns only one.
- *
- * @param {object} [options]
- * key value pairs with implementation specific options
- */
- async startTLS(options) {
-
- if (!this.getSieve().isSecure())
- return;
-
- if (!this.getSieve().capabilities.tls)
- throw new SieveClientException("Server does not support a secure connection.");
-
- try {
- await this.sendRequest(new SieveStartTLSRequest(), false);
-
- await this.getSieve().startTLS(options);
-
- // A bug free server we end up with two capability request, one
- // implicit after startTLS and one explicit from capabilities.
- // So we have to consume one of them silently...
- const capabilities = await this.sendRequest([
- new SieveCapabilitiesRequest(),
- new SieveInitRequest()], false);
-
- this.setCapabilities(capabilities);
-
- } catch (ex) {
- // Upon a cert validation we emit a notification...
- if (ex instanceof SieveCertValidationException)
- if (this.listeners.onCertError)
- this.listeners.onCertError(ex.getSecurityInfo());
-
- // ... and then rethrow.
- throw ex;
- }
- }
-
- /**
- * Converts a callback driven request into async/await code.
- *
- * @param {SieveAbstractRequest|Array<SieveAbstractRequest>} request
- * a request or list of request to be executed. They will be queued
- * at the same time and only the first request is used to resolve
- * the promise.
- *
- * @param {Function} [init]
- * an optional init function which will be called directly after
- * the first request was queued
- *
- * @returns {SieveAbstractResponse}
- * the response for the first request or an exception in case of an error.
- */
- async promisify(request, init) {
-
- if (!Array.isArray(request))
- request = [request];
-
- return await new Promise((resolve, reject) => {
-
- request[FIRST_ELEMENT].addResponseListener((response) => {
- resolve(response);
- });
-
- request[FIRST_ELEMENT].addErrorListener((response) => {
- reject(new SieveServerException(response));
- });
-
- request[FIRST_ELEMENT].addByeListener((response) => {
-
- if (response.getResponseCode().equalsCode("REFERRAL")) {
- reject(new SieveReferralException(response));
- return;
- }
-
- reject(new SieveServerException(response));
- });
-
- request[FIRST_ELEMENT].addTimeoutListener((error) => {
-
- if (error) {
- reject(error);
- return;
- }
-
- reject(new SieveTimeOutException("Request was canceled or took too long"));
- });
-
-
- this.getSieve().addRequest(request[FIRST_ELEMENT]);
-
- if (init)
- init();
-
- while (request.length > 1) {
- request.shift();
- this.getSieve().addRequest(request[FIRST_ELEMENT], true);
- }
- });
- }
-
- /**
- * Converts a callback driven request into async/await code.
- *
- * @param {SieveAbstractRequest|Array<SieveAbstractRequest>} request
- * a request or list of request to be executed. They will be queued
- * at the same time and only the first request is used to resolve
- * the promise.
- *
- * @param {boolean} [follow]
- * If true or omitted the request should follow server side referrals.
- * In case the server responded with a bye and a referral url. The request
- * will be queued into the new connections request queue.
- *
- * @param {Function} [init]
- * an optional init function which will be called directly after
- * the first request was queued
- *
- * @returns {SieveAbstractResponse}
- * the response for the first request or an exception in case of an error.
- */
- async sendRequest(request, follow, init) {
-
- if (follow === undefined || follow === null)
- follow = true;
-
- try {
- return await this.promisify(request, init);
- }
- catch (ex) {
-
- if (!(ex instanceof SieveReferralException)) {
- this.getLogger().logSession(`Sending Request failed ${ex}`);
- throw ex;
- }
-
- if (!follow) {
- this.getLogger().logSession(`Sending Request failed ${ex}`);
- throw ex;
- }
-
- await this.disconnect(true);
-
- this.getLogger().logSession(`Referred to Server: ${ex.getHostname()}`);
- await this.connect(ex.getHostname(), ex.getPort());
-
- return await this.promisify(request, init);
- }
- }
-
-
- /**
- * An internal method creating a server connection.
- *
- * @param {string} hostname
- * the sieve server's hostname.
- * @param {string} [port]
- * the sieve server's port. If omitted the default port 4190 is used.
- * @returns {SieveSession}
- * a self reference
- */
- async connect(hostname, port) {
-
- if (typeof (hostname) === "undefined" || hostname === null)
- throw new SieveClientException("No Hostname specified");
-
- if (typeof (port) === "undefined" || port === null)
- port = SIEVE_PORT;
-
- this.createSieve();
-
- this.getSieve().setIdleWait(this.getOption("keepAlive"));
-
- // TODO do we really need this? Or do we need this only for keep alive?
- this.getSieve().addListener(this);
-
- let proxy = null;
- if (this.listeners.onProxyLookup)
- proxy = await this.listeners.onProxyLookup(hostname, port);
-
- const init = () => {
- this.getSieve().connect(
- hostname, port,
- this.getOption("secure", true),
- this,
- proxy);
- };
-
- this.setCapabilities(
- await this.sendRequest(new SieveInitRequest(), false, init));
-
- await this.startTLS();
-
- await this.authenticate();
-
- return this;
- }
-
- /**
- * Disconnects the current sieve session.
- *
- * The disconnect is by default graceful, which means the client send a
- * logout command and waits for the server to terminate the connection.
- *
- * @param {boolean} [force]
- * if set to true the disconnect will be forced and not graceful.
- * This means the connection will be just disconnected.
- * @returns {SieveSession}
- * a self reference.
- */
- async disconnect(force) {
-
- if (this.getSieve() === null)
- return this;
-
- // We try to exit with a graceful Logout request...
- if (!force && this.getSieve().isAlive()) {
- try {
- await this.logout();
- } catch (ex) {
- this.getLogger().logSession(`Graceful logout failed ${ex}`);
- }
- }
-
- // ... in case it failed for we do it the hard way
- if (this.getSieve()) {
- await this.getSieve().disconnect();
- this.sieve = null;
- }
-
- return this;
- }
-
- /**
- * Lists all scripts available on the server.
- * @returns {string}
- * the current scripts.
- */
- async listScripts() {
- return (await this.sendRequest(new SieveListScriptsRequest())).getScripts();
- }
-
- /**
- * Renames a script.
- *
- * It prefers the new rename command. In case it is not supported it will
- * use a get, put and delete sequence to emulate the rename command.
- *
- * So the result will be the very same, but there is one slight difference.
- * Instead of throwing an error it will overwrite existing script with the
- * same name silently.
- *
- * @param {string} oldName
- * the old name
- * @param {string} newName
- * the new name
- */
- async renameScript(oldName, newName) {
-
- if (this.getSieve().getCompatibility().renamescript) {
- await this.sendRequest(new SieveRenameScriptRequest(oldName, newName));
- return;
- }
-
- // Get the scripts activation state and check if the script name clashes
- const scripts = await this.listScripts();
- let active = null;
-
- for (const item of scripts) {
- if (item.script === newName)
- throw new SieveClientException("Name already exists");
-
- if (item.script === oldName)
- active = item.active;
- }
-
- if (active === null)
- throw new SieveClientException(`Unknown Script ${oldName}`);
-
- // Get the script'S content and save is as a new file
- await this.putScript(newName,
- await this.getScript(oldName));
-
- // Activate the new script
- if (this.active === true)
- await this.activateScript(newName);
-
- // Finally delete the old script
- await this.deleteScript(oldName);
- }
-
- /**
- * Saves the given script. In case the script exists
- * it will be silently overwritten.
- *
- * @param {string} name
- * the script's name
- * @param {string} script
- * the script which should be saved.
- *
- */
- async putScript(name, script) {
- await this.sendRequest(new SievePutScriptRequest(name, script));
- }
-
- /**
- * Deletes the given script name.
- *
- * @param {string} name
- * the script which should be deleted.
- *
- */
- async deleteScript(name) {
- await this.sendRequest(new SieveDeleteScriptRequest(name));
- }
-
- /**
- * Gets the script with the given name.
- * In case the script does not exists the server will throw an error.
- *
- * @param {string} name
- * the scripts unique name
- * @returns {Promise<string>}
- * the scripts content as string
- */
- async getScript(name) {
- return (await this.sendRequest(new SieveGetScriptRequest(name))).getScriptBody();
- }
-
- /**
- * Activates the specified script and deactivated the current script.
- * Sieve supports at most one active script.
- *
- * To deactivate all scripts just omit the script parameter
- *
- * @param {string} [script]
- * the script which should be activated.
- * If omitted all script will be deactivated.
- *
- */
- async activateScript(script) {
- await this.sendRequest(new SieveSetActiveRequest(script));
- }
-
- /**
- * Checks the script for syntax errors.
- *
- * It uses the checkscript command if present otherwise
- * it emulates the checkscript by pushing a temporary script
- * to the server.
- *
- * Throws an exception in case the script is not valid.
- *
- * @param {string} script
- * the script which should be checked.
- *
- */
- async checkScript(script) {
-
- // We do not need to check an empty script...
- if (!script.length)
- return;
-
- // Use the CHECKSCRIPT command when possible, otherwise we need to ...
- // ... fallback to the PUTSCRIPT/DELETESCRIPT Hack...
- if (this.getSieve().getCompatibility().checkscript) {
- await this.sendRequest(new SieveCheckScriptRequest(script));
- return;
- }
-
- // ... we have to use the PUTSCRIPT/DELETESCRIPT Hack.
-
- // First we use PUTSCRIPT to store a temporary script on the server...
- // ... incase the command fails, it is most likely due to an syntax error...
- // ... if it succeeds the script is syntactically correct!
- await this.putScript("TMP_FILE_DELETE_ME", script);
-
- // then delete the temporary script. We need to do this only when
- // put script succeeded and when it was stored.
- await this.deleteScript("TMP_FILE_DELETE_ME");
- }
-
- /**
- * Sends a noop or keep alive response.
- * It is a request without side effect and without any
- * payload. It is typically used to test if the server
- * is available and to prevent closing the connection to the server.
- *
- * Servers do not have to support noop.
- * The implementation will use a capability request as
- * fallback as described in the rfc.
- *
- */
- async noop() {
-
- // In case th server does not support noop we fallback
- // to a capability request as suggested in the rfc.
- if (!this.getSieve().getCompatibility().noop) {
- await this.capabilities();
- }
-
- await this.sendRequest(new SieveNoopRequest());
- }
-
- /**
- * Sends a capability request.
- * In case of an error an exception will be thrown.
- *
- * @returns {object}
- * an object with the capabilities.
- */
- async capabilities() {
- return (await this.sendRequest(new SieveCapabilitiesRequest())).getDetails();
- }
-
- /**
- * Used to gracefully disconnect from the server.
- * It sends a logout request, then the server should
- * hangup the connection.
- */
- async logout() {
- await this.sendRequest(new SieveLogoutRequest(), false);
- }
-
- }
-
- exports.SieveAbstractSession = SieveAbstractSession;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveAbstractSession.mjs b/src/common/libManageSieve/SieveAbstractSession.mjs
new file mode 100644
index 00000000..4368c8e6
--- /dev/null
+++ b/src/common/libManageSieve/SieveAbstractSession.mjs
@@ -0,0 +1,841 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveLogger } from "./SieveLogger.mjs";
+import { Sieve } from "./SieveClient.mjs";
+
+import {
+ SieveSaslPlainRequest,
+ SieveSaslLoginRequest,
+ SieveSaslScramSha1Request,
+ SieveSaslScramSha256Request,
+ SieveSaslScramSha512Request,
+ SieveSaslExternalRequest,
+
+ SieveInitRequest,
+ SieveListScriptsRequest,
+ SieveRenameScriptRequest,
+ SievePutScriptRequest,
+ SieveDeleteScriptRequest,
+ SieveGetScriptRequest,
+ SieveSetActiveRequest,
+ SieveCheckScriptRequest,
+ SieveLogoutRequest,
+ SieveNoopRequest,
+ SieveCapabilitiesRequest,
+ SieveStartTLSRequest
+} from "./SieveRequest.mjs";
+
+import {
+ SieveClientException,
+ SieveServerException,
+ SieveReferralException,
+ SieveTimeOutException
+} from "./SieveExceptions.mjs";
+
+const SIEVE_PORT = 4190;
+
+
+/**
+ * This class realizes a manage sieve connection to a remote server.
+ * It provides the logic for login, logout, heartbeats, watchdogs and
+ * much more.
+ *
+ * It is save to have concurrent call within a session. The sieve backend
+ * uses a queue to process them. So you don't need to worry about using
+ * stuff in parallel.
+ *
+ * It is highly async but uses the ES6 await syntax, which makes it behave
+ * like a synchronous api.
+ */
+class SieveAbstractSession {
+
+ /**
+ * Creates a new Session instance.
+ *
+ * @param {string} id
+ * the unique session id.
+ * @param {object.<string, object>} options
+ * a dictionary with options as key/value pairs.
+ */
+ constructor(id, options) {
+ this.id = id;
+ this.options = options;
+ this.listeners = {};
+ this.sieve = null;
+ }
+
+ /**
+ * Returns the logger bound to this session.
+ * @abstract
+ *
+ * @returns {SieveLogger}
+ * a reference to the current logger
+ */
+ getLogger() {
+ if (!this.logger)
+ this.logger = new SieveLogger(this.id, this.getOption("logLevel"));
+
+ return this.logger;
+ }
+
+ /**
+ * Returns the sieve client bound to this session.
+ *
+ * @returns {SieveAbstractClient}
+ * a reference to the sieve client.
+ */
+ getSieve() {
+ return this.sieve;
+ }
+
+ /**
+ * Creates a new sieve client for this session.
+ */
+ createSieve() {
+ if (typeof(this.sieve) !== "undefined" && this.sieve !== null)
+ throw new SieveClientException("Sieve Connection Active");
+
+ this.sieve = new Sieve(this.getLogger());
+ }
+
+ /**
+ * The server may close our connection after being idle for too long.
+ * This can be prevented by sending regular keep alive packets.
+ *
+ * If supported the noop command is used otherwise a capability
+ * request is used.
+ */
+ async onIdle() {
+ this.getLogger().logSession("Sending keep alive packet...");
+ await this.noop();
+ }
+
+ /**
+ * The default error handler called upon any unhandled error or exception.
+ * Called e.g. when the connection to the server was terminated unexpectedly.
+ *
+ * The default behaviour is to disconnect.
+ *
+ * @param {Error} error
+ * the error message which causes this exceptional state.
+ */
+ async onError(error) {
+ this.getLogger().logSession(`SieveAbstractSession OnError: ${error.message}`);
+ await this.disconnect(true);
+ }
+
+ /**
+ * Called when the connection gets disconnected by the server.
+ */
+ async onDisconnected() {
+ this.getLogger().logSession(`SieveAbstractSession: onDisconnected`);
+
+ // TODO Do we really need this?
+ await this.disconnect(true);
+ }
+
+ /**
+ * Binds the capabilities to the sieve object.
+ *
+ * @param {object} capabilities
+ * a struct containing the capabilities.
+ */
+ setCapabilities(capabilities) {
+
+ this.getSieve().setCompatibility(capabilities.getCompatibility());
+
+ // FIXME we should use a getter and setter...
+ this.getSieve().capabilities = {
+ tls: capabilities.getTLS(),
+ extensions: capabilities.getExtensions(),
+ sasl: capabilities.getSasl(),
+ implementation: capabilities.getImplementation(),
+ version: capabilities.getVersion()
+ };
+ }
+
+ /**
+ * Checks if the current session is connected to the server
+ *
+ * @returns {boolean}
+ * true in case the session is connected otherwise false.
+ */
+ isConnected() {
+ if (!this.getSieve())
+ return false;
+
+ return this.getSieve().isAlive();
+ }
+
+ /**
+ * Normally the server returns more than one SASL Mechanism.
+ * The list is sorted by the server. It starts with the most
+ * preferred mechanism and ends with the least preferred one.
+ *
+ * This means in case the user has forced a preferred mechanism.
+ * We try to use this first. In case is is not supported by the server
+ * or the user has no preference we start iterating though the advertised
+ * mechanism until we find a matching one.
+ *
+ * The is one exception to this rule. As suggested in the RFC,
+ * the SASL Login is only used as very last resort.
+ *
+ * Note: In case we do not support any of the server's advertised
+ * mechanism an exception is thrown.
+ *
+ * Note: LOGIN is deprecated, it is only used as very last resort.
+ *
+ * @param {string} [mechanism]
+ * the sasl mechanism which shall be used.
+ * If omitted or set to "default" the most preferred which is supported
+ * by client ans server is chosen.
+ *
+ * @returns {SieveAbstractSaslRequest}
+ * the sasl request which implements the most preferred compatible mechanism.
+ */
+ getSaslMechanism(mechanism) {
+
+ if (mechanism === undefined || mechanism === null)
+ mechanism = "default";
+
+ if (mechanism === "default")
+ mechanism = [...this.getSieve().capabilities.sasl];
+ else
+ mechanism = [mechanism];
+
+ // ... translate the SASL Mechanism into an SieveSaslLogin Object ...
+ while (mechanism.length > 0) {
+ // remove and test the first element...
+ switch (mechanism.shift().toUpperCase()) {
+ case "PLAIN":
+ return new SieveSaslPlainRequest();
+
+ case "SCRAM-SHA-1":
+ return new SieveSaslScramSha1Request();
+
+ case "SCRAM-SHA-256":
+ return new SieveSaslScramSha256Request();
+
+ case "SCRAM-SHA-512":
+ return new SieveSaslScramSha512Request();
+
+ case "EXTERNAL":
+ return new SieveSaslExternalRequest();
+
+ case "LOGIN":
+ // as suggested in the RFC, we use SASL LOGIN
+ // only as last resort...
+
+ // this means in case it is the only mechanism
+ // we have no options
+ if (!mechanism.length)
+ return new SieveSaslLoginRequest();
+
+ // otherwise be move it to the end of the
+ // mechanism list.
+ mechanism.push("LOGIN");
+ break;
+ }
+ }
+
+ throw new SieveClientException("No compatible SASL Mechanism (error.sasl)");
+ }
+
+ /**
+ * Gets an configuration parameter from the session's options.
+ *
+ * @param {string} name
+ * the option name.
+ * @param {Function} [fallback]
+ * the optional fallback value, if not undefined is returned.
+ *
+ * @returns {object}
+ * the option's value.
+ */
+ getOption(name, fallback) {
+ if (this.options[name] === null || this.options[name] === undefined)
+ return fallback;
+
+ return this.options[name];
+ }
+
+ /**
+ * Registers the callback listener for the given name.
+ * There can be at most one listener per name.
+ *
+ * To disable a listener just set the callback handler to null
+ * or undefined
+ *
+ * @param {string} name
+ * the callback event name.
+ * @param {Function} [callback]
+ * the callback function, if omitted the handler will be removed.
+ */
+ on(name, callback) {
+
+ if (name === "authenticate") {
+ this.listeners.onAuthenticate = callback;
+ return;
+ }
+
+ if (name === "authorize") {
+ this.listeners.onAuthorize = callback;
+ return;
+ }
+
+ if (name === "error") {
+ this.listeners.onError = callback;
+ return;
+ }
+
+ if (name === "disconnected") {
+ this.listeners.onDisconnected = callback;
+ return;
+ }
+
+ throw new SieveClientException(`Unknown callback handler ${name}`);
+ }
+
+ /**
+ * The authentication starts for secure connections after
+ * the tls handshake and for unencrypted connection directly
+ * after the initial server response.
+ *
+ * Please note after a successful tls handshake the server
+ * may update the SASL Mechanism. The server may force the user
+ * to use TLS by providing initially an empty list of SASL
+ * Mechanisms. After a successful tls handshake it then upgrades
+ * the SASL Mechanisms.
+ */
+ async authenticate() {
+
+ const mechanism = this.getOption("sasl", "default");
+
+ if (mechanism === "none")
+ return;
+
+ const request = this.getSaslMechanism(mechanism);
+
+ if (!this.listeners.onAuthenticate)
+ throw new SieveClientException("No Authentication handler registered");
+
+ const authentication = await this.listeners.onAuthenticate(request.hasPassword());
+
+ // SASL External has no password it relies completely on SSL...
+ if (request.hasPassword()) {
+ const password = authentication.password;
+
+ if (typeof (password) === "undefined" || password === null)
+ throw new SieveClientException("error.authentication");
+
+ request.setPassword(password);
+ }
+
+ request.setUsername(authentication.username);
+
+ // check if the authentication method supports proxy authorization...
+ if (request.isAuthorizable()) {
+
+ if (!this.listeners.onAuthorize)
+ throw new SieveClientException("No Authorization handler registered");
+
+ // ... if so retrieve the authorization identity
+ const authorization = await this.listeners.onAuthorize();
+
+ if (typeof (authorization) === "undefined" || authorization === null)
+ throw new SieveClientException("error.authorization");
+
+ if (authorization !== "")
+ request.setAuthorization(authorization);
+ }
+
+ await this.sendRequest(request);
+ }
+
+ /**
+ * Starts a new TLS connections.
+ * It throws an exception in case the tls handshake failed for some reason.
+ *
+ * Old Cyrus servers do not advertise their capabilities
+ * after the tls handshake. So we need some magic here.
+ * In addition the implicit request we send an explicit
+ * capability request.
+ *
+ * A bug free server will return with two capability responses
+ * while a buggy implementation returns only one.
+ *
+ * @param {object} [options]
+ * key value pairs with implementation specific options
+ */
+ async startTLS(options) {
+
+ if (!this.getSieve().isSecure())
+ return;
+
+ if (!this.getSieve().capabilities.tls)
+ throw new SieveClientException("Server does not support a secure connection.");
+
+ await this.sendRequest(new SieveStartTLSRequest());
+
+ await this.getSieve().startTLS(options);
+
+ // After a successfully tls handshake the server will advertise
+ // the capabilities, especially the SASL mechanism are likely to change.
+
+ // Old Cyrus implementation fail to advertise the capabilities.
+ // So that we actively request them as optional. This does not
+ // hurt bug free implementations and makes cyrus happy.
+ const capabilities = await this.sendRequest(new SieveCapabilitiesRequest());
+ this.sendRequest(new SieveInitRequest().makeOptional());
+
+ this.setCapabilities(capabilities);
+ }
+
+ /**
+ * Converts a callback driven request into async/await code.
+ *
+ * @param {SieveAbstractRequest} request
+ * a request or list of request to be executed. They will be queued
+ * at the same time and only the first request is used to resolve
+ * the promise.
+ *
+ * @param {Function} [init]
+ * an optional init function which will be called directly after
+ * the first request was queued
+ *
+ * @returns {SieveAbstractResponse}
+ * the response for the first request or an exception in case of an error.
+ */
+ async promisify(request, init) {
+
+ // eslint-disable-next-line no-async-promise-executor
+ return await new Promise(async (resolve, reject) => {
+
+ request.addResponseListener((response) => {
+ resolve(response);
+ });
+
+ request.addErrorListener((response) => {
+ reject(new SieveServerException(response));
+ });
+
+ request.addByeListener((response) => {
+
+ if (response.getResponseCode().equalsCode("REFERRAL")) {
+ reject(new SieveReferralException(response));
+ return;
+ }
+
+ reject(new SieveServerException(response));
+ });
+
+ request.addTimeoutListener((error) => {
+
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ reject(new SieveTimeOutException("Request was canceled or took too long"));
+ });
+
+
+ await (this.getSieve().addRequest(request));
+
+ if (init)
+ init();
+ });
+ }
+
+ /**
+ * Refers the connection to a different server.
+ *
+ * The server can send a referral request to the client at any time.
+ * The client's job is to disconnects and reconnect to the referred
+ * server's hostname and port.
+ *
+ * @param {string} host
+ * the new hostname
+ * @param {int} port
+ * the new hostname's port
+ *
+ * @returns {SieveAbstractSession}
+ * the response for the first request or an exception in case of an error.
+ */
+ async refer(host, port) {
+ this.getLogger().logSession(`SieveAbstractSession: Disconnecting old connection`);
+ await this.disconnect(true);
+
+ this.getLogger().logSession(`SieveAbstractSession: Connecting to referred Server: ${host}:${port}`);
+ return await this.connect(host, port);
+ }
+
+ /**
+ * Converts a callback driven request into async/await code.
+ *
+ * @param {SieveAbstractRequest|Array<SieveAbstractRequest>} request
+ * a request or list of request to be executed. They will be queued
+ * at the same time and only the first request is used to resolve
+ * the promise.
+ *
+ * @param {Function} [init]
+ * an optional init function which will be called directly after
+ * the first request was queued
+ *
+ * @returns {SieveAbstractResponse}
+ * the response for the first request or an exception in case of an error.
+ */
+ async sendRequest(request, init) {
+
+ try {
+ return await this.promisify(request, init);
+ }
+ catch (ex) {
+
+ if ((ex instanceof SieveReferralException) && (this.canRefer)) {
+ this.getLogger().logSession(`Referral received`);
+ this.getLogger().logSession(`Switching to ${ex.getHostname()}:${ex.getPort()}`);
+ await this.refer(ex.getHostname(), ex.getPort());
+ return await this.promisify(request, init);
+ }
+
+ this.getLogger().logSession(`Sending Request failed ${ex}`);
+ throw ex;
+ }
+ }
+
+ /**
+ * By default a request will follow automatically a referral.
+ * This means it will transparently disconnect from the old
+ * and reconnect to the new host.
+ *
+ * In case the following is disabled an exception will be thrown
+ * instead.
+ *
+ * You want to disable the implicit referral during stateful phases
+ * like initial connection.
+ *
+ * Imagine the following scenario. The connection is secured via
+ * startTLS and during authentication the referral is received.
+ * Then the automatic referral would then disconnect from the old
+ * host and connect to the new host. Then it would try to continue
+ * with the authentication step on a non secure connection. The startTLS
+ * step simply got lost.
+ */
+ disableReferrals() {
+ this.canRefer = false;
+ }
+
+ /**
+ * Enables automatic referral following.
+ *
+ * see the disableReferrals method for more details.
+ */
+ enableReferrals() {
+ this.canRefer = true;
+ }
+
+
+ /**
+ * An internal method creating a server connection.
+ *
+ * @param {string} hostname
+ * the sieve server's hostname.
+ * @param {string} [port]
+ * the sieve server's port. If omitted the default port 4190 is used.
+ * @returns {SieveSession}
+ * a self reference
+ */
+ async connect(hostname, port) {
+
+ if (typeof (hostname) === "undefined" || hostname === null)
+ throw new SieveClientException("No Hostname specified");
+
+ if (typeof (port) === "undefined" || port === null)
+ port = SIEVE_PORT;
+
+ this.createSieve();
+
+ this.getSieve().setIdleWait(this.getOption("keepAlive"));
+
+ // TODO do we really need this? Or do we need this only for keep alive?
+ this.getSieve().addListener(this);
+
+ // A referral during connection means we need to connect to the new
+ // server and start the whole handshake process again.
+ this.disableReferrals();
+
+ try {
+
+ const init = () => {
+ this.getSieve().connect(
+ hostname, port,
+ this.getOption("secure", true),
+ this,
+ null);
+ };
+
+ this.setCapabilities(
+ await this.sendRequest(new SieveInitRequest(), init));
+
+ await this.startTLS();
+
+ await this.authenticate();
+ } catch (ex) {
+
+ if (!(ex instanceof SieveReferralException))
+ throw ex;
+
+ // In case we got a referral we renegotiate the whole authentication
+ this.getLogger().logSession(`Referral received during authentication`);
+ this.getLogger().logSession(`Switching to ${ex.getHostname()}:${ex.getPort()}`);
+
+ await this.refer(ex.getHostname(), ex.getPort());
+ } finally {
+ this.enableReferrals(true);
+ }
+
+ return this;
+ }
+
+ /**
+ * Disconnects the current sieve session.
+ *
+ * The disconnect is by default graceful, which means the client send a
+ * logout command and waits for the server to terminate the connection.
+ *
+ * @param {boolean} [force]
+ * if set to true the disconnect will be forced and not graceful.
+ * This means the connection will be just disconnected.
+ * @returns {SieveSession}
+ * a self reference.
+ */
+ async disconnect(force) {
+
+ if (this.getSieve() === null)
+ return this;
+
+ this.getLogger().logSession(`SieveAbstractSession: Disconnecting Session ${force}`);
+
+ // We try to exit with a graceful Logout request...
+ if (!force && this.getSieve().isAlive()) {
+ try {
+ await this.logout();
+ } catch (ex) {
+ this.getLogger().logSession(`Graceful logout failed ${ex}`);
+ }
+ }
+
+ // ... in case it failed for we do it the hard way
+ if (this.getSieve()) {
+ this.getLogger().logSession(`SieveAbstractSession: Forcing Disconnect`);
+ await this.getSieve().disconnect();
+ this.sieve = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Lists all scripts available on the server.
+ * @returns {string}
+ * the current scripts.
+ */
+ async listScripts() {
+ return (await this.sendRequest(new SieveListScriptsRequest())).getScripts();
+ }
+
+ /**
+ * Renames a script.
+ *
+ * It prefers the new rename command. In case it is not supported it will
+ * use a get, put and delete sequence to emulate the rename command.
+ *
+ * So the result will be the very same, but there is one slight difference.
+ * Instead of throwing an error it will overwrite existing script with the
+ * same name silently.
+ *
+ * @param {string} oldName
+ * the old name
+ * @param {string} newName
+ * the new name
+ */
+ async renameScript(oldName, newName) {
+
+ if (this.getSieve().getCompatibility().renamescript) {
+ await this.sendRequest(new SieveRenameScriptRequest(oldName, newName));
+ return;
+ }
+
+ // Get the scripts activation state and check if the script name clashes
+ const scripts = await this.listScripts();
+ let active = null;
+
+ for (const item of scripts) {
+ if (item.script === newName)
+ throw new SieveClientException("Name already exists");
+
+ if (item.script === oldName)
+ active = item.active;
+ }
+
+ if (active === null)
+ throw new SieveClientException(`Unknown Script ${oldName}`);
+
+ // Get the script'S content and save is as a new file
+ await this.putScript(newName,
+ await this.getScript(oldName));
+
+ // Activate the new script
+ if (this.active === true)
+ await this.activateScript(newName);
+
+ // Finally delete the old script
+ await this.deleteScript(oldName);
+ }
+
+ /**
+ * Saves the given script. In case the script exists
+ * it will be silently overwritten.
+ *
+ * @param {string} name
+ * the script's name
+ * @param {string} script
+ * the script which should be saved.
+ *
+ */
+ async putScript(name, script) {
+ await this.sendRequest(new SievePutScriptRequest(name, script));
+ }
+
+ /**
+ * Deletes the given script name.
+ *
+ * @param {string} name
+ * the script which should be deleted.
+ *
+ */
+ async deleteScript(name) {
+ await this.sendRequest(new SieveDeleteScriptRequest(name));
+ }
+
+ /**
+ * Gets the script with the given name.
+ * In case the script does not exists the server will throw an error.
+ *
+ * @param {string} name
+ * the scripts unique name
+ * @returns {Promise<string>}
+ * the scripts content as string
+ */
+ async getScript(name) {
+ return (await this.sendRequest(new SieveGetScriptRequest(name))).getScriptBody();
+ }
+
+ /**
+ * Activates the specified script and deactivated the current script.
+ * Sieve supports at most one active script.
+ *
+ * To deactivate all scripts just omit the script parameter
+ *
+ * @param {string} [script]
+ * the script which should be activated.
+ * If omitted all script will be deactivated.
+ *
+ */
+ async activateScript(script) {
+ await this.sendRequest(new SieveSetActiveRequest(script));
+ }
+
+ /**
+ * Checks the script for syntax errors.
+ *
+ * It uses the checkscript command if present otherwise
+ * it emulates the checkscript by pushing a temporary script
+ * to the server.
+ *
+ * Throws an exception in case the script is not valid.
+ *
+ * @param {string} script
+ * the script which should be checked.
+ *
+ */
+ async checkScript(script) {
+
+ // We do not need to check an empty script...
+ if (!script.length)
+ return;
+
+ // Use the CHECKSCRIPT command when possible, otherwise we need to ...
+ // ... fallback to the PUTSCRIPT/DELETESCRIPT Hack...
+ if (this.getSieve().getCompatibility().checkscript) {
+ await this.sendRequest(new SieveCheckScriptRequest(script));
+ return;
+ }
+
+ // ... we have to use the PUTSCRIPT/DELETESCRIPT Hack.
+
+ // First we use PUTSCRIPT to store a temporary script on the server...
+ // ... incase the command fails, it is most likely due to an syntax error...
+ // ... if it succeeds the script is syntactically correct!
+ await this.putScript("TMP_FILE_DELETE_ME", script);
+
+ // then delete the temporary script. We need to do this only when
+ // put script succeeded and when it was stored.
+ await this.deleteScript("TMP_FILE_DELETE_ME");
+ }
+
+ /**
+ * Sends a noop or keep alive response.
+ * It is a request without side effect and without any
+ * payload. It is typically used to test if the server
+ * is available and to prevent closing the connection to the server.
+ *
+ * Servers do not have to support noop.
+ * The implementation will use a capability request as
+ * fallback as described in the rfc.
+ *
+ */
+ async noop() {
+
+ // In case th server does not support noop we fallback
+ // to a capability request as suggested in the rfc.
+ if (!this.getSieve().getCompatibility().noop) {
+ await this.capabilities();
+ }
+
+ await this.sendRequest(new SieveNoopRequest());
+ }
+
+ /**
+ * Sends a capability request.
+ * In case of an error an exception will be thrown.
+ *
+ * @returns {object}
+ * an object with the capabilities.
+ */
+ async capabilities() {
+ return (await this.sendRequest(new SieveCapabilitiesRequest())).getDetails();
+ }
+
+ /**
+ * Used to gracefully disconnect from the server.
+ * It sends a logout request, then the server should
+ * hangup the connection.
+ */
+ async logout() {
+ await this.sendRequest(new SieveLogoutRequest());
+ }
+
+}
+
+export { SieveAbstractSession };
diff --git a/src/common/libManageSieve/SieveAbstractTimer.mjs b/src/common/libManageSieve/SieveAbstractTimer.mjs
new file mode 100644
index 00000000..fd14645c
--- /dev/null
+++ b/src/common/libManageSieve/SieveAbstractTimer.mjs
@@ -0,0 +1,38 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/**
+ * Implements an abstract interface for a simple single shot timer.
+ */
+class SieveAbstractTimer {
+
+ /**
+ * Starts the timer. In case the timer is already running it will be restarted
+ * @abstract
+ *
+ * @param {Function} callback
+ * the callback to be invoked when the timer fires.
+ * @param {int} ms
+ * the ms after which the timer should fire
+ */
+ start(callback, ms) {
+ throw new Error(`Override SieveAbstractTimer::start(${callback},${ms})`);
+ }
+
+ /**
+ * Stops the timer. It will fail silently in case the timer is already stopped.
+ */
+ cancel() {
+ throw new Error(`Override SieveAbstractTimer::cancel()`);
+ }
+}
+
+export { SieveAbstractTimer };
diff --git a/src/common/libManageSieve/SieveExceptions.js b/src/common/libManageSieve/SieveExceptions.js
deleted file mode 100644
index 2aeecc8a..00000000
--- a/src/common/libManageSieve/SieveExceptions.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /**
- * A generic base class for Sieve Exceptions.
- */
- class SieveException extends Error {
- }
-
- /**
- * An error on the client side.
- */
- class SieveClientException extends SieveException {
- }
-
- /**
- * The request took too long or it was canceled.
- * This is a clients side exception.
- */
- class SieveTimeOutException extends SieveClientException {
-
- /**
- * Creates a new timeout exception.
- *
- * @param {Error} [error]
- * the optional root error which caused this exception.
- */
- constructor(error) {
- super("Request took too long or was canceled.");
- this.error = error;
- }
- }
-
- /**
- * The Certificate validation failed.
- */
- class SieveCertValidationException extends SieveClientException {
- /**
- * Creates a Certificate Validation Exception.
- *
- * @param {object} securityInfo
- * the security info object with details on the certificate.
- */
- constructor(securityInfo) {
- super("Error while validating Certificate");
-
- this.securityInfo = securityInfo;
- }
-
- /**
- * The security Info object with detailed information
- * on the certificate which caused this error.
- *
- * @returns {object}
- * the security info.
- */
- getSecurityInfo() {
- return this.securityInfo;
- }
- }
-
- /**
- * The server signaled an error.
- *
- * The most reliable way to recover from such an error is to
- * disconnect and then reconnect to the server.
- */
- class SieveServerException extends SieveException {
-
- /**
- * Creates a server side exception
- *
- * @param {SieveSimpleResponse} response
- * the servers response which indicated the error.
- */
- constructor(response) {
- super(response.getMessage());
- this.response = response;
- }
-
- /**
- * Returns the server's response it typically contains the cause
- * why the request failed.
- *
- * @returns {SieveSimpleResponse}
- * the server response objet
- */
- getResponse() {
- return this.response;
- }
- }
-
- /**
- * The server terminated the connection and referred to a new host
- */
- class SieveReferralException extends SieveServerException {
-
- /**
- * The new remote hostname to which the server referred the connection.
- * @returns {string}
- * the hostname
- */
- getHostname() {
- return this.getResponse().getResponseCode().getHostname();
- }
-
- /**
- * The new remote port to which the server referred the connection.
- * @returns {string}
- * the port
- */
- getPort() {
- return this.getResponse().getResponseCode().getPort();
- }
- }
-
- exports.SieveClientException = SieveClientException;
- exports.SieveReferralException = SieveReferralException;
- exports.SieveServerException = SieveServerException;
- exports.SieveTimeOutException = SieveTimeOutException;
- exports.SieveCertValidationException = SieveCertValidationException;
-
- exports.SieveException = SieveException;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveExceptions.mjs b/src/common/libManageSieve/SieveExceptions.mjs
new file mode 100644
index 00000000..cb59a73f
--- /dev/null
+++ b/src/common/libManageSieve/SieveExceptions.mjs
@@ -0,0 +1,133 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+/**
+ * A generic base class for Sieve Exceptions.
+ */
+class SieveException extends Error {
+}
+
+/**
+ * An error on the client side.
+ */
+class SieveClientException extends SieveException {
+}
+
+/**
+ * The request took too long or it was canceled.
+ * This is a clients side exception.
+ */
+class SieveTimeOutException extends SieveClientException {
+
+ /**
+ * Creates a new timeout exception.
+ *
+ * @param {Error} [error]
+ * the optional root error which caused this exception.
+ */
+ constructor(error) {
+ super("Request took too long or was canceled.");
+ this.error = error;
+ }
+}
+
+/**
+ * The Certificate validation failed.
+ */
+class SieveCertValidationException extends SieveClientException {
+ /**
+ * Creates a Certificate Validation Exception.
+ *
+ * @param {object} securityInfo
+ * the security info object with details on the certificate.
+ */
+ constructor(securityInfo) {
+ super("Error while validating Certificate");
+
+ this.securityInfo = securityInfo;
+ }
+
+ /**
+ * The security Info object with detailed information
+ * on the certificate which caused this error.
+ *
+ * @returns {object}
+ * the security info.
+ */
+ getSecurityInfo() {
+ return this.securityInfo;
+ }
+}
+
+/**
+ * The server signaled an error.
+ *
+ * The most reliable way to recover from such an error is to
+ * disconnect and then reconnect to the server.
+ */
+class SieveServerException extends SieveException {
+
+ /**
+ * Creates a server side exception
+ *
+ * @param {SieveSimpleResponse} response
+ * the servers response which indicated the error.
+ */
+ constructor(response) {
+ super(response.getMessage());
+ this.response = response;
+ }
+
+ /**
+ * Returns the server's response it typically contains the cause
+ * why the request failed.
+ *
+ * @returns {SieveSimpleResponse}
+ * the server response objet
+ */
+ getResponse() {
+ return this.response;
+ }
+}
+
+/**
+ * The server terminated the connection and referred to a new host
+ */
+class SieveReferralException extends SieveServerException {
+
+ /**
+ * The new remote hostname to which the server referred the connection.
+ * @returns {string}
+ * the hostname
+ */
+ getHostname() {
+ return this.getResponse().getResponseCode().getHostname();
+ }
+
+ /**
+ * The new remote port to which the server referred the connection.
+ * @returns {string}
+ * the port
+ */
+ getPort() {
+ return this.getResponse().getResponseCode().getPort();
+ }
+}
+
+export {
+ SieveClientException,
+ SieveReferralException,
+ SieveServerException,
+ SieveTimeOutException,
+ SieveCertValidationException,
+ SieveException
+};
diff --git a/src/common/libManageSieve/SieveLogger.mjs b/src/common/libManageSieve/SieveLogger.mjs
new file mode 100644
index 00000000..49d46db4
--- /dev/null
+++ b/src/common/libManageSieve/SieveLogger.mjs
@@ -0,0 +1,288 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const TWO_CHARS = 2;
+const THREE_CHARS = 3;
+
+const BASE_10 = 10;
+
+// eslint-disable-next-line no-magic-numbers
+const LOG_REQUEST = (1 << 0);
+// eslint-disable-next-line no-magic-numbers
+const LOG_RESPONSE = (1 << 1);
+// eslint-disable-next-line no-magic-numbers
+const LOG_STATE = (1 << 2);
+// eslint-disable-next-line no-magic-numbers
+const LOG_STREAM = (1 << 3);
+// eslint-disable-next-line no-magic-numbers
+const LOG_SESSION_INFO = (1 << 4);
+// eslint-disable-next-line no-magic-numbers
+const LOG_TRACE = (1 << 5);
+
+const DEFAULT_LEVEL = 0;
+
+/**
+ * Implements a common and platform independent logging interface.
+ * The log level is interpreted as a bit filed with turns logging
+ * for the specified scope on and of.
+ *
+ * The level is concerning scopes and does not differentiate between
+ * warning, error and info.
+ */
+class SieveLogger {
+
+ /**
+ * Creates a new instance
+ * @param {string} [prefix]
+ * an optional prefix for this logger.
+ * @param {int} [level]
+ * the logger level
+ *
+ */
+ constructor(prefix, level) {
+ if (typeof (prefix) === "undefined")
+ prefix = "";
+
+ if (typeof (level) === "undefined")
+ level = DEFAULT_LEVEL;
+
+ this._level = level;
+ this._prefix = prefix;
+ }
+
+ /**
+ * Logs a request related information
+ * @param {string} message
+ * the request status to log.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logRequest(message) {
+ return this.log(message, LOG_REQUEST);
+ }
+
+ /**
+ * Logs response related information
+ * @param {byte[]} data
+ * the response status to log
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logResponse(data) {
+ const byteArray = new Uint8Array(data.slice(0, data.length));
+
+ return this.log(
+ "Server -> Client\n" + (new TextDecoder("UTF-8")).decode(byteArray),
+ LOG_RESPONSE);
+ }
+
+ /**
+ * Logs state machine information.
+ * @param {string} message
+ * the stat information to log.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logState(message) {
+ return this.log(message, LOG_STATE);
+ }
+
+ /**
+ * Dumps raw stream data to the log
+ * @param {string} message
+ * the stream information to log.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logStream(message) {
+ return this.log(message, LOG_STREAM);
+ }
+
+ /**
+ * Logs information about the session.
+ * @param {string} message
+ * the message to log.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logSession(message) {
+ return this.log(message, LOG_SESSION_INFO);
+ }
+
+ /**
+ * Logs the given message to the browser console.
+ *
+ * @param {string} message
+ * The message which should be logged
+ * @param {int} [level]
+ * the log level. If omitted the message will be always logged.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ log(message, level) {
+ if (!this.isLoggable(level))
+ return this;
+
+ if (this.isLoggable(LOG_TRACE)) {
+ // eslint-disable-next-line no-console
+ console.trace(`[${this.getTimestamp()} ${this.prefix()}] ${message}`);
+ return this;
+ }
+
+ // eslint-disable-next-line no-console
+ console.log(`[${this.getTimestamp()} ${this.prefix()}] ${message}`);
+ return this;
+ }
+
+ /**
+ * Checks if state information should be logged.
+ *
+ * @returns {boolean}
+ * true in case state information should be logged otherwise false.
+ */
+ isLevelState() {
+ return this.isLoggable(LOG_STATE);
+ }
+
+ /**
+ * Checks if session information should be logged.
+ *
+ * @returns {boolean}
+ * true in case session information should be logged otherwise false.
+ */
+ isLevelSession() {
+ return this.isLoggable(LOG_SESSION_INFO);
+ }
+
+ /**
+ * Tests if the log level should log.
+ *
+ * @param {int} level
+ * the level which should be checked.
+ * @returns {boolean}
+ * true in case the log level is activated otherwise false
+ */
+ isLoggable(level) {
+ if (typeof (level) === "undefined")
+ return true;
+
+ return !!(this.level() & level);
+ }
+
+ /**
+ * Checks if stream data should be logged.
+ *
+ * @returns {boolean}
+ * true in case the stream data should be logged otherwise false
+ */
+ isLevelStream() {
+ return this.isLoggable(LOG_STREAM);
+ }
+
+ /**
+ * Checks if request data should be logged.
+ *
+ * @returns {boolean}
+ * true in case the request data should be logged otherwise false
+ */
+ isLevelRequest() {
+ return this.isLoggable(LOG_REQUEST);
+ }
+
+ /**
+ * Checks if response data should be logged.
+ *
+ * @returns {boolean}
+ * true in case the response data should be logged otherwise false
+ */
+ isLevelResponse() {
+ return this.isLoggable(LOG_RESPONSE);
+ }
+
+ /**
+ * Gets and sets the log level to the given bit mask.
+ * Note that the log level is a bit mask, every bit in the
+ * bit mask corresponds to a special logger.
+ *
+ * In order to activate or deactivate a logger you need to
+ * get the level toggle the desired bits and set the new level.
+ *
+ * @param {int} [level]
+ * the desired log level as bit mask.
+ * @returns {int}
+ * the current log level
+ */
+ level(level) {
+ if (typeof (level) !== "undefined")
+ this._level = level;
+
+ return this._level;
+ }
+
+
+ /**
+ * Pads the given string with leading zeros
+ * @private
+ *
+ * @param {string} n
+ * the string which should be padded
+ * @param {int} m
+ * the maximum padding.
+ *
+ * @returns {string}
+ * the padded string
+ */
+ _pad(n, m) {
+
+ let str = n;
+
+ for (let i = 0; i < m; i++)
+ if (n < Math.pow(BASE_10, i))
+ str = '0' + str;
+
+ return str;
+ }
+
+ /**
+ * Gets the current time in iso format (hh:mm:ss.SSS)
+ *
+ * @returns {string}
+ * the current timestamp as string.
+ */
+ getTimestamp() {
+
+ const date = new Date();
+ return this._pad(date.getHours(), TWO_CHARS)
+ + ":" + this._pad(date.getMinutes(), TWO_CHARS)
+ + ":" + this._pad(date.getSeconds(), TWO_CHARS)
+ + "." + this._pad(date.getMilliseconds(), THREE_CHARS);
+ }
+
+
+ /**
+ * Gets and sets the loggers prefix. The prefix is appended to
+ * every logger message
+ *
+ * @param {string} [prefix]
+ * the new prefix.
+ * @returns {string}
+ * the current prefix.
+ */
+ prefix(prefix) {
+
+ if (typeof (prefix) !== "undefined")
+ this._prefix = prefix;
+
+ return this._prefix;
+ }
+}
+
+export { SieveLogger };
diff --git a/src/common/libManageSieve/SieveRequest.js b/src/common/libManageSieve/SieveRequest.js
deleted file mode 100755
index 47f45898..00000000
--- a/src/common/libManageSieve/SieveRequest.js
+++ /dev/null
@@ -1,1446 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-/*
- The communication in this library is asynchronous! After sending a request,
- you will be notified by a listener, as soon as a response arrives.
-
- If a request caused an error or timeout, its error listener will be called
- to resolve the issue. If a server rejects a request, the onError() function
- of the error listener will be invoked. In case of a timeout situation, the
- onTimeout() function is called.
-
- If a request succeeded, the corresponding response listener of the request
- will be notified.
-
- The addResponse(), getNextRequest(), hasNextRequest(), cancel() Methods are
- used by the Sieve object, and should not be invoked manually.
-
- When the sieve object receives a response, it is passed to the addResponse()
- Method of the requesting object. A timeout is signaled by passing invoking
- the cancel() Method.
-
-*/
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const {
- SieveSimpleResponse,
- SieveCapabilitiesResponse,
- SieveListScriptsResponse,
- SieveSaslLoginResponse,
- SieveSaslCramMd5Response,
- SieveGetScriptResponse,
- SieveSaslScramShaResponse
- } = require("./SieveResponse.js");
-
- const { SieveCrypto } = require("./SieveCrypto.js");
-
- const STATE_CRAM_MD5_INITIALIZED = 0;
- const STATE_CRAM_MD5_CHALLENGED = 1;
- const STATE_CRAM_MD5_COMPLETED = 4;
-
- const STATE_LOGIN_INITIALIZED = 0;
- const STATE_LOGIN_USERNAME = 1;
- const STATE_LOGIN_PASSWORD = 2;
- const STATE_LOGIN_COMPLETED = 4;
-
- const RESPONSE_OK = 0;
- const RESPONSE_BYE = 1;
- const RESPONSE_NO = 2;
-
- const SEED = 1234567890;
-
- /**
- * An abstract class, it is the prototype for any requests
- */
- class SieveAbstractRequest {
-
- /**
- * Creates a new Instance.
- */
- constructor() {
- this.errorListener = null;
- this.timeoutListener = null;
- this.byeListener = null;
-
- this.responseListener = null;
- }
-
- /**
- * The error listener is called whenever the server returns an error state
- * @param {Function} listener
- * the listener which should be invoked
- * @returns {SieveAbstractRequest}
- * a self reference
- */
- addErrorListener(listener) {
-
- if (typeof listener !== 'function') {
- throw new Error("Error listener is not a function");
- }
-
- this.errorListener = listener;
- return this;
- }
-
- /**
- * The timeout listener is calls whenever sending a request fails for some
- * reason. This could be because of a timeout or because the server terminated
- * the connection or something else happened.
- *
- * The listener does not necessarily wait for a timeout event. E.g. in case the
- * connection is lost it will fire immediately.
- *
- * @param {Function} listener
- * the listener which should be invoked
- * @returns {SieveAbstractRequest}
- * a self reference
- */
- addTimeoutListener(listener) {
-
- // TODO should be renamed to error listener as it is more than just a timeout handler...
- if (typeof listener !== 'function') {
- throw new Error("Timeout listener is not a function");
- }
-
- this.timeoutListener = listener;
- return this;
- }
-
- /**
- * The bye listener is called whenever the server terminated the connection
- * gracefully.
- *
- * @param {Function} listener
- * the listener which should be invoked
- * @returns {SieveAbstractRequest}
- * a self reference
- */
- addByeListener(listener) {
-
- if (typeof listener !== 'function') {
- throw new Error("Bye listener is not a function");
- }
-
- this.byeListener = listener;
- return this;
- }
-
- /**
- * Add a response listener to this request. The response listener will
- * be triggered in case a successful server response was received
- *
- * @param {Function} listener
- * the listener which should be invoked when the server response
- * @returns {SieveAbstractRequest}
- * a self reference
- */
- addResponseListener(listener) {
-
- if (typeof listener !== 'function') {
- throw new Error(`Listener is not a function ${listener}`);
- }
-
- this.responseListener = listener;
- return this;
- }
-
- /**
- * In general Sieve uses an unsolicited communication.
- * The client sends messages to server and the server responds
- * to those.
- *
- * But there are some exceptions to this rule, e.g. the
- * init request upon connecting or after tls completed.
- * Both are send by the server to the client.
- *
- * @returns {boolean}
- * true in case the request is unsolicited. Which means
- * the client sends a request and the server responds
- * to that.
- * false in case the request is solicited. Which means
- * it was send by the server without an explicit
- * request from the client.
- */
- isUnsolicited() {
- return true;
- }
-
- /**
- * Most request use a single request response pair. But especially the SASL
- * script use normally more than one round trip due to security reasons.
- *
- * This flags indicates if the Request's internal state engine was completed.
- *
- * @returns {boolean}
- * false in case the request has been completed. True in case it is still
- * running and waits for sending the next request.
- */
- hasNextRequest() {
- return false;
- }
-
- /**
- * Returns the next request as a string. It uses the given
- * Request builder to assemble the string.
- *
- * @param {SieveAbstractRequestBuilder} builder
- * a reference to a stateless request builder which can be used
- * to form the request string.
- * @returns {string}
- * the data which should be send to the server
- *
- * @abstract
- */
- getNextRequest(builder) {
- throw new Error(`Abstract Method implement me ${builder}`);
- }
-
- /**
- * Triggers a timeout on the error listener.
- * This should never be invoked directly by any other object than the sieve connection.
- *
- * @param {Error} [reason]
- * the optional reason why the request was canceled.
- */
- cancel(reason) {
- if (this.timeoutListener)
- this.timeoutListener(reason);
- }
-
- /**
- * Trigger the error listener to signal an error condition.
- *
- * This should never be invoked directly by any other object than the
- * sieve connection or this class.
- *
- * @param {SieveSimpleResponse} response
- * the response which should be handled by this request.
- */
- onNo(response) {
- if (this.errorListener)
- this.errorListener(response);
- }
-
- /**
- * Trigger the bye listener.
- *
- * This should never be invoked directly by any other object than the
- * sieve connection or this class.
- *
- * @param {SieveSimpleResponse} response
- * the response which should be handled by this request.
- */
- onBye(response) {
- if ((response.getResponse() === RESPONSE_BYE) && (this.byeListener))
- this.byeListener(response);
- }
-
- /**
- * Triggers the ok listener. This is normally when the request completed
- * successfully.
- *
- * This should never be invoked directly by any other object than the
- * sieve connection or this class.
- *
- * @param {SieveSimpleResponse} response
- * the response which should be handled by this request.
- *
- * @abstract
- */
- onOk(response) {
- throw new Error(`Abstract Method override me ${response}`);
- }
-
- /**
- * An abstract helper, which calls the default message handlers
- * for the given response
- *
- * @param {SieveSimpleResponse} response
- * the response which should be handled by this request.
- * @returns {SieveAbstractRequest}
- * a self reference
- */
- addResponse(response) {
- if (response.getResponse() === RESPONSE_OK)
- this.onOk(response);
- else if (response.getResponse() === RESPONSE_BYE)
- this.onBye(response);
- else if (response.getResponse() === RESPONSE_NO)
- this.onNo(response);
- else
- throw new Error("Invalid Response Code");
-
- return this;
- }
- }
-
- /**
- * An abstract calls derived from AbstractRequest. It is the foundation for
- * any requests implementing a SASL compatible authentication.
- */
- class SieveAbstractSaslRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- constructor() {
- super();
-
- this._username = "";
- this._password = "";
- this._authorization = "";
- }
-
- /**
- * Sets the sasl mechanisms username. Not all SASL Mechanisms require an username.
- *
- * @param {string} username
- * the username
- * @returns {SieveAbstractSaslRequest}
- * a self reference
- **/
- setUsername(username) {
- this._username = username;
- return this;
- }
-
- /**
- * Most SASL mechanisms need a password or secret to authenticate.
- * But there are also mechanisms like SASL EXTERNAL which do not need a password.
- * They use different methods to transfer the credentials.
- *
- * @returns {boolean}
- * indicates if this SASL Mechanism needs a password
- */
- hasPassword() {
- return true;
- }
-
- /**
- * Sets the sasl request's password.
- *
- * @param {string} password
- * the password which shall be used for the authentication.
- * @returns {SieveAbstractSaslRequest}
- * a self reference
- **/
- setPassword(password) {
- this._password = password;
- return this;
- }
-
- /**
- * Checks if this mechanism supports authorization. Keep in mind
- * authorization is rarely used and only very few mechanisms
- * support it.
- *
- * With authorization you use your credentials to login as a different user.
- * Which means you first authenticate with your username and then do the
- * authorization which switch the user. Typically admins and superusers have
- * such super powers.
- *
- * @returns {boolean}
- * true in case the request supports authorization otherwise false.
- */
- isAuthorizable() {
- // Sub classes shall overwrite this with true in case authorization is supported
- return false;
- }
-
- /**
- * Sets the username which should be authorized.
- * In case authorization is not supported it will be silently ignored.
- *
- * @param {string} authorization
- * the username used for authorization
- * @returns {SieveAbstractRequest}
- * a self reference
- **/
- setAuthorization(authorization) {
- if (this.isAuthorizable())
- this._authorization = authorization;
-
- return this;
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
- }
-
- /**
- * Loads a script from the server and returns the content.
- * In case the script is non existent an error will be triggered.
- */
- class SieveGetScriptRequest extends SieveAbstractRequest {
-
- /**
- * Create a new request which load a sieve script from the
- * remote server.
- *
- * @param {string} script
- * the script which should be retrieved
- */
- constructor(script) {
- super();
- this.script = script;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("GETSCRIPT")
- .addQuotedString(this.script);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveGetScriptResponse(this.script)).parse(parser));
- }
- }
-
- /**
- * Stores the given script on the server.
- * The script is validated by the server and will be rejected with a NO
- * in case the validation fails.
- *
- * Please not it will overwrite silently any existing script with the same name.
- */
- class SievePutScriptRequest extends SieveAbstractRequest {
-
- /**
- * Creates a request which stores a sieve script on the server.
- *
- * @param {string} script
- * the script's name
- * @param {string} body
- * the sieve script which should be stored on the server.
- */
- constructor(script, body) {
- super();
- this.script = script;
-
- // cleanup line breaks...
-
- // eslint-disable-next-line no-control-regex
- this.body = body.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("PUTSCRIPT")
- .addQuotedString(this.script)
- .addMultiLineString(this.body);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * The CheckScriptRequest validates the Syntax of a Sieve script. The script
- * is not stored on the server.
- *
- * If the script fails this test, the server replies with a NO response. The
- * response contains one or more CRLF separated error messages.
- *
- * An OK response can contain Syntax Warnings.
- *
- * C: CheckScript {31+}
- * C: #comment
- * C: InvalidSieveCommand
- * C:
- * S: NO "line 2: Syntax error"
- *
- */
- class SieveCheckScriptRequest extends SieveAbstractRequest {
-
- /**
- * Creates a new request which checks if the scripts' syntax is valid.
- * @param {string} body
- * the script which should be check for syntactical validity
- */
- constructor(body) {
- super();
- // Strings in JavaScript should use the encoding of the xul document and...
- // ... sockets use binary strings. That means for us we have to convert...
- // ... the JavaScript string into a UTF8 String.
-
- // Further more Sieve expects line breaks to be \r\n. Mozilla uses \n ...
- // ... according to the documentation. But for some unknown reason a ...
- // ... string sometimes contains mixed line breaks. Thus we convert ...
- // ... any \r\n, \r and \n to \r\n.
-
- // eslint-disable-next-line no-control-regex
- this.body = body.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder.addLiteral("CHECKSCRIPT")
- .addMultiLineString(this.body);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * This class encapsulates a Sieve SETACTIVE request.
- * <p>
- * Either none or one server scripts can be active, this means you can't have
- * more than one active scripts
- * <p>
- * You activate a Script by calling SETACTIVE and the script name. At activation
- * the previous active Script will become inactive.
- */
- class SieveSetActiveRequest extends SieveAbstractRequest {
-
- /**
- * Creates a new request which activates the given script
- * @param {string} script - The script name which should be activated. Passing
- * an empty string deactivates the active script.
- */
- constructor(script) {
- super();
-
- this.script = "";
-
- if ((typeof (script) !== 'undefined') && (script !== null))
- this.script = script;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("SETACTIVE")
- .addQuotedString(this.script);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * The capability request asks the server to transmit his
- * capabilities like the supported sieve extensions, as
- * well as a list with all possible SASL authentication mechanisms.
- *
- * The returned capabilities depends upon the context. A server may
- * refuse to advertise SASL Mechanisms while using an insecure
- * connection. As soon as you started a secure connection it may offer
- * additional Mechanisms.
- *
- * So you should always refresh the server's capabilities.
- */
- class SieveCapabilitiesRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("CAPABILITY");
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveCapabilitiesResponse()).parse(parser));
- }
- }
-
- /**
- * The delete script command is used to remove a script from the server.
- * Deleting an non existing script will result in an error. Also deleting
- * the active script will result in an error.
- */
- class SieveDeleteScriptRequest extends SieveAbstractRequest {
-
- /**
- * Creates a request which deletes the given script.
- * @param {string} script
- * the scripts name which should be deleted
- */
- constructor(script) {
- super();
- this.script = script;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("DELETESCRIPT")
- .addQuotedString(this.script);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * The NOOP request does nothing, it is used for protocol resynchronization or
- * to reset any inactivity auto-logout timer on the server.
- *
- * The response to the NOOP command is always OK.
- */
- class SieveNoopRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("NOOP");
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
-
- /**
- * This command is used to rename a Sieve script. The Server will reply with
- * a NO response if the old script does not exist, or a script with the new
- * name already exists.
- *
- * Renaming the active script is allowed, the server ensures that the
- * renamed script remains active.
- */
- class SieveRenameScriptRequest extends SieveAbstractRequest {
-
- /**
- * Creates a rename script request.
- *
- * @param {string} oldScript Name of the script, which should be renamed
- * @param {string} newScript New name of the script
- **/
- constructor(oldScript, newScript) {
- super();
- this.oldScript = oldScript;
- this.newScript = newScript;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("RENAMESCRIPT")
- .addQuotedString(this.oldScript)
- .addQuotedString(this.newScript);
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * This command is used to list all sieve script of the current user.
- * In case there are no scripts the server responds with an empty list.
- */
- class SieveListScriptsRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("LISTSCRIPTS");
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- new SieveListScriptsResponse().parse(parser));
- }
- }
-
- /**
- * Initializes switching to tls via start tls
- */
- class SieveStartTLSRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("STARTTLS");
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * A logout request signals the server that the client wishes to terminate
- * the current session.
- * <pre>
- * Client > LOGOUT
- * Server < OK "Logout Complete"
- * [ connection terminated ]
- * </pre>
- * <p>
- */
- class SieveLogoutRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder.addLiteral("LOGOUT");
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- onBye(response) {
- // As issued a logout request thus onBye response is perfectly fine...
- // ... and equivalent to an ok in this case.
- this.onOk(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * A ManageSieve server automatically post his capabilities as soon as the
- * connection is established or a secure channel is successfully started
- * (STARTTLS command). In order to capture this information a dummy request
- * is used. It does not send a real request, but it parses the initial response
- * of the sieve server. Therefore it is important to add the request before the
- * connection is established. Otherwise the message queue will be jammed.
- *
- * Server < "IMPLEMENTATION" "Cyrus timsieved v2.1.18-IPv6-Debian-2.1.18-1+sarge2"
- * < "SASL" "PLAIN"
- * < "SIEVE" "fileinto reject envelope vacation imapflags notify subaddress relational regex"
- * < "STARTTLS"
- * < OK
- *
- */
- class SieveInitRequest extends SieveAbstractRequest {
-
- /**
- * @inheritdoc
- */
- onOk(response) {
- if (this.responseListener)
- this.responseListener(response);
- }
-
- /**
- * @inheritdoc
- */
- isUnsolicited() {
- return false;
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveCapabilitiesResponse()).parse(parser));
- }
- }
-
- /**
- * Implements the SASL Plain authentication method.
- *
- * The password is only base64 encoded not encrypted. Therefore it can be
- * read or sniffed easily. A secure connection will solve this issue. Always
- * start a tls session before using this request.
- *
- * Client > AUTHENTICATE "PLAIN" AHRlc3QAc2VjcmV0 | AUTHENTICATE "PLAIN" [UTF8NULL]test[UTF8NULL]secret
- * Server < OK | OK
- */
- class SieveSaslPlainRequest extends SieveAbstractSaslRequest {
-
- /**
- * The sasl plain request always support proxy authentication.
- *
- * @returns {boolean}
- * always true
- */
- isAuthorizable() {
- return true;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("AUTHENTICATE")
- .addQuotedString("PLAIN")
- .addQuotedBase64(`${this._authorization}\0${this._username}\0${this._password}`);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse(
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- /**
- * This request implements the SALS Login authentication method. It is deprecated
- * and has been superseded by SASL Plain method. SASL Login uses a question and
- * answer style communication. The server will request first the username and
- * then the password.
- * <p>
- * Please note, that the password is not encrypted, it is only base64 encoded.
- * Therefore it can be read or sniffed easily. A secure connection will solve
- * this issue. So send whenever possible, a SieveStartTLSRequest before calling
- * this request.
- * <p>
- * Client > AUTHENTICATE "LOGIN" | AUTHENTICATE "LOGIN"
- * Server < {12} | {12}
- * < VXNlcm5hbWU6 | Username:
- * Client > {8+} | {8+}
- * > Z2Vlaw== | geek
- * Server < {12} | {12}
- * < UGFzc3dvcmQ6 | Password:
- * Client > {12+} | {12+}
- * > dGgzZzMzazE= | th3g33k1
- * Server < OK | OK
- *
- * @deprecated
- */
- class SieveSaslLoginRequest extends SieveAbstractSaslRequest {
-
- /**
- * @inheritdoc
- */
- constructor() {
- super();
- this.response = new SieveSaslLoginResponse();
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- switch (this.response.getState()) {
- case STATE_LOGIN_INITIALIZED:
- return builder
- .addLiteral("AUTHENTICATE")
- .addQuotedString("LOGIN");
- case STATE_LOGIN_USERNAME:
- return builder
- .addQuotedBase64(this._username);
- case STATE_LOGIN_PASSWORD:
- return builder
- .addQuotedBase64(this._password);
- }
-
- throw new Error("Unknown state in SASL login");
- }
-
- /**
- * @inheritdoc
- */
- hasNextRequest() {
- if (this.response.getState() === STATE_LOGIN_COMPLETED)
- return false;
-
- return true;
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- this.response.parse(parser);
-
- if (this.hasNextRequest())
- return this;
-
- return super.addResponse(this.response);
- }
- }
-
- /**
- * Implements the CRAM-MD5 Challenge-Response Mechanism as defined in RFC2195.
- *
- * It is considered to be weak and should only be used via encrypted connections.
- *
- * @author Thomas Schmid
- * @author Max Dittrich
- */
- class SieveSaslCramMd5Request extends SieveAbstractSaslRequest {
-
- /**
- * @inheritdoc
- */
- constructor() {
- super();
- this.response = new SieveSaslCramMd5Response();
- }
-
- /**
- * @inheritdoc
- */
- getCrypto() {
- return new SieveCrypto("MD5");
- }
-
- /**
- * Called when the server challenges the client to calculate a secret.
- *
- * @param {SieveAbstractRequestBuilder} builder
- * the builder which should be used to build the response.
- * @returns {SieveAbstractRequestBuilder}
- * the builder which contains the response for the challenge.
- */
- onChallenged(builder) {
-
- let challenge = this.response.getChallenge();
- // decoding the base64-encoded challenge
- challenge = builder.convertFromBase64(challenge);
-
- const crypto = this.getCrypto();
- const hmac = crypto.HMAC(this._password, challenge, "hex");
-
- return builder
- .addQuotedBase64(`${this._username} ${hmac}`);
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- switch (this.response.getState()) {
- case STATE_CRAM_MD5_INITIALIZED:
- return builder
- .addLiteral("AUTHENTICATE")
- .addQuotedString("CRAM-MD5");
- case STATE_CRAM_MD5_CHALLENGED:
- return this.onChallenged(builder);
- }
-
- throw new Error("Illegal state in SASL CRAM");
- }
-
- /**
- * @inheritdoc
- */
- hasNextRequest() {
- if (this.response.getState() === STATE_CRAM_MD5_COMPLETED)
- return false;
-
- return true;
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- this.response.parse(parser);
-
- if (this.hasNextRequest())
- return this;
-
- return super.addResponse(this.response);
- }
- }
-
- /**
- * This request implements an abstract base class for the "Salted Challenge Response Authentication
- * Mechanism" (SCRAM). A SASL SCRAM-SHA-1 compatible implementation is mandatory
- * for every manage sieve server. SASL SCRAM-SHA-1 supersedes DIGEST-MD5.
- *
- * @author Thomas Schmid
- */
- class SieveAbstractSaslScramRequest extends SieveAbstractSaslRequest {
-
- /**
- * @inheritdoc
- */
- constructor() {
- super();
- this.response = new SieveSaslScramShaResponse();
- }
-
- /**
- * Checks if the request supports authorization
- *
- * @returns {boolean}
- * true, as a scram request can always be used for a proxy authentication
- */
- isAuthorizable() {
- // overwrite the default as this mechanism support authorization
- return true;
- }
-
- /**
- * Gets the SASL Mechanism name.
- * @abstract
- *
- * @returns {string}
- * the SASL Mechanism's unique it as string.
- */
- getSaslName() {
- throw new Error("Implement SASL Name");
- }
-
- /**
- * Returns the crypto engine/provider which should be used for this request.
- * @abstract
- *
- * @returns {SieveCrypto}
- * the crypto engine
- */
- getCrypto() {
- throw new Error("Implement Crypto Method which returns a crypto provider");
- }
-
- /**
- *
- * @param {SieveAbstractRequestBuilder} builder
- * a reference to an request builder.
- *
- * @returns {SieveAbstractRequestBuilder}
- * a builder which contains the request data.
- */
- onChallengeServer(builder) {
-
- const crypto = this.getCrypto();
-
- this._cnonce = crypto.H("" + (Math.random() * SEED), "hex");
-
- // For integration tests, we need to fake the nonce...
- // ... so we take the nonce from the rfc otherwise the verification fails.
- //
- // ### DEBUG SHA1 ###
- // this._cnonce = "fyko+d2lbbFgONRv9qkxdawL";
- // this._username = "user";
- // this._password = "pencil";
- //
- // ### DEBUG SHA256 ###
- // this._cnonce = "rOprNGfwEbeRWgbNEkqO";
- // this._username = "user";
- // this._password = "pencil";
-
- // TODO SCRAM: escape/normalize authorization and username
- // ;; UTF8-char except NUL, "=", and ","
- // "=" is escaped by =2C and "," by =3D
-
- // Store client-first-message-bare
- this._authMessage = "n=" + this._username + ",r=" + this._cnonce;
- this._g2Header = "n," + (this._authorization !== "" ? "a=" + this._authorization : "") + ",";
-
- return builder
- .addLiteral("AUTHENTICATE")
- .addQuotedString(this.getSaslName())
- .addQuotedBase64(`${this._g2Header}${this._authMessage}`);
- }
-
- /**
- *
- * @param {SieveAbstractRequestBuilder} builder
- * a reference to an request builder.
- *
- * @returns {SieveAbstractRequestBuilder}
- * a builder which contains the request data.
- */
- onValidateChallenge(builder) {
- // Check if the server returned our nonce. This should prevent...
- // ... man in the middle attacks.
- const nonce = this.response.getNonce();
- if ((nonce.substr(0, this._cnonce.length) !== this._cnonce))
- throw new Error("Nonce invalid");
-
- const crypto = this.getCrypto();
-
- // As first step we need to salt the password...
- const salt = this.response.getSalt();
- const iter = this.response.getIterationCounter();
-
- // TODO Normalize password; and convert it into a byte array...
- // ... It might contain special characters.
-
- // ... this is done by applying a simplified PBKDF2 algorithm...
- // ... so we endup by calling Hi(Normalize(password), salt, i)
- this._saltedPassword = crypto.Hi(this._password, salt, iter);
-
- // the clientKey is defined as HMAC(SaltedPassword, "Client Key")
- const clientKey = crypto.HMAC(this._saltedPassword, "Client Key");
-
- // create the client-final-message-without-proof, ...
- const msg = "c=" + builder.convertToBase64(this._g2Header) + ",r=" + nonce;
- // ... append it and the server-first-message to client-first-message-bare...
- this._authMessage += "," + this.response.getServerFirstMessage() + "," + msg;
-
- // ... and convert it into a byte array.
- this._authMessage = crypto.strToByteArray(this._authMessage);
-
- // As next Step sign out message, this is done by applying the client...
- // ... key through a pseudorandom function to the message. It is defined...
- // as HMAC(H(ClientKey), AuthMessage)
- const clientSignature = crypto.HMAC(
- crypto.H(clientKey),
- this._authMessage);
-
- // We now complete the cryptographic part an apply our clientkey to the...
- // ... Signature, so that the server can be sure it is talking to us.
- // The RFC defines this step as ClientKey XOR ClientSignature
- const clientProof = clientKey;
- for (let k = 0; k < clientProof.length; k++)
- clientProof[k] ^= clientSignature[k];
-
- // Every thing done so let's send the message...
- // "c=" base64( (("" / "y") "," [ "a=" saslname ] "," ) "," "r=" c-nonce s-nonce ["," extensions] "," "p=" base64
- return builder
- .addQuotedBase64(msg + ",p=" + builder.convertToBase64(clientProof));
- // return "\""+btoa(msg+",p="+btoa(this.byteArrayToStr(clientProof)))+"\"\r\n";
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
-
- // Step1: Client sends Message to server. See SASL Login how to integrate it
- // into the AUTHENTICATE Command.
- //
- // e.g.: "AUTHENTICATE \"SCRAM-SHA-1\" \"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL\"\r\n"
-
- switch (this.response.getState()) {
- case 0:
- return this.onChallengeServer(builder);
- case 1:
- return this.onValidateChallenge(builder);
- case 2:
- // obviously we have to send an empty response. The server did not wrap...
- // ... the verifier into the Response Code...
- return builder
- .addQuotedString("");
- // return "\"\"\r\n";
- }
-
- throw new Error("Illegal state in SASL SCRAM");
- }
-
- /**
- * @inheritdoc
- */
- hasNextRequest() {
- if (this.response.getState() === 4)
- return false;
-
- return true;
- }
-
- /**
- * @inheritdoc
- */
- onOk(response) {
-
- const crypto = this.getCrypto();
-
- const serverSignature = crypto.HMAC(
- crypto.HMAC(
- this._saltedPassword,
- "Server Key"),
- this._authMessage
- );
-
- if (response.getVerifier() !== crypto.byteArrayToStr(serverSignature)) {
- response.message = "Server Signature not invalid ";
- response.response = 2;
- this.onNo(response);
- return;
- }
-
- super.onOk(response);
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
-
- this.response.parse(parser);
-
- if (this.hasNextRequest())
- return this;
-
- return super.addResponse(this.response);
- }
- }
-
- /**
- * Implements the SCRAM-SHA-1 mechanism.
- */
- class SieveSaslScramSha1Request extends SieveAbstractSaslScramRequest {
-
- /**
- * @inheritdoc
- */
- getSaslName() {
- return "SCRAM-SHA-1";
- }
-
- /**
- * @inheritdoc
- */
- getCrypto() {
- return new SieveCrypto("SHA1");
- }
- }
-
- /**
- * Implements the SCRAM-SHA-256 mechanism.
- */
- class SieveSaslScramSha256Request extends SieveAbstractSaslScramRequest {
-
- /**
- * @inheritdoc
- */
- getSaslName() {
- return "SCRAM-SHA-256";
- }
-
- /**
- * @inheritdoc
- */
- getCrypto() {
- return new SieveCrypto("SHA256");
- }
- }
-
- /**
- * This request implements SASL External Mechanism (rfc4422 Appendix A).
- * It's a dumb-dumb implementation, and relies upon an established tls connection.
- * It tells the server to use the cert provided during the TLS handshake.
- *
- * @author Thomas Schmid
- */
- class SieveSaslExternalRequest extends SieveAbstractSaslRequest {
-
- /**
- * @inheritdoc
- */
- isAuthorizable() {
- // overwrite the default behaviour.
- return true;
- }
-
- /**
- * @inheritdoc
- */
- getNextRequest(builder) {
- return builder
- .addLiteral("AUTHENTICATE")
- .addQuotedString("EXTERNAL")
- .addQuotedBase64("" + this._authorization);
- }
-
- /**
- * SASL External uses the TLS Cert for authentication.
- * Thus it does not rely upon any password, so this method returns always false.
- *
- * @returns {boolean}
- * returns always false
- */
- hasPassword() {
- return false;
- }
-
- /**
- * @inheritdoc
- */
- addResponse(parser) {
- return super.addResponse.call(this,
- (new SieveSimpleResponse()).parse(parser));
- }
- }
-
- exports.SieveGetScriptRequest = SieveGetScriptRequest;
- exports.SievePutScriptRequest = SievePutScriptRequest;
- exports.SieveCheckScriptRequest = SieveCheckScriptRequest;
- exports.SieveSetActiveRequest = SieveSetActiveRequest;
- exports.SieveCapabilitiesRequest = SieveCapabilitiesRequest;
- exports.SieveDeleteScriptRequest = SieveDeleteScriptRequest;
- exports.SieveNoopRequest = SieveNoopRequest;
- exports.SieveRenameScriptRequest = SieveRenameScriptRequest;
- exports.SieveListScriptsRequest = SieveListScriptsRequest;
- exports.SieveStartTLSRequest = SieveStartTLSRequest;
- exports.SieveLogoutRequest = SieveLogoutRequest;
- exports.SieveInitRequest = SieveInitRequest;
- exports.SieveSaslPlainRequest = SieveSaslPlainRequest;
- exports.SieveSaslLoginRequest = SieveSaslLoginRequest;
- exports.SieveSaslCramMd5Request = SieveSaslCramMd5Request;
- exports.SieveSaslScramSha1Request = SieveSaslScramSha1Request;
- exports.SieveSaslScramSha256Request = SieveSaslScramSha256Request;
- exports.SieveSaslExternalRequest = SieveSaslExternalRequest;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveRequest.mjs b/src/common/libManageSieve/SieveRequest.mjs
new file mode 100644
index 00000000..1813734c
--- /dev/null
+++ b/src/common/libManageSieve/SieveRequest.mjs
@@ -0,0 +1,1482 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/*
+ The communication in this library is asynchronous! After sending a request,
+ you will be notified by a listener, as soon as a response arrives.
+
+ If a request caused an error or timeout, its error listener will be called
+ to resolve the issue. If a server rejects a request, the onError() function
+ of the error listener will be invoked. In case of a timeout situation, the
+ onTimeout() function is called.
+
+ If a request succeeded, the corresponding response listener of the request
+ will be notified.
+
+ The onResponse(), getNextRequest(), hasNextRequest() Methods are
+ used by the Sieve object, and should not be invoked manually.
+
+ When the sieve object receives a response, it is passed to the onResponse()
+ Method of the requesting object. A timeout is signaled by passing invoking
+ the abort() Method.
+
+*/
+
+import {
+ SieveSimpleResponse,
+ SieveCapabilitiesResponse,
+ SieveListScriptsResponse,
+ SieveSaslLoginResponse,
+ SieveGetScriptResponse,
+ SieveSaslScramShaResponse
+} from "./SieveResponse.mjs";
+
+import { SieveCrypto } from "./SieveCrypto.mjs";
+
+import { SieveBase64Encoder } from "./SieveBase64.mjs";
+
+const STATE_LOGIN_INITIALIZED = 0;
+const STATE_LOGIN_USERNAME = 1;
+const STATE_LOGIN_PASSWORD = 2;
+const STATE_LOGIN_COMPLETED = 4;
+
+const RESPONSE_OK = 0;
+const RESPONSE_BYE = 1;
+const RESPONSE_NO = 2;
+
+const SEED = 1234567890;
+
+/**
+ * An abstract class, it is the prototype for any requests
+ */
+class SieveAbstractRequest {
+
+ /**
+ * Creates a new Instance.
+ */
+ constructor() {
+ this.errorListener = null;
+ this.timeoutListener = null;
+ this.byeListener = null;
+
+ this.responseListener = null;
+
+ this.optional = false;
+ this.abandoned = false;
+ }
+
+ /**
+ * The error listener is called whenever the server returns an error state
+ * @param {Function} listener
+ * the listener which should be invoked
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ */
+ addErrorListener(listener) {
+
+ if (typeof listener !== 'function') {
+ throw new Error("Error listener is not a function");
+ }
+
+ this.errorListener = listener;
+ return this;
+ }
+
+ /**
+ * The timeout listener is calls whenever sending a request fails for some
+ * reason. This could be because of a timeout or because the server terminated
+ * the connection or something else happened.
+ *
+ * The listener does not necessarily wait for a timeout event. E.g. in case the
+ * connection is lost it will fire immediately.
+ *
+ * @param {Function} listener
+ * the listener which should be invoked
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ */
+ addTimeoutListener(listener) {
+
+ // TODO should be renamed to error listener as it is more than just a timeout handler...
+ if (typeof listener !== 'function') {
+ throw new Error("Timeout listener is not a function");
+ }
+
+ this.timeoutListener = listener;
+ return this;
+ }
+
+ /**
+ * The bye listener is called whenever the server terminated the connection
+ * gracefully.
+ *
+ * @param {Function} listener
+ * the listener which should be invoked
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ */
+ addByeListener(listener) {
+
+ if (typeof listener !== 'function') {
+ throw new Error("Bye listener is not a function");
+ }
+
+ this.byeListener = listener;
+ return this;
+ }
+
+ /**
+ * Add a response listener to this request. The response listener will
+ * be triggered in case a successful server response was received
+ *
+ * @param {Function} listener
+ * the listener which should be invoked when the server response
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ */
+ addResponseListener(listener) {
+
+ if (typeof listener !== 'function') {
+ throw new Error(`Listener is not a function ${listener}`);
+ }
+
+ this.responseListener = listener;
+ return this;
+ }
+
+ /**
+ * Marks this request a optional. See isOptional for more details.
+ *
+ * @returns {SieveAbstractRequest}
+ * a self reference.
+ */
+ makeOptional() {
+ this.optional = true;
+ return this;
+ }
+
+ /**
+ * Optional means the request will be send to the server. But a server
+ * response is not required. This is used to workaround a common bug in
+ * old sieve implementations.
+ *
+ * Please note an optional request will resolve earliest after the next
+ * subsequent request was resolved.
+ *
+ * @returns {boolean}
+ * false in case this request is not optional which is the default.
+ */
+ isOptional() {
+ return this.optional;
+ }
+
+ /**
+ * In general sieve is bidirectional, client first communication. This means
+ * the client sends a request to server and the server responds to it. Request
+ * and responses typically form pairs.
+ *
+ * But there are exceptions to this rule, e.g. the init response upon
+ * connect or after tls handshake. In this case it is a one-way communication.
+ * The server sends a response without being triggered by a request.
+ *
+ * @returns {boolean}
+ * true in case the request is bidirectional. Which means
+ * the client sends a request and the server responds
+ * to that.
+ * false in case the request is unidirectional. Which means
+ * it was send by the server without being explicitly
+ * request by the client.
+ */
+ hasRequest() {
+ return true;
+ }
+
+ /**
+ * Most request use a single request response pair. But especially the SASL
+ * script use normally more than one round trip due to security reasons.
+ *
+ * This flags indicates if the Request's internal state engine was completed.
+ *
+ * @returns {boolean}
+ * false in case the request has been completed. True in case it is still
+ * running and waits for sending the next request.
+ */
+ hasNextRequest() {
+ return false;
+ }
+
+ /**
+ * Returns the next request as a string. It uses the given
+ * Request builder to assemble the string.
+ *
+ * @param {SieveRequestBuilder} builder
+ * a reference to a stateless request builder which can be used
+ * to form the request string.
+ * @returns {string}
+ * the data which should be send to the server
+ *
+ * @abstract
+ */
+ async getNextRequest(builder) {
+ throw new Error(`Abstract Method implement me ${builder}`);
+ }
+
+ /**
+ * Marks this request as abandoned because the request timedout or the socket
+ * was closed.This should never be invoked directly by any other object than
+ * the sieve connection.
+ *
+ * @param {Error} [reason]
+ * the optional reason why the request was abandoned.
+ */
+ abandon(reason) {
+ this.abandoned = true;
+ this.reason = reason;
+ }
+
+ /**
+ * Checks if this message was abandoned.
+ *
+ * @returns {boolean}
+ * true in case the request is abandoned otherwise false.
+ */
+ isAbandoned() {
+ return this.aborted;
+ }
+
+ /**
+ * Triggers the request timeout listener.
+ *
+ * The request has to be abandoned and non optional otherwise the
+ * call is silently discarded.
+ */
+ async onAbandon() {
+
+ if (!this.isAbandoned())
+ return;
+
+ if (this.isOptional())
+ return;
+
+ if (this.timeoutListener)
+ this.timeoutListener(this.reason);
+ }
+
+ /**
+ * Trigger the error listener to signal an error condition.
+ *
+ * This should never be invoked directly by any other object than the
+ * sieve connection or this class.
+ *
+ * @param {SieveSimpleResponse} response
+ * the response which should be handled by this request.
+ */
+ async onNo(response) {
+ if (this.errorListener)
+ this.errorListener(response);
+ }
+
+ /**
+ * Trigger the bye listener.
+ *
+ * This should never be invoked directly by any other object than the
+ * sieve connection or this class.
+ *
+ * @param {SieveSimpleResponse} response
+ * the response which should be handled by this request.
+ */
+ async onBye(response) {
+ if ((response.getResponse() === RESPONSE_BYE) && (this.byeListener))
+ this.byeListener(response);
+ }
+
+ /**
+ * Triggers the ok listener. This is normally when the request completed
+ * successfully.
+ *
+ * This should never be invoked directly by any other object than the
+ * sieve connection or this class.
+ *
+ * @param {SieveSimpleResponse} response
+ * the response which should be handled by this request.
+ *
+ * @abstract
+ */
+ async onOk(response) {
+ throw new Error(`Abstract method override me ${response}`);
+ }
+
+ /**
+ * An abstract helper, which calls the default message handlers
+ * for the given response
+ *
+ * @param {SieveSimpleResponse} response
+ * the response which should be handled by this request.
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ */
+ async onResponse(response) {
+
+ if (response.getResponse() === RESPONSE_OK) {
+ await this.onOk(response);
+ return this;
+ }
+
+ if (this.isOptional())
+ throw new Error("Invalid Response for an optional Request");
+
+ if (response.getResponse() === RESPONSE_BYE) {
+ await this.onBye(response);
+ return this;
+ }
+
+ if (response.getResponse() === RESPONSE_NO) {
+ await this.onNo(response);
+ return this;
+ }
+
+ throw new Error("Invalid Response Code");
+ }
+}
+
+/**
+ * An abstract calls derived from AbstractRequest. It is the foundation for
+ * any requests implementing a SASL compatible authentication.
+ */
+class SieveAbstractSaslRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ constructor() {
+ super();
+
+ this._username = "";
+ this._password = "";
+ this._authorization = "";
+ }
+
+ /**
+ * Sets the sasl mechanisms username. Not all SASL Mechanisms require an username.
+ *
+ * @param {string} username
+ * the username
+ * @returns {SieveAbstractSaslRequest}
+ * a self reference
+ **/
+ setUsername(username) {
+ this._username = username;
+ return this;
+ }
+
+ /**
+ * Most SASL mechanisms need a password or secret to authenticate.
+ * But there are also mechanisms like SASL EXTERNAL which do not need a password.
+ * They use different methods to transfer the credentials.
+ *
+ * @returns {boolean}
+ * indicates if this SASL Mechanism needs a password
+ */
+ hasPassword() {
+ return true;
+ }
+
+ /**
+ * Sets the sasl request's password.
+ *
+ * @param {string} password
+ * the password which shall be used for the authentication.
+ * @returns {SieveAbstractSaslRequest}
+ * a self reference
+ **/
+ setPassword(password) {
+ this._password = password;
+ return this;
+ }
+
+ /**
+ * Checks if this mechanism supports authorization. Keep in mind
+ * authorization is rarely used and only very few mechanisms
+ * support it.
+ *
+ * With authorization you use your credentials to login as a different user.
+ * Which means you first authenticate with your username and then do the
+ * authorization which switch the user. Typically admins and superusers have
+ * such super powers.
+ *
+ * @returns {boolean}
+ * true in case the request supports authorization otherwise false.
+ */
+ isAuthorizable() {
+ // Sub classes shall overwrite this with true in case authorization is supported
+ return false;
+ }
+
+ /**
+ * Sets the username which should be authorized.
+ * In case authorization is not supported it will be silently ignored.
+ *
+ * @param {string} authorization
+ * the username used for authorization
+ * @returns {SieveAbstractRequest}
+ * a self reference
+ **/
+ setAuthorization(authorization) {
+ if (this.isAuthorizable())
+ this._authorization = authorization;
+
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+}
+
+/**
+ * Loads a script from the server and returns the content.
+ * In case the script is non existent an error will be triggered.
+ */
+class SieveGetScriptRequest extends SieveAbstractRequest {
+
+ /**
+ * Create a new request which load a sieve script from the
+ * remote server.
+ *
+ * @param {string} script
+ * the script which should be retrieved
+ */
+ constructor(script) {
+ super();
+ this.script = script;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("GETSCRIPT")
+ .addQuotedString(this.script);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await (super.onResponse(
+ await (new SieveGetScriptResponse(this.script)).parse(parser)));
+ }
+}
+
+/**
+ * Stores the given script on the server.
+ * The script is validated by the server and will be rejected with a NO
+ * in case the validation fails.
+ *
+ * Please not it will overwrite silently any existing script with the same name.
+ */
+class SievePutScriptRequest extends SieveAbstractRequest {
+
+ /**
+ * Creates a request which stores a sieve script on the server.
+ *
+ * @param {string} script
+ * the script's name
+ * @param {string} body
+ * the sieve script which should be stored on the server.
+ */
+ constructor(script, body) {
+ super();
+ this.script = script;
+
+ // cleanup line breaks...
+
+ // eslint-disable-next-line no-control-regex
+ this.body = body.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("PUTSCRIPT")
+ .addQuotedString(this.script)
+ .addMultiLineString(this.body);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * The CheckScriptRequest validates the Syntax of a Sieve script. The script
+ * is not stored on the server.
+ *
+ * If the script fails this test, the server replies with a NO response. The
+ * response contains one or more CRLF separated error messages.
+ *
+ * An OK response can contain Syntax Warnings.
+ *
+ * C: CheckScript {31+}
+ * C: #comment
+ * C: InvalidSieveCommand
+ * C:
+ * S: NO "line 2: Syntax error"
+ *
+ */
+class SieveCheckScriptRequest extends SieveAbstractRequest {
+
+ /**
+ * Creates a new request which checks if the scripts' syntax is valid.
+ * @param {string} body
+ * the script which should be check for syntactical validity
+ */
+ constructor(body) {
+ super();
+ // Strings in JavaScript should use the encoding of the xul document and...
+ // ... sockets use binary strings. That means for us we have to convert...
+ // ... the JavaScript string into a UTF8 String.
+
+ // Further more Sieve expects line breaks to be \r\n. Mozilla uses \n ...
+ // ... according to the documentation. But for some unknown reason a ...
+ // ... string sometimes contains mixed line breaks. Thus we convert ...
+ // ... any \r\n, \r and \n to \r\n.
+
+ // eslint-disable-next-line no-control-regex
+ this.body = body.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder.addLiteral("CHECKSCRIPT")
+ .addMultiLineString(this.body);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * This class encapsulates a Sieve SETACTIVE request.
+ * <p>
+ * Either none or one server scripts can be active, this means you can't have
+ * more than one active scripts
+ * <p>
+ * You activate a Script by calling SETACTIVE and the script name. At activation
+ * the previous active Script will become inactive.
+ */
+class SieveSetActiveRequest extends SieveAbstractRequest {
+
+ /**
+ * Creates a new request which activates the given script
+ * @param {string} script - The script name which should be activated. Passing
+ * an empty string deactivates the active script.
+ */
+ constructor(script) {
+ super();
+
+ this.script = "";
+
+ if ((typeof (script) !== 'undefined') && (script !== null))
+ this.script = script;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("SETACTIVE")
+ .addQuotedString(this.script);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * The capability request asks the server to transmit his
+ * capabilities like the supported sieve extensions, as
+ * well as a list with all possible SASL authentication mechanisms.
+ *
+ * The returned capabilities depends upon the context. A server may
+ * refuse to advertise SASL Mechanisms while using an insecure
+ * connection. As soon as you started a secure connection it may offer
+ * additional Mechanisms.
+ *
+ * So you should always refresh the server's capabilities.
+ */
+class SieveCapabilitiesRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("CAPABILITY");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveCapabilitiesResponse()).parse(parser)));
+ }
+}
+
+/**
+ * The delete script command is used to remove a script from the server.
+ * Deleting an non existing script will result in an error. Also deleting
+ * the active script will result in an error.
+ */
+class SieveDeleteScriptRequest extends SieveAbstractRequest {
+
+ /**
+ * Creates a request which deletes the given script.
+ * @param {string} script
+ * the scripts name which should be deleted
+ */
+ constructor(script) {
+ super();
+ this.script = script;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("DELETESCRIPT")
+ .addQuotedString(this.script);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * The NOOP request does nothing, it is used for protocol resynchronization or
+ * to reset any inactivity auto-logout timer on the server.
+ *
+ * The response to the NOOP command is always OK.
+ */
+class SieveNoopRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("NOOP");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+
+/**
+ * This command is used to rename a Sieve script. The Server will reply with
+ * a NO response if the old script does not exist, or a script with the new
+ * name already exists.
+ *
+ * Renaming the active script is allowed, the server ensures that the
+ * renamed script remains active.
+ */
+class SieveRenameScriptRequest extends SieveAbstractRequest {
+
+ /**
+ * Creates a rename script request.
+ *
+ * @param {string} oldScript Name of the script, which should be renamed
+ * @param {string} newScript New name of the script
+ **/
+ constructor(oldScript, newScript) {
+ super();
+ this.oldScript = oldScript;
+ this.newScript = newScript;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("RENAMESCRIPT")
+ .addQuotedString(this.oldScript)
+ .addQuotedString(this.newScript);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * This command is used to list all sieve script of the current user.
+ * In case there are no scripts the server responds with an empty list.
+ */
+class SieveListScriptsRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("LISTSCRIPTS");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveListScriptsResponse()).parse(parser)));
+ }
+}
+
+/**
+ * Initializes switching to tls via start tls
+ */
+class SieveStartTLSRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("STARTTLS");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * A logout request signals the server that the client wishes to terminate
+ * the current session.
+ * <pre>
+ * Client > LOGOUT
+ * Server < OK "Logout Complete"
+ * [ connection terminated ]
+ * </pre>
+ * <p>
+ */
+class SieveLogoutRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder.addLiteral("LOGOUT");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onBye(response) {
+ // As issued a logout request thus onBye response is perfectly fine...
+ // ... and equivalent to an ok in this case.
+ await this.onOk(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * A ManageSieve server automatically post his capabilities as soon as the
+ * connection is established or a secure channel is successfully started
+ * (STARTTLS command). In order to capture this information a dummy request
+ * is used. It does not send a real request, but it parses the initial response
+ * of the sieve server. Therefore it is important to add the request before the
+ * connection is established. Otherwise the message queue will be jammed.
+ *
+ * Server < "IMPLEMENTATION" "Cyrus timsieved v2.1.18-IPv6-Debian-2.1.18-1+sarge2"
+ * < "SASL" "PLAIN"
+ * < "SIEVE" "fileinto reject envelope vacation imapflags notify subaddress relational regex"
+ * < "STARTTLS"
+ * < OK
+ *
+ */
+class SieveInitRequest extends SieveAbstractRequest {
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+ if (this.responseListener)
+ this.responseListener(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ hasRequest() {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveCapabilitiesResponse()).parse(parser)));
+ }
+}
+
+/**
+ * Implements the SASL Plain authentication method.
+ *
+ * The password is only base64 encoded not encrypted. Therefore it can be
+ * read or sniffed easily. A secure connection will solve this issue. Always
+ * start a tls session before using this request.
+ *
+ * Client > AUTHENTICATE "PLAIN" AHRlc3QAc2VjcmV0 | AUTHENTICATE "PLAIN" [UTF8NULL]test[UTF8NULL]secret
+ * Server < OK | OK
+ */
+class SieveSaslPlainRequest extends SieveAbstractSaslRequest {
+
+ /**
+ * The sasl plain request always support proxy authentication.
+ *
+ * @returns {boolean}
+ * always true
+ */
+ isAuthorizable() {
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("AUTHENTICATE")
+ .addQuotedString("PLAIN")
+ .addQuotedBase64(`${this._authorization}\0${this._username}\0${this._password}`);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+/**
+ * This request implements the SASL Login authentication method. It is deprecated
+ * and has been superseded by SASL Plain method. SASL Login uses a question and
+ * answer style communication. The server will request first the username and
+ * then the password.
+ * <p>
+ * Please note, that the password is not encrypted, it is only base64 encoded.
+ * Therefore it can be read or sniffed easily. A secure connection will solve
+ * this issue. So send whenever possible, a SieveStartTLSRequest before calling
+ * this request.
+ * <p>
+ * Client > AUTHENTICATE "LOGIN" | AUTHENTICATE "LOGIN"
+ * Server < {12} | {12}
+ * < VXNlcm5hbWU6 | Username:
+ * Client > {8+} | {8+}
+ * > Z2Vlaw== | geek
+ * Server < {12} | {12}
+ * < UGFzc3dvcmQ6 | Password:
+ * Client > {12+} | {12+}
+ * > dGgzZzMzazE= | th3g33k1
+ * Server < OK | OK
+ *
+ * @deprecated
+ */
+class SieveSaslLoginRequest extends SieveAbstractSaslRequest {
+
+ /**
+ * @inheritdoc
+ */
+ constructor() {
+ super();
+ this.response = new SieveSaslLoginResponse();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ switch (this.response.getState()) {
+ case STATE_LOGIN_INITIALIZED:
+ return builder
+ .addLiteral("AUTHENTICATE")
+ .addQuotedString("LOGIN");
+ case STATE_LOGIN_USERNAME:
+ return builder
+ .addQuotedBase64(this._username);
+ case STATE_LOGIN_PASSWORD:
+ return builder
+ .addQuotedBase64(this._password);
+ }
+
+ throw new Error("Unknown state in SASL login");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ hasNextRequest() {
+ if (this.response.hasError())
+ return false;
+
+ if (this.response.getState() === STATE_LOGIN_COMPLETED)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ await this.response.parse(parser);
+
+ if (this.hasNextRequest())
+ return this;
+
+ return await(super.onResponse(this.response));
+ }
+}
+
+const SHA_STATE_FIRST_MESSAGE = 0;
+const SHA_STATE_FINAL_MESSAGE = 1;
+const SHA_STATE_EMPTY_MESSAGE = 2;
+const SHA_STATE_COMPLETED = 4;
+
+/**
+ * This request implements an abstract base class for the "Salted Challenge Response Authentication
+ * Mechanism" (SCRAM). A SASL SCRAM-SHA-1 compatible implementation is mandatory
+ * for every manage sieve server. SASL SCRAM-SHA-1 supersedes DIGEST-MD5.
+ *
+ * @author Thomas Schmid
+ */
+class SieveAbstractSaslScramRequest extends SieveAbstractSaslRequest {
+
+ /**
+ * @inheritdoc
+ */
+ constructor() {
+ super();
+ this.response = new SieveSaslScramShaResponse();
+ }
+
+ /**
+ * Checks if the request supports authorization
+ *
+ * @returns {boolean}
+ * true, as a scram request can always be used for a proxy authentication
+ */
+ isAuthorizable() {
+ // overwrite the default as this mechanism support authorization
+ return true;
+ }
+
+ /**
+ * Gets the SASL Mechanism name.
+ * @abstract
+ *
+ * @returns {string}
+ * the SASL Mechanism's unique it as string.
+ */
+ getSaslName() {
+ throw new Error("Implement SASL Name");
+ }
+
+ /**
+ * Returns the crypto engine/provider which should be used for this request.
+ * @abstract
+ *
+ * @returns {SieveCrypto}
+ * the crypto engine
+ */
+ getCrypto() {
+ throw new Error("Implement Crypto Method which returns a crypto provider");
+ }
+
+ /**
+ * Before sending the username to the server is needs to be normalized.
+ * The comma and the equal character have a special meaning and need to
+ * escaped.
+ *
+ * @param {string} username
+ * the username which should be escaped
+ * @returns {string}
+ * the escaped username
+ */
+ normalizeUsername(username) {
+
+ // Safe Chars:
+ // UTF8-char except "=" and ","
+ // 0x01-0x2B, 0x2D-0x3C, 0X3E-0x7F,
+ // UTF8-2, UTF8-3, UTF8-4
+
+ // Theoretically we should run SASLPrep on str but the server does ist anyway.
+ return username.replace("=", "=3D").replace(",", "=2C");
+ }
+
+ /**
+ * Calculates a nonce for the initial request.
+ *
+ * @returns {string}
+ * a sequence of printable ascii characters except a comma (,).
+ */
+ async generateNonce() {
+ return await (this.getCrypto().H("" + (Math.random() * SEED), "hex"));
+ }
+
+ /**
+ * Sends the "client-first-message" to the server.
+ *
+ * @param {SieveRequestBuilder} builder
+ * a reference to an request builder.
+ *
+ * @returns {SieveRequestBuilder}
+ * a builder which contains the request data.
+ */
+ async sendFirstRequest(builder) {
+
+ this._cnonce = await this.generateNonce();
+
+ // For integration tests, we need to fake the nonce...
+ // ... so we take the nonce from the rfc otherwise the verification fails.
+ //
+ // ### DEBUG SHA1 ###
+ // this._cnonce = "fyko+d2lbbFgONRv9qkxdawL";
+ // this._username = "user";
+ // this._password = "pencil";
+ // this._authorization = "";
+ //
+ // ### DEBUG SHA256 ###
+ // this._cnonce = "rOprNGfwEbeRWgbNEkqO";
+ // this._username = "user";
+ // this._password = "pencil";
+
+ this._authMessage = `n=${this.normalizeUsername(this._username)},r=${this._cnonce}`;
+ this._g2Header = `n,${(this._authorization !== "" ? "a=" + this.normalizeUsername(this._authorization) : "")},`;
+
+ return builder
+ .addLiteral("AUTHENTICATE")
+ .addQuotedString(this.getSaslName())
+ .addQuotedBase64(`${this._g2Header}${this._authMessage}`);
+ }
+
+ /**
+ * Sends the "client-final-message" to the server.
+ *
+ * @param {SieveRequestBuilder} builder
+ * a reference to an request builder.
+ *
+ * @returns {SieveRequestBuilder}
+ * a builder which contains the request data.
+ */
+ async sendFinalRequest(builder) {
+
+ // Check if the server returned our nonce. This should prevent...
+ // ... man in the middle attacks.
+ const nonce = this.response.getNonce();
+ if ((nonce.substr(0, this._cnonce.length) !== this._cnonce))
+ throw new Error("Nonce invalid");
+
+ const crypto = this.getCrypto();
+
+ // As first step we need to salt the password...
+ const salt = this.response.getSalt();
+ const iter = this.response.getIterationCounter();
+
+ const password = (new TextEncoder()).encode(this._password);
+
+ // ... this is done by applying a simplified PBKDF2 algorithm...
+ // ... so we endup by calling Hi(Normalize(password), salt, i)
+ this._saltedPassword = await (crypto.Hi(crypto.normalize(password), salt, iter));
+
+ // the clientKey is defined as HMAC(SaltedPassword, "Client Key")
+ const clientKey = await (crypto.HMAC(this._saltedPassword, "Client Key"));
+
+ // create the client-final-message-without-proof, ...
+
+ const msg = `c=${(new SieveBase64Encoder(this._g2Header)).toUtf8()},r=${nonce}`;
+ // ... append it and the server-first-message to client-first-message-bare...
+ this._authMessage += "," + this.response.getServerFirstMessage() + "," + msg;
+
+ // ... and convert it into a byte array.
+ this._authMessage = crypto.strToByteArray(this._authMessage);
+
+
+ // As next Step sign out message, this is done by applying the client...
+ // ... key through a pseudorandom function to the message. It is defined...
+ // as HMAC(H(ClientKey), AuthMessage)
+ const clientSignature = await (crypto.HMAC(
+ await (crypto.H(clientKey)),
+ this._authMessage));
+
+ // We now complete the cryptographic part an apply our clientkey to the...
+ // ... Signature, so that the server can be sure it is talking to us.
+ // The RFC defines this step as ClientKey XOR ClientSignature
+ const clientProof = clientKey;
+ for (let k = 0; k < clientProof.length; k++)
+ clientProof[k] ^= clientSignature[k];
+
+ // Every thing done so let's send the message...
+ // "c=" base64( (("" / "y") "," [ "a=" saslname ] "," ) "," "r=" c-nonce s-nonce ["," extensions] "," "p=" base64
+ return builder
+ .addQuotedBase64(`${msg},p=${(new SieveBase64Encoder(clientProof)).toUtf8()}`);
+ // return "\""+btoa(msg+",p="+btoa(this.byteArrayToStr(clientProof)))+"\"\r\n";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+
+ // Step1: Client sends Message to server. See SASL Login how to integrate it
+ // into the AUTHENTICATE Command.
+ //
+ // e.g.: "AUTHENTICATE \"SCRAM-SHA-1\" \"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL\"\r\n"
+
+
+ switch (this.response.getState()) {
+ case SHA_STATE_FIRST_MESSAGE:
+ return await (this.sendFirstRequest(builder));
+ case SHA_STATE_FINAL_MESSAGE:
+ return await (this.sendFinalRequest(builder));
+ case SHA_STATE_EMPTY_MESSAGE:
+ // We have to send an empty response. The server did not wrap...
+ // ... the verifier into the Response Code...
+ return builder
+ .addQuotedString("");
+ }
+
+ throw new Error(`Illegal state ${this.response.getState()} in SASL SCRAM`);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ hasNextRequest() {
+
+ if (this.response.hasError())
+ return false;
+
+ if (this.response.getState() === SHA_STATE_COMPLETED)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onOk(response) {
+
+ const crypto = this.getCrypto();
+
+ const serverSignature = await (crypto.HMAC(
+ await (crypto.HMAC(this._saltedPassword, "Server Key")),
+ this._authMessage
+ ));
+
+ if (response.getVerifier() !== crypto.byteArrayToStr(serverSignature)) {
+
+ response.setResponse(RESPONSE_NO);
+ response.setMessage("Server Signature not invalid");
+
+ await this.onNo(response);
+ return;
+ }
+
+ await super.onOk(response);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+
+ await this.response.parse(parser);
+
+ if (this.hasNextRequest())
+ return this;
+
+ return await(super.onResponse(this.response));
+ }
+}
+
+/**
+ * Implements the SCRAM-SHA-1 mechanism.
+ */
+class SieveSaslScramSha1Request extends SieveAbstractSaslScramRequest {
+
+ /**
+ * @inheritdoc
+ */
+ getSaslName() {
+ return "SCRAM-SHA-1";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getCrypto() {
+ return new SieveCrypto("SHA-1");
+ }
+}
+
+/**
+ * Implements the SCRAM-SHA-256 mechanism.
+ */
+class SieveSaslScramSha256Request extends SieveAbstractSaslScramRequest {
+
+ /**
+ * @inheritdoc
+ */
+ getSaslName() {
+ return "SCRAM-SHA-256";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getCrypto() {
+ return new SieveCrypto("SHA-256");
+ }
+}
+
+/**
+ * Implements the SCRAM-SHA-512 mechanism.
+ */
+class SieveSaslScramSha512Request extends SieveAbstractSaslScramRequest {
+
+ /**
+ * @inheritdoc
+ */
+ getSaslName() {
+ return "SCRAM-SHA-512";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getCrypto() {
+ return new SieveCrypto("SHA-512");
+ }
+}
+
+/**
+ * This request implements SASL External Mechanism (rfc4422 Appendix A).
+ * It's a dumb-dumb implementation, and relies upon an established tls connection.
+ * It tells the server to use the cert provided during the TLS handshake.
+ *
+ * @author Thomas Schmid
+ */
+class SieveSaslExternalRequest extends SieveAbstractSaslRequest {
+
+ /**
+ * @inheritdoc
+ */
+ isAuthorizable() {
+ // overwrite the default behaviour.
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getNextRequest(builder) {
+ return builder
+ .addLiteral("AUTHENTICATE")
+ .addQuotedString("EXTERNAL")
+ .addQuotedBase64("" + this._authorization);
+ }
+
+ /**
+ * SASL External uses the TLS Cert for authentication.
+ * Thus it does not rely upon any password, so this method returns always false.
+ *
+ * @returns {boolean}
+ * returns always false
+ */
+ hasPassword() {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onResponse(parser) {
+ return await(super.onResponse(
+ await (new SieveSimpleResponse()).parse(parser)));
+ }
+}
+
+export {
+ SieveGetScriptRequest,
+ SievePutScriptRequest,
+ SieveCheckScriptRequest,
+ SieveSetActiveRequest,
+ SieveCapabilitiesRequest,
+ SieveDeleteScriptRequest,
+ SieveNoopRequest,
+ SieveRenameScriptRequest,
+ SieveListScriptsRequest,
+ SieveStartTLSRequest,
+ SieveLogoutRequest,
+ SieveInitRequest,
+ SieveSaslPlainRequest,
+ SieveSaslLoginRequest,
+ SieveSaslScramSha1Request,
+ SieveSaslScramSha256Request,
+ SieveSaslScramSha512Request,
+ SieveSaslExternalRequest
+};
diff --git a/src/common/libManageSieve/SieveRequestBuilder.mjs b/src/common/libManageSieve/SieveRequestBuilder.mjs
new file mode 100644
index 00000000..10ce2873
--- /dev/null
+++ b/src/common/libManageSieve/SieveRequestBuilder.mjs
@@ -0,0 +1,131 @@
+/*
+ * The contents of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via email
+ * from the author. Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveBase64Encoder } from "./SieveBase64.mjs";
+
+/**
+ * A helper class used to build standard compliant sieve requests.
+ */
+class SieveRequestBuilder {
+
+ /**
+ * Creates a new instance
+ */
+ constructor() {
+ this.data = "";
+ }
+
+ /**
+ * Adds a string as quoted base 64 encoded literal to the request.
+ *
+ * This is typically needed for SASL requests as they have to be
+ * base64 encoded by definition.
+ *
+ * @param {string} token
+ * the string which should be added to the request.
+ * @returns {SieveAbstractRequestBuilder}
+ * a self reference
+ */
+ addQuotedBase64(token) {
+ if (token === undefined || token === null)
+ throw new Error("Invalid token");
+
+ this.addLiteral(`"${(new SieveBase64Encoder(token)).toUtf8()}"`);
+ return this;
+ }
+
+ /**
+ * Adds a string as quoted literal to the request.
+ *
+ * This is typically used for string without a line break.
+ * In case you know you'll have a line break use the multiline
+ * version for better readability.
+ *
+ * Do not use this for any sasl method. All sasl strings
+ * have to be base 64 encoded. Refer to addQuotedBase64String instead.
+ *
+ * @param {string} [token]
+ * the string which should be added to the request.
+ * if omitted an empty string is sent.
+ * @returns {SieveAbstractRequestBuilder}
+ * a self reference
+ */
+ addQuotedString(token) {
+ if (typeof (token) === "undefined" || token === null)
+ token = "";
+
+ this.addLiteral('"' + this.escapeString(token) + '"');
+ return this;
+ }
+
+ /**
+ * Adds a string as multiline literal to the request.
+ *
+ * It improves the requests readability in case you need to send a
+ * string containing a line break.
+ *
+ * @param {string} token
+ * the string which should be added to the request.
+ * @returns {SieveAbstractRequestBuilder}
+ * a self reference
+ */
+ addMultiLineString(token) {
+ // Calculate the length in bytes
+ const length = (new TextEncoder()).encode(token).byteLength;
+ // return Buffer.byteLength(data, 'utf8');
+
+ this.addLiteral(`{${length}+}\r\n${token}`);
+ return this;
+ }
+
+ /**
+ * Adds a literal to the request.
+ * The literal will used as it is. It will not be wrapped in a string or escaped.
+ * In case you need this use the specialized methods.
+ *
+ * @param {string} token
+ * the literal which should be added.
+ * @returns {SieveAbstractRequestBuilder}
+ * a self reference
+ */
+ addLiteral(token) {
+
+ if (this.data !== "")
+ this.data += " ";
+
+ this.data += token;
+ return this;
+ }
+
+ /**
+ * Returns the current request as it was cached and build up to the call.
+ *
+ * @returns {string}
+ * the current request including a trailing line break
+ */
+ getBytes() {
+ return this.data + "\r\n";
+ }
+
+ /**
+ * Escapes a string. All Backslashes are converted to \\ while
+ * all quotes are escaped as \"
+ *
+ * @param {string} str
+ * the string which should be escaped
+ * @returns {string}
+ * the escaped string.
+ */
+ escapeString(str) {
+ return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
+ }
+
+}
+
+export { SieveRequestBuilder };
diff --git a/src/common/libManageSieve/SieveResponse.js b/src/common/libManageSieve/SieveResponse.js
deleted file mode 100755
index 06185b01..00000000
--- a/src/common/libManageSieve/SieveResponse.js
+++ /dev/null
@@ -1,1103 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- *
- * Contributors:
- * Max Dittrich
- */
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const {
- SieveResponseCode,
- SieveResponseCodeSasl,
- SieveResponseCodeReferral
- } = require("./SieveResponseCodes.js");
-
- const CHAR_LOWERCASE_B = 66;
- const CHAR_UPPERCASE_B = 98;
- const CHAR_B = [CHAR_LOWERCASE_B, CHAR_UPPERCASE_B];
-
- const CHAR_LOWERCASE_E = 69;
- const CHAR_UPPERCASE_E = 101;
- const CHAR_E = [CHAR_LOWERCASE_E, CHAR_UPPERCASE_E];
-
- const CHAR_LOWERCASE_N = 78;
- const CHAR_UPPERCASE_N = 110;
- const CHAR_N = [CHAR_LOWERCASE_N, CHAR_UPPERCASE_N];
-
- const CHAR_LOWERCASE_O = 79;
- const CHAR_UPPERCASE_O = 111;
- const CHAR_O = [CHAR_LOWERCASE_O, CHAR_UPPERCASE_O];
-
- const CHAR_LOWERCASE_K = 75;
- const CHAR_UPPERCASE_K = 107;
- const CHAR_K = [CHAR_LOWERCASE_K, CHAR_UPPERCASE_K];
-
- const CHAR_LOWERCASE_Y = 89;
- const CHAR_UPPERCASE_Y = 121;
- const CHAR_Y = [CHAR_LOWERCASE_Y, CHAR_UPPERCASE_Y];
-
- const CHAR_BRACKET_OPEN = 40;
- const CHAR_BRACKET_CLOSE = 41;
- const CHAR_SPACE = 32;
- const CHAR_CR = 13;
-
- const TOKEN_OK = [CHAR_O, CHAR_K];
- const TOKEN_BYE = [CHAR_B, CHAR_Y, CHAR_E];
- const TOKEN_NO = [CHAR_N, CHAR_O];
-
- const SIEVE_VERSION_1 = 1.0;
-
- const ONE_CHAR = 1;
-
- const RESPONSE_OK = 0;
- const RESPONSE_BYE = 1;
- const RESPONSE_NO = 2;
-
- const MAX_REDIRECTS_UNLIMITED = -1;
-
- /**
- * This class implements a generic response handler for simple sieve requests.
- *
- * Simple requests just indicate, wether the command succeeded or not. They
- * return only status information, and do not contain any data relevant for
- * the user.
- *
- * @see SieveResponseParser
- *
- * @param {SieveResponseParser} [parser]
- * a SieveResponseParser object containing the response sent by the server.
- *
- */
- class SieveSimpleResponse {
-
- /**
- * Initializes the simple response object.
- */
- constructor() {
- this.message = null;
- this.responseCode = null;
- this.response = null;
- }
-
- /**
- * Parses the server's status response. It indicates if the command succeeded or failed.
- *
- * @param {SieveAbstractResponseParser} parser
- * a SieveResponseParser object containing the response sent by the server.
- * @returns {SieveSimpleResponse}
- * a self reference
- */
- parse(parser) {
- /*
- * Examples for simple responses
- *
- * 'NO (0000) "Message"\r\n'
- * 'BYE (0000) {4+}\r\n1234\r\n'
- * 'NO \"Message\"\r\n'
- * 'BYE {4+}\r\n1234\r\n'
- * 'NO (0000)\r\n'
- */
-
- this.message = "";
- this.responseCode = [];
-
- // OK
- if (parser.startsWith(TOKEN_OK)) {
- this.response = RESPONSE_OK;
- parser.extract(TOKEN_OK.length);
- }
- // BYE
- else if (parser.startsWith(TOKEN_BYE)) {
- this.response = RESPONSE_BYE;
- parser.extract(TOKEN_BYE.length);
- }
- // NO
- else if (parser.startsWith(TOKEN_NO)) {
- this.response = RESPONSE_NO;
- parser.extract(TOKEN_NO.length);
- }
- else
- throw new Error("NO, OK or BYE expected in " + parser.getData());
-
- // is there a Message?
- if (parser.isLineBreak()) {
- parser.extractLineBreak();
- return this;
- }
-
- // remove the space
- parser.extractSpace();
-
- // we found "(" so we got an responseCode, they are extremely ugly...
- if (parser.startsWith([[CHAR_BRACKET_OPEN]])) {
- // remove the opening bracket...
- parser.extract(ONE_CHAR);
- // ... but remember it
- let nesting = 0;
-
- // According to the RFC the first tag must be always an atom, but in...
- // ... reality this is not true. Cyrus servers send it as a string
- if (parser.isString())
- this.responseCode.push(parser.extractString());
- else
- this.responseCode.push(parser.extractToken([CHAR_SPACE, CHAR_BRACKET_CLOSE]));
-
- while (parser.isSpace()) {
- parser.extractSpace();
-
- // We might stumble upon opening brackets...
- if (parser.startsWith([[CHAR_BRACKET_OPEN]])) {
- // ... oh we did, so increase our nesting counter.
- parser.extract(ONE_CHAR);
- nesting++;
- }
-
- // ok, more tokens, more fun...
- // ... it could be either a string, a number, an atom or even a bracket
- if (parser.isString())
- this.responseCode.push(parser.extractString());
- else
- this.responseCode.push(parser.extractToken([CHAR_SPACE, CHAR_BRACKET_CLOSE]));
-
- // is it a closing bracket
- if (parser.startsWith([[CHAR_BRACKET_CLOSE]]) && nesting) {
- parser.extract(ONE_CHAR);
- nesting--;
- }
- }
-
- if (!parser.startsWith([[CHAR_BRACKET_CLOSE]]))
- throw new Error("Closing brackets expected in " + parser.getData());
-
- parser.extract(ONE_CHAR);
-
- if (parser.isLineBreak()) {
- parser.extractLineBreak();
- return this;
- }
-
- parser.extractSpace();
- }
-
- this.message = parser.extractString();
-
- parser.extractLineBreak();
-
- return this;
- }
-
- /**
- * The server may return a human readable (error) message
- * @returns {string}
- * the human readable message
- */
- getMessage() {
- if ((typeof (this.message) === 'undefined') || (this.message === null))
- throw new Error("Message not Initialized");
-
- return this.message;
- }
-
- /**
- * Checks if the request failed. In this case the server returns an error
- * instead of the expected response.
- * @returns {boolean}
- * true in case the request succeeded, false in case it failed due to an error.
- */
- hasError() {
- if ((typeof (this.response) === 'undefined') || (this.response === null))
- throw new Error("response not Initialized");
-
- if (this.response === RESPONSE_OK)
- return false;
-
- return true;
- }
-
- /**
- * The server responds to a message with either an ok, bye or no.
- *
- * @returns {int}
- * the servers response. It is set to 0 in case of an OK, to 1 in case of a BYE and to 3 incase of a NO
- */
- getResponse() {
- return this.response;
- }
-
- /**
- * A response code is used by the server to narrow down an error or to give hints.
- *
- * E.g.: In case the server wants to do a referral. It will answer a request with a BYE
- * and adds the details to the REFERRAL response code.
- *
- * Or in case the user tries do delete the active script. Then the server responds with
- * a NO and will an response code "ACTIVE".
- *
- * In case of putting a script to the server it may respond with an OK and a WARNING
- * response code. Which means the script contains warnings which should addressed by the
- * server.
- *
- * @returns {SieveResponseCode}
- * the response code for the current request.
- */
- getResponseCode() {
- if ((typeof (this.responseCode) === 'undefined') || (this.responseCode === null))
- throw new Error("Response Code not Initialized");
-
- let code = "";
- if (this.responseCode.length)
- code = this.responseCode[0].toUpperCase();
-
- switch (code) {
- case "REFERRAL":
- return new SieveResponseCodeReferral(this.responseCode);
-
- case "SASL":
- return new SieveResponseCodeSasl(this.responseCode);
- }
-
- // TODO Implement all of the Response codes:
- // "ACTIVE" / "NONEXISTENT" / "ALREADYEXISTS" / "WARNINGS" /AUTH-TOO-WEAK /TRANSITION-NEEDED /TRYLATER/ ENCRYPT-NEEDED / QUOTA / TAG
- return new SieveResponseCode(this.responseCode);
- }
- }
-
- /**
- * Parses the capabilities posted by the ManageSieve server upon a client
- * connection, after successful STARTTLS and AUTHENTICATE or by issuing the
- * CAPABILITY command.
- *
- * @see {SieveCapabilitiesRequest}
- *
- * @param {SieveResponseParser} parser
- * a parser containing the response sent by the server
- */
- class SieveCapabilitiesResponse extends SieveSimpleResponse {
-
- /**
- * @inheritdoc
- */
- constructor() {
-
- super();
-
- this.details = {
- implementation: null,
- version: 0,
-
- extensions: {},
- tls: false,
- sasl: {},
-
- maxredirects: MAX_REDIRECTS_UNLIMITED,
- owner: "",
- notify: {},
- language: "i-default",
-
- compatibility: {}
- };
- }
-
- /**
- * Parses the sieve extensions string. It is a space separated list of strings.
- * @param {string} value
- * the string which should be parsed
- * @returns {object.<string, boolean>}
- * a map with pairs of extension name and activation status.
- */
- parseSieveExtensions(value) {
- const extensions = value.split(" ");
- const result = {};
-
- for (let i = 0; i < extensions.length; ++i)
- result["" + extensions[i]] = true;
-
- return result;
- }
-
- /**
- * @inheritdoc
- */
- parse(parser) {
- while (parser.isString()) {
- const tag = parser.extractString();
-
- let value = "";
- if (parser.isLineBreak() === false) {
- parser.extractSpace();
- value = parser.extractString();
- }
-
- parser.extractLineBreak();
-
- switch (tag.toUpperCase()) {
- case "STARTTLS":
- this.details.tls = true;
- break;
- case "IMPLEMENTATION":
- this.details.implementation = value;
- break;
- case "SASL":
- this.details.sasl = value.split(" ");
- break;
- case "SIEVE":
- this.details.extensions = this.parseSieveExtensions(value);
- break;
- case "VERSION":
- this.details.version = parseFloat(value);
- if (this.details.version < SIEVE_VERSION_1)
- break;
-
- // Version 1.0 introduced rename, noop and checkscript
- this.details.compatibility.renamescript = true;
- this.details.compatibility.noop = true;
- this.details.compatibility.checkscript = true;
-
- break;
- case "MAXREDIRECTS":
- this.details.maxredirects = parseInt(value, 10);
- break;
- case "LANGUAGE":
- this.details.language = value;
- break;
- case "NOTIFY":
- this.details.notify = value.split(" ");
- break;
- case "OWNER":
- this.details.owner = value;
- break;
- case "RENAME":
- this.details.compatibility.renamescript = true;
- break;
- case "NOOP":
- this.details.compatibility.noop = true;
- break;
- }
- }
-
- if (this.details.implementation === null)
- throw new Error("Server did not provide an Implementation string.");
-
- return super.parse(parser);
- }
-
- /**
- * Returns a structure which contains all the details on the server's capabilities
- * like the implementation, version, extension, sasl mechanisms etc.
- *
- * @returns {object}
- * the object which the capabilities.
- */
- getDetails() { return this.details; }
-
- /**
- * Returns the servers implementation details.
- *
- * This is a custom string which typically identifies the server's implementation
- * as well a the version.
- *
- * You should never attempt to parse this string.
- *
- * @returns {string}
- * the servers implementation details
- */
- getImplementation() { return this.details.implementation; }
-
- /**
- * Returns the list of supported sasl mechanisms.
- *
- * They may change after a secure channel was established.
- * @returns {string}
- * the sasl mechanism
- */
- getSasl() {
- return this.details.sasl;
- }
-
- /**
- * Returns the server's supported sieve language extensions
- *
- * @param {boolean} [asString]
- * optional if true a string will be returned otherwise a
- * structure with key value pairs.
- *
- * @returns {object.<string,boolean>|string}
- * the server's supported extension.
- */
- getExtensions(asString) {
- if (!asString)
- return this.details.extensions;
-
- let result = "";
-
- for (const item in this.details.extensions)
- result += item + " ";
-
- return result;
- }
-
- /**
- * Indicates wether or not TLS is supported by this implementation.
- *
- * Note: After the command STARTTLS or AUTHENTICATE completes successfully, this
- * value is always false.
- *
- * @returns {boolean}
- * true if TLS is supported, false if not.
- */
- getTLS() { return this.details.tls; }
-
- /**
- * In order to maintain compatibility to older implementations, the servers
- * should state their compatibility level upon login.
- *
- * A value of "0" indicates, minimal ManageSieve support. This means the server
- * implements the commands AUTHENTICATE, STARTTLS, LOGOUT, CAPABILITY, HAVESPACE,
- * PUTSCRIPT, LISTSCRIPTS, SETACTIVE, GETSCRIPT and DELETESCRIPT
- *
- * A value of "1.0" adds to the minimal ManageSieve Support the commands
- * RENAMESCRIPT, CHECKSCRIPT and NOOP.
- *
- * @returns {float}
- * a positive float describing the compatibility level of the ManageSieve server.
- */
- getVersion() { return this.details.version; }
-
- /**
- * Returns the limit on the number of Sieve "redirect" actions a script can
- * perform during a single evaluation.
- *
- * Note, this is different from the total number of "redirect" actions a
- * script can contain.
- *
- * @returns {int}
- * a non-negative number of redirects, or -1 for infinite redirects
- */
- getMaxRedirects() { return this.details.maxredirects; }
-
- /**
- * Returns a string array of URI schema parts for supported notification
- * methods. This capability is be specified, if the Sieve implementation
- * supports the "enotify" extension.
- *
- * @returns {string[]}
- * The schema parts as string array
- */
- getNotify() { return this.details.notify; }
-
- /**
- * Returns the language currently used for human readable error messages.
- * If this capability is not returned, the "i-default" [RFC2277] language is
- * assumed.
- *
- * Note that the current language might be per-user configurable (i.e. it
- * might change after authentication)
- *
- * @returns {string}
- * a [RFC4646] conform language tag as string
- */
- getLanguage() { return this.details.language; }
-
- /**
- * Returns a list with sieve commands which are supported by this implementation
- * and are not part of the absolute minimal ManageSieve support.
- *
- * The server advertises such additional commands either by explicitly
- * naming the command or by using the compatibility level capability.
- *
- * Examples are RENAME, NOOP and CHECKSCRIPT.
- *
- * @returns {object}
- * an associative array containing additional sieve commands
- */
- getCompatibility() { return this.details.compatibility; }
-
- /**
- * Gets the name of the logged in user.
- *
- * Note: This value is only available after AUTHENTICATE command succeeds
- *
- * @returns {string}
- * a String containing the username
- */
- getOwner() { return this.details.owner; }
- }
-
-
-
- /**
- * Parses list script response.
- */
- class SieveListScriptsResponse extends SieveSimpleResponse {
-
- /**
- * @inheritdoc
- */
- parse(parser) {
- // sieve-name = string
- // string = quoted / literal
- // (sieve-name [SP "ACTIVE"] CRLF) response-oknobye
-
- const scripts = [];
- let i = -1;
-
- while (parser.isString()) {
- i++;
-
- scripts[i] = {};
- scripts[i].script = parser.extractString();
-
- if (parser.isLineBreak()) {
- scripts[i].active = false;
- parser.extractLineBreak();
-
- continue;
- }
-
- parser.extractSpace();
-
- if (parser.extractToken([CHAR_CR]).toUpperCase() !== "ACTIVE")
- throw new Error("Error \"ACTIVE\" expected");
-
- scripts[i].active = true;
- parser.extractLineBreak();
-
- }
-
- this.scripts = scripts;
-
- return super.parse(parser);
- }
-
-
- /**
- * An array of objects. Each object represents a script and
- * has at least a property named script which contains the
- * script name and a property named active which is either
- * true or false.
- *
- * @returns {object[]}
- * an array of objects with the name and activation state for each script
- */
- getScripts() {
- return this.scripts;
- }
- }
-
- /**
- * Parses a get script response which returns the content
- * of a script
- */
- class SieveGetScriptResponse extends SieveSimpleResponse {
-
- /**
- * Parses a get script response.
- *
- * It is perfectly fine for the server to return an empty script.
- *
- * @param {string} name
- * the script name, to simplify the handling as the server just returns the content.
- */
- constructor(name) {
- super();
- this.scriptName = name;
- }
-
- /**
- * @inheritdoc
- */
- parse(parser) {
- let body = "";
- // [(<"> *1024QUOTED-CHAR <">) / ("{" number "+}" CRLF *OCTET) CRLF] response-oknobye
- if (parser.isString()) {
- body = parser.extractString();
- parser.extractLineBreak();
- }
-
- this.scriptBody = body;
-
- return super.parse(parser);
- }
-
- /**
- * Contains the requested sieve script.
- * Keep in mind scripts can't be locked, so several clients may manipulate
- * a script at the same time.
- *
- * @returns {string} returns the requested script's content
- */
- getScriptBody() { return this.scriptBody; }
-
- /**
- * Returns the requested script's name,
- *
- * @returns {string} the script name.
- */
- getScriptName() { return this.scriptName; }
- }
-
-
- /**
- * In contrast to a simple sieve request most of the sasl
- * requests are more complex and require multiple round trips
- * to be completed. Which means the response has to track
- * the state.
- *
- * This simple wrapper makes the response stateful.
- */
- class SieveStateFullResponse extends SieveSimpleResponse {
-
- /**
- * @inheritdoc
- */
- constructor() {
- super();
- this.state = 0;
- }
-
- /**
- * Gets the responses current state
- *
- * @returns {int}
- * the current state as integer
- */
- getState() { return this.state; }
-
- }
-
- const STATE_LOGIN_USERNAME = 0;
- const STATE_LOGIN_PASSWORD = 1;
- const STATE_LOGIN_VERIFICATION = 2;
- const STATE_LOGIN_COMPLETED = 4;
-
- /**
- * SASL Login responses consist of multiple responses and requests.
- *
- * This means you need to call the parse method unless you reach the desired
- * state in the state engine or unless an exception is thrown.
- *
- * The state can be retrieved by calling getState.
- */
- class SieveSaslLoginResponse extends SieveStateFullResponse {
-
- /**
- * @inheritdoc
- */
- parse(parser) {
-
- if ((this.state === STATE_LOGIN_USERNAME) && (parser.isString())) {
- // String should be 'Username:' or something similar
- parser.extractString();
- parser.extractLineBreak();
-
- this.state = STATE_LOGIN_PASSWORD;
- return this;
- }
-
- if ((this.state === STATE_LOGIN_PASSWORD) && (parser.isString())) {
- // String should be equivalent to 'Password:'
- parser.extractString();
- parser.extractLineBreak();
-
- this.state = STATE_LOGIN_VERIFICATION;
- return this;
- }
-
- if (this.state === STATE_LOGIN_VERIFICATION) {
- // Should be either a NO, BYE or OK
- this.state = STATE_LOGIN_COMPLETED;
-
- super.parse(parser);
- return this;
- }
-
- // is it an error message?
- try {
- super.parse(parser);
- }
- catch (ex) {
- throw new Error(`Illegal State: ${this.state} / ${parser.getData(0)}\n${ex}`);
- }
-
- this.state = STATE_LOGIN_COMPLETED;
- return this;
- }
- }
-
- const STATE_CRAMMD5_INITIATED = 0;
- const STATE_CRAMMD5_CHALLENGED = 1;
- const STATE_CRAMMD5_COMPLETED = 4;
-
- /**
- * @author Thomas Schmid
- * @author Max Dittrich
- */
- class SieveSaslCramMd5Response extends SieveStateFullResponse {
-
- /**
- * @inheritdoc
- */
- parse(parser) {
-
- if ((this.state === STATE_CRAMMD5_INITIATED) && (parser.isString())) {
- // The challenge is contained within a string
- this.challenge = parser.extractString();
- parser.extractLineBreak();
-
- this.state = STATE_CRAMMD5_CHALLENGED;
-
- return this;
- }
-
- if (this.state === STATE_CRAMMD5_CHALLENGED) {
- // Should be either a NO, BYE or OK
- this.state = STATE_CRAMMD5_COMPLETED;
-
- // Invoke the parent parser to consume the rest of the message
- super.parse(parser);
- return this;
- }
-
- throw new Error(`Illegal State: ${this.state} / ${parser.getData()}`);
- }
-
- /**
- * Gets the challenge returned by the server.
- *
- * @returns {string}
- * the server's challenge which needs to be answered.
- */
- getChallenge() {
- if (this.state < STATE_CRAMMD5_CHALLENGED)
- throw new Error("Illegal State, request not completed");
-
- return this.challenge;
- }
- }
-
- const SHA_STATE_FIRST_MESSAGE = 0;
- const SHA_STATE_FINAL_MESSAGE = 1;
- const SHA_STATE_COMPLETED = 4;
-
- const SHA_FIRST_TOKEN = 0;
- const SHA_PREFIX_LENGTH = 2;
-
- /**
- * Parses responses for SCRAM-SHA authentication.
- *
- * SCRAM is a secure client first authentication mechanism. The client
- * challenges the server and deicides if the connection is trustworthy.
- *
- * This requires a way mor logic on the client than with simple authentication
- * mechanisms. It also requires more communication, in total two roundtrips.
- */
- class SieveSaslScramShaResponse extends SieveStateFullResponse {
-
- /**
- * Extracts the reserved-mext token from the array of tokens.
- * @param {string[]} tokens
- * the first message response split into tokens.
- * @returns {string}
- * the optional reserved-mext token or an empty string.
- */
- _extractReservedMext(tokens) {
- const token = tokens[SHA_FIRST_TOKEN];
-
- // Test for the reserved-mext token. If it is existent, we just skip it
- if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("m="))
- return "";
-
- tokens.shift();
- return token.substr(SHA_FIRST_TOKEN);
- }
-
- /**
- * Extracts the nonce from the first message.
- * @param {string[]} tokens
- * the first message response split into tokens.
- * @returns {string}
- * the nonce or an exception in case it could not be extracted.
- */
- _extractNonce(tokens) {
- const token = tokens[SHA_FIRST_TOKEN];
-
- // Extract the nonce
- if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("r="))
- throw new Error(`Nonce expected but got ${token}`);
-
- tokens.shift();
-
- // remove the "r="
- return token.substr(SHA_PREFIX_LENGTH);
- }
-
- /**
- * Extracts the salt from the first message.
- * @param {string[]} tokens
- * the first message response split into tokens.
- * @returns {string}
- * the salt as base64 encoded string or an exception in case
- * it could not be extracted
- */
- _extractSalt(tokens) {
- const token = tokens[SHA_FIRST_TOKEN];
-
- if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("s="))
- throw new Error(`Salt expected but got ${token}`);
-
- tokens.shift();
-
- // remove the "s=""
- return token.substr(SHA_PREFIX_LENGTH);
- }
-
- /**
- * Extracts the iteration count from the first message.
- * @param {string[]} tokens
- * the first message response split into tokens.
- * @returns {int}
- * the iteration count as integer or an exception in case the
- * iterations could not be extracted.
- */
- _extractIterations(tokens) {
- const token = tokens[SHA_FIRST_TOKEN];
-
- if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("i="))
- throw new Error(`Iteration Count expected but got ${token}`);
-
- tokens.shift();
-
- // Remove the prefix and convert to an integer
- return parseInt(token.substr(SHA_PREFIX_LENGTH), 10);
- }
-
- /**
- * Parses the server-first-message it is defined to be:
- * [reserved-mext ","] nonce "," salt "," iteration-count ["," extensions]
- *
- * Where
- * reserved-mext : "m=" 1*(value-char)
- * nonce : "r=" c-nonce
- * salt : "s=" base64(salt)
- * iteration-count : "i=" posit-number
- *
- * Extensions are optional and for future use.
- * Neither c-nonce nor salt can contain a "," character
- *
- * @param {SieveResponseParser} parser
- * the response parser which contains the response to be parsed.
- *
- *
- * @private
- */
- _parseFirstMessage(parser) {
- this._serverFirstMessage = parser.convertFromBase64(parser.extractString());
-
- const tokens = this._serverFirstMessage.split(',');
-
- this._extractReservedMext(tokens);
- this._nonce = this._extractNonce(tokens);
-
- this._salt = parser.convertFromBase64(this._extractSalt(tokens));
- this._iter = this._extractIterations(tokens);
- }
-
- /**
- * Parses the server-final-message.
- *
- * It is defined to be:
- * (server-error / verifier) ["," extensions]
- *
- * Where
- * server-error : "e=" server-error-value
- * verifier : "v=" base64(ServerSignature)
- *
- * Extensions are optional and for future use.
- * As suggested by the RFC they will be ignored
- *
- * @param {SieveResponseParser} parser
- * the parser which should be to process the message.
- * @param {string} [data]
- * optional, the server's final message. It omitted it
- * will be parsed from the response.
- *
- *
- * @private
- */
- _parseFinalMessage(parser, data) {
-
- if (typeof (data) === "undefined" || data === null)
- data = parser.extractString();
-
- // server-final-message = (server-error / verifier) ["," extensions]
- const token = parser.convertFromBase64(data).split(",")[SHA_FIRST_TOKEN];
-
- if (token.length <= SHA_PREFIX_LENGTH)
- throw new Error(`Response expected but got: ${data}`);
-
- // server-error = "e="
- if (token.startsWith("e=")) {
- this._serverError = token.substr(SHA_PREFIX_LENGTH);
- return;
- }
-
- // verifier = "v=" base64
- if (token.startsWith("v=")) {
- this._verifier = parser.convertFromBase64(token.substr(SHA_PREFIX_LENGTH));
- return;
- }
-
- throw new Error("Invalid Final message");
- }
-
- /**
- * @inheritdoc
- */
- parse(parser) {
-
- if ((this.state === SHA_STATE_FIRST_MESSAGE) && (parser.isString())) {
- this._parseFirstMessage(parser);
- parser.extractLineBreak();
-
- this.state = SHA_STATE_FINAL_MESSAGE;
-
- return this;
- }
-
-
- // There are two valid responses...
- // ... either the Server sends us something like that:
- //
- // S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
- // C: ""
- // S: OK
-
- if ((this.state === SHA_STATE_FINAL_MESSAGE) && (parser.isString())) {
-
- this._parseFinalMessage(parser);
- parser.extractLineBreak();
-
- this.state = 2;
-
- return this;
- }
-
- if (this.state === 2) {
- super.parse(parser);
- this.state = SHA_STATE_COMPLETED;
- return this;
- }
-
- // Or the response is wrapped into the ResponseCode in order to save...
- // ... roundtrip time so we end up with the following
- //
- // S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==")
-
- if (this.state === SHA_STATE_FINAL_MESSAGE) {
- super.parse(parser);
-
- this._parseFinalMessage(parser, this.getResponseCode().getSasl(), parser);
-
- this.state = SHA_STATE_COMPLETED;
-
- return this;
- }
-
- throw new Error(`Illegal State: ${this.state} / ${parser.getData()}`);
- }
-
- /**
- * The salt is transferred with the first message and used to
- * randomize the SHA request
- *
- * @returns {string}
- * the salt
- */
- getSalt() {
- if (this.state < SHA_STATE_FINAL_MESSAGE)
- throw new Error("Illegal State, request not completed");
-
- return this._salt;
- }
-
- /**
- * @returns {int}
- * the number of iterations.
- */
- getIterationCounter() {
- if (this.state < SHA_STATE_FINAL_MESSAGE)
- throw new Error("Illegal State, request not completed");
-
- return this._iter;
- }
-
- /**
- * Returns the nonce. Please note this it is only available after the
- * final message was received
- *
- * @returns {string}
- * the nonce received from the server on an exception in case it is unknown.
- */
- getNonce() {
- if (this.state < SHA_STATE_FINAL_MESSAGE)
- throw new Error("Illegal State, request not completed");
-
- return this._nonce;
- }
-
- /**
- * Returns the servers first message. Pleas not is tis only available when the
- * final message was received.
- *
- * @returns {string}
- * the server's first message or an exception in case it is unknown.
- */
- getServerFirstMessage() {
- if (this.state < SHA_STATE_FINAL_MESSAGE)
- throw new Error("Illegal State, request not completed");
-
- return this._serverFirstMessage;
- }
-
- /**
- *
- */
- getServerError() {
- if (this.state < 2)
- throw new Error("Illegal State, request not completed");
-
- return this._serverError;
- }
-
- /**
- * The server's signature which needs to be verified.
- *
- * @returns {string}
- * the server's signature
- */
- getVerifier() {
- if (this.state < 2)
- throw new Error("Illegal State, request not completed");
-
- return this._verifier;
- }
- }
-
- exports.SieveSimpleResponse = SieveSimpleResponse;
- exports.SieveCapabilitiesResponse = SieveCapabilitiesResponse;
- exports.SieveListScriptsResponse = SieveListScriptsResponse;
- exports.SieveSaslLoginResponse = SieveSaslLoginResponse;
- exports.SieveSaslCramMd5Response = SieveSaslCramMd5Response;
- exports.SieveGetScriptResponse = SieveGetScriptResponse;
- exports.SieveSaslScramShaResponse = SieveSaslScramShaResponse;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveResponse.mjs b/src/common/libManageSieve/SieveResponse.mjs
new file mode 100644
index 00000000..6af83afe
--- /dev/null
+++ b/src/common/libManageSieve/SieveResponse.mjs
@@ -0,0 +1,1103 @@
+/*
+ * The contents of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via email
+ * from the author. Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ *
+ * Contributors:
+ * Max Dittrich
+ */
+
+import {
+ SieveResponseCode,
+ SieveResponseCodeSasl,
+ SieveResponseCodeReferral
+} from "./SieveResponseCodes.mjs";
+
+import { SieveBase64Decoder } from "./SieveBase64.mjs";
+
+const CHAR_LOWERCASE_B = 66;
+const CHAR_UPPERCASE_B = 98;
+const CHAR_B = [CHAR_LOWERCASE_B, CHAR_UPPERCASE_B];
+
+const CHAR_LOWERCASE_E = 69;
+const CHAR_UPPERCASE_E = 101;
+const CHAR_E = [CHAR_LOWERCASE_E, CHAR_UPPERCASE_E];
+
+const CHAR_LOWERCASE_N = 78;
+const CHAR_UPPERCASE_N = 110;
+const CHAR_N = [CHAR_LOWERCASE_N, CHAR_UPPERCASE_N];
+
+const CHAR_LOWERCASE_O = 79;
+const CHAR_UPPERCASE_O = 111;
+const CHAR_O = [CHAR_LOWERCASE_O, CHAR_UPPERCASE_O];
+
+const CHAR_LOWERCASE_K = 75;
+const CHAR_UPPERCASE_K = 107;
+const CHAR_K = [CHAR_LOWERCASE_K, CHAR_UPPERCASE_K];
+
+const CHAR_LOWERCASE_Y = 89;
+const CHAR_UPPERCASE_Y = 121;
+const CHAR_Y = [CHAR_LOWERCASE_Y, CHAR_UPPERCASE_Y];
+
+const CHAR_BRACKET_OPEN = 40;
+const CHAR_BRACKET_CLOSE = 41;
+const CHAR_SPACE = 32;
+const CHAR_CR = 13;
+
+const TOKEN_OK = [CHAR_O, CHAR_K];
+const TOKEN_BYE = [CHAR_B, CHAR_Y, CHAR_E];
+const TOKEN_NO = [CHAR_N, CHAR_O];
+
+const SIEVE_VERSION_1 = 1;
+
+const ONE_CHAR = 1;
+
+const RESPONSE_UNKNOWN = -1;
+const RESPONSE_OK = 0;
+const RESPONSE_BYE = 1;
+const RESPONSE_NO = 2;
+
+const MAX_REDIRECTS_UNLIMITED = -1;
+
+/**
+ * This class implements a generic response handler for simple sieve requests.
+ *
+ * Simple requests just indicate, wether the command succeeded or not. They
+ * return only status information, and do not contain any data relevant for
+ * the user.
+ *
+ * @see SieveResponseParser
+ *
+ * @param {SieveResponseParser} [parser]
+ * a SieveResponseParser object containing the response sent by the server.
+ *
+ */
+class SieveSimpleResponse {
+
+ /**
+ * Initializes the simple response object.
+ */
+ constructor() {
+ this.message = null;
+ this.responseCode = null;
+ this.response = RESPONSE_UNKNOWN;
+ }
+
+ /**
+ * Parses the server's status response. It indicates if the command succeeded or failed.
+ *
+ * @param {SieveResponseParser} parser
+ * a SieveResponseParser object containing the response sent by the server.
+ * @returns {SieveSimpleResponse}
+ * a self reference
+ */
+ parse(parser) {
+ /*
+ * Examples for simple responses
+ *
+ * 'NO (0000) "Message"\r\n'
+ * 'BYE (0000) {4+}\r\n1234\r\n'
+ * 'NO \"Message\"\r\n'
+ * 'BYE {4+}\r\n1234\r\n'
+ * 'NO (0000)\r\n'
+ */
+
+ this.setMessage("");
+ this.responseCode = [];
+
+ // OK
+ if (parser.startsWith(TOKEN_OK)) {
+ this.setResponse(RESPONSE_OK);
+ parser.extract(TOKEN_OK.length);
+ }
+ // BYE
+ else if (parser.startsWith(TOKEN_BYE)) {
+ this.setResponse(RESPONSE_BYE);
+ parser.extract(TOKEN_BYE.length);
+ }
+ // NO
+ else if (parser.startsWith(TOKEN_NO)) {
+ this.setResponse(RESPONSE_NO);
+ parser.extract(TOKEN_NO.length);
+ }
+ else
+ throw new Error("NO, OK or BYE expected in " + parser.getData());
+
+ // is there a Message?
+ if (parser.isLineBreak()) {
+ parser.extractLineBreak();
+ return this;
+ }
+
+ // remove the space
+ parser.extractSpace();
+
+ // we found "(" so we got an responseCode, they are extremely ugly...
+ if (parser.startsWith([[CHAR_BRACKET_OPEN]])) {
+ // remove the opening bracket...
+ parser.extract(ONE_CHAR);
+ // ... but remember it
+ let nesting = 0;
+
+ // According to the RFC the first tag must be always an atom, but in...
+ // ... reality this is not true. Cyrus servers send it as a string
+ if (parser.isString())
+ this.responseCode.push(parser.extractString());
+ else
+ this.responseCode.push(parser.extractToken([CHAR_SPACE, CHAR_BRACKET_CLOSE]));
+
+ while (parser.isSpace()) {
+ parser.extractSpace();
+
+ // We might stumble upon opening brackets...
+ if (parser.startsWith([[CHAR_BRACKET_OPEN]])) {
+ // ... oh we did, so increase our nesting counter.
+ parser.extract(ONE_CHAR);
+ nesting++;
+ }
+
+ // ok, more tokens, more fun...
+ // ... it could be either a string, a number, an atom or even a bracket
+ if (parser.isString())
+ this.responseCode.push(parser.extractString());
+ else
+ this.responseCode.push(parser.extractToken([CHAR_SPACE, CHAR_BRACKET_CLOSE]));
+
+ // is it a closing bracket
+ if (parser.startsWith([[CHAR_BRACKET_CLOSE]]) && nesting) {
+ parser.extract(ONE_CHAR);
+ nesting--;
+ }
+ }
+
+ if (!parser.startsWith([[CHAR_BRACKET_CLOSE]]))
+ throw new Error("Closing brackets expected in " + parser.getData());
+
+ parser.extract(ONE_CHAR);
+
+ if (parser.isLineBreak()) {
+ parser.extractLineBreak();
+ return this;
+ }
+
+ parser.extractSpace();
+ }
+
+ this.setMessage(parser.extractString());
+ parser.extractLineBreak();
+
+ return this;
+ }
+
+ /**
+ * The server may return a human readable (error) message
+ *
+ * @returns {string}
+ * the human readable message
+ */
+ getMessage() {
+ if ((typeof (this.message) === 'undefined') || (this.message === null))
+ throw new Error("Message not initialized");
+
+ return this.message;
+ }
+
+ /**
+ * Set or changes the server's response message.
+ *
+ * @param {string} [message]
+ * the optional message
+ *
+ * @returns {SieveSimpleResponse}
+ * a self reference
+ */
+ setMessage(message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * Checks if the request failed. In this case the server returns an error
+ * instead of the expected response.
+ *
+ * @returns {boolean}
+ * true in case the request succeeded, false in case it failed due to an error.
+ */
+ hasError() {
+ if (this.response === RESPONSE_UNKNOWN)
+ return false;
+
+ if (this.response === RESPONSE_OK)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * The server responds to a message with either an ok, bye or no.
+ *
+ * @returns {int}
+ * the servers response. It is set to 0 in case of an OK, to 1 in case of a BYE and to 3 incase of a NO
+ */
+ getResponse() {
+ if (this.response === RESPONSE_UNKNOWN)
+ throw new Error("Response not initialized");
+
+ return this.response;
+ }
+
+ /**
+ * Set or changes the server's response.
+ *
+ * @param {int} response
+ * the server's response code
+ *
+ * @returns {SieveSimpleResponse}
+ * a self reference
+ */
+ setResponse(response) {
+ this.response = response;
+ return this;
+ }
+
+ /**
+ * A response code is used by the server to narrow down an error or to give hints.
+ *
+ * E.g.: In case the server wants to do a referral. It will answer a request with a BYE
+ * and adds the details to the REFERRAL response code.
+ *
+ * Or in case the user tries do delete the active script. Then the server responds with
+ * a NO and will an response code "ACTIVE".
+ *
+ * In case of putting a script to the server it may respond with an OK and a WARNING
+ * response code. Which means the script contains warnings which should addressed by the
+ * server.
+ *
+ * @returns {SieveResponseCode}
+ * the response code for the current request.
+ */
+ getResponseCode() {
+ if ((typeof (this.responseCode) === 'undefined') || (this.responseCode === null))
+ throw new Error("Response Code not Initialized");
+
+ let code = "";
+ if (this.responseCode.length)
+ code = this.responseCode[0].toUpperCase();
+
+ switch (code) {
+ case "REFERRAL":
+ return new SieveResponseCodeReferral(this.responseCode);
+
+ case "SASL":
+ return new SieveResponseCodeSasl(this.responseCode);
+ }
+
+ // TODO Implement all of the Response codes:
+ // "ACTIVE" / "NONEXISTENT" / "ALREADYEXISTS" / "WARNINGS" /AUTH-TOO-WEAK /TRANSITION-NEEDED /TRYLATER/ ENCRYPT-NEEDED / QUOTA / TAG
+ return new SieveResponseCode(this.responseCode);
+ }
+}
+
+/**
+ * Parses the capabilities posted by the ManageSieve server upon a client
+ * connection, after successful STARTTLS and AUTHENTICATE or by issuing the
+ * CAPABILITY command.
+ *
+ * @see {SieveCapabilitiesRequest}
+ *
+ * @param {SieveResponseParser} parser
+ * a parser containing the response sent by the server
+ */
+class SieveCapabilitiesResponse extends SieveSimpleResponse {
+
+ /**
+ * @inheritdoc
+ */
+ constructor() {
+
+ super();
+
+ this.details = {
+ implementation: null,
+ version: 0,
+
+ extensions: {},
+ tls: false,
+ sasl: {},
+
+ maxredirects: MAX_REDIRECTS_UNLIMITED,
+ owner: "",
+ notify: {},
+ language: "i-default",
+
+ compatibility: {}
+ };
+ }
+
+ /**
+ * Parses the sieve extensions string. It is a space separated list of strings.
+ * @param {string} value
+ * the string which should be parsed
+ * @returns {object.<string, boolean>}
+ * a map with pairs of extension name and activation status.
+ */
+ parseSieveExtensions(value) {
+ const extensions = value.split(" ");
+ const result = {};
+
+ for (let i = 0; i < extensions.length; ++i)
+ result["" + extensions[i]] = true;
+
+ return result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ parse(parser) {
+
+ while (parser.isString()) {
+ const tag = parser.extractString();
+
+ let value = "";
+ if (parser.isLineBreak() === false) {
+ parser.extractSpace();
+ value = parser.extractString();
+ }
+
+ parser.extractLineBreak();
+
+ switch (tag.toUpperCase()) {
+ case "STARTTLS":
+ this.details.tls = true;
+ break;
+ case "IMPLEMENTATION":
+ this.details.implementation = value;
+ break;
+ case "SASL":
+ this.details.sasl = value.split(" ");
+ break;
+ case "SIEVE":
+ this.details.extensions = this.parseSieveExtensions(value);
+ break;
+ case "VERSION":
+ this.details.version = Number.parseFloat(value);
+ if (this.details.version < SIEVE_VERSION_1)
+ break;
+
+ // Version 1.0 introduced rename, noop and checkscript
+ this.details.compatibility.renamescript = true;
+ this.details.compatibility.noop = true;
+ this.details.compatibility.checkscript = true;
+
+ break;
+ case "MAXREDIRECTS":
+ this.details.maxredirects = Number.parseInt(value, 10);
+ break;
+ case "LANGUAGE":
+ this.details.language = value;
+ break;
+ case "NOTIFY":
+ this.details.notify = value.split(" ");
+ break;
+ case "OWNER":
+ this.details.owner = value;
+ break;
+ case "RENAME":
+ this.details.compatibility.renamescript = true;
+ break;
+ case "NOOP":
+ this.details.compatibility.noop = true;
+ break;
+ }
+ }
+
+ super.parse(parser);
+
+ // IMPLEMENTATION and SIEVE are mandatory fields in case of an OK Response
+ // But they do not exist in case of a BYE or NO.
+ if (this.hasError())
+ return this;
+
+ if (this.details.implementation === null)
+ throw new Error("Invalid capability response, no key named IMPLEMENTATION.");
+
+ if (this.details.sieve === null)
+ throw new Error("Invalid capability response, no key named SIEVE.");
+
+ return this;
+ }
+
+ /**
+ * Returns a structure which contains all the details on the server's capabilities
+ * like the implementation, version, extension, sasl mechanisms etc.
+ *
+ * @returns {object}
+ * the object which the capabilities.
+ */
+ getDetails() { return this.details; }
+
+ /**
+ * Returns the servers implementation details.
+ *
+ * This is a custom string which typically identifies the server's implementation
+ * as well a the version.
+ *
+ * You should never attempt to parse this string.
+ *
+ * @returns {string}
+ * the servers implementation details
+ */
+ getImplementation() { return this.details.implementation; }
+
+ /**
+ * Returns the list of supported sasl mechanisms.
+ *
+ * They may change after a secure channel was established.
+ * @returns {string}
+ * the sasl mechanism
+ */
+ getSasl() {
+ return this.details.sasl;
+ }
+
+ /**
+ * Returns the server's supported sieve language extensions
+ *
+ * @param {boolean} [asString]
+ * optional if true a string will be returned otherwise a
+ * structure with key value pairs.
+ *
+ * @returns {object.<string,boolean>|string}
+ * the server's supported extension.
+ */
+ getExtensions(asString) {
+ if (!asString)
+ return this.details.extensions;
+
+ let result = "";
+
+ for (const item in this.details.extensions)
+ result += item + " ";
+
+ return result;
+ }
+
+ /**
+ * Indicates wether or not TLS is supported by this implementation.
+ *
+ * Note: After the command STARTTLS or AUTHENTICATE completes successfully, this
+ * value is always false.
+ *
+ * @returns {boolean}
+ * true if TLS is supported, false if not.
+ */
+ getTLS() { return this.details.tls; }
+
+ /**
+ * In order to maintain compatibility to older implementations, the servers
+ * should state their compatibility level upon login.
+ *
+ * A value of "0" indicates, minimal ManageSieve support. This means the server
+ * implements the commands AUTHENTICATE, STARTTLS, LOGOUT, CAPABILITY, HAVESPACE,
+ * PUTSCRIPT, LISTSCRIPTS, SETACTIVE, GETSCRIPT and DELETESCRIPT
+ *
+ * A value of "1.0" adds to the minimal ManageSieve Support the commands
+ * RENAMESCRIPT, CHECKSCRIPT and NOOP.
+ *
+ * @returns {float}
+ * a positive float describing the compatibility level of the ManageSieve server.
+ */
+ getVersion() { return this.details.version; }
+
+ /**
+ * Returns the limit on the number of Sieve "redirect" actions a script can
+ * perform during a single evaluation.
+ *
+ * Note, this is different from the total number of "redirect" actions a
+ * script can contain.
+ *
+ * @returns {int}
+ * a non-negative number of redirects, or -1 for infinite redirects
+ */
+ getMaxRedirects() { return this.details.maxredirects; }
+
+ /**
+ * Returns a string array of URI schema parts for supported notification
+ * methods. This capability is be specified, if the Sieve implementation
+ * supports the "enotify" extension.
+ *
+ * @returns {string[]}
+ * The schema parts as string array
+ */
+ getNotify() { return this.details.notify; }
+
+ /**
+ * Returns the language currently used for human readable error messages.
+ * If this capability is not returned, the "i-default" [RFC2277] language is
+ * assumed.
+ *
+ * Note that the current language might be per-user configurable (i.e. it
+ * might change after authentication)
+ *
+ * @returns {string}
+ * a [RFC4646] conform language tag as string
+ */
+ getLanguage() { return this.details.language; }
+
+ /**
+ * Returns a list with sieve commands which are supported by this implementation
+ * and are not part of the absolute minimal ManageSieve support.
+ *
+ * The server advertises such additional commands either by explicitly
+ * naming the command or by using the compatibility level capability.
+ *
+ * Examples are RENAME, NOOP and CHECKSCRIPT.
+ *
+ * @returns {object}
+ * an associative array containing additional sieve commands
+ */
+ getCompatibility() { return this.details.compatibility; }
+
+ /**
+ * Gets the name of the logged in user.
+ *
+ * Note: This value is only available after AUTHENTICATE command succeeds
+ *
+ * @returns {string}
+ * a String containing the username
+ */
+ getOwner() { return this.details.owner; }
+}
+
+
+
+/**
+ * Parses list script response.
+ */
+class SieveListScriptsResponse extends SieveSimpleResponse {
+
+ /**
+ * @inheritdoc
+ */
+ parse(parser) {
+ // sieve-name = string
+ // string = quoted / literal
+ // (sieve-name [SP "ACTIVE"] CRLF) response-oknobye
+
+ const scripts = [];
+ let i = -1;
+
+ while (parser.isString()) {
+ i++;
+
+ scripts[i] = {};
+ scripts[i].script = parser.extractString();
+
+ if (parser.isLineBreak()) {
+ scripts[i].active = false;
+ parser.extractLineBreak();
+
+ continue;
+ }
+
+ parser.extractSpace();
+
+ if (parser.extractToken([CHAR_CR]).toUpperCase() !== "ACTIVE")
+ throw new Error("Error \"ACTIVE\" expected");
+
+ scripts[i].active = true;
+ parser.extractLineBreak();
+
+ }
+
+ this.scripts = scripts;
+
+ return super.parse(parser);
+ }
+
+
+ /**
+ * An array of objects. Each object represents a script and
+ * has at least a property named script which contains the
+ * script name and a property named active which is either
+ * true or false.
+ *
+ * @returns {object[]}
+ * an array of objects with the name and activation state for each script
+ */
+ getScripts() {
+ return this.scripts;
+ }
+}
+
+/**
+ * Parses a get script response which returns the content
+ * of a script
+ */
+class SieveGetScriptResponse extends SieveSimpleResponse {
+
+ /**
+ * Parses a get script response.
+ *
+ * It is perfectly fine for the server to return an empty script.
+ *
+ * @param {string} name
+ * the script name, to simplify the handling as the server just returns the content.
+ */
+ constructor(name) {
+ super();
+ this.scriptName = name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ parse(parser) {
+ let body = "";
+ // [(<"> *1024QUOTED-CHAR <">) / ("{" number "+}" CRLF *OCTET) CRLF] response-oknobye
+ if (parser.isString()) {
+ body = parser.extractString();
+ parser.extractLineBreak();
+ }
+
+ this.scriptBody = body;
+
+ return super.parse(parser);
+ }
+
+ /**
+ * Contains the requested sieve script.
+ * Keep in mind scripts can't be locked, so several clients may manipulate
+ * a script at the same time.
+ *
+ * @returns {string} returns the requested script's content
+ */
+ getScriptBody() { return this.scriptBody; }
+
+ /**
+ * Returns the requested script's name,
+ *
+ * @returns {string} the script name.
+ */
+ getScriptName() { return this.scriptName; }
+}
+
+
+/**
+ * In contrast to a simple sieve request most of the sasl
+ * requests are more complex and require multiple round trips
+ * to be completed. Which means the response has to track
+ * the state.
+ *
+ * This simple wrapper makes the response stateful.
+ */
+class SieveStateFullResponse extends SieveSimpleResponse {
+
+ /**
+ * @inheritdoc
+ */
+ constructor() {
+ super();
+ this.state = 0;
+ }
+
+ /**
+ * Gets the responses current state
+ *
+ * @returns {int}
+ * the current state as integer
+ */
+ getState() { return this.state; }
+
+}
+
+const STATE_LOGIN_USERNAME = 0;
+const STATE_LOGIN_PASSWORD = 1;
+const STATE_LOGIN_VERIFICATION = 2;
+const STATE_LOGIN_COMPLETED = 4;
+
+/**
+ * SASL Login responses consist of multiple responses and requests.
+ *
+ * This means you need to call the parse method unless you reach the desired
+ * state in the state engine or unless an exception is thrown.
+ *
+ * The state can be retrieved by calling getState.
+ */
+class SieveSaslLoginResponse extends SieveStateFullResponse {
+
+ /**
+ * @inheritdoc
+ */
+ parse(parser) {
+
+ if ((this.state === STATE_LOGIN_USERNAME) && (parser.isString())) {
+ // String should be 'Username:' or something similar
+ parser.extractString();
+ parser.extractLineBreak();
+
+ this.state = STATE_LOGIN_PASSWORD;
+ return this;
+ }
+
+ if ((this.state === STATE_LOGIN_PASSWORD) && (parser.isString())) {
+ // String should be equivalent to 'Password:'
+ parser.extractString();
+ parser.extractLineBreak();
+
+ this.state = STATE_LOGIN_VERIFICATION;
+ return this;
+ }
+
+ if (this.state === STATE_LOGIN_VERIFICATION) {
+ // Should be either a NO, BYE or OK
+ this.state = STATE_LOGIN_COMPLETED;
+
+ super.parse(parser);
+ return this;
+ }
+
+ // is it an error message?
+ try {
+ super.parse(parser);
+ }
+ catch (ex) {
+ throw new Error(`Illegal State: ${this.state} / ${parser.getData(0)}\n${ex}`);
+ }
+
+ this.state = STATE_LOGIN_COMPLETED;
+ return this;
+ }
+}
+
+const SHA_STATE_FIRST_MESSAGE = 0;
+const SHA_STATE_FINAL_MESSAGE = 1;
+const SHA_STATE_SIEVE_RESPONSE = 2;
+const SHA_STATE_COMPLETED = 4;
+
+const SHA_FIRST_TOKEN = 0;
+const SHA_PREFIX_LENGTH = 2;
+
+/**
+ * Parses responses for SCRAM-SHA authentication.
+ *
+ * SCRAM is a secure client first authentication mechanism. The client
+ * challenges the server and deicides if the connection is trustworthy.
+ *
+ * This requires a way mor logic on the client than with simple authentication
+ * mechanisms. It also requires more communication, in total two roundtrips.
+ */
+class SieveSaslScramShaResponse extends SieveStateFullResponse {
+
+ /**
+ * Extracts the reserved-mext token from the array of tokens.
+ * @param {string[]} tokens
+ * the first message response split into tokens.
+ * @returns {string}
+ * the optional reserved-mext token or an empty string.
+ */
+ _extractReservedMext(tokens) {
+ const token = tokens[SHA_FIRST_TOKEN];
+
+ // Test for the reserved-mext token. If it is existent, we just skip it
+ if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("m="))
+ return "";
+
+ tokens.shift();
+ return token.substr(SHA_FIRST_TOKEN);
+ }
+
+ /**
+ * Extracts the nonce from the first message.
+ * @param {string[]} tokens
+ * the first message response split into tokens.
+ * @returns {string}
+ * the nonce or an exception in case it could not be extracted.
+ */
+ _extractNonce(tokens) {
+ const token = tokens[SHA_FIRST_TOKEN];
+
+ // Extract the nonce
+ if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("r="))
+ throw new Error(`Nonce expected but got ${token}`);
+
+ tokens.shift();
+
+ // remove the "r="
+ return token.substr(SHA_PREFIX_LENGTH);
+ }
+
+ /**
+ * Extracts the salt from the first message.
+ * @param {string[]} tokens
+ * the first message response split into tokens.
+ * @returns {string}
+ * the salt as base64 encoded string or an exception in case
+ * it could not be extracted
+ */
+ _extractSalt(tokens) {
+ const token = tokens[SHA_FIRST_TOKEN];
+
+ if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("s="))
+ throw new Error(`Salt expected but got ${token}`);
+
+ tokens.shift();
+
+ // remove the "s=""
+ return token.substr(SHA_PREFIX_LENGTH);
+ }
+
+ /**
+ * Extracts the iteration count from the first message.
+ * @param {string[]} tokens
+ * the first message response split into tokens.
+ * @returns {int}
+ * the iteration count as integer or an exception in case the
+ * iterations could not be extracted.
+ */
+ _extractIterations(tokens) {
+ const token = tokens[SHA_FIRST_TOKEN];
+
+ if ((token.length <= SHA_PREFIX_LENGTH) || !token.startsWith("i="))
+ throw new Error(`Iteration Count expected but got ${token}`);
+
+ tokens.shift();
+
+ // Remove the prefix and convert to an integer
+ return Number.parseInt(token.substr(SHA_PREFIX_LENGTH), 10);
+ }
+
+ /**
+ * Parses the server-first-message it is defined to be:
+ * [reserved-mext ","] nonce "," salt "," iteration-count ["," extensions]
+ *
+ * Where
+ * reserved-mext : "m=" 1*(value-char)
+ * nonce : "r=" c-nonce
+ * salt : "s=" base64(salt)
+ * iteration-count : "i=" posit-number
+ *
+ * Extensions are optional and for future use.
+ * Neither c-nonce nor salt can contain a "," character
+ *
+ * @param {SieveResponseParser} parser
+ * the response parser which contains the response to be parsed.
+ *
+ *
+ * @private
+ */
+ _parseFirstMessage(parser) {
+
+ this._serverFirstMessage =
+ (new SieveBase64Decoder(parser.extractString())).toUtf8();
+
+ const tokens = this._serverFirstMessage.split(',');
+
+ this._extractReservedMext(tokens);
+ this._nonce = this._extractNonce(tokens);
+
+ this._salt = (new SieveBase64Decoder(this._extractSalt(tokens))).toArray();
+ this._iter = this._extractIterations(tokens);
+ }
+
+ /**
+ * Parses the server-final-message.
+ *
+ * It is defined to be:
+ * (server-error / verifier) ["," extensions]
+ *
+ * Where
+ * server-error : "e=" server-error-value
+ * verifier : "v=" base64(ServerSignature)
+ *
+ * Extensions are optional and for future use.
+ * As suggested by the RFC they will be ignored
+ *
+ * @param {SieveResponseParser} parser
+ * the parser which should be to process the message.
+ * @param {string} [data]
+ * optional, the server's final message. It omitted it
+ * will be parsed from the response.
+ *
+ *
+ * @private
+ */
+ async _parseFinalMessage(parser, data) {
+
+ if (typeof (data) === "undefined" || data === null)
+ data = parser.extractString();
+
+ // server-final-message = (server-error / verifier) ["," extensions]
+ data = (new SieveBase64Decoder(data)).toUtf8();
+
+ const token = data.split(",")[SHA_FIRST_TOKEN];
+
+ if (token.length <= SHA_PREFIX_LENGTH)
+ throw new Error(`Response expected but got: ${data}`);
+
+ // server-error = "e="
+ if (token.startsWith("e=")) {
+ this._serverError = token.substr(SHA_PREFIX_LENGTH);
+ return;
+ }
+
+ // verifier = "v=" base64
+ if (token.startsWith("v=")) {
+ this._verifier = (new SieveBase64Decoder(
+ token.substr(SHA_PREFIX_LENGTH))).toArray();
+ return;
+ }
+
+ throw new Error("Invalid Final message");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async parse(parser) {
+
+ if ((this.state === SHA_STATE_FIRST_MESSAGE) && (parser.isString())) {
+ await this._parseFirstMessage(parser);
+ parser.extractLineBreak();
+
+ this.state = SHA_STATE_FINAL_MESSAGE;
+
+ return this;
+ }
+
+
+ // There are two valid responses...
+ // ... either the Server sends us an explicit final message:
+ //
+ // S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
+ // C: ""
+ // S: OK
+
+ if ((this.state === SHA_STATE_FINAL_MESSAGE) && (parser.isString())) {
+
+ await this._parseFinalMessage(parser);
+ parser.extractLineBreak();
+
+ this.state = SHA_STATE_SIEVE_RESPONSE;
+
+ return this;
+ }
+
+ // Or the response is wrapped into the ResponseCode in order to save...
+ // ... roundtrip time so we end up with the following
+ //
+ // S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==")
+ super.parse(parser);
+
+ if (this.hasError())
+ return this;
+
+ if (this.state === SHA_STATE_SIEVE_RESPONSE) {
+ this.state = SHA_STATE_COMPLETED;
+ return this;
+ }
+
+ if (this.state === SHA_STATE_FINAL_MESSAGE) {
+ await this._parseFinalMessage(parser, this.getResponseCode().getSasl());
+ this.state = SHA_STATE_COMPLETED;
+ return this;
+ }
+
+ return this;
+ }
+
+ /**
+ * The salt is transferred with the first message and used to
+ * randomize the SHA request
+ *
+ * @returns {string}
+ * the salt
+ */
+ getSalt() {
+ if (this.state < SHA_STATE_FINAL_MESSAGE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._salt;
+ }
+
+ /**
+ * Returns the number of iterations required by the server.
+ * It is only available after the final message was received.
+ *
+ * @returns {int}
+ * the number of iterations.
+ */
+ getIterationCounter() {
+ if (this.state < SHA_STATE_FINAL_MESSAGE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._iter;
+ }
+
+ /**
+ * Returns the nonce. Please note this it is only available after the
+ * final message was received
+ *
+ * @returns {string}
+ * the nonce received from the server on an exception in case it is unknown.
+ */
+ getNonce() {
+ if (this.state < SHA_STATE_FINAL_MESSAGE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._nonce;
+ }
+
+ /**
+ * Returns the servers first message. Pleas not is tis only available when the
+ * final message was received.
+ *
+ * @returns {string}
+ * the server's first message or an exception in case it is unknown.
+ */
+ getServerFirstMessage() {
+ if (this.state < SHA_STATE_FINAL_MESSAGE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._serverFirstMessage;
+ }
+
+ /**
+ * Returns the error message returned by the server.
+ *
+ * @returns {string}
+ * the error message;
+ */
+ getServerError() {
+ if (this.state < SHA_STATE_SIEVE_RESPONSE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._serverError;
+ }
+
+ /**
+ * The server's signature which needs to be verified.
+ *
+ * @returns {string}
+ * the server's signature
+ */
+ getVerifier() {
+ if (this.state < SHA_STATE_SIEVE_RESPONSE)
+ throw new Error("Illegal State, request not completed");
+
+ return this._verifier;
+ }
+}
+
+export {
+ SieveSimpleResponse,
+ SieveCapabilitiesResponse,
+ SieveListScriptsResponse,
+ SieveSaslLoginResponse,
+ SieveGetScriptResponse,
+ SieveSaslScramShaResponse
+};
diff --git a/src/common/libManageSieve/SieveResponseCodes.js b/src/common/libManageSieve/SieveResponseCodes.js
deleted file mode 100755
index 13dc1f1d..00000000
--- a/src/common/libManageSieve/SieveResponseCodes.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const RESPONSE_CODE_NAME = 0;
- const RESPONSE_CODE_EXTENSION = 1;
-
- const NOT_FOUND = -1;
-
- /**
- * The response codes is a string with optional additional arguments
- */
- class SieveResponseCode {
-
- /**
- * Creates a new instance.
- *
- * @param {string[]} code
- * the response code including the additional arguments returned by the server.
- */
- constructor(code) {
- this.code = code;
- }
-
- /**
- * Response codes should not encapsulated in quotes according to the RFC.
- * Never the less Cyrus Servers sometimes do encapsulate the response codes.
- *
- * This method is aware of this behaviour, and should be always when comparing
- * ResponseCodes
- *
- * @param {string} code
- * the response code which should be tested for equality
- * @returns {boolean}
- * true in case the response code is equal otherwise false.
- */
- equalsCode(code) {
- if ((!this.code) || (!this.code.length))
- return false;
-
- if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== code.toUpperCase())
- return false;
-
- return true;
- }
- }
-
-
- /**
- * The server for sasl request always the response code "SASL"
- * followed by additional information.
- */
- class SieveResponseCodeSasl extends SieveResponseCode {
-
- /**
- * @inheritdoc
- */
- constructor(code) {
- super(code);
-
- if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== "SASL")
- throw new Error("Malformed SASL Response Code");
- }
-
- /**
- * Gets the sasl response code.
- *
- * @returns {string}
- * Returns the sasl response
- */
- getSasl() {
- return this.code[RESPONSE_CODE_EXTENSION];
- }
- }
-
- /**
- * In case of a referral a special response code is returned.
- * It contains the address to which the server refers us.
- */
- class SieveResponseCodeReferral extends SieveResponseCode {
-
- /**
- * @inheritdoc
- */
- constructor(code) {
- super(code);
-
- if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== "REFERRAL")
- throw new Error("Malformed REFERRAL Response Code");
-
- // We should have received something similar to
- // REFERRAL "sieve://c3.mail.example.com"
-
- // the quoted text contains the authority
- // authority = [ userinfo "@" ] host [ ":" port ]
- const uri = this.code[RESPONSE_CODE_EXTENSION];
-
- // remove the sieve:// scheme
- this.hostname = uri.slice("sieve://".length);
-
- // cleanup any script urls.
- if (this.hostname.indexOf("/") >= 0)
- this.hostname = this.hostname.slice(0, this.hostname.indexOf("/"));
-
- if (this.hostname.indexOf(":") === NOT_FOUND)
- return;
-
- // extract the port
- this.port = this.hostname.slice(this.hostname.indexOf(":") + ":".length);
- this.hostname = this.hostname.slice(0, this.hostname.indexOf(":"));
- }
-
- /**
- * Returns the hostname of the referred server.
- *
- * @returns {string}
- * the hostname as string.
- */
- getHostname() {
- return this.hostname;
- }
-
- /**
- * Returns the port of the referred server. If the server did not specify
- * any Port null is returned.
- *
- * @returns {int}
- * the port number or null
- */
- getPort() {
- return this.port;
- }
- }
-
- exports.SieveResponseCode = SieveResponseCode;
- exports.SieveResponseCodeSasl = SieveResponseCodeSasl;
- exports.SieveResponseCodeReferral = SieveResponseCodeReferral;
-
-})(module.exports || this);
diff --git a/src/common/libManageSieve/SieveResponseCodes.mjs b/src/common/libManageSieve/SieveResponseCodes.mjs
new file mode 100644
index 00000000..8bd276af
--- /dev/null
+++ b/src/common/libManageSieve/SieveResponseCodes.mjs
@@ -0,0 +1,142 @@
+/*
+ * The contents of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via email
+ * from the author. Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const RESPONSE_CODE_NAME = 0;
+const RESPONSE_CODE_EXTENSION = 1;
+
+/**
+ * The response codes is a string with optional additional arguments
+ */
+class SieveResponseCode {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {string[]} code
+ * the response code including the additional arguments returned by the server.
+ */
+ constructor(code) {
+ this.code = code;
+ }
+
+ /**
+ * Response codes should not encapsulated in quotes according to the RFC.
+ * Never the less Cyrus Servers sometimes do encapsulate the response codes.
+ *
+ * This method is aware of this behaviour, and should be always when comparing
+ * ResponseCodes
+ *
+ * @param {string} code
+ * the response code which should be tested for equality
+ * @returns {boolean}
+ * true in case the response code is equal otherwise false.
+ */
+ equalsCode(code) {
+ if ((!this.code) || (!this.code.length))
+ return false;
+
+ if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== code.toUpperCase())
+ return false;
+
+ return true;
+ }
+}
+
+
+/**
+ * The server for sasl request always the response code "SASL"
+ * followed by additional information.
+ */
+class SieveResponseCodeSasl extends SieveResponseCode {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(code) {
+ super(code);
+
+ if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== "SASL")
+ throw new Error("Malformed SASL Response Code");
+ }
+
+ /**
+ * Gets the sasl response code.
+ *
+ * @returns {string}
+ * Returns the sasl response
+ */
+ getSasl() {
+ return this.code[RESPONSE_CODE_EXTENSION];
+ }
+}
+
+/**
+ * In case of a referral a special response code is returned.
+ * It contains the address to which the server refers us.
+ */
+class SieveResponseCodeReferral extends SieveResponseCode {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(code) {
+ super(code);
+
+ if (this.code[RESPONSE_CODE_NAME].toUpperCase() !== "REFERRAL")
+ throw new Error("Malformed REFERRAL Response Code");
+
+ // We should have received something similar to
+ // REFERRAL "sieve://c3.mail.example.com"
+
+ // the quoted text contains the authority
+ // authority = [ userinfo "@" ] host [ ":" port ]
+ const uri = this.code[RESPONSE_CODE_EXTENSION];
+
+ // remove the sieve:// scheme
+ this.hostname = uri.slice("sieve://".length);
+
+ // cleanup any script urls.
+ if (this.hostname.includes("/"))
+ this.hostname = this.hostname.slice(0, this.hostname.indexOf("/"));
+
+ if (!this.hostname.includes(":"))
+ return;
+
+ // extract the port
+ this.port = this.hostname.slice(this.hostname.indexOf(":") + ":".length);
+ this.hostname = this.hostname.slice(0, this.hostname.indexOf(":"));
+ }
+
+ /**
+ * Returns the hostname of the referred server.
+ *
+ * @returns {string}
+ * the hostname as string.
+ */
+ getHostname() {
+ return this.hostname;
+ }
+
+ /**
+ * Returns the port of the referred server. If the server did not specify
+ * any Port null is returned.
+ *
+ * @returns {int}
+ * the port number or null
+ */
+ getPort() {
+ return this.port;
+ }
+}
+
+export {
+ SieveResponseCode,
+ SieveResponseCodeSasl,
+ SieveResponseCodeReferral
+};
diff --git a/src/common/libManageSieve/SieveResponseParser.mjs b/src/common/libManageSieve/SieveResponseParser.mjs
new file mode 100644
index 00000000..66e0a4c3
--- /dev/null
+++ b/src/common/libManageSieve/SieveResponseParser.mjs
@@ -0,0 +1,425 @@
+/*
+ * The contents of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via email
+ * from the author. Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+const CHAR_LF = 10;
+const CHAR_CR = 13;
+const CHAR_SPACE = 32;
+const CHAR_QUOTE = 34;
+const CHAR_BACKSLASH = 92;
+const CHAR_LEFT_BRACES = 123;
+const CHAR_RIGHT_BRACES = 125;
+
+const NOT_FOUND = -1;
+const CHAR_LEN = 1;
+
+/**
+ * The manage sieve protocol syntax uses a fixed grammar which is based on atomic tokens.
+ * This class offers an interface to test for and extract these predefined tokens. It supports
+ * Strings (Quoted and Literal), White Space (Line Break, Space ...) as well as arbitrary tokens.
+ *
+ * The parser does not change or alter the byte array's content. So extracting data does not shrink
+ * the array free any bytes. This parser is just some kind of a view to this array.
+ *
+ * Tokens are automatically converted from UTF-8 encoded byte arrays to JavaScript Unicode Strings
+ * during extraction.
+ */
+class SieveResponseParser {
+
+ /**
+ * Expects as input a byte array using UTF-8 encoding. It's because the manage sieve
+ * protocol is defined to uses UTF-8 encoding and Mozilla sockets return byte based incoming messages streams.
+ * @param {byte[]} data
+ * the response which should be parsed as a byte array encoded in UTF-8
+ */
+ constructor(data) {
+ if ((typeof (data) === 'undefined') || (data === null))
+ throw new Error("Error Parsing Response...\nData is null");
+
+ this.pos = 0;
+ this.data = data;
+ }
+
+ /**
+ * Extracts the given number of bytes from the buffer.
+ *
+ * @param {int} size
+ * The number of bytes as integer which should be extracted
+ *
+ */
+ extract(size) {
+ this.pos += size;
+ }
+
+ /**
+ * Tests if the array starts with a line break (#13#10)
+ *
+ * @returns {boolean}
+ * true if the buffer with a line break, otherwise false
+ */
+ isLineBreak() {
+ // Are we out of bounds?
+ if (this.data.length < this.pos + CHAR_LEN)
+ return false;
+
+ // Test for a line break #13#10
+ if (this.data[this.pos] !== CHAR_CR)
+ return false;
+
+ if (this.data[this.pos + CHAR_LEN] !== CHAR_LF)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Extracts a line break (#13#10) for the buffer
+ *
+ * If it does not start with a line break an exception is thrown.
+ *
+ * @returns {SieveAbstractResponseParser}
+ * a self reference
+ */
+ extractLineBreak() {
+ if (this.isLineBreak() === false)
+ throw new Error(`Line break expected but found:\n${this.getData()}`);
+
+ this.pos += "\r\n".length;
+
+ return this;
+ }
+
+ /**
+ * Test if the buffer starts with a space character (#32)
+ * @returns {boolean}
+ * true if buffer starts with a space character, otherwise false
+ */
+ isSpace() {
+ if (this.data[this.pos] === CHAR_SPACE)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Extracts a space character (#32) form the buffer
+ *
+ * If it does not start with a space character an exception is thrown.
+ *
+ */
+ extractSpace() {
+ if (this.isSpace() === false)
+ throw new Error(`Space expected but found:\n${this.getData()}`);
+
+ this.pos++;
+ }
+
+ /**
+ * Tests if the current buffer position is a literal string.
+ * Literals strings are defined as:
+ *
+ * literal = "{" number "+}" CRLF *OCTET
+ *
+ * @returns {boolean}
+ * true in case it is a literal otherwise false.
+ */
+ isLiteral() {
+ if (this.data[this.pos] === CHAR_LEFT_BRACES)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Extracts a literal string from the current position.
+ * Literals strings are defined as:
+ *
+ * literal = "{" number "+}" CRLF *OCTET
+ *
+ * Please not it is perfectly fine to have a literal with a zero byte length.
+ *
+ * @returns {string}
+ * the string or an exception in case the literal could not be extracted.
+ */
+ extractLiteral() {
+ if (this.isLiteral() === false)
+ throw new Error(`Literal Expected but found\n ${this.getData()}`);
+
+ // remove the "{"
+ this.pos++;
+
+ // some sieve implementations are broken, this means ....
+ // ... we can get "{4+}\r\n1234" or "{4}\r\n1234"
+
+ const nextBracket = this.indexOf(CHAR_RIGHT_BRACES);
+ if (nextBracket === NOT_FOUND)
+ throw new Error(`Error unbalanced parentheses "{" in \n ${this.getData()}`);
+
+ // extract the size, and ignore "+"
+ const size = Number.parseInt(this.getData(this.pos, nextBracket).replace(/\+/, ""), 10);
+
+ this.pos = nextBracket + CHAR_LEN;
+
+ this.extractLineBreak();
+
+ // extract the literal...
+ const literal = this.getData(this.pos, this.pos + size);
+ this.pos += size;
+
+ return literal;
+ }
+
+ /**
+ * Searches the buffer for a character.
+ *
+ * @param {byte} character
+ * the character which should be found
+ * @param {int} [offset]
+ * an absolute offset, from which to start searching
+ * @returns {int} character
+ * the characters absolute position within the buffer otherwise -1 if not found
+ */
+ indexOf(character, offset) {
+ if (typeof (offset) === "undefined")
+ offset = this.pos;
+
+ for (let i = offset; i < this.data.length; i++)
+ if (this.data[i] === character)
+ return i;
+
+ return NOT_FOUND;
+ }
+
+ /**
+ * Test if the buffer starts with a quote character (#34)
+ * @returns {boolean}
+ * true if buffer starts with a quote character, otherwise false
+ */
+ isQuoted() {
+ if (this.data[this.pos] === CHAR_QUOTE)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Extracts a quoted string form the buffer. It is aware of escape sequences.
+ *
+ * If it does not start with a valid string an exception is thrown.
+ *
+ * @returns {string}
+ * the quoted string extracted, it is guaranteed to be free of escape sequences
+ */
+ extractQuoted() {
+ if (this.isQuoted() === false)
+ throw new Error(`Quoted string expected but found\n${this.getData()}`);
+
+ // now search for the end. But we need to be aware of escape sequences.
+ let nextQuote = this.pos + CHAR_LEN;
+
+ while (this.data[nextQuote] !== CHAR_QUOTE) {
+
+ // Quoted stings can not contain line breaks...
+ if (this.data[nextQuote] === CHAR_LF)
+ throw new Error("Line break (LF) in Quoted String detected");
+
+ if (this.data[nextQuote] === CHAR_CR)
+ throw new Error("Line break (CR) in Quoted String detected");
+
+ // is it an escape sequence?
+ if (this.data[nextQuote] === CHAR_BACKSLASH) {
+ // Yes, it's a backslash so get the next char...
+ nextQuote++;
+
+ // ... only \\ and \" are valid escape sequences
+ if ((this.data[nextQuote] !== CHAR_BACKSLASH) && (this.data[nextQuote] !== CHAR_QUOTE))
+ throw new Error("Invalid Escape Sequence");
+ }
+
+ // move to the next character
+ nextQuote++;
+
+ if (this.nextQuote >= this.data.length)
+ throw new Error("Unterminated Quoted string");
+ }
+
+ let quoted = this.getData(this.pos + CHAR_LEN, nextQuote);
+
+ this.pos = nextQuote + CHAR_LEN;
+
+ // Cleanup escape sequences
+ quoted = quoted.replace(/\\"/g, '"');
+ quoted = quoted.replace(/\\\\/g, '\\');
+
+ return quoted;
+ }
+
+ /**
+ * Tests if the a quoted or literal string starts at the current position.
+ *
+ * @returns {boolean}
+ * true if a strings starts, otherwise false
+ */
+ isString() {
+ if (this.isQuoted())
+ return true;
+
+ if (this.isLiteral())
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Extracts a quoted or literal string from the current position
+ *
+ * @returns {string}
+ * the quote or literal string or an exception in case no string could be extracted.
+ */
+ extractString() {
+ if (this.isQuoted())
+ return this.extractQuoted();
+ if (this.isLiteral())
+ return this.extractLiteral();
+
+ throw new Error(`String expected but found\n${this.getData()}`);
+ }
+
+ /**
+ * Extracts a token form a response. The token is being delimited by any
+ * separator. The extracted token does not include the separator.
+ *
+ * Throws an exception if none of the separators is found.
+ *
+ * @param {byte[]} separators
+ * an array containing possible token separators. The first match always wins.
+ * @returns {string}
+ * the extracted token.
+ */
+ extractToken(separators) {
+ // Search for the separators, the one with the lowest index which is not...
+ // ... equal to -1 wins. The -2 indicates not initialized...
+ let index = NOT_FOUND;
+
+ for (let i = 0; i < separators.length; i++) {
+ const idx = this.indexOf(separators[i], this.pos);
+
+ if (idx === NOT_FOUND)
+ continue;
+
+ if (index === NOT_FOUND)
+ index = idx;
+ else
+ index = Math.min(index, idx);
+ }
+
+ if (index === NOT_FOUND)
+ throw new Error(`Delimiter >>${separators}<< not found in: ${this.getData()}`);
+
+ const token = this.getData(this.pos, index);
+ this.pos = index;
+
+ return token;
+ }
+
+ /**
+ * Tests if the buffer starts with the specified bytes.
+ *
+ * As the buffer is encoded in UTF-8, the specified bytes have to be
+ * encoded in UTF-8, otherwise the result is unpredictable.
+ *
+ * @param {Byte[]} bytes
+ * the bytes to compare as byte array encoded in UTF-8
+ *
+ * @returns {boolean}
+ * true if bytes match the beginning of the buffer, otherwise false
+ */
+ startsWith(bytes) {
+ if (!bytes.length)
+ return false;
+
+ for (let i = 0; i < bytes.length; i++) {
+ let result = false;
+
+ for (let ii = 0; ii < bytes[i].length; ii++)
+ if (bytes[i][ii] === this.data[this.pos + i])
+ result = true;
+
+ if (result === false)
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a copy of the current buffer.
+ *
+ * @returns {byte[]}
+ * an a copy of the array's current view. It is encoded in UTF-8
+ */
+ getByteArray() {
+ return this.data.slice(this.pos, this.data.length);
+ }
+
+ /**
+ * Returns a copy of the response parsers buffer as JavaScript Unicode string.
+ *
+ * Manage Sieve encodes literals in UTF-8 while network sockets are usually
+ * binary. So we can't use java scripts build in string functions as they expect
+ * pure unicode.
+ *
+ * @param {int} [startIndex]
+ * Optional zero-based index at which to begin.
+ * @param {int} [endIndex]
+ * Optional Zero-based index at which to end.
+ * @returns {string} the copy buffers content
+ */
+ getData(startIndex, endIndex) {
+
+ if (typeof (endIndex) === "undefined" || endIndex === null)
+ endIndex = this.data.length;
+
+ if (typeof (startIndex) === "undefined" || startIndex === null)
+ startIndex = this.pos;
+
+ const byteArray = this.data.slice(startIndex, endIndex);
+
+ return (new TextDecoder("UTF-8")).decode(new Uint8Array(byteArray));
+ }
+
+
+ /**
+ * Check if the buffer is empty. This means the buffer does not contain any
+ * extractable bytes or tokens.
+ *
+ * @returns {boolean}
+ * true if the buffer is empty, otherwise false
+ */
+ isEmpty() {
+ if (this.data.length >= this.pos)
+ return true;
+
+ return false;
+ }
+
+
+ /**
+ * Returns the read pointes current position.
+ * Can be used to resync the parser with a buffer.
+ *
+ * @returns {int}
+ * the current read pointer offset relative to the start in bytes.
+ */
+ getPosition() {
+ return this.pos;
+ }
+}
+
+export { SieveResponseParser };
diff --git a/src/common/libManageSieve/doc/HMAC/rfc2104.txt b/src/common/libManageSieve/doc/HMAC/rfc2104.txt
new file mode 100644
index 00000000..1fb8fe11
--- /dev/null
+++ b/src/common/libManageSieve/doc/HMAC/rfc2104.txt
@@ -0,0 +1,619 @@
+
+
+
+
+
+
+Network Working Group H. Krawczyk
+Request for Comments: 2104 IBM
+Category: Informational M. Bellare
+ UCSD
+ R. Canetti
+ IBM
+ February 1997
+
+
+ HMAC: Keyed-Hashing for Message Authentication
+
+Status of This Memo
+
+ This memo provides information for the Internet community. This memo
+ does not specify an Internet standard of any kind. Distribution of
+ this memo is unlimited.
+
+Abstract
+
+ This document describes HMAC, a mechanism for message authentication
+ using cryptographic hash functions. HMAC can be used with any
+ iterative cryptographic hash function, e.g., MD5, SHA-1, in
+ combination with a secret shared key. The cryptographic strength of
+ HMAC depends on the properties of the underlying hash function.
+
+1. Introduction
+
+ Providing a way to check the integrity of information transmitted
+ over or stored in an unreliable medium is a prime necessity in the
+ world of open computing and communications. Mechanisms that provide
+ such integrity check based on a secret key are usually called
+ "message authentication codes" (MAC). Typically, message
+ authentication codes are used between two parties that share a secret
+ key in order to validate information transmitted between these
+ parties. In this document we present such a MAC mechanism based on
+ cryptographic hash functions. This mechanism, called HMAC, is based
+ on work by the authors [BCK1] where the construction is presented and
+ cryptographically analyzed. We refer to that work for the details on
+ the rationale and security analysis of HMAC, and its comparison to
+ other keyed-hash methods.
+
+
+
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 1]
+
+RFC 2104 HMAC February 1997
+
+
+ HMAC can be used in combination with any iterated cryptographic hash
+ function. MD5 and SHA-1 are examples of such hash functions. HMAC
+ also uses a secret key for calculation and verification of the
+ message authentication values. The main goals behind this
+ construction are
+
+ * To use, without modifications, available hash functions.
+ In particular, hash functions that perform well in software,
+ and for which code is freely and widely available.
+
+ * To preserve the original performance of the hash function without
+ incurring a significant degradation.
+
+ * To use and handle keys in a simple way.
+
+ * To have a well understood cryptographic analysis of the strength of
+ the authentication mechanism based on reasonable assumptions on the
+ underlying hash function.
+
+ * To allow for easy replaceability of the underlying hash function in
+ case that faster or more secure hash functions are found or
+ required.
+
+ This document specifies HMAC using a generic cryptographic hash
+ function (denoted by H). Specific instantiations of HMAC need to
+ define a particular hash function. Current candidates for such hash
+ functions include SHA-1 [SHA], MD5 [MD5], RIPEMD-128/160 [RIPEMD].
+ These different realizations of HMAC will be denoted by HMAC-SHA1,
+ HMAC-MD5, HMAC-RIPEMD, etc.
+
+ Note: To the date of writing of this document MD5 and SHA-1 are the
+ most widely used cryptographic hash functions. MD5 has been recently
+ shown to be vulnerable to collision search attacks [Dobb]. This
+ attack and other currently known weaknesses of MD5 do not compromise
+ the use of MD5 within HMAC as specified in this document (see
+ [Dobb]); however, SHA-1 appears to be a cryptographically stronger
+ function. To this date, MD5 can be considered for use in HMAC for
+ applications where the superior performance of MD5 is critical. In
+ any case, implementers and users need to be aware of possible
+ cryptanalytic developments regarding any of these cryptographic hash
+ functions, and the eventual need to replace the underlying hash
+ function. (See section 6 for more information on the security of
+ HMAC.)
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 2]
+
+RFC 2104 HMAC February 1997
+
+
+2. Definition of HMAC
+
+ The definition of HMAC requires a cryptographic hash function, which
+ we denote by H, and a secret key K. We assume H to be a cryptographic
+ hash function where data is hashed by iterating a basic compression
+ function on blocks of data. We denote by B the byte-length of such
+ blocks (B=64 for all the above mentioned examples of hash functions),
+ and by L the byte-length of hash outputs (L=16 for MD5, L=20 for
+ SHA-1). The authentication key K can be of any length up to B, the
+ block length of the hash function. Applications that use keys longer
+ than B bytes will first hash the key using H and then use the
+ resultant L byte string as the actual key to HMAC. In any case the
+ minimal recommended length for K is L bytes (as the hash output
+ length). See section 3 for more information on keys.
+
+ We define two fixed and different strings ipad and opad as follows
+ (the 'i' and 'o' are mnemonics for inner and outer):
+
+ ipad = the byte 0x36 repeated B times
+ opad = the byte 0x5C repeated B times.
+
+ To compute HMAC over the data `text' we perform
+
+ H(K XOR opad, H(K XOR ipad, text))
+
+ Namely,
+
+ (1) append zeros to the end of K to create a B byte string
+ (e.g., if K is of length 20 bytes and B=64, then K will be
+ appended with 44 zero bytes 0x00)
+ (2) XOR (bitwise exclusive-OR) the B byte string computed in step
+ (1) with ipad
+ (3) append the stream of data 'text' to the B byte string resulting
+ from step (2)
+ (4) apply H to the stream generated in step (3)
+ (5) XOR (bitwise exclusive-OR) the B byte string computed in
+ step (1) with opad
+ (6) append the H result from step (4) to the B byte string
+ resulting from step (5)
+ (7) apply H to the stream generated in step (6) and output
+ the result
+
+ For illustration purposes, sample code based on MD5 is provided as an
+ appendix.
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 3]
+
+RFC 2104 HMAC February 1997
+
+
+3. Keys
+
+ The key for HMAC can be of any length (keys longer than B bytes are
+ first hashed using H). However, less than L bytes is strongly
+ discouraged as it would decrease the security strength of the
+ function. Keys longer than L bytes are acceptable but the extra
+ length would not significantly increase the function strength. (A
+ longer key may be advisable if the randomness of the key is
+ considered weak.)
+
+ Keys need to be chosen at random (or using a cryptographically strong
+ pseudo-random generator seeded with a random seed), and periodically
+ refreshed. (Current attacks do not indicate a specific recommended
+ frequency for key changes as these attacks are practically
+ infeasible. However, periodic key refreshment is a fundamental
+ security practice that helps against potential weaknesses of the
+ function and keys, and limits the damage of an exposed key.)
+
+4. Implementation Note
+
+ HMAC is defined in such a way that the underlying hash function H can
+ be used with no modification to its code. In particular, it uses the
+ function H with the pre-defined initial value IV (a fixed value
+ specified by each iterative hash function to initialize its
+ compression function). However, if desired, a performance
+ improvement can be achieved at the cost of (possibly) modifying the
+ code of H to support variable IVs.
+
+ The idea is that the intermediate results of the compression function
+ on the B-byte blocks (K XOR ipad) and (K XOR opad) can be precomputed
+ only once at the time of generation of the key K, or before its first
+ use. These intermediate results are stored and then used to
+ initialize the IV of H each time that a message needs to be
+ authenticated. This method saves, for each authenticated message,
+ the application of the compression function of H on two B-byte blocks
+ (i.e., on (K XOR ipad) and (K XOR opad)). Such a savings may be
+ significant when authenticating short streams of data. We stress
+ that the stored intermediate values need to be treated and protected
+ the same as secret keys.
+
+ Choosing to implement HMAC in the above way is a decision of the
+ local implementation and has no effect on inter-operability.
+
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 4]
+
+RFC 2104 HMAC February 1997
+
+
+5. Truncated output
+
+ A well-known practice with message authentication codes is to
+ truncate the output of the MAC and output only part of the bits
+ (e.g., [MM, ANSI]). Preneel and van Oorschot [PV] show some
+ analytical advantages of truncating the output of hash-based MAC
+ functions. The results in this area are not absolute as for the
+ overall security advantages of truncation. It has advantages (less
+ information on the hash result available to an attacker) and
+ disadvantages (less bits to predict for the attacker). Applications
+ of HMAC can choose to truncate the output of HMAC by outputting the t
+ leftmost bits of the HMAC computation for some parameter t (namely,
+ the computation is carried in the normal way as defined in section 2
+ above but the end result is truncated to t bits). We recommend that
+ the output length t be not less than half the length of the hash
+ output (to match the birthday attack bound) and not less than 80 bits
+ (a suitable lower bound on the number of bits that need to be
+ predicted by an attacker). We propose denoting a realization of HMAC
+ that uses a hash function H with t bits of output as HMAC-H-t. For
+ example, HMAC-SHA1-80 denotes HMAC computed using the SHA-1 function
+ and with the output truncated to 80 bits. (If the parameter t is not
+ specified, e.g. HMAC-MD5, then it is assumed that all the bits of the
+ hash are output.)
+
+6. Security
+
+ The security of the message authentication mechanism presented here
+ depends on cryptographic properties of the hash function H: the
+ resistance to collision finding (limited to the case where the
+ initial value is secret and random, and where the output of the
+ function is not explicitly available to the attacker), and the
+ message authentication property of the compression function of H when
+ applied to single blocks (in HMAC these blocks are partially unknown
+ to an attacker as they contain the result of the inner H computation
+ and, in particular, cannot be fully chosen by the attacker).
+
+ These properties, and actually stronger ones, are commonly assumed
+ for hash functions of the kind used with HMAC. In particular, a hash
+ function for which the above properties do not hold would become
+ unsuitable for most (probably, all) cryptographic applications,
+ including alternative message authentication schemes based on such
+ functions. (For a complete analysis and rationale of the HMAC
+ function the reader is referred to [BCK1].)
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 5]
+
+RFC 2104 HMAC February 1997
+
+
+ Given the limited confidence gained so far as for the cryptographic
+ strength of candidate hash functions, it is important to observe the
+ following two properties of the HMAC construction and its secure use
+ for message authentication:
+
+ 1. The construction is independent of the details of the particular
+ hash function H in use and then the latter can be replaced by any
+ other secure (iterative) cryptographic hash function.
+
+ 2. Message authentication, as opposed to encryption, has a
+ "transient" effect. A published breaking of a message authentication
+ scheme would lead to the replacement of that scheme, but would have
+ no adversarial effect on information authenticated in the past. This
+ is in sharp contrast with encryption, where information encrypted
+ today may suffer from exposure in the future if, and when, the
+ encryption algorithm is broken.
+
+ The strongest attack known against HMAC is based on the frequency of
+ collisions for the hash function H ("birthday attack") [PV,BCK2], and
+ is totally impractical for minimally reasonable hash functions.
+
+ As an example, if we consider a hash function like MD5 where the
+ output length equals L=16 bytes (128 bits) the attacker needs to
+ acquire the correct message authentication tags computed (with the
+ _same_ secret key K!) on about 2**64 known plaintexts. This would
+ require the processing of at least 2**64 blocks under H, an
+ impossible task in any realistic scenario (for a block length of 64
+ bytes this would take 250,000 years in a continuous 1Gbps link, and
+ without changing the secret key K during all this time). This attack
+ could become realistic only if serious flaws in the collision
+ behavior of the function H are discovered (e.g. collisions found
+ after 2**30 messages). Such a discovery would determine the immediate
+ replacement of the function H (the effects of such failure would be
+ far more severe for the traditional uses of H in the context of
+ digital signatures, public key certificates, etc.).
+
+ Note: this attack needs to be strongly contrasted with regular
+ collision attacks on cryptographic hash functions where no secret key
+ is involved and where 2**64 off-line parallelizable (!) operations
+ suffice to find collisions. The latter attack is approaching
+ feasibility [VW] while the birthday attack on HMAC is totally
+ impractical. (In the above examples, if one uses a hash function
+ with, say, 160 bit of output then 2**64 should be replaced by 2**80.)
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 6]
+
+RFC 2104 HMAC February 1997
+
+
+ A correct implementation of the above construction, the choice of
+ random (or cryptographically pseudorandom) keys, a secure key
+ exchange mechanism, frequent key refreshments, and good secrecy
+ protection of keys are all essential ingredients for the security of
+ the integrity verification mechanism provided by HMAC.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 7]
+
+RFC 2104 HMAC February 1997
+
+
+Appendix -- Sample Code
+
+ For the sake of illustration we provide the following sample code for
+ the implementation of HMAC-MD5 as well as some corresponding test
+ vectors (the code is based on MD5 code as described in [MD5]).
+
+/*
+** Function: hmac_md5
+*/
+
+void
+hmac_md5(text, text_len, key, key_len, digest)
+unsigned char* text; /* pointer to data stream */
+int text_len; /* length of data stream */
+unsigned char* key; /* pointer to authentication key */
+int key_len; /* length of authentication key */
+caddr_t digest; /* caller digest to be filled in */
+
+{
+ MD5_CTX context;
+ unsigned char k_ipad[65]; /* inner padding -
+ * key XORd with ipad
+ */
+ unsigned char k_opad[65]; /* outer padding -
+ * key XORd with opad
+ */
+ unsigned char tk[16];
+ int i;
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+
+ MD5_CTX tctx;
+
+ MD5Init(&tctx);
+ MD5Update(&tctx, key, key_len);
+ MD5Final(tk, &tctx);
+
+ key = tk;
+ key_len = 16;
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+
+
+
+Krawczyk, et. al. Informational [Page 8]
+
+RFC 2104 HMAC February 1997
+
+
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+ /* start out by storing key in pads */
+ bzero( k_ipad, sizeof k_ipad);
+ bzero( k_opad, sizeof k_opad);
+ bcopy( key, k_ipad, key_len);
+ bcopy( key, k_opad, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i=0; i<64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ MD5Init(&context); /* init context for 1st
+ * pass */
+ MD5Update(&context, k_ipad, 64) /* start with inner pad */
+ MD5Update(&context, text, text_len); /* then text of datagram */
+ MD5Final(digest, &context); /* finish up 1st pass */
+ /*
+ * perform outer MD5
+ */
+ MD5Init(&context); /* init context for 2nd
+ * pass */
+ MD5Update(&context, k_opad, 64); /* start with outer pad */
+ MD5Update(&context, digest, 16); /* then results of 1st
+ * hash */
+ MD5Final(digest, &context); /* finish up 2nd pass */
+}
+
+Test Vectors (Trailing '\0' of a character string not included in test):
+
+ key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+ key_len = 16 bytes
+ data = "Hi There"
+ data_len = 8 bytes
+ digest = 0x9294727a3638bb1c13f48ef8158bfc9d
+
+ key = "Jefe"
+ data = "what do ya want for nothing?"
+ data_len = 28 bytes
+ digest = 0x750c783e6ab0b503eaa86e310a5db738
+
+ key = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
+
+
+Krawczyk, et. al. Informational [Page 9]
+
+RFC 2104 HMAC February 1997
+
+
+ key_len 16 bytes
+ data = 0xDDDDDDDDDDDDDDDDDDDD...
+ ..DDDDDDDDDDDDDDDDDDDD...
+ ..DDDDDDDDDDDDDDDDDDDD...
+ ..DDDDDDDDDDDDDDDDDDDD...
+ ..DDDDDDDDDDDDDDDDDDDD
+ data_len = 50 bytes
+ digest = 0x56be34521d144c88dbb8c733f0e8b3f6
+
+Acknowledgments
+
+ Pau-Chen Cheng, Jeff Kraemer, and Michael Oehler, have provided
+ useful comments on early drafts, and ran the first interoperability
+ tests of this specification. Jeff and Pau-Chen kindly provided the
+ sample code and test vectors that appear in the appendix. Burt
+ Kaliski, Bart Preneel, Matt Robshaw, Adi Shamir, and Paul van
+ Oorschot have provided useful comments and suggestions during the
+ investigation of the HMAC construction.
+
+References
+
+ [ANSI] ANSI X9.9, "American National Standard for Financial
+ Institution Message Authentication (Wholesale)," American
+ Bankers Association, 1981. Revised 1986.
+
+ [Atk] Atkinson, R., "IP Authentication Header", RFC 1826, August
+ 1995.
+
+ [BCK1] M. Bellare, R. Canetti, and H. Krawczyk,
+ "Keyed Hash Functions and Message Authentication",
+ Proceedings of Crypto'96, LNCS 1109, pp. 1-15.
+ (http://www.research.ibm.com/security/keyed-md5.html)
+
+ [BCK2] M. Bellare, R. Canetti, and H. Krawczyk,
+ "Pseudorandom Functions Revisited: The Cascade Construction",
+ Proceedings of FOCS'96.
+
+ [Dobb] H. Dobbertin, "The Status of MD5 After a Recent Attack",
+ RSA Labs' CryptoBytes, Vol. 2 No. 2, Summer 1996.
+ http://www.rsa.com/rsalabs/pubs/cryptobytes.html
+
+ [PV] B. Preneel and P. van Oorschot, "Building fast MACs from hash
+ functions", Advances in Cryptology -- CRYPTO'95 Proceedings,
+ Lecture Notes in Computer Science, Springer-Verlag Vol.963,
+ 1995, pp. 1-14.
+
+ [MD5] Rivest, R., "The MD5 Message-Digest Algorithm",
+ RFC 1321, April 1992.
+
+
+
+Krawczyk, et. al. Informational [Page 10]
+
+RFC 2104 HMAC February 1997
+
+
+ [MM] Meyer, S. and Matyas, S.M., Cryptography, New York Wiley,
+ 1982.
+
+ [RIPEMD] H. Dobbertin, A. Bosselaers, and B. Preneel, "RIPEMD-160: A
+ strengthened version of RIPEMD", Fast Software Encryption,
+ LNCS Vol 1039, pp. 71-82.
+ ftp://ftp.esat.kuleuven.ac.be/pub/COSIC/bosselae/ripemd/.
+
+ [SHA] NIST, FIPS PUB 180-1: Secure Hash Standard, April 1995.
+
+ [Tsu] G. Tsudik, "Message authentication with one-way hash
+ functions", In Proceedings of Infocom'92, May 1992.
+ (Also in "Access Control and Policy Enforcement in
+ Internetworks", Ph.D. Dissertation, Computer Science
+ Department, University of Southern California, April 1991.)
+
+ [VW] P. van Oorschot and M. Wiener, "Parallel Collision
+ Search with Applications to Hash Functions and Discrete
+ Logarithms", Proceedings of the 2nd ACM Conf. Computer and
+ Communications Security, Fairfax, VA, November 1994.
+
+Authors' Addresses
+
+ Hugo Krawczyk
+ IBM T.J. Watson Research Center
+ P.O.Box 704
+ Yorktown Heights, NY 10598
+
+ EMail: hugo@watson.ibm.com
+
+ Mihir Bellare
+ Dept of Computer Science and Engineering
+ Mail Code 0114
+ University of California at San Diego
+ 9500 Gilman Drive
+ La Jolla, CA 92093
+
+ EMail: mihir@cs.ucsd.edu
+
+ Ran Canetti
+ IBM T.J. Watson Research Center
+ P.O.Box 704
+ Yorktown Heights, NY 10598
+
+ EMail: canetti@watson.ibm.com
+
+
+
+
+
+
+Krawczyk, et. al. Informational [Page 11]
+
diff --git a/src/common/libManageSieve/doc/HMAC/rfc4086.txt b/src/common/libManageSieve/doc/HMAC/rfc4086.txt
new file mode 100644
index 00000000..91041845
--- /dev/null
+++ b/src/common/libManageSieve/doc/HMAC/rfc4086.txt
@@ -0,0 +1,2691 @@
+
+
+
+
+
+
+Network Working Group D. Eastlake, 3rd
+Request for Comments: 4086 Motorola Laboratories
+BCP: 106 J. Schiller
+Obsoletes: 1750 MIT
+Category: Best Current Practice S. Crocker
+ June 2005
+
+ Randomness Requirements for Security
+
+Status of This Memo
+
+ This document specifies an Internet Best Current Practices for the
+ Internet Community, and requests discussion and suggestions for
+ improvements. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2005).
+
+Abstract
+
+ Security systems are built on strong cryptographic algorithms that
+ foil pattern analysis attempts. However, the security of these
+ systems is dependent on generating secret quantities for passwords,
+ cryptographic keys, and similar quantities. The use of pseudo-random
+ processes to generate secret quantities can result in pseudo-
+ security. A sophisticated attacker may find it easier to reproduce
+ the environment that produced the secret quantities and to search the
+ resulting small set of possibilities than to locate the quantities in
+ the whole of the potential number space.
+
+ Choosing random quantities to foil a resourceful and motivated
+ adversary is surprisingly difficult. This document points out many
+ pitfalls in using poor entropy sources or traditional pseudo-random
+ number generation techniques for generating such quantities. It
+ recommends the use of truly random hardware techniques and shows that
+ the existing hardware on many systems can be used for this purpose.
+ It provides suggestions to ameliorate the problem when a hardware
+ solution is not available, and it gives examples of how large such
+ quantities need to be for some applications.
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 1]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+Table of Contents
+
+ 1. Introduction and Overview .......................................3
+ 2. General Requirements ............................................4
+ 3. Entropy Sources .................................................7
+ 3.1. Volume Required ............................................7
+ 3.2. Existing Hardware Can Be Used For Randomness ...............8
+ 3.2.1. Using Existing Sound/Video Input ....................8
+ 3.2.2. Using Existing Disk Drives ..........................8
+ 3.3. Ring Oscillator Sources ....................................9
+ 3.4. Problems with Clocks and Serial Numbers ...................10
+ 3.5. Timing and Value of External Events .......................11
+ 3.6. Non-hardware Sources of Randomness ........................12
+ 4. De-skewing .....................................................12
+ 4.1. Using Stream Parity to De-Skew ............................13
+ 4.2. Using Transition Mappings to De-Skew ......................14
+ 4.3. Using FFT to De-Skew ......................................15
+ 4.4. Using Compression to De-Skew ..............................15
+ 5. Mixing .........................................................16
+ 5.1. A Trivial Mixing Function .................................17
+ 5.2. Stronger Mixing Functions .................................18
+ 5.3. Using S-Boxes for Mixing ..................................19
+ 5.4. Diffie-Hellman as a Mixing Function .......................19
+ 5.5. Using a Mixing Function to Stretch Random Bits ............20
+ 5.6. Other Factors in Choosing a Mixing Function ...............20
+ 6. Pseudo-random Number Generators ................................21
+ 6.1. Some Bad Ideas ............................................21
+ 6.1.1. The Fallacy of Complex Manipulation ................21
+ 6.1.2. The Fallacy of Selection from a Large Database .....22
+ 6.1.3. Traditional Pseudo-random Sequences ................23
+ 6.2. Cryptographically Strong Sequences ........................24
+ 6.2.1. OFB and CTR Sequences ..............................25
+ 6.2.2. The Blum Blum Shub Sequence Generator ..............26
+ 6.3. Entropy Pool Techniques ...................................27
+ 7. Randomness Generation Examples and Standards ...................28
+ 7.1. Complete Randomness Generators ............................28
+ 7.1.1. US DoD Recommendations for Password Generation .....28
+ 7.1.2. The /dev/random Device .............................29
+ 7.1.3. Windows CryptGenRandom .............................30
+ 7.2. Generators Assuming a Source of Entropy ...................31
+ 7.2.1. X9.82 Pseudo-Random Number Generation ..............31
+ 7.2.2. X9.17 Key Generation ...............................33
+ 7.2.3. DSS Pseudo-random Number Generation ................34
+ 8. Examples of Randomness Required ................................34
+ 8.1. Password Generation .......................................35
+ 8.2. A Very High Security Cryptographic Key ....................36
+ 9. Conclusion .....................................................38
+ 10. Security Considerations ........................................38
+
+
+
+Eastlake, et al. Standards Track [Page 2]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ 11. Acknowledgments ................................................39
+ Appendix A: Changes from RFC 1750 ..................................40
+ Informative References .............................................41
+
+1. Introduction and Overview
+
+ Software cryptography is coming into wider use, although there is a
+ long way to go until it becomes pervasive. Systems such as SSH,
+ IPSEC, TLS, S/MIME, PGP, DNSSEC, and Kerberos are maturing and
+ becoming a part of the network landscape [SSH] [IPSEC] [TLS] [S/MIME]
+ [MAIL_PGP*] [DNSSEC*]. For comparison, when the previous version of
+ this document [RFC1750] was issued in 1994, the only Internet
+ cryptographic security specification in the IETF was the Privacy
+ Enhanced Mail protocol [MAIL_PEM*].
+
+ These systems provide substantial protection against snooping and
+ spoofing. However, there is a potential flaw. At the heart of all
+ cryptographic systems is the generation of secret, unguessable (i.e.,
+ random) numbers.
+
+ The lack of generally available facilities for generating such random
+ numbers (that is, the lack of general availability of truly
+ unpredictable sources) forms an open wound in the design of
+ cryptographic software. For the software developer who wants to
+ build a key or password generation procedure that runs on a wide
+ range of hardware, this is a very real problem.
+
+ Note that the requirement is for data that an adversary has a very
+ low probability of guessing or determining. This can easily fail if
+ pseudo-random data is used that meets only traditional statistical
+ tests for randomness, or that is based on limited-range sources such
+ as clocks. Sometimes such pseudo-random quantities can be guessed by
+ an adversary searching through an embarrassingly small space of
+ possibilities.
+
+ This Best Current Practice document describes techniques for
+ producing random quantities that will be resistant to attack. It
+ recommends that future systems include hardware random number
+ generation or provide access to existing hardware that can be used
+ for this purpose. It suggests methods for use if such hardware is
+ not available, and it gives some estimates of the number of random
+ bits required for sample applications.
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 3]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+2. General Requirements
+
+ Today, a commonly encountered randomness requirement is to pick a
+ user password, usually a simple character string. Obviously, a
+ password that can be guessed does not provide security. For re-
+ usable passwords, it is desirable that users be able to remember the
+ password. This may make it advisable to use pronounceable character
+ strings or phrases composed of ordinary words. But this affects only
+ the format of the password information, not the requirement that the
+ password be very hard to guess.
+
+ Many other requirements come from the cryptographic arena.
+ Cryptographic techniques can be used to provide a variety of
+ services, including confidentiality and authentication. Such
+ services are based on quantities, traditionally called "keys", that
+ are unknown to and unguessable by an adversary.
+
+ There are even TCP/IP protocol uses for randomness in picking initial
+ sequence numbers [RFC1948].
+
+ Generally speaking, the above examples also illustrate two different
+ types of random quantities that may be wanted. In the case of
+ human-usable passwords, the only important characteristic is that
+ they be unguessable. It is not important that they may be composed
+ of ASCII characters, so the top bit of every byte is zero, for
+ example. On the other hand, for fixed length keys and the like, one
+ normally wants quantities that appear to be truly random, that is,
+ quantities whose bits will pass statistical randomness tests.
+
+ In some cases, such as the use of symmetric encryption with the one-
+ time pads or an algorithm like the US Advanced Encryption Standard
+ [AES], the parties who wish to communicate confidentially and/or with
+ authentication must all know the same secret key. In other cases,
+ where asymmetric or "public key" cryptographic techniques are used,
+ keys come in pairs. One key of the pair is private and must be kept
+ secret by one party; the other is public and can be published to the
+ world. It is computationally infeasible to determine the private key
+ from the public key, and knowledge of the public key is of no help to
+ an adversary [ASYMMETRIC]. See general references [SCHNEIER,
+ FERGUSON, KAUFMAN].
+
+ The frequency and volume of the requirement for random quantities
+ differs greatly for different cryptographic systems. With pure RSA,
+ random quantities are required only when a new key pair is generated;
+ thereafter, any number of messages can be signed without a further
+ need for randomness. The public key Digital Signature Algorithm
+ devised by the US National Institute of Standards and Technology
+ (NIST) requires good random numbers for each signature [DSS]. And
+
+
+
+Eastlake, et al. Standards Track [Page 4]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ encrypting with a one-time pad (in principle the strongest possible
+ encryption technique) requires randomness of equal volume to all the
+ messages to be processed. See general references [SCHNEIER,
+ FERGUSON, KAUFMAN].
+
+ In most of these cases, an adversary can try to determine the
+ "secret" key by trial and error. This is possible as long as the key
+ is enough smaller than the message that the correct key can be
+ uniquely identified. The probability of an adversary succeeding at
+ this must be made acceptably low, depending on the particular
+ application. The size of the space the adversary must search is
+ related to the amount of key "information" present, in an
+ information-theoretic sense [SHANNON]. This depends on the number of
+ different secret values possible and the probability of each value,
+ as follows:
+
+ -----
+ \
+ Bits of information = \ - p * log ( p )
+ / i 2 i
+ /
+ -----
+
+ where i counts from 1 to the number of possible secret values and p
+ sub i is the probability of the value numbered i. (Because p sub i
+ is less than one, the log will be negative, so each term in the sum
+ will be non-negative.)
+
+ If there are 2^n different values of equal probability, then n bits
+ of information are present and an adversary would have to try, on the
+ average, half of the values, or 2^(n-1), before guessing the secret
+ quantity. If the probability of different values is unequal, then
+ there is less information present, and fewer guesses will, on
+ average, be required by an adversary. In particular, any values that
+ an adversary can know to be impossible or of low probability can be
+ initially ignored by the adversary, who will search through the more
+ probable values first.
+
+ For example, consider a cryptographic system that uses 128-bit keys.
+ If these keys are derived using a fixed pseudo-random number
+ generator that is seeded with an 8-bit seed, then an adversary needs
+ to search through only 256 keys (by running the pseudo-random number
+ generator with every possible seed), not 2^128 keys as may at first
+ appear to be the case. Only 8 bits of "information" are in these
+ 128-bit keys.
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 5]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ While the above analysis is correct on average, it can be misleading
+ in some cases for cryptographic analysis where what is really
+ important is the work factor for an adversary. For example, assume
+ that there is a pseudo-random number generator generating 128-bit
+ keys, as in the previous paragraph, but that it generates zero half
+ of the time and a random selection from the remaining 2^128 - 1
+ values the rest of the time. The Shannon equation above says that
+ there are 64 bits of information in one of these key values, but an
+ adversary, simply by trying the value zero, can break the security of
+ half of the uses, albeit a random half. Thus, for cryptographic
+ purposes, it is also useful to look at other measures, such as min-
+ entropy, defined as
+
+ Min-entropy = - log ( maximum ( p ) )
+ i
+
+ where i is as above. Using this equation, we get 1 bit of min-
+ entropy for our new hypothetical distribution, as opposed to 64 bits
+ of classical Shannon entropy.
+
+ A continuous spectrum of entropies, sometimes called Renyi entropy,
+ has been defined, specified by the parameter r. Here r = 1 is
+ Shannon entropy and r = infinity is min-entropy. When r = zero, it
+ is just log (n), where n is the number of non-zero probabilities.
+ Renyi entropy is a non-increasing function of r, so min-entropy is
+ always the most conservative measure of entropy and usually the best
+ to use for cryptographic evaluation [LUBY].
+
+ Statistically tested randomness in the traditional sense is NOT the
+ same as the unpredictability required for security use.
+
+ For example, the use of a widely available constant sequence, such as
+ the random table from the CRC Standard Mathematical Tables, is very
+ weak against an adversary. An adversary who learns of or guesses it
+ can easily break all security, future and past, based on the sequence
+ [CRC]. As another example, using AES with a constant key to encrypt
+ successive integers such as 1, 2, 3, ... will produce output that
+ also has excellent statistical randomness properties but is
+ predictable. On the other hand, taking successive rolls of a six-
+ sided die and encoding the resulting values in ASCII would produce
+ statistically poor output with a substantial unpredictable component.
+ So note that passing or failing statistical tests doesn't reveal
+ whether something is unpredictable or predictable.
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 6]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+3. Entropy Sources
+
+ Entropy sources tend to be very implementation dependent. Once one
+ has gathered sufficient entropy, it can be used as the seed to
+ produce the required amount of cryptographically strong pseudo-
+ randomness, as described in Sections 6 and 7, after being de-skewed
+ or mixed as necessary, as described in Sections 4 and 5.
+
+ Is there any hope for true, strong, portable randomness in the
+ future? There might be. All that's needed is a physical source of
+ unpredictable numbers.
+
+ Thermal noise (sometimes called Johnson noise in integrated circuits)
+ or a radioactive decay source and a fast, free-running oscillator
+ would do the trick directly [GIFFORD]. This is a trivial amount of
+ hardware, and it could easily be included as a standard part of a
+ computer system's architecture. Most audio (or video) input devices
+ are usable [TURBID]. Furthermore, any system with a spinning disk or
+ ring oscillator and a stable (crystal) time source or the like has an
+ adequate source of randomness ([DAVIS] and Section 3.3). All that's
+ needed is the common perception among computer vendors that this
+ small additional hardware and the software to access it is necessary
+ and useful.
+
+ ANSI X9 is currently developing a standard that includes a part
+ devoted to entropy sources. See Part 2 of [X9.82].
+
+3.1. Volume Required
+
+ How much unpredictability is needed? Is it possible to quantify the
+ requirement in terms of, say, number of random bits per second?
+
+ The answer is that not very much is needed. For AES, the key can be
+ 128 bits, and, as we show in an example in Section 8, even the
+ highest security system is unlikely to require strong keying material
+ of much over 200 bits. If a series of keys is needed, they can be
+ generated from a strong random seed (starting value) using a
+ cryptographically strong sequence, as explained in Section 6.2. A
+ few hundred random bits generated at start-up or once a day is enough
+ if such techniques are used. Even if the random bits are generated
+ as slowly as one per second and it is not possible to overlap the
+ generation process, it should be tolerable in most high-security
+ applications to wait 200 seconds occasionally.
+
+ These numbers are trivial to achieve. It could be achieved by a
+ person repeatedly tossing a coin, and almost any hardware based
+ process is likely to be much faster.
+
+
+
+
+Eastlake, et al. Standards Track [Page 7]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+3.2. Existing Hardware Can Be Used For Randomness
+
+ As described below, many computers come with hardware that can, with
+ care, be used to generate truly random quantities.
+
+3.2.1. Using Existing Sound/Video Input
+
+ Many computers are built with inputs that digitize some real-world
+ analog source, such as sound from a microphone or video input from a
+ camera. The "input" from a sound digitizer with no source plugged in
+ or from a camera with the lens cap on is essentially thermal noise.
+ If the system has enough gain to detect anything, such input can
+ provide reasonably high quality random bits. This method is
+ extremely dependent on the hardware implementation.
+
+ For example, on some UNIX-based systems, one can read from the
+ /dev/audio device with nothing plugged into the microphone jack or
+ with the microphone receiving only low level background noise. Such
+ data is essentially random noise, although it should not be trusted
+ without some checking, in case of hardware failure, and it will have
+ to be de-skewed.
+
+ Combining this approach with compression to de-skew (see Section 4),
+ one can generate a huge amount of medium-quality random data with the
+ UNIX-style command line:
+
+ cat /dev/audio | compress - >random-bits-file
+
+ A detailed examination of this type of randomness source appears in
+ [TURBID].
+
+3.2.2. Using Existing Disk Drives
+
+ Disk drives have small random fluctuations in their rotational speed
+ due to chaotic air turbulence [DAVIS, Jakobsson]. The addition of
+ low-level disk seek-time instrumentation produces a series of
+ measurements that contain this randomness. Such data is usually
+ highly correlated, so significant processing is needed, as described
+ in Section 5.2 below. Nevertheless, experimentation a decade ago
+ showed that, with such processing, even slow disk drives on the
+ slower computers of that day could easily produce 100 bits a minute
+ or more of excellent random data.
+
+ Every increase in processor speed, which increases the resolution
+ with which disk motion can be timed or increases the rate of disk
+ seeks, increases the rate of random bit generation possible with this
+ technique. At the time of this paper and with modern hardware, a
+ more typical rate of random bit production would be in excess of
+
+
+
+Eastlake, et al. Standards Track [Page 8]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ 10,000 bits a second. This technique is used in random number
+ generators included in many operating system libraries.
+
+ Note: the inclusion of cache memories in disk controllers has little
+ effect on this technique if very short seek times, which represent
+ cache hits, are simply ignored.
+
+3.3. Ring Oscillator Sources
+
+ If an integrated circuit is being designed or field-programmed, an
+ odd number of gates can be connected in series to produce a free-
+ running ring oscillator. By sampling a point in the ring at a fixed
+ frequency (for example, one determined by a stable crystal
+ oscillator), some amount of entropy can be extracted due to
+ variations in the free-running oscillator timing. It is possible to
+ increase the rate of entropy by XOR'ing sampled values from a few
+ ring oscillators with relatively prime lengths. It is sometimes
+ recommended that an odd number of rings be used so that, even if the
+ rings somehow become synchronously locked to each other, there will
+ still be sampled bit transitions. Another possible source to sample
+ is the output of a noisy diode.
+
+ Sampled bits from such sources will have to be heavily de-skewed, as
+ disk rotation timings must be (see Section 4). An engineering study
+ would be needed to determine the amount of entropy being produced
+ depending on the particular design. In any case, these can be good
+ sources whose cost is a trivial amount of hardware by modern
+ standards.
+
+ As an example, IEEE 802.11i suggests the circuit below, with due
+ attention in the design to isolation of the rings from each other and
+ from clocked circuits to avoid undesired synchronization, etc., and
+ with extensive post processing [IEEE_802.11i].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 9]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ |\ |\ |\
+ +-->| >0-->| >0-- 19 total --| >0--+-------+
+ | |/ |/ |/ | |
+ | | |
+ +----------------------------------+ V
+ +-----+
+ |\ |\ |\ | | output
+ +-->| >0-->| >0-- 23 total --| >0--+--->| XOR |------>
+ | |/ |/ |/ | | |
+ | | +-----+
+ +----------------------------------+ ^ ^
+ | |
+ |\ |\ |\ | |
+ +-->| >0-->| >0-- 29 total --| >0--+------+ |
+ | |/ |/ |/ | |
+ | | |
+ +----------------------------------+ |
+ |
+ Other randomness, if available ---------+
+
+3.4. Problems with Clocks and Serial Numbers
+
+ Computer clocks and similar operating system or hardware values,
+ provide significantly fewer real bits of unpredictability than might
+ appear from their specifications.
+
+ Tests have been done on clocks on numerous systems, and it was found
+ that their behavior can vary widely and in unexpected ways. One
+ version of an operating system running on one set of hardware may
+ actually provide, say, microsecond resolution in a clock, while a
+ different configuration of the "same" system may always provide the
+ same lower bits and only count in the upper bits at much lower
+ resolution. This means that successive reads of the clock may
+ produce identical values even if enough time has passed that the
+ value "should" change based on the nominal clock resolution. There
+ are also cases where frequently reading a clock can produce
+ artificial sequential values, because of extra code that checks for
+ the clock being unchanged between two reads and increases it by one!
+ Designing portable application code to generate unpredictable numbers
+ based on such system clocks is particularly challenging because the
+ system designer does not always know the properties of the system
+ clock.
+
+ Use of a hardware serial number (such as an Ethernet MAC address) may
+ also provide fewer bits of uniqueness than one would guess. Such
+ quantities are usually heavily structured, and subfields may have
+ only a limited range of possible values, or values may be easily
+ guessable based on approximate date of manufacture or other data.
+
+
+
+Eastlake, et al. Standards Track [Page 10]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ For example, it is likely that a company that manufactures both
+ computers and Ethernet adapters will, at least internally, use its
+ own adapters, which significantly limits the range of built-in
+ addresses.
+
+ Problems such as those described above make the production of code to
+ generate unpredictable quantities difficult if the code is to be
+ ported across a variety of computer platforms and systems.
+
+3.5. Timing and Value of External Events
+
+ It is possible to measure the timing and content of mouse movement,
+ key strokes, and similar user events. This is a reasonable source of
+ unguessable data, with some qualifications. On some machines, input
+ such as key strokes is buffered. Even though the user's inter-
+ keystroke timing may have sufficient variation and unpredictability,
+ there might not be an easy way to access that variation. Another
+ problem is that no standard method exists for sampling timing
+ details. This makes it hard to use this technique to build standard
+ software intended for distribution to a large range of machines.
+
+ The amount of mouse movement and the actual key strokes are usually
+ easier to access than timings, but they may yield less
+ unpredictability because the user may provide highly repetitive
+ input.
+
+ Other external events, such as network packet arrival times and
+ lengths, can also be used, but only with great care. In particular,
+ the possibility of manipulation of such network traffic measurements
+ by an adversary and the lack of history at system start-up must be
+ carefully considered. If this input is subject to manipulation, it
+ must not be trusted as a source of entropy.
+
+ In principle, almost any external sensor, such as raw radio reception
+ or temperature sensing in appropriately equipped computers, can be
+ used. But in each case, careful consideration must be given to how
+ much this data is subject to adversarial manipulation and to how much
+ entropy it can actually provide.
+
+ The above techniques are quite powerful against attackers that have
+ no access to the quantities being measured. For example, these
+ techniques would be powerful against offline attackers who had no
+ access to one's environment and who were trying to crack one's random
+ seed after the fact. In all cases, the more accurately one can
+ measure the timing or value of an external sensor, the more rapidly
+ one can generate bits.
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 11]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+3.6. Non-hardware Sources of Randomness
+
+ The best source of input entropy would be a hardware-based random
+ source such as ring oscillators, disk drive timing, thermal noise, or
+ radioactive decay. However, if none of these is available, there are
+ other possibilities. These include system clocks, system or
+ input/output buffers, user/system/hardware/network serial numbers or
+ addresses and timing, and user input. Unfortunately, each of these
+ sources can produce very limited or predictable values under some
+ circumstances.
+
+ Some of the sources listed above would be quite strong on multi-user
+ systems, where each user of the system is in essence a source of
+ randomness. However, on a small single-user or embedded system,
+ especially at start-up, it might be possible for an adversary to
+ assemble a similar configuration. This could give the adversary
+ inputs to the mixing process that were well-enough correlated to
+ those used originally to make exhaustive search practical.
+
+ The use of multiple random inputs with a strong mixing function is
+ recommended and can overcome weakness in any particular input. The
+ timing and content of requested "random" user keystrokes can yield
+ hundreds of random bits, but conservative assumptions need to be
+ made. For example, one reasonably conservative assumption would be
+ that an inter-keystroke interval provides at most a few bits of
+ randomness, but only when the interval is unique in the sequence of
+ intervals up to that point. A similar assumption would be that a key
+ code provides a few bits of randomness, but only when the code is
+ unique in the sequence. Thus, an interval or key code that
+ duplicated a previous value would be assumed to provide no additional
+ randomness. The results of mixing these timings with typed
+ characters could be further combined with clock values and other
+ inputs.
+
+ This strategy may make practical portable code for producing good
+ random numbers for security, even if some of the inputs are very weak
+ on some of the target systems. However, it may still fail against a
+ high-grade attack on small, single-user, or embedded systems,
+ especially if the adversary has ever been able to observe the
+ generation process in the past. A hardware-based random source is
+ still preferable.
+
+4. De-skewing
+
+ Is there any specific requirement on the shape of the distribution of
+ quantities gathered for the entropy to produce the random numbers?
+ The good news is that the distribution need not be uniform. All that
+ is needed to bound performance is a conservative estimate of how
+
+
+
+Eastlake, et al. Standards Track [Page 12]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ non-uniform it is. Simple techniques to de-skew a bit stream are
+ given below, and stronger cryptographic techniques are described in
+ Section 5.2.
+
+4.1. Using Stream Parity to De-Skew
+
+ As a simple but not particularly practical example, consider taking a
+ sufficiently long string of bits and mapping the string to "zero" or
+ "one". The mapping will not yield a perfectly uniform distribution,
+ but it can be as close as desired. One mapping that serves the
+ purpose is to take the parity of the string. This has the advantages
+ that it is robust across all degrees of skew up to the estimated
+ maximum skew and that it is trivial to implement in hardware.
+
+ The following analysis gives the number of bits that must be sampled:
+
+ Suppose that the ratio of ones to zeros is ( 0.5 + E ) to
+ ( 0.5 - E ), where E is between 0 and 0.5 and is a measure of the
+ "eccentricity" of the distribution. Consider the distribution of the
+ parity function of N bit samples. The respective probabilities that
+ the parity will be one or zero will be the sum of the odd or even
+ terms in the binomial expansion of (p + q)^N, where p = 0.5 + E, the
+ probability of a one, and q = 0.5 - E, the probability of a zero.
+
+ These sums can be computed easily as
+
+ N N
+ 1/2 * ( ( p + q ) + ( p - q ) )
+
+ and
+ N N
+ 1/2 * ( ( p + q ) - ( p - q ) ).
+
+ (Which formula corresponds to the probability that the parity will be
+ 1 depends on whether N is odd or even.)
+
+ Since p + q = 1 and p - q = 2E, these expressions reduce to
+
+ N
+ 1/2 * [1 + (2E) ]
+
+ and
+ N
+ 1/2 * [1 - (2E) ].
+
+ Neither of these will ever be exactly 0.5 unless E is zero, but we
+ can bring them arbitrarily close to 0.5. If we want the
+ probabilities to be within some delta d of 0.5, e.g., then
+
+
+
+Eastlake, et al. Standards Track [Page 13]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ N
+ ( 0.5 + ( 0.5 * (2E) ) ) < 0.5 + d.
+
+ Solving for N yields N > log(2d)/log(2E). (Note that 2E is less than
+ 1, so its log is negative. Division by a negative number reverses
+ the sense of an inequality.)
+
+ The following table gives the length N of the string that must be
+ sampled for various degrees of skew in order to come within 0.001 of
+ a 50/50 distribution.
+
+ +---------+--------+-------+
+ | Prob(1) | E | N |
+ +---------+--------+-------+
+ | 0.5 | 0.00 | 1 |
+ | 0.6 | 0.10 | 4 |
+ | 0.7 | 0.20 | 7 |
+ | 0.8 | 0.30 | 13 |
+ | 0.9 | 0.40 | 28 |
+ | 0.95 | 0.45 | 59 |
+ | 0.99 | 0.49 | 308 |
+ +---------+--------+-------+
+
+ The last entry shows that even if the distribution is skewed 99% in
+ favor of ones, the parity of a string of 308 samples will be within
+ 0.001 of a 50/50 distribution. But, as we shall see in section 5.2,
+ there are much stronger techniques that extract more of the available
+ entropy.
+
+4.2. Using Transition Mappings to De-Skew
+
+ Another technique, originally due to von Neumann [VON_NEUMANN], is to
+ examine a bit stream as a sequence of non-overlapping pairs. One
+ could then discard any 00 or 11 pairs found, interpret 01 as a 0 and
+ 10 as a 1. Assume that the probability of a 1 is 0.5+E and that the
+ probability of a 0 is 0.5-E, where E is the eccentricity of the
+ source as described in the previous section. Then the probability of
+ each pair is shown in the following table:
+
+ +------+-----------------------------------------+
+ | pair | probability |
+ +------+-----------------------------------------+
+ | 00 | (0.5 - E)^2 = 0.25 - E + E^2 |
+ | 01 | (0.5 - E)*(0.5 + E) = 0.25 - E^2 |
+ | 10 | (0.5 + E)*(0.5 - E) = 0.25 - E^2 |
+ | 11 | (0.5 + E)^2 = 0.25 + E + E^2 |
+ +------+-----------------------------------------+
+
+
+
+
+Eastlake, et al. Standards Track [Page 14]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ This technique will completely eliminate any bias but requires an
+ indeterminate number of input bits for any particular desired number
+ of output bits. The probability of any particular pair being
+ discarded is 0.5 + 2E^2, so the expected number of input bits to
+ produce X output bits is X/(0.25 - E^2).
+
+ This technique assumes that the bits are from a stream where each bit
+ has the same probability of being a 0 or 1 as any other bit in the
+ stream and that bits are uncorrelated, i.e., that the bits come from
+ identical independent distributions. If alternate bits are from two
+ correlated sources, for example, the above analysis breaks down.
+
+ The above technique also provides another illustration of how a
+ simple statistical analysis can mislead if one is not always on the
+ lookout for patterns that could be exploited by an adversary. If the
+ algorithm were misread slightly so that overlapping successive bits
+ pairs were used instead of non-overlapping pairs, the statistical
+ analysis given would be the same. However, instead of providing an
+ unbiased, uncorrelated series of random 1s and 0s, it would produce a
+ totally predictable sequence of exactly alternating 1s and 0s.
+
+4.3. Using FFT to De-Skew
+
+ When real-world data consists of strongly correlated bits, it may
+ still contain useful amounts of entropy. This entropy can be
+ extracted through various transforms, the most powerful of which are
+ described in section 5.2 below.
+
+ Using the Fourier transform of the data or its optimized variant, the
+ FFT, is interesting primarily for theoretical reasons. It can be
+ shown that this technique will discard strong correlations. If
+ adequate data is processed and if remaining correlations decay,
+ spectral lines that approach statistical independence and normally
+ distributed randomness can be produced [BRILLINGER].
+
+4.4. Using Compression to De-Skew
+
+ Reversible compression techniques also provide a crude method of de-
+ skewing a skewed bit stream. This follows directly from the
+ definition of reversible compression and the formula in Section 2 for
+ the amount of information in a sequence. Since the compression is
+ reversible, the same amount of information must be present in the
+ shorter output as was present in the longer input. By the Shannon
+ information equation, this is only possible if, on average, the
+ probabilities of the different shorter sequences are more uniformly
+ distributed than were the probabilities of the longer sequences.
+ Therefore, the shorter sequences must be de-skewed relative to the
+ input.
+
+
+
+Eastlake, et al. Standards Track [Page 15]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ However, many compression techniques add a somewhat predictable
+ preface to their output stream and may insert a similar sequence
+ periodically in their output or otherwise introduce subtle patterns
+ of their own. They should be considered only rough techniques
+ compared to those described in Section 5.2. At a minimum, the
+ beginning of the compressed sequence should be skipped and only later
+ bits should used for applications requiring roughly-random bits.
+
+5. Mixing
+
+ What is the best overall strategy for obtaining unguessable random
+ numbers in the absence of a strong, reliable hardware entropy source?
+ It is to obtain input from a number of uncorrelated sources and to
+ mix them with a strong mixing function. Such a function will
+ preserve the entropy present in any of the sources, even if other
+ quantities being combined happen to be fixed or easily guessable (low
+ entropy). This approach may be advisable even with a good hardware
+ source, as hardware can also fail. However, this should be weighed
+ against a possible increase in the chance of overall failure due to
+ added software complexity.
+
+ Once one has used good sources, such as some of those listed in
+ Section 3, and mixed them as described in this section, one has a
+ strong seed. This can then be used to produce large quantities of
+ cryptographically strong material as described in Sections 6 and 7.
+
+ A strong mixing function is one that combines inputs and produces an
+ output in which each output bit is a different complex non-linear
+ function of all the input bits. On average, changing any input bit
+ will change about half the output bits. But because the relationship
+ is complex and non-linear, no particular output bit is guaranteed to
+ change when any particular input bit is changed.
+
+ Consider the problem of converting a stream of bits that is skewed
+ towards 0 or 1 or which has a somewhat predictable pattern to a
+ shorter stream which is more random, as discussed in Section 4. This
+ is simply another case where a strong mixing function is desired, to
+ mix the input bits and produce a smaller number of output bits. The
+ technique given in Section 4.1, using the parity of a number of bits,
+ is simply the result of successively XORing them. This is examined
+ as a trivial mixing function, immediately below. Use of stronger
+ mixing functions to extract more of the randomness in a stream of
+ skewed bits is examined in Section 5.2. See also [NASLUND].
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 16]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+5.1. A Trivial Mixing Function
+
+ For expository purposes we describe a trivial example for single bit
+ inputs using the Exclusive Or (XOR) function. This function is
+ equivalent to addition without carry, as show in the table below.
+ This is a degenerate case in which the one output bit always changes
+ for a change in either input bit. But, despite its simplicity, it
+ provides a useful illustration.
+
+ +-----------+-----------+----------+
+ | input 1 | input 2 | output |
+ +-----------+-----------+----------+
+ | 0 | 0 | 0 |
+ | 0 | 1 | 1 |
+ | 1 | 0 | 1 |
+ | 1 | 1 | 0 |
+ +-----------+-----------+----------+
+
+ If inputs 1 and 2 are uncorrelated and combined in this fashion, then
+ the output will be an even better (less skewed) random bit than the
+ inputs are. If we assume an "eccentricity" E as defined in Section
+ 4.1 above, then the output eccentricity relates to the input
+ eccentricity as follows:
+
+ E = 2 * E * E
+ output input 1 input 2
+
+ Since E is never greater than 1/2, the eccentricity is always
+ improved, except in the case in which at least one input is a totally
+ skewed constant. This is illustrated in the following table, where
+ the top and left side values are the two input eccentricities and the
+ entries are the output eccentricity:
+
+ +--------+--------+--------+--------+--------+--------+--------+
+ | E | 0.00 | 0.10 | 0.20 | 0.30 | 0.40 | 0.50 |
+ +--------+--------+--------+--------+--------+--------+--------+
+ | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
+ | 0.10 | 0.00 | 0.02 | 0.04 | 0.06 | 0.08 | 0.10 |
+ | 0.20 | 0.00 | 0.04 | 0.08 | 0.12 | 0.16 | 0.20 |
+ | 0.30 | 0.00 | 0.06 | 0.12 | 0.18 | 0.24 | 0.30 |
+ | 0.40 | 0.00 | 0.08 | 0.16 | 0.24 | 0.32 | 0.40 |
+ | 0.50 | 0.00 | 0.10 | 0.20 | 0.30 | 0.40 | 0.50 |
+ +--------+--------+--------+--------+--------+--------+--------+
+
+ However, note that the above calculations assume that the inputs are
+ not correlated. If the inputs were, say, the parity of the number of
+ minutes from midnight on two clocks accurate to a few seconds, then
+ each might appear random if sampled at random intervals much longer
+
+
+
+Eastlake, et al. Standards Track [Page 17]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ than a minute. Yet if they were both sampled and combined with XOR,
+ the result would be zero most of the time.
+
+5.2. Stronger Mixing Functions
+
+ The US Government Advanced Encryption Standard [AES] is an example of
+ a strong mixing function for multiple bit quantities. It takes up to
+ 384 bits of input (128 bits of "data" and 256 bits of "key") and
+ produces 128 bits of output, each of which is dependent on a complex
+ non-linear function of all input bits. Other encryption functions
+ with this characteristic, such as [DES], can also be used by
+ considering them to mix all of their key and data input bits.
+
+ Another good family of mixing functions is the "message digest" or
+ hashing functions such as the US Government Secure Hash Standards
+ [SHA*] and the MD4, MD5 [MD4, MD5] series. These functions all take
+ a practically unlimited amount of input and produce a relatively
+ short fixed-length output mixing all the input bits. The MD* series
+ produces 128 bits of output, SHA-1 produces 160 bits, and other SHA
+ functions produce up to 512 bits.
+
+ Although the message digest functions are designed for variable
+ amounts of input, AES and other encryption functions can also be used
+ to combine any number of inputs. If 128 bits of output is adequate,
+ the inputs can be packed into a 128-bit data quantity and successive
+ AES "keys", padding with zeros if needed; the quantity is then
+ successively encrypted by the "keys" using AES in Electronic Codebook
+ Mode. Alternatively, the input could be packed into one 128-bit key
+ and multiple data blocks and a CBC-MAC could be calculated [MODES].
+
+ More complex mixing should be used if more than 128 bits of output
+ are needed and one wants to employ AES (but note that it is
+ absolutely impossible to get more bits of "randomness" out than are
+ put in). For example, suppose that inputs are packed into three
+ quantities, A, B, and C. One may use AES to encrypt A with B and
+ then with C as keys to produce the first part of the output, then
+ encrypt B with C and then A for more output and, if necessary,
+ encrypt C with A and then B for yet more output. Still more output
+ can be produced by reversing the order of the keys given above. The
+ same can be done with the hash functions, hashing various subsets of
+ the input data or different copies of the input data with different
+ prefixes and/or suffixes to produce multiple outputs.
+
+ For an example of using a strong mixing function, reconsider the case
+ of a string of 308 bits, each of which is biased 99% toward zero.
+ The parity technique given in Section 4.1 reduces this to one bit,
+ with only a 1/1000 deviance from being equally likely a zero or one.
+ But, applying the equation for information given in Section 2, this
+
+
+
+Eastlake, et al. Standards Track [Page 18]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ 308-bit skewed sequence contains over 5 bits of information. Thus,
+ hashing it with SHA-1 and taking the bottom 5 bits of the result
+ would yield 5 unbiased random bits and not the single bit given by
+ calculating the parity of the string. Alternatively, for some
+ applications, you could use the entire hash output to retain almost
+ all of the 5+ bits of entropy in a 160-bit quantity.
+
+5.3. Using S-Boxes for Mixing
+
+ Many modern block encryption functions, including DES and AES,
+ incorporate modules known as S-Boxes (substitution boxes). These
+ produce a smaller number of outputs from a larger number of inputs
+ through a complex non-linear mixing function that has the effect of
+ concentrating limited entropy from the inputs into the output.
+
+ S-Boxes sometimes incorporate bent Boolean functions (functions of an
+ even number of bits producing one output bit with maximum non-
+ linearity). Looking at the output for all input pairs differing in
+ any particular bit position, exactly half the outputs are different.
+ An S-Box in which each output bit is produced by a bent function such
+ that any linear combination of these functions is also a bent
+ function is called a "perfect S-Box".
+
+ S-boxes and various repeated applications or cascades of such boxes
+ can be used for mixing [SBOX1, SBOX2].
+
+5.4. Diffie-Hellman as a Mixing Function
+
+ Diffie-Hellman exponential key exchange is a technique that yields a
+ shared secret between two parties. It can be computationally
+ infeasible for a third party to determine this secret even if they
+ can observe all the messages between the two communicating parties.
+ This shared secret is a mixture of initial quantities generated by
+ each of the parties [D-H].
+
+ If these initial quantities are random and uncorrelated, then the
+ shared secret combines their entropy but, of course, can not produce
+ more randomness than the size of the shared secret generated.
+
+ Although this is true if the Diffie-Hellman computation is performed
+ privately, an adversary who can observe either of the public keys and
+ knows the modulus being used need only search through the space of
+ the other secret key in order to be able to calculate the shared
+ secret [D-H]. So, conservatively, it would be best to consider
+ public Diffie-Hellman to produce a quantity whose guessability
+ corresponds to the worse of the two inputs. Because of this and the
+ fact that Diffie-Hellman is computationally intensive, its use as a
+ mixing function is not recommended.
+
+
+
+Eastlake, et al. Standards Track [Page 19]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+5.5. Using a Mixing Function to Stretch Random Bits
+
+ Although it is not necessary for a mixing function to produce the
+ same or fewer output bits than its inputs, mixing bits cannot
+ "stretch" the amount of random unpredictability present in the
+ inputs. Thus, four inputs of 32 bits each, in which there are 12
+ bits worth of unpredictability (such as 4,096 equally probable
+ values) in each input, cannot produce more than 48 bits worth of
+ unpredictable output. The output can be expanded to hundreds or
+ thousands of bits by, for example, mixing with successive integers,
+ but the clever adversary's search space is still 2^48 possibilities.
+ Furthermore, mixing to fewer bits than are input will tend to
+ strengthen the randomness of the output.
+
+ The last table in Section 5.1 shows that mixing a random bit with a
+ constant bit with Exclusive Or will produce a random bit. While this
+ is true, it does not provide a way to "stretch" one random bit into
+ more than one. If, for example, a random bit is mixed with a 0 and
+ then with a 1, this produces a two bit sequence but it will always be
+ either 01 or 10. Since there are only two possible values, there is
+ still only the one bit of original randomness.
+
+5.6. Other Factors in Choosing a Mixing Function
+
+ For local use, AES has the advantages that it has been widely tested
+ for flaws, is reasonably efficient in software, and is widely
+ documented and implemented with hardware and software implementations
+ available all over the world including open source code. The SHA*
+ family have had a little less study and tend to require more CPU
+ cycles than AES but there is no reason to believe they are flawed.
+ Both SHA* and MD5 were derived from the earlier MD4 algorithm. They
+ all have source code available [SHA*, MD4, MD5]. Some signs of
+ weakness have been found in MD4 and MD5. In particular, MD4 has only
+ three rounds and there are several independent breaks of the first
+ two or last two rounds. And some collisions have been found in MD5
+ output.
+
+ AES was selected by a robust, public, and international process. It
+ and SHA* have been vouched for by the US National Security Agency
+ (NSA) on the basis of criteria that mostly remain secret, as was DES.
+ While this has been the cause of much speculation and doubt,
+ investigation of DES over the years has indicated that NSA
+ involvement in modifications to its design, which originated with
+ IBM, was primarily to strengthen it. There has been no announcement
+ of a concealed or special weakness being found in DES. It is likely
+ that the NSA modifications to MD4 to produce the SHA algorithms
+ similarly strengthened these algorithms, possibly against threats not
+ yet known in the public cryptographic community.
+
+
+
+Eastlake, et al. Standards Track [Page 20]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ Where input lengths are unpredictable, hash algorithms are more
+ convenient to use than block encryption algorithms since they are
+ generally designed to accept variable length inputs. Block
+ encryption algorithms generally require an additional padding
+ algorithm to accommodate inputs that are not an even multiple of the
+ block size.
+
+ As of the time of this document, the authors know of no patent claims
+ to the basic AES, DES, SHA*, MD4, and MD5 algorithms other than
+ patents for which an irrevocable royalty free license has been
+ granted to the world. There may, of course, be essential patents of
+ which the authors are unaware or patents on implementations or uses
+ or other relevant patents issued or to be issued.
+
+6. Pseudo-random Number Generators
+
+ When a seed has sufficient entropy, from input as described in
+ Section 3 and possibly de-skewed and mixed as described in Sections 4
+ and 5, one can algorithmically extend that seed to produce a large
+ number of cryptographically-strong random quantities. Such
+ algorithms are platform independent and can operate in the same
+ fashion on any computer. For the algorithms to be secure, their
+ input and internal workings must be protected from adversarial
+ observation.
+
+ The design of such pseudo-random number generation algorithms, like
+ the design of symmetric encryption algorithms, is not a task for
+ amateurs. Section 6.1 below lists a number of bad ideas that failed
+ algorithms have used. To learn what works, skip Section 6.1 and just
+ read the remainder of this section and Section 7, which describes and
+ references some standard pseudo random number generation algorithms.
+ See Section 7 and Part 3 of [X9.82].
+
+6.1. Some Bad Ideas
+
+ The subsections below describe a number of ideas that might seem
+ reasonable but that lead to insecure pseudo-random number generation.
+
+6.1.1. The Fallacy of Complex Manipulation
+
+ One approach that may give a misleading appearance of
+ unpredictability is to take a very complex algorithm (or an excellent
+ traditional pseudo-random number generator with good statistical
+ properties) and to calculate a cryptographic key by starting with
+ limited data such as the computer system clock value as the seed.
+ Adversaries who knew roughly when the generator was started would
+ have a relatively small number of seed values to test, as they would
+ know likely values of the system clock. Large numbers of pseudo-
+
+
+
+Eastlake, et al. Standards Track [Page 21]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ random bits could be generated, but the search space that an
+ adversary would need to check could be quite small.
+
+ Thus, very strong or complex manipulation of data will not help if
+ the adversary can learn what the manipulation is and if there is not
+ enough entropy in the starting seed value. They can usually use the
+ limited number of results stemming from a limited number of seed
+ values to defeat security.
+
+ Another serious strategic error is to assume that a very complex
+ pseudo-random number generation algorithm will produce strong random
+ numbers, when there has been no theory behind or analysis of the
+ algorithm. There is a excellent example of this fallacy near the
+ beginning of Chapter 3 in [KNUTH], where the author describes a
+ complex algorithm. It was intended that the machine language program
+ corresponding to the algorithm would be so complicated that a person
+ trying to read the code without comments wouldn't know what the
+ program was doing. Unfortunately, actual use of this algorithm
+ showed that it almost immediately converged to a single repeated
+ value in one case and a small cycle of values in another case.
+
+ Not only does complex manipulation not help you if you have a limited
+ range of seeds, but blindly-chosen complex manipulation can destroy
+ the entropy in a good seed!
+
+6.1.2. The Fallacy of Selection from a Large Database
+
+ Another approach that can give a misleading appearance of
+ unpredictability is to randomly select a quantity from a database and
+ to assume that its strength is related to the total number of bits in
+ the database. For example, typical USENET servers process many
+ megabytes of information per day [USENET_1, USENET_2]. Assume that a
+ random quantity was selected by fetching 32 bytes of data from a
+ random starting point in this data. This does not yield 32*8 = 256
+ bits worth of unguessability. Even if much of the data is human
+ language that contains no more than 2 or 3 bits of information per
+ byte, it doesn't yield 32*2 = 64 bits of unguessability. For an
+ adversary with access to the same Usenet database, the unguessability
+ rests only on the starting point of the selection. That is perhaps a
+ little over a couple of dozen bits of unguessability.
+
+ The same argument applies to selecting sequences from the data on a
+ publicly available CD/DVD recording or any other large public
+ database. If the adversary has access to the same database, this
+ "selection from a large volume of data" step buys little. However,
+ if a selection can be made from data to which the adversary has no
+ access, such as system buffers on an active multi-user system, it may
+ be of help.
+
+
+
+Eastlake, et al. Standards Track [Page 22]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+6.1.3. Traditional Pseudo-random Sequences
+
+ This section talks about traditional sources of deterministic or
+ "pseudo-random" numbers. These typically start with a "seed"
+ quantity and use simple numeric or logical operations to produce a
+ sequence of values. Note that none of the techniques discussed in
+ this section is suitable for cryptographic use. They are presented
+ for general information.
+
+ [KNUTH] has a classic exposition on pseudo-random numbers.
+ Applications he mentions are simulations of natural phenomena,
+ sampling, numerical analysis, testing computer programs, decision
+ making, and games. None of these have the same characteristics as
+ the sorts of security uses we are talking about. Only in the last
+ two could there be an adversary trying to find the random quantity.
+ However, in these cases, the adversary normally has only a single
+ chance to use a guessed value. In guessing passwords or attempting
+ to break an encryption scheme, the adversary normally has many,
+ perhaps unlimited, chances at guessing the correct value. Sometimes
+ the adversary can store the message to be broken and repeatedly
+ attack it. Adversaries are also be assumed to be aided by a
+ computer.
+
+ For testing the "randomness" of numbers, Knuth suggests a variety of
+ measures, including statistical and spectral. These tests check
+ things like autocorrelation between different parts of a "random"
+ sequence or distribution of its values. But these tests could be met
+ by a constant stored random sequence, such as the "random" sequence
+ printed in the CRC Standard Mathematical Tables [CRC]. Despite
+ meeting all the tests suggested by Knuth, that sequence is unsuitable
+ for cryptographic us, as adversaries must be assumed to have copies
+ of all commonly published "random" sequences and to be able to spot
+ the source and predict future values.
+
+ A typical pseudo-random number generation technique is the linear
+ congruence pseudo-random number generator. This technique uses
+ modular arithmetic, where the value numbered N+1 is calculated from
+ the value numbered N by
+
+ V = ( V * a + b )(Mod c)
+ N+1 N
+
+ The above technique has a strong relationship to linear shift
+ register pseudo-random number generators, which are well understood
+ cryptographically [SHIFT*]. In such generators, bits are introduced
+ at one end of a shift register as the Exclusive Or (binary sum
+ without carry) of bits from selected fixed taps into the register.
+ For example, consider the following:
+
+
+
+Eastlake, et al. Standards Track [Page 23]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ +----+ +----+ +----+ +----+
+ | B | <-- | B | <-- | B | <-- . . . . . . <-- | B | <-+
+ | 0 | | 1 | | 2 | | n | |
+ +----+ +----+ +----+ +----+ |
+ | | | |
+ | | V +-----+
+ | V +----------------> | |
+ V +-----------------------------> | XOR |
+ +---------------------------------------------------> | |
+ +-----+
+
+ V = ( ( V * 2 ) + B XOR B ... )(Mod 2^n)
+ N+1 N 0 2
+
+ The quality of traditional pseudo-random number generator algorithms
+ is measured by statistical tests on such sequences. Carefully-chosen
+ values a, b, c, and initial V or carefully-chosen placement of the
+ shift register tap in the above simple process can produce excellent
+ statistics.
+
+ These sequences may be adequate in simulations (Monte Carlo
+ experiments) as long as the sequence is orthogonal to the structure
+ of the space being explored. Even there, subtle patterns may cause
+ problems. However, such sequences are clearly bad for use in
+ security applications. They are fully predictable if the initial
+ state is known. Depending on the form of the pseudo-random number
+ generator, the sequence may be determinable from observation of a
+ short portion of the sequence [SCHNEIER, STERN]. For example, with
+ the generators above, one can determine V(n+1) given knowledge of
+ V(n). In fact, it has been shown that with these techniques, even if
+ only one bit of the pseudo-random values are released, the seed can
+ be determined from short sequences.
+
+ Not only have linear congruent generators been broken, but techniques
+ are now known for breaking all polynomial congruent generators
+ [KRAWCZYK].
+
+6.2. Cryptographically Strong Sequences
+
+ In cases where a series of random quantities must be generated, an
+ adversary may learn some values in the sequence. In general,
+ adversaries should not be able to predict other values from the ones
+ that they know.
+
+ The correct technique is to start with a strong random seed, to take
+ cryptographically strong steps from that seed [FERGUSON, SCHNEIER],
+ and not to reveal the complete state of the generator in the sequence
+ elements. If each value in the sequence can be calculated in a fixed
+
+
+
+Eastlake, et al. Standards Track [Page 24]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ way from the previous value, then when any value is compromised, all
+ future values can be determined. This would be the case, for
+ example, if each value were a constant function of the previously
+ used values, even if the function were a very strong, non-invertible
+ message digest function.
+
+ (Note that if a technique for generating a sequence of key values is
+ fast enough, it can trivially be used as the basis for a
+ confidentiality system. If two parties use the same sequence
+ generation technique and start with the same seed material, they will
+ generate identical sequences. These could, for example, be XOR'ed at
+ one end with data being sent to encrypt it, and XOR'ed with this data
+ as received to decrypt it, due to the reversible properties of the
+ XOR operation. This is commonly referred to as a simple stream
+ cipher.)
+
+6.2.1. OFB and CTR Sequences
+
+ One way to produce a strong sequence is to take a seed value and hash
+ the quantities produced by concatenating the seed with successive
+ integers, or the like, and then to mask the values obtained so as to
+ limit the amount of generator state available to the adversary.
+
+ It may also be possible to use an "encryption" algorithm with a
+ random key and seed value to encrypt successive integers, as in
+ counter (CTR) mode encryption. Alternatively, one can feedback all
+ of the output value from encryption into the value to be encrypted
+ for the next iteration. This is a particular example of output
+ feedback mode (OFB) [MODES].
+
+ An example is shown below in which shifting and masking are used to
+ combine part of the output feedback with part of the old input. This
+ type of partial feedback should be avoided for reasons described
+ below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 25]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ +---------------+
+ | V |
+ | | n |--+
+ +--+------------+ |
+ | | +---------+
+ shift| +---> | | +-----+
+ +--+ | Encrypt | <--- | Key |
+ | +-------- | | +-----+
+ | | +---------+
+ V V
+ +------------+--+
+ | V | |
+ | n+1 |
+ +---------------+
+
+ Note that if a shift of one is used, this is the same as the shift
+ register technique described in Section 6.1.3, but with the all-
+ important difference that the feedback is determined by a complex
+ non-linear function of all bits rather than by a simple linear or
+ polynomial combination of output from a few bit position taps.
+
+ Donald W. Davies showed that this sort of shifted partial output
+ feedback significantly weakens an algorithm, compared to feeding all
+ the output bits back as input. In particular, for DES, repeatedly
+ encrypting a full 64-bit quantity will give an expected repeat in
+ about 2^63 iterations. Feeding back anything less than 64 (and more
+ than 0) bits will give an expected repeat in between 2^31 and 2^32
+ iterations!
+
+ To predict values of a sequence from others when the sequence was
+ generated by these techniques is equivalent to breaking the
+ cryptosystem or to inverting the "non-invertible" hashing with only
+ partial information available. The less information revealed in each
+ iteration, the harder it will be for an adversary to predict the
+ sequence. Thus it is best to use only one bit from each value. It
+ has been shown that in some cases this makes it impossible to break a
+ system even when the cryptographic system is invertible and could be
+ broken if all of each generated value were revealed.
+
+6.2.2. The Blum Blum Shub Sequence Generator
+
+ Currently the generator which has the strongest public proof of
+ strength is called the Blum Blum Shub generator, named after its
+ inventors [BBS]. It is also very simple and is based on quadratic
+ residues. Its only disadvantage is that it is computationally
+ intensive compared to the traditional techniques given in Section
+ 6.1.3. This is not a major drawback if it is used for moderately-
+ infrequent purposes, such as generating session keys.
+
+
+
+Eastlake, et al. Standards Track [Page 26]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ Simply choose two large prime numbers (say, p and q) that each gives
+ a remainder of 3 when divided by 4. Let n = p * q. Then choose a
+ random number, x, that is relatively prime to n. The initial seed
+ for the generator and the method for calculating subsequent values
+ are then:
+
+ 2
+ s = ( x )(Mod n)
+ 0
+ 2
+ s = ( s )(Mod n)
+ i+1 i
+
+ Be careful to use only a few bits from the bottom of each s. It is
+ always safe to use only the lowest-order bit. If one uses no more
+ than the:
+
+ log ( log ( s ) )
+ 2 2 i
+
+ low-order bits, then predicting any additional bits from a sequence
+ generated in this manner is provably as hard as factoring n. As long
+ as the initial x is secret, n can be made public if desired.
+
+ An interesting characteristic of this generator is that any of the s
+ values can be directly calculated. In particular,
+
+ ( (2^i) (Mod ((p-1)*(q-1)) ) )
+ s = ( s )(Mod n)
+ i 0
+
+ This means that in applications where many keys are generated in this
+ fashion, it is not necessary to save them all. Each key can be
+ effectively indexed and recovered from that small index and the
+ initial s and n.
+
+6.3. Entropy Pool Techniques
+
+ Many modern pseudo-random number sources, such as those described in
+ Sections 7.1.2 and 7.1.3 utilize the technique of maintaining a
+ "pool" of bits and providing operations for strongly mixing input
+ with some randomness into the pool and extracting pseudo-random bits
+ from the pool. This is illustrated in the figure below.
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 27]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ +--------+ +------+ +---------+
+ --->| Mix In |--->| POOL |--->| Extract |--->
+ | Bits | | | | Bits |
+ +--------+ +------+ +---------+
+ ^ V
+ | |
+ +-----------+
+
+ Bits to be fed into the pool can come from any of the various
+ hardware, environmental, or user input sources discussed above. It
+ is also common to save the state of the pool on system shutdown and
+ to restore it on re-starting, when stable storage is available.
+
+ Care must be taken that enough entropy has been added to the pool to
+ support particular output uses desired. See [RSA_BULL1] for similar
+ suggestions.
+
+7. Randomness Generation Examples and Standards
+
+ Several public standards and widely deployed examples are now in
+ place for the generation of keys or other cryptographically random
+ quantities. Some, in section 7.1, include an entropy source.
+ Others, described in section 7.2, provide the pseudo-random number
+ strong-sequence generator but assume the input of a random seed or
+ input from a source of entropy.
+
+7.1. Complete Randomness Generators
+
+ Three standards are described below. The two older standards use
+ DES, with its 64-bit block and key size limit, but any equally strong
+ or stronger mixing function could be substituted [DES]. The third is
+ a more modern and stronger standard based on SHA-1 [SHA*]. Lastly,
+ the widely deployed modern UNIX and Windows random number generators
+ are described.
+
+7.1.1. US DoD Recommendations for Password Generation
+
+ The United States Department of Defense has specific recommendations
+ for password generation [DoD]. It suggests using the US Data
+ Encryption Standard [DES] in Output Feedback Mode [MODES] as follows:
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 28]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ Use an initialization vector determined from
+ the system clock,
+ system ID,
+ user ID, and
+ date and time;
+ use a key determined from
+ system interrupt registers,
+ system status registers, and
+ system counters; and,
+ as plain text, use an external randomly generated 64-bit
+ quantity such as the ASCII bytes for 8 characters typed
+ in by a system administrator.
+
+ The password can then be calculated from the 64 bit "cipher text"
+ generated by DES in 64-bit Output Feedback Mode. As many bits as are
+ needed can be taken from these 64 bits and expanded into a
+ pronounceable word, phrase, or other format if a human being needs to
+ remember the password.
+
+7.1.2. The /dev/random Device
+
+ Several versions of the UNIX operating system provide a kernel-
+ resident random number generator. Some of these generators use
+ events captured by the Kernel during normal system operation.
+
+ For example, on some versions of Linux, the generator consists of a
+ random pool of 512 bytes represented as 128 words of 4 bytes each.
+ When an event occurs, such as a disk drive interrupt, the time of the
+ event is XOR'ed into the pool, and the pool is stirred via a
+ primitive polynomial of degree 128. The pool itself is treated as a
+ ring buffer, with new data being XOR'ed (after stirring with the
+ polynomial) across the entire pool.
+
+ Each call that adds entropy to the pool estimates the amount of
+ likely true entropy the input contains. The pool itself contains a
+ accumulator that estimates the total over all entropy of the pool.
+
+ Input events come from several sources, as listed below.
+ Unfortunately, for server machines without human operators, the first
+ and third are not available, and entropy may be added slowly in that
+ case.
+
+ 1. Keyboard interrupts. The time of the interrupt and the scan code
+ are added to the pool. This in effect adds entropy from the human
+ operator by measuring inter-keystroke arrival times.
+
+ 2. Disk completion and other interrupts. A system being used by a
+ person will likely have a hard-to-predict pattern of disk
+
+
+
+Eastlake, et al. Standards Track [Page 29]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ accesses. (But not all disk drivers support capturing this timing
+ information with sufficient accuracy to be useful.)
+
+ 3. Mouse motion. The timing and mouse position are added in.
+
+ When random bytes are required, the pool is hashed with SHA-1 [SHA*]
+ to yield the returned bytes of randomness. If more bytes are
+ required than the output of SHA-1 (20 bytes), then the hashed output
+ is stirred back into the pool and a new hash is performed to obtain
+ the next 20 bytes. As bytes are removed from the pool, the estimate
+ of entropy is correspondingly decremented.
+
+ To ensure a reasonably random pool upon system startup, the standard
+ startup and shutdown scripts save the pool to a disk file at shutdown
+ and read this file at system startup.
+
+ There are two user-exported interfaces. /dev/random returns bytes
+ from the pool but blocks when the estimated entropy drops to zero.
+ As entropy is added to the pool from events, more data becomes
+ available via /dev/random. Random data obtained from such a
+ /dev/random device is suitable for key generation for long term keys,
+ if enough random bits are in the pool or are added in a reasonable
+ amount of time.
+
+ /dev/urandom works like /dev/random; however, it provides data even
+ when the entropy estimate for the random pool drops to zero. This
+ may be adequate for session keys or for other key generation tasks
+ for which blocking to await more random bits is not acceptable. The
+ risk of continuing to take data even when the pool's entropy estimate
+ is small in that past output may be computable from current output,
+ provided that an attacker can reverse SHA-1. Given that SHA-1 is
+ designed to be non-invertible, this is a reasonable risk.
+
+ To obtain random numbers under Linux, Solaris, or other UNIX systems
+ equipped with code as described above, all an application has to do
+ is open either /dev/random or /dev/urandom and read the desired
+ number of bytes.
+
+ (The Linux Random device was written by Theodore Ts'o. It was based
+ loosely on the random number generator in PGP 2.X and PGP 3.0 (aka
+ PGP 5.0).)
+
+7.1.3. Windows CryptGenRandom
+
+ Microsoft's recommendation to users of the widely deployed Windows
+ operating system is generally to use the CryptGenRandom pseudo-random
+ number generation call with the CryptAPI cryptographic service
+ provider. This takes a handle to a cryptographic service provider
+
+
+
+Eastlake, et al. Standards Track [Page 30]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ library, a pointer to a buffer by which the caller can provide
+ entropy and into which the generated pseudo-randomness is returned,
+ and an indication of how many octets of randomness are desired.
+
+ The Windows CryptAPI cryptographic service provider stores a seed
+ state variable with every user. When CryptGenRandom is called, this
+ is combined with any randomness provided in the call and with various
+ system and user data such as the process ID, thread ID, system clock,
+ system time, system counter, memory status, free disk clusters, and
+ hashed user environment block. This data is all fed to SHA-1, and
+ the output is used to seed an RC4 key stream. That key stream is
+ used to produce the pseudo-random data requested and to update the
+ user's seed state variable.
+
+ Users of Windows ".NET" will probably find it easier to use the
+ RNGCryptoServiceProvider.GetBytes method interface.
+
+ For further information, see [WSC].
+
+7.2. Generators Assuming a Source of Entropy
+
+ The pseudo-random number generators described in the following three
+ sections all assume that a seed value with sufficient entropy is
+ provided to them. They then generate a strong sequence (see Section
+ 6.2) from that seed.
+
+7.2.1. X9.82 Pseudo-Random Number Generation
+
+ The ANSI X9F1 committee is in the final stages of creating a standard
+ for random number generation covering both true randomness generators
+ and pseudo-random number generators. It includes a number of
+ pseudo-random number generators based on hash functions, one of which
+ will probably be based on HMAC SHA hash constructs [RFC2104]. The
+ draft version of this generator is described below, omitting a number
+ of optional features [X9.82].
+
+ In the subsections below, the HMAC hash construct is simply referred
+ to as HMAC but, of course, a particular standard SHA function must be
+ selected in an particular use. Generally speaking, if the strength
+ of the pseudo-random values to be generated is to be N bits, the SHA
+ function chosen must generate N or more bits of output, and a source
+ of at least N bits of input entropy will be required. The same hash
+ function must be used throughout an instantiation of this generator.
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 31]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+7.2.1.1. Notation
+
+ In the following sections, the notation give below is used:
+
+ hash_length is the output size of the underlying hash function in
+ use.
+
+ input_entropy is the input bit string that provides entropy to the
+ generator.
+
+ K is a bit string of size hash_length that is part of the state of
+ the generator and is updated at least once each time random
+ bits are generated.
+
+ V is a bit string of size hash_length and is part of the state of
+ the generator. It is updated each time hash_length bits of
+ output are generated.
+
+ "|" represents concatenation.
+
+7.2.1.2. Initializing the Generator
+
+ Set V to all zero bytes, except the low-order bit of each byte is set
+ to one.
+
+ Set K to all zero bytes, then set:
+
+ K = HMAC ( K, V | 0x00 | input_entropy )
+
+ V = HMAC ( K, V )
+
+ K = HMAC ( K, V | 0x01 | input_entropy )
+
+ V = HMAC ( K, V )
+
+ Note: All SHA algorithms produce an integral number of bytes, so the
+ lengths of K and V will be integral numbers of bytes.
+
+7.2.1.3. Generating Random Bits
+
+ When output is called for, simply set:
+
+ V = HMAC ( K, V )
+
+ and use the leading bits from V. If more bits are needed than the
+ length of V, set "temp" to a null bit string and then repeatedly
+ perform:
+
+
+
+
+Eastlake, et al. Standards Track [Page 32]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ V = HMAC ( K, V )
+ temp = temp | V
+
+ stopping as soon as temp is equal to or longer than the number of
+ random bits requested. Use the requested number of leading bits from
+ temp. The definition of the algorithm prohibits requesting more than
+ 2^35 bits.
+
+ After extracting and saving the pseudo-random output bits as
+ described above, before returning you must also perform two more
+ HMACs as follows:
+
+ K = HMAC ( K, V | 0x00 )
+ V = HMAC ( K, V )
+
+7.2.2. X9.17 Key Generation
+
+ The American National Standards Institute has specified the
+ following method for generating a sequence of keys [X9.17]:
+
+ s is the initial 64 bit seed.
+ 0
+
+ g is the sequence of generated 64-bit key quantities
+ n
+
+ k is a random key reserved for generating this key sequence.
+
+ t is the time at which a key is generated, to as fine a resolution
+ as is available (up to 64 bits).
+
+ DES ( K, Q ) is the DES encryption of quantity Q with key K.
+
+ Then:
+
+ g = DES ( k, DES ( k, t ) XOR s )
+ n n
+
+ s = DES ( k, DES ( k, t ) XOR g )
+ n+1 n
+
+
+ If g sub n is to be used as a DES key, then every eighth bit should
+ be adjusted for parity for that use, but the entire 64 bit unmodified
+ g should be used in calculating the next s.
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 33]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+7.2.3. DSS Pseudo-random Number Generation
+
+ Appendix 3 of the NIST Digital Signature Standard [DSS] provides a
+ method of producing a sequence of pseudo-random 160 bit quantities
+ for use as private keys or the like. This has been modified by
+ Change Notice 1 [DSS_CN1] to produce the following algorithm for
+ generating general-purpose pseudo-random numbers:
+
+ t = 0x 67452301 EFCDAB89 98BADCFE 10325476 C3D2E1F0
+
+ XKEY = initial seed
+ 0
+
+ For j = 0 to ...
+
+ XVAL = ( XKEY + optional user input ) (Mod 2^512)
+ j
+
+ X = G( t, XVAL )
+ j
+
+ XKEY = ( 1 + XKEY + X ) (Mod 2^512)
+ j+1 j j
+
+
+ The quantities X thus produced are the pseudo-random sequence of
+ 160-bit values. Two functions can be used for "G" above. Each
+ produces a 160-bit value and takes two arguments, a 160-bit value and
+ a 512 bit value.
+
+ The first is based on SHA-1 and works by setting the 5 linking
+ variables, denoted H with subscripts in the SHA-1 specification, to
+ the first argument divided into fifths. Then steps (a) through (e)
+ of section 7 of the NIST SHA-1 specification are run over the second
+ argument as if it were a 512-bit data block. The values of the
+ linking variable after those steps are then concatenated to produce
+ the output of G [SHA*].
+
+ As an alternative method, NIST also defined an alternate G function
+ based on multiple applications of the DES encryption function [DSS].
+
+8. Examples of Randomness Required
+
+ Below are two examples showing rough calculations of randomness
+ needed for security. The first is for moderate security passwords,
+ while the second assumes a need for a very high-security
+ cryptographic key.
+
+
+
+
+Eastlake, et al. Standards Track [Page 34]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ In addition, [ORMAN] and [RSA_BULL13] provide information on the
+ public key lengths that should be used for exchanging symmetric keys.
+
+8.1. Password Generation
+
+ Assume that user passwords change once a year and that it is desired
+ that the probability that an adversary could guess the password for a
+ particular account be less than one in a thousand. Further assume
+ that sending a password to the system is the only way to try a
+ password. Then the crucial question is how often an adversary can
+ try possibilities. Assume that delays have been introduced into a
+ system so that an adversary can make at most one password try every
+ six seconds. That's 600 per hour, or about 15,000 per day, or about
+ 5,000,000 tries in a year. Assuming any sort of monitoring, it is
+ unlikely that someone could actually try continuously for a year.
+ Even if log files are only checked monthly, 500,000 tries is more
+ plausible before the attack is noticed and steps are taken to change
+ passwords and make it harder to try more passwords.
+
+ To have a one-in-a-thousand chance of guessing the password in
+ 500,000 tries implies a universe of at least 500,000,000 passwords,
+ or about 2^29. Thus, 29 bits of randomness are needed. This can
+ probably be achieved by using the US DoD-recommended inputs for
+ password generation, as it has 8 inputs that probably average over 5
+ bits of randomness each (see section 7.1). Using a list of 1,000
+ words, the password could be expressed as a three-word phrase
+ (1,000,000,000 possibilities). By using case-insensitive letters and
+ digits, six characters would suffice ((26+10)^6 = 2,176,782,336
+ possibilities).
+
+ For a higher-security password, the number of bits required goes up.
+ To decrease the probability by 1,000 requires increasing the universe
+ of passwords by the same factor, which adds about 10 bits. Thus, to
+ have only a one in a million chance of a password being guessed under
+ the above scenario would require 39 bits of randomness and a password
+ that was a four-word phrase from a 1,000 word list, or eight
+ letters/digits. To go to a one-in-10^9 chance, 49 bits of randomness
+ are needed, implying a five-word phrase or a ten-letter/digit
+ password.
+
+ In a real system, of course, there are other factors. For example,
+ the larger and harder to remember passwords are, the more likely
+ users will bed to write them down, resulting in an additional risk of
+ compromise.
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 35]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+8.2. A Very High Security Cryptographic Key
+
+ Assume that a very high security key is needed for symmetric
+ encryption/decryption between two parties. Assume also that an
+ adversary can observe communications and knows the algorithm being
+ used. Within the field of random possibilities, the adversary can
+ try key values in hopes of finding the one in use. Assume further
+ that brute force trial of keys is the best the adversary can do.
+
+8.2.1. Effort per Key Trial
+
+ How much effort will it take to try each key? For very high-security
+ applications, it is best to assume a low value of effort. Even if it
+ would clearly take tens of thousands of computer cycles or more to
+ try a single key, there may be some pattern that enables huge blocks
+ of key values to be tested with much less effort per key. Thus, it
+ is probably best to assume no more than a couple of hundred cycles
+ per key. (There is no clear lower bound on this, as computers
+ operate in parallel on a number of bits and a poor encryption
+ algorithm could allow many keys or even groups of keys to be tested
+ in parallel. However, we need to assume some value and can hope that
+ a reasonably strong algorithm has been chosen for our hypothetical
+ high-security task.)
+
+ If the adversary can command a highly parallel processor or a large
+ network of work stations, 10^11 cycles per second is probably a
+ minimum assumption today. Looking forward a few years, there should
+ be at least an order of magnitude improvement. Thus, it is
+ reasonable to assume that 10^10 keys could be checked per second, or
+ 3.6*10^12 per hour or 6*10^14 per week, or 2.4*10^15 per month. This
+ implies a need for a minimum of 63 bits of randomness in keys, to be
+ sure that they cannot be found in a month. Even then it is possible
+ that, a few years from now, a highly determined and resourceful
+ adversary could break the key in 2 weeks; on average, they need try
+ only half the keys.
+
+ These questions are considered in detail in "Minimal Key Lengths for
+ Symmetric Ciphers to Provide Adequate Commercial Security: A Report
+ by an Ad Hoc Group of Cryptographers and Computer Scientists"
+ [KeyStudy] that was sponsored by the Business Software Alliance. It
+ concluded that a reasonable key length in 1995 for very high security
+ is in the range of 75 to 90 bits and, since the cost of cryptography
+ does not vary much with the key size, it recommends 90 bits. To
+ update these recommendations, just add 2/3 of a bit per year for
+ Moore's law [MOORE]. This translates to a determination, in the year
+ 2004, a reasonable key length is in the 81- to 96-bit range. In
+ fact, today, it is increasingly common to use keys longer than 96
+
+
+
+
+Eastlake, et al. Standards Track [Page 36]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ bits, such as 128-bit (or longer) keys with AES and keys with
+ effective lengths of 112-bits with triple-DES.
+
+8.2.2. Meet-in-the-Middle Attacks
+
+ If chosen or known plain text and the resulting encrypted text are
+ available, a "meet-in-the-middle" attack is possible if the structure
+ of the encryption algorithm allows it. (In a known plain text
+ attack, the adversary knows all or part (possibly some standard
+ header or trailer fields) of the messages being encrypted. In a
+ chosen plain text attack, the adversary can force some chosen plain
+ text to be encrypted, possibly by "leaking" an exciting text that is
+ sent by the adversary over an encrypted channel because the text is
+ so interesting.
+
+ The following is an oversimplified explanation of the meet-in-the-
+ middle attack: the adversary can half-encrypt the known or chosen
+ plain text with all possible first half-keys, sort the output, and
+ then half-decrypt the encoded text with all the second half-keys. If
+ a match is found, the full key can be assembled from the halves and
+ used to decrypt other parts of the message or other messages. At its
+ best, this type of attack can halve the exponent of the work required
+ by the adversary while adding a very large but roughly constant
+ factor of effort. Thus, if this attack can be mounted, a doubling of
+ the amount of randomness in the very strong key to a minimum of 192
+ bits (96*2) is required for the year 2004, based on the [KeyStudy]
+ analysis.
+
+ This amount of randomness is well beyond the limit of that in the
+ inputs recommended by the US DoD for password generation and could
+ require user-typing timing, hardware random number generation, or
+ other sources of randomness.
+
+ The meet-in-the-middle attack assumes that the cryptographic
+ algorithm can be decomposed in this way. Hopefully no modern
+ algorithm has this weakness, but there may be cases where we are not
+ sure of that or even of what algorithm a key will be used with. Even
+ if a basic algorithm is not subject to a meet-in-the-middle attack,
+ an attempt to produce a stronger algorithm by applying the basic
+ algorithm twice (or two different algorithms sequentially) with
+ different keys will gain less added security than would be expected.
+ Such a composite algorithm would be subject to a meet-in-the-middle
+ attack.
+
+ Enormous resources may be required to mount a meet-in-the-middle
+ attack, but they are probably within the range of the national
+ security services of a major nation. Essentially all nations spy on
+ other nations' traffic.
+
+
+
+Eastlake, et al. Standards Track [Page 37]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+8.2.3. Other Considerations
+
+ [KeyStudy] also considers the possibilities of special-purpose code-
+ breaking hardware and having an adequate safety margin.
+
+ Note that key length calculations such as those above are
+ controversial and depend on various assumptions about the
+ cryptographic algorithms in use. In some cases, a professional with
+ a deep knowledge of algorithm-breaking techniques and of the strength
+ of the algorithm in use could be satisfied with less than half of the
+ 192 bit key size derived above.
+
+ For further examples of conservative design principles, see
+ [FERGUSON].
+
+9. Conclusion
+
+ Generation of unguessable "random" secret quantities for security use
+ is an essential but difficult task.
+
+ Hardware techniques for producing the needed entropy would be
+ relatively simple. In particular, the volume and quality would not
+ need to be high, and existing computer hardware, such as audio input
+ or disk drives, can be used.
+
+ Widely-available computational techniques can process low-quality
+ random quantities from multiple sources, or a larger quantity of such
+ low-quality input from one source, to produce a smaller quantity of
+ higher-quality keying material. In the absence of hardware sources
+ of randomness, a variety of user and software sources can frequently,
+ with care, be used instead. However, most modern systems already
+ have hardware, such as disk drives or audio input, that could be used
+ to produce high-quality randomness.
+
+ Once a sufficient quantity of high-quality seed key material (a
+ couple of hundred bits) is available, computational techniques are
+ available to produce cryptographically-strong sequences of
+ computationally-unpredictable quantities from this seed material.
+
+10. Security Considerations
+
+ The entirety of this document concerns techniques and recommendations
+ for generating unguessable "random" quantities for use as passwords,
+ cryptographic keys, initialization vectors, sequence numbers, and
+ similar security applications.
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 38]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+11. Acknowledgements
+
+ Special thanks to Paul Hoffman and John Kelsey for their extensive
+ comments and to Peter Gutmann, who has permitted the incorporation of
+ material from his paper "Software Generation of Practically Strong
+ Random Numbers".
+
+ The following people (in alphabetic order) have contributed
+ substantially to this document:
+
+ Steve Bellovin, Daniel Brown, Don Davis, Peter Gutmann, Tony
+ Hansen, Sandy Harris, Paul Hoffman, Scott Hollenback, Russ
+ Housley, Christian Huitema, John Kelsey, Mats Naslund, and Damir
+ Rajnovic.
+
+ The following people (in alphabetic order) contributed to RFC 1750,
+ the predecessor of this document:
+
+ David M. Balenson, Don T. Davis, Carl Ellison, Marc Horowitz,
+ Christian Huitema, Charlie Kaufman, Steve Kent, Hal Murray, Neil
+ Haller, Richard Pitkin, Tim Redmond, and Doug Tygar.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 39]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+Appendix A: Changes from RFC 1750
+
+ 1. Additional acknowledgements have been added.
+
+ 2. Insertion of section 5.3 on mixing with S-boxes.
+
+ 3. Addition of section 3.3 on Ring Oscillator randomness sources.
+
+ 4. Addition of AES and the members of the SHA series producing more
+ than 160 bits. Use of AES has been emphasized and the use of DES
+ de-emphasized.
+
+ 5. Addition of section 6.3 on entropy pool techniques.
+
+ 6. Addition of section 7.2.3 on the pseudo-random number generation
+ techniques given in FIPS 186-2 (with Change Notice 1), 7.2.1 on
+ those given in X9.82, section 7.1.2 on the random number
+ generation techniques of the /dev/random device in Linux and other
+ UNIX systems, and section 7.1.3 on random number generation
+ techniques in the Windows operating system.
+
+ 7. Addition of references to the "Minimal Key Lengths for Symmetric
+ Ciphers to Provide Adequate Commercial Security" study published
+ in January 1996 [KeyStudy] and to [RFC1948].
+
+ 8. Added caveats to using Diffie-Hellman as a mixing function and,
+ because of those caveats and its computationally intensive nature,
+ recommend against its use.
+
+ 9. Addition of references to the X9.82 effort and the [TURBID] and
+ [NASLUND] papers.
+
+ 10. Addition of discussion of min-entropy and Renyi entropy and
+ references to the [LUBY] book.
+
+ 11. Major restructuring, minor wording changes, and a variety of
+ reference updates.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 40]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+Informative References
+
+ [AES] "Specification of the Advanced Encryption Standard
+ (AES)", United States of America, US National
+ Institute of Standards and Technology, FIPS 197,
+ November 2001.
+
+ [ASYMMETRIC] Simmons, G., Ed., "Secure Communications and
+ Asymmetric Cryptosystems", AAAS Selected Symposium
+ 69, ISBN 0-86531-338-5, Westview Press, 1982.
+
+ [BBS] Blum, L., Blum, M., and M. Shub, "A Simple
+ Unpredictable Pseudo-Random Number Generator", SIAM
+ Journal on Computing, v. 15, n. 2, 1986.
+
+ [BRILLINGER] Brillinger, D., "Time Series: Data Analysis and
+ Theory", Holden-Day, 1981.
+
+ [CRC] "C.R.C. Standard Mathematical Tables", Chemical
+ Rubber Publishing Company.
+
+ [DAVIS] Davis, D., Ihaka, R., and P. Fenstermacher,
+ "Cryptographic Randomness from Air Turbulence in Disk
+ Drives", Advances in Cryptology - Crypto '94,
+ Springer-Verlag Lecture Notes in Computer Science
+ #839, 1984.
+
+ [DES] "Data Encryption Standard", US National Institute of
+ Standards and Technology, FIPS 46-3, October 1999.
+ Also, "Data Encryption Algorithm", American National
+ Standards Institute, ANSI X3.92-1981. See also FIPS
+ 112, "Password Usage", which includes FORTRAN code
+ for performing DES.
+
+ [D-H] Rescorla, E., "Diffie-Hellman Key Agreement Method",
+ RFC 2631, June 1999.
+
+ [DNSSEC1] Arends, R., Austein, R., Larson, M., Massey, D., and
+ S. Rose, "DNS Security Introduction and
+ Requirements", RFC 4033, March 2005.
+
+ [DNSSEC2] Arends, R., Austein, R., Larson, M., Massey, D., and
+ S. Rose, "Resource Records for the DNS Security
+ Extensions", RFC 4034, March 2005.
+
+ [DNSSEC3] Arends, R., Austein, R., Larson, M., Massey, D., and
+ S. Rose, "Protocol Modifications for the DNS Security
+ Extensions", RFC 4035, March 2005.
+
+
+
+Eastlake, et al. Standards Track [Page 41]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ [DoD] "Password Management Guideline", United States of
+ America, Department of Defense, Computer Security
+ Center, CSC-STD-002-85, April 1885.
+
+ (See also "Password Usage", FIPS 112, which
+ incorporates CSC-STD-002-85 as one of its appendices.
+ FIPS 112 is currently available at:
+ http://www.idl.nist.gov/fipspubs/fip112.htm.)
+
+ [DSS] "Digital Signature Standard (DSS)", US National
+ Institute of Standards and Technology, FIPS 186-2,
+ January 2000.
+
+ [DSS_CN1] "Digital Signature Standard Change Notice 1", US
+ National Institute of Standards and Technology, FIPS
+ 186-2 Change Notice 1, 5, October 2001.
+
+ [FERGUSON] Ferguson, N. and B. Schneier, "Practical
+ Cryptography", Wiley Publishing Inc., ISBN
+ 047122894X, April 2003.
+
+ [GIFFORD] Gifford, D., "Natural Random Number", MIT/LCS/TM-371,
+ September 1988.
+
+ [IEEE_802.11i] "Amendment to Standard for Telecommunications and
+ Information Exchange Between Systems - LAN/MAN
+ Specific Requirements - Part 11: Wireless Medium
+ Access Control (MAC) and physical layer (PHY)
+ specifications: Medium Access Control (MAC) Security
+ Enhancements", IEEE, January 2004.
+
+ [IPSEC] Kent, S. and R. Atkinson, "Security Architecture for
+ the Internet Protocol", RFC 2401, November 1998.
+
+ [Jakobsson] Jakobsson, M., Shriver, E., Hillyer, B., and A.
+ Juels, "A practical secure random bit generator",
+ Proceedings of the Fifth ACM Conference on Computer
+ and Communications Security, 1998.
+
+ [KAUFMAN] Kaufman, C., Perlman, R., and M. Speciner, "Network
+ Security: Private Communication in a Public World",
+ Prentis Hall PTR, ISBN 0-13-046019-2, 2nd Edition
+ 2002.
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 42]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ [KeyStudy] Blaze, M., Diffie, W., Riverst, R., Schneier, B.
+ Shimomura, T., Thompson, E., and M. Weiner, "Minimal
+ Key Lengths for Symmetric Ciphers to Provide Adequate
+ Commercial Security: A Report by an Ad Hoc Group of
+ Cryptographers and Computer Scientists", January
+ 1996. Currently available at:
+ http://www.crypto.com/papers/keylength.txt and
+ http://www.securitydocs.com/library/441.
+
+ [KNUTH] Knuth, D., "The Art of Computer Programming", Volume
+ 2: Seminumerical Algorithms, Chapter 3: Random
+ Numbers, Addison-Wesley Publishing Company, 3rd
+ Edition, November 1997.
+
+ [KRAWCZYK] Krawczyk, H., "How to Predict Congruential
+ Generators", Journal of Algorithms, V. 13, N. 4,
+ December 1992.
+
+ [LUBY] Luby, M., "Pseudorandomness and Cryptographic
+ Applications", Princeton University Press, ISBN
+ 0691025460, 8 January 1996.
+
+ [MAIL_PEM1] Linn, J., "Privacy Enhancement for Internet
+ Electronic Mail: Part I: Message Encryption and
+ Authentication Procedures", RFC 1421, February 1993.
+
+ [MAIL_PEM2] Kent, S., "Privacy Enhancement for Internet
+ Electronic Mail: Part II: Certificate-Based Key
+ Management", RFC 1422, February 1993.
+
+ [MAIL_PEM3] Balenson, D., "Privacy Enhancement for Internet
+ Electronic Mail: Part III: Algorithms, Modes, and
+ Identifiers", RFC 1423, February 1993.
+
+ [MAIL_PEM4] Kaliski, B., "Privacy Enhancement for Internet
+ Electronic Mail: Part IV: Key Certification and
+ Related Services", RFC 1424, February 1993.
+
+ [MAIL_PGP1] Callas, J., Donnerhacke, L., Finney, H., and R.
+ Thayer, "OpenPGP Message Format", RFC 2440, November
+ 1998.
+
+ [MAIL_PGP2] Elkins, M., Del Torto, D., Levien, R., and T.
+ Roessler, "MIME Security with OpenPGP", RFC 3156,
+ August 2001.
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 43]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ [S/MIME] RFCs 2632 through 2634:
+
+ Ramsdell, B., "S/MIME Version 3 Certificate
+ Handling", RFC 2632, June 1999.
+
+ Ramsdell, B., "S/MIME Version 3 Message
+ Specification", RFC 2633, June 1999.
+
+ Hoffman, P., "Enhanced Security Services for S/MIME",
+ RFC 2634, June 1999.
+
+ [MD4] Rivest, R., "The MD4 Message-Digest Algorithm", RFC
+ 1320, April 1992.
+
+ [MD5] Rivest, R., "The MD5 Message-Digest Algorithm ", RFC
+ 1321, April 1992.
+
+ [MODES] "DES Modes of Operation", US National Institute of
+ Standards and Technology, FIPS 81, December 1980.
+ Also: "Data Encryption Algorithm - Modes of
+ Operation", American National Standards Institute,
+ ANSI X3.106-1983.
+
+ [MOORE] Moore's Law: the exponential increase in the logic
+ density of silicon circuits. Originally formulated
+ by Gordon Moore in 1964 as a doubling every year
+ starting in 1962, in the late 1970s the rate fell to
+ a doubling every 18 months and has remained there
+ through the date of this document. See "The New
+ Hacker's Dictionary", Third Edition, MIT Press, ISBN
+ 0-262-18178-9, Eric S. Raymond, 1996.
+
+ [NASLUND] Naslund, M. and A. Russell, "Extraction of Optimally
+ Unbiased Bits from a Biased Source", IEEE
+ Transactions on Information Theory. 46(3), May 2000.
+
+ [ORMAN] Orman, H. and P. Hoffman, "Determining Strengths For
+ Public Keys Used For Exchanging Symmetric Keys", BCP
+ 86, RFC 3766, April 2004.
+
+ [RFC1750] Eastlake 3rd, D., Crocker, S., and J. Schiller,
+ "Randomness Recommendations for Security", RFC 1750,
+ December 1994.
+
+ [RFC1948] Bellovin, S., "Defending Against Sequence Number
+ Attacks", RFC 1948, May 1996.
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 44]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC:
+ Keyed-Hashing for Message Authentication", RFC 2104,
+ February 1997.
+
+ [RSA_BULL1] "Suggestions for Random Number Generation in
+ Software", RSA Laboratories Bulletin #1, January
+ 1996.
+
+ [RSA_BULL13] Silverman, R., "A Cost-Based Security Analysis of
+ Symmetric and Asymmetric Key Lengths", RSA
+ Laboratories Bulletin #13, April 2000 (revised
+ November 2001).
+
+ [SBOX1] Mister, S. and C. Adams, "Practical S-box Design",
+ Selected Areas in Cryptography, 1996.
+
+ [SBOX2] Nyberg, K., "Perfect Non-linear S-boxes", Advances in
+ Cryptography, Eurocrypt '91 Proceedings, Springer-
+ Verland, 1991.
+
+ [SCHNEIER] Schneier, B., "Applied Cryptography: Protocols,
+ Algorithms, and Source Code in C", 2nd Edition, John
+ Wiley & Sons, 1996.
+
+ [SHANNON] Shannon, C., "The Mathematical Theory of
+ Communication", University of Illinois Press, 1963.
+ Originally from: Bell System Technical Journal, July
+ and October, 1948.
+
+ [SHIFT1] Golub, S., "Shift Register Sequences", Aegean Park
+ Press, Revised Edition, 1982.
+
+ [SHIFT2] Barker, W., "Cryptanalysis of Shift-Register
+ Generated Stream Cypher Systems", Aegean Park Press,
+ 1984.
+
+ [SHA] "Secure Hash Standard", US National Institute of
+ Science and Technology, FIPS 180-2, 1 August 2002.
+
+ [SHA_RFC] Eastlake 3rd, D. and P. Jones, "US Secure Hash
+ Algorithm 1 (SHA1)", RFC 3174, September 2001.
+
+ [SSH] Products of the SECSH Working Group, Works in
+ Progress, 2005.
+
+ [STERN] Stern, J., "Secret Linear Congruential Generators are
+ not Cryptographically Secure", Proc. IEEE STOC, 1987.
+
+
+
+
+Eastlake, et al. Standards Track [Page 45]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+ [TLS] Dierks, T. and C. Allen, "The TLS Protocol Version
+ 1.0", RFC 2246, January 1999.
+
+ [TURBID] Denker, J., "High Entropy Symbol Generator",
+ <http://www.av8n.com/turbid/paper/turbid.htm>, 2003.
+
+ [USENET_1] Kantor, B. and P. Lapsley, "Network News Transfer
+ Protocol", RFC 977, February 1986.
+
+ [USENET_2] Barber, S., "Common NNTP Extensions", RFC 2980,
+ October 2000.
+
+ [VON_NEUMANN] Von Nuemann, J., "Various techniques used in
+ connection with random digits", Von Neumann's
+ Collected Works, Vol. 5, Pergamon Press, 1963.
+
+ [WSC] Howard, M. and D. LeBlanc, "Writing Secure Code,
+ Second Edition", Microsoft Press, ISBN 0735617228,
+ December 2002.
+
+ [X9.17] "American National Standard for Financial Institution
+ Key Management (Wholesale)", American Bankers
+ Association, 1985.
+
+ [X9.82] "Random Number Generation", American National
+ Standards Institute, ANSI X9F1, Work in Progress.
+ Part 1 - Overview and General Principles.
+ Part 2 - Non-Deterministic Random Bit Generators
+ Part 3 - Deterministic Random Bit Generators
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 46]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+Authors' Addresses
+
+ Donald E. Eastlake 3rd
+ Motorola Laboratories
+ 155 Beaver Street
+ Milford, MA 01757 USA
+
+ Phone: +1 508-786-7554 (w)
+ +1 508-634-2066 (h)
+ EMail: Donald.Eastlake@motorola.com
+
+
+ Jeffrey I. Schiller
+ MIT, Room E40-311
+ 77 Massachusetts Avenue
+ Cambridge, MA 02139-4307 USA
+
+ Phone: +1 617-253-0161
+ EMail: jis@mit.edu
+
+
+ Steve Crocker
+
+ EMail: steve@stevecrocker.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 47]
+
+RFC 4086 Randomness Requirements for Security June 2005
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2005).
+
+ This document is subject to the rights, licenses and restrictions
+ contained in BCP 78, and except as set forth therein, the authors
+ retain all their rights.
+
+ This document and the information contained herein are provided on an
+ "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+ OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+ ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+ INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+ WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Intellectual Property
+
+ The IETF takes no position regarding the validity or scope of any
+ Intellectual Property Rights or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; nor does it represent that it has
+ made any independent effort to identify any such rights. Information
+ on the procedures with respect to rights in RFC documents can be
+ found in BCP 78 and BCP 79.
+
+ Copies of IPR disclosures made to the IETF Secretariat and any
+ assurances of licenses to be made available, or the result of an
+ attempt made to obtain a general license or permission for the use of
+ such proprietary rights by implementers or users of this
+ specification can be obtained from the IETF on-line IPR repository at
+ http://www.ietf.org/ipr.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights that may cover technology that may be required to implement
+ this standard. Please address the information to the IETF at ietf-
+ ipr@ietf.org.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+Eastlake, et al. Standards Track [Page 48]
+
diff --git a/src/common/libManageSieve/doc/IMAP4 Referrals/rfc2221.txt b/src/common/libManageSieve/doc/IMAP4 Referrals/rfc2221.txt
new file mode 100644
index 00000000..78f73c90
--- /dev/null
+++ b/src/common/libManageSieve/doc/IMAP4 Referrals/rfc2221.txt
@@ -0,0 +1,284 @@
+
+
+
+
+
+Network Working Group M. Gahrns
+Request for Comments: 2221 Microsoft
+Category: Standards Track October 1997
+
+
+ IMAP4 Login Referrals
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (1997). All Rights Reserved.
+
+1. Abstract
+
+ When dealing with large amounts of users and many IMAP4 [RFC-2060]
+ servers, it is often necessary to move users from one IMAP4 server to
+ another. For example, hardware failures or organizational changes
+ may dictate such a move.
+
+ Login referrals allow clients to transparently connect to an
+ alternate IMAP4 server, if their home IMAP4 server has changed.
+
+ A referral mechanism can provide efficiencies over the alternative
+ 'proxy method', in which the local IMAP4 server contacts the remote
+ server on behalf of the client, and then transfers the data from the
+ remote server to itself, and then on to the client. The referral
+ mechanism's direct client connection to the remote server is often a
+ more efficient use of bandwidth, and does not require the local
+ server to impersonate the client when authenticating to the remote
+ server.
+
+2. Conventions used in this document
+
+ In examples, "C:" and "S:" indicate lines sent by the client and
+ server respectively.
+
+ A home server, is an IMAP4 server that contains the user's inbox.
+
+ A remote server is a server that contains remote mailboxes.
+
+
+
+
+
+Gahrns Standards Track [Page 1]
+
+RFC 2221 IMAP4 Login Referrals October 1997
+
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [RFC-2119].
+
+3. Introduction and Overview
+
+ IMAP4 servers that support this extension MUST list the keyword
+ LOGIN-REFERRALS in their CAPABILITY response. No client action is
+ needed to invoke the LOGIN-REFERRALS capability in a server.
+
+ A LOGIN-REFERRALS capable IMAP4 server SHOULD NOT return a referral
+ to a server that will return a referral. A client MUST NOT follow
+ more than 10 levels of referral without consulting the user.
+
+ A LOGIN-REFERRALS response code MUST contain as an argument a valid
+ IMAP server URL as defined in [IMAP-URL].
+
+ A home server referral consists of either a tagged NO or OK, or an
+ untagged BYE response that contains a LOGIN-REFERRALS response code.
+
+ Example: A001 NO [REFERRAL IMAP://user;AUTH=*@SERVER2/] Remote Server
+
+ NOTE: user;AUTH=* is specified as required by [IMAP-URL] to avoid a
+ client falling back to anonymous login.
+
+4. Home Server Referrals
+
+ A home server referral may be returned in response to an AUTHENTICATE
+ or LOGIN command, or it may appear in the connection startup banner.
+ If a server returns a home server referral in a tagged NO response,
+ that server does not contain any mailboxes that are accessible to the
+ user. If a server returns a home server referral in a tagged OK
+ response, it indicates that the user's personal mailboxes are
+ elsewhere, but the server contains public mailboxes which are
+ readable by the user. After receiving a home server referral, the
+ client can not make any assumptions as to whether this was a
+ permanent or temporary move of the user.
+
+4.1. LOGIN and AUTHENTICATE Referrals
+
+ An IMAP4 server MAY respond to a LOGIN or AUTHENTICATE command with a
+ home server referral if it wishes to direct the user to another IMAP4
+ server.
+
+ Example: C: A001 LOGIN MIKE PASSWORD
+ S: A001 NO [REFERRAL IMAP://MIKE@SERVER2/] Specified user
+ is invalid on this server. Try SERVER2.
+
+
+
+
+Gahrns Standards Track [Page 2]
+
+RFC 2221 IMAP4 Login Referrals October 1997
+
+
+ Example: C: A001 LOGIN MATTHEW PASSWORD
+ S: A001 OK [REFERRAL IMAP://MATTHEW@SERVER2/] Specified
+ user's personal mailboxes located on Server2, but
+ public mailboxes are available.
+
+ Example: C: A001 AUTHENTICATE GSSAPI
+ <authentication exchange>
+ S: A001 NO [REFERRAL IMAP://user;AUTH=GSSAPI@SERVER2/]
+ Specified user is invalid on this server. Try
+ SERVER2.
+
+4.2. BYE at connection startup referral
+
+ An IMAP4 server MAY respond with an untagged BYE and a REFERRAL
+ response code that contains an IMAP URL to a home server if it is not
+ willing to accept connections and wishes to direct the client to
+ another IMAP4 server.
+
+ Example: S: * BYE [REFERRAL IMAP://user;AUTH=*@SERVER2/] Server not
+ accepting connections. Try SERVER2
+
+5. Formal Syntax
+
+ The following syntax specification uses the augmented Backus-Naur
+ Form (BNF) as described in [ABNF].
+
+ This amends the "resp_text_code" element of the IMAP4 grammar
+ described in [RFC-2060]
+
+ resp_text_code =/ "REFERRAL" SPACE <imapurl>
+ ; See [IMAP-URL] for definition of <imapurl>
+ ; See [RFC-2060] for base definition of resp_text_code
+
+6. Security Considerations
+
+ The IMAP4 login referral mechanism makes use of IMAP URLs, and as
+ such, have the same security considerations as general internet URLs
+ [RFC-1738], and in particular IMAP URLs [IMAP-URL].
+
+ A server MUST NOT give a login referral if authentication for that
+ user fails. This is to avoid revealing information about the user's
+ account to an unauthorized user.
+
+ With the LOGIN-REFERRALS capability, it is potentially easier to
+ write a rogue 'password catching' server that collects login data and
+ then refers the client to their actual IMAP4 server. Although
+ referrals reduce the effort to write such a server, the referral
+ response makes detection of the intrusion easier.
+
+
+
+Gahrns Standards Track [Page 3]
+
+RFC 2221 IMAP4 Login Referrals October 1997
+
+
+7. References
+
+ [RFC-2060], Crispin, M., "Internet Message Access Protocol - Version
+ 4rev1", RFC 2060, December 1996.
+
+ [IMAP-URL], Newman, C., "IMAP URL Scheme", RFC 2192, Innosoft,
+ September 1997.
+
+ [RFC-1738], Berners-Lee, T., Masinter, L. and M. McCahill, "Uniform
+ Resource Locators (URL)", RFC 1738, December 1994.
+
+ [RFC-2119], Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", RFC 2119, March 1997.
+
+ [ABNF], DRUMS working group, Dave Crocker Editor, "Augmented BNF for
+ Syntax Specifications: ABNF", Work in Progress.
+
+8. Acknowledgments
+
+ Many valuable suggestions were received from private discussions and
+ the IMAP4 mailing list. In particular, Raymond Cheng, Mark Crispin,
+ Mark Keasling Chris Newman and Larry Osterman made significant
+ contributions to this document.
+
+9. Author's Address
+
+ Mike Gahrns
+ Microsoft
+ One Microsoft Way
+ Redmond, WA, 98072
+
+ Phone: (206) 936-9833
+ EMail: mikega@microsoft.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Gahrns Standards Track [Page 4]
+
+RFC 2221 IMAP4 Login Referrals October 1997
+
+
+10. Full Copyright Statement
+
+ Copyright (C) The Internet Society (1997). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implmentation may be prepared, copied, published
+ andand distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE."
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Gahrns Standards Track [Page 5]
+
+
+
diff --git a/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-06.txt b/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-06.txt
new file mode 100644
index 00000000..6deab9c1
--- /dev/null
+++ b/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-06.txt
@@ -0,0 +1,1237 @@
+Network Working Group Tim Martin
+Document: draft-martin-managesieve-06.txt Mirapoint Inc.
+Expires: August 2006 Alexey Melnikov
+ Isode Limited
+ February 2006
+
+
+ A Protocol for Remotely Managing Sieve Scripts
+
+ <draft-martin-managesieve-06.txt>
+
+Status of this Memo
+
+ By submitting this Internet-Draft, each author represents that any
+ applicable patent or other IPR claims of which he or she is aware
+ have been or will be disclosed, and any of which he or she becomes
+ aware will be disclosed, in accordance with Section 6 of BCP 79.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF), its areas, and its working groups. Note that
+ other groups may also distribute working documents as Internet-
+ Drafts.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ The list of current Internet-Drafts can be accessed at
+ http://www.ietf.org/ietf/1id-abstracts.txt.
+
+ The list of Internet-Draft Shadow Directories can be accessed at
+ http://www.ietf.org/shadow.html.
+
+ Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2006).
+
+
+Abstract
+
+ Sieve scripts allow users to filter incoming email. Message stores
+ are commonly sealed servers so users cannot log into them, yet users
+ must be able to update their scripts on them. This document
+ describes a protocol "sieve" for securely managing Sieve scripts on
+ a remote server. This protocol allows a user to have multiple
+ scripts, and also alerts a user to syntactically flawed scripts.
+
+
+ Table of Contents
+
+
+
+Status of this Memo . . . . . . . . . . . . . . . . . . . . . . . . 1
+Abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
+1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3
+1.1. Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
+1.2. Conventions Used in the Document . . . . . . . . . . . . . . 4
+1.3. Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
+1.4. Response Codes . . . . . . . . . . . . . . . . . . . . . . . 5
+1.5. Active Script . . . . . . . . . . . . . . . . . . . . . . . . 6
+1.6. Quotas . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
+1.7. Script Names . . . . . . . . . . . . . . . . . . . . . . . . 7
+1.8. Capabilities . . . . . . . . . . . . . . . . . . . . . . . . 7
+2. Commands . . . . . . . . . . . . . . . . . . . . . . . . . . 8
+2.1. AUTHENTICATE Command . . . . . . . . . . . . . . . . . . . . 8
+2.2. STARTTLS Command . . . . . . . . . . . . . . . . . . . . . . 10
+2.3. LOGOUT Command . . . . . . . . . . . . . . . . . . . . . . . 10
+2.4. CAPABILITY Command . . . . . . . . . . . . . . . . . . . . . 11
+2.5. HAVESPACE Command . . . . . . . . . . . . . . . . . . . . . . 11
+2.6. PUTSCRIPT Command . . . . . . . . . . . . . . . . . . . . . . 11
+2.7. LISTSCRIPTS Command . . . . . . . . . . . . . . . . . . . . . 13
+2.8. SETACTIVE Command . . . . . . . . . . . . . . . . . . . . . . 13
+2.9. GETSCRIPT Command . . . . . . . . . . . . . . . . . . . . . . 13
+2.10. DELETESCRIPT Command . . . . . . . . . . . . . . . . . . . . 14
+3. Sieve URL Scheme . . . . . . . . . . . . . . . . . . . . . . 14
+4. Formal Syntax . . . . . . . . . . . . . . . . . . . . . . . . 15
+5. Security Considerations . . . . . . . . . . . . . . . . . . . 18
+7. References . . . . . . . . . . . . . . . . . . . . . . . . .
+7.1. Normative References . . . . . . . . . . . . . . . . . . . .
+7.2. Informative References . . . . . . . . . . . . . . . . . . .
+8. Author's Address . . . . . . . . . . . . . . . . . . . . . .
+
+
+1. Introduction
+
+
+
+1.1. Changes
+
+ [[Note to RFC editor: please delete this section before publication]]
+
+ Changes since 05
+
+ -More ABNF fixes
+
+ -Added IANA considerations
+
+ -Added/fixed text about AUTHENTICATE.
+
+ -Updated the text om Sieve URLs.
+
+ -Updated and added new examples.
+
+ Changes since 04
+
+ -Updated boilerplate and some references. Added Alexey as co-editor.
+
+ -Minor ABNF fixes
+
+ -Cleaned up terminology (for example, made more consistent with SASL)
+
+ -Added more examples, fixed some existing examples
+
+ -Clarified that STARTTLS command is optional
+
+ -Clarified that disabling an active script when there is no script active
+ is not an error.
+
+ Changes since 03
+
+ -Add referals and Sieve URLs
+
+ -Lots of spelling/grammer fixes
+
+ -Don't give capabilities after successful STARTTLS. This is because
+ it isn't consistant with AUTHENTICATE. There is language specifying
+ that a client should re-issue a CAPABILITY command after
+ AUTHENTICATE/STARTTLS.
+
+ -Putting a script of length 0 doesn't remove the script. If this
+ functionality is desired, the DELETESCRIPT command should be used.
+
+ Changes since 02
+
+ -add BYE response
+
+ -typo on line 588
+
+ -allow ANONYMOUS access for sieve script verification
+
+ -updated SIEVE spec reference
+
+ Changes since 01
+
+ -changed contact info
+
+ Changes since 00
+
+ -added response codes (from ACAP)
+
+ -removed special-ok response from authenticate command (response
+ codes obsolete it)
+
+ -changed service name to "sieve"
+
+ -ABNF fixes
+
+ -Alexey's wording changes
+
+ -Eliminated lame PLAIN paragraph
+
+ Changes since PRE
+
+ -dropped synchronized literals. added HAVESPACE command
+
+ -changed capability response syntax. added CAPABILITY command
+
+ -allowed pipelining
+
+ - "sieve" -> "Sieve". Other minor fixes
+
+ -made script names more flexible
+
+ -added starttls support
+
+
+1.2. Conventions Used in the Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [KEYWORDS].
+
+ In examples, "C:" and "S:" indicate lines sent by the client and
+ server respectively. Line breaks that do not start a new "C:" or
+ "S:" exist for editorial reasons.
+
+
+1.3. Syntax
+
+ This a line oriented protocol much like [IMAP4rev1] or [ACAP]. There
+ are three types: ATOMS, numbers and strings. Strings may be quoted
+ or literal. See [ACAP] for detailed descriptions of these types.
+
+ Each command consists of an atom followed by zero or more strings
+ and numbers terminated by a newline.
+
+ All client queries are replied to with either an OK, NO, or BYE
+ response. Each response may be followed by a response code (see
+ response codes section) and by a string consisting of human readable
+ text in the local language. The contents of the string SHOULD be
+ shown to the user and implementations MUST NOT attempt to parse the
+ message for meaning.
+
+ The BYE response may be used if the server wishes to close the
+ connection. A server may wish to do this because the client was idle
+ for too long or there were too many failed authentication attempts. This
+ response can be issued at any time and should be immediately followed
+ by a server hang-up of the connection. If a server has a inactivity
+ timeout resulting in client autologout it MUST be no less than 30
+ minutes.
+
+ <<IANA registration is pending. Current implementations generally use
+ port number 2000.>>
+
+
+1.4. Response Codes
+
+ An OK, NO, or BYE response from the server MAY contain a response
+ code to describe the event in a more detailed machine parsable
+ fashion. A response code consists of data inside parentheses in the
+ form of an atom, possibly followed by a space and arguments.
+ Response codes are defined when there is a specific action that a
+ client can take based upon the additional information. In order to
+ support future extension, the response code is represented as a
+ slash-separated hierarchy with each level of hierarchy representing
+ increasing detail about the error. Clients MUST tolerate additional
+ hierarchical response code detail which they don't understand.
+
+ The currently defined response codes are:
+
+ AUTH-TOO-WEAK
+
+ This response code is returned in the NO response from an
+ AUTHENTICATE command. It indicates that site security policy forbids
+ the use of the requested mechanism for the specified authentication
+ identity.
+
+ ENCRYPT-NEEDED
+
+ This response code is returned on NO result from an AUTHENTICATE
+ command. It indicates that site security policy requires the use of
+ a strong encryption mechanism for the specified authentication
+ identity and mechanism.
+
+ QUOTA
+
+ The command would have placed the user above the site-defined quota
+ constraints.
+
+ REFERRAL
+
+ This response code may be returned with a BYE result from any
+ command, and includes a mandatory parameter that indicates what
+ server to access to manage this user's sieve scripts. The server
+ will be specified by a Sieve URL (see "Sieve URL Scheme" section).
+ The scriptname portion of the URL MUST NOT be specified. The client
+ should authenticate to the specified server and use it for all
+ further commands in the current session.
+
+ SASL
+
+ This response code can occur in the OK response to a successful
+ AUTHENTICATE command and includes the optional final server response
+ data from the server as specified by [SASL].
+
+ TRANSITION-NEEDED
+
+ This response code occurs in a NO response of an AUTHENTICATE
+ command. It indicates that the user name is valid, but the entry in
+ the authentication database needs to be updated in order to permit
+ authentication with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, followed by authenticating
+ once using the [PLAIN] authentication mechanism. The selected
+ mechanism SHOULD then work for authentications in subsequent sessions.
+
+ This condition can happen if a user has an entry in a system
+ authentication database such as Unix /etc/passwd, but does not have
+ credentials suitable for use by the specified mechanism.
+
+
+ TRYLATER
+
+ A command failed due to a temporary server failure. The client MAY
+ continue using local information and try the command later.
+
+ Client implementations MUST tolerate response codes that they do not
+ recognize.
+
+
+1.5. Active Script
+
+ A user may have multiple Sieve scripts on the server, yet only one
+ script may be used for filtering of incoming messages. This is the
+ active script. Users may have zero or one active scripts and MUST
+ use the SETACTIVE command described below for changing the active
+ script or disabling Sieve processing. For example, a user may have
+ an everyday script they normally use and a special script they use
+ when they go on vacation. Users can change which script is being
+ used without having to download and upload a script stored somewhere
+ else.
+
+
+1.6. Quotas
+
+ Servers SHOULD impose quotas to prevent malicious users from
+ overflowing available storage. If a command would place a user over
+ a quota setting, servers MUST reply with a NO response. Client
+ implementations MUST be able to handle commands failing because of
+ quota restrictions.
+
+
+1.7. Script Names
+
+ Sieve script names may contain any valid UTF-8 characters, but names
+ must be at least one octet long. Zero octets script name
+ has special meaning (see SETACTIVE command section). Servers MUST
+ allow names of up to 128 UTF-8 octets <<(do we really want to specify
+ length in UTF-8 octets, as opposed to Unicode characters?)>>
+ in length, and may allow longer
+ names.
+
+
+1.8. Capabilities
+
+ Server capabilities are sent by the server upon a client connection.
+ Clients may request the capabilities at a later time by issuing the
+ CAPABILITY command described later. The capabilities consist of a
+ series of lines each with one or two strings. The first string is
+ the name of the capability, which is case-insensitive. The second
+ optional string is the value associated with that capability.
+ Order of capabilities is arbitrary, but each capability name can
+ appear at most once.
+
+ The following capabilities are defined in this document:
+
+ IMPLEMENTATION - Name of implementation and version
+
+ SASL - List of SASL mechanisms supported by the server, each
+ separated by a space
+
+ SIEVE - List of space separated Sieve extensions supported
+
+ STARTTLS - If TLS [TLS] is supported by this implementation
+
+ A server implementation MUST return SIEVE and IMPLEMENTATION
+ capabilities.
+
+ A client implementation MUST ignore any other capabilities given
+ that it does not understand.
+
+ Example:
+
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: "STARTTLS"
+ S: OK
+
+ <<Add RENAMESCRIPT>>
+
+
+2. Commands
+
+ The following commands are valid. Prior to successful authentication
+ only the AUTHENTICATE, CAPABILITY, STARTTLS, and LOGOUT commands are
+ valid. Servers MUST reject all other commands with a NO response.
+ Clients may pipeline commands (send more than one command at a time
+ without waiting for completion of the first command ). However, a
+ group of commands sent together MUST NOT have an AUTHENTICATE,
+ a STARTTLS or a HAVESPACE command anywhere but the last command in
+ the list.
+
+
+2.1. AUTHENTICATE Command
+
+ Arguments:
+ String - mechanism
+ String - initial data (optional)
+
+ The AUTHENTICATE command indicates a SASL [SASL] authentication
+ mechanism to the server. If the server supports the requested
+ authentication mechanism, it performs an authentication protocol
+ exchange to identify and authenticate the user. Optionally, it also
+ negotiates a security layer for subsequent protocol interactions.
+ If the requested authentication mechanism is not supported, the
+ server rejects the AUTHENTICATE command by sending the NO response.
+
+ The authentication protocol exchange consists of a series of server
+ challenges and client responses that are specific to the selected
+ authentication mechanism. A server challenge consists of a string
+ (quoted or literal) followed by a CRLF. The contents of the string is
+ a base-64 encoding of the SASL data. A client response consists of
+ a string (quoted or literal) with the base-64 encoding of the SASL
+ data followed by a CRLF. If the client wishes to cancel the
+ authentication exchange, it issues a string containing a single "*".
+ If the server receives such a response, it MUST reject the
+ AUTHENTICATE command by sending an NO reply.
+
+ Note that an empty challenge/response is sent as an empty string.
+ If the mechanism dictates that the final response is sent by the
+ server this data MAY be placed within the data portion of the SASL
+ response code to save a round trip.
+
+ The optional initial-response argument to the AUTHENTICATE command
+ is used to save a round trip when using authentication mechanisms
+ that are defined to send no data in the initial challenge. When the
+ initial-response argument is used with such a mechanism, the initial
+ empty challenge is not sent to the client and the server uses the
+ data in the initial-response argument as if it were sent in response
+ to the empty challenge. If the initial-response argument to the
+ AUTHENTICATE command is used with a mechanism that sends data in the
+ initial challenge, the server rejects the AUTHENTICATE command by
+ sending the NO response.
+
+ The service name specified by this protocol's profile of SASL is
+ "sieve".
+
+ Reauthentication is not supported by ManageSieve protocol's profile
+ of SASL. I.e. after a successfully completed AUTHENTICATE command,
+ no more AUTHENTICATE commands may be issued in the same session.
+ After a successful AUTHENTICATE command completes, a server MUST
+ reject any further AUTHENTICATE commands with a NO reply.
+
+ If a security layer is negotiated through the SASL authentication
+ exchange, it takes effect immediately following the CRLF that
+ concludes the authentication exchange for the client, and the CRLF
+ of the OK response for the server.
+
+ When a security layer takes effect, the ManageSieve protocol is reset
+ to the initial state (the state in ManageSieve after a client
+ has connected to the server). The server MUST discard any
+ knowledge obtained from the client which was not obtained from
+ the SASL (or TLS) negotiation itself.
+ Likewise, the client MUST discard any knowledge obtained from
+ the server, such as the list of ManageSieve extensions, which
+ was not obtained from the SASL (or TLS) negotiation itself.
+ (Note that a client MAY compare the advertised SASL mechanisms before and
+ after authentication in order to detect an active down-negotiation attack.
+ See below.)
+
+ Once a SASL security layer is established, the server MUST re-issue the
+ capability results, followed by an OK response. This is necessary to
+ protect against man-in-the-middle attacks which alter the capabilities
+ list prior to SASL negotiation.
+ The capability results MUST include all SASL mechanisms. This is done in
+ order to allow client to detect active down-negotiation attack.
+
+ When both [TLS] and SASL security layers are in effect, the
+ TLS encoding MUST be applied (when sending data) after the SASL encoding,
+ regardless of the order in which the layers were negotiated.
+
+ Server implementations SHOULD support SASL proxy authentication so
+ that an administrator can administer a user's scripts. Proxy
+ authentication is when a user authenticates as herself/himself but
+ requests the server to act (authorize) as another user.
+
+ <<The authorization identity generated by this [SASL] exchange
+ is a simple username, and both client and server MUST use the
+ [SASLprep] profile of the [StringPrep] algorithm to prepare
+ these names for transmission or comparison. If preparation of
+ the authorization identity fails or results in an empty string
+ (unless it was transmitted as the empty string), the server
+ MUST fail the authentication.>>
+
+ If an AUTHENTICATE command fails with a NO response, the client may
+ try another authentication mechanism by issuing another AUTHENTICATE
+ command. In other words, the client may request authentication
+ types in decreasing order of preference.
+
+ Note that a failed NO response to the AUTHENTICATE command may contain
+ one of the following response codes: AUTH-TOO-WEAK, ENCRYPT-NEEDED or
+ TRANSITION-NEEDED. See section 1.4 for detailed description of the
+ relevant conditions.
+
+ To ensure interoperability, client and server implementations
+ of this extension MUST implement the [DIGEST-MD5] SASL
+ mechanism. <<What is the IESG policy on this?>>
+
+
+ Implementations MAY advertise the ANONYMOUS SASL mechanism [SASL-ANON].
+ This indicates that the server supports ANONYMOUS SIEVE
+ script syntax verification. Only the CAPABILITY, PUTSCRIPT and
+ LOGOUT commands are available to the anonymous user. All other
+ commands MUST give NO responses. Furthermore the PUTSCRIPT command
+ SHOULD NOT <<MUST NOT?>> store any data. In this mode a positive
+ response to the PUTSCRIPT command indicates that the given script
+ does not have any syntax errors.
+
+ Examples (Note that long lines are folded for readability and are
+ not part of protocol exchange):
+
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: "cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0
+ RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh
+ cnNldD11dGYtOA=="
+ C: "Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw
+ MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im
+ ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw
+ ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg="
+ S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==")
+
+ A slightly different variant of the same authentication exchange:
+
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: {128+}
+ S: cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0
+ RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh
+ cnNldD11dGYtOA==
+ C: {276+}
+ C: Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw
+ MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im
+ ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw
+ ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg="
+ S: {56+}
+ S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
+ C: ""
+ S: OK
+
+ Another example demostrating use of SASL PLAIN mechanism under TLS:
+
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" ""
+<<Is this allowed?>>
+ S: "SIEVE" "FILEINTO VACATION"
+ S: "STARTTLS"
+ S: OK
+ C: STARTTLS
+ S: OK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "PLAIN"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: OK
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xu"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xz"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xy"
+ S: BYE "Too many failed authentication attempts"
+ <Server closes connection>
+
+
+2.2. STARTTLS Command
+
+ Support for STARTTLS command in servers is optional. Its availability
+ is advertised with "STARTTLS" capability as described in section
+ 1.8.
+
+ The STARTTLS command requests commencement of a TLS negotiation.
+ The negotiation begins immediately after the CRLF in the OK
+ response. After a client issues a STARTTLS command, it MUST NOT
+ issue further commands until a server response is seen and the TLS
+ negotiation is complete.
+
+ The STARTTLS command is only valid in non-authenticated state. The
+ server remains in non-authenticated state, even if client
+ credentials are supplied during the TLS negotiation. The SASL [SASL]
+ EXTERNAL mechanism MAY be used to authenticate once TLS client
+ credentials are successfully exchanged, but servers supporting the
+ STARTTLS command are not required to support the EXTERNAL mechanism.
+
+ After the TLS layer is established, the server MUST re-issue the
+ capability results, followed by an OK response. This is necessary to
+ protect against man-in-the-middle attacks which alter the capabilities
+ list prior to STARTTLS.
+
+ The capability result MUST NOT include the STARTTLS capability.
+
+ The client MUST discard cached capability information and replace it
+ with the new information. The server MAY advertise different
+ capabilities after STARTTLS.
+
+ Example:
+
+ C: STARTTLS
+ S: OK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: OK
+
+
+2.3. LOGOUT Command
+
+ The client sends the LOGOUT command when it is finished with a
+ connection and wishes to terminate it. The server MUST reply with an
+ OK response and terminate the connection. The server MUST ignore
+ commands issued by the client after the LOGOUT command.
+
+ Example:
+
+ C: Logout
+ S: OK
+ <connection terminated>
+
+
+2.4. CAPABILITY Command
+
+ The CAPABILITY command requests the server capabilities as described
+ earlier in this document. While the capabilities are sent upon
+ connection, they may change during authentication. The client SHOULD
+ issue a CAPABILITY command after successful authentication or after
+ negotiating a security layer using STARTTLS.
+
+
+ Example:
+
+ C: CAPABILITY
+ S: "IMPLEMENTATION" "CMU Cyrus Sieved v001"
+ S: "SASL" "PLAIN KERBEROS_V4 GSSAPI"
+ S: "SIEVE" "FILEINTO VACATION"
+ S: "STARTTLS"
+ S: OK
+
+
+2.5. HAVESPACE Command
+
+ Arguments:
+ String - name
+ Number - size
+
+ The HAVESPACE command is used to query the server for available
+ space. Clients specify the name they wish to save the script as and
+ it's size in octets. Servers respond with an NO if storing a script
+ with that name and size would fail or OK otherwise. Clients should
+ issue this command before attempting to place a script on the
+ server.
+
+ Example:
+
+ C: HAVESPACE "myscript" 999999
+ S: NO (QUOTA) "Quota exceeded"
+
+ C: HAVESPACE "foobar" 435
+ S: OK
+
+
+2.6. PUTSCRIPT Command
+
+ Arguments:
+ String - Script name
+ String - Script content
+
+ The PUTSCRIPT command is used by the client to submit a Sieve script
+ to the server.
+
+ If the script already exists upon success the old script will be
+ overwritten. The old script MUST NOT be overwritten if PUTSCRIPT
+ fails in any way. A script of zero length SHOULD be disallowed.
+
+ This command places the script on the server. It does not affect
+ whether the script is processed on incoming mail. The SETACTIVE
+ command is used to mark a script as active.
+
+ When submitting large scripts clients SHOULD use the HAVESPACE
+ command beforehand to query if the server is willing to accept a
+ script of that size.
+
+ The server MUST check the submitted script for syntactic validity.
+ If the script fails this test the server MUST reply with a NO
+ response. Any script that fails the validity test MUST NOT be stored
+ on the server. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming
+ language compilers. Client implementations should note that this may
+ be a multiline literal string with more than one error message
+ separated by newlines.
+
+ Example:
+
+ C: Putscript "foo" {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ C: Putscript "mysievescript" {110+}
+ C: require ["fileinto"];
+ C:
+ C: if envelope :contains "to" "tmartin+sent" {
+ C: fileinto "INBOX.sent";
+ C: }
+ S: OK
+
+
+2.7. LISTSCRIPTS Command
+
+ This command lists the scripts the user has on the server. Upon
+ success a list of linebreak separated script names is returned
+ followed by an OK response. If there exists an active script the
+ atom ACTIVE is appended to the line of that script. The ACTIVE
+ string MUST NOT appear on more than one response line.
+
+ Example:
+
+ C: Listscripts
+ S: "summer_script"
+ S: "vacation_script"
+ S: "main_script" ACTIVE
+ S: OK
+
+
+2.8. SETACTIVE Command
+
+ Arguments:
+ String - script name
+
+ This command sets a script active. If the script name is the empty
+ string (i.e. "") then any active script is disabled. Disabling an active script
+ when there is no script active is not an error and MUST result in OK reply.
+
+ If the script does not exist on the server then the server MUST reply with a NO
+ response.
+
+ Examples:
+
+ C: Setactive "vacationscript"
+ S: Ok
+
+ C: Setactive ""
+ S: Ok
+
+ C: Setactive "baz"
+ S: No "There is no script by that name"
+
+
+2.9. GETSCRIPT Command
+
+ Arguments:
+ String - Script name
+
+ This command gets the contents of the specified script. If the
+ script does not exist the server MUST reply with a NO response. Upon
+ success a string with the contents of the script is returned
+ followed by a OK response.
+
+ Example:
+
+ C: Getscript "myscript"
+ S: {48+}
+ S: #this is my wonderful script
+ S: reject "I reject all";
+ S:
+ S: OK
+
+
+2.10. DELETESCRIPT Command
+
+ Parameters:
+ sieve-name - Script name
+
+ This command is used to delete a user's Sieve script. Servers MUST
+ reply with a NO response if the script does not exist. The server
+ MUST NOT allow the client to delete an active script and reply with
+ a NO response if attempted. If a client wishes to delete an active
+ script it should use the SETACTIVE command to disable the script
+ first.
+
+ Example:
+
+ C: Deletescript "foo"
+ S: Ok
+
+ C: Deletescript "baz"
+ S: No "You may not delete an active script"
+
+
+3. Sieve URL Scheme
+
+ URI scheme name: sieve
+
+ Status: permanent
+
+ URI scheme syntax:
+
+ Described using ABNF [ABNF] and ABNF entities from [URI-GEN].
+
+ sieveurl = sieveurl-server / sieveurl-script
+
+ sieveurl-server = "sieve://" authority
+
+ sieveurl-script = "sieve://" [ authority ] "/" scriptname
+
+ scriptname = *pchar
+
+ URI scheme semantics:
+
+ A Sieve URL identifies a Sieve server or a Sieve
+ script on a Sieve server. <<The latter form is associated with
+ application/sieve MIME type.>>
+ <<There is no MIME type associated with this URI.>>
+
+ The server form is used in the REFERRAL response code in order
+ to designate another server where the client should perform
+ its operations.
+
+ The script form allows to retrieve (GETSCRIPT), update (PUTSCRIPT),
+ delete (DELETESCRIPT) or activate (SETACTIVE) the named script,
+ however the most typical action would be to retrieve the script.
+ If the script name is empty, the URI requests that the client
+ lists available scripts using LISTSCRIPTS command.
+
+ Encoding considerations: The script name, if present,
+ is in UTF-8. Non-US-ASCII UTF-8 octets MUST be percent-encoded as
+ described in [URI-GEN].
+
+ The user name (in the "authority" part), if present,
+ is in UTF-8. Non-US-ASCII UTF-8 octets MUST be percent-encoded as
+ described in [URI-GEN].
+
+ Applications/protocols that use this URI scheme name:
+ <<The protocol is described in this document.>>
+
+ Interoperability considerations: None.
+
+ Security considerations: <<None>>.
+
+ Contact: Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+ References: This document and <<RFC 3028>>.
+
+
+4. Formal Syntax
+
+ The following syntax specification uses the augmented Backus-Naur
+ Form (BNF) notation as specified in [ABNF]. This uses the ABNF core
+ rules as specified in Appendix A of the ABNF specification [ABNF].
+
+ Except as noted otherwise, all alphabetic characters are case-
+ insensitive. The use of upper or lower case characters to define
+ token strings is for editorial clarity only. Implementations MUST
+ accept these strings in a case-insensitive fashion.
+
+
+ SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-21 / %x23-5B / %x5D-7F
+ ;; any TEXT-CHAR except QUOTED-SPECIALS
+
+ QUOTED-CHAR = SAFE-UTF8-CHAR / "\" QUOTED-SPECIALS
+
+ QUOTED-SPECIALS = <"> / "\"
+
+ SAFE-UTF8-CHAR = SAFE-CHAR / UTF8-2 / UTF8-3 / UTF8-4 /
+ UTF8-5 / UTF8-6
+
+ UTF8-1 = %x80-BF
+
+ UTF8-2 = %xC0-DF UTF8-1
+
+ UTF8-3 = %xE0-EF 2UTF8-1
+
+ UTF8-4 = %xF0-F7 3UTF8-1
+
+ UTF8-5 = %xF8-FB 4UTF8-1
+
+ UTF8-6 = %xFC-FD 5UTF8-1
+
+ auth-type = <"> auth-type-name <">
+
+ auth-type-name = iana-token
+ ;; as defined in SASL [SASL]
+
+ command = command-authenticate / command-logout /
+ command-getscript / command-setactive /
+ command-listscripts / command-deletescript /
+ command-putscript / command-capability /
+ command-havespace / command-starttls
+
+ command-authenticate = "AUTHENTICATE" SP auth-type [SP string]
+ *(CRLF string) CRLF
+
+ command-capability = "CAPABILITY" CRLF
+
+ command-deletescript = "DELETESCRIPT" SP sieve-name CRLF
+
+ command-getscript = "GETSCRIPT" SP sieve-name CRLF
+
+ command-havespace = "HAVESPACE" SP sieve-name SP number CRLF
+
+ command-listscripts = "LISTSCRIPTS" CRLF
+
+ command-logout = "LOGOUT" CRLF
+
+ command-putscript = "PUTSCRIPT" SP sieve-name SP string CRLF
+
+ command-setactive = "SETACTIVE" SP sieve-name CRLF
+
+ command-starttls = "STARTTLS" CRLF
+
+ literal = "{" number "+}" CRLF *OCTET
+ ;; The number represents the number of octets.
+ ;; Sieve scripts MUST be sent as literal-utf8.
+ ;; <<literal-utf8>> is defined in ACAP.
+
+ number = 1*DIGIT
+ ;; A 32-bit unsigned number.
+ ;; (0 <= n < 4,294,967,296)
+
+ quoted = <"> *1024QUOTED-CHAR <">
+ ;; limited to 1024 octets between the <">s
+
+ resp-code = "AUTH-TOO-WEAK" / "ENCRYPT-NEEDED" /
+ "QUOTA" / resp-code-sasl / resp-code-referral
+ "TRANSITION-NEEDED" / "TRYLATER" /
+ resp-code-ext
+
+ resp-code-referral = "REFERRAL" SP sieveurl
+
+ resp-code-sasl = "SASL" SP string
+
+ resp-code-ext = iana-token [SP extension-data]
+ ;; unknown codes MUST be tolerated by the client
+
+ response = response-authenticate / response-logout /
+ response-getscript / response-setactive /
+ response-listscripts / response-deletescript /
+ response-putscript / response-capability /
+ response-havespace / response-starttls
+
+ response-authenticate = *(string CRLF) (response-oknobye)
+
+ response-capability = *(single-capability) response-oknobye
+
+ single-capability = capability-name [SP string] CRLF
+
+ capability-name = string
+ <<Note that literals are allowed!>>
+
+ initial-capabilities = <"> "IMPLEMENTATION" <"> SP string /
+ <"> "SASL" <"> SP sasl-mechs /
+ <"> "SIEVE" <"> SP sieve-extensions /
+ <"> "STARTTLS" <">
+ ;; Each capability conforms to
+ ;; the syntax for single-capability.
+ ;; Also note that the capability name
+ ;; can be returned as either literal
+ ;; or quoted, even though only "quoted"
+ ;; string is shown above.
+
+ sasl-mechs = string
+ ; space separated list of SASL mechanisms,
+ ; can be empty
+
+ sieve-extensions = string
+ ; space separated list of supported SIEVE extensions,
+ ; can be empty
+
+ response-deletescript = response-oknobye
+
+ response-getscript = [string CRLF] response-oknobye
+
+ response-havespace = response-oknobye
+
+ response-listscripts = *(sieve-name [SP "ACTIVE"] CRLF) response-oknobye
+ ;; ACTIVE may only occur with one sieve-name
+
+ response-logout = response-oknobye
+
+ response-oknobye = ("OK" / "NO" / "BYE") [SP "(" resp-code ")"]
+ [SP string] CRLF
+
+ response-putscript = response-oknobye
+
+ response-setactive = response-oknobye
+
+ response-starttls = response-oknobye
+
+ sieve-name = string
+
+ string = quoted / literal
+
+
+5. Security Considerations
+
+ The AUTHENTICATE command uses SASL [SASL] and possibly TLS [TLS] to provide
+ basic authentication, authorization, integrity and privacy services.
+ When a SASL mechanism is used the security considerations for that
+ mechanism apply.
+
+ This protocol transactions are susceptible to passive observers or
+ man in the middle attacks which alter the data, unless the optional
+ encryption and integrity services of the AUTHENTICATE command are
+ enabled, or an external security mechanism is used for protection.
+ It may be useful to allow configuration of both clients and servers
+ to refuse to transfer sensitive information in the absence of strong
+ encryption.
+
+
+6. IANA Considerations
+
+ IANA is requested to reserve TCP port number 2000 for use with
+ the Manage Sieve protocol described in this document.
+
+ IANA is requested to create a new registry for Manage Sieve
+ capabilities. The registration template for Manage Sieve capabilities
+ is specified in the next section.
+ Manage Sieve protocol capabilities MUST be specified in a standards
+ track or IESG approved experimental RFC.
+
+ <<Add a new registry for response codes, as per ABNF comments.>>
+
+ <<Reference to SIEVE URL registration.>>
+
+
+6.1. Manage Sieve Capability Registration Template
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name:
+
+ Description:
+
+ Relevant publications:
+
+ Person & email address to contact for further information:
+
+ Author/Change controller:
+
+
+6.2. Registration of Initial Manage Sieve capabilities.
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: IMPLEMENTATION
+
+ Description: Its value contains name of server
+ implementation and its version.
+
+ Relevant publications: this RFC, section 1.8.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: SASL
+
+ Description: Its value contains a space separated
+ list of SASL mechanisms supported by server.
+
+ Relevant publications: this RFC, sections 1.8 and 2.1.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: SIEVE
+
+ Description: Its value contains a space separated
+ list of supported SIEVE extensions
+
+ Relevant publications: this RFC, section 1.8.
+ <<Also [SIEVE]>>
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: STARTTLS
+
+ Description: This capability is returned if server
+ supports TLS (STARTTLS command).
+
+ Relevant publications: this RFC, sections 1.8 and 2.2.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+7. References
+
+7.1. Normative References
+
+ [KEYWORDS] S. Bradner, "Key words for use in RFCs to Indicate
+ Requirement Levels", RFC 2119, March 1997
+ <ftp://ftp.isi.edu/in-notes/rfc2119.txt>
+
+ [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", RFC 4234, October 2005.
+
+ [ACAP] Newman, Myers, "ACAP -- Application Configuration Access Proto-
+ col", RFC 2244, Innosoft, Netscape, November 1997.
+
+ [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication and Security
+ Layer (SASL)", work in progress, draft-ietf-sasl-rfc2222bis-*.txt.
+
+ [SASLprep] Zeilega, K., "SASLprep: Stringprep profile for user names
+ and passwords", work in progress, draft-ietf-sasl-saslprep-*.txt.
+
+ [StringPrep] Hoffman, P. and Blanchet, M., "Preparation of
+ Internationalized Strings ("stringprep")", work in progress,
+ draft-hoffman-rfc3454bis-*.txt.
+
+ [SASL-ANON] K. Zeilenga (Ed.), "The Anonymous SASL Mechanism",
+ work in progress, draft-ietf-sasl-anon-XX.txt (Obsoletes RFC 2245).
+
+ [SIEVE] Guenther, P. and Showalter, T., "Sieve: An Email Filtering
+ Language", work in Progress, draft-ietf-sieve-3028bis-XX.txt
+
+ [TLS] Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", RFC 2246,
+ January 1999. <<Needs updating>>
+
+ [URI-GEN] Berners-Lee, T., Fielding, R. and L. Masinter,
+ "Uniform Resource Identifier (URI): Generic Syntax", RFC 3986,
+ January 2005.
+
+
+7.2. Informative References
+
+ [IMAP4rev1] Crispin, M., "Internet Message Access Protocol - Version
+ 4rev1", RFC 3501, March 2003.
+
+ [PLAIN] K. Zeilenga, "The Plain SASL Mechanism",
+ work in progress, draft-ietf-sasl-plain-XX.txt (Updates RFC 2595).
+
+
+8. Author's Address
+
+ Tim Martin
+ Mirapoint Inc.
+ 909 Hermosa Court
+ Sunnyvale, CA 94085
+ Phone: (408) 720-3835
+ EMail: timmartin@alumni.cmu.edu
+
+ Alexey Melnikov
+ Isode Ltd.
+ 5 Castle Business Village
+ 36 Station Road
+ Hampton, Middlesex, TW12 2BX, GB
+ Email: alexey.melnikov@isode.com
+
+
+Intellectual Property Statement
+
+ The IETF takes no position regarding the validity or scope of any
+ Intellectual Property Rights or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; nor does it represent that it has
+ made any independent effort to identify any such rights. Information
+ on the procedures with respect to rights in RFC documents can be
+ found in BCP 78 and BCP 79.
+
+ Copies of IPR disclosures made to the IETF Secretariat and any
+ assurances of licenses to be made available, or the result of an
+ attempt made to obtain a general license or permission for the use of
+ such proprietary rights by implementers or users of this
+ specification can be obtained from the IETF on-line IPR repository at
+ http://www.ietf.org/ipr.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights that may cover technology that may be required to implement
+ this standard. Please address the information to the IETF at
+ ietf-ipr@ietf.org.
+
+ The IETF has been notified of intellectual property rights claimed in
+ regard to some or all of the specification contained in this
+ document. For more information consult the online list of claimed
+ rights.
+
+
+Disclaimer of Validity
+
+ This document and the information contained herein are provided on an
+ "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+ OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+ ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+ INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+ WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+Copyright Statement
+
+ Copyright (C) The Internet Society (2006). This document is subject
+ to the rights, licenses and restrictions contained in BCP 78, and
+ except as set forth therein, the authors retain all their rights.
+
+Acknowledgment
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+Appendix A. Acknowledgments
+
+ Thanks to Simon Josefsson, Larry Greenfield, Allen Johnson, Chris
+ Newman, Lyndon Nerenberg, Tim Showalter, Sarah Robeson, and Walter
+ Wong for help with this document.
diff --git a/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-08.txt b/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-08.txt
new file mode 100644
index 00000000..59bb20f9
--- /dev/null
+++ b/src/common/libManageSieve/doc/Manage Sieve/draft-martin-managesieve-08.txt
@@ -0,0 +1,1359 @@
+Network Working Group Tim Martin
+INTERNET-DRAFT BeThereBeSquare Inc.
+Intended status: Standards Track Alexey Melnikov
+Expires: February 2008 Isode Limited
+ August 14, 2007
+
+
+
+ A Protocol for Remotely Managing Sieve Scripts
+ <draft-martin-managesieve-08.txt>
+
+Status of this Memo
+
+ By submitting this Internet-Draft, each author represents that any
+ applicable patent or other IPR claims of which he or she is aware
+ have been or will be disclosed, and any of which he or she becomes
+ aware will be disclosed, in accordance with Section 6 of BCP 79.
+
+ Internet-Drafts are working documents of the Internet
+ Engineering Task Force (IETF), its areas, and its
+ working groups. Note that other groups may also distribute
+ working documents as Internet-Drafts.
+
+ Internet-Drafts are draft documents valid for a maximum of
+ six months and may be updated, replaced, or obsoleted by
+ other documents at any time. It is inappropriate to use
+ Internet-Drafts as reference material or to cite them other
+ than as "work in progress."
+
+ The list of current Internet-Drafts can be accessed at
+ http://www.ietf.org/1id-abstracts.html
+
+ The list of Internet-Draft Shadow Directories can be accessed at
+ http://www.ietf.org/shadow.html
+
+ Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The IETF Trust (2007).
+
+
+Abstract
+
+ Sieve scripts allow users to filter incoming email. Message stores
+ are commonly sealed servers so users cannot log into them, yet users
+ must be able to update their scripts on them. This document
+ describes a protocol "sieve" for securely managing Sieve scripts on
+ a remote server. This protocol allows a user to have multiple
+ scripts, and also alerts a user to syntactically flawed scripts.
+
+
+ Table of Contents
+
+
+
+Status of this Memo . . . . . . . . . . . . . . . . . . . . . . . . 1
+Abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
+1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3
+1.1. Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
+1.2. Conventions Used in the Document . . . . . . . . . . . . . . 4
+1.3. Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
+1.4. Response Codes . . . . . . . . . . . . . . . . . . . . . . . 5
+1.5. Active Script . . . . . . . . . . . . . . . . . . . . . . . . 6
+1.6. Quotas . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
+1.7. Script Names . . . . . . . . . . . . . . . . . . . . . . . . 7
+1.8. Capabilities . . . . . . . . . . . . . . . . . . . . . . . . 7
+2. Commands . . . . . . . . . . . . . . . . . . . . . . . . . . 8
+2.1. AUTHENTICATE Command . . . . . . . . . . . . . . . . . . . . 8
+2.2. STARTTLS Command . . . . . . . . . . . . . . . . . . . . . . 10
+2.3. LOGOUT Command . . . . . . . . . . . . . . . . . . . . . . . 10
+2.4. CAPABILITY Command . . . . . . . . . . . . . . . . . . . . . 11
+2.5. HAVESPACE Command . . . . . . . . . . . . . . . . . . . . . . 11
+2.6. PUTSCRIPT Command . . . . . . . . . . . . . . . . . . . . . . 11
+2.7. LISTSCRIPTS Command . . . . . . . . . . . . . . . . . . . . . 13
+2.8. SETACTIVE Command . . . . . . . . . . . . . . . . . . . . . . 13
+2.9. GETSCRIPT Command . . . . . . . . . . . . . . . . . . . . . . 13
+2.10. DELETESCRIPT Command . . . . . . . . . . . . . . . . . . . . 14
+3. Sieve URL Scheme . . . . . . . . . . . . . . . . . . . . . . 14
+4. Formal Syntax . . . . . . . . . . . . . . . . . . . . . . . . 15
+5. Security Considerations . . . . . . . . . . . . . . . . . . . 18
+7. References . . . . . . . . . . . . . . . . . . . . . . . . .
+7.1. Normative References . . . . . . . . . . . . . . . . . . . .
+7.2. Informative References . . . . . . . . . . . . . . . . . . .
+8. Author's Address . . . . . . . . . . . . . . . . . . . . . .
+
+
+1. Introduction
+
+
+
+1.1. Changes
+
+ [[Note to RFC editor: please delete this section before
+ publication]]
+
+ Changes since 07
+
+ -Fixed examples to match 3028bis - capability names are case
+ sensitive, so examples should show "fileinto" instead of
+ "FILEINTO", etc.
+
+ -Minor editorial changes
+
+ Changes since 06
+
+ -Clarified meaning of the QUOTA response code
+
+ -Clarified which characters are not allowed in script names
+ and the maximum script name length
+
+ -Clarified that the empty list of SASL mechanisms is allowed
+
+ -Clarified that PUTSCRIPT must not store data after anonymous
+ authentication
+
+ -Move text about NOTIFY capability into this document
+
+ -Additional examples
+
+ -Updated ABNF, References, Contact information
+
+ Changes since 05
+
+ -More ABNF fixes
+
+ -Added IANA considerations
+
+ -Added/fixed text about AUTHENTICATE.
+
+ -Updated the text om Sieve URLs.
+
+ -Updated and added new examples.
+
+ Changes since 04
+
+ -Updated boilerplate and some references. Added Alexey as co-editor.
+
+ -Minor ABNF fixes
+
+ -Cleaned up terminology (for example, made more consistent with
+ SASL)
+
+ -Added more examples, fixed some existing examples
+
+ -Clarified that STARTTLS command is optional
+
+ -Clarified that disabling an active script when there is no script
+ active is not an error.
+
+ Changes since 03
+
+ -Add referals and Sieve URLs
+
+ -Lots of spelling/grammer fixes
+
+ -Don't give capabilities after successful STARTTLS. This is because
+ it isn't consistant with AUTHENTICATE. There is language specifying
+ that a client should re-issue a CAPABILITY command after
+ AUTHENTICATE/STARTTLS.
+
+ -Putting a script of length 0 doesn't remove the script. If this
+ functionality is desired, the DELETESCRIPT command should be used.
+
+ Changes since 02
+
+ -add BYE response
+
+ -typo on line 588
+
+ -allow ANONYMOUS access for sieve script verification
+
+ -updated SIEVE spec reference
+
+ Changes since 01
+
+ -changed contact info
+
+ Changes since 00
+
+ -added response codes (from ACAP)
+
+ -removed special-ok response from authenticate command (response
+ codes obsolete it)
+
+ -changed service name to "sieve"
+
+ -ABNF fixes
+
+ -Alexey's wording changes
+
+ -Eliminated lame PLAIN paragraph
+
+ Changes since PRE
+
+ -dropped synchronized literals. added HAVESPACE command
+
+ -changed capability response syntax. added CAPABILITY command
+
+ -allowed pipelining
+
+ - "sieve" -> "Sieve". Other minor fixes
+
+ -made script names more flexible
+
+ -added starttls support
+
+
+1.2. Conventions Used in the Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [KEYWORDS].
+
+ In examples, "C:" and "S:" indicate lines sent by the client and
+ server respectively. Line breaks that do not start a new "C:" or
+ "S:" exist for editorial reasons.
+
+
+1.3. Syntax
+
+ This a line oriented protocol much like [IMAP4rev1] or [ACAP]. There
+ are three types: ATOMS, numbers and strings. Strings may be quoted
+ or literal. See [ACAP] for detailed descriptions of these types.
+ <<Clarify that the CRLF after the literal size is not "end of the
+ line">>
+
+ Each command consists of an atom followed by zero or more strings
+ and numbers terminated by a newline.
+
+ All client queries are replied to with either an OK, NO, or BYE
+ response. Each response may be followed by a response code (see
+ response codes section) and by a string consisting of human readable
+ text in the local language. The contents of the string SHOULD be
+ shown to the user and implementations MUST NOT attempt to parse the
+ message for meaning.
+
+ The BYE response may be used if the server wishes to close the
+ connection. A server may wish to do this because the client was idle
+ for too long or there were too many failed authentication attempts.
+ This response can be issued at any time and should be immediately
+ followed by a server hang-up of the connection. If a server has a
+ inactivity timeout resulting in client autologout it MUST be no less
+ than 30 minutes.
+
+ <<IANA registration is pending. Current implementations generally
+ use port number 2000.>>
+
+
+1.4. Response Codes
+
+ An OK, NO, or BYE response from the server MAY contain a response
+ code to describe the event in a more detailed machine parsable
+ fashion. A response code consists of data inside parentheses in the
+ form of an atom, possibly followed by a space and arguments.
+ Response codes are defined when there is a specific action that a
+ client can take based upon the additional information. In order to
+ support future extension, the response code is represented as a
+ slash-separated hierarchy with each level of hierarchy representing
+ increasing detail about the error. Clients MUST tolerate additional
+ hierarchical response code detail which they don't understand.
+
+ The currently defined response codes are:
+
+ AUTH-TOO-WEAK
+
+ This response code is returned in the NO response from an
+ AUTHENTICATE command. It indicates that site security policy forbids
+ the use of the requested mechanism for the specified authentication
+ identity.
+
+ ENCRYPT-NEEDED
+
+ This response code is returned in the NO response from an
+ AUTHENTICATE command. It indicates that site security policy
+ requires the use of a strong encryption mechanism for the specified
+ authentication identity and mechanism.
+
+ QUOTA
+
+ If this response code is returned in the NO/BYE response, it means
+ that the command would have placed the user above the site-defined
+ quota constraints. If this response code is returned in the OK
+ response, it can mean that the user is near its quota or that the
+ user exceeded its quota, but the server supports soft quotas.
+
+ REFERRAL
+
+ This response code may be returned with a BYE result from any
+ command, and includes a mandatory parameter that indicates what
+ server to access to manage this user's sieve scripts. The server
+ will be specified by a Sieve URL (see "Sieve URL Scheme" section).
+ The scriptname portion of the URL MUST NOT be specified. The client
+ should authenticate to the specified server and use it for all
+ further commands in the current session.
+
+ SASL
+
+ This response code can occur in the OK response to a successful
+ AUTHENTICATE command and includes the optional final server response
+ data from the server as specified by [SASL].
+
+ TRANSITION-NEEDED
+
+ This response code occurs in a NO response of an AUTHENTICATE
+ command. It indicates that the user name is valid, but the entry in
+ the authentication database needs to be updated in order to permit
+ authentication with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, followed by
+ authenticating once using the [PLAIN] authentication mechanism.
+ The selected mechanism SHOULD then work for authentications in
+ subsequent sessions.
+
+ This condition can happen if a user has an entry in a system
+ authentication database such as Unix /etc/passwd, but does not have
+ credentials suitable for use by the specified mechanism.
+
+ TRYLATER
+
+ A command failed due to a temporary server failure. The client MAY
+ continue using local information and try the command later. This
+ response code only make sense when returned in a NO/BYE response.
+
+
+ Client implementations MUST tolerate response codes that they do not
+ recognize.
+
+
+1.5. Active Script
+
+ A user may have multiple Sieve scripts on the server, yet only one
+ script may be used for filtering of incoming messages. This is the
+ active script. Users may have zero or one active scripts and MUST
+ use the SETACTIVE command described below for changing the active
+ script or disabling Sieve processing. For example, a user may have
+ an everyday script they normally use and a special script they use
+ when they go on vacation. Users can change which script is being
+ used without having to download and upload a script stored somewhere
+ else.
+
+
+1.6. Quotas
+
+ Servers SHOULD impose quotas to prevent malicious users from
+ overflowing available storage. If a command would place a user over
+ a quota setting, servers that impose such quotas MUST reply with a
+ NO response. Client implementations MUST be able to handle commands
+ failing because of quota restrictions.
+
+
+1.7. Script Names
+
+ Sieve script names may contain any valid UTF-8 characters, except
+ for NUL, CR or LF. Names MUST be at least one octet long. Zero
+ octets script name has special meaning (see SETACTIVE command
+ section). Servers MUST allow names of up to 128 Unicode characters
+ in length, and MAY allow longer names.
+
+
+1.8. Capabilities
+
+ Server capabilities are sent by the server upon a client connection.
+ Clients may request the capabilities at a later time by issuing the
+ CAPABILITY command described later. The capabilities consist of a
+ series of lines each with one or two strings. The first string is
+ the name of the capability, which is case-insensitive. The second
+ optional string is the value associated with that capability.
+ Order of capabilities is arbitrary, but each capability name can
+ appear at most once.
+
+ The following capabilities are defined in this document:
+
+ IMPLEMENTATION - Name of implementation and version
+
+ SASL - List of SASL mechanisms supported by the server, each
+ separated by a space. This list can be empty if and only if
+ STARTTLS is also advertised. This means that the client must
+ negotiate TLS encryption with STARTTLS first, at which point
+ the SASL capability will list a non empty list of SASL mechanisms.
+ <<But STARTTLS is optional!>>
+
+ SIEVE - List of space separated Sieve extensions (as listed
+ in Sieve "require" action [SIEVE]) supported by the Sieve engine
+
+ STARTTLS - If TLS [TLS] is supported by this implementation
+
+ NOTIFY - A space separated list of URI schema parts for supported
+ notification methods. This capability MUST be specified if the
+ Sieve implementation supports the "enotify" extension
+ [NOTIFY].
+
+ A server implementation MUST return SIEVE and IMPLEMENTATION
+ capabilities.
+
+ A client implementation MUST ignore any listed capabilities
+ that it does not understand.
+
+ Example:
+
+ S: "IMPlemENTATION" "Example1 ManageSieved v001"
+ S: "SASl" "DIGEST-MD5 GSSAPI"
+ S: "SIeVE" "fileinto vacation"
+ S: "StaRTTLS"
+ S: "NOTIFY" "xmpp mailto"
+ S: OK
+
+ <<Add RENAMESCRIPT>>
+
+
+2. Commands
+
+ The following commands are valid. Prior to successful authentication
+ only the AUTHENTICATE, CAPABILITY, STARTTLS, and LOGOUT commands are
+ valid. Servers MUST reject all other commands with a NO response.
+ Clients may pipeline commands (send more than one command at a time
+ without waiting for completion of the first command ). However, a
+ group of commands sent together MUST NOT have an AUTHENTICATE,
+ a STARTTLS or a HAVESPACE command anywhere but the last command in
+ the list.
+
+
+2.1. AUTHENTICATE Command
+
+ Arguments:
+ String - mechanism
+ String - initial data (optional)
+
+ The AUTHENTICATE command indicates a SASL [SASL] authentication
+ mechanism to the server. If the server supports the requested
+ authentication mechanism, it performs an authentication protocol
+ exchange to identify and authenticate the user. Optionally, it also
+ negotiates a security layer for subsequent protocol interactions.
+ If the requested authentication mechanism is not supported, the
+ server rejects the AUTHENTICATE command by sending the NO response.
+
+ The authentication protocol exchange consists of a series of server
+ challenges and client responses that are specific to the selected
+ authentication mechanism. A server challenge consists of a string
+ (quoted or literal) followed by a CRLF. The contents of the string
+ is a base-64 encoding [BASE64] of the SASL data. A client response
+ consists of a string (quoted or literal) with the base-64 encoding
+ of the SASL data followed by a CRLF. If the client wishes to cancel
+ the authentication exchange, it issues a string containing a single
+ "*". If the server receives such a response, it MUST reject the
+ AUTHENTICATE command by sending an NO reply.
+
+ Note that an empty challenge/response is sent as an empty string.
+ If the mechanism dictates that the final response is sent by the
+ server this data MAY be placed within the data portion of the SASL
+ response code to save a round trip.
+
+ The optional initial-response argument to the AUTHENTICATE command
+ is used to save a round trip when using authentication mechanisms
+ that are defined to send no data in the initial challenge. When the
+ initial-response argument is used with such a mechanism, the initial
+ empty challenge is not sent to the client and the server uses the
+ data in the initial-response argument as if it were sent in response
+ to the empty challenge. If the initial-response argument to the
+ AUTHENTICATE command is used with a mechanism that sends data in the
+ initial challenge, the server rejects the AUTHENTICATE command by
+ sending the NO response.
+
+ The service name specified by this protocol's profile of SASL is
+ "sieve".
+
+ Reauthentication is not supported by ManageSieve protocol's profile
+ of SASL. I.e. after a successfully completed AUTHENTICATE command,
+ no more AUTHENTICATE commands may be issued in the same session.
+ After a successful AUTHENTICATE command completes, a server MUST
+ reject any further AUTHENTICATE commands with a NO reply.
+
+ If a security layer is negotiated through the SASL authentication
+ exchange, it takes effect immediately following the CRLF that
+ concludes the authentication exchange for the client, and the CRLF
+ of the OK response for the server.
+
+ When a security layer takes effect, the ManageSieve protocol is
+ reset to the initial state (the state in ManageSieve after a client
+ has connected to the server). The server MUST discard any
+ knowledge obtained from the client which was not obtained from
+ the SASL (or TLS) negotiation itself.
+ Likewise, the client MUST discard any knowledge obtained from
+ the server, such as the list of ManageSieve extensions, which
+ was not obtained from the SASL (or TLS) negotiation itself.
+ (Note that a client MAY compare the advertised SASL mechanisms
+ before and after authentication in order to detect an active
+ down-negotiation attack. See below.)
+
+ Once a SASL security layer is established, the server MUST re-issue
+ the capability results, followed by an OK response. This is
+ necessary to protect against man-in-the-middle attacks which alter
+ the capabilities list prior to SASL negotiation.
+ The capability results MUST include all SASL mechanisms. This is
+ done in order to allow client to detect active down-negotiation
+ attack.
+
+ When both [TLS] and SASL security layers are in effect, the
+ TLS encoding MUST be applied (when sending data) after the SASL
+ encoding, regardless of the order in which the layers were
+ negotiated.
+
+ Server implementations SHOULD support SASL proxy authentication so
+ that an administrator can administer a user's scripts. Proxy
+ authentication is when a user authenticates as herself/himself but
+ requests the server to act (authorize) as another user.
+
+ <<The authorization identity generated by this [SASL] exchange
+ is a simple username, and both client and server MUST use the
+ [SASLprep] profile of the [StringPrep] algorithm to prepare
+ these names for transmission or comparison. If preparation of
+ the authorization identity fails or results in an empty string
+ (unless it was transmitted as the empty string), the server
+ MUST fail the authentication.>>
+
+ If an AUTHENTICATE command fails with a NO response, the client may
+ try another authentication mechanism by issuing another AUTHENTICATE
+ command. In other words, the client may request authentication
+ types in decreasing order of preference.
+
+ Note that a failed NO response to the AUTHENTICATE command may
+ contain one of the following response codes: AUTH-TOO-WEAK,
+ ENCRYPT-NEEDED or TRANSITION-NEEDED. See section 1.4 for detailed
+ description of the relevant conditions.
+
+ To ensure interoperability, client and server implementations
+ of this extension MUST implement the [DIGEST-MD5] SASL
+ mechanism. <<What is the IESG policy on this?>>
+
+
+ Implementations MAY advertise the ANONYMOUS SASL mechanism
+ [SASL-ANON]. This indicates that the server supports ANONYMOUS SIEVE
+ script syntax verification. Only the CAPABILITY, PUTSCRIPT and
+ LOGOUT commands are available to the anonymous user. All other
+ commands MUST give NO responses. Furthermore the PUTSCRIPT command
+ MUST NOT persistently store any data. In this mode a positive
+ response to the PUTSCRIPT command indicates that the given script
+ does not have any syntax errors.
+
+ Examples (Note that long lines are folded for readability and are
+ not part of protocol exchange):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: "cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0
+ RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh
+ cnNldD11dGYtOA=="
+ C: "Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw
+ MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im
+ ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw
+ ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg="
+ S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZ
+ mZmZA==")
+
+ A slightly different variant of the same authentication exchange:
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: {128+}
+ S: cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0
+ RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh
+ cnNldD11dGYtOA==
+ C: {276+}
+ C: Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw
+ MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im
+ ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw
+ ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg="
+ S: {56+}
+ S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
+ C: ""
+ S: OK
+
+ Another example demostrating use of SASL PLAIN mechanism under TLS.
+ This example also demonstrate use of SASL "initial response"
+ (the second parameter to the Authenticate command):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" ""
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+ C: STARTTLS
+ S: OK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "PLAIN"
+ S: "SIEVE" "fileinto vacation"
+ S: OK
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xu"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xz"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xy"
+ S: BYE "Too many failed authentication attempts"
+ <Server closes connection>
+
+ The following example demonstrate use of SASL "initial response".
+ It also demonstrates that an empty response can be sent as a
+ literal:
+
+ C: AUTHENTICATE "GSSAPI" {1488+}
+ C: YIIE[...1480 octets here ...]dA==
+ S: {208+}
+ S: YIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKic
+ [...114 octets here ...]
+ /yzpAy9p+Y0LanLskOTvMc0MnjgAa4YEr3eJ6
+ C: {0+}
+ C:
+ S: {44+}
+ S: BQQF/wAMAAwAAAAAYRGFAo6W0vIHti8i1UXODgEAEAA=
+ C: {44+}
+ C: BQQE/wAMAAwAAAAAIsT1iv9UkZApw471iXt6cwEAAAE=
+ S: OK
+
+
+2.2. STARTTLS Command
+
+ Support for STARTTLS command in servers is optional. Its
+ availability is advertised with "STARTTLS" capability as described
+ in section 1.8.
+
+ The STARTTLS command requests commencement of a TLS negotiation.
+ The negotiation begins immediately after the CRLF in the OK
+ response. After a client issues a STARTTLS command, it MUST NOT
+ issue further commands until a server response is seen and the TLS
+ negotiation is complete.
+
+ The STARTTLS command is only valid in non-authenticated state. The
+ server remains in non-authenticated state, even if client
+ credentials are supplied during the TLS negotiation. The SASL [SASL]
+ EXTERNAL mechanism MAY be used to authenticate once TLS client
+ credentials are successfully exchanged, but servers supporting the
+ STARTTLS command are not required to support the EXTERNAL mechanism.
+
+ After the TLS layer is established, the server MUST re-issue the
+ capability results, followed by an OK response. This is necessary to
+ protect against man-in-the-middle attacks which alter the
+ capabilities list prior to STARTTLS. This capability result MUST NOT
+ include the STARTTLS capability.
+
+ The client MUST discard cached capability information and replace it
+ with the new information. The server MAY advertise different
+ capabilities after STARTTLS.
+
+ Example:
+
+ C: StartTls
+ S: oK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: ok
+
+
+2.3. LOGOUT Command
+
+ The client sends the LOGOUT command when it is finished with a
+ connection and wishes to terminate it. The server MUST reply with an
+ OK response and terminate the connection. The server MUST ignore
+ commands issued by the client after the LOGOUT command.
+
+ Example:
+
+ C: Logout
+ S: Ok
+ <connection terminated>
+
+
+2.4. CAPABILITY Command
+
+ The CAPABILITY command requests the server capabilities as described
+ earlier in this document. While the capabilities are sent upon
+ connection, they may change during authentication. The client SHOULD
+ issue a CAPABILITY command after successful authentication or after
+ negotiating a security layer using STARTTLS.
+
+
+ Example:
+
+ C: CAPABILITY
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "PLAIN KERBEROS_V4 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+
+
+2.5. HAVESPACE Command
+
+ Arguments:
+ String - name
+ Number - size
+
+ The HAVESPACE command is used to query the server for available
+ space. Clients specify the name they wish to save the script as and
+ its size in octets. Servers respond with an NO if storing a script
+ with that name and size would fail or OK otherwise. Clients SHOULD
+ issue this command before attempting to place a script on the
+ server.
+
+ Example:
+
+ C: HAVESPACE "myscript" 999999
+ S: NO (QUOTA) "Quota exceeded"
+
+ C: HAVESPACE "foobar" 435
+ S: OK
+
+
+2.6. PUTSCRIPT Command
+
+ Arguments:
+ String - Script name
+ String - Script content
+
+ The PUTSCRIPT command is used by the client to submit a Sieve script
+ to the server.
+
+ If the script already exists, upon success the old script will be
+ overwritten. The old script MUST NOT be overwritten if PUTSCRIPT
+ fails in any way. A script of zero length SHOULD be disallowed.
+
+ This command places the script on the server. It does not affect
+ whether the script is processed on incoming mail, unless it replaces
+ the script which is already active. The SETACTIVE
+ command is used to mark a script as active.
+
+ When submitting large scripts clients SHOULD use the HAVESPACE
+ command beforehand to query if the server is willing to accept a
+ script of that size.
+
+ The server MUST check the submitted script for syntactic validity.
+ If the script fails this test the server MUST reply with a NO
+ response. Any script that fails the validity test MUST NOT be stored
+ on the server. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving the line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming
+ language compilers. Client implementations should note that this may
+ be a multiline literal string with more than one error message
+ separated by newlines.
+
+ Example:
+
+ C: Putscript "foo" {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ C: Putscript "mysievescript" {110+}
+ C: require ["fileinto"];
+ C:
+ C: if envelope :contains "to" "tmartin+sent" {
+ C: fileinto "INBOX.sent";
+ C: }
+ S: OK
+
+
+2.7. LISTSCRIPTS Command
+
+ This command lists the scripts the user has on the server. Upon
+ success a list of CRLF separated script names (each represented
+ as a quoted or literal string) is returned followed by an OK
+ response. If there exists an active script the atom ACTIVE is
+ appended to the corresponding script name. The atom ACTIVE
+ MUST NOT appear on more than one response line.
+
+ Example:
+
+ C: Listscripts
+ S: "summer_script"
+ S: "vacation_script"
+ S: {13+}
+ S: clever"script
+ S: "main_script" ACTIVE
+ S: OK
+
+ C: listscripts
+ S: "summer_script"
+ S: "main_script" active
+ S: OK
+
+
+2.8. SETACTIVE Command
+
+ Arguments:
+ String - script name
+
+ This command sets a script active. If the script name is the empty
+ string (i.e. "") then any active script is disabled. Disabling an
+ active script when there is no script active is not an error and
+ MUST result in OK reply.
+
+ If the script does not exist on the server then the server MUST
+ reply with a NO response.
+
+ Examples:
+
+ C: Setactive "vacationscript"
+ S: Ok
+
+ C: Setactive ""
+ S: Ok
+
+ C: Setactive "baz"
+ S: No "There is no script by that name"
+
+ C: Setactive "baz"
+ S: No {31+}
+ S: There is no script by that name
+
+
+2.9. GETSCRIPT Command
+
+ Arguments:
+ String - Script name
+
+ This command gets the contents of the specified script. If the
+ script does not exist the server MUST reply with a NO response. Upon
+ success a string with the contents of the script is returned
+ followed by a OK response.
+
+ Example:
+
+ C: Getscript "myscript"
+ S: {54+}
+ S: #this is my wonderful script
+ S: reject "I reject all";
+ S:
+ S: OK
+
+
+2.10. DELETESCRIPT Command
+
+ Parameters:
+ sieve-name - Script name
+
+ This command is used to delete a user's Sieve script. Servers MUST
+ reply with a NO response if the script does not exist. The server
+ MUST NOT allow the client to delete an active script, so the server
+ MUST reply with a NO response if attempted. If a client wishes to
+ delete an active script it should use the SETACTIVE command to
+ disable the script first.
+
+ Example:
+
+ C: Deletescript "foo"
+ S: Ok
+
+ C: Deletescript "baz"
+ S: No "You may not delete an active script"
+
+
+3. Sieve URL Scheme
+
+ URI scheme name: sieve
+
+ Status: permanent
+
+ URI scheme syntax:
+
+ Described using ABNF [ABNF] and ABNF entities from [URI-GEN].
+
+ sieveurl = sieveurl-server / sieveurl-script
+
+ sieveurl-server = "sieve://" authority
+
+ sieveurl-script = "sieve://" [ authority ] "/" scriptname
+
+ scriptname = *pchar
+
+ URI scheme semantics:
+
+ A Sieve URL identifies a Sieve server or a Sieve
+ script on a Sieve server. <<The latter form is associated with
+ application/sieve MIME type.>>
+ <<There is no MIME type associated with this URI.>>
+
+ The server form is used in the REFERRAL response code in order
+ to designate another server where the client should perform
+ its operations.
+
+ The script form allows to retrieve (GETSCRIPT), update
+ (PUTSCRIPT), delete (DELETESCRIPT) or activate (SETACTIVE)
+ the named script, however the most typical action would be to
+ retrieve the script. If the script name is empty, the URI
+ requests that the client lists available scripts using
+ the LISTSCRIPTS command.
+
+ Encoding considerations: The script name, if present,
+ is in UTF-8. Non-US-ASCII UTF-8 octets MUST be percent-encoded as
+ described in [URI-GEN].
+
+ The user name (in the "authority" part), if present,
+ is in UTF-8. Non-US-ASCII UTF-8 octets MUST be percent-encoded as
+ described in [URI-GEN].
+
+ Applications/protocols that use this URI scheme name:
+ ManageSieve [RFC XXXX] clients and servers.
+ Clients that can store user preferences in protocols such as
+ LDAP or ACAP. <<Add ref>>
+
+ Interoperability considerations: None.
+
+ Security considerations: <<None>>.
+
+ Contact: Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+ References: This document and <<RFC 3028>> [SIEVE].
+
+
+4. Formal Syntax
+
+ The following syntax specification uses the augmented Backus-Naur
+ Form (BNF) notation as specified in [ABNF]. This uses the ABNF core
+ rules as specified in Appendix A of the ABNF specification [ABNF].
+
+ Except as noted otherwise, all alphabetic characters are case-
+ insensitive. The use of upper or lower case characters to define
+ token strings is for editorial clarity only. Implementations MUST
+ accept these strings in a case-insensitive fashion.
+
+
+ SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-21 / %x23-5B /
+ %x5D-7F
+ ;; any TEXT-CHAR except QUOTED-SPECIALS
+
+ QUOTED-CHAR = SAFE-UTF8-CHAR / DQUOTE QUOTED-SPECIALS
+
+ QUOTED-SPECIALS = DQUOTE / "\"
+
+ SAFE-UTF8-CHAR = SAFE-CHAR / UTF8-2 / UTF8-3 / UTF8-4 /
+ UTF8-5 / UTF8-6
+
+ UTF8-1 = %x80-BF
+
+ UTF8-2 = %xC0-DF UTF8-1
+
+ UTF8-3 = %xE0-EF 2UTF8-1
+
+ UTF8-4 = %xF0-F7 3UTF8-1
+
+ UTF8-5 = %xF8-FB 4UTF8-1
+
+ UTF8-6 = %xFC-FD 5UTF8-1
+
+ auth-type = DQUOTE auth-type-name DQUOTE
+
+ auth-type-name = iana-token
+ ;; as defined in SASL [SASL]
+
+ command = command-authenticate / command-logout /
+ command-getscript / command-setactive /
+ command-listscripts / command-deletescript /
+ command-putscript / command-capability /
+ command-havespace / command-starttls
+
+ command-authenticate = "AUTHENTICATE" SP auth-type [SP string]
+ *(CRLF string) CRLF
+
+ command-capability = "CAPABILITY" CRLF
+
+ command-deletescript = "DELETESCRIPT" SP sieve-name CRLF
+
+ command-getscript = "GETSCRIPT" SP sieve-name CRLF
+
+ command-havespace = "HAVESPACE" SP sieve-name SP number CRLF
+
+ command-listscripts = "LISTSCRIPTS" CRLF
+
+ command-logout = "LOGOUT" CRLF
+
+ command-putscript = "PUTSCRIPT" SP sieve-name SP string CRLF
+
+ command-setactive = "SETACTIVE" SP sieve-name CRLF
+
+ command-starttls = "STARTTLS" CRLF
+
+ literal = "{" number "+}" CRLF *OCTET
+ ;; The number represents the number of
+ ;; octets.
+<<Need to make "+" only allowed from clients to server, to match
+ IMAP LITERAL+). Also need to also update the examples to match.>>
+
+ number = 1*DIGIT
+ ;; A 32-bit unsigned number.
+ ;; (0 <= n < 4,294,967,296)
+
+ quoted = DQUOTE *1024QUOTED-CHAR DQUOTE
+ ;; limited to 1024 octets between the <">s
+
+ resp-code = "AUTH-TOO-WEAK" / "ENCRYPT-NEEDED" /
+ "QUOTA" / resp-code-sasl /
+ resp-code-referral /
+ "TRANSITION-NEEDED" / "TRYLATER" /
+ resp-code-ext
+
+ resp-code-referral = "REFERRAL" SP sieveurl
+
+ resp-code-sasl = "SASL" SP string
+
+ resp-code-ext = iana-token [SP extension-data]
+ ;; unknown response codes MUST be tolerated
+ ;; by the client. <<"iana-token" and
+ ;; "extension-data" are defined in ACAP>>
+
+ response = response-authenticate /
+ response-logout /
+ response-getscript /
+ response-setactive /
+ response-listscripts /
+ response-deletescript /
+ response-putscript /
+ response-capability /
+ response-havespace /
+ response-starttls
+
+ response-authenticate = *(string CRLF) (response-oknobye)
+
+ response-capability = *(single-capability) response-oknobye
+
+ single-capability = capability-name [SP string] CRLF
+
+ capability-name = string
+ <<Note that literals are allowed!>>
+
+ initial-capabilities = DQUOTE "IMPLEMENTATION" DQUOTE SP string /
+ DQUOTE "SASL" DQUOTE SP sasl-mechs /
+ DQUOTE "SIEVE" DQUOTE SP sieve-extensions /
+ DQUOTE "STARTTLS" DQUOTE
+ ;; Each capability conforms to
+ ;; the syntax for single-capability.
+ ;; Also note that the capability name
+ ;; can be returned as either literal
+ ;; or quoted, even though only "quoted"
+ ;; string is shown above.
+
+ sasl-mechs = string
+ ; space separated list of SASL mechanisms,
+ ; can be empty
+
+ sieve-extensions = string
+ ; space separated list of supported SIEVE extensions,
+ ; can be empty
+
+ response-deletescript = response-oknobye
+
+ response-getscript = [string CRLF] response-oknobye
+ <<Need to rewrite ABNF to make it clear that
+ script content is required with OK response
+ and must not be sent with the NO (and
+ possibly BYE) response.>>
+
+ response-havespace = response-oknobye
+
+ response-listscripts = *(sieve-name [SP "ACTIVE"] CRLF)
+ response-oknobye
+ ;; ACTIVE may only occur with one sieve-name
+
+ response-logout = response-oknobye
+
+ response-oknobye = ("OK" / "NO" / "BYE") [SP "(" resp-code ")"]
+ [SP string] CRLF
+
+ response-putscript = response-oknobye
+
+ response-setactive = response-oknobye
+
+ response-starttls = response-oknobye
+
+ sieve-name = string
+ ;; MUST NOT contain NUL, CR or LF
+
+ string = quoted / literal
+
+
+5. Security Considerations
+
+ The AUTHENTICATE command uses SASL [SASL] to provide authentication
+ and authorization services.
+ Integrity and privacy services can be provided by [SASL] and/or
+ [TLS]. When a SASL mechanism is used the security considerations for
+ that mechanism apply.
+
+ This protocol's transactions are susceptible to passive observers or
+ man in the middle attacks which alter the data, unless the optional
+ encryption and integrity services of the SASL (via the AUTHENTICATE
+ command) and/or [TLS] (via the STARTTLS command) are enabled, or an
+ external security mechanism is used for protection. It may be useful
+ to allow configuration of both clients and servers to refuse to
+ transfer sensitive information in the absence of strong encryption.
+
+
+6. IANA Considerations
+
+ IANA is requested to reserve TCP port number 2000 for use with
+ the Manage Sieve protocol described in this document.
+
+ IANA is requested to create a new registry for Manage Sieve
+ capabilities. The registration template for Manage Sieve
+ capabilities is specified in the next section.
+ Manage Sieve protocol capabilities MUST be specified in a standards
+ track or IESG approved experimental RFC.
+
+ <<Add a new registry for response codes, as per ABNF comments.>>
+
+ <<Reference to SIEVE URL registration.>>
+
+
+6.1. Manage Sieve Capability Registration Template
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name:
+
+ Description:
+
+ Relevant publications:
+
+ Person & email address to contact for further information:
+
+ Author/Change controller:
+
+
+6.2. Registration of Initial Manage Sieve capabilities.
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: IMPLEMENTATION
+
+ Description: Its value contains name of server
+ implementation and its version.
+
+ Relevant publications: this RFC, section 1.8.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: SASL
+
+ Description: Its value contains a space separated
+ list of SASL mechanisms supported by server.
+
+ Relevant publications: this RFC, sections 1.8 and 2.1.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: SIEVE
+
+ Description: Its value contains a space separated
+ list of supported SIEVE extensions
+
+ Relevant publications: this RFC, section 1.8.
+ <<Also [SIEVE]>>
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: STARTTLS
+
+ Description: This capability is returned if server
+ supports TLS (STARTTLS command).
+
+ Relevant publications: this RFC, sections 1.8 and 2.2.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+
+ To: iana@iana.org
+ Subject: Manage Sieve Capability Registration
+
+ Please register the following Manage Sieve Capability:
+
+ Capability name: NOTIFY
+
+ Description: This capability is returned if server
+ supports 'enotify' Sieve extension.
+
+ Relevant publications: this RFC, section 1.8.
+
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+7. References
+
+7.1. Normative References
+
+ [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", RFC 4234, October 2005.
+
+ [ACAP] Newman, Myers, "ACAP -- Application Configuration Access
+ Protocol", RFC 2244, Innosoft, Netscape, November 1997.
+
+ [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication and
+ Security Layer (SASL)", RFC 4422, June 2006.
+
+ [SASLprep] Zeilega, K., "SASLprep: Stringprep profile for User Names
+ and Passwords", RFC 4013, February 2005.
+
+ [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of
+ Internationalized Strings ("stringprep")", RFC 3454, December 2002.
+
+ [SASL-ANON] K. Zeilenga (Ed.), "Anonymous Simple Authentication and
+ Security Layer (SASL) Mechanism", RFC 4505, June 2006.
+
+ [SIEVE] Guenther, P. and Showalter, T., "Sieve: An Email Filtering
+ Language", work in Progress, draft-ietf-sieve-3028bis-XX.txt
+
+ [TLS] Dierks, T. and E. Rescorla, "The Transport Layer Security
+ (TLS) Protocol Version 1.1", RFC 4346, April 2006.
+
+ [URI-GEN] Berners-Lee, T., Fielding, R. and L. Masinter,
+ "Uniform Resource Identifier (URI): Generic Syntax", RFC 3986,
+ January 2005.
+
+ [BASE64] - Josefsson, S., "The Base16, Base32, and Base64 Data
+ Encodings", RFC 4648, October 2006.
+
+ [NOTIFY] Melnikov, A. (Ed.), Leiba, B. (Ed.), Segmuller, W. and
+ T. Martin, "Sieve Extension: Notifications", work in progress,
+ draft-ietf-sieve-notify-XX.txt.
+
+
+7.2. Informative References
+
+ [IMAP4rev1] Crispin, M., "Internet Message Access Protocol - Version
+ 4rev1", RFC 3501, March 2003.
+
+ [PLAIN] K. Zeilenga, "The PLAIN Simple Authentication and Security
+ Layer (SASL) Mechanism", RFC 4616, August 2006.
+
+ [DIGEST-MD5] Melnikov, A. (Ed.), "Using Digest Authentication as
+ a SASL Mechanism", work in progress,
+ draft-ietf-sasl-rfc2831bis-XX.txt.
+
+
+8. Author's Address
+
+ Tim Martin
+ BeThereBeSquare Inc.
+ 672 Haight st.
+ San Francisco, CA 94117
+ Phone: (510) 260-4175
+ EMail: timmartin@alumni.cmu.edu
+
+ Alexey Melnikov
+ Isode Ltd.
+ 5 Castle Business Village
+ 36 Station Road
+ Hampton, Middlesex, TW12 2BX, GB
+ Email: alexey.melnikov@isode.com
+
+
+Intellectual Property
+
+ The IETF takes no position regarding the validity or scope of any
+ Intellectual Property Rights or other rights that might be claimed
+ to pertain to the implementation or use of the technology described
+ in this document or the extent to which any license under such
+ rights might or might not be available; nor does it represent that
+ it has made any independent effort to identify any such rights.
+ Information on the procedures with respect to rights in RFC
+ documents can be found in BCP 78 and BCP 79.
+
+ Copies of IPR disclosures made to the IETF Secretariat and any
+ assurances of licenses to be made available, or the result of an
+ attempt made to obtain a general license or permission for the use
+ of such proprietary rights by implementers or users of this
+ specification can be obtained from the IETF on-line IPR repository
+ at http://www.ietf.org/ipr.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights that may cover technology that may be required to implement
+ this standard. Please address the information to the IETF at ietf-
+ ipr@ietf.org.
+
+
+18. Full Copyright Statement
+
+ Copyright (C) The IETF Trust (2007).
+
+ This document is subject to the rights, licenses and restrictions
+ contained in BCP 78, and except as set forth therein, the authors
+ retain all their rights.
+
+ This document and the information contained herein are provided on
+ an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE
+ REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE
+ IETF TRUST AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY
+ WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE
+ ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS
+ FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+Appendix A. Acknowledgments
+
+ Thanks to Simon Josefsson, Larry Greenfield, Allen Johnson, Chris
+ Newman, Lyndon Nerenberg, Tim Showalter, Sarah Robeson, Walter
+ Wong, Barry Leiba, Arnt Gulbrandsen, Stephan Bosch for help with
+ this document.
diff --git a/src/common/libManageSieve/doc/Manage Sieve/rfc5804.txt b/src/common/libManageSieve/doc/Manage Sieve/rfc5804.txt
new file mode 100644
index 00000000..d6deaa89
--- /dev/null
+++ b/src/common/libManageSieve/doc/Manage Sieve/rfc5804.txt
@@ -0,0 +1,2747 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) A. Melnikov, Ed.
+Request for Comments: 5804 Isode Limited
+Category: Standards Track T. Martin
+ISSN: 2070-1721 BeThereBeSquare, Inc.
+ July 2010
+
+
+ A Protocol for Remotely Managing Sieve Scripts
+
+Abstract
+
+ Sieve scripts allow users to filter incoming email. Message stores
+ are commonly sealed servers so users cannot log into them, yet users
+ must be able to update their scripts on them. This document
+ describes a protocol "ManageSieve" for securely managing Sieve
+ scripts on a remote server. This protocol allows a user to have
+ multiple scripts, and also alerts a user to syntactically flawed
+ scripts.
+
+Status of This Memo
+
+ This is an Internet Standards Track document.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Further information on
+ Internet Standards is available in Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc5804.
+
+Copyright Notice
+
+ Copyright (c) 2010 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+Melnikov & Martin Standards Track [Page 1]
+
+RFC 5804 ManageSieve July 2010
+
+
+Table of Contents
+
+ 1. Introduction ....................................................3
+ 1.1. Commands and Responses .....................................3
+ 1.2. Syntax .....................................................3
+ 1.3. Response Codes .............................................3
+ 1.4. Active Script ..............................................6
+ 1.5. Quotas .....................................................6
+ 1.6. Script Names ...............................................6
+ 1.7. Capabilities ...............................................7
+ 1.8. Transport ..................................................9
+ 1.9. Conventions Used in This Document .........................10
+ 2. Commands .......................................................10
+ 2.1. AUTHENTICATE Command ......................................11
+ 2.1.1. Use of SASL PLAIN Mechanism over TLS ...............16
+ 2.2. STARTTLS Command ..........................................16
+ 2.2.1. Server Identity Check ..............................17
+ 2.3. LOGOUT Command ............................................20
+ 2.4. CAPABILITY Command ........................................20
+ 2.5. HAVESPACE Command .........................................20
+ 2.6. PUTSCRIPT Command .........................................21
+ 2.7. LISTSCRIPTS Command .......................................23
+ 2.8. SETACTIVE Command .........................................24
+ 2.9. GETSCRIPT Command .........................................25
+ 2.10. DELETESCRIPT Command .....................................25
+ 2.11. RENAMESCRIPT Command .....................................26
+ 2.12. CHECKSCRIPT Command ......................................27
+ 2.13. NOOP Command .............................................28
+ 2.14. Recommended Extensions ...................................28
+ 2.14.1. UNAUTHENTICATE Command ............................28
+ 3. Sieve URL Scheme ...............................................29
+ 4. Formal Syntax ..................................................31
+ 5. Security Considerations ........................................37
+ 6. IANA Considerations ............................................38
+ 6.1. ManageSieve Capability Registration Template ..............39
+ 6.2. Registration of Initial ManageSieve Capabilities ..........39
+ 6.3. ManageSieve Response Code Registration Template ...........41
+ 6.4. Registration of Initial ManageSieve Response Codes ........41
+ 7. Internationalization Considerations ............................46
+ 8. Acknowledgements ...............................................46
+ 9. References .....................................................47
+ 9.1. Normative References ......................................47
+ 9.2. Informative References ....................................48
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 2]
+
+RFC 5804 ManageSieve July 2010
+
+
+1. Introduction
+
+1.1. Commands and Responses
+
+ A ManageSieve connection consists of the establishment of a client/
+ server network connection, an initial greeting from the server, and
+ client/server interactions. These client/server interactions consist
+ of a client command, server data, and a server completion result
+ response.
+
+ All interactions transmitted by client and server are in the form of
+ lines, that is, strings that end with a CRLF. The protocol receiver
+ of a ManageSieve client or server is either reading a line or reading
+ a sequence of octets with a known count followed by a line.
+
+1.2. Syntax
+
+ ManageSieve is a line-oriented protocol much like [IMAP] or [ACAP],
+ which runs over TCP. There are three data types: atoms, numbers and
+ strings. Strings may be quoted or literal. See [ACAP] for detailed
+ descriptions of these types.
+
+ Each command consists of an atom (the command name) followed by zero
+ or more strings and numbers terminated by CRLF.
+
+ All client queries are replied to with either an OK, NO, or BYE
+ response. Each response may be followed by a response code (see
+ Section 1.3) and by a string consisting of human-readable text in the
+ local language (as returned by the LANGUAGE capability; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. The contents of the string
+ SHOULD be shown to the user ,and implementations MUST NOT attempt to
+ parse the message for meaning.
+
+ The BYE response SHOULD be used if the server wishes to close the
+ connection. A server may wish to do this because the client was idle
+ for too long or there were too many failed authentication attempts.
+ This response can be issued at any time and should be immediately
+ followed by a server hang-up of the connection. If a server has an
+ inactivity timeout resulting in client autologout, it MUST be no less
+ than 30 minutes after successful authentication. The inactivity
+ timeout MAY be less before authentication.
+
+1.3. Response Codes
+
+ An OK, NO, or BYE response from the server MAY contain a response
+ code to describe the event in a more detailed machine-parsable
+ fashion. A response code consists of data inside parentheses in the
+ form of an atom, possibly followed by a space and arguments.
+
+
+
+Melnikov & Martin Standards Track [Page 3]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response codes are defined when there is a specific action that a
+ client can take based upon the additional information. In order to
+ support future extension, the response code is represented as a
+ slash-separated (Solidus, %x2F) hierarchy with each level of
+ hierarchy representing increasing detail about the error. Response
+ codes MUST NOT start with the Solidus character. Clients MUST
+ tolerate additional hierarchical response code detail that they don't
+ understand. For example, if the client supports the "QUOTA" response
+ code, but doesn't understand the "QUOTA/MAXSCRIPTS" response code, it
+ should treat "QUOTA/MAXSCRIPTS" as "QUOTA".
+
+ Client implementations MUST tolerate (ignore) response codes that
+ they do not recognize.
+
+ The currently defined response codes are the following:
+
+ AUTH-TOO-WEAK
+
+ This response code is returned in the NO or BYE response from an
+ AUTHENTICATE command. It indicates that site security policy forbids
+ the use of the requested mechanism for the specified authentication
+ identity.
+
+ ENCRYPT-NEEDED
+
+ This response code is returned in the NO or BYE response from an
+ AUTHENTICATE command. It indicates that site security policy
+ requires the use of a strong encryption mechanism for the specified
+ authentication identity and mechanism.
+
+ QUOTA
+
+ If this response code is returned in the NO/BYE response, it means
+ that the command would have placed the user above the site-defined
+ quota constraints. If this response code is returned in the OK
+ response, it can mean that the user's storage is near its quota, or
+ it can mean that the account exceeded its quota but that the
+ condition is being allowed by the server (the server supports
+ so-called soft quotas). The QUOTA response code has two more
+ detailed variants: "QUOTA/MAXSCRIPTS" (the maximum number of per-user
+ scripts) and "QUOTA/MAXSIZE" (the maximum script size).
+
+ REFERRAL
+
+ This response code may be returned with a BYE result from any
+ command, and includes a mandatory parameter that indicates what
+ server to access to manage this user's Sieve scripts. The server
+ will be specified by a Sieve URL (see Section 3). The scriptname
+
+
+
+Melnikov & Martin Standards Track [Page 4]
+
+RFC 5804 ManageSieve July 2010
+
+
+ portion of the URL MUST NOT be specified. The client should
+ authenticate to the specified server and use it for all further
+ commands in the current session.
+
+ SASL
+
+ This response code can occur in the OK response to a successful
+ AUTHENTICATE command and includes the optional final server response
+ data from the server as specified by [SASL].
+
+ TRANSITION-NEEDED
+
+ This response code occurs in a NO response of an AUTHENTICATE
+ command. It indicates that the user name is valid, but the entry in
+ the authentication database needs to be updated in order to permit
+ authentication with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, verifying server identity
+ as specified in Section 2.2.1, and finally authenticating once using
+ the [PLAIN] authentication mechanism. The selected mechanism SHOULD
+ then work for authentications in subsequent sessions.
+
+ This condition can happen if a user has an entry in a system
+ authentication database such as Unix /etc/passwd, but does not have
+ credentials suitable for use by the specified mechanism.
+
+ TRYLATER
+
+ A command failed due to a temporary server failure. The client MAY
+ continue using local information and try the command later. This
+ response code only makes sense when returned in a NO/BYE response.
+
+ ACTIVE
+
+ A command failed because it is not allowed on the active script, for
+ example, DELETESCRIPT on the active script. This response code only
+ makes sense when returned in a NO/BYE response.
+
+ NONEXISTENT
+
+ A command failed because the referenced script name doesn't exist.
+ This response code only makes sense when returned in a NO/BYE
+ response.
+
+ ALREADYEXISTS
+
+ A command failed because the referenced script name already exists.
+ This response code only makes sense when returned in a NO/BYE
+ response.
+
+
+
+Melnikov & Martin Standards Track [Page 5]
+
+RFC 5804 ManageSieve July 2010
+
+
+ TAG
+
+ This response code name is followed by a string specified in the
+ command. See Section 2.13 for a possible use case.
+
+ WARNINGS
+
+ This response code MAY be returned by the server in the OK response
+ (but it might be returned with the NO/BYE response as well) and
+ signals the client that even though the script is syntactically
+ valid, it might contain errors not intended by the script writer.
+ This response code is typically returned in response to PUTSCRIPT
+ and/or CHECKSCRIPT commands. A client seeing such response code
+ SHOULD present the returned warning text to the user.
+
+1.4. Active Script
+
+ A user may have multiple Sieve scripts on the server, yet only one
+ script may be used for filtering of incoming messages. This is the
+ active script. Users may have zero or one active script and MUST use
+ the SETACTIVE command described below for changing the active script
+ or disabling Sieve processing. For example, users may have an
+ everyday script they normally use and a special script they use when
+ they go on vacation. Users can change which script is being used
+ without having to download and upload a script stored somewhere else.
+
+1.5. Quotas
+
+ Servers SHOULD impose quotas to prevent malicious users from
+ overflowing available storage. If a command would place a user over
+ a quota setting, servers that impose such quotas MUST reply with a NO
+ response containing the QUOTA response code. Client implementations
+ MUST be able to handle commands failing because of quota
+ restrictions.
+
+1.6. Script Names
+
+ A Sieve script name is a sequence of Unicode characters encoded in
+ UTF-8 [UTF-8]. A script name MUST comply with Net-Unicode Definition
+ (Section 2 of [NET-UNICODE]), with the additional restriction of
+ prohibiting the following Unicode characters:
+
+ o 0000-001F; [CONTROL CHARACTERS]
+
+ o 007F; DELETE
+
+ o 0080-009F; [CONTROL CHARACTERS]
+
+
+
+
+Melnikov & Martin Standards Track [Page 6]
+
+RFC 5804 ManageSieve July 2010
+
+
+ o 2028; LINE SEPARATOR
+
+ o 2029; PARAGRAPH SEPARATOR
+
+ Sieve script names MUST be at least one octet (and hence Unicode
+ character) long. Zero octets script name has a special meaning (see
+ Section 2.8). Servers MUST allow names of up to 128 Unicode
+ characters in length (which can take up to 512 bytes when encoded in
+ UTF-8, not counting the terminating NUL), and MAY allow longer names.
+ A server that receives a script name longer than its internal limit
+ MUST reject the corresponding operation, in particular it MUST NOT
+ truncate the script name.
+
+1.7. Capabilities
+
+ Server capabilities are sent automatically by the server upon a
+ client connection, or after successful STARTTLS and AUTHENTICATE
+ (which establishes a Simple Authentication and Security Layer (SASL))
+ commands. Capabilities may change immediately after a successfully
+ completed STARTTLS command, and/or immediately after a successfully
+ completed AUTHENTICATE command, and/or after a successfully completed
+ UNAUTHENTICATE command (see Section 2.14.1). Capabilities MUST
+ remain static at all other times.
+
+ Clients MAY request the capabilities at a later time by issuing the
+ CAPABILITY command described later. The capabilities consist of a
+ series of lines each with one or two strings. The first string is
+ the name of the capability, which is case-insensitive. The second
+ optional string is the value associated with that capability. Order
+ of capabilities is arbitrary, but each capability name can appear at
+ most once.
+
+ The following capabilities are defined in this document:
+
+ IMPLEMENTATION - Name of implementation and version. This capability
+ MUST always be returned by the server.
+
+ SASL - List of SASL mechanisms supported by the server, each
+ separated by a space. This list can be empty if and only if STARTTLS
+ is also advertised. This means that the client must negotiate TLS
+ encryption with STARTTLS first, at which point the SASL capability
+ will list a non-empty list of SASL mechanisms.
+
+ SIEVE - List of space-separated Sieve extensions (as listed in Sieve
+ "require" action [SIEVE]) supported by the Sieve engine. This
+ capability MUST always be returned by the server.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 7]
+
+RFC 5804 ManageSieve July 2010
+
+
+ STARTTLS - If TLS [TLS] is supported by this implementation. Before
+ advertising this capability a server MUST verify to the best of its
+ ability that TLS can be successfully negotiated by a client with
+ common cipher suites. Specifically, a server should verify that a
+ server certificate has been installed and that the TLS subsystem has
+ successfully initialized. This capability SHOULD NOT be advertised
+ once STARTTLS or AUTHENTICATE command completes successfully. Client
+ and server implementations MUST implement the STARTTLS extension.
+
+ MAXREDIRECTS - Specifies the limit on the number of Sieve "redirect"
+ actions a script can perform during a single evaluation. Note that
+ this is different from the total number of "redirect" actions a
+ script can contain. The value is a non-negative number represented
+ as a ManageSieve string.
+
+ NOTIFY - A space-separated list of URI schema parts for supported
+ notification methods. This capability MUST be specified if the Sieve
+ implementation supports the "enotify" extension [NOTIFY].
+
+ LANGUAGE - The language (<Language-Tag> from [RFC5646]) currently
+ used for human-readable error messages. If this capability is not
+ returned, the "i-default" [RFC2277] language is assumed. Note that
+ the current language MAY be per-user configurable (i.e., it MAY
+ change after authentication).
+
+ OWNER - The canonical name of the logged-in user (SASL "authorization
+ identity") encoded in UTF-8. This capability MUST NOT be returned in
+ unauthenticated state and SHOULD be returned once the AUTHENTICATE
+ command succeeds.
+
+ VERSION - This capability MUST be returned by servers compliant with
+ this document or its successor. For servers compliant with this
+ document, the capability value is the string "1.0". Lack of this
+ capability means that the server predates this specification and thus
+ doesn't support the following commands: RENAMESCRIPT, CHECKSCRIPT,
+ and NOOP.
+
+ Section 2.14 defines some additional ManageSieve extensions and their
+ respective capabilities.
+
+ A server implementation MUST return SIEVE, IMPLEMENTATION, and
+ VERSION capabilities.
+
+ A client implementation MUST ignore any listed capabilities that it
+ does not understand.
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 8]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Example:
+
+ S: "IMPlemENTATION" "Example1 ManageSieved v001"
+ S: "SASl" "DIGEST-MD5 GSSAPI"
+ S: "SIeVE" "fileinto vacation"
+ S: "StaRTTLS"
+ S: "NOTIFY" "xmpp mailto"
+ S: "MAXREdIRECTS" "5"
+ S: "VERSION" "1.0"
+ S: OK
+
+ After successful authentication, this might look like this:
+
+ Example:
+
+ S: "IMPlemENTATION" "Example1 ManageSieved v001"
+ S: "SASl" "DIGEST-MD5 GSSAPI"
+ S: "SIeVE" "fileinto vacation"
+ S: "NOTIFY" "xmpp mailto"
+ S: "OWNER" "alexey@example.com"
+ S: "MAXREdIRECTS" "5"
+ S: "VERSION" "1.0"
+ S: OK
+
+1.8. Transport
+
+ The ManageSieve protocol assumes a reliable data stream such as that
+ provided by TCP. When TCP is used, a ManageSieve server typically
+ listens on port 4190.
+
+ Before opening the TCP connection, the ManageSieve client first MUST
+ resolve the Domain Name System (DNS) hostname associated with the
+ receiving entity and determine the appropriate TCP port for
+ communication with the receiving entity. The process is as follows:
+
+ 1. Attempt to resolve the hostname using a [DNS-SRV] Service of
+ "sieve" and a Proto of "tcp" for the target domain (e.g.,
+ "example.net"), resulting in resource records such as
+ "_sieve._tcp.example.net.". The result of the SRV lookup, if
+ successful, will be one or more combinations of a port and
+ hostname; the ManageSieve client MUST resolve the returned
+ hostnames to IPv4/IPv6 addresses according to returned SRV record
+ weight. IP addresses from the first successfully resolved
+ hostname (with the corresponding port number returned by SRV
+ lookup) are used to connect to the server. If connection using
+ one of the IP addresses fails, the next resolved IP address is
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 9]
+
+RFC 5804 ManageSieve July 2010
+
+
+ used to connect. If connection to all resolved IP addresses
+ fails, then the resolution/connect is repeated for the next
+ hostname returned by SRV lookup.
+
+ 2. If the SRV lookup fails, the fallback SHOULD be a normal IPv4 or
+ IPv6 address record resolution to determine the IP address, where
+ the port used is the default ManageSieve port of 4190.
+
+1.9. Conventions Used in This Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [KEYWORDS].
+
+ In examples, "C:" and "S:" indicate lines sent by the client and
+ server respectively. Line breaks that do not start a new "C:" or
+ "S:" exist for editorial reasons.
+
+ Examples of authentication in this document are using DIGEST-MD5
+ [DIGEST-MD5] and GSSAPI [GSSAPI] SASL mechanisms.
+
+2. Commands
+
+ This section and its subsections describe valid ManageSieve commands.
+ Upon initial connection to the server, the client's session is in
+ non-authenticated state. Prior to successful authentication, only
+ the AUTHENTICATE, CAPABILITY, STARTTLS, LOGOUT, and NOOP (see Section
+ 2.13) commands are valid. ManageSieve extensions MAY define other
+ commands that are valid in non-authenticated state. Servers MUST
+ reject all other commands with a NO response. Clients may pipeline
+ commands (send more than one command at a time without waiting for
+ completion of the first command). However, a group of commands sent
+ together MUST NOT have an AUTHENTICATE (*), a STARTTLS, or a
+ HAVESPACE command anywhere but the last command in the list.
+
+ (*) - The only exception to this rule is when the AUTHENTICATE
+ command contains an initial response for a SASL mechanism that allows
+ clients to send data first, the mechanism is known to complete in one
+ round trip, and the mechanism doesn't negotiate a SASL security
+ layer. Two examples of such SASL mechanisms are PLAIN [PLAIN] and
+ EXTERNAL [SASL].
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 10]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.1. AUTHENTICATE Command
+
+ Arguments: String - mechanism
+ String - initial data (optional)
+
+ The AUTHENTICATE command indicates a SASL [SASL] authentication
+ mechanism to the server. If the server supports the requested
+ authentication mechanism, it performs an authentication protocol
+ exchange to identify and authenticate the user. Optionally, it also
+ negotiates a security layer for subsequent protocol interactions. If
+ the requested authentication mechanism is not supported, the server
+ rejects the AUTHENTICATE command by sending the NO response.
+
+ The authentication protocol exchange consists of a series of server
+ challenges and client responses that are specific to the selected
+ authentication mechanism. A server challenge consists of a string
+ (quoted or literal) followed by a CRLF. The contents of the string
+ is a base-64 encoding [BASE64] of the SASL data. A client response
+ consists of a string (quoted or literal) with the base-64 encoding of
+ the SASL data followed by a CRLF. If the client wishes to cancel the
+ authentication exchange, it issues a string containing a single "*".
+ If the server receives such a response, it MUST reject the
+ AUTHENTICATE command by sending a NO reply.
+
+ Note that an empty challenge/response is sent as an empty string. If
+ the mechanism dictates that the final response is sent by the server,
+ this data MAY be placed within the data portion of the SASL response
+ code to save a round trip.
+
+ The optional initial-response argument to the AUTHENTICATE command is
+ used to save a round trip when using authentication mechanisms that
+ are defined to send no data in the initial challenge. When the
+ initial-response argument is used with such a mechanism, the initial
+ empty challenge is not sent to the client and the server uses the
+ data in the initial-response argument as if it were sent in response
+ to the empty challenge. If the initial-response argument to the
+ AUTHENTICATE command is used with a mechanism that sends data in the
+ initial challenge, the server MUST reject the AUTHENTICATE command by
+ sending the NO response.
+
+ The service name specified by this protocol's profile of SASL is
+ "sieve".
+
+ Reauthentication is not supported by ManageSieve protocol's profile
+ of SASL. That is, after a successfully completed AUTHENTICATE
+ command, no more AUTHENTICATE commands may be issued in the same
+ session. After a successful AUTHENTICATE command completes, a server
+ MUST reject any further AUTHENTICATE commands with a NO reply.
+
+
+
+Melnikov & Martin Standards Track [Page 11]
+
+RFC 5804 ManageSieve July 2010
+
+
+ However, note that a server may implement the UNAUTHENTICATE
+ extension described in Section 2.14.1.
+
+ If a security layer is negotiated through the SASL authentication
+ exchange, it takes effect immediately following the CRLF that
+ concludes the successful authentication exchange for the client, and
+ the CRLF of the OK response for the server.
+
+ When a security layer takes effect, the ManageSieve protocol is reset
+ to the initial state (the state in ManageSieve after a client has
+ connected to the server). The server MUST discard any knowledge
+ obtained from the client that was not obtained from the SASL (or TLS)
+ negotiation itself. Likewise, the client MUST discard any knowledge
+ obtained from the server, such as the list of ManageSieve extensions,
+ that was not obtained from the SASL (and/or TLS) negotiation itself.
+ (Note that a client MAY compare the advertised SASL mechanisms before
+ and after authentication in order to detect an active down-
+ negotiation attack. See below.)
+
+ Once a SASL security layer is established, the server MUST re-issue
+ the capability results, followed by an OK response. This is
+ necessary to protect against man-in-the-middle attacks that alter the
+ capabilities list prior to SASL negotiation. The capability results
+ MUST include all SASL mechanisms the server was capable of
+ negotiating with that client. This is done in order to allow the
+ client to detect an active down-negotiation attack. If a user-
+ oriented client detects such a down-negotiation attack, it SHOULD
+ either notify the user (it MAY give the user the opportunity to
+ continue with the ManageSieve session in this case) or close the
+ transport connection and indicate that a down-negotiation attack
+ might be in progress. If an automated client detects a down-
+ negotiation attack, it SHOULD return or log an error indicating that
+ a possible attack might be in progress and/or SHOULD close the
+ transport connection.
+
+ When both [TLS] and SASL security layers are in effect, the TLS
+ encoding MUST be applied (when sending data) after the SASL encoding.
+
+ Server implementations SHOULD support SASL proxy authentication so
+ that an administrator can administer a user's scripts. Proxy
+ authentication is when a user authenticates as herself/himself but
+ requests the server to act (authorize) as another user.
+
+ The authorization identity generated by this [SASL] exchange is a
+ "simple username" (in the sense defined in [SASLprep]), and both
+ client and server MUST use the [SASLprep] profile of the [StringPrep]
+ algorithm to prepare these names for transmission or comparison. If
+ preparation of the authorization identity fails or results in an
+
+
+
+Melnikov & Martin Standards Track [Page 12]
+
+RFC 5804 ManageSieve July 2010
+
+
+ empty string (unless it was transmitted as the empty string), the
+ server MUST fail the authentication.
+
+ If an AUTHENTICATE command fails with a NO response, the client MAY
+ try another authentication mechanism by issuing another AUTHENTICATE
+ command. In other words, the client may request authentication types
+ in decreasing order of preference.
+
+ Note that a failed (NO) response to the AUTHENTICATE command may
+ contain one of the following response codes: AUTH-TOO-WEAK, ENCRYPT-
+ NEEDED, or TRANSITION-NEEDED. See Section 1.3 for detailed
+ description of the relevant conditions.
+
+ To ensure interoperability, both client and server implementations of
+ the ManageSieve protocol MUST implement the SCRAM-SHA-1 [SCRAM] SASL
+ mechanism, as well as [PLAIN] over [TLS].
+
+ Note: use of PLAIN over TLS reflects current use of PLAIN over TLS in
+ other email-related protocols; however, a longer-term goal is to
+ migrate email-related protocols from using PLAIN over TLS to SCRAM-
+ SHA-1 mechanism.
+
+ Examples (Note that long lines are folded for readability and are not
+ part of protocol exchange):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: "VERSION" "1.0"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: "cmVhbG09ImVsd29vZC5pbm5vc29mdC5leGFtcGxlLmNvbSIsbm9uY2U9Ik
+ 9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGFsZ29yaXRobT1tZDUtc2Vz
+ cyxjaGFyc2V0PXV0Zi04"
+ C: "Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuZXhhbXBsZS5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJo
+ aCIsbmM9MDAwMDAwMDEsY25vbmNlPSJPQTZNSFhoNlZxVHJSayIsZGlnZX
+ N0LXVyaT0ic2lldmUvZWx3b29kLmlubm9zb2Z0LmV4YW1wbGUuY29tIixy
+ ZXNwb25zZT1kMzg4ZGFkOTBkNGJiZDc2MGExNTIzMjFmMjE0M2FmNyxxb3
+ A9YXV0aA=="
+ S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZ
+ mZmZA==")
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 13]
+
+RFC 5804 ManageSieve July 2010
+
+
+ A slightly different variant of the same authentication exchange is:
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "VERSION" "1.0"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: {136}
+ S: cmVhbG09ImVsd29vZC5pbm5vc29mdC5leGFtcGxlLmNvbSIsbm9uY2U9Ik
+ 9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGFsZ29yaXRobT1tZDUtc2Vz
+ cyxjaGFyc2V0PXV0Zi04
+ C: {300+}
+ C: Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuZXhhbXBsZS5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJo
+ aCIsbmM9MDAwMDAwMDEsY25vbmNlPSJPQTZNSFhoNlZxVHJSayIsZGlnZX
+ N0LXVyaT0ic2lldmUvZWx3b29kLmlubm9zb2Z0LmV4YW1wbGUuY29tIixy
+ ZXNwb25zZT1kMzg4ZGFkOTBkNGJiZDc2MGExNTIzMjFmMjE0M2FmNyxxb3
+ A9YXV0aA==
+ S: {56}
+ S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
+ C: ""
+ S: OK
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 14]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Another example demonstrating use of SASL PLAIN mechanism under TLS
+ follows. This example also demonstrate use of SASL "initial
+ response" (the second parameter to the Authenticate command):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" ""
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+ C: STARTTLS
+ S: OK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN"
+ S: "SIEVE" "fileinto vacation"
+ S: OK
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xu"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xz"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xy"
+ S: BYE "Too many failed authentication attempts"
+ <Server closes connection>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 15]
+
+RFC 5804 ManageSieve July 2010
+
+
+ The following example demonstrates use of SASL "initial response".
+ It also demonstrates that an empty response can be sent as a literal
+ and that negotiating a SASL security layer results in the server
+ re-issuing server capabilities:
+
+ C: AUTHENTICATE "GSSAPI" {1488+}
+ C: YIIE[...1480 octets here ...]dA==
+ S: {208}
+ S: YIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKic
+ [...114 octets here ...]
+ /yzpAy9p+Y0LanLskOTvMc0MnjgAa4YEr3eJ6
+ C: {0+}
+ C:
+ S: {44}
+ S: BQQF/wAMAAwAAAAAYRGFAo6W0vIHti8i1UXODgEAEAA=
+ C: {44+}
+ C: BQQE/wAMAAwAAAAAIsT1iv9UkZApw471iXt6cwEAAAE=
+ S: OK
+ <Further commands/responses are under SASL security layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "LANGUAGE" "ru"
+ S: "MAXREDIRECTS" "3"
+ S: ok
+
+2.1.1. Use of SASL PLAIN Mechanism over TLS
+
+ This section is normative for ManageSieve client implementations that
+ support SASL [PLAIN] over [TLS].
+
+ If a ManageSieve client is willing to use SASL PLAIN over TLS to
+ authenticate to the ManageSieve server, the client MUST verify the
+ server identity (see Section 2.2.1). If the server identity can't be
+ verified (e.g., the server has not provided any certificate, or if
+ the certificate verification fails), the client MUST NOT attempt to
+ authenticate using the SASL PLAIN mechanism.
+
+2.2. STARTTLS Command
+
+ Support for STARTTLS command in servers is optional. Its
+ availability is advertised with "STARTTLS" capability as described in
+ Section 1.7.
+
+ The STARTTLS command requests commencement of a TLS [TLS]
+ negotiation. The negotiation begins immediately after the CRLF in
+ the OK response. After a client issues a STARTTLS command, it MUST
+
+
+
+Melnikov & Martin Standards Track [Page 16]
+
+RFC 5804 ManageSieve July 2010
+
+
+ NOT issue further commands until a server response is seen and the
+ TLS negotiation is complete.
+
+ The STARTTLS command is only valid in non-authenticated state. The
+ server remains in non-authenticated state, even if client credentials
+ are supplied during the TLS negotiation. The SASL [SASL] EXTERNAL
+ mechanism MAY be used to authenticate once TLS client credentials are
+ successfully exchanged, but servers supporting the STARTTLS command
+ are not required to support the EXTERNAL mechanism.
+
+ After the TLS layer is established, the server MUST re-issue the
+ capability results, followed by an OK response. This is necessary to
+ protect against man-in-the-middle attacks that alter the capabilities
+ list prior to STARTTLS. This capability result MUST NOT include the
+ STARTTLS capability.
+
+ The client MUST discard cached capability information and replace it
+ with the new information. The server MAY advertise different
+ capabilities after STARTTLS.
+
+ Example:
+
+ C: StartTls
+ S: oK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "VERSION" "1.0"
+ S: "LANGUAGE" "fr"
+ S: ok
+
+2.2.1. Server Identity Check
+
+ During the TLS negotiation, the ManageSieve client MUST check its
+ understanding of the server hostname/IP address against the server's
+ identity as presented in the server Certificate message, in order to
+ prevent man-in-the-middle attacks. In this section, the client's
+ understanding of the server's identity is called the "reference
+ identity".
+
+ Checking is performed according to the following rules:
+
+ o If the reference identity is a hostname:
+
+ 1. If a subjectAltName extension of the SRVName [X509-SRV],
+ dNSName [X509] (in that order of preference) type is present
+ in the server's certificate, then it SHOULD be used as the
+
+
+
+Melnikov & Martin Standards Track [Page 17]
+
+RFC 5804 ManageSieve July 2010
+
+
+ source of the server's identity. Matching is performed as
+ described in Section 2.2.1.1, with the exception that no
+ wildcard matching is allowed for SRVName type. If the
+ certificate contains multiple names (e.g., more than one
+ dNSName field), then a match with any one of the fields is
+ considered acceptable.
+
+ 2. The client MAY use other types of subjectAltName for
+ performing comparison.
+
+ 3. The server's identity MAY also be verified by comparing the
+ reference identity to the Common Name (CN) [RFC4519] value in
+ the leaf Relative Distinguished Name (RDN) of the subjectName
+ field of the server's certificate. This comparison is
+ performed using the rules for comparison of DNS names in
+ Section 2.2.1.1, below. Although the use of the Common Name
+ value is existing practice, it is deprecated, and
+ Certification Authorities are encouraged to provide
+ subjectAltName values instead. Note that the TLS
+ implementation may represent DNs in certificates according to
+ X.500 or other conventions. For example, some X.500
+ implementations order the RDNs in a DN using a left-to-right
+ (most significant to least significant) convention instead of
+ LDAP's right-to-left convention.
+
+ o When the reference identity is an IP address, the iPAddress
+ subjectAltName SHOULD be used by the client for comparison. The
+ comparison is performed as described in Section 2.2.1.2.
+
+ If the server identity check fails, user-oriented clients SHOULD
+ either notify the user (clients MAY give the user the opportunity to
+ continue with the ManageSieve session in this case) or close the
+ transport connection and indicate that the server's identity is
+ suspect. Automated clients SHOULD return or log an error indicating
+ that the server's identity is suspect and/or SHOULD close the
+ transport connection. Automated clients MAY provide a configuration
+ setting that disables this check, but MUST provide a setting that
+ enables it.
+
+ Beyond the server identity check described in this section, clients
+ should be prepared to do further checking to ensure that the server
+ is authorized to provide the service it is requested to provide. The
+ client may need to make use of local policy information in making
+ this determination.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 18]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.2.1.1. Comparison of DNS Names
+
+ If the reference identity is an internationalized domain name,
+ conforming implementations MUST convert it to the ASCII Compatible
+ Encoding (ACE) format as specified in Section 4 of RFC 3490 [RFC3490]
+ before comparison with subjectAltName values of type dNSName.
+ Specifically, conforming implementations MUST perform the conversion
+ operation specified in Section 4 of [RFC3490] as follows:
+
+ o in step 1, the domain name SHALL be considered a "stored string";
+
+ o in step 3, set the flag called "UseSTD3ASCIIRules";
+
+ o in step 4, process each label with the "ToASCII" operation; and
+
+ o in step 5, change all label separators to U+002E (full stop).
+
+ After performing the "to-ASCII" conversion, the DNS labels and names
+ MUST be compared for equality according to the rules specified in
+ Section 3 of [RFC3490]; i.e., once all label separators are replaced
+ with U+002E (dot) they are compared in the case-insensitive manner.
+
+ The '*' (ASCII 42) wildcard character is allowed in subjectAltName
+ values of type dNSName, and then only as the left-most (least
+ significant) DNS label in that value. This wildcard matches any
+ left-most DNS label in the server name. That is, the subject
+ *.example.com matches the server names a.example.com and
+ b.example.com, but does not match example.com or a.b.example.com.
+
+2.2.1.2. Comparison of IP Addresses
+
+ When the reference identity is an IP address, the identity MUST be
+ converted to the "network byte order" octet string representation
+ [RFC791][RFC2460]. For IP Version 4, as specified in RFC 791, the
+ octet string will contain exactly four octets. For IP Version 6, as
+ specified in RFC 2460, the octet string will contain exactly sixteen
+ octets. This octet string is then compared against subjectAltName
+ values of type iPAddress. A match occurs if the reference identity
+ octet string and value octet strings are identical.
+
+2.2.1.3. Comparison of Other subjectName Types
+
+ Client implementations MAY support matching against subjectAltName
+ values of other types as described in other documents.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 19]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.3. LOGOUT Command
+
+ The client sends the LOGOUT command when it is finished with a
+ connection and wishes to terminate it. The server MUST reply with an
+ OK response. The server MUST ignore commands issued by the client
+ after the LOGOUT command.
+
+ The client SHOULD wait for the OK response before closing the
+ connection. This avoids the TCP connection going into the TIME_WAIT
+ state on the server. In order to avoid going into the TIME_WAIT TCP
+ state, the server MAY wait for a short while for the client to close
+ the TCP connection first. Whether or not the server waits for the
+ client to close the connection, it MUST then close the connection
+ itself.
+
+ Example:
+
+ C: Logout
+ S: Ok
+ <connection is terminated>
+
+2.4. CAPABILITY Command
+
+ The CAPABILITY command requests the server capabilities as described
+ earlier in this document. It has no parameters.
+
+ Example:
+
+ C: CAPABILITY
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN SCRAM-SHA-1 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+
+2.5. HAVESPACE Command
+
+ Arguments: String - name
+ Number - script size
+
+ The HAVESPACE command is used to query the server for available
+ space. Clients specify the name they wish to save the script as and
+ its size in octets. Both parameters can be used by the server to see
+ if the script with the specified name and size is within a user's
+ quota(s). For example, the server MAY use the script name to check
+ if a script would be replaced or a new one would be created. Servers
+ respond with a NO if storing a script with that name and size would
+
+
+
+Melnikov & Martin Standards Track [Page 20]
+
+RFC 5804 ManageSieve July 2010
+
+
+ fail or OK otherwise. Clients SHOULD issue this command before
+ attempting to place a script on the server.
+
+ Note that the OK response from the HAVESPACE command does not
+ constitute a guarantee of success as server disk space conditions
+ could change between the client issuing the HAVESPACE and the client
+ issuing the PUTSCRIPT commands. A QUOTA response code (see
+ Section 1.3) remains a possible (albeit unlikely) response to a
+ subsequent PUTSCRIPT with the same name and size.
+
+ Example:
+
+ C: HAVESPACE "myscript" 999999
+ S: NO (QUOTA/MAXSIZE) "Quota exceeded"
+
+ C: HAVESPACE "foobar" 435
+ S: OK
+
+2.6. PUTSCRIPT Command
+
+ Arguments: String - Script name
+ String - Script content
+
+ The PUTSCRIPT command is used by the client to submit a Sieve script
+ to the server.
+
+ If the script already exists, upon success the old script will be
+ overwritten. The old script MUST NOT be overwritten if PUTSCRIPT
+ fails in any way. A script of zero length SHOULD be disallowed.
+
+ This command places the script on the server. It does not affect
+ whether the script is processed on incoming mail, unless it replaces
+ the script that is already active. The SETACTIVE command is used to
+ mark a script as active.
+
+ When submitting large scripts, clients SHOULD use the HAVESPACE
+ command beforehand to query if the server is willing to accept a
+ script of that size.
+
+ The server MUST check the submitted script for validity, which
+ includes checking that the script complies with the Sieve grammar
+ [SIEVE] and that all Sieve extensions mentioned in the script's
+ "require" statement(s) are supported by the Sieve interpreter. (Note
+ that if the Sieve interpreter supports the Sieve "ihave" extension
+ [I-HAVE], any unrecognized/unsupported extension mentioned in the
+ "ihave" test MUST NOT cause the validation failure.) Other checks
+ such as validating the supplied command arguments for each command
+ MAY be performed. Essentially, the performed validation SHOULD be
+
+
+
+Melnikov & Martin Standards Track [Page 21]
+
+RFC 5804 ManageSieve July 2010
+
+
+ the same as performed when compiling the script for execution.
+ Implementations that use a binary representation to store compiled
+ scripts can extend the validation to a full compilation, in order to
+ avoid validating uploaded scripts multiple times.
+
+ If the script fails the validation, the server MUST reply with a NO
+ response. Any script that fails the validity test MUST NOT be stored
+ on the server. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving the line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming language
+ compilers. Client implementations should note that this may be a
+ multiline literal string with more than one error message separated
+ by CRLFs. The human-readable message is in the language returned in
+ the latest LANGUAGE capability (or in "i-default"; see Section 1.7),
+ encoded in UTF-8 [UTF-8].
+
+ An OK response MAY contain the WARNINGS response code. In such a
+ case the human-readable message that follows the OK response SHOULD
+ contain a specific warning message (or messages) giving the line
+ number(s) in the script that might contain errors not intended by the
+ script writer. The human-readable message is in the language
+ returned in the latest LANGUAGE capability (or in "i-default"; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. A client seeing such a
+ response code SHOULD present the message to the user.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 22]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Examples:
+
+ C: Putscript "foo" {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ C: Putscript "mysievescript" {110+}
+ C: require ["fileinto"];
+ C:
+ C: if envelope :contains "to" "tmartin+sent" {
+ C: fileinto "INBOX.sent";
+ C: }
+ S: OK
+
+ C: Putscript "myforwards" {190+}
+ C: redirect "111@example.net";
+ C:
+ C: if size :under 10k {
+ C: redirect "mobile@cell.example.com";
+ C: }
+ C:
+ C: if envelope :contains "to" "tmartin+lists" {
+ C: redirect "lists@groups.example.com";
+ C: }
+ S: OK (WARNINGS) "line 8: server redirect action
+ limit is 2, this redirect might be ignored"
+
+2.7. LISTSCRIPTS Command
+
+ This command lists the scripts the user has on the server. Upon
+ success, a list of CRLF-separated script names (each represented as a
+ quoted or literal string) is returned followed by an OK response. If
+ there exists an active script, the atom ACTIVE is appended to the
+ corresponding script name. The atom ACTIVE MUST NOT appear on more
+ than one response line.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 23]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Example:
+
+ C: Listscripts
+ S: "summer_script"
+ S: "vacation_script"
+ S: {13}
+ S: clever"script
+ S: "main_script" ACTIVE
+ S: OK
+
+ C: listscripts
+ S: "summer_script"
+ S: "main_script" active
+ S: OK
+
+2.8. SETACTIVE Command
+
+ Arguments: String - script name
+
+ This command sets a script active. If the script name is the empty
+ string (i.e., ""), then any active script is disabled. Disabling an
+ active script when there is no script active is not an error and MUST
+ result in an OK reply.
+
+ If the script does not exist on the server, then the server MUST
+ reply with a NO response. Such a reply SHOULD contain the
+ NONEXISTENT response code.
+
+ Examples:
+
+ C: Setactive "vacationscript"
+ S: Ok
+
+ C: Setactive ""
+ S: Ok
+
+ C: Setactive "baz"
+ S: No (NONEXISTENT) "There is no script by that name"
+
+ C: Setactive "baz"
+ S: No (NONEXISTENT) {31}
+ S: There is no script by that name
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 24]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.9. GETSCRIPT Command
+
+ Arguments: String - script name
+
+ This command gets the contents of the specified script. If the
+ script does not exist, the server MUST reply with a NO response.
+ Such a reply SHOULD contain the NONEXISTENT response code.
+
+ Upon success, a string with the contents of the script is returned
+ followed by an OK response.
+
+ Example:
+
+ C: Getscript "myscript"
+ S: {54}
+ S: #this is my wonderful script
+ S: reject "I reject all";
+ S:
+ S: OK
+
+2.10. DELETESCRIPT Command
+
+ Arguments: String - script name
+
+ This command is used to delete a user's Sieve script. Servers MUST
+ reply with a NO response if the script does not exist. Such
+ responses SHOULD include the NONEXISTENT response code.
+
+ The server MUST NOT allow the client to delete an active script, so
+ the server MUST reply with a NO response if attempted. Such a
+ response SHOULD contain the ACTIVE response code. If a client wishes
+ to delete an active script, it should use the SETACTIVE command to
+ disable the script first.
+
+ Example:
+
+ C: Deletescript "foo"
+ S: Ok
+
+ C: Deletescript "baz"
+ S: No (ACTIVE) "You may not delete an active script"
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 25]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.11. RENAMESCRIPT Command
+
+ Arguments: String - Old Script name
+ String - New Script name
+
+ This command is used to rename a user's Sieve script. Servers MUST
+ reply with a NO response if the old script does not exist (in which
+ case the NONEXISTENT response code SHOULD be included), or a script
+ with the new name already exists (in which case the ALREADYEXISTS
+ response code SHOULD be included). Renaming the active script is
+ allowed; the renamed script remains active.
+
+ Example:
+
+ C: Renamescript "foo" "bar"
+ S: Ok
+
+ C: Renamescript "baz" "bar"
+ S: No "bar already exists"
+
+ If the server doesn't support the RENAMESCRIPT command, the client
+ can emulate it by performing the following steps:
+
+ 1. List available scripts with LISTSCRIPTS. If the script with the
+ new script name exists, then the client should ask the user
+ whether to abort the operation, to replace the script (by issuing
+ the DELETESCRIPT <newname> after that), or to choose a different
+ name.
+
+ 2. Download the old script with GETSCRIPT <oldname>.
+
+ 3. Upload the old script with the new name: PUTSCRIPT <newname>.
+
+ 4. If the old script was active (as reported by LISTSCRIPTS in step
+ 1), then make the new script active: SETACTIVE <newname>.
+
+ 5. Delete the old script: DELETESCRIPT <oldname>.
+
+ Note that these steps don't describe how to handle various other
+ error conditions (for example, NO response containing QUOTA response
+ code in step 3). Error handling is left as an exercise for the
+ reader.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 26]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.12. CHECKSCRIPT Command
+
+ Arguments: String - Script content
+
+ The CHECKSCRIPT command is used by the client to verify Sieve script
+ validity without storing the script on the server.
+
+ The server MUST check the submitted script for syntactic validity,
+ which includes checking that all Sieve extensions mentioned in Sieve
+ script "require" statement(s) are supported by the Sieve interpreter.
+ (Note that if the Sieve interpreter supports the Sieve "ihave"
+ extension [I-HAVE], any unrecognized/unsupported extension mentioned
+ in the "ihave" test MUST NOT cause the syntactic validation failure.)
+ If the script fails this test, the server MUST reply with a NO
+ response. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving the line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming language
+ compilers. Client implementations should note that this may be a
+ multiline literal string with more than one error message separated
+ by CRLFs. The human-readable message is in the language returned in
+ the latest LANGUAGE capability (or in "i-default"; see Section 1.7),
+ encoded in UTF-8 [UTF-8].
+
+ Examples:
+
+ C: CheckScript {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ A ManageSieve server supporting this command MUST NOT check if the
+ script will put the current user over its quota limit.
+
+ An OK response MAY contain the WARNINGS response code. In such a
+ case, the human-readable message that follows the OK response SHOULD
+ contain a specific warning message (or messages) giving the line
+ number(s) in the script that might contain errors not intended by the
+ script writer. The human-readable message is in the language
+ returned in the latest LANGUAGE capability (or in "i-default"; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. A client seeing such a
+ response code SHOULD present the message to the user.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 27]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.13. NOOP Command
+
+ Arguments: String - tag to echo back (optional)
+
+ The NOOP command does nothing, beyond returning a response to the
+ client. It may be used by clients for protocol re-synchronization or
+ to reset any inactivity auto-logout timer on the server.
+
+ The response to the NOOP command is always OK, followed by the TAG
+ response code together with the supplied string. If no string was
+ supplied in the NOOP command, the TAG response code MUST NOT be
+ included.
+
+ Examples:
+
+ C: NOOP
+ S: OK "NOOP completed"
+
+ C: NOOP "STARTTLS-SYNC-42"
+ S: OK (TAG {16}
+ S: STARTTLS-SYNC-42) "Done"
+
+2.14. Recommended Extensions
+
+ The UNAUTHENTICATE extension (advertised as the "UNAUTHENTICATE"
+ capability with no parameters) defines a new UNAUTHENTICATE command,
+ which allows a client to return the server to non-authenticated
+ state. Support for this extension is RECOMMENDED.
+
+2.14.1. UNAUTHENTICATE Command
+
+ The UNAUTHENTICATE command returns the server to the
+ non-authenticated state. It doesn't affect any previously
+ established TLS [TLS] or SASL (Section 2.1) security layer.
+
+ The UNAUTHENTICATE command is only valid in authenticated state. If
+ issued in a wrong state, the server MUST reject it with a NO
+ response.
+
+ The UNAUTHENTICATE command has no parameters.
+
+ When issued in the authenticated state, the UNAUTHENTICATE command
+ MUST NOT fail (i.e., it must never return anything other than OK or
+ BYE).
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 28]
+
+RFC 5804 ManageSieve July 2010
+
+
+3. Sieve URL Scheme
+
+ URI scheme name: sieve
+
+ Status: permanent
+
+ URI scheme syntax: Described using ABNF [ABNF]. Some ABNF
+ productions not defined below are from [URI-GEN].
+
+ sieveurl = sieveurl-server / sieveurl-list-scripts /
+ sieveurl-script
+
+ sieveurl-server = "sieve://" authority
+
+ sieveurl-list-scripts = "sieve://" authority ["/"]
+
+ sieveurl-script = "sieve://" authority "/"
+ [owner "/"] scriptname
+
+ authority = <defined in [URI-GEN]>
+
+ owner = *ochar
+ ;; %-encoded version of [SASL] authorization
+ ;; identity (script owner) or "userid".
+ ;;
+ ;; Empty owner is used to reference
+ ;; global scripts.
+ ;;
+ ;; Note that ASCII characters such as " ", ";",
+ ;; "&", "=", "/" and "?" must be %-encoded
+ ;; as per rule specified in [URI-GEN].
+
+ scriptname = 1*ochar
+ ;; %-encoded version of UTF-8 representation
+ ;; of the script name.
+ ;; Note that ASCII characters such as " ", ";",
+ ;; "&", "=", "/" and "?" must be %-encoded
+ ;; as per rule specified in [URI-GEN].
+
+ ochar = unreserved / pct-encoded / sub-delims-sh /
+ ":" / "@"
+ ;; Same as [URI-GEN] 'pchar',
+ ;; but without ";", "&" and "=".
+
+ unreserved = <defined in [URI-GEN]>
+
+ pct-encoded = <defined in [URI-GEN]>
+
+
+
+
+Melnikov & Martin Standards Track [Page 29]
+
+RFC 5804 ManageSieve July 2010
+
+
+ sub-delims-sh = "!" / "$" / "'" / "(" / ")" /
+ "*" / "+" / ","
+ ;; Same as [URI-GEN] sub-delims,
+ ;; but without ";", "&" and "=".
+
+ URI scheme semantics:
+
+ A Sieve URL identifies a Sieve server or a Sieve script on a Sieve
+ server. The latter form is associated with the application/sieve
+ MIME type defined in [SIEVE]. There is no MIME type associated
+ with the former form of Sieve URI.
+
+ The server form is used in the REFERRAL response code (see Section
+ 1.3) in order to designate another server where the client should
+ perform its operations.
+
+ The script form allows to retrieve (GETSCRIPT), update
+ (PUTSCRIPT), delete (DELETESCRIPT), or activate (SETACTIVE) the
+ named script; however, the most typical action would be to
+ retrieve the script. If the script name is empty (omitted), the
+ URI requests that the client lists available scripts using the
+ LISTSCRIPTS command.
+
+ Encoding considerations:
+
+ The script name and/or the owner, if present, is in UTF-8. Non--
+ US-ASCII UTF-8 octets MUST be percent-encoded as described in
+ [URI-GEN]. US-ASCII characters such as " " (space), ";", "&",
+ "=", "/" and "?" MUST be %-encoded as described in [URI-GEN].
+ Note that "&" and "?" are in this list in order to allow for
+ future extensions.
+
+ Note that the empty owner (e.g., sieve://example.com//script) is
+ different from the missing owner (e.g.,
+ sieve://example.com/script) and is reserved for referencing global
+ scripts.
+
+ The user name (in the "authority" part), if present, is in UTF-8.
+ Non-US-ASCII UTF-8 octets MUST be percent-encoded as described in
+ [URI-GEN].
+
+ Applications/protocols that use this URI scheme name:
+ ManageSieve [RFC5804] clients and servers. Clients that can store
+ user preferences in protocols such as [LDAP] or [ACAP].
+
+ Interoperability considerations: None.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 30]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Security considerations:
+ The <scriptname> part of a ManageSieve URL might potentially disclose
+ some confidential information about the author of the script or,
+ depending on a ManageSieve implementation, about configuration of the
+ mail system. The latter might be used to prepare for a more complex
+ attack on the mail system.
+
+ Clients resolving ManageSieve URLs that wish to achieve data
+ confidentiality and/or integrity SHOULD use the STARTTLS command (if
+ supported by the server) before starting authentication, or use a
+ SASL mechanism, such as GSSAPI, that provides a confidentiality
+ security layer.
+
+ Contact: Alexey Melnikov <alexey.melnikov@isode.com>
+
+ Author/Change controller: IESG.
+
+ References: This document and RFC 5228 [SIEVE].
+
+4. Formal Syntax
+
+ The following syntax specification uses the Augmented Backus-Naur
+ Form (BNF) notation as specified in [ABNF]. This uses the ABNF core
+ rules as specified in Appendix A of the ABNF specification [ABNF].
+ "UTF8-2", "UTF8-3", and "UTF8-4" non-terminal are defined in [UTF-8].
+
+ Except as noted otherwise, all alphabetic characters are case-
+ insensitive. The use of upper- or lowercase characters to define
+ token strings is for editorial clarity only. Implementations MUST
+ accept these strings in a case-insensitive fashion.
+
+ SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-21 / %x23-5B /
+ %x5D-7F
+ ;; any TEXT-CHAR except QUOTED-SPECIALS
+
+ QUOTED-CHAR = SAFE-UTF8-CHAR / "\" QUOTED-SPECIALS
+
+ QUOTED-SPECIALS = DQUOTE / "\"
+
+ SAFE-UTF8-CHAR = SAFE-CHAR / UTF8-2 / UTF8-3 / UTF8-4
+ ;; <UTF8-2>, <UTF8-3>, and <UTF8-4>
+ ;; are defined in [UTF-8].
+
+ ATOM-CHAR = "!" / %x23-27 / %x2A-5B / %x5D-7A / %x7C-7E
+ ;; Any CHAR except ATOM-SPECIALS
+
+ ATOM-SPECIALS = "(" / ")" / "{" / SP / CTL / QUOTED-SPECIALS
+
+
+
+
+Melnikov & Martin Standards Track [Page 31]
+
+RFC 5804 ManageSieve July 2010
+
+
+ NZDIGIT = %x31-39
+ ;; 1-9
+
+ atom = 1*1024ATOM-CHAR
+
+ iana-token = atom
+ ;; MUST be registered with IANA
+
+ auth-type = DQUOTE auth-type-name DQUOTE
+
+ auth-type-name = iana-token
+ ;; as defined in SASL [SASL]
+
+ command = (command-any / command-auth /
+ command-nonauth) CRLF
+ ;; Modal based on state
+
+ command-any = command-capability / command-logout /
+ command-noop
+ ;; Valid in all states
+
+ command-auth = command-getscript / command-setactive /
+ command-listscripts / command-deletescript /
+ command-putscript / command-checkscript /
+ command-havespace /
+ command-renamescript /
+ command-unauthenticate
+ ;; Valid only in Authenticated state
+
+ command-nonauth = command-authenticate / command-starttls
+ ;; Valid only when in Non-Authenticated
+ ;; state
+
+ command-authenticate = "AUTHENTICATE" SP auth-type [SP string]
+ *(CRLF string)
+
+ command-capability = "CAPABILITY"
+
+ command-deletescript = "DELETESCRIPT" SP sieve-name
+
+ command-getscript = "GETSCRIPT" SP sieve-name
+
+ command-havespace = "HAVESPACE" SP sieve-name SP number
+
+ command-listscripts = "LISTSCRIPTS"
+
+ command-noop = "NOOP" [SP string]
+
+
+
+
+Melnikov & Martin Standards Track [Page 32]
+
+RFC 5804 ManageSieve July 2010
+
+
+ command-logout = "LOGOUT"
+
+ command-putscript = "PUTSCRIPT" SP sieve-name SP sieve-script
+
+ command-checkscript = "CHECKSCRIPT" SP sieve-script
+
+ sieve-script = string
+
+ command-renamescript = "RENAMESCRIPT" SP old-sieve-name SP
+ new-sieve-name
+
+ old-sieve-name = sieve-name
+
+ new-sieve-name = sieve-name
+
+ command-setactive = "SETACTIVE" SP active-sieve-name
+
+ command-starttls = "STARTTLS"
+
+ command-unauthenticate= "UNAUTHENTICATE"
+
+ extend-token = atom
+ ;; MUST be defined by a Standards Track or
+ ;; IESG-approved experimental protocol
+ ;; extension
+
+ extension-data = extension-item *(SP extension-item)
+
+ extension-item = extend-token / string / number /
+ "(" [extension-data] ")"
+
+ literal-c2s = "{" number "+}" CRLF *OCTET
+ ;; The number represents the number of
+ ;; octets.
+ ;; This type of literal can only be sent
+ ;; from the client to the server.
+
+ literal-s2c = "{" number "}" CRLF *OCTET
+ ;; Almost identical to literal-c2s,
+ ;; but with no '+' character.
+ ;; The number represents the number of
+ ;; octets.
+ ;; This type of literal can only be sent
+ ;; from the server to the client.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 33]
+
+RFC 5804 ManageSieve July 2010
+
+
+ number = (NZDIGIT *DIGIT) / "0"
+ ;; A 32-bit unsigned number
+ ;; with no extra leading zeros.
+ ;; (0 <= n < 4,294,967,296)
+
+ number-str = string
+ ;; <number> encoded as a <string>.
+
+ quoted = DQUOTE *1024QUOTED-CHAR DQUOTE
+ ;; limited to 1024 octets between the <">s
+
+ resp-code = "AUTH-TOO-WEAK" / "ENCRYPT-NEEDED" / "QUOTA"
+ ["/" ("MAXSCRIPTS" / "MAXSIZE")] /
+ resp-code-sasl /
+ resp-code-referral /
+ "TRANSITION-NEEDED" / "TRYLATER" /
+ "ACTIVE" / "NONEXISTENT" /
+ "ALREADYEXISTS" / "WARNINGS" /
+ "TAG" SP string /
+ resp-code-ext
+
+ resp-code-referral = "REFERRAL" SP sieveurl
+
+ resp-code-sasl = "SASL" SP string
+
+ resp-code-name = iana-token
+ ;; The response code name is hierarchical,
+ ;; separated by '/'.
+ ;; The response code name MUST NOT start
+ ;; with '/'.
+
+ resp-code-ext = resp-code-name [SP extension-data]
+ ;; unknown response codes MUST be tolerated
+ ;; by the client.
+
+ response = response-authenticate /
+ response-logout /
+ response-getscript /
+ response-setactive /
+ response-listscripts /
+ response-deletescript /
+ response-putscript /
+ response-checkscript /
+ response-capability /
+ response-havespace /
+ response-starttls /
+ response-renamescript /
+ response-noop /
+
+
+
+Melnikov & Martin Standards Track [Page 34]
+
+RFC 5804 ManageSieve July 2010
+
+
+ response-unauthenticate
+
+ response-authenticate = *(string CRLF)
+ ((response-ok [response-capability]) /
+ response-nobye)
+ ;; <response-capability> is REQUIRED if a
+ ;; SASL security layer was negotiated and
+ ;; MUST be omitted otherwise.
+
+ response-capability = *(single-capability) response-oknobye
+
+ single-capability = capability-name [SP string] CRLF
+
+ capability-name = string
+
+ ;; Note that literal-s2c is allowed.
+
+ initial-capabilities = DQUOTE "IMPLEMENTATION" DQUOTE SP string /
+ DQUOTE "SASL" DQUOTE SP sasl-mechs /
+ DQUOTE "SIEVE" DQUOTE SP sieve-extensions /
+ DQUOTE "MAXREDIRECTS" DQUOTE SP number-str /
+ DQUOTE "NOTIFY" DQUOTE SP notify-mechs /
+ DQUOTE "STARTTLS" DQUOTE /
+ DQUOTE "LANGUAGE" DQUOTE SP language /
+ DQUOTE "VERSION" DQUOTE SP version /
+ DQUOTE "OWNER" DQUOTE SP string
+ ;; Each capability conforms to
+ ;; the syntax for single-capability.
+ ;; Also, note that the capability name
+ ;; can be returned as either literal-s2c
+ ;; or quoted, even though only "quoted"
+ ;; string is shown above.
+
+ version = ( DQUOTE "1.0" DQUOTE ) / version-ext
+
+ version-ext = DQUOTE ver-major "." ver-minor DQUOTE
+ ; Future versions specified in updates
+ ; to this document. An increment to
+ ; the ver-major means a backward-incompatible
+ ; change to the protocol, e.g., "3.5" (ver-major "3")
+ ; is not backward-compatible with any "2.X" version.
+ ; Any version "Z.W" MUST be backward compatible
+ ; with any version "Z.Q", where Q < W.
+ ; For example, version "2.4" is backward compatible
+ ; with version "2.0", "2.1", "2.2", and "2.3".
+
+ ver-major = number
+
+
+
+
+Melnikov & Martin Standards Track [Page 35]
+
+RFC 5804 ManageSieve July 2010
+
+
+ ver-minor = number
+
+ sasl-mechs = string
+ ; Space-separated list of SASL mechanisms,
+ ; each SASL mechanism name complies with rules
+ ; specified in [SASL].
+ ; Can be empty.
+
+ sieve-extensions = string
+ ; Space-separated list of supported SIEVE extensions.
+ ; Can be empty.
+
+ language = string
+ ; Contains <Language-Tag> from [RFC5646].
+
+
+ notify-mechs = string
+ ; Space-separated list of URI schema parts
+ ; for supported notification [NOTIFY] methods.
+ ; MUST NOT be empty.
+
+ response-deletescript = response-oknobye
+
+ response-getscript = (sieve-script CRLF response-ok) /
+ response-nobye
+
+ response-havespace = response-oknobye
+
+ response-listscripts = *(sieve-name [SP "ACTIVE"] CRLF)
+ response-oknobye
+ ;; ACTIVE may only occur with one sieve-name
+
+ response-logout = response-oknobye
+
+ response-unauthenticate= response-oknobye
+ ;; "NO" response can only be returned when
+ ;; the command is issued in a wrong state
+ ;; or has a wrong number of parameters
+
+ response-ok = "OK" [SP "(" resp-code ")"]
+ [SP string] CRLF
+ ;; The string contains human-readable text
+ ;; encoded as UTF-8.
+
+ response-nobye = ("NO" / "BYE") [SP "(" resp-code ")"]
+ [SP string] CRLF
+ ;; The string contains human-readable text
+ ;; encoded as UTF-8.
+
+
+
+Melnikov & Martin Standards Track [Page 36]
+
+RFC 5804 ManageSieve July 2010
+
+
+ response-oknobye = response-ok / response-nobye
+
+ response-noop = response-ok
+
+ response-putscript = response-oknobye
+
+ response-checkscript = response-oknobye
+
+ response-renamescript = response-oknobye
+
+ response-setactive = response-oknobye
+
+ response-starttls = (response-ok response-capability) /
+ response-nobye
+
+ sieve-name = string
+ ;; See Section 1.6 for the full list of
+ ;; prohibited characters.
+ ;; Empty string is not allowed.
+
+ active-sieve-name = string
+ ;; See Section 1.6 for the full list of
+ ;; prohibited characters.
+ ;; This is similar to <sieve-name>, but
+ ;; empty string is allowed and has a special
+ ;; meaning.
+
+ string = quoted / literal-c2s / literal-s2c
+ ;; literal-c2s is only allowed when sent
+ ;; from the client to the server.
+ ;; literal-s2c is only allowed when sent
+ ;; from the server to the client.
+ ;; quoted is allowed in either direction.
+
+5. Security Considerations
+
+ The AUTHENTICATE command uses SASL [SASL] to provide authentication
+ and authorization services. Integrity and privacy services can be
+ provided by [SASL] and/or [TLS]. When a SASL mechanism is used, the
+ security considerations for that mechanism apply.
+
+ This protocol's transactions are susceptible to passive observers or
+ man-in-the-middle attacks that alter the data, unless the optional
+ encryption and integrity services of the SASL (via the AUTHENTICATE
+ command) and/or [TLS] (via the STARTTLS command) are enabled, or an
+ external security mechanism is used for protection. It may be useful
+ to allow configuration of both clients and servers to refuse to
+ transfer sensitive information in the absence of strong encryption.
+
+
+
+Melnikov & Martin Standards Track [Page 37]
+
+RFC 5804 ManageSieve July 2010
+
+
+ If an implementation supports SASL mechanisms that are vulnerable to
+ passive eavesdropping attacks (such as [PLAIN]), then the
+ implementation MUST support at least one configuration where these
+ SASL mechanisms are not advertised or used without the presence of an
+ external security layer such as [TLS].
+
+ Some response codes returned on failed AUTHENTICATE command may
+ disclose whether or not the username is valid (e.g., TRANSITION-
+ NEEDED), so server implementations SHOULD provide the ability to
+ disable these features (or make them not conditional on a per-user
+ basis) for sites concerned about such disclosure. In the case of
+ ENCRYPT-NEEDED, if it is applied to all identities then no extra
+ information is disclosed, but if it is applied on a per-user basis it
+ can disclose information.
+
+ A compromised or malicious server can use the TRANSITION-NEEDED
+ response code to force the client that is configured to use a
+ mechanism that does not disclose the user's password to the server
+ (e.g., Kerberos), to send the bare password to the server. Clients
+ SHOULD have the ability to disable the password transition feature,
+ or disclose that risk to the user and offer the user an option of how
+ to proceed.
+
+6. IANA Considerations
+
+ IANA has reserved TCP port number 4190 for use with the ManageSieve
+ protocol described in this document.
+
+ IANA has registered the "sieve" URI scheme defined in Section 3 of
+ this document.
+
+ IANA has registered "sieve" in the "GSSAPI/Kerberos/SASL Service
+ Names" registry.
+
+ IANA has created a new registry for ManageSieve capabilities. The
+ registration template for ManageSieve capabilities is specified in
+ Section 6.1. ManageSieve protocol capabilities MUST be specified in
+ a Standards-Track or IESG-approved Experimental RFC.
+
+ IANA has created a new registry for ManageSieve response codes. The
+ registration template for ManageSieve response codes is specified in
+ Section 6.3. ManageSieve protocol response codes MUST be specified
+ in a Standards-Track or IESG-approved Experimental RFC.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 38]
+
+RFC 5804 ManageSieve July 2010
+
+
+6.1. ManageSieve Capability Registration Template
+
+ To: iana@iana.org
+ Subject: ManageSieve Capability Registration
+
+ Please register the following ManageSieve capability:
+
+ Capability name:
+ Description:
+ Relevant publications:
+ Person & email address to contact for further information:
+ Author/Change controller:
+
+6.2. Registration of Initial ManageSieve Capabilities
+
+ To: iana@iana.org
+ Subject: ManageSieve Capability Registration
+
+ Please register the following ManageSieve capabilities:
+
+ Capability name: IMPLEMENTATION
+ Description: Its value contains the name of the server
+ implementation and its version.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: SASL
+ Description: Its value contains a space-separated list of SASL
+ mechanisms supported by the server.
+ Relevant publications: this RFC, Sections 1.7 and 2.1.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: SIEVE
+ Description: Its value contains a space-separated list of supported
+ SIEVE extensions.
+ Relevant publications: this RFC, Section 1.7. Also [SIEVE].
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 39]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Capability name: STARTTLS
+ Description: This capability is returned if the server supports TLS
+ (STARTTLS command).
+ Relevant publications: this RFC, Sections 1.7 and 2.2.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: NOTIFY
+ Description: This capability is returned if the server supports the
+ 'enotify' [NOTIFY] Sieve extension.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: MAXREDIRECTS
+ Description: This capability returns the limit on the number of
+ Sieve "redirect" actions a script can perform during a
+ single evaluation. The value is a non-negative number
+ represented as a ManageSieve string.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: LANGUAGE
+ Description: The language (<Language-Tag> from [RFC5646]) currently
+ used for human-readable error messages.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: OWNER
+ Description: Its value contains the UTF-8-encoded name of the
+ currently logged-in user ("authorization identity"
+ according to RFC 4422).
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 40]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Capability name: VERSION
+ Description: This capability is returned if the server is compliant
+ with RFC 5804; i.e., that it supports RENAMESCRIPT,
+ CHECKSCRIPT, and NOOP commands.
+ Relevant publications: this RFC, Sections 2.11, 2.12, and 2.13.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+6.3. ManageSieve Response Code Registration Template
+
+ To: iana@iana.org
+ Subject: ManageSieve Response Code Registration
+
+ Please register the following ManageSieve response code:
+
+ Response Code:
+ Arguments (use ABNF to specify syntax, or the word NONE if none
+ can be specified):
+ Purpose:
+ Published Specification(s):
+ Person & email address to contact for further information:
+ Author/Change controller:
+
+6.4. Registration of Initial ManageSieve Response Codes
+
+ To: iana@iana.org
+ Subject: ManageSieve Response Code Registration
+
+ Please register the following ManageSieve response codes:
+
+ Response Code: AUTH-TOO-WEAK
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code is returned in the NO response from
+ an AUTHENTICATE command. It indicates that site
+ security policy forbids the use of the requested
+ mechanism for the specified authentication identity.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 41]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: ENCRYPT-NEEDED
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code is returned in the NO response from
+ an AUTHENTICATE command. It indicates that site
+ security policy requires the use of a strong
+ encryption mechanism for the specified authentication
+ identity and mechanism.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: QUOTA
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined quota constraints. If
+ this response code is returned in the OK response, it
+ can mean that the user is near its quota or that the
+ user exceeded its quota, but the server supports soft
+ quotas.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: QUOTA/MAXSCRIPTS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined limit on the number of
+ Sieve scripts. If this response code is returned in
+ the OK response, it can mean that the user is near its
+ quota or that the user exceeded its quota, but the
+ server supports soft quotas. This response code is a
+ more specific version of the QUOTA response code.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 42]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: QUOTA/MAXSIZE
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined maximum script size.
+ If this response code is returned in the OK response,
+ it can mean that the user is near its quota or that
+ the user exceeded its quota, but the server supports
+ soft quotas. This response code is a more specific
+ version of the QUOTA response code.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: REFERRAL
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): <sieveurl>
+ Purpose: This response code may be returned with a BYE result
+ from any command, and includes a mandatory parameter
+ that indicates what server to access to manage this
+ user's Sieve scripts. The server will be specified by
+ a Sieve URL (see Section 3). The scriptname portion
+ of the URL MUST NOT be specified. The client should
+ authenticate to the specified server and use it for
+ all further commands in the current session.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: SASL
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): <string>
+ Purpose: This response code can occur in the OK response to a
+ successful AUTHENTICATE command and includes the
+ optional final server response data from the server as
+ specified by [SASL].
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 43]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: TRANSITION-NEEDED
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code occurs in a NO response of an
+ AUTHENTICATE command. It indicates that the user name
+ is valid, but the entry in the authentication database
+ needs to be updated in order to permit authentication
+ with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, followed
+ by authenticating once using the [PLAIN]
+ authentication mechanism. The selected mechanism
+ SHOULD then work for authentications in subsequent
+ sessions.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: TRYLATER
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed due to a temporary server failure.
+ The client MAY continue using local information and
+ try the command later. This response code only make
+ sense when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: ACTIVE
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because it is not allowed on the
+ active script, for example, DELETESCRIPT on the active
+ script. This response code only makes sense when
+ returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 44]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: NONEXISTENT
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because the referenced script name
+ doesn't exist. This response code only makes sense
+ when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: ALREADYEXISTS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because the referenced script name
+ already exists. This response code only makes sense
+ when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: WARNINGS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code MAY be returned by the server in
+ the OK response (but it might be returned with the NO/
+ BYE response as well) and signals the client that even
+ though the script is syntactically valid, it might
+ contain errors not intended by the script writer.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: TAG
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): string
+ Purpose: This response code name is followed by a string
+ specified in the command that caused this response.
+ It is typically used for client state synchronization.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov@isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 45]
+
+RFC 5804 ManageSieve July 2010
+
+
+7. Internationalization Considerations
+
+ The LANGUAGE capability (see Section 1.7) allows a client to discover
+ the current language used in all human-readable responses that might
+ be returned at the end of any OK/NO/BYE response. Human-readable
+ text in OK responses typically doesn't need to be shown to the user,
+ unless it is returned in response to a PUTSCRIPT or CHECKSCRIPT
+ command that also contains the WARNINGS response code (Section 1.3).
+ Human-readable text from NO/BYE responses is intended be shown to the
+ user, unless the client can automatically handle failure of the
+ command that caused such a response. Clients SHOULD use response
+ codes (Section 1.3) for automatic error handling. Response codes MAY
+ also be used by the client to present error messages in a language
+ understood by the user, for example, if the LANGUAGE capability
+ doesn't return a language understood by the user.
+
+ Note that the human-readable text from OK (WARNINGS) or NO/BYE
+ responses for PUTSCRIPT/CHECKSCRIPT commands is intended for advanced
+ users that understand Sieve language. Such advanced users are often
+ sophisticated enough to be able to handle whatever language the
+ server is using, even if it is not their preferred language, and will
+ want to see error/warning text no matter what language the server
+ puts it in.
+
+ A client that generates Sieve script automatically, for example, if
+ the script is generated without user intervention or from a UI that
+ presents an abstract list of conditions and corresponding actions,
+ SHOULD NOT present warning/error messages to the user, because the
+ user might not even be aware that the client is using Sieve
+ underneath. However, if the client has a debugging mode, such
+ warnings/errors SHOULD be available in the debugging mode.
+
+ Note that this document doesn't provide a way to modify the currently
+ used language. It is expected that a future extension will address
+ that.
+
+8. Acknowledgements
+
+ Thanks to Simon Josefsson, Larry Greenfield, Allen Johnson, Chris
+ Newman, Lyndon Nerenberg, Tim Showalter, Sarah Robeson, Walter Wong,
+ Barry Leiba, Arnt Gulbrandsen, Stephan Bosch, Ken Murchison, Phil
+ Pennock, Ned Freed, Jeffrey Hutzelman, Mark E. Mallett, Dilyan
+ Palauzov, Dave Cridland, Aaron Stone, Robert Burrell Donkin, Patrick
+ Ben Koetter, Bjoern Hoehrmann, Martin Duerst, Pasi Eronen, Magnus
+ Westerlund, Tim Polk, and Julien Coloos for help with this document.
+ Special thank you to Phil Pennock for providing text for the NOOP
+ command, as well as finding various bugs in the document.
+
+
+
+
+Melnikov & Martin Standards Track [Page 46]
+
+RFC 5804 ManageSieve July 2010
+
+
+9. References
+
+9.1. Normative References
+
+ [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", STD 68, RFC 5234, January 2008.
+
+ [ACAP] Newman, C. and J. Myers, "ACAP -- Application
+ Configuration Access Protocol", RFC 2244, November
+ 1997.
+
+ [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data
+ Encodings", RFC 4648, October 2006.
+
+ [DNS-SRV] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR
+ for specifying the location of services (DNS SRV)",
+ RFC 2782, February 2000.
+
+ [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [NET-UNICODE] Klensin, J. and M. Padlipsky, "Unicode Format for
+ Network Interchange", RFC 5198, March 2008.
+
+ [NOTIFY] Melnikov, A., Leiba, B., Segmuller, W., and T. Martin,
+ "Sieve Email Filtering: Extension for Notifications",
+ RFC 5435, January 2009.
+
+ [RFC2277] Alvestrand, H., "IETF Policy on Character Sets and
+ Languages", BCP 18, RFC 2277, January 1998.
+
+ [RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version
+ 6 (IPv6) Specification", RFC 2460, December 1998.
+
+ [RFC3490] Faltstrom, P., Hoffman, P., and A. Costello,
+ "Internationalizing Domain Names in Applications
+ (IDNA)", RFC 3490, March 2003.
+
+ [RFC4519] Sciberras, A., "Lightweight Directory Access Protocol
+ (LDAP): Schema for User Applications", RFC 4519, June
+ 2006.
+
+ [RFC5646] Phillips, A. and M. Davis, "Tags for Identifying
+ Languages", BCP 47, RFC 5646, September 2009.
+
+ [RFC791] Postel, J., "Internet Protocol", STD 5, RFC 791,
+ September 1981.
+
+
+
+
+Melnikov & Martin Standards Track [Page 47]
+
+RFC 5804 ManageSieve July 2010
+
+
+ [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication
+ and Security Layer (SASL)", RFC 4422, June 2006.
+
+ [SASLprep] Zeilenga, K., "SASLprep: Stringprep Profile for User
+ Names and Passwords", RFC 4013, February 2005.
+
+ [SCRAM] Menon-Sen, A., Melnikov, A., Newman, C., and N.
+ Williams, "Salted Challenge Response Authentication
+ Mechanism (SCRAM) SASL and GSS-API Mechanisms", RFC
+ 5802, July 2010.
+
+ [SIEVE] Guenther, P. and T. Showalter, "Sieve: An Email
+ Filtering Language", RFC 5228, January 2008.
+
+ [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of
+ Internationalized Strings ("stringprep")", RFC 3454,
+ December 2002.
+
+ [TLS] Dierks, T. and E. Rescorla, "The Transport Layer
+ Security (TLS) Protocol Version 1.2", RFC 5246, August
+ 2008.
+
+ [URI-GEN] Berners-Lee, T., Fielding, R., and L. Masinter,
+ "Uniform Resource Identifier (URI): Generic Syntax",
+ STD 66, RFC 3986, January 2005.
+
+ [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO
+ 10646", STD 63, RFC 3629, November 2003.
+
+ [X509] Cooper, D., Santesson, S., Farrell, S., Boeyen, S.,
+ Housley, R., and W. Polk, "Internet X.509 Public Key
+ Infrastructure Certificate and Certificate Revocation
+ List (CRL) Profile", RFC 5280, May 2008.
+
+ [X509-SRV] Santesson, S., "Internet X.509 Public Key
+ Infrastructure Subject Alternative Name for Expression
+ of Service Name", RFC 4985, August 2007.
+
+9.2. Informative References
+
+ [DIGEST-MD5] Leach, P. and C. Newman, "Using Digest Authentication
+ as a SASL Mechanism", RFC 2831, May 2000.
+
+ [GSSAPI] Melnikov, A., "The Kerberos V5 ("GSSAPI") Simple
+ Authentication and Security Layer (SASL) Mechanism",
+ RFC 4752, November 2006.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 48]
+
+RFC 5804 ManageSieve July 2010
+
+
+ [I-HAVE] Freed, N., "Sieve Email Filtering: Ihave Extension",
+ RFC 5463, March 2009.
+
+ [IMAP] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL -
+ VERSION 4rev1", RFC 3501, March 2003.
+
+ [LDAP] Zeilenga, K., "Lightweight Directory Access Protocol
+ (LDAP): Technical Specification Road Map", RFC 4510,
+ June 2006.
+
+ [PLAIN] Zeilenga, K., "The PLAIN Simple Authentication and
+ Security Layer (SASL) Mechanism", RFC 4616, August
+ 2006.
+
+Authors' Addresses
+
+ Alexey Melnikov (editor)
+ Isode Limited
+ 5 Castle Business Village
+ 36 Station Road
+ Hampton, Middlesex TW12 2BX
+ UK
+
+ EMail: Alexey.Melnikov@isode.com
+
+
+ Tim Martin
+ BeThereBeSquare, Inc.
+ 672 Haight st.
+ San Francisco, CA 94117
+ USA
+
+ Phone: +1 510 260-4175
+ EMail: timmartin@alumni.cmu.edu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 49]
+
diff --git a/src/common/libManageSieve/doc/SASL CRAM/rfc2195.txt b/src/common/libManageSieve/doc/SASL CRAM/rfc2195.txt
new file mode 100644
index 00000000..4a2725bf
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL CRAM/rfc2195.txt
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+Network Working Group J. Klensin
+Request for Comments: 2195 R. Catoe
+Category: Standards Track P. Krumviede
+Obsoletes: 2095 MCI
+ September 1997
+
+
+ IMAP/POP AUTHorize Extension for Simple Challenge/Response
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Abstract
+
+ While IMAP4 supports a number of strong authentication mechanisms as
+ described in RFC 1731, it lacks any mechanism that neither passes
+ cleartext, reusable passwords across the network nor requires either
+ a significant security infrastructure or that the mail server update
+ a mail-system-wide user authentication file on each mail access.
+ This specification provides a simple challenge-response
+ authentication protocol that is suitable for use with IMAP4. Since
+ it utilizes Keyed-MD5 digests and does not require that the secret be
+ stored in the clear on the server, it may also constitute an
+ improvement on APOP for POP3 use as specified in RFC 1734.
+
+1. Introduction
+
+ Existing Proposed Standards specify an AUTHENTICATE mechanism for the
+ IMAP4 protocol [IMAP, IMAP-AUTH] and a parallel AUTH mechanism for
+ the POP3 protocol [POP3-AUTH]. The AUTHENTICATE mechanism is
+ intended to be extensible; the four methods specified in [IMAP-AUTH]
+ are all fairly powerful and require some security infrastructure to
+ support. The base POP3 specification [POP3] also contains a
+ lightweight challenge-response mechanism called APOP. APOP is
+ associated with most of the risks associated with such protocols: in
+ particular, it requires that both the client and server machines have
+ access to the shared secret in cleartext form. CRAM offers a method
+ for avoiding such cleartext storage while retaining the algorithmic
+ simplicity of APOP in using only MD5, though in a "keyed" method.
+
+
+
+
+
+
+
+Klensin, Catoe & Krumviede Standards Track [Page 1]
+
+RFC 2195 IMAP/POP AUTHorize Extension September 1997
+
+
+ At present, IMAP [IMAP] lacks any facility corresponding to APOP.
+ The only alternative to the strong mechanisms identified in [IMAP-
+ AUTH] is a presumably cleartext username and password, supported
+ through the LOGIN command in [IMAP]. This document describes a
+ simple challenge-response mechanism, similar to APOP and PPP CHAP
+ [PPP], that can be used with IMAP (and, in principle, with POP3).
+
+ This mechanism also has the advantage over some possible alternatives
+ of not requiring that the server maintain information about email
+ "logins" on a per-login basis. While mechanisms that do require such
+ per-login history records may offer enhanced security, protocols such
+ as IMAP, which may have several connections between a given client
+ and server open more or less simultaneous, may make their
+ implementation particularly challenging.
+
+2. Challenge-Response Authentication Mechanism (CRAM)
+
+ The authentication type associated with CRAM is "CRAM-MD5".
+
+ The data encoded in the first ready response contains an
+ presumptively arbitrary string of random digits, a timestamp, and the
+ fully-qualified primary host name of the server. The syntax of the
+ unencoded form must correspond to that of an RFC 822 'msg-id'
+ [RFC822] as described in [POP3].
+
+ The client makes note of the data and then responds with a string
+ consisting of the user name, a space, and a 'digest'. The latter is
+ computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
+ the key is a shared secret and the digested text is the timestamp
+ (including angle-brackets).
+
+ This shared secret is a string known only to the client and server.
+ The `digest' parameter itself is a 16-octet value which is sent in
+ hexadecimal format, using lower-case ASCII characters.
+
+ When the server receives this client response, it verifies the digest
+ provided. If the digest is correct, the server should consider the
+ client authenticated and respond appropriately.
+
+ Keyed MD5 is chosen for this application because of the greater
+ security imparted to authentication of short messages. In addition,
+ the use of the techniques described in [KEYED-MD5] for precomputation
+ of intermediate results make it possible to avoid explicit cleartext
+ storage of the shared secret on the server system by instead storing
+ the intermediate results which are known as "contexts".
+
+
+
+
+
+
+Klensin, Catoe & Krumviede Standards Track [Page 2]
+
+RFC 2195 IMAP/POP AUTHorize Extension September 1997
+
+
+ CRAM does not support a protection mechanism.
+
+ Example:
+
+ The examples in this document show the use of the CRAM mechanism with
+ the IMAP4 AUTHENTICATE command [IMAP-AUTH]. The base64 encoding of
+ the challenges and responses is part of the IMAP4 AUTHENTICATE
+ command, not part of the CRAM specification itself.
+
+ S: * OK IMAP4 Server
+ C: A0001 AUTHENTICATE CRAM-MD5
+ S: + PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
+ C: dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
+ S: A0001 OK CRAM authentication successful
+
+ In this example, the shared secret is the string
+ 'tanstaaftanstaaf'. Hence, the Keyed MD5 digest is produced by
+ calculating
+
+ MD5((tanstaaftanstaaf XOR opad),
+ MD5((tanstaaftanstaaf XOR ipad),
+ <1896.697170952@postoffice.reston.mci.net>))
+
+ where ipad and opad are as defined in the keyed-MD5 Work in
+ Progress [KEYED-MD5] and the string shown in the challenge is the
+ base64 encoding of <1896.697170952@postoffice.reston.mci.net>. The
+ shared secret is null-padded to a length of 64 bytes. If the
+ shared secret is longer than 64 bytes, the MD5 digest of the
+ shared secret is used as a 16 byte input to the keyed MD5
+ calculation.
+
+ This produces a digest value (in hexadecimal) of
+
+ b913a602c7eda7a495b4e6e7334d3890
+
+ The user name is then prepended to it, forming
+
+ tim b913a602c7eda7a495b4e6e7334d3890
+
+ Which is then base64 encoded to meet the requirements of the IMAP4
+ AUTHENTICATE command (or the similar POP3 AUTH command), yielding
+
+ dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
+
+
+
+
+
+
+
+
+Klensin, Catoe & Krumviede Standards Track [Page 3]
+
+RFC 2195 IMAP/POP AUTHorize Extension September 1997
+
+
+3. References
+
+ [CHAP] Lloyd, B., and W. Simpson, "PPP Authentication Protocols",
+ RFC 1334, October 1992.
+
+ [IMAP] Crispin, M., "Internet Message Access Protocol - Version
+ 4rev1", RFC 2060, University of Washington, December 1996.
+
+ [IMAP-AUTH] Myers, J., "IMAP4 Authentication Mechanisms",
+ RFC 1731, Carnegie Mellon, December 1994.
+
+ [KEYED-MD5] Krawczyk, Bellare, Canetti, "HMAC: Keyed-Hashing for
+ Message Authentication", RFC 2104, February 1997.
+
+ [MD5] Rivest, R., "The MD5 Message Digest Algorithm",
+ RFC 1321, MIT Laboratory for Computer Science, April 1992.
+
+ [POP3] Myers, J., and M. Rose, "Post Office Protocol - Version 3",
+ STD 53, RFC 1939, Carnegie Mellon, May 1996.
+
+ [POP3-AUTH] Myers, J., "POP3 AUTHentication command", RFC 1734,
+ Carnegie Mellon, December, 1994.
+
+4. Security Considerations
+
+ It is conjectured that use of the CRAM authentication mechanism
+ provides origin identification and replay protection for a session.
+ Accordingly, a server that implements both a cleartext password
+ command and this authentication type should not allow both methods of
+ access for a given user.
+
+ While the saving, on the server, of "contexts" (see section 2) is
+ marginally better than saving the shared secrets in cleartext as is
+ required by CHAP [CHAP] and APOP [POP3], it is not sufficient to
+ protect the secrets if the server itself is compromised.
+ Consequently, servers that store the secrets or contexts must both be
+ protected to a level appropriate to the potential information value
+ in user mailboxes and identities.
+
+ As the length of the shared secret increases, so does the difficulty
+ of deriving it.
+
+ While there are now suggestions in the literature that the use of MD5
+ and keyed MD5 in authentication procedures probably has a limited
+ effective lifetime, the technique is now widely deployed and widely
+ understood. It is believed that this general understanding may
+ assist with the rapid replacement, by CRAM-MD5, of the current uses
+ of permanent cleartext passwords in IMAP. This document has been
+
+
+
+Klensin, Catoe & Krumviede Standards Track [Page 4]
+
+RFC 2195 IMAP/POP AUTHorize Extension September 1997
+
+
+ deliberately written to permit easy upgrading to use SHA (or whatever
+ alternatives emerge) when they are considered to be widely available
+ and adequately safe.
+
+ Even with the use of CRAM, users are still vulnerable to active
+ attacks. An example of an increasingly common active attack is 'TCP
+ Session Hijacking' as described in CERT Advisory CA-95:01 [CERT95].
+
+ See section 1 above for additional discussion.
+
+5. Acknowledgements
+
+ This memo borrows ideas and some text liberally from [POP3] and
+ [RFC-1731] and thanks are due the authors of those documents. Ran
+ Atkinson made a number of valuable technical and editorial
+ contributions to the document.
+
+6. Authors' Addresses
+
+ John C. Klensin
+ MCI Telecommunications
+ 800 Boylston St, 7th floor
+ Boston, MA 02199
+ USA
+
+ EMail: klensin@mci.net
+ Phone: +1 617 960 1011
+
+ Randy Catoe
+ MCI Telecommunications
+ 2100 Reston Parkway
+ Reston, VA 22091
+ USA
+
+ EMail: randy@mci.net
+ Phone: +1 703 715 7366
+
+ Paul Krumviede
+ MCI Telecommunications
+ 2100 Reston Parkway
+ Reston, VA 22091
+ USA
+
+ EMail: paul@mci.net
+ Phone: +1 703 715 7251
+
+
+
+
+
+
+Klensin, Catoe & Krumviede Standards Track [Page 5]
+
diff --git a/src/common/libManageSieve/doc/SASL LOGIN/draft-murchison-sasl-login-00.txt b/src/common/libManageSieve/doc/SASL LOGIN/draft-murchison-sasl-login-00.txt
new file mode 100644
index 00000000..e6ffc297
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL LOGIN/draft-murchison-sasl-login-00.txt
@@ -0,0 +1,396 @@
+
+
+
+
+
+
+
+Internet Draft K. Murchison
+Category: Informational M. Crispin
+Expires: March 2, 2004 28 August 2003
+
+
+ The LOGIN SASL Mechanism
+
+ <draft-murchison-sasl-login-00.txt>
+
+
+Status of this Memo
+
+ This document is an Internet-Draft and is subject to all provisions
+ of Section 10 of RFC2026.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF), its areas, and its working groups. Note that
+ other groups may also distribute working documents as
+ Internet-Drafts.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ The list of current Internet-Drafts can be accessed at
+ http://www.ietf.org/1id-abstracts.html
+
+ The list of Internet-Draft Shadow Directories can be accessed at
+ http://www.ietf.org/shadow.html
+
+
+Copyright Notice
+
+ Copyright (C) The Internet Society 2003. All Rights Reserved.
+
+
+Abstract
+
+ This document documents the obsolete clear-text user/password Simple
+ Authentication and Security Layer (SASL) mechanism called the LOGIN
+ mechanism. The LOGIN mechanism was intended to be used, in
+ combination with data confidentiality services provided by a lower
+ layer, in protocols which lack a simple password authentication
+ command.
+
+
+
+
+
+
+Expires: March 2, 2004 Murchison [Page 1]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+
+Conventions Used in the Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [KEYWORDS].
+
+
+1. Background and Intended Usage
+
+ This document documents the obsolete LOGIN Simple Authentication and
+ Security Layer ([SASL]) mechanism which was in use in protocols with
+ no clear-text login command (e.g., [SMTP-AUTH]).
+
+ Note: The LOGIN SASL mechanism is obsoleted in favor of the PLAIN
+ SASL mechanism ([PLAIN]). The LOGIN mechanism is documented here
+ only for the purpose of backwards compatibility with legacy software.
+ Clients SHOULD implement the PLAIN SASL mechanism and use it whenever
+ offered by a server. The LOGIN SASL mechanism SHOULD NOT be used by
+ a client when other plaintext mechanisms are offered by a server.
+
+ The name associated with this mechanism is "LOGIN".
+
+ The LOGIN SASL mechanism does not provide a security layer. This
+ mechanism MUST NOT be used without adequate security protection as
+ the mechanism affords no integrity nor confidentiality protection
+ itself. The LOGIN SASL mechanism MUST NOT be advertised or used in
+ any configuration that prohibits the PLAIN mechanism or plaintext
+ LOGIN (or USER/PASS) command that sends passwords in the clear.
+
+
+2. LOGIN SASL Mechanism
+
+ The authorization identity is the same string as the "username" in
+ the traditional (non-SASL) LOGIN or USER commands; the authorization
+ authenticator is the same string as the traditional "password". The
+ authentication identity is the same as the authorization identity in
+ this mechanism.
+
+ Only US-ASCII printable characters SHOULD be used in the username and
+ password to permit maximal interoperability. If non-US-ASCII
+ characters are used in a username, they MUST use UTF-8. Passwords
+ MAY contain arbitrary binary data excluding NUL, CR and LF
+ characters. However, if a password is supplied to the client as a
+ sequence of characters (e.g., a password dialog box), those
+ characters MUST be encoded as UTF-8.
+
+ The username MUST be less than 64 characters in length.
+
+
+
+Expires: March 2, 2004 Murchison [Page 2]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+2.1. Client side of authentication protocol exchange
+
+ The client expects the server to issue a challenge. The client then
+ responds with the authorization identity. The client then expects
+ the server to issue a second challenge. The client then responds
+ with the authorization authenticator. The contents of both
+ challenges SHOULD be ignored.
+
+
+2.2. Server side of authentication protocol exchange
+
+ The server issues the string "User Name" in challenge, and receives a
+ client response. This response is recorded as the authorization
+ identity. The server then issues the string "Password" in challenge,
+ and receives a client response. This response is recorded as the
+ authorization authenticator. The server must verify that the
+ authorization authenticator permits login as the authorization
+ identity.
+
+ Note: There is at least one widely deployed client which requires
+ that the challenge strings transmitted by the server be "Username:"
+ and "Password:" respectively. For this reason, server
+ implementations MAY send these challenge strings instead of those
+ listed above.
+
+
+2.3. Example
+
+ This example shows the use of the LOGIN mechanism with the SMTP AUTH
+ command [SMTP-AUTH] under the protection of SMTP STARTTLS [SMTP-TLS].
+ The user name is "tim" and the password is "tanstaaftanstaaf". The
+ base64 encoding of the challenges and responses is part of the SMTP
+ AUTH command, not part of the LOGIN specification itself. "C:" and
+ "S:" indicate lines sent by the client and server respectively.
+
+ S: 220 smtp.example.com ESMTP server ready
+ C: EHLO test.example.com
+ S: 250-smtp.example.com
+ S: 250-STARTTLS
+ S: 250 AUTH CRAM-MD5
+ C: STARTTLS
+ S: 220 Ready to start TLS
+ <TLS negotiation, further commands are under TLS layer>
+ C: EHLO test.example.com
+ S: 250-smtp.example.com
+ S: 250 AUTH LOGIN CRAM-MD5
+ C: AUTH LOGIN
+ S: 334 VXNlciBOYW1lAA==
+
+
+
+Expires: March 2, 2004 Murchison [Page 3]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+ C: dGlt
+ S: 334 UGFzc3dvcmQA
+ C: dGFuc3RhYWZ0YW5zdGFhZg==
+ S: 235 Authentication successful.
+
+
+3.
+ Security Considerations
+
+ The LOGIN mechanism relies upon an underlying encryption layer or
+ other secure channel for security. When used without an encryption
+ layer or secure channel, it is vulnerable to a common network
+ eavesdropping attack. Therefore the LOGIN mechanism MUST NOT be
+ advertised or used in any configuration that prohibits the PLAIN
+ mechanism or a plaintext LOGIN (or USER/PASS) command that sends
+ passwords in the clear.
+
+
+4.
+ IANA Considerations
+
+ The registration for the LOGIN SASL mechanism follows:
+
+ SASL mechanism name: LOGIN
+ Security Considerations: See section 3 of this memo
+ Published specification: this memo
+ Person & email address to contact for futher information:
+ See section 7 of this memo
+ Intended usage: OBSOLETE
+ Owner/Change controller: See section 7 of this memo
+
+
+5.
+ References
+
+
+5.1.
+ Normative References
+
+
+ [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", Harvard University, RFC 2119, March 1997.
+
+
+ [SASL] Melnikov, A., Ed., "Simple Authentication and Security Layer
+ (SASL)", Isode, draft-ietf-sasl-rfc2222bis-xx.txt, Work In
+ Progress.
+
+
+
+
+Expires: March 2, 2004 Murchison [Page 4]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+5.2. Informative References
+
+
+ [PLAIN] Zeilenga, Kurt D., Ed., "The Plain SASL Mechanism",
+ OpenLDAP Foundation, draft-ietf-sasl-plain-xx.txt, Work In
+ Progress.
+
+
+ [SMTP-AUTH] Myers, J., "SMTP Service Extension for Authentication",
+ Netscape Communications, RFC 2554, March 1999.
+
+
+ [SMTP-TLS] Hoffman, P., "SMTP Service Extension for Secure SMTP
+ over Transport Layer Security", Internet Mail Consortium, RFC
+ 3207, February 2002.
+
+
+
+6. Acknowledgments
+
+ Thanks to Rob Siemborski for his input and feedback on this document.
+
+
+7.
+ Author's Address
+
+ Kenneth Murchison
+ Oceana Matrix Ltd.
+ 21 Princeton Place
+ Orchard Park, NY 14127
+
+ Phone: (716) 662-8973
+
+ EMail: ken@oceana.com
+
+
+
+
+ Mark R. Crispin
+ Networks and Distributed Computing
+ University of Washington
+ 4545 15th Avenue NE
+ Seattle, WA 98105-4527
+
+ Phone: (206) 543-5762
+
+ EMail: MRC@CAC.Washington.EDU
+
+
+
+
+Expires: March 2, 2004 Murchison [Page 5]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+8.
+ Intellectual Property Considerations
+
+ The IETF takes no position regarding the validity or scope of any
+ intellectual property or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; neither does it represent that it has
+ made any effort to identify any such rights. Information on the
+ IETF's procedures with respect to rights in standards-track and
+ standards-related documentation can be found in BCP-11. Copies of
+ claims of rights made available for publication and any assurances of
+ licenses to be made available, or the result of an attempt made to
+ obtain a general license or permission for the use of such proprietary
+ rights by implementors or users of this specification can be obtained
+ from the IETF Secretariat.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights which may cover technology that may be required to practice
+ this standard. Please address the information to the IETF Executive
+ Director.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Expires: March 2, 2004 Murchison [Page 6]
+
+Internet Draft LOGIN SASL Mechanism August 28, 2004
+
+
+9.
+ Full Copyright Statement
+
+ Copyright (C) The Internet Society 2003. All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implmentation may be prepared, copied, published and
+ distributed, in whole or in part, without restriction of any kind,
+ provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be followed,
+ or as required to translate it into languages other than English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET
+ ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+ INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+ WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Expires: March 2, 2004 Murchison [Page 7]
+
diff --git a/src/common/libManageSieve/doc/SASL PLAIN/rfc4616.txt b/src/common/libManageSieve/doc/SASL PLAIN/rfc4616.txt
new file mode 100644
index 00000000..991189d5
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL PLAIN/rfc4616.txt
@@ -0,0 +1,619 @@
+
+
+
+
+
+
+Network Working Group K. Zeilenga, Ed.
+Request for Comments: 4616 OpenLDAP Foundation
+Updates: 2595 August 2006
+Category: Standards Track
+
+
+ The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
+
+Status of This Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2006).
+
+Abstract
+
+ This document defines a simple clear-text user/password Simple
+ Authentication and Security Layer (SASL) mechanism called the PLAIN
+ mechanism. The PLAIN mechanism is intended to be used, in
+ combination with data confidentiality services provided by a lower
+ layer, in protocols that lack a simple password authentication
+ command.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 1]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+1. Introduction
+
+ Clear-text, multiple-use passwords are simple, interoperate with
+ almost all existing operating system authentication databases, and
+ are useful for a smooth transition to a more secure password-based
+ authentication mechanism. The drawback is that they are unacceptable
+ for use over network connections where data confidentiality is not
+ ensured.
+
+ This document defines the PLAIN Simple Authentication and Security
+ Layer ([SASL]) mechanism for use in protocols with no clear-text
+ login command (e.g., [ACAP] or [SMTP-AUTH]). This document updates
+ RFC 2595, replacing Section 6. Changes since RFC 2595 are detailed
+ in Appendix A.
+
+ The name associated with this mechanism is "PLAIN".
+
+ The PLAIN SASL mechanism does not provide a security layer.
+
+ The PLAIN mechanism should not be used without adequate data security
+ protection as this mechanism affords no integrity or confidentiality
+ protections itself. The mechanism is intended to be used with data
+ security protections provided by application-layer protocol,
+ generally through its use of Transport Layer Security ([TLS])
+ services.
+
+ By default, implementations SHOULD advertise and make use of the
+ PLAIN mechanism only when adequate data security services are in
+ place. Specifications for IETF protocols that indicate that this
+ mechanism is an applicable authentication mechanism MUST mandate that
+ implementations support an strong data security service, such as TLS.
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [Keywords].
+
+2. PLAIN SASL Mechanism
+
+ The mechanism consists of a single message, a string of [UTF-8]
+ encoded [Unicode] characters, from the client to the server. The
+ client presents the authorization identity (identity to act as),
+ followed by a NUL (U+0000) character, followed by the authentication
+ identity (identity whose password will be used), followed by a NUL
+ (U+0000) character, followed by the clear-text password. As with
+ other SASL mechanisms, the client does not provide an authorization
+ identity when it wishes the server to derive an identity from the
+ credentials and use that as the authorization identity.
+
+
+
+
+Zeilenga Standards Track [Page 2]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+ The formal grammar for the client message using Augmented BNF [ABNF]
+ follows.
+
+ message = [authzid] UTF8NUL authcid UTF8NUL passwd
+ authcid = 1*SAFE ; MUST accept up to 255 octets
+ authzid = 1*SAFE ; MUST accept up to 255 octets
+ passwd = 1*SAFE ; MUST accept up to 255 octets
+ UTF8NUL = %x00 ; UTF-8 encoded NUL character
+
+ SAFE = UTF1 / UTF2 / UTF3 / UTF4
+ ;; any UTF-8 encoded Unicode character except NUL
+
+ UTF1 = %x01-7F ;; except NUL
+ UTF2 = %xC2-DF UTF0
+ UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) /
+ %xED %x80-9F UTF0 / %xEE-EF 2(UTF0)
+ UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) /
+ %xF4 %x80-8F 2(UTF0)
+ UTF0 = %x80-BF
+
+ The authorization identity (authzid), authentication identity
+ (authcid), password (passwd), and NUL character deliminators SHALL be
+ transferred as [UTF-8] encoded strings of [Unicode] characters. As
+ the NUL (U+0000) character is used as a deliminator, the NUL (U+0000)
+ character MUST NOT appear in authzid, authcid, or passwd productions.
+
+ The form of the authzid production is specific to the application-
+ level protocol's SASL profile [SASL]. The authcid and passwd
+ productions are form-free. Use of non-visible characters or
+ characters that a user may be unable to enter on some keyboards is
+ discouraged.
+
+ Servers MUST be capable of accepting authzid, authcid, and passwd
+ productions up to and including 255 octets. It is noted that the
+ UTF-8 encoding of a Unicode character may be as long as 4 octets.
+
+ Upon receipt of the message, the server will verify the presented (in
+ the message) authentication identity (authcid) and password (passwd)
+ with the system authentication database, and it will verify that the
+ authentication credentials permit the client to act as the (presented
+ or derived) authorization identity (authzid). If both steps succeed,
+ the user is authenticated.
+
+ The presented authentication identity and password strings, as well
+ as the database authentication identity and password strings, are to
+ be prepared before being used in the verification process. The
+ [SASLPrep] profile of the [StringPrep] algorithm is the RECOMMENDED
+ preparation algorithm. The SASLprep preparation algorithm is
+
+
+
+Zeilenga Standards Track [Page 3]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+ recommended to improve the likelihood that comparisons behave in an
+ expected manner. The SASLprep preparation algorithm is not mandatory
+ so as to allow the server to employ other preparation algorithms
+ (including none) when appropriate. For instance, use of a different
+ preparation algorithm may be necessary for the server to interoperate
+ with an external system.
+
+ When preparing the presented strings using [SASLPrep], the presented
+ strings are to be treated as "query" strings (Section 7 of
+ [StringPrep]) and hence unassigned code points are allowed to appear
+ in their prepared output. When preparing the database strings using
+ [SASLPrep], the database strings are to be treated as "stored"
+ strings (Section 7 of [StringPrep]) and hence unassigned code points
+ are prohibited from appearing in their prepared output.
+
+ Regardless of the preparation algorithm used, if the output of a
+ non-invertible function (e.g., hash) of the expected string is
+ stored, the string MUST be prepared before input to that function.
+
+ Regardless of the preparation algorithm used, if preparation fails or
+ results in an empty string, verification SHALL fail.
+
+ When no authorization identity is provided, the server derives an
+ authorization identity from the prepared representation of the
+ provided authentication identity string. This ensures that the
+ derivation of different representations of the authentication
+ identity produces the same authorization identity.
+
+ The server MAY use the credentials to initialize any new
+ authentication database, such as one suitable for [CRAM-MD5] or
+ [DIGEST-MD5].
+
+3. Pseudo-Code
+
+ This section provides pseudo-code illustrating the verification
+ process (using hashed passwords and the SASLprep preparation
+ function) discussed above. This section is not definitive.
+
+ boolean Verify(string authzid, string authcid, string passwd) {
+ string pAuthcid = SASLprep(authcid, true); # prepare authcid
+ string pPasswd = SASLprep(passwd, true); # prepare passwd
+ if (pAuthcid == NULL || pPasswd == NULL) {
+ return false; # preparation failed
+ }
+ if (pAuthcid == "" || pPasswd == "") {
+ return false; # empty prepared string
+ }
+
+
+
+
+Zeilenga Standards Track [Page 4]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+ storedHash = FetchPasswordHash(pAuthcid);
+ if (storedHash == NULL || storedHash == "") {
+ return false; # error or unknown authcid
+ }
+
+ if (!Compare(storedHash, Hash(pPasswd))) {
+ return false; # incorrect password
+ }
+
+ if (authzid == NULL ) {
+ authzid = DeriveAuthzid(pAuthcid);
+ if (authzid == NULL || authzid == "") {
+ return false; # could not derive authzid
+ }
+ }
+
+ if (!Authorize(pAuthcid, authzid)) {
+ return false; # not authorized
+ }
+
+ return true;
+ }
+
+ The second parameter of the SASLprep function, when true, indicates
+ that unassigned code points are allowed in the input. When the
+ SASLprep function is called to prepare the password prior to
+ computing the stored hash, the second parameter would be false.
+
+ The second parameter provided to the Authorize function is not
+ prepared by this code. The application-level SASL profile should be
+ consulted to determine what, if any, preparation is necessary.
+
+ Note that the DeriveAuthzid and Authorize functions (whether
+ implemented as one function or two, whether designed in a manner in
+ which these functions or whether the mechanism implementation can be
+ reused elsewhere) require knowledge and understanding of mechanism
+ and the application-level protocol specification and/or
+ implementation details to implement.
+
+ Note that the Authorize function outcome is clearly dependent on
+ details of the local authorization model and policy. Both functions
+ may be dependent on other factors as well.
+
+
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 5]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+4. Examples
+
+ This section provides examples of PLAIN authentication exchanges.
+ The examples are intended to help the readers understand the above
+ text. The examples are not definitive.
+
+ "C:" and "S:" indicate lines sent by the client and server,
+ respectively. "<NUL>" represents a single NUL (U+0000) character.
+ The Application Configuration Access Protocol ([ACAP]) is used in the
+ examples.
+
+ The first example shows how the PLAIN mechanism might be used for
+ user authentication.
+
+ S: * ACAP (SASL "CRAM-MD5") (STARTTLS)
+ C: a001 STARTTLS
+ S: a001 OK "Begin TLS negotiation now"
+ <TLS negotiation, further commands are under TLS layer>
+ S: * ACAP (SASL "CRAM-MD5" "PLAIN")
+ C: a002 AUTHENTICATE "PLAIN"
+ S: + ""
+ C: {21}
+ C: <NUL>tim<NUL>tanstaaftanstaaf
+ S: a002 OK "Authenticated"
+
+ The second example shows how the PLAIN mechanism might be used to
+ attempt to assume the identity of another user. In this example, the
+ server rejects the request. Also, this example makes use of the
+ protocol optional initial response capability to eliminate a round-
+ trip.
+
+ S: * ACAP (SASL "CRAM-MD5") (STARTTLS)
+ C: a001 STARTTLS
+ S: a001 OK "Begin TLS negotiation now"
+ <TLS negotiation, further commands are under TLS layer>
+ S: * ACAP (SASL "CRAM-MD5" "PLAIN")
+ C: a002 AUTHENTICATE "PLAIN" {20+}
+ C: Ursel<NUL>Kurt<NUL>xipj3plmq
+ S: a002 NO "Not authorized to requested authorization identity"
+
+5. Security Considerations
+
+ As the PLAIN mechanism itself provided no integrity or
+ confidentiality protections, it should not be used without adequate
+ external data security protection, such as TLS services provided by
+ many application-layer protocols. By default, implementations SHOULD
+ NOT advertise and SHOULD NOT make use of the PLAIN mechanism unless
+ adequate data security services are in place.
+
+
+
+Zeilenga Standards Track [Page 6]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+ When the PLAIN mechanism is used, the server gains the ability to
+ impersonate the user to all services with the same password
+ regardless of any encryption provided by TLS or other confidentiality
+ protection mechanisms. Whereas many other authentication mechanisms
+ have similar weaknesses, stronger SASL mechanisms address this issue.
+ Clients are encouraged to have an operational mode where all
+ mechanisms that are likely to reveal the user's password to the
+ server are disabled.
+
+ General [SASL] security considerations apply to this mechanism.
+
+ Unicode, [UTF-8], and [StringPrep] security considerations also
+ apply.
+
+6. IANA Considerations
+
+ The SASL Mechanism registry [IANA-SASL] entry for the PLAIN mechanism
+ has been updated by the IANA to reflect that this document now
+ provides its technical specification.
+
+ To: iana@iana.org
+ Subject: Updated Registration of SASL mechanism PLAIN
+
+ SASL mechanism name: PLAIN
+ Security considerations: See RFC 4616.
+ Published specification (optional, recommended): RFC 4616
+ Person & email address to contact for further information:
+ Kurt Zeilenga <kurt@openldap.org>
+ IETF SASL WG <ietf-sasl@imc.org>
+ Intended usage: COMMON
+ Author/Change controller: IESG <iesg@ietf.org>
+ Note: Updates existing entry for PLAIN
+
+7. Acknowledgements
+
+ This document is a revision of RFC 2595 by Chris Newman. Portions of
+ the grammar defined in Section 2 were borrowed from [UTF-8] by
+ Francois Yergeau.
+
+ This document is a product of the IETF Simple Authentication and
+ Security Layer (SASL) Working Group.
+
+
+
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 7]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+8. Normative References
+
+ [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF for
+ Syntax Specifications: ABNF", RFC 4234, October 2005.
+
+ [Keywords] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [SASL] Melnikov, A., Ed., and K. Zeilenga, Ed., "Simple
+ Authentication and Security Layer (SASL)", RFC 4422,
+ June 2006.
+
+ [SASLPrep] Zeilenga, K., "SASLprep: Stringprep Profile for User
+ Names and Passwords", RFC 4013, February 2005.
+
+ [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of
+ Internationalized Strings ("stringprep")", RFC 3454,
+ December 2002.
+
+ [Unicode] The Unicode Consortium, "The Unicode Standard, Version
+ 3.2.0" is defined by "The Unicode Standard, Version
+ 3.0" (Reading, MA, Addison-Wesley, 2000. ISBN 0-201-
+ 61633-5), as amended by the "Unicode Standard Annex
+ #27: Unicode 3.1"
+ (http://www.unicode.org/reports/tr27/) and by the
+ "Unicode Standard Annex #28: Unicode 3.2"
+ (http://www.unicode.org/reports/tr28/).
+
+ [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO
+ 10646", STD 63, RFC 3629, November 2003.
+
+ [TLS] Dierks, T. and E. Rescorla, "The Transport Layer
+ Security (TLS) Protocol Version 1.1", RFC 4346, April
+ 2006.
+
+9. Informative References
+
+ [ACAP] Newman, C. and J. Myers, "ACAP -- Application
+ Configuration Access Protocol", RFC 2244, November
+ 1997.
+
+ [CRAM-MD5] Nerenberg, L., Ed., "The CRAM-MD5 SASL Mechanism", Work
+ in Progress, June 2006.
+
+ [DIGEST-MD5] Melnikov, A., Ed., "Using Digest Authentication as a
+ SASL Mechanism", Work in Progress, June 2006.
+
+
+
+
+
+Zeilenga Standards Track [Page 8]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+ [IANA-SASL] IANA, "SIMPLE AUTHENTICATION AND SECURITY LAYER (SASL)
+ MECHANISMS",
+ <http://www.iana.org/assignments/sasl-mechanisms>.
+
+ [SMTP-AUTH] Myers, J., "SMTP Service Extension for Authentication",
+ RFC 2554, March 1999.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 9]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+Appendix A. Changes since RFC 2595
+
+ This appendix is non-normative.
+
+ This document replaces Section 6 of RFC 2595.
+
+ The specification details how the server is to compare client-
+ provided character strings with stored character strings.
+
+ The ABNF grammar was updated. In particular, the grammar now allows
+ LINE FEED (U+000A) and CARRIAGE RETURN (U+000D) characters in the
+ authzid, authcid, passwd productions. However, whether these control
+ characters may be used depends on the string preparation rules
+ applicable to the production. For passwd and authcid productions,
+ control characters are prohibited. For authzid, one must consult the
+ application-level SASL profile. This change allows PLAIN to carry
+ all possible authorization identity strings allowed in SASL.
+
+ Pseudo-code was added.
+
+ The example section was expanded to illustrate more features of the
+ PLAIN mechanism.
+
+Editor's Address
+
+ Kurt D. Zeilenga
+ OpenLDAP Foundation
+
+ EMail: Kurt@OpenLDAP.org
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 10]
+
+RFC 4616 The PLAIN SASL Mechanism August 2006
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2006).
+
+ This document is subject to the rights, licenses and restrictions
+ contained in BCP 78, and except as set forth therein, the authors
+ retain all their rights.
+
+ This document and the information contained herein are provided on an
+ "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+ OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+ ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+ INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+ WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Intellectual Property
+
+ The IETF takes no position regarding the validity or scope of any
+ Intellectual Property Rights or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; nor does it represent that it has
+ made any independent effort to identify any such rights. Information
+ on the procedures with respect to rights in RFC documents can be
+ found in BCP 78 and BCP 79.
+
+ Copies of IPR disclosures made to the IETF Secretariat and any
+ assurances of licenses to be made available, or the result of an
+ attempt made to obtain a general license or permission for the use of
+ such proprietary rights by implementers or users of this
+ specification can be obtained from the IETF on-line IPR repository at
+ http://www.ietf.org/ipr.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights that may cover technology that may be required to implement
+ this standard. Please address the information to the IETF at
+ ietf-ipr@ietf.org.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is provided by the IETF
+ Administrative Support Activity (IASA).
+
+
+
+
+
+
+
+Zeilenga Standards Track [Page 11]
+
diff --git a/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc2898.txt b/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc2898.txt
new file mode 100644
index 00000000..79af9b84
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc2898.txt
@@ -0,0 +1,1907 @@
+
+
+
+
+
+
+Network Working Group B. Kaliski
+Request for Comments: 2898 RSA Laboratories
+Category: Informational September 2000
+
+
+ PKCS #5: Password-Based Cryptography Specification
+ Version 2.0
+
+Status of this Memo
+
+ This memo provides information for the Internet community. It does
+ not specify an Internet standard of any kind. Distribution of this
+ memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+Abstract
+
+ This memo represents a republication of PKCS #5 v2.0 from RSA
+ Laboratories' Public-Key Cryptography Standards (PKCS) series, and
+ change control is retained within the PKCS process. The body of this
+ document, except for the security considerations section, is taken
+ directly from that specification.
+
+ This document provides recommendations for the implementation of
+ password-based cryptography, covering key derivation functions,
+ encryption schemes, message-authentication schemes, and ASN.1 syntax
+ identifying the techniques.
+
+ The recommendations are intended for general application within
+ computer and communications systems, and as such include a fair
+ amount of flexibility. They are particularly intended for the
+ protection of sensitive information such as private keys, as in PKCS
+ #8 [25]. It is expected that application standards and implementation
+ profiles based on these specifications may include additional
+ constraints.
+
+ Other cryptographic techniques based on passwords, such as password-
+ based key entity authentication and key establishment protocols
+ [4][5][26] are outside the scope of this document. Guidelines for
+ the selection of passwords are also outside the scope.
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 1]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+Table of Contents
+
+ 1. Introduction ............................................... 3
+ 2. Notation ................................................... 3
+ 3. Overview ................................................... 4
+ 4. Salt and iteration count ................................... 6
+ 4.1 Salt ................................................... 6
+ 4.2 Iteration count ........................................ 8
+ 5. Key derivation functions ................................... 8
+ 5.1 PBKDF1 ................................................. 9
+ 5.2 PBKDF2 ................................................. 9
+ 6. Encryption schemes ......................................... 11
+ 6.1 PBES1 .................................................. 12
+ 6.1.1 Encryption operation ............................ 12
+ 6.1.2 Decryption operation ............................ 13
+ 6.2 PBES2 .................................................. 14
+ 6.2.1 Encryption operation ............................ 14
+ 6.2.2 Decryption operation ............................ 15
+ 7. Message authentication schemes ............................. 15
+ 7.1 PBMAC1 ................................................. 16
+ 7.1.1 MAC generation .................................. 16
+ 7.1.2 MAC verification ................................ 16
+ 8. Security Considerations .................................... 17
+ 9. Author's Address............................................ 17
+ A. ASN.1 syntax ............................................... 18
+ A.1 PBKDF1 ................................................. 18
+ A.2 PBKDF2 ................................................. 18
+ A.3 PBES1 .................................................. 20
+ A.4 PBES2 .................................................. 20
+ A.5 PBMAC1 ................................................. 21
+ B. Supporting techniques ...................................... 22
+ B.1 Pseudorandom functions ................................. 22
+ B.2 Encryption schemes ..................................... 23
+ B.3 Message authentication schemes ......................... 26
+ C. ASN.1 module ............................................... 26
+ Intellectual Property Considerations ............................ 30
+ Revision history ................................................ 30
+ References ...................................................... 31
+ Contact Information & About PKCS ................................ 33
+ Full Copyright Statement ........................................ 34
+
+
+
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 2]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+1. Introduction
+
+ This document provides recommendations for the implementation of
+ password-based cryptography, covering the following aspects:
+
+ - key derivation functions
+ - encryption schemes
+ - message-authentication schemes
+ - ASN.1 syntax identifying the techniques
+
+ The recommendations are intended for general application within
+ computer and communications systems, and as such include a fair
+ amount of flexibility. They are particularly intended for the
+ protection of sensitive information such as private keys, as in PKCS
+ #8 [25]. It is expected that application standards and implementation
+ profiles based on these specifications may include additional
+ constraints.
+
+ Other cryptographic techniques based on passwords, such as password-
+ based key entity authentication and key establishment protocols
+ [4][5][26] are outside the scope of this document. Guidelines for
+ the selection of passwords are also outside the scope.
+
+ This document supersedes PKCS #5 version 1.5 [24], but includes
+ compatible techniques.
+
+2. Notation
+
+ C ciphertext, an octet string
+
+ c iteration count, a positive integer
+
+ DK derived key, an octet string
+
+ dkLen length in octets of derived key, a positive integer
+
+ EM encoded message, an octet string
+
+ Hash underlying hash function
+
+ hLen length in octets of pseudorandom function output, a positive
+ integer
+
+ l length in blocks of derived key, a positive integer
+
+ IV initialization vector, an octet string
+
+ K encryption key, an octet string
+
+
+
+Kaliski Informational [Page 3]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ KDF key derivation function
+
+ M message, an octet string
+
+ P password, an octet string
+
+ PRF underlying pseudorandom function
+
+ PS padding string, an octet string
+
+ psLen length in octets of padding string, a positive integer
+
+ S salt, an octet string
+
+ T message authentication code, an octet string
+
+ T_1, ..., T_l, U_1, ..., U_c
+ intermediate values, octet strings
+
+ 01, 02, ..., 08
+ octets with value 1, 2, ..., 8
+
+ \xor bit-wise exclusive-or of two octet strings
+
+ || || octet length operator
+
+ || concatenation operator
+
+ <i..j> substring extraction operator: extracts octets i through j,
+ 0 <= i <= j
+
+3. Overview
+
+ In many applications of public-key cryptography, user security is
+ ultimately dependent on one or more secret text values or passwords.
+ Since a password is not directly applicable as a key to any
+ conventional cryptosystem, however, some processing of the password
+ is required to perform cryptographic operations with it. Moreover, as
+ passwords are often chosen from a relatively small space, special
+ care is required in that processing to defend against search attacks.
+
+ A general approach to password-based cryptography, as described by
+ Morris and Thompson [8] for the protection of password tables, is to
+ combine a password with a salt to produce a key. The salt can be
+ viewed as an index into a large set of keys derived from the
+ password, and need not be kept secret. Although it may be possible
+ for an opponent to construct a table of possible passwords (a so-
+ called "dictionary attack"), constructing a table of possible keys
+
+
+
+Kaliski Informational [Page 4]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ will be difficult, since there will be many possible keys for each
+ password. An opponent will thus be limited to searching through
+ passwords separately for each salt.
+
+ Another approach to password-based cryptography is to construct key
+ derivation techniques that are relatively expensive, thereby
+ increasing the cost of exhaustive search. One way to do this is to
+ include an iteration count in the key derivation technique,
+ indicating how many times to iterate some underlying function by
+ which keys are derived. A modest number of iterations, say 1000, is
+ not likely to be a burden for legitimate parties when computing a
+ key, but will be a significant burden for opponents.
+
+ Salt and iteration count formed the basis for password-based
+ encryption in PKCS #5 v1.5, and adopted here as well for the various
+ cryptographic operations. Thus, password-based key derivation as
+ defined here is a function of a password, a salt, and an iteration
+ count, where the latter two quantities need not be kept secret.
+
+ From a password-based key derivation function, it is straightforward
+ to define password-based encryption and message authentication
+ schemes. As in PKCS #5 v1.5, the password-based encryption schemes
+ here are based on an underlying, conventional encryption scheme,
+ where the key for the conventional scheme is derived from the
+ password. Similarly, the password-based message authentication scheme
+ is based on an underlying conventional scheme. This two-layered
+ approach makes the password-based techniques modular in terms of the
+ underlying techniques they can be based on.
+
+ It is expected that the password-based key derivation functions may
+ find other applications than just the encryption and message
+ authentication schemes defined here. For instance, one might derive a
+ set of keys with a single application of a key derivation function,
+ rather than derive each key with a separate application of the
+ function. The keys in the set would be obtained as substrings of the
+ output of the key derivation function. This approach might be
+ employed as part of key establishment in a session-oriented protocol.
+ Another application is password checking, where the output of the key
+ derivation function is stored (along with the salt and iteration
+ count) for the purposes of subsequent verification of a password.
+
+ Throughout this document, a password is considered to be an octet
+ string of arbitrary length whose interpretation as a text string is
+ unspecified. In the interest of interoperability, however, it is
+ recommended that applications follow some common text encoding rules.
+ ASCII and UTF-8 [27] are two possibilities. (ASCII is a subset of
+ UTF-8.)
+
+
+
+
+Kaliski Informational [Page 5]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ Although the selection of passwords is outside the scope of this
+ document, guidelines have been published [17] that may well be taken
+ into account.
+
+4. Salt and Iteration Count
+
+ Inasmuch as salt and iteration count are central to the techniques
+ defined in this document, some further discussion is warranted.
+
+4.1 Salt
+
+ A salt in password-based cryptography has traditionally served the
+ purpose of producing a large set of keys corresponding to a given
+ password, among which one is selected at random according to the
+ salt. An individual key in the set is selected by applying a key
+ derivation function KDF, as
+
+ DK = KDF (P, S)
+
+ where DK is the derived key, P is the password, and S is the salt.
+ This has two benefits:
+
+ 1. It is difficult for an opponent to precompute all the keys
+ corresponding to a dictionary of passwords, or even the most
+ likely keys. If the salt is 64 bits long, for instance, there
+ will be as many as 2^64 keys for each password. An opponent is
+ thus limited to searching for passwords after a password-based
+ operation has been performed and the salt is known.
+
+ 2. It is unlikely that the same key will be selected twice.
+ Again, if the salt is 64 bits long, the chance of "collision"
+ between keys does not become significant until about 2^32 keys
+ have been produced, according to the Birthday Paradox. This
+ addresses some of the concerns about interactions between
+ multiple uses of the same key, which may apply for some
+ encryption and authentication techniques.
+
+ In password-based encryption, the party encrypting a message can gain
+ assurance that these benefits are realized simply by selecting a
+ large and sufficiently random salt when deriving an encryption key
+ from a password. A party generating a message authentication code can
+ gain such assurance in a similar fashion.
+
+ The party decrypting a message or verifying a message authentication
+ code, however, cannot be sure that a salt supplied by another party
+ has actually been generated at random. It is possible, for instance,
+ that the salt may have been copied from another password-based
+ operation, in an attempt to exploit interactions between multiple
+
+
+
+Kaliski Informational [Page 6]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ uses of the same key. For instance, suppose two legitimate parties
+ exchange a encrypted message, where the encryption key is an 80-bit
+ key derived from a shared password with some salt. An opponent could
+ take the salt from that encryption and provide it to one of the
+ parties as though it were for a 40-bit key. If the party reveals the
+ result of decryption with the 40-bit key, the opponent may be able to
+ solve for the 40-bit key. In the case that 40-bit key is the first
+ half of the 80-bit key, the opponent can then readily solve for the
+ remaining 40 bits of the 80-bit key.
+
+ To defend against such attacks, either the interaction between
+ multiple uses of the same key should be carefully analyzed, or the
+ salt should contain data that explicitly distinguishes between
+ different operations. For instance, the salt might have an
+ additional, non-random octet that specifies whether the derived key
+ is for encryption, for message authentication, or for some other
+ operation.
+
+ Based on this, the following is recommended for salt selection:
+
+ 1. If there is no concern about interactions between multiple uses
+ of the same key (or a prefix of that key) with the password-
+ based encryption and authentication techniques supported for a
+ given password, then the salt may be generated at random and
+ need not be checked for a particular format by the party
+ receiving the salt. It should be at least eight octets (64
+ bits) long.
+
+ 2. Otherwise, the salt should contain data that explicitly
+ distinguishes between different operations and different key
+ lengths, in addition to a random part that is at least eight
+ octets long, and this data should be checked or regenerated by
+ the party receiving the salt. For instance, the salt could have
+ an additional non-random octet that specifies the purpose of
+ the derived key. Alternatively, it could be the encoding of a
+ structure that specifies detailed information about the derived
+ key, such as the encryption or authentication technique and a
+ sequence number among the different keys derived from the
+ password. The particular format of the additional data is left
+ to the application.
+
+ Note. If a random number generator or pseudorandom generator is not
+ available, a deterministic alternative for generating the salt (or
+ the random part of it) is to apply a password-based key derivation
+ function to the password and the message M to be processed. For
+ instance, the salt could be computed with a key derivation function
+ as S = KDF (P, M). This approach is not recommended if the message M
+
+
+
+
+Kaliski Informational [Page 7]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ is known to belong to a small message space (e.g., "Yes" or "No"),
+ however, since then there will only be a small number of possible
+ salts.
+
+4.2 Iteration Count
+
+ An iteration count has traditionally served the purpose of increasing
+ the cost of producing keys from a password, thereby also increasing
+ the difficulty of attack. For the methods in this document, a minimum
+ of 1000 iterations is recommended. This will increase the cost of
+ exhaustive search for passwords significantly, without a noticeable
+ impact in the cost of deriving individual keys.
+
+5. Key Derivation Functions
+
+ A key derivation function produces a derived key from a base key and
+ other parameters. In a password-based key derivation function, the
+ base key is a password and the other parameters are a salt value and
+ an iteration count, as outlined in Section 3.
+
+ The primary application of the password-based key derivation
+ functions defined here is in the encryption schemes in Section 6 and
+ the message authentication scheme in Section 7. Other applications
+ are certainly possible, hence the independent definition of these
+ functions.
+
+ Two functions are specified in this section: PBKDF1 and PBKDF2.
+ PBKDF2 is recommended for new applications; PBKDF1 is included only
+ for compatibility with existing applications, and is not recommended
+ for new applications.
+
+ A typical application of the key derivation functions defined here
+ might include the following steps:
+
+ 1. Select a salt S and an iteration count c, as outlined in
+ Section 4.
+
+ 2. Select a length in octets for the derived key, dkLen.
+
+ 3. Apply the key derivation function to the password, the salt,
+ the iteration count and the key length to produce a derived
+ key.
+
+ 4. Output the derived key.
+
+ Any number of keys may be derived from a password by varying the
+ salt, as described in Section 3.
+
+
+
+
+Kaliski Informational [Page 8]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+5.1 PBKDF1
+
+ PBKDF1 applies a hash function, which shall be MD2 [6], MD5 [19] or
+ SHA-1 [18], to derive keys. The length of the derived key is bounded
+ by the length of the hash function output, which is 16 octets for MD2
+ and MD5 and 20 octets for SHA-1. PBKDF1 is compatible with the key
+ derivation process in PKCS #5 v1.5.
+
+ PBKDF1 is recommended only for compatibility with existing
+ applications since the keys it produces may not be large enough for
+ some applications.
+
+ PBKDF1 (P, S, c, dkLen)
+
+ Options: Hash underlying hash function
+
+ Input: P password, an octet string
+ S salt, an eight-octet string
+ c iteration count, a positive integer
+ dkLen intended length in octets of derived key,
+ a positive integer, at most 16 for MD2 or
+ MD5 and 20 for SHA-1
+
+ Output: DK derived key, a dkLen-octet string
+
+ Steps:
+
+ 1. If dkLen > 16 for MD2 and MD5, or dkLen > 20 for SHA-1, output
+ "derived key too long" and stop.
+
+ 2. Apply the underlying hash function Hash for c iterations to the
+ concatenation of the password P and the salt S, then extract
+ the first dkLen octets to produce a derived key DK:
+
+ T_1 = Hash (P || S) ,
+ T_2 = Hash (T_1) ,
+ ...
+ T_c = Hash (T_{c-1}) ,
+ DK = Tc<0..dkLen-1>
+
+ 3. Output the derived key DK.
+
+5.2 PBKDF2
+
+ PBKDF2 applies a pseudorandom function (see Appendix B.1 for an
+ example) to derive keys. The length of the derived key is essentially
+ unbounded. (However, the maximum effective search space for the
+
+
+
+
+Kaliski Informational [Page 9]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ derived key may be limited by the structure of the underlying
+ pseudorandom function. See Appendix B.1 for further discussion.)
+ PBKDF2 is recommended for new applications.
+
+ PBKDF2 (P, S, c, dkLen)
+
+ Options: PRF underlying pseudorandom function (hLen
+ denotes the length in octets of the
+ pseudorandom function output)
+
+ Input: P password, an octet string
+ S salt, an octet string
+ c iteration count, a positive integer
+ dkLen intended length in octets of the derived
+ key, a positive integer, at most
+ (2^32 - 1) * hLen
+
+ Output: DK derived key, a dkLen-octet string
+
+ Steps:
+
+ 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
+ stop.
+
+ 2. Let l be the number of hLen-octet blocks in the derived key,
+ rounding up, and let r be the number of octets in the last
+ block:
+
+ l = CEIL (dkLen / hLen) ,
+ r = dkLen - (l - 1) * hLen .
+
+ Here, CEIL (x) is the "ceiling" function, i.e. the smallest
+ integer greater than, or equal to, x.
+
+ 3. For each block of the derived key apply the function F defined
+ below to the password P, the salt S, the iteration count c, and
+ the block index to compute the block:
+
+ T_1 = F (P, S, c, 1) ,
+ T_2 = F (P, S, c, 2) ,
+ ...
+ T_l = F (P, S, c, l) ,
+
+ where the function F is defined as the exclusive-or sum of the
+ first c iterates of the underlying pseudorandom function PRF
+ applied to the password P and the concatenation of the salt S
+ and the block index i:
+
+
+
+
+Kaliski Informational [Page 10]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
+
+ where
+
+ U_1 = PRF (P, S || INT (i)) ,
+ U_2 = PRF (P, U_1) ,
+ ...
+ U_c = PRF (P, U_{c-1}) .
+
+ Here, INT (i) is a four-octet encoding of the integer i, most
+ significant octet first.
+
+ 4. Concatenate the blocks and extract the first dkLen octets to
+ produce a derived key DK:
+
+ DK = T_1 || T_2 || ... || T_l<0..r-1>
+
+ 5. Output the derived key DK.
+
+ Note. The construction of the function F follows a "belt-and-
+ suspenders" approach. The iterates U_i are computed recursively to
+ remove a degree of parallelism from an opponent; they are exclusive-
+ ored together to reduce concerns about the recursion degenerating
+ into a small set of values.
+
+6. Encryption Schemes
+
+ An encryption scheme, in the symmetric setting, consists of an
+ encryption operation and a decryption operation, where the encryption
+ operation produces a ciphertext from a message under a key, and the
+ decryption operation recovers the message from the ciphertext under
+ the same key. In a password-based encryption scheme, the key is a
+ password.
+
+ A typical application of a password-based encryption scheme is a
+ private-key protection method, where the message contains private-key
+ information, as in PKCS #8. The encryption schemes defined here would
+ be suitable encryption algorithms in that context.
+
+ Two schemes are specified in this section: PBES1 and PBES2. PBES2 is
+ recommended for new applications; PBES1 is included only for
+ compatibility with existing applications, and is not recommended for
+ new applications.
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 11]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+6.1 PBES1
+
+ PBES1 combines the PBKDF1 function (Section 5.1) with an underlying
+ block cipher, which shall be either DES [15] or RC2(tm) [21] in CBC
+ mode [16]. PBES1 is compatible with the encryption scheme in PKCS #5
+ v1.5.
+
+ PBES1 is recommended only for compatibility with existing
+ applications, since it supports only two underlying encryption
+ schemes, each of which has a key size (56 or 64 bits) that may not be
+ large enough for some applications.
+
+6.1.1 Encryption Operation
+
+ The encryption operation for PBES1 consists of the following steps,
+ which encrypt a message M under a password P to produce a ciphertext
+ C:
+
+ 1. Select an eight-octet salt S and an iteration count c, as
+ outlined in Section 4.
+
+ 2. Apply the PBKDF1 key derivation function (Section 5.1) to the
+ password P, the salt S, and the iteration count c to produce at
+ derived key DK of length 16 octets:
+
+ DK = PBKDF1 (P, S, c, 16) .
+
+ 3. Separate the derived key DK into an encryption key K consisting
+ of the first eight octets of DK and an initialization vector IV
+ consisting of the next eight octets:
+
+ K = DK<0..7> ,
+ IV = DK<8..15> .
+
+ 4. Concatenate M and a padding string PS to form an encoded
+ message EM:
+
+ EM = M || PS ,
+
+ where the padding string PS consists of 8-(||M|| mod 8) octets
+ each with value 8-(||M|| mod 8). The padding string PS will
+ satisfy one of the following statements:
+
+ PS = 01, if ||M|| mod 8 = 7 ;
+ PS = 02 02, if ||M|| mod 8 = 6 ;
+ ...
+ PS = 08 08 08 08 08 08 08 08, if ||M|| mod 8 = 0.
+
+
+
+
+Kaliski Informational [Page 12]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The length in octets of the encoded message will be a multiple
+ of eight and it will be possible to recover the message M
+ unambiguously from the encoded message. (This padding rule is
+ taken from RFC 1423 [3].)
+
+ 5. Encrypt the encoded message EM with the underlying block cipher
+ (DES or RC2) in cipher block chaining mode under the encryption
+ key K with initialization vector IV to produce the ciphertext
+ C. For DES, the key K shall be considered as a 64-bit encoding
+ of a 56-bit DES key with parity bits ignored (see [9]). For
+ RC2, the "effective key bits" shall be 64 bits.
+
+ 6. Output the ciphertext C.
+
+ The salt S and the iteration count c may be conveyed to the party
+ performing decryption in an AlgorithmIdentifier value (see Appendix
+ A.3).
+
+6.1.2 Decryption Operation
+
+ The decryption operation for PBES1 consists of the following steps,
+ which decrypt a ciphertext C under a password P to recover a message
+ M:
+
+ 1. Obtain the eight-octet salt S and the iteration count c.
+
+ 2. Apply the PBKDF1 key derivation function (Section 5.1) to the
+ password P, the salt S, and the iteration count c to produce a
+ derived key DK of length 16 octets:
+
+ DK = PBKDF1 (P, S, c, 16)
+
+ 3. Separate the derived key DK into an encryption key K consisting
+ of the first eight octets of DK and an initialization vector IV
+ consisting of the next eight octets:
+
+ K = DK<0..7> ,
+ IV = DK<8..15> .
+
+ 4. Decrypt the ciphertext C with the underlying block cipher (DES
+ or RC2) in cipher block chaining mode under the encryption key
+ K with initialization vector IV to recover an encoded message
+ EM. If the length in octets of the ciphertext C is not a
+ multiple of eight, output "decryption error" and stop.
+
+ 5. Separate the encoded message EM into a message M and a padding
+ string PS:
+
+
+
+
+Kaliski Informational [Page 13]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ EM = M || PS ,
+
+ where the padding string PS consists of some number psLen
+ octets each with value psLen, where psLen is between 1 and 8.
+ If it is not possible to separate the encoded message EM in
+ this manner, output "decryption error" and stop.
+
+ 6. Output the recovered message M.
+
+6.2 PBES2
+
+ PBES2 combines a password-based key derivation function, which shall
+ be PBKDF2 (Section 5.2) for this version of PKCS #5, with an
+ underlying encryption scheme (see Appendix B.2 for examples). The key
+ length and any other parameters for the underlying encryption scheme
+ depend on the scheme.
+
+ PBES2 is recommended for new applications.
+
+6.2.1 Encryption Operation
+
+ The encryption operation for PBES2 consists of the following steps,
+ which encrypt a message M under a password P to produce a ciphertext
+ C, applying a selected key derivation function KDF and a selected
+ underlying encryption scheme:
+
+ 1. Select a salt S and an iteration count c, as outlined in
+ Section 4.
+
+ 2. Select the length in octets, dkLen, for the derived key for the
+ underlying encryption scheme.
+
+ 3. Apply the selected key derivation function to the password P,
+ the salt S, and the iteration count c to produce a derived key
+ DK of length dkLen octets:
+
+ DK = KDF (P, S, c, dkLen) .
+
+ 4. Encrypt the message M with the underlying encryption scheme
+ under the derived key DK to produce a ciphertext C. (This step
+ may involve selection of parameters such as an initialization
+ vector and padding, depending on the underlying scheme.)
+
+ 5. Output the ciphertext C.
+
+
+
+
+
+
+
+Kaliski Informational [Page 14]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The salt S, the iteration count c, the key length dkLen, and
+ identifiers for the key derivation function and the underlying
+ encryption scheme may be conveyed to the party performing decryption
+ in an AlgorithmIdentifier value (see Appendix A.4).
+
+6.2.2 Decryption Operation
+
+ The decryption operation for PBES2 consists of the following steps,
+ which decrypt a ciphertext C under a password P to recover a message
+ M:
+
+ 1. Obtain the salt S for the operation.
+
+ 2. Obtain the iteration count c for the key derivation function.
+
+ 3. Obtain the key length in octets, dkLen, for the derived key for
+ the underlying encryption scheme.
+
+ 4. Apply the selected key derivation function to the password P,
+ the salt S, and the iteration count c to produce a derived key
+ DK of length dkLen octets:
+
+ DK = KDF (P, S, c, dkLen) .
+
+ 5. Decrypt the ciphertext C with the underlying encryption scheme
+ under the derived key DK to recover a message M. If the
+ decryption function outputs "decryption error," then output
+ "decryption error" and stop.
+
+ 6. Output the recovered message M.
+
+7. Message Authentication Schemes
+
+ A message authentication scheme consists of a MAC (message
+ authentication code) generation operation and a MAC verification
+ operation, where the MAC generation operation produces a message
+ authentication code from a message under a key, and the MAC
+ verification operation verifies the message authentication code under
+ the same key. In a password-based message authentication scheme, the
+ key is a password.
+
+ One scheme is specified in this section: PBMAC1.
+
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 15]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+7.1 PBMAC1
+
+ PBMAC1 combines a password-based key derivation function, which shall
+ be PBKDF2 (Section 5.2) for this version of PKCS #5, with an
+ underlying message authentication scheme (see Appendix B.3 for an
+ example). The key length and any other parameters for the underlying
+ message authentication scheme depend on the scheme.
+
+7.1.1 MAC Generation
+
+ The MAC generation operation for PBMAC1 consists of the following
+ steps, which process a message M under a password P to generate a
+ message authentication code T, applying a selected key derivation
+ function KDF and a selected underlying message authentication scheme:
+
+ 1. Select a salt S and an iteration count c, as outlined in
+ Section 4.
+
+ 2. Select a key length in octets, dkLen, for the derived key for
+ the underlying message authentication function.
+
+ 3. Apply the selected key derivation function to the password P,
+ the salt S, and the iteration count c to produce a derived key
+ DK of length dkLen octets:
+
+ DK = KDF (P, S, c, dkLen) .
+
+ 4. Process the message M with the underlying message
+ authentication scheme under the derived key DK to generate a
+ message authentication code T.
+
+ 5. Output the message authentication code T.
+
+ The salt S, the iteration count c, the key length dkLen, and
+ identifiers for the key derivation function and underlying message
+ authentication scheme may be conveyed to the party performing
+ verification in an AlgorithmIdentifier value (see Appendix A.5).
+
+7.1.2 MAC Verification
+
+ The MAC verification operation for PBMAC1 consists of the following
+ steps, which process a message M under a password P to verify a
+ message authentication code T:
+
+ 1. Obtain the salt S and the iteration count c.
+
+ 2. Obtain the key length in octets, dkLen, for the derived key for
+ the underlying message authentication scheme.
+
+
+
+Kaliski Informational [Page 16]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ 3. Apply the selected key derivation function to the password P,
+ the salt S, and the iteration count c to produce a derived key
+ DK of length dkLen octets:
+
+ DK = KDF (P, S, c, dkLen) .
+
+ 4. Process the message M with the underlying message
+ authentication scheme under the derived key DK to verify the
+ message authentication code T.
+
+ 5. If the message authentication code verifies, output "correct";
+ else output "incorrect."
+
+8. Security Considerations
+
+ Password-based cryptography is generally limited in the security that
+ it can provide, particularly for methods such as those defined in
+ this document where off-line password search is possible. While the
+ use of salt and iteration count can increase the complexity of attack
+ (see Section 4 for recommendations), it is essential that passwords
+ are selected well, and relevant guidelines (e.g., [17]) should be
+ taken into account. It is also important that passwords be protected
+ well if stored.
+
+ In general, different keys should be derived from a password for
+ different uses to minimize the possibility of unintended
+ interactions. For password-based encryption with a single algorithm,
+ a random salt is sufficient to ensure that different keys will be
+ produced. In certain other situations, as outlined in Section 4, a
+ structured salt is necessary. The recommendations in Section 4 should
+ thus be taken into account when selecting the salt value.
+
+9. Author's Address
+
+ Burt Kaliski
+ RSA Laboratories
+ 20 Crosby Drive
+ Bedford, MA 01730 USA
+
+ EMail: bkaliski@rsasecurity.com
+
+
+
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 17]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+APPENDICES
+
+A. ASN.1 Syntax
+
+ This section defines ASN.1 syntax for the key derivation functions,
+ the encryption schemes, the message authentication scheme, and
+ supporting techniques. The intended application of these definitions
+ includes PKCS #8 and other syntax for key management, encrypted data,
+ and integrity-protected data. (Various aspects of ASN.1 are specified
+ in several ISO/IEC standards [9][10][11][12][13][14].)
+
+ The object identifier pkcs-5 identifies the arc of the OID tree from
+ which the PKCS #5-specific OIDs in this section are derived:
+
+ rsadsi OBJECT IDENTIFIER ::= {iso(1) member-body(2) us(840) 113549}
+ pkcs OBJECT IDENTIFIER ::= {rsadsi 1}
+ pkcs-5 OBJECT IDENTIFIER ::= {pkcs 5}
+
+A.1 PBKDF1
+
+ No object identifier is given for PBKDF1, as the object identifiers
+ for PBES1 are sufficient for existing applications and PBKDF2 is
+ recommended for new applications.
+
+A.2 PBKDF2
+
+ The object identifier id-PBKDF2 identifies the PBKDF2 key derivation
+ function (Section 5.2).
+
+ id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type PBKDF2-params:
+
+ PBKDF2-params ::= SEQUENCE {
+ salt CHOICE {
+ specified OCTET STRING,
+ otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ },
+ iterationCount INTEGER (1..MAX),
+ keyLength INTEGER (1..MAX) OPTIONAL,
+ prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
+ algid-hmacWithSHA1 }
+
+ The fields of type PKDF2-params have the following meanings:
+
+
+
+
+
+
+Kaliski Informational [Page 18]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ - salt specifies the salt value, or the source of the salt value.
+ It shall either be an octet string or an algorithm ID with an OID
+ in the set PBKDF2-SaltSources, which is reserved for future
+ versions of PKCS #5.
+
+ The salt-source approach is intended to indicate how the salt
+ value is to be generated as a function of parameters in the
+ algorithm ID, application data, or both. For instance, it may
+ indicate that the salt value is produced from the encoding of a
+ structure that specifies detailed information about the derived
+ key as suggested in Section 4.1. Some of the information may be
+ carried elsewhere, e.g., in the encryption algorithm ID. However,
+ such facilities are deferred to a future version of PKCS #5.
+
+ In this version, an application may achieve the benefits mentioned
+ in Section 4.1 by choosing a particular interpretation of the salt
+ value in the specified alternative.
+
+ PBKDF2-SaltSources ALGORITHM-IDENTIFIER ::= { ... }
+
+ - iterationCount specifies the iteration count. The maximum
+ iteration count allowed depends on the implementation. It is
+ expected that implementation profiles may further constrain the
+ bounds.
+
+ - keyLength, an optional field, is the length in octets of the
+ derived key. The maximum key length allowed depends on the
+ implementation; it is expected that implementation profiles may
+ further constrain the bounds. The field is provided for
+ convenience only; the key length is not cryptographically
+ protected. If there is concern about interaction between
+ operations with different key lengths for a given salt (see
+ Section 4.1), the salt should distinguish among the different key
+ lengths.
+
+ - prf identifies the underlying pseudorandom function. It shall be
+ an algorithm ID with an OID in the set PBKDF2-PRFs, which for this
+ version of PKCS #5 shall consist of id-hmacWithSHA1 (see Appendix
+ B.1.1) and any other OIDs defined by the application.
+
+ PBKDF2-PRFs ALGORITHM-IDENTIFIER ::=
+ { {NULL IDENTIFIED BY id-hmacWithSHA1}, ... }
+
+ The default pseudorandom function is HMAC-SHA-1:
+
+ algid-hmacWithSHA1 AlgorithmIdentifier {{PBKDF2-PRFs}} ::=
+ {algorithm id-hmacWithSHA1, parameters NULL : NULL}
+
+
+
+
+Kaliski Informational [Page 19]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+A.3 PBES1
+
+ Different object identifiers identify the PBES1 encryption scheme
+ (Section 6.1) according to the underlying hash function in the key
+ derivation function and the underlying block cipher, as summarized in
+ the following table:
+
+ Hash Function Block Cipher OID
+ MD2 DES pkcs-5.1
+ MD2 RC2 pkcs-5.4
+ MD5 DES pkcs-5.3
+ MD5 RC2 pkcs-5.6
+ SHA-1 DES pkcs-5.10
+ SHA-1 RC2 pkcs-5.11
+
+ pbeWithMD2AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 1}
+ pbeWithMD2AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 4}
+ pbeWithMD5AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 3}
+ pbeWithMD5AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 6}
+ pbeWithSHA1AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 10}
+ pbeWithSHA1AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 11}
+
+ For each OID, the parameters field associated with the OID in an
+ AlgorithmIdentifier shall have type PBEParameter:
+
+ PBEParameter ::= SEQUENCE {
+ salt OCTET STRING (SIZE(8)),
+ iterationCount INTEGER }
+
+ The fields of type PBEParameter have the following meanings:
+
+ - salt specifies the salt value, an eight-octet string.
+
+ - iterationCount specifies the iteration count.
+
+A.4 PBES2
+
+ The object identifier id-PBES2 identifies the PBES2 encryption scheme
+ (Section 6.2).
+
+ id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type PBES2-params:
+
+ PBES2-params ::= SEQUENCE {
+ keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} }
+
+
+
+Kaliski Informational [Page 20]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The fields of type PBES2-params have the following meanings:
+
+ - keyDerivationFunc identifies the underlying key derivation
+ function. It shall be an algorithm ID with an OID in the set
+ PBES2-KDFs, which for this version of PKCS #5 shall consist of
+ id-PBKDF2 (Appendix A.2).
+
+ PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+ { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+
+ - encryptionScheme identifies the underlying encryption scheme. It
+ shall be an algorithm ID with an OID in the set PBES2-Encs, whose
+ definition is left to the application. Example underlying
+ encryption schemes are given in Appendix B.2.
+
+ PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
+
+A.5 PBMAC1
+
+ The object identifier id-PBMAC1 identifies the PBMAC1 message
+ authentication scheme (Section 7.1).
+
+ id-PBMAC1 OBJECT IDENTIFIER ::= {pkcs-5 14}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type PBMAC1-params:
+
+ PBMAC1-params ::= SEQUENCE {
+ keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}},
+ messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}} }
+
+ The keyDerivationFunc field has the same meaning as the corresponding
+ field of PBES2-params (Appendix A.4) except that the set of OIDs is
+ PBMAC1-KDFs.
+
+ PBMAC1-KDFs ALGORITHM-IDENTIFIER ::=
+ { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+
+ The messageAuthScheme field identifies the underlying message
+ authentication scheme. It shall be an algorithm ID with an OID in the
+ set PBMAC1-MACs, whose definition is left to the application. Example
+ underlying encryption schemes are given in Appendix B.3.
+
+ PBMAC1-MACs ALGORITHM-IDENTIFIER ::= { ... }
+
+
+
+
+
+
+
+Kaliski Informational [Page 21]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+B. Supporting Techniques
+
+ This section gives several examples of underlying functions and
+ schemes supporting the password-based schemes in Sections 5, 6 and 7.
+
+ While these supporting techniques are appropriate for applications to
+ implement, none of them is required to be implemented. It is
+ expected, however, that profiles for PKCS #5 will be developed that
+ specify particular supporting techniques.
+
+ This section also gives object identifiers for the supporting
+ techniques. The object identifiers digestAlgorithm and
+ encryptionAlgorithm identify the arcs from which certain algorithm
+ OIDs referenced in this section are derived:
+
+ digestAlgorithm OBJECT IDENTIFIER ::= {rsadsi 2}
+ encryptionAlgorithm OBJECT IDENTIFIER ::= {rsadsi 3}
+
+B.1 Pseudorandom functions
+
+ An example pseudorandom function for PBKDF2 (Section 5.2) is HMAC-
+ SHA-1.
+
+B.1.1 HMAC-SHA-1
+
+ HMAC-SHA-1 is the pseudorandom function corresponding to the HMAC
+ message authentication code [7] based on the SHA-1 hash function
+ [18]. The pseudorandom function is the same function by which the
+ message authentication code is computed, with a full-length output.
+ (The first argument to the pseudorandom function PRF serves as HMAC's
+ "key," and the second serves as HMAC's "text." In the case of PBKDF2,
+ the "key" is thus the password and the "text" is the salt.) HMAC-
+ SHA-1 has a variable key length and a 20-octet (160-bit) output
+ value.
+
+ Although the length of the key to HMAC-SHA-1 is essentially
+ unbounded, the effective search space for pseudorandom function
+ outputs may be limited by the structure of the function. In
+ particular, when the key is longer than 512 bits, HMAC-SHA-1 will
+ first hash it to 160 bits. Thus, even if a long derived key
+ consisting of several pseudorandom function outputs is produced from
+ a key, the effective search space for the derived key will be at most
+ 160 bits. Although the specific limitation for other key sizes
+ depends on details of the HMAC construction, one should assume, to be
+ conservative, that the effective search space is limited to 160 bits
+ for other key sizes as well.
+
+
+
+
+
+Kaliski Informational [Page 22]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ (The 160-bit limitation should not generally pose a practical
+ limitation in the case of password-based cryptography, since the
+ search space for a password is unlikely to be greater than 160 bits.)
+
+ The object identifier id-hmacWithSHA1 identifies the HMAC-SHA-1
+ pseudorandom function:
+
+ id-hmacWithSHA1 OBJECT IDENTIFIER ::= {digestAlgorithm 7}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type NULL. This object identifier is
+ employed in the object set PBKDF2-PRFs (Appendix A.2).
+
+ Note. Although HMAC-SHA-1 was designed as a message authentication
+ code, its proof of security is readily modified to accommodate
+ requirements for a pseudorandom function, under stronger assumptions.
+
+ A hash function may also meet the requirements of a pseudorandom
+ function under certain assumptions. For instance, the direct
+ application of a hash function to to the concatenation of the "key"
+ and the "text" may be appropriate, provided that "text" has
+ appropriate structure to prevent certain attacks. HMAC-SHA-1 is
+ preferable, however, because it treats "key" and "text" as separate
+ arguments and does not require "text" to have any structure.
+
+B.2 Encryption Schemes
+
+ Example pseudorandom functions for PBES2 (Section 6.2) are DES-CBC-
+ Pad, DES-EDE2-CBC-Pad, RC2-CBC-Pad, and RC5-CBC-Pad.
+
+ The object identifiers given in this section are intended to be
+ employed in the object set PBES2-Encs (Appendix A.4).
+
+B.2.1 DES-CBC-Pad
+
+ DES-CBC-Pad is single-key DES [15] in CBC mode [16] with the RFC 1423
+ padding operation (see Section 6.1.1). DES-CBC-Pad has an eight-octet
+ encryption key and an eight-octet initialization vector. The key is
+ considered as a 64-bit encoding of a 56-bit DES key with parity bits
+ ignored.
+
+ The object identifier desCBC (defined in the NIST/OSI Implementors'
+ Workshop agreements) identifies the DES-CBC-Pad encryption scheme:
+
+ desCBC OBJECT IDENTIFIER ::=
+ {iso(1) identified-organization(3) oiw(14) secsig(3)
+ algorithms(2) 7}
+
+
+
+
+Kaliski Informational [Page 23]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type OCTET STRING (SIZE(8)),
+ specifying the initialization vector for CBC mode.
+
+B.2.2 DES-EDE3-CBC-Pad
+
+ DES-EDE3-CBC-Pad is three-key triple-DES in CBC mode [1] with the RFC
+ 1423 padding operation. DES-EDE3-CBC-Pad has a 24-octet encryption
+ key and an eight-octet initialization vector. The key is considered
+ as the concatenation of three eight-octet keys, each of which is a
+ 64-bit encoding of a 56-bit DES key with parity bits ignored.
+
+ The object identifier des-EDE3-CBC identifies the DES-EDE3-CBC-Pad
+ encryption scheme:
+
+ des-EDE3-CBC OBJECT IDENTIFIER ::= {encryptionAlgorithm 7}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type OCTET STRING (SIZE(8)),
+ specifying the initialization vector for CBC mode.
+
+ Note. An OID for DES-EDE3-CBC without padding is given in ANSI X9.52
+ [1]; the one given here is preferred since it specifies padding.
+
+B.2.3 RC2-CBC-Pad
+
+ RC2-CBC-Pad is the RC2(tm) encryption algorithm [21] in CBC mode with
+ the RFC 1423 padding operation. RC2-CBC-Pad has a variable key
+ length, from one to 128 octets, a separate "effective key bits"
+ parameter from one to 1024 bits that limits the effective search
+ space independent of the key length, and an eight-octet
+ initialization vector.
+
+ The object identifier rc2CBC identifies the RC2-CBC-Pad encryption
+ scheme:
+
+ rc2CBC OBJECT IDENTIFIER ::= {encryptionAlgorithm 2}
+
+ The parameters field associated with OID in an AlgorithmIdentifier
+ shall have type RC2-CBC-Parameter:
+
+ RC2-CBC-Parameter ::= SEQUENCE {
+ rc2ParameterVersion INTEGER OPTIONAL,
+ iv OCTET STRING (SIZE(8)) }
+
+
+
+
+
+
+
+Kaliski Informational [Page 24]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The fields of type RC2-CBCParameter have the following meanings:
+
+ - rc2ParameterVersion is a proprietary RSA Security Inc. encoding of
+ the "effective key bits" for RC2. The following encodings are
+ defined:
+
+ Effective Key Bits Encoding
+ 40 160
+ 64 120
+ 128 58
+ b >= 256 b
+
+ If the rc2ParameterVersion field is omitted, the "effective key bits"
+ defaults to 32. (This is for backward compatibility with certain very
+ old implementations.)
+
+ - iv is the eight-octet initialization vector.
+
+B.2.4 RC5-CBC-Pad
+
+ RC5-CBC-Pad is the RC5(tm) encryption algorithm [20] in CBC mode with
+ a generalization of the RFC 1423 padding operation. This scheme is
+ fully specified in [2]. RC5-CBC-Pad has a variable key length, from 0
+ to 256 octets, and supports both a 64-bit block size and a 128-bit
+ block size. For the former, it has an eight-octet initialization
+ vector, and for the latter, a 16-octet initialization vector.
+ RC5-CBC-Pad also has a variable number of "rounds" in the encryption
+ operation, from 8 to 127.
+
+ Note: The generalization of the padding operation is as follows. For
+ RC5 with a 64-bit block size, the padding string is as defined in RFC
+ 1423. For RC5 with a 128-bit block size, the padding string consists
+ of 16-(||M|| mod 16) octets each with value 16-(||M|| mod 16).
+
+ The object identifier rc5-CBC-PAD [2] identifies RC5-CBC-Pad
+ encryption scheme:
+
+ rc5-CBC-PAD OBJECT IDENTIFIER ::= {encryptionAlgorithm 9}
+
+ The parameters field associated with this OID in an
+ AlgorithmIdentifier shall have type RC5-CBC-Parameters:
+
+ RC5-CBC-Parameters ::= SEQUENCE {
+ version INTEGER {v1-0(16)} (v1-0),
+ rounds INTEGER (8..127),
+ blockSizeInBits INTEGER (64 | 128),
+ iv OCTET STRING OPTIONAL }
+
+
+
+
+Kaliski Informational [Page 25]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ The fields of type RC5-CBC-Parameters have the following meanings:
+
+ - version is the version of the algorithm, which shall be v1-0.
+
+ - rounds is the number of rounds in the encryption operation, which
+ shall be between 8 and 127.
+
+ - blockSizeInBits is the block size in bits, which shall be 64 or
+ 128.
+
+ - iv is the initialization vector, an eight-octet string for 64-bit
+ RC5 and a 16-octet string for 128-bit RC5. The default is a string
+ of the appropriate length consisting of zero octets.
+
+B.3 Message Authentication Schemes
+
+ An example message authentication scheme for PBMAC1 (Section 7.1) is
+ HMAC-SHA-1.
+
+B.3.1 HMAC-SHA-1
+
+ HMAC-SHA-1 is the HMAC message authentication scheme [7] based on the
+ SHA-1 hash function [18]. HMAC-SHA-1 has a variable key length and a
+ 20-octet (160-bit) message authentication code.
+
+ The object identifier id-hmacWithSHA1 (see Appendix B.1.1) identifies
+ the HMAC-SHA-1 message authentication scheme. (The object identifier
+ is the same for both the pseudorandom function and the message
+ authentication scheme; the distinction is to be understood by
+ context.) This object identifier is intended to be employed in the
+ object set PBMAC1-Macs (Appendix A.5).
+
+C. ASN.1 Module
+
+ For reference purposes, the ASN.1 syntax in the preceding sections is
+ presented as an ASN.1 module here.
+
+ -- PKCS #5 v2.0 ASN.1 Module
+ -- Revised March 25, 1999
+
+ -- This module has been checked for conformance with the
+ -- ASN.1 standard by the OSS ASN.1 Tools
+
+ PKCS5v2-0 {iso(1) member-body(2) us(840) rsadsi(113549)
+ pkcs(1) pkcs-5(5) modules(16) pkcs5v2-0(1)}
+
+ DEFINITIONS ::= BEGIN
+
+
+
+
+Kaliski Informational [Page 26]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ -- Basic object identifiers
+
+ rsadsi OBJECT IDENTIFIER ::= {iso(1) member-body(2) us(840) 113549}
+ pkcs OBJECT IDENTIFIER ::= {rsadsi 1}
+
+ pkcs-5 OBJECT IDENTIFIER ::= {pkcs 5}
+
+ -- Basic types and classes
+
+ AlgorithmIdentifier { ALGORITHM-IDENTIFIER:InfoObjectSet } ::=
+ SEQUENCE {
+ algorithm ALGORITHM-IDENTIFIER.&id({InfoObjectSet}),
+ parameters ALGORITHM-IDENTIFIER.&Type({InfoObjectSet}
+ {@algorithm}) OPTIONAL
+ }
+
+ ALGORITHM-IDENTIFIER ::= TYPE-IDENTIFIER
+
+ -- PBKDF2
+
+ PBKDF2Algorithms ALGORITHM-IDENTIFIER ::=
+ { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ...}
+
+ id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12}
+
+ algid-hmacWithSHA1 AlgorithmIdentifier {{PBKDF2-PRFs}} ::=
+ {algorithm id-hmacWithSHA1, parameters NULL : NULL}
+
+ PBKDF2-params ::= SEQUENCE {
+ salt CHOICE {
+ specified OCTET STRING,
+ otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ },
+ iterationCount INTEGER (1..MAX),
+ keyLength INTEGER (1..MAX) OPTIONAL,
+ prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
+ algid-hmacWithSHA1
+ }
+
+ PBKDF2-SaltSources ALGORITHM-IDENTIFIER ::= { ... }
+
+ PBKDF2-PRFs ALGORITHM-IDENTIFIER ::=
+ { {NULL IDENTIFIED BY id-hmacWithSHA1}, ... }
+
+ -- PBES1
+
+
+ PBES1Algorithms ALGORITHM-IDENTIFIER ::= {
+
+
+
+Kaliski Informational [Page 27]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ {PBEParameter IDENTIFIED BY pbeWithMD2AndDES-CBC} |
+ {PBEParameter IDENTIFIED BY pbeWithMD2AndRC2-CBC} |
+ {PBEParameter IDENTIFIED BY pbeWithMD5AndDES-CBC} |
+ {PBEParameter IDENTIFIED BY pbeWithMD5AndRC2-CBC} |
+ {PBEParameter IDENTIFIED BY pbeWithSHA1AndDES-CBC} |
+ {PBEParameter IDENTIFIED BY pbeWithSHA1AndRC2-CBC},
+ ...
+ }
+
+ pbeWithMD2AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 1}
+ pbeWithMD2AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 4}
+ pbeWithMD5AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 3}
+ pbeWithMD5AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 6}
+ pbeWithSHA1AndDES-CBC OBJECT IDENTIFIER ::= {pkcs-5 10}
+ pbeWithSHA1AndRC2-CBC OBJECT IDENTIFIER ::= {pkcs-5 11}
+
+ PBEParameter ::= SEQUENCE {
+ salt OCTET STRING (SIZE(8)),
+ iterationCount INTEGER
+ }
+
+ -- PBES2
+
+ PBES2Algorithms ALGORITHM-IDENTIFIER ::=
+ { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
+
+ id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
+
+ PBES2-params ::= SEQUENCE {
+ keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
+ }
+
+ PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+ { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+
+ PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
+
+ -- PBMAC1
+
+ PBMAC1Algorithms ALGORITHM-IDENTIFIER ::=
+ { {PBMAC1-params IDENTIFIED BY id-PBMAC1}, ...}
+
+ id-PBMAC1 OBJECT IDENTIFIER ::= {pkcs-5 14}
+
+ PBMAC1-params ::= SEQUENCE {
+ keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}},
+ messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}}
+
+
+
+Kaliski Informational [Page 28]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ }
+
+ PBMAC1-KDFs ALGORITHM-IDENTIFIER ::=
+ { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+
+ PBMAC1-MACs ALGORITHM-IDENTIFIER ::= { ... }
+
+ -- Supporting techniques
+
+ digestAlgorithm OBJECT IDENTIFIER ::= {rsadsi 2}
+ encryptionAlgorithm OBJECT IDENTIFIER ::= {rsadsi 3}
+
+ SupportingAlgorithms ALGORITHM-IDENTIFIER ::= {
+ {NULL IDENTIFIED BY id-hmacWithSHA1} |
+ {OCTET STRING (SIZE(8)) IDENTIFIED BY desCBC} |
+ {OCTET STRING (SIZE(8)) IDENTIFIED BY des-EDE3-CBC} |
+ {RC2-CBC-Parameter IDENTIFIED BY rc2CBC} |
+ {RC5-CBC-Parameters IDENTIFIED BY rc5-CBC-PAD},
+ ...
+ }
+
+ id-hmacWithSHA1 OBJECT IDENTIFIER ::= {digestAlgorithm 7}
+
+ desCBC OBJECT IDENTIFIER ::=
+ {iso(1) identified-organization(3) oiw(14) secsig(3)
+ algorithms(2) 7} -- from OIW
+
+ des-EDE3-CBC OBJECT IDENTIFIER ::= {encryptionAlgorithm 7}
+
+ rc2CBC OBJECT IDENTIFIER ::= {encryptionAlgorithm 2}
+
+ RC2-CBC-Parameter ::= SEQUENCE {
+ rc2ParameterVersion INTEGER OPTIONAL,
+ iv OCTET STRING (SIZE(8))
+ }
+
+ rc5-CBC-PAD OBJECT IDENTIFIER ::= {encryptionAlgorithm 9}
+
+ RC5-CBC-Parameters ::= SEQUENCE {
+ version INTEGER {v1-0(16)} (v1-0),
+ rounds INTEGER (8..127),
+ blockSizeInBits INTEGER (64 | 128),
+ iv OCTET STRING OPTIONAL
+ }
+
+ END
+
+
+
+
+
+Kaliski Informational [Page 29]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+Intellectual Property Considerations
+
+ RSA Security makes no patent claims on the general constructions
+ described in this document, although specific underlying techniques
+ may be covered. Among the underlying techniques, the RC5 encryption
+ algorithm (Appendix B.2.4) is protected by U.S. Patents 5,724,428
+ [22] and 5,835,600 [23].
+
+ RC2 and RC5 are trademarks of RSA Security.
+
+ License to copy this document is granted provided that it is
+ identified as RSA Security Inc. Public-Key Cryptography Standards
+ (PKCS) in all material mentioning or referencing this document.
+
+ RSA Security makes no representations regarding intellectual property
+ claims by other parties. Such determination is the responsibility of
+ the user.
+
+Revision history
+
+ Versions 1.0-1.3
+
+ Versions 1.0-1.3 were distributed to participants in RSA Data
+ Security Inc.'s Public-Key Cryptography Standards meetings in
+ February and March 1991.
+
+ Version 1.4
+
+ Version 1.4 was part of the June 3, 1991 initial public release of
+ PKCS. Version 1.4 was published as NIST/OSI Implementors' Workshop
+ document SEC-SIG-91-20.
+
+ Version 1.5
+
+ Version 1.5 incorporated several editorial changes, including
+ updates to the references and the addition of a revision history.
+
+ Version 2.0
+
+ Version 2.0 incorporates major editorial changes in terms of the
+ document structure, and introduces the PBES2 encryption scheme,
+ the PBMAC1 message authentication scheme, and independent
+ password-based key derivation functions. This version continues to
+ support the encryption process in version 1.5.
+
+
+
+
+
+
+
+Kaliski Informational [Page 30]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+References
+
+ [1] American National Standard X9.52 - 1998, Triple Data Encryption
+ Algorithm Modes of Operation. Working draft, Accredited
+ Standards Committee X9, July 27, 1998.
+
+ [2] Baldwin, R. and R. Rivest, "The RC5, RC5-CBC, RC5-CBC-Pad, and
+ RC5-CTS Algorithms", RFC 2040, October 1996.
+
+ [3] Balenson, D., "Privacy Enhancement for Internet Electronic Mail:
+ Part III: Algorithms, Modes, and Identifiers", RFC 1423,
+ February 1993.
+
+ [4] S.M. Bellovin and M. Merritt. Encrypted key exchange:
+ Password-based protocols secure against dictionary attacks. In
+ Proceedings of the 1992 IEEE Computer Society Conference on
+ Research in Security and Privacy, pages 72-84, IEEE Computer
+ Society, 1992.
+
+ [5] D. Jablon. Strong password-only authenticated key exchange. ACM
+ Computer Communications Review, October 1996.
+
+ [6] Kaliski, B., "The MD2 Message-Digest Algorithm", RFC 1319, April
+ 1992.
+
+ [7] Krawczyk, H., Bellare, M. and R. Canetti, "HMAC: Keyed-Hashing
+ for Message Authentication", RFC 2104, February 1997.
+
+ [8] Robert Morris and Ken Thompson. Password security: A case
+ history. Communications of the ACM, 22(11):594-597, November
+ 1979.
+
+ [9] ISO/IEC 8824-1:1995: Information technology - Abstract Syntax
+ Notation One (ASN.1) - Specification of basic notation. 1995.
+
+ [10] ISO/IEC 8824-1:1995/Amd.1:1995 Information technology - Abstract
+ Syntax Notation One (ASN.1) - Specification of basic notation -
+ Amendment 1 - Rules of extensibility. 1995.
+
+ [11] ISO/IEC 8824-2:1995 Information technology - Abstract Syntax
+ Notation One (ASN.1) - Information object specification. 1995.
+
+ [12] ISO/IEC 8824-2:1995/Amd.1:1995 Information technology - Abstract
+ Syntax Notation One (ASN.1) - Information object specification -
+ Amendment 1 - Rules of extensibility. 1995.
+
+ [13] ISO/IEC 8824-3:1995 Information technology - Abstract Syntax
+ Notation One (ASN.1) - Constraint specification. 1995.
+
+
+
+Kaliski Informational [Page 31]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+ [14] ISO/IEC 8824-4:1995 Information technology - Abstract Syntax
+ Notation One (ASN.1) - Parameterization of ASN.1 specifications.
+ 1995.
+
+ [15] National Institute of Standards and Technology (NIST). FIPS PUB
+ 46-2: Data Encryption Standard. December 30, 1993.
+
+ [16] National Institute of Standards and Technology (NIST). FIPS PUB
+ 81: DES Modes of Operation. December 2, 1980.
+
+ [17] National Institute of Standards and Technology (NIST). FIPS PUB
+ 112: Password Usage. May 30, 1985.
+
+ [18] National Institute of Standards and Technology (NIST). FIPS PUB
+ 180-1: Secure Hash Standard. April 1994.
+
+ [19] Rivest, R., "The MD5 Message-Digest Algorithm", RFC 1321, April
+ 1992.
+
+ [20] R.L. Rivest. The RC5 encryption algorithm. In Proceedings of the
+ Second International Workshop on Fast Software Encryption, pages
+ 86-96, Springer-Verlag, 1994.
+
+ [21] Rivest, R., "A Description of the RC2(r) Encryption Algorithm",
+ RFC 2268, March 1998.
+
+ [22] R.L. Rivest. Block-Encryption Algorithm with Data-Dependent
+ Rotations. U.S. Patent No. 5,724,428, March 3, 1998.
+
+ [23] R.L. Rivest. Block Encryption Algorithm with Data-Dependent
+ Rotations. U.S. Patent No. 5,835,600, November 10, 1998.
+
+ [24] RSA Laboratories. PKCS #5: Password-Based Encryption Standard.
+ Version 1.5, November 1993.
+
+ [25] RSA Laboratories. PKCS #8: Private-Key Information Syntax
+ Standard. Version 1.2, November 1993.
+
+ [26] T. Wu. The Secure Remote Password protocol. In Proceedings of
+ the 1998 Internet Society Network and Distributed System
+ Security Symposium, pages 97-111, Internet Society, 1998.
+
+ [27] Yergeau, F., "UTF-8, a transformation format of ISO 10646", RFC
+ 2279, January 1998.
+
+
+
+
+
+
+
+Kaliski Informational [Page 32]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+Contact Information & About PKCS
+
+ The Public-Key Cryptography Standards are specifications produced by
+ RSA Laboratories in cooperation with secure systems developers
+ worldwide for the purpose of accelerating the deployment of public-
+ key cryptography. First published in 1991 as a result of meetings
+ with a small group of early adopters of public-key technology, the
+ PKCS documents have become widely referenced and implemented.
+ Contributions from the PKCS series have become part of many formal
+ and de facto standards, including ANSI X9 documents, PKIX, SET,
+ S/MIME, and SSL.
+
+ Further development of PKCS occurs through mailing list discussions
+ and occasional workshops, and suggestions for improvement are
+ welcome. For more information, contact:
+
+ PKCS Editor
+ RSA Laboratories
+ 20 Crosby Drive
+ Bedford, MA 01730 USA
+ pkcs-editor@rsasecurity.com
+ http://www.rsalabs.com/pkcs/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 33]
+
+RFC 2898 Password-Based Cryptography September 2000
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kaliski Informational [Page 34]
+
diff --git a/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc6070.txt b/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc6070.txt
new file mode 100644
index 00000000..67cd340b
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL SCRAM/PKCS #5/rfc6070.txt
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) S. Josefsson
+Request for Comments: 6070 SJD AB
+Category: Informational January 2011
+ISSN: 2070-1721
+
+
+ PKCS #5: Password-Based Key Derivation Function 2 (PBKDF2)
+ Test Vectors
+
+Abstract
+
+ This document contains test vectors for the Public-Key Cryptography
+ Standards (PKCS) #5 Password-Based Key Derivation Function 2 (PBKDF2)
+ with the Hash-based Message Authentication Code (HMAC) Secure Hash
+ Algorithm (SHA-1) pseudorandom function.
+
+Status of This Memo
+
+ This document is not an Internet Standards Track specification; it is
+ published for informational purposes.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Not all documents
+ approved by the IESG are a candidate for any level of Internet
+ Standard; see Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc6070.
+
+Copyright Notice
+
+ Copyright (c) 2011 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+Josefsson Informational [Page 1]
+
+RFC 6070 PKCS #5 PBKDF2 Test Vectors January 2011
+
+
+Table of Contents
+
+ 1. Introduction ....................................................2
+ 2. PBKDF2 HMAC-SHA1 Test Vectors ...................................2
+ 3. Acknowledgements ................................................4
+ 4. Copying Conditions ..............................................4
+ 5. Security Considerations .........................................4
+ 6. References ......................................................4
+ 6.1. Normative References .......................................4
+ 6.2. Informative References .....................................5
+
+1. Introduction
+
+ The Public-Key Cryptography Standards (PKCS) #5 [RFC2898] Password-
+ Based Key Derivation Function 2 (PBKDF2) is used by several protocols
+ to derive encryption keys from a password.
+
+ For example, Salted Challenge Response Authentication Mechanism
+ (SCRAM) [RFC5802] uses PBKDF2 with Hash-based Message Authentication
+ Code (HMAC) [RFC2104] and Secure Hash Algorithm (SHA-1)
+ [FIPS.180-1.1995].
+
+ Test vectors for the algorithm were not included in the original
+ specification, but are often useful for implementers. This document
+ addresses the shortcoming.
+
+2. PBKDF2 HMAC-SHA1 Test Vectors
+
+ The input strings below are encoded using ASCII [ANSI.X3-4.1986].
+ The sequence "\0" (without quotation marks) means a literal ASCII NUL
+ value (1 octet). "DK" refers to the Derived Key.
+
+ Input:
+ P = "password" (8 octets)
+ S = "salt" (4 octets)
+ c = 1
+ dkLen = 20
+
+ Output:
+ DK = 0c 60 c8 0f 96 1f 0e 71
+ f3 a9 b5 24 af 60 12 06
+ 2f e0 37 a6 (20 octets)
+
+
+
+
+
+
+
+
+
+Josefsson Informational [Page 2]
+
+RFC 6070 PKCS #5 PBKDF2 Test Vectors January 2011
+
+
+ Input:
+ P = "password" (8 octets)
+ S = "salt" (4 octets)
+ c = 2
+ dkLen = 20
+
+ Output:
+ DK = ea 6c 01 4d c7 2d 6f 8c
+ cd 1e d9 2a ce 1d 41 f0
+ d8 de 89 57 (20 octets)
+
+
+ Input:
+ P = "password" (8 octets)
+ S = "salt" (4 octets)
+ c = 4096
+ dkLen = 20
+
+ Output:
+ DK = 4b 00 79 01 b7 65 48 9a
+ be ad 49 d9 26 f7 21 d0
+ 65 a4 29 c1 (20 octets)
+
+
+ Input:
+ P = "password" (8 octets)
+ S = "salt" (4 octets)
+ c = 16777216
+ dkLen = 20
+
+ Output:
+ DK = ee fe 3d 61 cd 4d a4 e4
+ e9 94 5b 3d 6b a2 15 8c
+ 26 34 e9 84 (20 octets)
+
+
+ Input:
+ P = "passwordPASSWORDpassword" (24 octets)
+ S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" (36 octets)
+ c = 4096
+ dkLen = 25
+
+ Output:
+ DK = 3d 2e ec 4f e4 1c 84 9b
+ 80 c8 d8 36 62 c0 e4 4a
+ 8b 29 1a 96 4c f2 f0 70
+ 38 (25 octets)
+
+
+
+
+Josefsson Informational [Page 3]
+
+RFC 6070 PKCS #5 PBKDF2 Test Vectors January 2011
+
+
+ Input:
+ P = "pass\0word" (9 octets)
+ S = "sa\0lt" (5 octets)
+ c = 4096
+ dkLen = 16
+
+ Output:
+ DK = 56 fa 6a a7 55 48 09 9d
+ cc 37 d7 f0 34 25 e0 c3 (16 octets)
+
+3. Acknowledgements
+
+ Barry Brachman and Love Hornquist Astrand confirmed the test vectors
+ (using independent implementations) and pointed out a mistake in the
+ salt octet length count.
+
+4. Copying Conditions
+
+ This document should be considered a Code Component and is thus
+ available under the BSD license.
+
+5. Security Considerations
+
+ The security considerations in [RFC2898] apply. This document does
+ not introduce any new security considerations.
+
+6. References
+
+6.1. Normative References
+
+ [ANSI.X3-4.1986]
+ American National Standards Institute, "Coded Character
+ Set - 7-bit American Standard Code for Information
+ Interchange", ANSI X3.4, 1986.
+
+ [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
+ Hashing for Message Authentication", RFC 2104,
+ February 1997.
+
+ [RFC2898] Kaliski, B., "PKCS #5: Password-Based Cryptography
+ Specification Version 2.0", RFC 2898, September 2000.
+
+ [FIPS.180-1.1995]
+ National Institute of Standards and Technology, "Secure
+ Hash Standard", FIPS PUB 180-1, April 1995,
+ <http://www.itl.nist.gov/fipspubs/fip180-1.htm>.
+
+
+
+
+
+Josefsson Informational [Page 4]
+
+RFC 6070 PKCS #5 PBKDF2 Test Vectors January 2011
+
+
+6.2. Informative References
+
+ [RFC5802] Newman, C., Menon-Sen, A., Melnikov, A., and N. Williams,
+ "Salted Challenge Response Authentication Mechanism
+ (SCRAM) SASL and GSS-API Mechanisms", RFC 5802,
+ July 2010.
+
+Author's Address
+
+ Simon Josefsson
+ SJD AB
+ Hagagatan 24
+ Stockholm 113 47
+ SE
+
+ EMail: simon@josefsson.org
+ URI: http://josefsson.org/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Josefsson Informational [Page 5]
+
diff --git a/src/common/libManageSieve/doc/SASL SCRAM/draft-melnikov-scram-sha-512-01.txt b/src/common/libManageSieve/doc/SASL SCRAM/draft-melnikov-scram-sha-512-01.txt
new file mode 100644
index 00000000..307cd877
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL SCRAM/draft-melnikov-scram-sha-512-01.txt
@@ -0,0 +1,392 @@
+
+
+
+
+Network Working Group A. Melnikov, Ed.
+Internet-Draft Isode Ltd
+Intended status: Standards Track November 18, 2020
+Expires: May 22, 2021
+
+
+SCRAM-SHA-512 and SCRAM-SHA-512-PLUS Simple Authentication and Security
+ Layer (SASL) Mechanisms
+ draft-melnikov-scram-sha-512-01
+
+Abstract
+
+ This document registers the Simple Authentication and Security Layer
+ (SASL) mechanisms SCRAM-SHA-512 and SCRAM-SHA-512-PLUS.
+
+Status of This Memo
+
+ This Internet-Draft is submitted in full conformance with the
+ provisions of BCP 78 and BCP 79.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF). Note that other groups may also distribute
+ working documents as Internet-Drafts. The list of current Internet-
+ Drafts is at https://datatracker.ietf.org/drafts/current/.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ This Internet-Draft will expire on May 22, 2021.
+
+Copyright Notice
+
+ Copyright (c) 2020 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (https://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+Melnikov Expires May 22, 2021 [Page 1]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+Table of Contents
+
+ 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 2. Key Word Definitions . . . . . . . . . . . . . . . . . . . . 2
+ 3. SCRAM-SHA-512 and SCRAM-SHA-512-PLUS . . . . . . . . . . . . 2
+ 4. Security Considerations . . . . . . . . . . . . . . . . . . . 3
+ 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 3
+ 6. References . . . . . . . . . . . . . . . . . . . . . . . . . 4
+ 6.1. Normative References . . . . . . . . . . . . . . . . . . 4
+ 6.2. Informative References . . . . . . . . . . . . . . . . . 5
+ Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . 7
+ Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 7
+
+1. Introduction
+
+ This document registers the SASL [RFC4422] mechanisms SCRAM-SHA-512
+ and SCRAM-SHA-512-PLUS. SHA-512 has stronger security properties
+ than SHA-1, and it is expected that SCRAM mechanisms based on it will
+ have greater predicted longevity than the SCRAM mechanisms based on
+ SHA-1.
+
+2. Key Word Definitions
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
+ "OPTIONAL" in this document are to be interpreted as described in BCP
+ 14 [RFC2119] [RFC8174] when, and only when, they appear in all
+
+3. SCRAM-SHA-512 and SCRAM-SHA-512-PLUS
+
+ The SCRAM-SHA-512 and SCRAM-SHA-512-PLUS SASL mechanisms are defined
+ in the same way that SCRAM-SHA-1 and SCRAM-SHA-1-PLUS are defined in
+ [RFC5802], except that the hash function for HMAC() and H() uses
+ SHA-512 instead of SHA-1 [RFC6234].
+
+ For the SCRAM-SHA-512 and SCRAM-SHA-512-PLUS SASL mechanisms, the
+ hash iteration-count announced by a server SHOULD be at least 10000.
+
+ The GSS-API mechanism OID for SCRAM-SHA-512 is 1.3.6.1.5.5.<TBD> (see
+ Section 5).
+
+ This is a simple example of a SCRAM-SHA-512 authentication exchange
+ when the client doesn't support channel bindings. The username
+ 'user' and password 'pencil' are being used.
+
+ [[TBD: Add an example]]
+
+
+
+
+
+Melnikov Expires May 22, 2021 [Page 2]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+4. Security Considerations
+
+ The security considerations from [RFC5802] still apply.
+
+ To be secure, either SCRAM-SHA-512-PLUS and SCRAM-SHA-1-PLUS MUST be
+ used over a TLS channel that has had the session hash extension
+ [RFC7627] negotiated, or session resumption MUST NOT have been used.
+ When using SCRAM over TLS 1.2 [RFC5246], the "tls-unique" channel
+ binding is still the default channel binding to use (see Section 6.1
+ of [RFC5802]), assuming the above conditions are satisfied. As "tls-
+ unique" channel binding is not defined for TLS 1.3 [RFC8446], when
+ using SCRAM over TLS 1.3, the "tls-exporter" channel binding
+ [tls-1.3-channel-binding] MUST be the default channel binding (in the
+ sense specified in Section 6.1 of [RFC5802]) to use.
+
+ See [RFC4270] and [RFC6194] for reasons to move from SHA-1 to a
+ strong security mechanism like SHA-512.
+
+ The strength of this mechanism is dependent in part on the hash
+ iteration-count, as denoted by "i" in [RFC5802]. As a rule of thumb,
+ the hash iteration-count should be such that a modern machine will
+ take 0.1 seconds to perform the complete algorithm; however, this is
+ unlikely to be practical on mobile devices and other relatively low-
+ performance systems. At the time this was written, the rule of thumb
+ gives around 15,000 iterations required; however, a hash iteration-
+ count of 10000 takes around 0.5 seconds on current mobile handsets.
+ This computational cost can be avoided by caching the ClientKey
+ (assuming the Salt and hash iteration-count is stable). Therefore,
+ the recommendation of this specification is that the hash iteration-
+ count SHOULD be at least 10000, but careful consideration ought to be
+ given to using a significantly higher value, particularly where
+ mobile use is less important.
+
+5. IANA Considerations
+
+ IANA is requested to add the following new SASL SCRAM mechanisms to
+ the "SASL SCRAM Family Mechanisms" registry:
+
+
+
+ To: iana@iana.org
+
+ Subject: Registration of a new SASL SCRAM Family mechanism SCRAM-
+ SHA-512
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-512
+
+ Security considerations: Section 4 of RFC XXXX
+
+
+
+Melnikov Expires May 22, 2021 [Page 3]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+ Published specification (optional, recommended): RFC XXXX
+
+ Minimum iteration-count: 10000
+
+ OID: 1.3.6.1.5.5.<TBD>
+
+ Person & email address to contact for further information: IETF
+ KITTEN WG <kitten@ietf.org>
+
+ Intended usage: COMMON
+
+ Owner/Change controller: IESG <iesg@ietf.org>
+
+ Note:
+
+ To: iana@iana.org
+
+ Subject: Registration of a new SASL SCRAM Family mechanism SCRAM-
+ SHA-512-PLUS
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-
+ 512-PLUS
+
+ Security considerations: Section 4 of RFC XXXX
+
+ Published specification (optional, recommended): RFC XXXX
+
+ Minimum iteration-count: 10000
+
+ OID: 1.3.6.1.5.5.<TBD>
+
+ Person & email address to contact for further information: IETF
+ KITTEN WG <kitten@ietf.org>
+
+ Intended usage: COMMON
+
+ Owner/Change controller: IESG <iesg@ietf.org>
+
+ Note:
+
+6. References
+
+6.1. Normative References
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119,
+ DOI 10.17487/RFC2119, March 1997,
+ <https://www.rfc-editor.org/info/rfc2119>.
+
+
+
+Melnikov Expires May 22, 2021 [Page 4]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+ [RFC4422] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple
+ Authentication and Security Layer (SASL)", RFC 4422,
+ DOI 10.17487/RFC4422, June 2006,
+ <https://www.rfc-editor.org/info/rfc4422>.
+
+ [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security
+ (TLS) Protocol Version 1.2", RFC 5246,
+ DOI 10.17487/RFC5246, August 2008,
+ <https://www.rfc-editor.org/info/rfc5246>.
+
+ [RFC5802] Newman, C., Menon-Sen, A., Melnikov, A., and N. Williams,
+ "Salted Challenge Response Authentication Mechanism
+ (SCRAM) SASL and GSS-API Mechanisms", RFC 5802,
+ DOI 10.17487/RFC5802, July 2010,
+ <https://www.rfc-editor.org/info/rfc5802>.
+
+ [RFC6234] Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms
+ (SHA and SHA-based HMAC and HKDF)", RFC 6234,
+ DOI 10.17487/RFC6234, May 2011,
+ <https://www.rfc-editor.org/info/rfc6234>.
+
+ [RFC7627] Bhargavan, K., Ed., Delignat-Lavaud, A., Pironti, A.,
+ Langley, A., and M. Ray, "Transport Layer Security (TLS)
+ Session Hash and Extended Master Secret Extension",
+ RFC 7627, DOI 10.17487/RFC7627, September 2015,
+ <https://www.rfc-editor.org/info/rfc7627>.
+
+ [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
+ 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
+ May 2017, <https://www.rfc-editor.org/info/rfc8174>.
+
+ [RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol
+ Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
+ <https://www.rfc-editor.org/info/rfc8446>.
+
+ [tls-1.3-channel-binding]
+ Whited, S., "Channel Bindings for TLS 1.3", draft-ietf-
+ kitten-tls-channel-bindings-for-tls13-00 (work in
+ progress), June 2020.
+
+6.2. Informative References
+
+ [RFC4270] Hoffman, P. and B. Schneier, "Attacks on Cryptographic
+ Hashes in Internet Protocols", RFC 4270,
+ DOI 10.17487/RFC4270, November 2005,
+ <https://www.rfc-editor.org/info/rfc4270>.
+
+
+
+
+
+Melnikov Expires May 22, 2021 [Page 5]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+ [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
+ IANA Considerations Section in RFCs", RFC 5226,
+ DOI 10.17487/RFC5226, May 2008,
+ <https://www.rfc-editor.org/info/rfc5226>.
+
+ [RFC6194] Polk, T., Chen, L., Turner, S., and P. Hoffman, "Security
+ Considerations for the SHA-0 and SHA-1 Message-Digest
+ Algorithms", RFC 6194, DOI 10.17487/RFC6194, March 2011,
+ <https://www.rfc-editor.org/info/rfc6194>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov Expires May 22, 2021 [Page 6]
+
+Internet-Draft SASL SCRAM-SHA-512/SCRAM-SHA-512-PLUS November 2020
+
+
+Acknowledgements
+
+ This document is based on RFC 7677 by Tony Hansen.
+
+Author's Address
+
+ Alexey Melnikov (editor)
+ Isode Ltd
+ 14 Castle Mews
+ Hampton, Middlesex TW12 2NP
+ UK
+
+ EMail: alexey.melnikov@isode.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov Expires May 22, 2021 [Page 7]
diff --git a/src/common/libManageSieve/doc/SASL SCRAM/rfc5802.txt b/src/common/libManageSieve/doc/SASL SCRAM/rfc5802.txt
new file mode 100644
index 00000000..971003a7
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL SCRAM/rfc5802.txt
@@ -0,0 +1,1571 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) C. Newman
+Request for Comments: 5802 Oracle
+Category: Standards Track A. Menon-Sen
+ISSN: 2070-1721 Oryx Mail Systems GmbH
+ A. Melnikov
+ Isode, Ltd.
+ N. Williams
+ Oracle
+ July 2010
+
+
+ Salted Challenge Response Authentication Mechanism (SCRAM)
+ SASL and GSS-API Mechanisms
+
+Abstract
+
+ The secure authentication mechanism most widely deployed and used by
+ Internet application protocols is the transmission of clear-text
+ passwords over a channel protected by Transport Layer Security (TLS).
+ There are some significant security concerns with that mechanism,
+ which could be addressed by the use of a challenge response
+ authentication mechanism protected by TLS. Unfortunately, the
+ challenge response mechanisms presently on the standards track all
+ fail to meet requirements necessary for widespread deployment, and
+ have had success only in limited use.
+
+ This specification describes a family of Simple Authentication and
+ Security Layer (SASL; RFC 4422) authentication mechanisms called the
+ Salted Challenge Response Authentication Mechanism (SCRAM), which
+ addresses the security concerns and meets the deployability
+ requirements. When used in combination with TLS or an equivalent
+ security layer, a mechanism from this family could improve the status
+ quo for application protocol authentication and provide a suitable
+ choice for a mandatory-to-implement mechanism for future application
+ protocol standards.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 1]
+
+RFC 5802 SCRAM July 2010
+
+
+Status of This Memo
+
+ This is an Internet Standards Track document.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Further information on
+ Internet Standards is available in Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc5802.
+
+Copyright Notice
+
+ Copyright (c) 2010 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 2]
+
+RFC 5802 SCRAM July 2010
+
+
+Table of Contents
+
+ 1. Introduction ....................................................4
+ 2. Conventions Used in This Document ...............................5
+ 2.1. Terminology ................................................5
+ 2.2. Notation ...................................................6
+ 3. SCRAM Algorithm Overview ........................................7
+ 4. SCRAM Mechanism Names ...........................................8
+ 5. SCRAM Authentication Exchange ...................................9
+ 5.1. SCRAM Attributes ..........................................10
+ 5.2. Compliance with SASL Mechanism Requirements ...............13
+ 6. Channel Binding ................................................14
+ 6.1. Default Channel Binding ...................................15
+ 7. Formal Syntax ..................................................15
+ 8. SCRAM as a GSS-API Mechanism ...................................19
+ 8.1. GSS-API Principal Name Types for SCRAM ....................19
+ 8.2. GSS-API Per-Message Tokens for SCRAM ......................20
+ 8.3. GSS_Pseudo_random() for SCRAM .............................20
+ 9. Security Considerations ........................................20
+ 10. IANA Considerations ...........................................22
+ 11. Acknowledgements ..............................................23
+ 12. References ....................................................24
+ 12.1. Normative References .....................................24
+ 12.2. Normative References for GSS-API Implementors ............24
+ 12.3. Informative References ...................................25
+ Appendix A. Other Authentication Mechanisms .......................27
+ Appendix B. Design Motivations ....................................27
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 3]
+
+RFC 5802 SCRAM July 2010
+
+
+1. Introduction
+
+ This specification describes a family of authentication mechanisms
+ called the Salted Challenge Response Authentication Mechanism (SCRAM)
+ which addresses the requirements necessary to deploy a challenge-
+ response mechanism more widely than past attempts (see Appendix A and
+ Appendix B). When used in combination with Transport Layer Security
+ (TLS; see [RFC5246]) or an equivalent security layer, a mechanism
+ from this family could improve the status quo for application
+ protocol authentication and provide a suitable choice for a
+ mandatory-to-implement mechanism for future application protocol
+ standards.
+
+ For simplicity, this family of mechanisms does not presently include
+ negotiation of a security layer [RFC4422]. It is intended to be used
+ with an external security layer such as that provided by TLS or SSH,
+ with optional channel binding [RFC5056] to the external security
+ layer.
+
+ SCRAM is specified herein as a pure Simple Authentication and
+ Security Layer (SASL) [RFC4422] mechanism, but it conforms to the new
+ bridge between SASL and the Generic Security Service Application
+ Program Interface (GSS-API) called "GS2" [RFC5801]. This means that
+ this document defines both, a SASL mechanism and a GSS-API mechanism.
+
+ SCRAM provides the following protocol features:
+
+ o The authentication information stored in the authentication
+ database is not sufficient by itself to impersonate the client.
+ The information is salted to prevent a pre-stored dictionary
+ attack if the database is stolen.
+
+ o The server does not gain the ability to impersonate the client to
+ other servers (with an exception for server-authorized proxies).
+
+ o The mechanism permits the use of a server-authorized proxy without
+ requiring that proxy to have super-user rights with the back-end
+ server.
+
+ o Mutual authentication is supported, but only the client is named
+ (i.e., the server has no name).
+
+ o When used as a SASL mechanism, SCRAM is capable of transporting
+ authorization identities (see [RFC4422], Section 2) from the
+ client to the server.
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 4]
+
+RFC 5802 SCRAM July 2010
+
+
+ A separate document defines a standard LDAPv3 [RFC4510] attribute
+ that enables storage of the SCRAM authentication information in LDAP.
+ See [RFC5803].
+
+ For an in-depth discussion of why other challenge response mechanisms
+ are not considered sufficient, see Appendix A. For more information
+ about the motivations behind the design of this mechanism, see
+ Appendix B.
+
+2. Conventions Used in This Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [RFC2119].
+
+ Formal syntax is defined by [RFC5234] including the core rules
+ defined in Appendix B of [RFC5234].
+
+ Example lines prefaced by "C:" are sent by the client and ones
+ prefaced by "S:" by the server. If a single "C:" or "S:" label
+ applies to multiple lines, then the line breaks between those lines
+ are for editorial clarity only, and are not part of the actual
+ protocol exchange.
+
+2.1. Terminology
+
+ This document uses several terms defined in [RFC4949] ("Internet
+ Security Glossary") including the following: authentication,
+ authentication exchange, authentication information, brute force,
+ challenge-response, cryptographic hash function, dictionary attack,
+ eavesdropping, hash result, keyed hash, man-in-the-middle, nonce,
+ one-way encryption function, password, replay attack, and salt.
+ Readers not familiar with these terms should use that glossary as a
+ reference.
+
+ Some clarifications and additional definitions follow:
+
+ o Authentication information: Information used to verify an identity
+ claimed by a SCRAM client. The authentication information for a
+ SCRAM identity consists of salt, iteration count, "StoredKey" and
+ "ServerKey" (as defined in the algorithm overview) for each
+ supported cryptographic hash function.
+
+ o Authentication database: The database used to look up the
+ authentication information associated with a particular identity.
+ For application protocols, LDAPv3 (see [RFC4510]) is frequently
+
+
+
+
+
+Newman, et al. Standards Track [Page 5]
+
+RFC 5802 SCRAM July 2010
+
+
+ used as the authentication database. For network-level protocols
+ such as PPP or 802.11x, the use of RADIUS [RFC2865] is more
+ common.
+
+ o Base64: An encoding mechanism defined in [RFC4648] that converts
+ an octet string input to a textual output string that can be
+ easily displayed to a human. The use of base64 in SCRAM is
+ restricted to the canonical form with no whitespace.
+
+ o Octet: An 8-bit byte.
+
+ o Octet string: A sequence of 8-bit bytes.
+
+ o Salt: A random octet string that is combined with a password
+ before applying a one-way encryption function. This value is used
+ to protect passwords that are stored in an authentication
+ database.
+
+2.2. Notation
+
+ The pseudocode description of the algorithm uses the following
+ notations:
+
+ o ":=": The variable on the left-hand side represents the octet
+ string resulting from the expression on the right-hand side.
+
+ o "+": Octet string concatenation.
+
+ o "[ ]": A portion of an expression enclosed in "[" and "]" may not
+ be included in the result under some circumstances. See the
+ associated text for a description of those circumstances.
+
+ o Normalize(str): Apply the SASLprep profile [RFC4013] of the
+ "stringprep" algorithm [RFC3454] as the normalization algorithm to
+ a UTF-8 [RFC3629] encoded "str". The resulting string is also in
+ UTF-8. When applying SASLprep, "str" is treated as a "stored
+ strings", which means that unassigned Unicode codepoints are
+ prohibited (see Section 7 of [RFC3454]). Note that
+ implementations MUST either implement SASLprep or disallow use of
+ non US-ASCII Unicode codepoints in "str".
+
+ o HMAC(key, str): Apply the HMAC keyed hash algorithm (defined in
+ [RFC2104]) using the octet string represented by "key" as the key
+ and the octet string "str" as the input string. The size of the
+ result is the hash result size for the hash function in use. For
+ example, it is 20 octets for SHA-1 (see [RFC3174]).
+
+
+
+
+
+Newman, et al. Standards Track [Page 6]
+
+RFC 5802 SCRAM July 2010
+
+
+ o H(str): Apply the cryptographic hash function to the octet string
+ "str", producing an octet string as a result. The size of the
+ result depends on the hash result size for the hash function in
+ use.
+
+ o XOR: Apply the exclusive-or operation to combine the octet string
+ on the left of this operator with the octet string on the right of
+ this operator. The length of the output and each of the two
+ inputs will be the same for this use.
+
+ o Hi(str, salt, i):
+
+ U1 := HMAC(str, salt + INT(1))
+ U2 := HMAC(str, U1)
+ ...
+ Ui-1 := HMAC(str, Ui-2)
+ Ui := HMAC(str, Ui-1)
+
+ Hi := U1 XOR U2 XOR ... XOR Ui
+
+ where "i" is the iteration count, "+" is the string concatenation
+ operator, and INT(g) is a 4-octet encoding of the integer g, most
+ significant octet first.
+
+ Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
+ pseudorandom function (PRF) and with dkLen == output length of
+ HMAC() == output length of H().
+
+3. SCRAM Algorithm Overview
+
+ The following is a description of a full, uncompressed SASL SCRAM
+ authentication exchange. Nothing in SCRAM prevents either sending
+ the client-first message with the SASL authentication request defined
+ by an application protocol ("initial client response"), or sending
+ the server-final message as additional data of the SASL outcome of
+ authentication exchange defined by an application protocol. See
+ [RFC4422] for more details.
+
+ Note that this section omits some details, such as client and server
+ nonces. See Section 5 for more details.
+
+ To begin with, the SCRAM client is in possession of a username and
+ password (*) (or a ClientKey/ServerKey, or SaltedPassword). It sends
+ the username to the server, which retrieves the corresponding
+ authentication information, i.e., a salt, StoredKey, ServerKey, and
+ the iteration count i. (Note that a server implementation may choose
+
+
+
+
+
+Newman, et al. Standards Track [Page 7]
+
+RFC 5802 SCRAM July 2010
+
+
+ to use the same iteration count for all accounts.) The server sends
+ the salt and the iteration count to the client, which then computes
+ the following values and sends a ClientProof to the server:
+
+ (*) Note that both the username and the password MUST be encoded in
+ UTF-8 [RFC3629].
+
+ Informative Note: Implementors are encouraged to create test cases
+ that use both usernames and passwords with non-ASCII codepoints. In
+ particular, it's useful to test codepoints whose "Unicode
+ Normalization Form C" and "Unicode Normalization Form KC" are
+ different. Some examples of such codepoints include Vulgar Fraction
+ One Half (U+00BD) and Acute Accent (U+00B4).
+
+ SaltedPassword := Hi(Normalize(password), salt, i)
+ ClientKey := HMAC(SaltedPassword, "Client Key")
+ StoredKey := H(ClientKey)
+ AuthMessage := client-first-message-bare + "," +
+ server-first-message + "," +
+ client-final-message-without-proof
+ ClientSignature := HMAC(StoredKey, AuthMessage)
+ ClientProof := ClientKey XOR ClientSignature
+ ServerKey := HMAC(SaltedPassword, "Server Key")
+ ServerSignature := HMAC(ServerKey, AuthMessage)
+
+ The server authenticates the client by computing the ClientSignature,
+ exclusive-ORing that with the ClientProof to recover the ClientKey
+ and verifying the correctness of the ClientKey by applying the hash
+ function and comparing the result to the StoredKey. If the ClientKey
+ is correct, this proves that the client has access to the user's
+ password.
+
+ Similarly, the client authenticates the server by computing the
+ ServerSignature and comparing it to the value sent by the server. If
+ the two are equal, it proves that the server had access to the user's
+ ServerKey.
+
+ The AuthMessage is computed by concatenating messages from the
+ authentication exchange. The format of these messages is defined in
+ Section 7.
+
+4. SCRAM Mechanism Names
+
+ A SCRAM mechanism name is a string "SCRAM-" followed by the
+ uppercased name of the underlying hash function taken from the IANA
+ "Hash Function Textual Names" registry (see http://www.iana.org),
+ optionally followed by the suffix "-PLUS" (see below). Note that
+ SASL mechanism names are limited to 20 octets, which means that only
+
+
+
+Newman, et al. Standards Track [Page 8]
+
+RFC 5802 SCRAM July 2010
+
+
+ hash function names with lengths shorter or equal to 9 octets
+ (20-length("SCRAM-")-length("-PLUS") can be used. For cases when the
+ underlying hash function name is longer than 9 octets, an alternative
+ 9-octet (or shorter) name can be used to construct the corresponding
+ SCRAM mechanism name, as long as this alternative name doesn't
+ conflict with any other hash function name from the IANA "Hash
+ Function Textual Names" registry. In order to prevent future
+ conflict, such alternative names SHOULD be registered in the IANA
+ "Hash Function Textual Names" registry.
+
+ For interoperability, all SCRAM clients and servers MUST implement
+ the SCRAM-SHA-1 authentication mechanism, i.e., an authentication
+ mechanism from the SCRAM family that uses the SHA-1 hash function as
+ defined in [RFC3174].
+
+ The "-PLUS" suffix is used only when the server supports channel
+ binding to the external channel. If the server supports channel
+ binding, it will advertise both the "bare" and "plus" versions of
+ whatever mechanisms it supports (e.g., if the server supports only
+ SCRAM with SHA-1, then it will advertise support for both SCRAM-SHA-1
+ and SCRAM-SHA-1-PLUS). If the server does not support channel
+ binding, then it will advertise only the "bare" version of the
+ mechanism (e.g., only SCRAM-SHA-1). The "-PLUS" exists to allow
+ negotiation of the use of channel binding. See Section 6.
+
+5. SCRAM Authentication Exchange
+
+ SCRAM is a SASL mechanism whose client response and server challenge
+ messages are text-based messages containing one or more attribute-
+ value pairs separated by commas. Each attribute has a one-letter
+ name. The messages and their attributes are described in
+ Section 5.1, and defined in Section 7.
+
+ SCRAM is a client-first SASL mechanism (see [RFC4422], Section 5,
+ item 2a), and returns additional data together with a server's
+ indication of a successful outcome.
+
+ This is a simple example of a SCRAM-SHA-1 authentication exchange
+ when the client doesn't support channel bindings (username 'user' and
+ password 'pencil' are used):
+
+ C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,
+ i=4096
+ C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,
+ p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+
+
+
+
+Newman, et al. Standards Track [Page 9]
+
+RFC 5802 SCRAM July 2010
+
+
+ First, the client sends the "client-first-message" containing:
+
+ o a GS2 header consisting of a flag indicating whether channel
+ binding is supported-but-not-used, not supported, or used, and an
+ optional SASL authorization identity;
+
+ o SCRAM username and a random, unique nonce attributes.
+
+ Note that the client's first message will always start with "n", "y",
+ or "p"; otherwise, the message is invalid and authentication MUST
+ fail. This is important, as it allows for GS2 extensibility (e.g.,
+ to add support for security layers).
+
+ In response, the server sends a "server-first-message" containing the
+ user's iteration count i and the user's salt, and appends its own
+ nonce to the client-specified one.
+
+ The client then responds by sending a "client-final-message" with the
+ same nonce and a ClientProof computed using the selected hash
+ function as explained earlier.
+
+ The server verifies the nonce and the proof, verifies that the
+ authorization identity (if supplied by the client in the first
+ message) is authorized to act as the authentication identity, and,
+ finally, it responds with a "server-final-message", concluding the
+ authentication exchange.
+
+ The client then authenticates the server by computing the
+ ServerSignature and comparing it to the value sent by the server. If
+ the two are different, the client MUST consider the authentication
+ exchange to be unsuccessful, and it might have to drop the
+ connection.
+
+5.1. SCRAM Attributes
+
+ This section describes the permissible attributes, their use, and the
+ format of their values. All attribute names are single US-ASCII
+ letters and are case-sensitive.
+
+ Note that the order of attributes in client or server messages is
+ fixed, with the exception of extension attributes (described by the
+ "extensions" ABNF production), which can appear in any order in the
+ designated positions. See Section 7 for authoritative reference.
+
+ o a: This is an optional attribute, and is part of the GS2 [RFC5801]
+ bridge between the GSS-API and SASL. This attribute specifies an
+ authorization identity. A client may include it in its first
+ message to the server if it wants to authenticate as one user, but
+
+
+
+Newman, et al. Standards Track [Page 10]
+
+RFC 5802 SCRAM July 2010
+
+
+ subsequently act as a different user. This is typically used by
+ an administrator to perform some management task on behalf of
+ another user, or by a proxy in some situations.
+
+ Upon the receipt of this value the server verifies its
+ correctness according to the used SASL protocol profile.
+ Failed verification results in failed authentication exchange.
+
+ If this attribute is omitted (as it normally would be), the
+ authorization identity is assumed to be derived from the
+ username specified with the (required) "n" attribute.
+
+ The server always authenticates the user specified by the "n"
+ attribute. If the "a" attribute specifies a different user,
+ the server associates that identity with the connection after
+ successful authentication and authorization checks.
+
+ The syntax of this field is the same as that of the "n" field
+ with respect to quoting of '=' and ','.
+
+ o n: This attribute specifies the name of the user whose password is
+ used for authentication (a.k.a. "authentication identity"
+ [RFC4422]). A client MUST include it in its first message to the
+ server. If the "a" attribute is not specified (which would
+ normally be the case), this username is also the identity that
+ will be associated with the connection subsequent to
+ authentication and authorization.
+
+ Before sending the username to the server, the client SHOULD
+ prepare the username using the "SASLprep" profile [RFC4013] of
+ the "stringprep" algorithm [RFC3454] treating it as a query
+ string (i.e., unassigned Unicode code points are allowed). If
+ the preparation of the username fails or results in an empty
+ string, the client SHOULD abort the authentication exchange
+ (*).
+
+ (*) An interactive client can request a repeated entry of the
+ username value.
+
+ Upon receipt of the username by the server, the server MUST
+ either prepare it using the "SASLprep" profile [RFC4013] of the
+ "stringprep" algorithm [RFC3454] treating it as a query string
+ (i.e., unassigned Unicode codepoints are allowed) or otherwise
+ be prepared to do SASLprep-aware string comparisons and/or
+ index lookups. If the preparation of the username fails or
+ results in an empty string, the server SHOULD abort the
+
+
+
+
+
+Newman, et al. Standards Track [Page 11]
+
+RFC 5802 SCRAM July 2010
+
+
+ authentication exchange. Whether or not the server prepares
+ the username using "SASLprep", it MUST use it as received in
+ hash calculations.
+
+ The characters ',' or '=' in usernames are sent as '=2C' and
+ '=3D' respectively. If the server receives a username that
+ contains '=' not followed by either '2C' or '3D', then the
+ server MUST fail the authentication.
+
+ o m: This attribute is reserved for future extensibility. In this
+ version of SCRAM, its presence in a client or a server message
+ MUST cause authentication failure when the attribute is parsed by
+ the other end.
+
+ o r: This attribute specifies a sequence of random printable ASCII
+ characters excluding ',' (which forms the nonce used as input to
+ the hash function). No quoting is applied to this string. As
+ described earlier, the client supplies an initial value in its
+ first message, and the server augments that value with its own
+ nonce in its first response. It is important that this value be
+ different for each authentication (see [RFC4086] for more details
+ on how to achieve this). The client MUST verify that the initial
+ part of the nonce used in subsequent messages is the same as the
+ nonce it initially specified. The server MUST verify that the
+ nonce sent by the client in the second message is the same as the
+ one sent by the server in its first message.
+
+ o c: This REQUIRED attribute specifies the base64-encoded GS2 header
+ and channel binding data. It is sent by the client in its second
+ authentication message. The attribute data consist of:
+
+ * the GS2 header from the client's first message (recall that the
+ GS2 header contains a channel binding flag and an optional
+ authzid). This header is going to include channel binding type
+ prefix (see [RFC5056]), if and only if the client is using
+ channel binding;
+
+ * followed by the external channel's channel binding data, if and
+ only if the client is using channel binding.
+
+ o s: This attribute specifies the base64-encoded salt used by the
+ server for this user. It is sent by the server in its first
+ message to the client.
+
+ o i: This attribute specifies an iteration count for the selected
+ hash function and user, and MUST be sent by the server along with
+ the user's salt.
+
+
+
+
+Newman, et al. Standards Track [Page 12]
+
+RFC 5802 SCRAM July 2010
+
+
+ For the SCRAM-SHA-1/SCRAM-SHA-1-PLUS SASL mechanism, servers
+ SHOULD announce a hash iteration-count of at least 4096. Note
+ that a client implementation MAY cache ClientKey&ServerKey (or
+ just SaltedPassword) for later reauthentication to the same
+ service, as it is likely that the server is going to advertise
+ the same salt value upon reauthentication. This might be
+ useful for mobile clients where CPU usage is a concern.
+
+ o p: This attribute specifies a base64-encoded ClientProof. The
+ client computes this value as described in the overview and sends
+ it to the server.
+
+ o v: This attribute specifies a base64-encoded ServerSignature. It
+ is sent by the server in its final message, and is used by the
+ client to verify that the server has access to the user's
+ authentication information. This value is computed as explained
+ in the overview.
+
+ o e: This attribute specifies an error that occurred during
+ authentication exchange. It is sent by the server in its final
+ message and can help diagnose the reason for the authentication
+ exchange failure. On failed authentication, the entire server-
+ final-message is OPTIONAL; specifically, a server implementation
+ MAY conclude the SASL exchange with a failure without sending the
+ server-final-message. This results in an application-level error
+ response without an extra round-trip. If the server-final-message
+ is sent on authentication failure, then the "e" attribute MUST be
+ included.
+
+ o As-yet unspecified mandatory and optional extensions. Mandatory
+ extensions are encoded as values of the 'm' attribute (see ABNF
+ for reserved-mext in section 7). Optional extensions use as-yet
+ unassigned attribute names.
+
+ Mandatory extensions sent by one peer but not understood by the
+ other MUST cause authentication failure (the server SHOULD send
+ the "extensions-not-supported" server-error-value).
+
+ Unknown optional extensions MUST be ignored upon receipt.
+
+5.2. Compliance with SASL Mechanism Requirements
+
+ This section describes compliance with SASL mechanism requirements
+ specified in Section 5 of [RFC4422].
+
+ 1) "SCRAM-SHA-1" and "SCRAM-SHA-1-PLUS".
+
+ 2a) SCRAM is a client-first mechanism.
+
+
+
+Newman, et al. Standards Track [Page 13]
+
+RFC 5802 SCRAM July 2010
+
+
+ 2b) SCRAM sends additional data with success.
+
+ 3) SCRAM is capable of transferring authorization identities from
+ the client to the server.
+
+ 4) SCRAM does not offer any security layers (SCRAM offers channel
+ binding instead).
+
+ 5) SCRAM has a hash protecting the authorization identity.
+
+6. Channel Binding
+
+ SCRAM supports channel binding to external secure channels, such as
+ TLS. Clients and servers may or may not support channel binding,
+ therefore the use of channel binding is negotiable. SCRAM does not
+ provide security layers, however, therefore it is imperative that
+ SCRAM provide integrity protection for the negotiation of channel
+ binding.
+
+ Use of channel binding is negotiated as follows:
+
+ o Servers that support the use of channel binding SHOULD advertise
+ both the non-PLUS (SCRAM-<hash-function>) and PLUS-variant (SCRAM-
+ <hash-function>-PLUS) mechanism name. If the server cannot
+ support channel binding, it SHOULD advertise only the non-PLUS-
+ variant. If the server would never succeed in the authentication
+ of the non-PLUS-variant due to policy reasons, it MUST advertise
+ only the PLUS-variant.
+
+ o If the client supports channel binding and the server does not
+ appear to (i.e., the client did not see the -PLUS name advertised
+ by the server), then the client MUST NOT use an "n" gs2-cbind-
+ flag.
+
+ o Clients that support mechanism negotiation and channel binding
+ MUST use a "p" gs2-cbind-flag when the server offers the PLUS-
+ variant of the desired GS2 mechanism.
+
+ o If the client does not support channel binding, then it MUST use
+ an "n" gs2-cbind-flag. Conversely, if the client requires the use
+ of channel binding then it MUST use a "p" gs2-cbind-flag. Clients
+ that do not support mechanism negotiation never use a "y" gs2-
+ cbind-flag, they use either "p" or "n" according to whether they
+ require and support the use of channel binding or whether they do
+ not, respectively.
+
+ o Upon receipt of the client-first message, the server checks the
+ channel binding flag (gs2-cbind-flag).
+
+
+
+Newman, et al. Standards Track [Page 14]
+
+RFC 5802 SCRAM July 2010
+
+
+ * If the flag is set to "y" and the server supports channel
+ binding, the server MUST fail authentication. This is because
+ if the client sets the channel binding flag to "y", then the
+ client must have believed that the server did not support
+ channel binding -- if the server did in fact support channel
+ binding, then this is an indication that there has been a
+ downgrade attack (e.g., an attacker changed the server's
+ mechanism list to exclude the -PLUS suffixed SCRAM mechanism
+ name(s)).
+
+ * If the channel binding flag was "p" and the server does not
+ support the indicated channel binding type, then the server
+ MUST fail authentication.
+
+ The server MUST always validate the client's "c=" field. The server
+ does this by constructing the value of the "c=" attribute and then
+ checking that it matches the client's c= attribute value.
+
+ For more discussions of channel bindings, and the syntax of channel
+ binding data for various security protocols, see [RFC5056].
+
+6.1. Default Channel Binding
+
+ A default channel binding type agreement process for all SASL
+ application protocols that do not provide their own channel binding
+ type agreement is provided as follows.
+
+ 'tls-unique' is the default channel binding type for any application
+ that doesn't specify one.
+
+ Servers MUST implement the "tls-unique" [RFC5929] channel binding
+ type, if they implement any channel binding. Clients SHOULD
+ implement the "tls-unique" [RFC5929] channel binding type, if they
+ implement any channel binding. Clients and servers SHOULD choose the
+ highest-layer/innermost end-to-end TLS channel as the channel to
+ which to bind.
+
+ Servers MUST choose the channel binding type indicated by the client,
+ or fail authentication if they don't support it.
+
+7. Formal Syntax
+
+ The following syntax specification uses the Augmented Backus-Naur
+ form (ABNF) notation as specified in [RFC5234]. "UTF8-2", "UTF8-3",
+ and "UTF8-4" non-terminal are defined in [RFC3629].
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 15]
+
+RFC 5802 SCRAM July 2010
+
+
+ ALPHA = <as defined in RFC 5234 appendix B.1>
+ DIGIT = <as defined in RFC 5234 appendix B.1>
+ UTF8-2 = <as defined in RFC 3629 (STD 63)>
+ UTF8-3 = <as defined in RFC 3629 (STD 63)>
+ UTF8-4 = <as defined in RFC 3629 (STD 63)>
+
+ attr-val = ALPHA "=" value
+ ;; Generic syntax of any attribute sent
+ ;; by server or client
+
+ value = 1*value-char
+
+ value-safe-char = %x01-2B / %x2D-3C / %x3E-7F /
+ UTF8-2 / UTF8-3 / UTF8-4
+ ;; UTF8-char except NUL, "=", and ",".
+
+ value-char = value-safe-char / "="
+
+ printable = %x21-2B / %x2D-7E
+ ;; Printable ASCII except ",".
+ ;; Note that any "printable" is also
+ ;; a valid "value".
+
+ base64-char = ALPHA / DIGIT / "/" / "+"
+
+ base64-4 = 4base64-char
+
+ base64-3 = 3base64-char "="
+
+ base64-2 = 2base64-char "=="
+
+ base64 = *base64-4 [base64-3 / base64-2]
+
+ posit-number = %x31-39 *DIGIT
+ ;; A positive number.
+
+ saslname = 1*(value-safe-char / "=2C" / "=3D")
+ ;; Conforms to <value>.
+
+ authzid = "a=" saslname
+ ;; Protocol specific.
+
+ cb-name = 1*(ALPHA / DIGIT / "." / "-")
+ ;; See RFC 5056, Section 7.
+ ;; E.g., "tls-server-end-point" or
+ ;; "tls-unique".
+
+
+
+
+
+Newman, et al. Standards Track [Page 16]
+
+RFC 5802 SCRAM July 2010
+
+
+ gs2-cbind-flag = ("p=" cb-name) / "n" / "y"
+ ;; "n" -> client doesn't support channel binding.
+ ;; "y" -> client does support channel binding
+ ;; but thinks the server does not.
+ ;; "p" -> client requires channel binding.
+ ;; The selected channel binding follows "p=".
+
+ gs2-header = gs2-cbind-flag "," [ authzid ] ","
+ ;; GS2 header for SCRAM
+ ;; (the actual GS2 header includes an optional
+ ;; flag to indicate that the GSS mechanism is not
+ ;; "standard", but since SCRAM is "standard", we
+ ;; don't include that flag).
+
+ username = "n=" saslname
+ ;; Usernames are prepared using SASLprep.
+
+ reserved-mext = "m=" 1*(value-char)
+ ;; Reserved for signaling mandatory extensions.
+ ;; The exact syntax will be defined in
+ ;; the future.
+
+ channel-binding = "c=" base64
+ ;; base64 encoding of cbind-input.
+
+ proof = "p=" base64
+
+ nonce = "r=" c-nonce [s-nonce]
+ ;; Second part provided by server.
+
+ c-nonce = printable
+
+ s-nonce = printable
+
+ salt = "s=" base64
+
+ verifier = "v=" base64
+ ;; base-64 encoded ServerSignature.
+
+ iteration-count = "i=" posit-number
+ ;; A positive number.
+
+ client-first-message-bare =
+ [reserved-mext ","]
+ username "," nonce ["," extensions]
+
+ client-first-message =
+ gs2-header client-first-message-bare
+
+
+
+Newman, et al. Standards Track [Page 17]
+
+RFC 5802 SCRAM July 2010
+
+
+ server-first-message =
+ [reserved-mext ","] nonce "," salt ","
+ iteration-count ["," extensions]
+
+ client-final-message-without-proof =
+ channel-binding "," nonce [","
+ extensions]
+
+ client-final-message =
+ client-final-message-without-proof "," proof
+
+ server-error = "e=" server-error-value
+
+ server-error-value = "invalid-encoding" /
+ "extensions-not-supported" / ; unrecognized 'm' value
+ "invalid-proof" /
+ "channel-bindings-dont-match" /
+ "server-does-support-channel-binding" /
+ ; server does not support channel binding
+ "channel-binding-not-supported" /
+ "unsupported-channel-binding-type" /
+ "unknown-user" /
+ "invalid-username-encoding" /
+ ; invalid username encoding (invalid UTF-8 or
+ ; SASLprep failed)
+ "no-resources" /
+ "other-error" /
+ server-error-value-ext
+ ; Unrecognized errors should be treated as "other-error".
+ ; In order to prevent information disclosure, the server
+ ; may substitute the real reason with "other-error".
+
+ server-error-value-ext = value
+ ; Additional error reasons added by extensions
+ ; to this document.
+
+ server-final-message = (server-error / verifier)
+ ["," extensions]
+
+ extensions = attr-val *("," attr-val)
+ ;; All extensions are optional,
+ ;; i.e., unrecognized attributes
+ ;; not defined in this document
+ ;; MUST be ignored.
+
+ cbind-data = 1*OCTET
+
+
+
+
+
+Newman, et al. Standards Track [Page 18]
+
+RFC 5802 SCRAM July 2010
+
+
+ cbind-input = gs2-header [ cbind-data ]
+ ;; cbind-data MUST be present for
+ ;; gs2-cbind-flag of "p" and MUST be absent
+ ;; for "y" or "n".
+
+8. SCRAM as a GSS-API Mechanism
+
+ This section and its sub-sections and all normative references of it
+ not referenced elsewhere in this document are INFORMATIONAL for SASL
+ implementors, but they are NORMATIVE for GSS-API implementors.
+
+ SCRAM is actually also a GSS-API mechanism. The messages are the
+ same, but a) the GS2 header on the client's first message and channel
+ binding data is excluded when SCRAM is used as a GSS-API mechanism,
+ and b) the RFC2743 section 3.1 initial context token header is
+ prefixed to the client's first authentication message (context
+ token).
+
+ The GSS-API mechanism OID for SCRAM-SHA-1 is 1.3.6.1.5.5.14 (see
+ Section 10).
+
+ SCRAM security contexts always have the mutual_state flag
+ (GSS_C_MUTUAL_FLAG) set to TRUE. SCRAM does not support credential
+ delegation, therefore SCRAM security contexts alway have the
+ deleg_state flag (GSS_C_DELEG_FLAG) set to FALSE.
+
+8.1. GSS-API Principal Name Types for SCRAM
+
+ SCRAM does not explicitly name acceptor principals. However, the use
+ of acceptor principal names to find or prompt for passwords is
+ useful. Therefore, SCRAM supports standard generic name syntaxes for
+ acceptors such as GSS_C_NT_HOSTBASED_SERVICE (see [RFC2743], Section
+ 4.1). Implementations should use the target name passed to
+ GSS_Init_sec_context(), if any, to help retrieve or prompt for SCRAM
+ passwords.
+
+ SCRAM supports only a single name type for initiators:
+ GSS_C_NT_USER_NAME. GSS_C_NT_USER_NAME is the default name type for
+ SCRAM.
+
+ There is no name canonicalization procedure for SCRAM beyond applying
+ SASLprep as described in Section 5.1.
+
+ The query, display, and exported name syntaxes for SCRAM principal
+ names are all the same. There are no SCRAM-specific name syntaxes
+ (SCRAM initiator principal names are free-form); -- applications
+ should use generic GSS-API name types such as GSS_C_NT_USER_NAME and
+
+
+
+
+Newman, et al. Standards Track [Page 19]
+
+RFC 5802 SCRAM July 2010
+
+
+ GSS_C_NT_HOSTBASED_SERVICE (see [RFC2743], Section 4). The exported
+ name token does, of course, conform to [RFC2743], Section 3.2, but
+ the "NAME" part of the token is just a SCRAM user name.
+
+8.2. GSS-API Per-Message Tokens for SCRAM
+
+ The per-message tokens for SCRAM as a GSS-API mechanism SHALL be the
+ same as those for the Kerberos V GSS-API mechanism [RFC4121] (see
+ Section 4.2 and sub-sections), using the Kerberos V "aes128-cts-hmac-
+ sha1-96" enctype [RFC3962].
+
+ The replay_det_state (GSS_C_REPLAY_FLAG), sequence_state
+ (GSS_C_SEQUENCE_FLAG), conf_avail (GSS_C_CONF_FLAG) and integ_avail
+ (GSS_C_CONF_FLAG) security context flags are always set to TRUE.
+
+ The 128-bit session "protocol key" SHALL be derived by using the
+ least significant (right-most) 128 bits of HMAC(StoredKey, "GSS-API
+ session key" || ClientKey || AuthMessage). "Specific keys" are then
+ derived as usual as described in Section 2 of [RFC4121], [RFC3961],
+ and [RFC3962].
+
+ The terms "protocol key" and "specific key" are Kerberos V5 terms
+ [RFC3961].
+
+ SCRAM does support PROT_READY, and is PROT_READY on the initiator
+ side first upon receipt of the server's reply to the initial security
+ context token.
+
+8.3. GSS_Pseudo_random() for SCRAM
+
+ The GSS_Pseudo_random() [RFC4401] for SCRAM SHALL be the same as for
+ the Kerberos V GSS-API mechanism [RFC4402]. There is no acceptor-
+ asserted sub-session key for SCRAM, thus GSS_C_PRF_KEY_FULL and
+ GSS_C_PRF_KEY_PARTIAL are equivalent for SCRAM's GSS_Pseudo_random().
+ The protocol key to be used for the GSS_Pseudo_random() SHALL be the
+ same as the key defined in Section 8.2.
+
+9. Security Considerations
+
+ If the authentication exchange is performed without a strong security
+ layer (such as TLS with data confidentiality), then a passive
+ eavesdropper can gain sufficient information to mount an offline
+ dictionary or brute-force attack that can be used to recover the
+ user's password. The amount of time necessary for this attack
+ depends on the cryptographic hash function selected, the strength of
+ the password, and the iteration count supplied by the server. An
+ external security layer with strong encryption will prevent this
+ attack.
+
+
+
+Newman, et al. Standards Track [Page 20]
+
+RFC 5802 SCRAM July 2010
+
+
+ If the external security layer used to protect the SCRAM exchange
+ uses an anonymous key exchange, then the SCRAM channel binding
+ mechanism can be used to detect a man-in-the-middle attack on the
+ security layer and cause the authentication to fail as a result.
+ However, the man-in-the-middle attacker will have gained sufficient
+ information to mount an offline dictionary or brute-force attack.
+ For this reason, SCRAM allows to increase the iteration count over
+ time. (Note that a server that is only in possession of "StoredKey"
+ and "ServerKey" can't automatically increase the iteration count upon
+ successful authentication. Such an increase would require resetting
+ the user's password.)
+
+ If the authentication information is stolen from the authentication
+ database, then an offline dictionary or brute-force attack can be
+ used to recover the user's password. The use of salt mitigates this
+ attack somewhat by requiring a separate attack on each password.
+ Authentication mechanisms that protect against this attack are
+ available (e.g., the EKE class of mechanisms). RFC 2945 [RFC2945] is
+ an example of such technology. The WG elected not to use EKE like
+ mechanisms as a basis for SCRAM.
+
+ If an attacker obtains the authentication information from the
+ authentication repository and either eavesdrops on one authentication
+ exchange or impersonates a server, the attacker gains the ability to
+ impersonate that user to all servers providing SCRAM access using the
+ same hash function, password, iteration count, and salt. For this
+ reason, it is important to use randomly generated salt values.
+
+ SCRAM does not negotiate a hash function to use. Hash function
+ negotiation is left to the SASL mechanism negotiation. It is
+ important that clients be able to sort a locally available list of
+ mechanisms by preference so that the client may pick the appropriate
+ mechanism to use from a server's advertised mechanism list. This
+ preference order is not specified here as it is a local matter. The
+ preference order should include objective and subjective notions of
+ mechanism cryptographic strength (e.g., SCRAM with a successor to
+ SHA-1 may be preferred over SCRAM with SHA-1).
+
+ Note that to protect the SASL mechanism negotiation applications
+ normally must list the server mechanisms twice: once before and once
+ after authentication, the latter using security layers. Since SCRAM
+ does not provide security layers, the only ways to protect the
+ mechanism negotiation are a) use channel binding to an external
+ channel, or b) use an external channel that authenticates a user-
+ provided server name.
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 21]
+
+RFC 5802 SCRAM July 2010
+
+
+ SCRAM does not protect against downgrade attacks of channel binding
+ types. The complexities of negotiating a channel binding type, and
+ handling down-grade attacks in that negotiation, were intentionally
+ left out of scope for this document.
+
+ A hostile server can perform a computational denial-of-service attack
+ on clients by sending a big iteration count value.
+
+ See [RFC4086] for more information about generating randomness.
+
+10. IANA Considerations
+
+ IANA has added the following family of SASL mechanisms to the SASL
+ Mechanism registry established by [RFC4422]:
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL family SCRAM
+
+ SASL mechanism name (or prefix for the family): SCRAM-*
+ Security considerations: Section 7 of [RFC5802]
+ Published specification (optional, recommended): [RFC5802]
+ Person & email address to contact for further information:
+ IETF SASL WG <sasl@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note: Members of this family MUST be explicitly registered
+ using the "IETF Review" [RFC5226] registration procedure.
+ Reviews MUST be requested on the SASL mailing list
+ <sasl@ietf.org> (or a successor designated by the responsible
+ Security AD).
+
+ Note to future SCRAM-mechanism designers: each new SCRAM-SASL
+ mechanism MUST be explicitly registered with IANA and MUST comply
+ with SCRAM-mechanism naming convention defined in Section 4 of this
+ document.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 22]
+
+RFC 5802 SCRAM July 2010
+
+
+ IANA has added the following entries to the SASL Mechanism registry
+ established by [RFC4422]:
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL mechanism SCRAM-SHA-1
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-1
+ Security considerations: Section 7 of [RFC5802]
+ Published specification (optional, recommended): [RFC5802]
+ Person & email address to contact for further information:
+ IETF SASL WG <sasl@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note:
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL mechanism SCRAM-SHA-1-PLUS
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-1-PLUS
+ Security considerations: Section 7 of [RFC5802]
+ Published specification (optional, recommended): [RFC5802]
+ Person & email address to contact for further information:
+ IETF SASL WG <sasl@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note:
+
+ Per this document, IANA has assigned a GSS-API mechanism OID for
+ SCRAM-SHA-1 from the iso.org.dod.internet.security.mechanisms prefix
+ (see "SMI Security for Mechanism Codes" registry).
+
+11. Acknowledgements
+
+ This document benefited from discussions on the SASL WG mailing list.
+ The authors would like to specially thank Dave Cridland, Simon
+ Josefsson, Jeffrey Hutzelman, Kurt Zeilenga, Pasi Eronen, Ben
+ Campbell, Peter Saint-Andre, and Tobias Markmann for their
+ contributions to this document. A special thank you to Simon
+ Josefsson for shepherding this document and for doing one of the
+ first implementations of this specification.
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 23]
+
+RFC 5802 SCRAM July 2010
+
+
+12. References
+
+12.1. Normative References
+
+ [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
+ Hashing for Message Authentication", RFC 2104,
+ February 1997.
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [RFC3174] Eastlake, D. and P. Jones, "US Secure Hash Algorithm 1
+ (SHA1)", RFC 3174, September 2001.
+
+ [RFC3454] Hoffman, P. and M. Blanchet, "Preparation of
+ Internationalized Strings ("stringprep")", RFC 3454,
+ December 2002.
+
+ [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO
+ 10646", STD 63, RFC 3629, November 2003.
+
+ [RFC4013] Zeilenga, K., "SASLprep: Stringprep Profile for User Names
+ and Passwords", RFC 4013, February 2005.
+
+ [RFC4422] Melnikov, A. and K. Zeilenga, "Simple Authentication and
+ Security Layer (SASL)", RFC 4422, June 2006.
+
+ [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
+ Encodings", RFC 4648, October 2006.
+
+ [RFC5056] Williams, N., "On the Use of Channel Bindings to Secure
+ Channels", RFC 5056, November 2007.
+
+ [RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", STD 68, RFC 5234, January 2008.
+
+ [RFC5929] Altman, J., Williams, N., and L. Zhu, "Channel Bindings
+ for TLS", RFC 5929, July 2010.
+
+12.2. Normative References for GSS-API Implementors
+
+ [RFC2743] Linn, J., "Generic Security Service Application Program
+ Interface Version 2, Update 1", RFC 2743, January 2000.
+
+ [RFC3961] Raeburn, K., "Encryption and Checksum Specifications for
+ Kerberos 5", RFC 3961, February 2005.
+
+
+
+
+
+Newman, et al. Standards Track [Page 24]
+
+RFC 5802 SCRAM July 2010
+
+
+ [RFC3962] Raeburn, K., "Advanced Encryption Standard (AES)
+ Encryption for Kerberos 5", RFC 3962, February 2005.
+
+ [RFC4121] Zhu, L., Jaganathan, K., and S. Hartman, "The Kerberos
+ Version 5 Generic Security Service Application Program
+ Interface (GSS-API) Mechanism: Version 2", RFC 4121,
+ July 2005.
+
+ [RFC4401] Williams, N., "A Pseudo-Random Function (PRF) API
+ Extension for the Generic Security Service Application
+ Program Interface (GSS-API)", RFC 4401, February 2006.
+
+ [RFC4402] Williams, N., "A Pseudo-Random Function (PRF) for the
+ Kerberos V Generic Security Service Application Program
+ Interface (GSS-API) Mechanism", RFC 4402, February 2006.
+
+ [RFC5801] Josefsson, S. and N. Williams, "Using Generic Security
+ Service Application Program Interface (GSS-API) Mechanisms
+ in Simple Authentication and Security Layer (SASL): The
+ GS2 Mechanism Family", RFC 5801, July 2010.
+
+12.3. Informative References
+
+ [CRAMHISTORIC]
+ Zeilenga, K., "CRAM-MD5 to Historic", Work in Progress,
+ November 2008.
+
+ [DIGESTHISTORIC]
+ Melnikov, A., "Moving DIGEST-MD5 to Historic", Work
+ in Progress, July 2008.
+
+ [RFC2865] Rigney, C., Willens, S., Rubens, A., and W. Simpson,
+ "Remote Authentication Dial In User Service (RADIUS)",
+ RFC 2865, June 2000.
+
+ [RFC2898] Kaliski, B., "PKCS #5: Password-Based Cryptography
+ Specification Version 2.0", RFC 2898, September 2000.
+
+ [RFC2945] Wu, T., "The SRP Authentication and Key Exchange System",
+ RFC 2945, September 2000.
+
+ [RFC4086] Eastlake, D., Schiller, J., and S. Crocker, "Randomness
+ Requirements for Security", BCP 106, RFC 4086, June 2005.
+
+ [RFC4510] Zeilenga, K., "Lightweight Directory Access Protocol
+ (LDAP): Technical Specification Road Map", RFC 4510,
+ June 2006.
+
+
+
+
+Newman, et al. Standards Track [Page 25]
+
+RFC 5802 SCRAM July 2010
+
+
+ [RFC4616] Zeilenga, K., "The PLAIN Simple Authentication and
+ Security Layer (SASL) Mechanism", RFC 4616, August 2006.
+
+ [RFC4949] Shirey, R., "Internet Security Glossary, Version 2",
+ RFC 4949, August 2007.
+
+ [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
+ IANA Considerations Section in RFCs", BCP 26, RFC 5226,
+ May 2008.
+
+ [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security
+ (TLS) Protocol Version 1.2", RFC 5246, August 2008.
+
+ [RFC5803] Melnikov, A., "Lightweight Directory Access Protocol
+ (LDAP) Schema for Storing Salted Challenge Response
+ Authentication Mechanism (SCRAM) Secrets", RFC 5803,
+ July 2010.
+
+ [tls-server-end-point]
+ IANA, "Registration of TLS server end-point channel
+ bindings", available from http://www.iana.org, June 2008.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 26]
+
+RFC 5802 SCRAM July 2010
+
+
+Appendix A. Other Authentication Mechanisms
+
+ The DIGEST-MD5 [DIGESTHISTORIC] mechanism has proved to be too
+ complex to implement and test, and thus has poor interoperability.
+ The security layer is often not implemented, and almost never used;
+ everyone uses TLS instead. For a more complete list of problems with
+ DIGEST-MD5 that led to the creation of SCRAM, see [DIGESTHISTORIC].
+
+ The CRAM-MD5 SASL mechanism, while widely deployed, also has some
+ problems. In particular, it is missing some modern SASL features
+ such as support for internationalized usernames and passwords,
+ support for passing of authorization identity, and support for
+ channel bindings. It also doesn't support server authentication.
+ For a more complete list of problems with CRAM-MD5, see
+ [CRAMHISTORIC].
+
+ The PLAIN [RFC4616] SASL mechanism allows a malicious server or
+ eavesdropper to impersonate the authenticating user to any other
+ server for which the user has the same password. It also sends the
+ password in the clear over the network, unless TLS is used. Server
+ authentication is not supported.
+
+Appendix B. Design Motivations
+
+ The following design goals shaped this document. Note that some of
+ the goals have changed since the initial version of the document.
+
+ o The SASL mechanism has all modern SASL features: support for
+ internationalized usernames and passwords, support for passing of
+ authorization identity, and support for channel bindings.
+
+ o The protocol supports mutual authentication.
+
+ o The authentication information stored in the authentication
+ database is not sufficient by itself to impersonate the client.
+
+ o The server does not gain the ability to impersonate the client to
+ other servers (with an exception for server-authorized proxies),
+ unless such other servers allow SCRAM authentication and use the
+ same salt and iteration count for the user.
+
+ o The mechanism is extensible, but (hopefully) not over-engineered
+ in this respect.
+
+ o The mechanism is easier to implement than DIGEST-MD5 in both
+ clients and servers.
+
+
+
+
+
+Newman, et al. Standards Track [Page 27]
+
+RFC 5802 SCRAM July 2010
+
+
+Authors' Addresses
+
+ Chris Newman
+ Oracle
+ 800 Royal Oaks
+ Monrovia, CA 91016
+ USA
+
+ EMail: chris.newman@oracle.com
+
+
+ Abhijit Menon-Sen
+ Oryx Mail Systems GmbH
+
+ EMail: ams@toroid.org
+
+
+ Alexey Melnikov
+ Isode, Ltd.
+
+ EMail: Alexey.Melnikov@isode.com
+
+
+ Nicolas Williams
+ Oracle
+ 5300 Riata Trace Ct
+ Austin, TX 78727
+ USA
+
+ EMail: Nicolas.Williams@oracle.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Newman, et al. Standards Track [Page 28]
+
diff --git a/src/common/libManageSieve/doc/SASL SCRAM/rfc7677.txt b/src/common/libManageSieve/doc/SASL SCRAM/rfc7677.txt
new file mode 100644
index 00000000..d90ea868
--- /dev/null
+++ b/src/common/libManageSieve/doc/SASL SCRAM/rfc7677.txt
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) T. Hansen
+Request for Comments: 7677 AT&T Laboratories
+Updates: 5802 November 2015
+Category: Standards Track
+ISSN: 2070-1721
+
+
+ SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
+ Simple Authentication and Security Layer (SASL) Mechanisms
+
+Abstract
+
+ This document registers the Simple Authentication and Security Layer
+ (SASL) mechanisms SCRAM-SHA-256 and SCRAM-SHA-256-PLUS, provides
+ guidance for secure implementation of the original SCRAM-SHA-1-PLUS
+ mechanism, and updates the SCRAM registration procedures of RFC 5802.
+
+Status of This Memo
+
+ This is an Internet Standards Track document.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Further information on
+ Internet Standards is available in Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc7677.
+
+Copyright Notice
+
+ Copyright (c) 2015 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+
+Hansen Standards Track [Page 1]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+Table of Contents
+
+ 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 2. Key Word Definitions . . . . . . . . . . . . . . . . . . . . 2
+ 3. SCRAM-SHA-256 and SCRAM-SHA-256-PLUS . . . . . . . . . . . . 2
+ 4. Security Considerations . . . . . . . . . . . . . . . . . . . 3
+ 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 4
+ 5.1. Updates to SCRAM-* Registration . . . . . . . . . . . . . 4
+ 5.2. SASL-SCRAM Family Mechanisms Registration Procedure . . . 4
+ 6. References . . . . . . . . . . . . . . . . . . . . . . . . . 6
+ 6.1. Normative References . . . . . . . . . . . . . . . . . . 6
+ 6.2. Informative References . . . . . . . . . . . . . . . . . 7
+ Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . 7
+ Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 8
+
+1. Introduction
+
+ This document registers the SASL mechanisms SCRAM-SHA-256 and SCRAM-
+ SHA-256-PLUS. SHA-256 has stronger security properties than SHA-1,
+ and it is expected that SCRAM mechanisms based on it will have
+ greater predicted longevity than the SCRAM mechanisms based on SHA-1.
+
+ The registration form for the SCRAM family of algorithms is also
+ updated from [RFC5802].
+
+ After publication of [RFC5802], it was discovered that Transport
+ Layer Security (TLS) [RFC5246] does not have the expected properties
+ for the "tls-unique" channel binding to be secure [RFC7627].
+ Therefore, this document contains normative text that applies to both
+ the original SCRAM-SHA-1-PLUS and the newly introduced SCRAM-SHA-
+ 256-PLUS mechanism.
+
+2. Key Word Definitions
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [RFC2119].
+
+3. SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
+
+ The SCRAM-SHA-256 and SCRAM-SHA-256-PLUS SASL mechanisms are defined
+ in the same way that SCRAM-SHA-1 and SCRAM-SHA-1-PLUS are defined in
+ [RFC5802], except that the hash function for HMAC() and H() uses
+ SHA-256 instead of SHA-1 [RFC6234].
+
+ For the SCRAM-SHA-256 and SCRAM-SHA-256-PLUS SASL mechanisms, the
+ hash iteration-count announced by a server SHOULD be at least 4096.
+
+
+
+
+Hansen Standards Track [Page 2]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+ The GSS-API mechanism OID for SCRAM-SHA-256 is 1.3.6.1.5.5.18 (see
+ Section 5).
+
+ This is a simple example of a SCRAM-SHA-256 authentication exchange
+ when the client doesn't support channel bindings. The username
+ 'user' and password 'pencil' are being used.
+
+ C: n,,n=user,r=rOprNGfwEbeRWgbNEkqO
+
+ S: r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,
+ s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096
+
+ C: c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,
+ p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=
+
+ S: v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=
+
+4. Security Considerations
+
+ The security considerations from [RFC5802] still apply.
+
+ To be secure, either SCRAM-SHA-256-PLUS and SCRAM-SHA-1-PLUS MUST be
+ used over a TLS channel that has had the session hash extension
+ [RFC7627] negotiated, or session resumption MUST NOT have been used.
+
+ See [RFC4270] and [RFC6194] for reasons to move from SHA-1 to a
+ strong security mechanism like SHA-256.
+
+ The strength of this mechanism is dependent in part on the hash
+ iteration-count, as denoted by "i" in [RFC5802]. As a rule of thumb,
+ the hash iteration-count should be such that a modern machine will
+ take 0.1 seconds to perform the complete algorithm; however, this is
+ unlikely to be practical on mobile devices and other relatively low-
+ performance systems. At the time this was written, the rule of thumb
+ gives around 15,000 iterations required; however, a hash iteration-
+ count of 4096 takes around 0.5 seconds on current mobile handsets.
+ This computational cost can be avoided by caching the ClientKey
+ (assuming the Salt and hash iteration-count is stable). Therefore,
+ the recommendation of this specification is that the hash iteration-
+ count SHOULD be at least 4096, but careful consideration ought to be
+ given to using a significantly higher value, particularly where
+ mobile use is less important.
+
+
+
+
+
+
+
+
+
+Hansen Standards Track [Page 3]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+5. IANA Considerations
+
+5.1. Updates to SCRAM-* Registration
+
+ The IANA registry for SCRAM-* (the SCRAM family of SASL mechanisms)
+ in the SASL mechanism registry ([RFC4422]) has been updated as
+ follows. The email address for reviews has been updated, and the
+ note at the end changed.
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL family SCRAM
+
+ SASL mechanism name (or prefix for the family): SCRAM-*
+ Security considerations: Section 7 of [RFC5802]
+ Published specification (optional, recommended): RFC 7677
+ Person & email address to contact for further information:
+ IETF KITTEN WG <kitten@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note: Members of this family MUST be explicitly registered using
+ the "IETF Review" [RFC5226] registration procedure. Reviews
+ MUST be requested on the KITTEN mailing list kitten@ietf.org
+ (or a successor designated by the responsible Security AD).
+
+ Note to future SCRAM-mechanism designers: each new SASL SCRAM
+ mechanism MUST be explicitly registered with IANA within the SASL
+ SCRAM Family Mechanisms registry.
+
+5.2. SASL-SCRAM Family Mechanisms Registration Procedure
+
+ A new IANA registry has been added for members of the SCRAM family of
+ SASL mechanisms, named "SASL SCRAM Family Mechanisms". It adds two
+ new fields to the existing SCRAM mechanism registry: Minimum
+ iteration-count and Associated OID. Below is the template for
+ registration of a new SASL family SCRAM. (Note that the string
+ "TBD-BY-IANA" should be left as is, so that it may be filled in at
+ registration time by IANA.)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Hansen Standards Track [Page 4]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL SCRAM family mechanism
+
+ SASL mechanism name (or prefix for the family): SCRAM-<NAME>
+ Security considerations: Section 7 of [RFC5802]
+ Published specification (optional, recommended): RFC 7677
+ Minimum iteration-count: The minimum hash iteration-count that
+ servers SHOULD announce
+ Associated OID: TBD-BY-IANA
+ Person & email address to contact for further information:
+ IETF KITTEN WG <kitten@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+
+ Note: Members of this family MUST be explicitly registered using
+ the "IETF Review" [RFC5226] registration procedure. Reviews MUST
+ be requested on the KITTEN mailing list kitten@ietf.org (or a
+ successor designated by the responsible Security Area Director).
+
+ Note: At publication of a new SASL SCRAM Family Mechanism, IANA
+ SHOULD assign a GSS-API mechanism OID for this mechanism from the
+ iso.org.dod.internet.security.mechanisms prefix (see the "SMI
+ Security for Mechanism Codes" registry) and fill in the value for
+ "TBD-BY-IANA" above. Only one OID needs to be assigned for a
+ SCRAM-<NAME> and SCRAM-<NAME>-PLUS pair. The same OID should be
+ assigned to both entries in the registry.
+
+ Note to future SASL SCRAM mechanism designers: each new SASL SCRAM
+ mechanism MUST be explicitly registered with IANA and MUST comply
+ with the SCRAM-mechanism naming convention defined in Section 4 of
+ [RFC5802].
+
+ The existing entries for SASL SCRAM-SHA-1 and SCRAM-SHA-1-PLUS have
+ been moved from the existing SASL mechanism registry to the "SASL
+ SCRAM Family Mechanisms" registry. At that time, the following
+ values were added:
+
+ Minimum iteration-count: 4096
+ OID: 1.3.6.1.5.5.14 (from [RFC5802])
+
+
+
+
+
+
+
+
+
+
+
+
+Hansen Standards Track [Page 5]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+ The following new SASL SCRAM mechanisms have been added to the "SASL
+ SCRAM Family Mechanisms" registry:
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL SCRAM Family mechanism
+ SCRAM-SHA-256
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-256
+ Security considerations: Section 4 of RFC 7677
+ Published specification (optional, recommended): RFC 7677
+ Minimum iteration-count: 4096
+ OID: 1.3.6.1.5.5.18
+ Person & email address to contact for further information:
+ IETF KITTEN WG <kitten@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note:
+
+ To: iana@iana.org
+ Subject: Registration of a new SASL SCRAM Family mechanism
+ SCRAM-SHA-256-PLUS
+
+ SASL mechanism name (or prefix for the family): SCRAM-SHA-256-PLUS
+ Security considerations: Section 4 of RFC 7677
+ Published specification (optional, recommended): RFC 7677
+ Minimum iteration-count: 4096
+ OID: 1.3.6.1.5.5.18
+ Person & email address to contact for further information:
+ IETF KITTEN WG <kitten@ietf.org>
+ Intended usage: COMMON
+ Owner/Change controller: IESG <iesg@ietf.org>
+ Note:
+
+6. References
+
+6.1. Normative References
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119,
+ DOI 10.17487/RFC2119, March 1997,
+ <http://www.rfc-editor.org/info/rfc2119>.
+
+ [RFC4422] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple
+ Authentication and Security Layer (SASL)", RFC 4422,
+ DOI 10.17487/RFC4422, June 2006,
+ <http://www.rfc-editor.org/info/rfc4422>.
+
+
+
+
+
+Hansen Standards Track [Page 6]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+ [RFC5802] Newman, C., Menon-Sen, A., Melnikov, A., and N. Williams,
+ "Salted Challenge Response Authentication Mechanism
+ (SCRAM) SASL and GSS-API Mechanisms", RFC 5802,
+ DOI 10.17487/RFC5802, July 2010,
+ <http://www.rfc-editor.org/info/rfc5802>.
+
+ [RFC6234] Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms
+ (SHA and SHA-based HMAC and HKDF)", RFC 6234,
+ DOI 10.17487/RFC6234, May 2011,
+ <http://www.rfc-editor.org/info/rfc6234>.
+
+ [RFC7627] Bhargavan, K., Ed., Delignat-Lavaud, A., Pironti, A.,
+ Langley, A., and M. Ray, "Transport Layer Security (TLS)
+ Session Hash and Extended Master Secret Extension",
+ RFC 7627, DOI 10.17487/RFC7627, September 2015,
+ <http://www.rfc-editor.org/info/rfc7627>.
+
+6.2. Informative References
+
+ [RFC4270] Hoffman, P. and B. Schneier, "Attacks on Cryptographic
+ Hashes in Internet Protocols", RFC 4270,
+ DOI 10.17487/RFC4270, November 2005,
+ <http://www.rfc-editor.org/info/rfc4270>.
+
+ [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
+ IANA Considerations Section in RFCs", BCP 26, RFC 5226,
+ DOI 10.17487/RFC5226, May 2008,
+ <http://www.rfc-editor.org/info/rfc5226>.
+
+ [RFC6194] Polk, T., Chen, L., Turner, S., and P. Hoffman, "Security
+ Considerations for the SHA-0 and SHA-1 Message-Digest
+ Algorithms", RFC 6194, DOI 10.17487/RFC6194, March 2011,
+ <http://www.rfc-editor.org/info/rfc6194>.
+
+ [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security
+ (TLS) Protocol Version 1.2", RFC 5246,
+ DOI 10.17487/RFC5246, August 2008,
+ <http://www.rfc-editor.org/info/rfc5246>.
+
+Acknowledgements
+
+ This document benefited from discussions on the KITTEN WG mailing
+ list. The author would like to specially thank Russ Allbery, Dave
+ Cridland, Shawn Emery, Stephen Farrell, Simon Josefsson, Pearl Liang,
+ Alexey Melnikov, Peter Saint-Andre, Robert Sparks, Martin Thompson,
+ and Nico Williams for their comments on this topic.
+
+
+
+
+
+Hansen Standards Track [Page 7]
+
+RFC 7677 SASL SCRAM-SHA-256/SCRAM-SHA-256-PLUS November 2015
+
+
+Author's Address
+
+ Tony Hansen
+ AT&T Laboratories
+ 200 Laurel Ave. South
+ Middletown, NJ 07748
+ United States
+
+ Email: tony+scramsha256@maillennium.att.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Hansen Standards Track [Page 8]
+
diff --git a/src/common/libManageSieve/doc/rfc6234.txt b/src/common/libManageSieve/doc/rfc6234.txt
new file mode 100644
index 00000000..814568ab
--- /dev/null
+++ b/src/common/libManageSieve/doc/rfc6234.txt
@@ -0,0 +1,7115 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) D. Eastlake 3rd
+Request for Comments: 6234 Huawei
+Obsoletes: 4634 T. Hansen
+Updates: 3174 AT&T Labs
+Category: Informational May 2011
+ISSN: 2070-1721
+
+
+ US Secure Hash Algorithms
+ (SHA and SHA-based HMAC and HKDF)
+
+Abstract
+
+ The United States of America has adopted a suite of Secure Hash
+ Algorithms (SHAs), including four beyond SHA-1, as part of a Federal
+ Information Processing Standard (FIPS), namely SHA-224, SHA-256,
+ SHA-384, and SHA-512. This document makes open source code
+ performing these SHA hash functions conveniently available to the
+ Internet community. The sample code supports input strings of
+ arbitrary bit length. Much of the text herein was adapted by the
+ authors from FIPS 180-2.
+
+ This document replaces RFC 4634, fixing errata and adding code for an
+ HMAC-based extract-and-expand Key Derivation Function, HKDF (RFC
+ 5869). As with RFC 4634, code to perform SHA-based Hashed Message
+ Authentication Codes (HMACs) is also included.
+
+Status of This Memo
+
+ This document is not an Internet Standards Track specification; it is
+ published for informational purposes.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Not all documents
+ approved by the IESG are a candidate for any level of Internet
+ Standard; see Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc6234.
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 1]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+Copyright Notice
+
+ Copyright (c) 2011 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 2]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+Table of Contents
+
+ 1. Overview of Contents ............................................4
+ 2. Notation for Bit Strings and Integers ...........................5
+ 3. Operations on Words .............................................6
+ 4. Message Padding and Parsing .....................................8
+ 4.1. SHA-224 and SHA-256 ........................................8
+ 4.2. SHA-384 and SHA-512 ........................................9
+ 5. Functions and Constants Used ...................................10
+ 5.1. SHA-224 and SHA-256 .......................................10
+ 5.2. SHA-384 and SHA-512 .......................................11
+ 6. Computing the Message Digest ...................................12
+ 6.1. SHA-224 and SHA-256 Initialization ........................12
+ 6.2. SHA-224 and SHA-256 Processing ............................13
+ 6.3. SHA-384 and SHA-512 Initialization ........................14
+ 6.4. SHA-384 and SHA-512 Processing ............................15
+ 7. HKDF- and SHA-Based HMACs ......................................17
+ 7.1. SHA-Based HMACs ...........................................17
+ 7.2. HKDF ......................................................17
+ 8. C Code for SHAs, HMAC, and HKDF ................................17
+ 8.1. The Header Files ..........................................21
+ 8.1.1. The .h file ........................................21
+ 8.1.2. stdint-example.h ...................................29
+ 8.1.3. sha-private.h ......................................29
+ 8.2. The SHA Code ..............................................30
+ 8.2.1. sha1.c .............................................30
+ 8.2.2. sha224-256.c .......................................39
+ 8.2.3. sha384-512.c .......................................51
+ 8.2.4. usha.c .............................................73
+ 8.3. The HMAC Code .............................................79
+ 8.4. The HKDF Code .............................................84
+ 8.5. The Test Driver ...........................................91
+ 9. Security Considerations .......................................123
+ 10. Acknowledgements .............................................123
+ 11. References ...................................................124
+ 11.1. Normative References ....................................124
+ 11.2. Informative References ..................................124
+ Appendix: Changes from RFC 4634...................................126
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 3]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+1. Overview of Contents
+
+ This document includes specifications for the United States of
+ America (USA) Federal Information Processing Standard (FIPS) Secure
+ Hash Algorithms (SHAs), code to implement the SHAs, code to implement
+ HMAC (Hashed Message Authentication Code, [RFC2104]) based on the
+ SHAs, and code to implement HKDF (HMAC-based Key Derivation Function,
+ [RFC5869]) based on HMAC. Specifications for HMAC and HKDF are not
+ included as they appear elsewhere in the RFC series [RFC2104]
+ [RFC5869].
+
+ NOTE: Much of the text below is taken from [SHS], and the assertions
+ of the security of the hash algorithms described therein are made by
+ the US Government, the author of [SHS], not by the listed authors of
+ this document. See also [RFC6194] concerning the security of SHA-1.
+
+ The text below specifies Secure Hash Algorithms, SHA-224 [RFC3874],
+ SHA-256, SHA-384, and SHA-512, for computing a condensed
+ representation of a message or a data file. (SHA-1 is specified in
+ [RFC3174].) When a message of any length < 2^64 bits (for SHA-224 and
+ SHA-256) or < 2^128 bits (for SHA-384 and SHA-512) is input to one of
+ these algorithms, the result is an output called a message digest.
+ The message digests range in length from 224 to 512 bits, depending
+ on the algorithm. Secure Hash Algorithms are typically used with
+ other cryptographic algorithms, such as digital signature algorithms
+ and keyed-hash authentication codes, the generation of random numbers
+ [RFC4086], or in key derivation functions.
+
+ The algorithms specified in this document are called secure because
+ it is computationally infeasible to (1) find a message that
+ corresponds to a given message digest, or (2) find two different
+ messages that produce the same message digest. Any change to a
+ message in transit will, with very high probability, result in a
+ different message digest. This will result in a verification failure
+ when the Secure Hash Algorithm is used with a digital signature
+ algorithm or a keyed-hash message authentication algorithm.
+
+ The code provided herein supports input strings of arbitrary bit
+ length. SHA-1's sample code from [RFC3174] has also been updated to
+ handle input strings of arbitrary bit length. Permission is granted
+ for all uses, commercial and non-commercial, of this code.
+
+ This document obsoletes [RFC4634], and the changes from that RFC are
+ summarized in the Appendix.
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 4]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ ASN.1 OIDs (Object Identifiers) for the SHA algorithms, taken from
+ [RFC4055], are as follows:
+
+ id-sha1 OBJECT IDENTIFIER ::= { iso(1)
+ identified-organization(3) oiw(14)
+ secsig(3) algorithms(2) 26 }
+ id-sha224 OBJECT IDENTIFIER ::= {{ joint-iso-itu-t(2)
+ country(16) us(840) organization(1) gov(101)
+ csor(3) nistalgorithm(4) hashalgs(2) 4 }
+ id-sha256 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
+ country(16) us(840) organization(1) gov(101)
+ csor(3) nistalgorithm(4) hashalgs(2) 1 }
+ id-sha384 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
+ country(16) us(840) organization(1) gov(101)
+ csor(3) nistalgorithm(4) hashalgs(2) 2 }
+ id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
+ country(16) us(840) organization(1) gov(101)
+ csor(3) nistalgorithm(4) hashalgs(2) 3 }
+
+ Section 2 below defines the terminology and functions used as
+ building blocks to form these algorithms. Section 3 describes the
+ fundamental operations on words from which these algorithms are
+ built. Section 4 describes how messages are padded up to an integral
+ multiple of the required block size and then parsed into blocks.
+ Section 5 defines the constants and the composite functions used to
+ specify the hash algorithms. Section 6 gives the actual
+ specification for the SHA-224, SHA-256, SHA-384, and SHA-512
+ functions. Section 7 provides pointers to the specification of HMAC
+ keyed message authentication codes and to the specification of an
+ extract-and-expand key derivation function based on HMAC.
+
+ Section 8 gives sample code for the SHA algorithms, for SHA-based
+ HMACs, and for HMAC-based extract-and-expand key derivation function.
+
+2. Notation for Bit Strings and Integers
+
+ The following terminology related to bit strings and integers will be
+ used:
+
+ a. A hex digit is an element of the set {0, 1, ... , 9, A, ... , F}.
+ A hex digit is the representation of a 4-bit string. Examples: 7
+ = 0111, A = 1010.
+
+ b. A word equals a 32-bit or 64-bit string that may be represented
+ as a sequence of 8 or 16 hex digits, respectively. To convert a
+ word to hex digits, each 4-bit string is converted to its hex
+ equivalent as described in (a) above. Example:
+
+
+
+
+Eastlake & Hansen Informational [Page 5]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 1010 0001 0000 0011 1111 1110 0010 0011 = A103FE23.
+
+ Throughout this document, the "big-endian" convention is used when
+ expressing both 32-bit and 64-bit words, so that within each word
+ the most significant bit is shown in the leftmost bit position.
+
+ c. An integer may be represented as a word or pair of words.
+
+ An integer between 0 and 2^32 - 1 inclusive may be represented as
+ a 32-bit word. The least significant four bits of the integer are
+ represented by the rightmost hex digit of the word representation.
+ Example: the integer 291 = 2^8+2^5+2^1+2^0 = 256+32+2+1 is
+ represented by the hex word 00000123.
+
+ The same holds true for an integer between 0 and 2^64-1 inclusive,
+ which may be represented as a 64-bit word.
+
+ If Z is an integer, 0 <= z < 2^64, then z = (2^32)x + y where
+ 0 <= x < 2^32 and 0 <= y < 2^32. Since x and y can be represented
+ as words X and Y, respectively, z can be represented as the pair
+ of words (X,Y).
+
+ Again, the "big-endian" convention is used and the most
+ significant word is in the leftmost word position for values
+ represented by multiple-words.
+
+ d. block = 512-bit or 1024-bit string. A block (e.g., B) may be
+ represented as a sequence of 32-bit or 64-bit words.
+
+3. Operations on Words
+
+ The following logical operators will be applied to words in all four
+ hash operations specified herein. SHA-224 and SHA-256 operate on
+ 32-bit words while SHA-384 and SHA-512 operate on 64-bit words.
+
+ In the operations below, x<<n is obtained as follows: discard the
+ leftmost n bits of x and then pad the result with n zeroed bits on
+ the right (the result will still be the same number of bits).
+ Similarly, x>>n is obtained as follows: discard the rightmost n bits
+ of x and then prepend the result with n zeroed bits on the left (the
+ result will still be the same number of bits).
+
+ a. Bitwise logical word operations
+
+ X AND Y = bitwise logical "and" of X and Y.
+
+ X OR Y = bitwise logical "inclusive-or" of X and Y.
+
+
+
+
+Eastlake & Hansen Informational [Page 6]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ X XOR Y = bitwise logical "exclusive-or" of X and Y.
+
+ NOT X = bitwise logical "complement" of X.
+
+ Example:
+ 01101100101110011101001001111011
+ XOR 01100101110000010110100110110111
+ --------------------------------
+ = 00001001011110001011101111001100
+
+ b. The operation X + Y is defined as follows: words X and Y represent
+ w-bit integers x and y, where 0 <= x < 2^w and 0 <= y < 2^w. For
+ positive integers n and m, let
+
+ n mod m
+
+ be the remainder upon dividing n by m. Compute
+
+ z = (x + y) mod 2^w.
+
+ Then 0 <= z < 2^w. Convert z to a word, Z, and define Z = X + Y.
+
+ c. The right shift operation SHR^n(x), where x is a w-bit word and n
+ is an integer with 0 <= n < w, is defined by
+
+ SHR^n(x) = x>>n
+
+ d. The rotate right (circular right shift) operation ROTR^n(x), where
+ x is a w-bit word and n is an integer with 0 <= n < w, is defined
+ by
+
+ ROTR^n(x) = (x>>n) OR (x<<(w-n))
+
+ e. The rotate left (circular left shift) operation ROTL^n(x), where x
+ is a w-bit word and n is an integer with 0 <= n < w, is defined by
+
+ ROTL^n(X) = (x<<n) OR (x>>(w-n))
+
+ Note the following equivalence relationships, where w is fixed in
+ each relationship:
+
+ ROTL^n(x) = ROTR^(w-n)(x)
+
+ ROTR^n(x) = ROTL^(w-n)(x)
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 7]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+4. Message Padding and Parsing
+
+ The hash functions specified herein are used to compute a message
+ digest for a message or data file that is provided as input. The
+ message or data file should be considered to be a bit string. The
+ length of the message is the number of bits in the message (the empty
+ message has length 0). If the number of bits in a message is a
+ multiple of 8, for compactness we can represent the message in hex.
+ The purpose of message padding is to make the total length of a
+ padded message a multiple of 512 for SHA-224 and SHA-256 or a
+ multiple of 1024 for SHA-384 and SHA-512.
+
+ The following specifies how this padding shall be performed. As a
+ summary, a "1" followed by m "0"s followed by a 64-bit or 128-bit
+ integer are appended to the end of the message to produce a padded
+ message of length 512*n or 1024*n. The appended integer is the
+ length of the original message. The padded message is then processed
+ by the hash function as n 512-bit or 1024-bit blocks.
+
+4.1. SHA-224 and SHA-256
+
+ Suppose a message has length L < 2^64. Before it is input to the
+ hash function, the message is padded on the right as follows:
+
+ a. "1" is appended. Example: if the original message is "01010000",
+ this is padded to "010100001".
+
+ b. K "0"s are appended where K is the smallest, non-negative solution
+ to the equation
+
+ ( L + 1 + K ) mod 512 = 448
+
+ c. Then append the 64-bit block that is L in binary representation.
+ After appending this block, the length of the message will be a
+ multiple of 512 bits.
+
+ Example: Suppose the original message is the bit string
+
+ 01100001 01100010 01100011 01100100 01100101
+
+ After step (a) this gives
+
+ 01100001 01100010 01100011 01100100 01100101 1
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 8]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Since L = 40, the number of bits in the above is 41 and K = 407
+ "0"s are appended, making the total now 448. This gives the
+ following in hex:
+
+ 61626364 65800000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000
+
+ The 64-bit representation of L = 40 is hex 00000000 00000028.
+ Hence the final padded message is the following hex
+
+ 61626364 65800000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000028
+
+4.2. SHA-384 and SHA-512
+
+ Suppose a message has length L < 2^128. Before it is input to the
+ hash function, the message is padded on the right as follows:
+
+ a. "1" is appended. Example: if the original message is "01010000",
+ this is padded to "010100001".
+
+ b. K "0"s are appended where K is the smallest, non-negative solution
+ to the equation
+
+ ( L + 1 + K ) mod 1024 = 896
+
+ c. Then append the 128-bit block that is L in binary representation.
+ After appending this block, the length of the message will be a
+ multiple of 1024 bits.
+
+ Example: Suppose the original message is the bit string
+
+ 01100001 01100010 01100011 01100100 01100101
+
+ After step (a) this gives
+
+ 01100001 01100010 01100011 01100100 01100101 1
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 9]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Since L = 40, the number of bits in the above is 41 and K = 855
+ "0"s are appended, making the total now 896. This gives the
+ following in hex:
+
+ 61626364 65800000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+
+ The 128-bit representation of L = 40 is hex 00000000 00000000
+ 00000000 00000028. Hence the final padded message is the
+ following hex:
+
+ 61626364 65800000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000028
+
+5. Functions and Constants Used
+
+ The following subsections give the six logical functions and the
+ table of constants used in each of the hash functions.
+
+5.1. SHA-224 and SHA-256
+
+ SHA-224 and SHA-256 use six logical functions, where each function
+ operates on 32-bit words, which are represented as x, y, and z. The
+ result of each function is a new 32-bit word.
+
+ CH( x, y, z) = (x AND y) XOR ( (NOT x) AND z)
+
+ MAJ( x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
+
+ BSIG0(x) = ROTR^2(x) XOR ROTR^13(x) XOR ROTR^22(x)
+
+ BSIG1(x) = ROTR^6(x) XOR ROTR^11(x) XOR ROTR^25(x)
+
+ SSIG0(x) = ROTR^7(x) XOR ROTR^18(x) XOR SHR^3(x)
+
+ SSIG1(x) = ROTR^17(x) XOR ROTR^19(x) XOR SHR^10(x)
+
+
+
+
+Eastlake & Hansen Informational [Page 10]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ SHA-224 and SHA-256 use the same sequence of sixty-four constant
+ 32-bit words, K0, K1, ..., K63. These words represent the first 32
+ bits of the fractional parts of the cube roots of the first sixty-
+ four prime numbers. In hex, these constant words are as follows
+ (from left to right):
+
+ 428a2f98 71374491 b5c0fbcf e9b5dba5
+ 3956c25b 59f111f1 923f82a4 ab1c5ed5
+ d807aa98 12835b01 243185be 550c7dc3
+ 72be5d74 80deb1fe 9bdc06a7 c19bf174
+ e49b69c1 efbe4786 0fc19dc6 240ca1cc
+ 2de92c6f 4a7484aa 5cb0a9dc 76f988da
+ 983e5152 a831c66d b00327c8 bf597fc7
+ c6e00bf3 d5a79147 06ca6351 14292967
+ 27b70a85 2e1b2138 4d2c6dfc 53380d13
+ 650a7354 766a0abb 81c2c92e 92722c85
+ a2bfe8a1 a81a664b c24b8b70 c76c51a3
+ d192e819 d6990624 f40e3585 106aa070
+ 19a4c116 1e376c08 2748774c 34b0bcb5
+ 391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3
+ 748f82ee 78a5636f 84c87814 8cc70208
+ 90befffa a4506ceb bef9a3f7 c67178f2
+
+5.2. SHA-384 and SHA-512
+
+ SHA-384 and SHA-512 each use six logical functions, where each
+ function operates on 64-bit words, which are represented as x, y, and
+ z. The result of each function is a new 64-bit word.
+
+ CH( x, y, z) = (x AND y) XOR ( (NOT x) AND z)
+
+ MAJ( x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
+
+ BSIG0(x) = ROTR^28(x) XOR ROTR^34(x) XOR ROTR^39(x)
+
+ BSIG1(x) = ROTR^14(x) XOR ROTR^18(x) XOR ROTR^41(x)
+
+ SSIG0(x) = ROTR^1(x) XOR ROTR^8(x) XOR SHR^7(x)
+
+ SSIG1(x) = ROTR^19(x) XOR ROTR^61(x) XOR SHR^6(x)
+
+ SHA-384 and SHA-512 use the same sequence of eighty constant 64-bit
+ words, K0, K1, ... K79. These words represent the first 64 bits of
+ the fractional parts of the cube roots of the first eighty prime
+ numbers. In hex, these constant words are as follows (from left to
+ right):
+
+
+
+
+
+Eastlake & Hansen Informational [Page 11]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 428a2f98d728ae22 7137449123ef65cd b5c0fbcfec4d3b2f e9b5dba58189dbbc
+ 3956c25bf348b538 59f111f1b605d019 923f82a4af194f9b ab1c5ed5da6d8118
+ d807aa98a3030242 12835b0145706fbe 243185be4ee4b28c 550c7dc3d5ffb4e2
+ 72be5d74f27b896f 80deb1fe3b1696b1 9bdc06a725c71235 c19bf174cf692694
+ e49b69c19ef14ad2 efbe4786384f25e3 0fc19dc68b8cd5b5 240ca1cc77ac9c65
+ 2de92c6f592b0275 4a7484aa6ea6e483 5cb0a9dcbd41fbd4 76f988da831153b5
+ 983e5152ee66dfab a831c66d2db43210 b00327c898fb213f bf597fc7beef0ee4
+ c6e00bf33da88fc2 d5a79147930aa725 06ca6351e003826f 142929670a0e6e70
+ 27b70a8546d22ffc 2e1b21385c26c926 4d2c6dfc5ac42aed 53380d139d95b3df
+ 650a73548baf63de 766a0abb3c77b2a8 81c2c92e47edaee6 92722c851482353b
+ a2bfe8a14cf10364 a81a664bbc423001 c24b8b70d0f89791 c76c51a30654be30
+ d192e819d6ef5218 d69906245565a910 f40e35855771202a 106aa07032bbd1b8
+ 19a4c116b8d2d0c8 1e376c085141ab53 2748774cdf8eeb99 34b0bcb5e19b48a8
+ 391c0cb3c5c95a63 4ed8aa4ae3418acb 5b9cca4f7763e373 682e6ff3d6b2b8a3
+ 748f82ee5defb2fc 78a5636f43172f60 84c87814a1f0ab72 8cc702081a6439ec
+ 90befffa23631e28 a4506cebde82bde9 bef9a3f7b2c67915 c67178f2e372532b
+ ca273eceea26619c d186b8c721c0c207 eada7dd6cde0eb1e f57d4f7fee6ed178
+ 06f067aa72176fba 0a637dc5a2c898a6 113f9804bef90dae 1b710b35131c471b
+ 28db77f523047d84 32caab7b40c72493 3c9ebe0a15c9bebc 431d67c49c100d4c
+ 4cc5d4becb3e42b6 597f299cfc657e2a 5fcb6fab3ad6faec 6c44198c4a475817
+
+6. Computing the Message Digest
+
+ The output of each of the secure hash functions, after being applied
+ to a message of N blocks, is the hash quantity H(N). For SHA-224 and
+ SHA-256, H(i) can be considered to be eight 32-bit words, H(i)0,
+ H(i)1, ... H(i)7. For SHA-384 and SHA-512, it can be considered to
+ be eight 64-bit words, H(i)0, H(i)1, ..., H(i)7.
+
+ As described below, the hash words are initialized, modified as each
+ message block is processed, and finally concatenated after processing
+ the last block to yield the output. For SHA-256 and SHA-512, all of
+ the H(N) variables are concatenated while the SHA-224 and SHA-384
+ hashes are produced by omitting some from the final concatenation.
+
+6.1. SHA-224 and SHA-256 Initialization
+
+ For SHA-224, the initial hash value, H(0), consists of the following
+ 32-bit words in hex:
+
+ H(0)0 = c1059ed8
+ H(0)1 = 367cd507
+ H(0)2 = 3070dd17
+ H(0)3 = f70e5939
+ H(0)4 = ffc00b31
+ H(0)5 = 68581511
+ H(0)6 = 64f98fa7
+ H(0)7 = befa4fa4
+
+
+
+Eastlake & Hansen Informational [Page 12]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ For SHA-256, the initial hash value, H(0), consists of the following
+ eight 32-bit words, in hex. These words were obtained by taking the
+ first 32 bits of the fractional parts of the square roots of the
+ first eight prime numbers.
+
+ H(0)0 = 6a09e667
+ H(0)1 = bb67ae85
+ H(0)2 = 3c6ef372
+ H(0)3 = a54ff53a
+ H(0)4 = 510e527f
+ H(0)5 = 9b05688c
+ H(0)6 = 1f83d9ab
+ H(0)7 = 5be0cd19
+
+6.2. SHA-224 and SHA-256 Processing
+
+ SHA-224 and SHA-256 perform identical processing on message blocks
+ and differ only in how H(0) is initialized and how they produce their
+ final output. They may be used to hash a message, M, having a length
+ of L bits, where 0 <= L < 2^64. The algorithm uses (1) a message
+ schedule of sixty-four 32-bit words, (2) eight working variables of
+ 32 bits each, and (3) a hash value of eight 32-bit words.
+
+ The words of the message schedule are labeled W0, W1, ..., W63. The
+ eight working variables are labeled a, b, c, d, e, f, g, and h. The
+ words of the hash value are labeled H(i)0, H(i)1, ..., H(i)7, which
+ will hold the initial hash value, H(0), replaced by each successive
+ intermediate hash value (after each message block is processed),
+ H(i), and ending with the final hash value, H(N), after all N blocks
+ are processed. They also use two temporary words, T1 and T2.
+
+ The input message is padded as described in Section 4.1 above, then
+ parsed into 512-bit blocks that are considered to be composed of
+ sixteen 32-bit words M(i)0, M(i)1, ..., M(i)15. The following
+ computations are then performed for each of the N message blocks.
+ All addition is performed modulo 2^32.
+
+ For i = 1 to N
+
+ 1. Prepare the message schedule W:
+ For t = 0 to 15
+ Wt = M(i)t
+ For t = 16 to 63
+ Wt = SSIG1(W(t-2)) + W(t-7) + SSIG0(w(t-15)) + W(t-16)
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 13]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 2. Initialize the working variables:
+ a = H(i-1)0
+ b = H(i-1)1
+ c = H(i-1)2
+ d = H(i-1)3
+ e = H(i-1)4
+ f = H(i-1)5
+ g = H(i-1)6
+ h = H(i-1)7
+
+ 3. Perform the main hash computation:
+ For t = 0 to 63
+ T1 = h + BSIG1(e) + CH(e,f,g) + Kt + Wt
+ T2 = BSIG0(a) + MAJ(a,b,c)
+ h = g
+ g = f
+ f = e
+ e = d + T1
+ d = c
+ c = b
+ b = a
+ a = T1 + T2
+
+ 4. Compute the intermediate hash value H(i)
+ H(i)0 = a + H(i-1)0
+ H(i)1 = b + H(i-1)1
+ H(i)2 = c + H(i-1)2
+ H(i)3 = d + H(i-1)3
+ H(i)4 = e + H(i-1)4
+ H(i)5 = f + H(i-1)5
+ H(i)6 = g + H(i-1)6
+ H(i)7 = h + H(i-1)7
+
+ After the above computations have been sequentially performed for all
+ of the blocks in the message, the final output is calculated. For
+ SHA-256, this is the concatenation of all of H(N)0, H(N)1, through
+ H(N)7. For SHA-224, this is the concatenation of H(N)0, H(N)1,
+ through H(N)6.
+
+6.3. SHA-384 and SHA-512 Initialization
+
+ For SHA-384, the initial hash value, H(0), consists of the following
+ eight 64-bit words, in hex. These words were obtained by taking the
+ first 64 bits of the fractional parts of the square roots of the
+ ninth through sixteenth prime numbers.
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 14]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ H(0)0 = cbbb9d5dc1059ed8
+ H(0)1 = 629a292a367cd507
+ H(0)2 = 9159015a3070dd17
+ H(0)3 = 152fecd8f70e5939
+ H(0)4 = 67332667ffc00b31
+ H(0)5 = 8eb44a8768581511
+ H(0)6 = db0c2e0d64f98fa7
+ H(0)7 = 47b5481dbefa4fa4
+
+ For SHA-512, the initial hash value, H(0), consists of the following
+ eight 64-bit words, in hex. These words were obtained by taking the
+ first 64 bits of the fractional parts of the square roots of the
+ first eight prime numbers.
+
+ H(0)0 = 6a09e667f3bcc908
+ H(0)1 = bb67ae8584caa73b
+ H(0)2 = 3c6ef372fe94f82b
+ H(0)3 = a54ff53a5f1d36f1
+ H(0)4 = 510e527fade682d1
+ H(0)5 = 9b05688c2b3e6c1f
+ H(0)6 = 1f83d9abfb41bd6b
+ H(0)7 = 5be0cd19137e2179
+
+6.4. SHA-384 and SHA-512 Processing
+
+ SHA-384 and SHA-512 perform identical processing on message blocks
+ and differ only in how H(0) is initialized and how they produce their
+ final output. They may be used to hash a message, M, having a length
+ of L bits, where 0 <= L < 2^128. The algorithm uses (1) a message
+ schedule of eighty 64-bit words, (2) eight working variables of 64
+ bits each, and (3) a hash value of eight 64-bit words.
+
+ The words of the message schedule are labeled W0, W1, ..., W79. The
+ eight working variables are labeled a, b, c, d, e, f, g, and h. The
+ words of the hash value are labeled H(i)0, H(i)1, ..., H(i)7, which
+ will hold the initial hash value, H(0), replaced by each successive
+ intermediate hash value (after each message block is processed),
+ H(i), and ending with the final hash value, H(N) after all N blocks
+ are processed.
+
+ The input message is padded as described in Section 4.2 above, then
+ parsed into 1024-bit blocks that are considered to be composed of
+ sixteen 64-bit words M(i)0, M(i)1, ..., M(i)15. The following
+ computations are then performed for each of the N message blocks.
+ All addition is performed modulo 2^64.
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 15]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ For i = 1 to N
+
+ 1. Prepare the message schedule W:
+ For t = 0 to 15
+ Wt = M(i)t
+ For t = 16 to 79
+ Wt = SSIG1(W(t-2)) + W(t-7) + SSIG0(W(t-15)) + W(t-16)
+
+ 2. Initialize the working variables:
+ a = H(i-1)0
+ b = H(i-1)1
+ c = H(i-1)2
+ d = H(i-1)3
+ e = H(i-1)4
+ f = H(i-1)5
+ g = H(i-1)6
+ h = H(i-1)7
+
+ 3. Perform the main hash computation:
+ For t = 0 to 79
+ T1 = h + BSIG1(e) + CH(e,f,g) + Kt + Wt
+ T2 = BSIG0(a) + MAJ(a,b,c)
+ h = g
+ g = f
+ f = e
+ e = d + T1
+ d = c
+ c = b
+ b = a
+ a = T1 + T2
+
+ 4. Compute the intermediate hash value H(i)
+ H(i)0 = a + H(i-1)0
+ H(i)1 = b + H(i-1)1
+ H(i)2 = c + H(i-1)2
+ H(i)3 = d + H(i-1)3
+ H(i)4 = e + H(i-1)4
+ H(i)5 = f + H(i-1)5
+ H(i)6 = g + H(i-1)6
+ H(i)7 = h + H(i-1)7
+
+ After the above computations have been sequentially performed for all
+ of the blocks in the message, the final output is calculated. For
+ SHA-512, this is the concatenation of all of H(N)0, H(N)1, through
+ H(N)7. For SHA-384, this is the concatenation of H(N)0, H(N)1,
+ through H(N)5.
+
+
+
+
+
+Eastlake & Hansen Informational [Page 16]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+7. HKDF- and SHA-Based HMACs
+
+ Below are brief descriptions and pointers to more complete
+ descriptions and code for (1) SHA-based HMACs and (2) an HMAC-based
+ extract-and-expand key derivation function. Both HKDF and HMAC were
+ devised by Hugo Krawczyk.
+
+7.1. SHA-Based HMACs
+
+ HMAC is a method for computing a keyed MAC (Message Authentication
+ Code) using a hash function as described in [RFC2104]. It uses a key
+ to mix in with the input text to produce the final hash.
+
+ Sample code is also provided, in Section 8.3 below, to perform HMAC
+ based on any of the SHA algorithms described herein. The sample code
+ found in [RFC2104] was written in terms of a specified text size.
+ Since SHA is defined in terms of an arbitrary number of bits, the
+ sample HMAC code has been written to allow the text input to HMAC to
+ have an arbitrary number of octets and bits. A fixed-length
+ interface is also provided.
+
+7.2. HKDF
+
+ HKDF is a specific Key Derivation Function (KDF), that is, a function
+ of initial keying material from which the KDF derives one or more
+ cryptographically strong secret keys. HKDF, which is described in
+ [RFC5869], is based on HMAC.
+
+ Sample code for HKDF is provided in Section 8.4 below.
+
+8. C Code for SHAs, HMAC, and HKDF
+
+ Below is a demonstration implementation of these secure hash
+ functions in C. Section 8.1 contains the header file sha.h that
+ declares all constants, structures, and functions used by the SHA and
+ HMAC functions. It includes conditionals based on the state of
+ definition of USE_32BIT_ONLY that, if that symbol is defined at
+ compile time, avoids 64-bit operations. It also contains sha-
+ private.h that provides some declarations common to all the SHA
+ functions. Section 8.2 contains the C code for sha1.c, sha224-256.c,
+ sha384-512.c, and usha.c. Section 8.3 contains the C code for the
+ HMAC functions, and Section 8.4 contains the C code for HKDF.
+ Section 8.5 contains a test driver to exercise the code.
+
+ For each of the digest lengths $$$, there is the following set of
+ constants, a structure, and functions:
+
+
+
+
+
+Eastlake & Hansen Informational [Page 17]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Constants:
+ SHA$$$HashSize number of octets in the hash
+ SHA$$$HashSizeBits number of bits in the hash
+ SHA$$$_Message_Block_Size
+ number of octets used in the intermediate
+ message blocks
+ Most functions return an enum value that is one of:
+ shaSuccess(0) on success
+ shaNull(1) when presented with a null pointer parameter
+ shaInputTooLong(2) when the input data is too long
+ shaStateError(3) when SHA$$$Input is called after
+ SHA$$$FinalBits or SHA$$$Result
+
+ Structure:
+ typedef SHA$$$Context
+ an opaque structure holding the complete state
+ for producing the hash
+
+ Functions:
+ int SHA$$$Reset(SHA$$$Context *context);
+ Reset the hash context state.
+ int SHA$$$Input(SHA$$$Context *context, const uint8_t *octets,
+ unsigned int bytecount);
+ Incorporate bytecount octets into the hash.
+ int SHA$$$FinalBits(SHA$$$Context *, const uint8_t octet,
+ unsigned int bitcount);
+ Incorporate bitcount bits into the hash. The bits are in
+ the upper portion of the octet. SHA$$$Input() cannot be
+ called after this.
+ int SHA$$$Result(SHA$$$Context *,
+ uint8_t Message_Digest[SHA$$$HashSize]);
+ Do the final calculations on the hash and copy the value
+ into Message_Digest.
+
+ In addition, functions with the prefix USHA are provided that take a
+ SHAversion value (SHA$$$) to select the SHA function suite. They add
+ the following constants, structure, and functions:
+
+ Constants:
+ shaBadParam(4) constant returned by USHA functions when
+ presented with a bad SHAversion (SHA$$$)
+ parameter or other illegal parameter values
+ USAMaxHashSize maximum of the SHA hash sizes
+ SHA$$$ SHAversion enumeration values, used by USHA,
+ HMAC, and HKDF functions to select the SHA
+ function suite
+
+
+
+
+
+Eastlake & Hansen Informational [Page 18]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Structure:
+ typedef USHAContext
+ an opaque structure holding the complete state
+ for producing the hash
+
+ Functions:
+ int USHAReset(USHAContext *context, SHAversion whichSha);
+ Reset the hash context state.
+ int USHAInput(USHAContext context*,
+ const uint8_t *bytes, unsigned int bytecount);
+ Incorporate bytecount octets into the hash.
+ int USHAFinalBits(USHAContext *context,
+ const uint8_t bits, unsigned int bitcount);
+ Incorporate bitcount bits into the hash.
+ int USHAResult(USHAContext *context,
+ uint8_t Message_Digest[USHAMaxHashSize]);
+ Do the final calculations on the hash and copy the value
+ into Message_Digest. Octets in Message_Digest beyond
+ USHAHashSize(whichSha) are left untouched.
+ int USHAHashSize(enum SHAversion whichSha);
+ The number of octets in the given hash.
+ int USHAHashSizeBits(enum SHAversion whichSha);
+ The number of bits in the given hash.
+ int USHABlockSize(enum SHAversion whichSha);
+ The internal block size for the given hash.
+ const char *USHAHashName(enum SHAversion whichSha);
+ This function will return the name of the given SHA
+ algorithm as a string.
+
+ The HMAC functions follow the same pattern to allow any length of
+ text input to be used.
+
+ Structure:
+ typedef HMACContext an opaque structure holding the complete state
+ for producing the keyed message digest (MAC)
+
+ Functions:
+ int hmacReset(HMACContext *ctx, enum SHAversion whichSha,
+ const unsigned char *key, int key_len);
+ Reset the MAC context state.
+ int hmacInput(HMACContext *ctx, const unsigned char *text,
+ int text_len);
+ Incorporate text_len octets into the MAC.
+ int hmacFinalBits(HMACContext *ctx, const uint8_t bits,
+ unsigned int bitcount);
+ Incorporate bitcount bits into the MAC.
+ int hmacResult(HMACContext *ctx,
+ uint8_t Message_Digest[USHAMaxHashSize]);
+
+
+
+Eastlake & Hansen Informational [Page 19]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Do the final calculations on the MAC and copy the value into
+ Message_Digest. Octets in Message_Digest beyond
+ USHAHashSize(whichSha) are left untouched.
+
+ In addition, a combined interface is provided, similar to that shown
+ in [RFC2104], that allows a fixed-length text input to be used.
+
+ int hmac(SHAversion whichSha,
+ const unsigned char *text, int text_len,
+ const unsigned char *key, int key_len,
+ uint8_t Message_Digest[USHAMaxHashSize]);
+ Calculate the given digest for the given text and key, and
+ return the resulting MAC. Octets in Message_Digest beyond
+ USHAHashSize(whichSha) are left untouched.
+
+ The HKDF functions follow the same pattern to allow any length of
+ text input to be used.
+
+ Structure:
+ typedef HKDFContext an opaque structure holding the complete state
+ for producing the keying material
+ Functions:
+ int hkdfReset(HKDFContext *context, enum SHAversion whichSha,
+ const unsigned char *salt, int salt_len)
+ Reset the key derivation state and initialize it with the
+ salt_len octets of the optional salt.
+ int hkdfInput(HKDFContext *context, const unsigned char *ikm,
+ int ikm_len)
+ Incorporate ikm_len octets into the entropy extractor.
+ int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits,
+ unsigned int ikm_bit_count)
+ Incorporate ikm_bit_count bits into the entropy extractor.
+ int hkdfResult(HKDFContext *context,
+ uint8_t prk[USHAMaxHashSize],
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len)
+ Finish the HKDF extraction and perform the final HKDF
+ expansion, storing the okm_len octets into output keying
+ material (okm). Optionally store the pseudo-random key
+ (prk) that is generated internally.
+
+ In addition, combined interfaces are provided, similar to that shown
+ in [RFC5869], that allows a fixed-length text input to be used.
+
+ int hkdfExtract(SHAversion whichSha,
+ const unsigned char *salt, int salt_len,
+ const unsigned char *ikm, int ikm_len,
+ uint8_t prk[USHAMaxHashSize])
+
+
+
+Eastlake & Hansen Informational [Page 20]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Perform HKDF extraction, combining the salt_len octets of
+ the optional salt with the ikm_len octets of the input
+ keying material (ikm) to form the pseudo-random key prk.
+ The output prk must be large enough to hold the octets
+ appropriate for the given hash type.
+
+ int hkdfExpand(SHAversion whichSha,
+ const uint8_t prk[ ], int prk_len,
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len)
+ Perform HKDF expansion, combining the prk_len octets of the
+ pseudo-random key prk with the info_len octets of info to
+ form the okm_len octets stored in okm.
+
+ int hkdf(SHAversion whichSha,
+ const unsigned char *salt, int salt_len,
+ const unsigned char *ikm, int ikm_len,
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len)
+ This combined interface performs both HKDF extraction and
+ expansion. The variables are the same as in hkdfExtract()
+ and hkdfExpand().
+
+8.1. The Header Files
+
+8.1.1. The .h file
+
+ The following sha.h file, as stated in the comments within the file,
+ assumes that <stdint.h> is available on your system. If it is not,
+ you should change to including <stdint-example.h>, provided in
+ Section 8.1.2, or the like.
+
+/**************************** sha.h ****************************/
+/***************** See RFC 6234 for details. *******************/
+/*
+ Copyright (c) 2011 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, are permitted provided that the following
+ conditions are met:
+
+ - Redistributions of source code must retain the above
+ copyright notice, this list of conditions and
+ the following disclaimer.
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 21]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ - Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ - Neither the name of Internet Society, IETF or IETF Trust, nor
+ the names of specific contributors, may be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef _SHA_H_
+#define _SHA_H_
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithms
+ * as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+ * The five hashes are defined in these sizes:
+ * SHA-1 20 byte / 160 bit
+ * SHA-224 28 byte / 224 bit
+ * SHA-256 32 byte / 256 bit
+ * SHA-384 48 byte / 384 bit
+ * SHA-512 64 byte / 512 bit
+ *
+
+
+
+
+Eastlake & Hansen Informational [Page 22]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * Compilation Note:
+ * These files may be compiled with two options:
+ * USE_32BIT_ONLY - use 32-bit arithmetic only, for systems
+ * without 64-bit integers
+ *
+ * USE_MODIFIED_MACROS - use alternate form of the SHA_Ch()
+ * and SHA_Maj() macros that are equivalent
+ * and potentially faster on many systems
+ *
+ */
+
+#include <stdint.h>
+/*
+ * If you do not have the ISO standard stdint.h header file, then you
+ * must typedef the following:
+ * name meaning
+ * uint64_t unsigned 64-bit integer
+ * uint32_t unsigned 32-bit integer
+ * uint8_t unsigned 8-bit integer (i.e., unsigned char)
+ * int_least16_t integer of >= 16 bits
+ *
+ * See stdint-example.h
+ */
+
+#ifndef _SHA_enum_
+#define _SHA_enum_
+/*
+ * All SHA functions return one of these values.
+ */
+enum {
+ shaSuccess = 0,
+ shaNull, /* Null pointer parameter */
+ shaInputTooLong, /* input data too long */
+ shaStateError, /* called Input after FinalBits or Result */
+ shaBadParam /* passed a bad parameter */
+};
+#endif /* _SHA_enum_ */
+
+/*
+ * These constants hold size information for each of the SHA
+ * hashing operations
+ */
+enum {
+ SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64,
+ SHA256_Message_Block_Size = 64, SHA384_Message_Block_Size = 128,
+ SHA512_Message_Block_Size = 128,
+ USHA_Max_Message_Block_Size = SHA512_Message_Block_Size,
+
+
+
+
+Eastlake & Hansen Informational [Page 23]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32,
+ SHA384HashSize = 48, SHA512HashSize = 64,
+ USHAMaxHashSize = SHA512HashSize,
+
+ SHA1HashSizeBits = 160, SHA224HashSizeBits = 224,
+ SHA256HashSizeBits = 256, SHA384HashSizeBits = 384,
+ SHA512HashSizeBits = 512, USHAMaxHashSizeBits = SHA512HashSizeBits
+};
+
+/*
+ * These constants are used in the USHA (Unified SHA) functions.
+ */
+typedef enum SHAversion {
+ SHA1, SHA224, SHA256, SHA384, SHA512
+} SHAversion;
+
+/*
+ * This structure will hold context information for the SHA-1
+ * hashing operation.
+ */
+typedef struct SHA1Context {
+ uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
+
+ uint32_t Length_High; /* Message length in bits */
+ uint32_t Length_Low; /* Message length in bits */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 512-bit message blocks */
+ uint8_t Message_Block[SHA1_Message_Block_Size];
+
+ int Computed; /* Is the hash computed? */
+ int Corrupted; /* Cumulative corruption code */
+} SHA1Context;
+
+/*
+ * This structure will hold context information for the SHA-256
+ * hashing operation.
+ */
+typedef struct SHA256Context {
+ uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */
+
+ uint32_t Length_High; /* Message length in bits */
+ uint32_t Length_Low; /* Message length in bits */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 512-bit message blocks */
+ uint8_t Message_Block[SHA256_Message_Block_Size];
+
+
+
+
+Eastlake & Hansen Informational [Page 24]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ int Computed; /* Is the hash computed? */
+ int Corrupted; /* Cumulative corruption code */
+} SHA256Context;
+
+/*
+ * This structure will hold context information for the SHA-512
+ * hashing operation.
+ */
+typedef struct SHA512Context {
+#ifdef USE_32BIT_ONLY
+ uint32_t Intermediate_Hash[SHA512HashSize/4]; /* Message Digest */
+ uint32_t Length[4]; /* Message length in bits */
+#else /* !USE_32BIT_ONLY */
+ uint64_t Intermediate_Hash[SHA512HashSize/8]; /* Message Digest */
+ uint64_t Length_High, Length_Low; /* Message length in bits */
+#endif /* USE_32BIT_ONLY */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 1024-bit message blocks */
+ uint8_t Message_Block[SHA512_Message_Block_Size];
+
+ int Computed; /* Is the hash computed?*/
+ int Corrupted; /* Cumulative corruption code */
+} SHA512Context;
+
+/*
+ * This structure will hold context information for the SHA-224
+ * hashing operation. It uses the SHA-256 structure for computation.
+ */
+typedef struct SHA256Context SHA224Context;
+
+/*
+ * This structure will hold context information for the SHA-384
+ * hashing operation. It uses the SHA-512 structure for computation.
+ */
+typedef struct SHA512Context SHA384Context;
+
+/*
+ * This structure holds context information for all SHA
+ * hashing operations.
+ */
+typedef struct USHAContext {
+ int whichSha; /* which SHA is being used */
+ union {
+ SHA1Context sha1Context;
+ SHA224Context sha224Context; SHA256Context sha256Context;
+ SHA384Context sha384Context; SHA512Context sha512Context;
+ } ctx;
+
+
+
+Eastlake & Hansen Informational [Page 25]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+} USHAContext;
+
+/*
+ * This structure will hold context information for the HMAC
+ * keyed-hashing operation.
+ */
+typedef struct HMACContext {
+ int whichSha; /* which SHA is being used */
+ int hashSize; /* hash size of SHA being used */
+ int blockSize; /* block size of SHA being used */
+ USHAContext shaContext; /* SHA context */
+ unsigned char k_opad[USHA_Max_Message_Block_Size];
+ /* outer padding - key XORd with opad */
+ int Computed; /* Is the MAC computed? */
+ int Corrupted; /* Cumulative corruption code */
+
+} HMACContext;
+
+/*
+ * This structure will hold context information for the HKDF
+ * extract-and-expand Key Derivation Functions.
+ */
+typedef struct HKDFContext {
+ int whichSha; /* which SHA is being used */
+ HMACContext hmacContext;
+ int hashSize; /* hash size of SHA being used */
+ unsigned char prk[USHAMaxHashSize];
+ /* pseudo-random key - output of hkdfInput */
+ int Computed; /* Is the key material computed? */
+ int Corrupted; /* Cumulative corruption code */
+} HKDFContext;
+
+/*
+ * Function Prototypes
+ */
+
+/* SHA-1 */
+extern int SHA1Reset(SHA1Context *);
+extern int SHA1Input(SHA1Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA1FinalBits(SHA1Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA1Result(SHA1Context *,
+ uint8_t Message_Digest[SHA1HashSize]);
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 26]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/* SHA-224 */
+extern int SHA224Reset(SHA224Context *);
+extern int SHA224Input(SHA224Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA224FinalBits(SHA224Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA224Result(SHA224Context *,
+ uint8_t Message_Digest[SHA224HashSize]);
+
+/* SHA-256 */
+extern int SHA256Reset(SHA256Context *);
+extern int SHA256Input(SHA256Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA256FinalBits(SHA256Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA256Result(SHA256Context *,
+ uint8_t Message_Digest[SHA256HashSize]);
+
+/* SHA-384 */
+extern int SHA384Reset(SHA384Context *);
+extern int SHA384Input(SHA384Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA384FinalBits(SHA384Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA384Result(SHA384Context *,
+ uint8_t Message_Digest[SHA384HashSize]);
+
+/* SHA-512 */
+extern int SHA512Reset(SHA512Context *);
+extern int SHA512Input(SHA512Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA512FinalBits(SHA512Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA512Result(SHA512Context *,
+ uint8_t Message_Digest[SHA512HashSize]);
+
+/* Unified SHA functions, chosen by whichSha */
+extern int USHAReset(USHAContext *context, SHAversion whichSha);
+extern int USHAInput(USHAContext *context,
+ const uint8_t *bytes, unsigned int bytecount);
+extern int USHAFinalBits(USHAContext *context,
+ uint8_t bits, unsigned int bit_count);
+extern int USHAResult(USHAContext *context,
+ uint8_t Message_Digest[USHAMaxHashSize]);
+extern int USHABlockSize(enum SHAversion whichSha);
+extern int USHAHashSize(enum SHAversion whichSha);
+extern int USHAHashSizeBits(enum SHAversion whichSha);
+extern const char *USHAHashName(enum SHAversion whichSha);
+
+
+
+Eastlake & Hansen Informational [Page 27]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * HMAC Keyed-Hashing for Message Authentication, RFC 2104,
+ * for all SHAs.
+ * This interface allows a fixed-length text input to be used.
+ */
+extern int hmac(SHAversion whichSha, /* which SHA algorithm to use */
+ const unsigned char *text, /* pointer to data stream */
+ int text_len, /* length of data stream */
+ const unsigned char *key, /* pointer to authentication key */
+ int key_len, /* length of authentication key */
+ uint8_t digest[USHAMaxHashSize]); /* caller digest to fill in */
+
+/*
+ * HMAC Keyed-Hashing for Message Authentication, RFC 2104,
+ * for all SHAs.
+ * This interface allows any length of text input to be used.
+ */
+extern int hmacReset(HMACContext *context, enum SHAversion whichSha,
+ const unsigned char *key, int key_len);
+extern int hmacInput(HMACContext *context, const unsigned char *text,
+ int text_len);
+extern int hmacFinalBits(HMACContext *context, uint8_t bits,
+ unsigned int bit_count);
+extern int hmacResult(HMACContext *context,
+ uint8_t digest[USHAMaxHashSize]);
+
+/*
+ * HKDF HMAC-based Extract-and-Expand Key Derivation Function,
+ * RFC 5869, for all SHAs.
+ */
+extern int hkdf(SHAversion whichSha, const unsigned char *salt,
+ int salt_len, const unsigned char *ikm, int ikm_len,
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len);
+extern int hkdfExtract(SHAversion whichSha, const unsigned char *salt,
+ int salt_len, const unsigned char *ikm,
+ int ikm_len, uint8_t prk[USHAMaxHashSize]);
+extern int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ],
+ int prk_len, const unsigned char *info,
+ int info_len, uint8_t okm[ ], int okm_len);
+
+/*
+ * HKDF HMAC-based Extract-and-Expand Key Derivation Function,
+ * RFC 5869, for all SHAs.
+ * This interface allows any length of text input to be used.
+ */
+extern int hkdfReset(HKDFContext *context, enum SHAversion whichSha,
+ const unsigned char *salt, int salt_len);
+
+
+
+Eastlake & Hansen Informational [Page 28]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+extern int hkdfInput(HKDFContext *context, const unsigned char *ikm,
+ int ikm_len);
+extern int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits,
+ unsigned int ikm_bit_count);
+extern int hkdfResult(HKDFContext *context,
+ uint8_t prk[USHAMaxHashSize],
+ const unsigned char *info, int info_len,
+ uint8_t okm[USHAMaxHashSize], int okm_len);
+#endif /* _SHA_H_ */
+
+8.1.2. stdint-example.h
+
+If your system does not have <stdint.h>, the following should be
+adequate as a substitute for compiling the other code in this document.
+
+/*********************** stdint-example.h **********************/
+/**** Use this file if your system does not have a stdint.h. ***/
+/***************** See RFC 6234 for details. *******************/
+#ifndef STDINT_H
+#define STDINT_H
+
+typedef unsigned long long uint64_t; /* unsigned 64-bit integer */
+typedef unsigned int uint32_t; /* unsigned 32-bit integer */
+typedef unsigned char uint8_t; /* unsigned 8-bit integer */
+ /* (i.e., unsigned char) */
+typedef int int_least32_t; /* integer of >= 32 bits */
+typedef short int_least16_t; /* integer of >= 16 bits */
+
+#endif /* STDINT_H */
+
+8.1.3. sha-private.h
+
+ The sha-private.h header file contains definitions that should only
+ be needed internally in the other code in this document. These
+ definitions should not be needed in application code calling the code
+ provided in this document.
+
+/************************ sha-private.h ************************/
+/***************** See RFC 6234 for details. *******************/
+#ifndef _SHA_PRIVATE__H
+#define _SHA_PRIVATE__H
+/*
+ * These definitions are defined in FIPS 180-3, section 4.1.
+ * Ch() and Maj() are defined identically in sections 4.1.1,
+ * 4.1.2, and 4.1.3.
+ *
+ * The definitions used in FIPS 180-3 are as follows:
+ */
+
+
+
+Eastlake & Hansen Informational [Page 29]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+#ifndef USE_MODIFIED_MACROS
+#define SHA_Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define SHA_Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#else /* USE_MODIFIED_MACROS */
+/*
+ * The following definitions are equivalent and potentially faster.
+ */
+
+#define SHA_Ch(x, y, z) (((x) & ((y) ^ (z))) ^ (z))
+#define SHA_Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
+
+#endif /* USE_MODIFIED_MACROS */
+
+#define SHA_Parity(x, y, z) ((x) ^ (y) ^ (z))
+
+#endif /* _SHA_PRIVATE__H */
+
+8.2. The SHA Code
+
+ This code is primarily intended as expository reference code and
+ could be optimized further. For example, the assignment rotations
+ through the variables a, b, ..., h could be treated as a cycle and
+ the loop unrolled, rather than doing the explicit copying.
+
+ Note that there are alternative representations of the Ch() and Maj()
+ functions controlled by an ifdef.
+
+8.2.1. sha1.c
+
+/**************************** sha1.c ***************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithm SHA-1
+ * as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+
+
+
+Eastlake & Hansen Informational [Page 30]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The SHA-1 algorithm produces a 160-bit message digest for a
+ * given data stream that can serve as a means of providing a
+ * "fingerprint" for a message.
+ *
+ * Portability Issues:
+ * SHA-1 is defined in terms of 32-bit "words". This code
+ * uses <stdint.h> (included via "sha.h") to define 32- and
+ * 8-bit unsigned integer types. If your C compiler does
+ * not support 32-bit unsigned integers, this code is not
+ * appropriate.
+ *
+ * Caveats:
+ * SHA-1 is designed to work with messages less than 2^64 bits
+ * long. This implementation uses SHA1Input() to hash the bits
+ * that are a multiple of the size of an 8-bit octet, and then
+ * optionally uses SHA1FinalBits() to hash the final few bits of
+ * the input.
+ */
+
+#include "sha.h"
+#include "sha-private.h"
+
+/*
+ * Define the SHA1 circular left shift macro
+ */
+#define SHA1_ROTL(bits,word) \
+ (((word) << (bits)) | ((word) >> (32-(bits))))
+
+/*
+ * Add "length" to the length.
+ * Set Corrupted when overflow has occurred.
+ */
+static uint32_t addTemp;
+#define SHA1AddLength(context, length) \
+ (addTemp = (context)->Length_Low, \
+ (context)->Corrupted = \
+ (((context)->Length_Low += (length)) < addTemp) && \
+ (++(context)->Length_High == 0) ? shaInputTooLong \
+ : (context)->Corrupted )
+
+/* Local Function Prototypes */
+static void SHA1ProcessMessageBlock(SHA1Context *context);
+static void SHA1Finalize(SHA1Context *context, uint8_t Pad_Byte);
+static void SHA1PadMessage(SHA1Context *context, uint8_t Pad_Byte);
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 31]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * SHA1Reset
+ *
+ * Description:
+ * This function will initialize the SHA1Context in preparation
+ * for computing a new SHA1 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA1Reset(SHA1Context *context)
+{
+ if (!context) return shaNull;
+
+ context->Length_High = context->Length_Low = 0;
+ context->Message_Block_Index = 0;
+
+ /* Initial Hash Values: FIPS 180-3 section 5.3.1 */
+ context->Intermediate_Hash[0] = 0x67452301;
+ context->Intermediate_Hash[1] = 0xEFCDAB89;
+ context->Intermediate_Hash[2] = 0x98BADCFE;
+ context->Intermediate_Hash[3] = 0x10325476;
+ context->Intermediate_Hash[4] = 0xC3D2E1F0;
+
+ context->Computed = 0;
+ context->Corrupted = shaSuccess;
+
+ return shaSuccess;
+}
+
+/*
+ * SHA1Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+
+
+
+Eastlake & Hansen Informational [Page 32]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA1Input(SHA1Context *context,
+ const uint8_t *message_array, unsigned length)
+{
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (!message_array) return shaNull;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (context->Corrupted) return context->Corrupted;
+
+ while (length--) {
+ context->Message_Block[context->Message_Block_Index++] =
+ *message_array;
+
+ if ((SHA1AddLength(context, 8) == shaSuccess) &&
+ (context->Message_Block_Index == SHA1_Message_Block_Size))
+ SHA1ProcessMessageBlock(context);
+
+ message_array++;
+ }
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA1FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+
+
+
+Eastlake & Hansen Informational [Page 33]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ */
+int SHA1FinalBits(SHA1Context *context, uint8_t message_bits,
+ unsigned int length)
+{
+ static uint8_t masks[8] = {
+ /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80,
+ /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0,
+ /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8,
+ /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE
+ };
+
+ static uint8_t markbit[8] = {
+ /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40,
+ /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10,
+ /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04,
+ /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01
+ };
+
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (length >= 8) return context->Corrupted = shaBadParam;
+
+ SHA1AddLength(context, length);
+ SHA1Finalize(context,
+ (uint8_t) ((message_bits & masks[length]) | markbit[length]));
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA1Result
+ *
+ * Description:
+ * This function will return the 160-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 19.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA-1 hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+
+
+
+
+Eastlake & Hansen Informational [Page 34]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA1Result(SHA1Context *context,
+ uint8_t Message_Digest[SHA1HashSize])
+{
+ int i;
+
+ if (!context) return shaNull;
+ if (!Message_Digest) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+
+ if (!context->Computed)
+ SHA1Finalize(context, 0x80);
+
+ for (i = 0; i < SHA1HashSize; ++i)
+ Message_Digest[i] = (uint8_t) (context->Intermediate_Hash[i>>2]
+ >> (8 * ( 3 - ( i & 0x03 ) )));
+
+ return shaSuccess;
+}
+
+/*
+ * SHA1ProcessMessageBlock
+ *
+ * Description:
+ * This helper function will process the next 512 bits of the
+ * message stored in the Message_Block array.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ *
+ * Returns:
+ * Nothing.
+ *
+ * Comments:
+ * Many of the variable names in this code, especially the
+ * single character names, were used because those were the
+ * names used in the Secure Hash Standard.
+ */
+static void SHA1ProcessMessageBlock(SHA1Context *context)
+{
+ /* Constants defined in FIPS 180-3, section 4.2.1 */
+ const uint32_t K[4] = {
+ 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6
+ };
+
+
+
+Eastlake & Hansen Informational [Page 35]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ int t; /* Loop counter */
+ uint32_t temp; /* Temporary word value */
+ uint32_t W[80]; /* Word sequence */
+ uint32_t A, B, C, D, E; /* Word buffers */
+
+ /*
+ * Initialize the first 16 words in the array W
+ */
+ for (t = 0; t < 16; t++) {
+ W[t] = ((uint32_t)context->Message_Block[t * 4]) << 24;
+ W[t] |= ((uint32_t)context->Message_Block[t * 4 + 1]) << 16;
+ W[t] |= ((uint32_t)context->Message_Block[t * 4 + 2]) << 8;
+ W[t] |= ((uint32_t)context->Message_Block[t * 4 + 3]);
+ }
+
+ for (t = 16; t < 80; t++)
+ W[t] = SHA1_ROTL(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+
+ A = context->Intermediate_Hash[0];
+ B = context->Intermediate_Hash[1];
+ C = context->Intermediate_Hash[2];
+ D = context->Intermediate_Hash[3];
+ E = context->Intermediate_Hash[4];
+
+ for (t = 0; t < 20; t++) {
+ temp = SHA1_ROTL(5,A) + SHA_Ch(B, C, D) + E + W[t] + K[0];
+ E = D;
+ D = C;
+ C = SHA1_ROTL(30,B);
+ B = A;
+ A = temp;
+ }
+
+ for (t = 20; t < 40; t++) {
+ temp = SHA1_ROTL(5,A) + SHA_Parity(B, C, D) + E + W[t] + K[1];
+ E = D;
+ D = C;
+ C = SHA1_ROTL(30,B);
+ B = A;
+ A = temp;
+ }
+
+ for (t = 40; t < 60; t++) {
+ temp = SHA1_ROTL(5,A) + SHA_Maj(B, C, D) + E + W[t] + K[2];
+ E = D;
+ D = C;
+ C = SHA1_ROTL(30,B);
+ B = A;
+
+
+
+Eastlake & Hansen Informational [Page 36]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ A = temp;
+ }
+
+ for (t = 60; t < 80; t++) {
+ temp = SHA1_ROTL(5,A) + SHA_Parity(B, C, D) + E + W[t] + K[3];
+ E = D;
+ D = C;
+ C = SHA1_ROTL(30,B);
+ B = A;
+ A = temp;
+ }
+
+ context->Intermediate_Hash[0] += A;
+ context->Intermediate_Hash[1] += B;
+ context->Intermediate_Hash[2] += C;
+ context->Intermediate_Hash[3] += D;
+ context->Intermediate_Hash[4] += E;
+ context->Message_Block_Index = 0;
+}
+
+/*
+ * SHA1Finalize
+ *
+ * Description:
+ * This helper function finishes off the digest calculations.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+static void SHA1Finalize(SHA1Context *context, uint8_t Pad_Byte)
+{
+ int i;
+ SHA1PadMessage(context, Pad_Byte);
+ /* message may be sensitive, clear it out */
+ for (i = 0; i < SHA1_Message_Block_Size; ++i)
+ context->Message_Block[i] = 0;
+ context->Length_High = 0; /* and clear length */
+ context->Length_Low = 0;
+
+
+
+Eastlake & Hansen Informational [Page 37]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ context->Computed = 1;
+}
+
+/*
+ * SHA1PadMessage
+ *
+ * Description:
+ * According to the standard, the message must be padded to the next
+ * even multiple of 512 bits. The first padding bit must be a '1'.
+ * The last 64 bits represent the length of the original message.
+ * All bits in between should be 0. This helper function will pad
+ * the message according to those rules by filling the Message_Block
+ * array accordingly. When it returns, it can be assumed that the
+ * message digest has been computed.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to pad.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * Nothing.
+ */
+static void SHA1PadMessage(SHA1Context *context, uint8_t Pad_Byte)
+{
+ /*
+ * Check to see if the current message block is too small to hold
+ * the initial padding bits and length. If so, we will pad the
+ * block, process it, and then continue padding into a second
+ * block.
+ */
+ if (context->Message_Block_Index >= (SHA1_Message_Block_Size - 8)) {
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+ while (context->Message_Block_Index < SHA1_Message_Block_Size)
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+ SHA1ProcessMessageBlock(context);
+ } else
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+
+ while (context->Message_Block_Index < (SHA1_Message_Block_Size - 8))
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+
+
+
+
+Eastlake & Hansen Informational [Page 38]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /*
+ * Store the message length as the last 8 octets
+ */
+ context->Message_Block[56] = (uint8_t) (context->Length_High >> 24);
+ context->Message_Block[57] = (uint8_t) (context->Length_High >> 16);
+ context->Message_Block[58] = (uint8_t) (context->Length_High >> 8);
+ context->Message_Block[59] = (uint8_t) (context->Length_High);
+ context->Message_Block[60] = (uint8_t) (context->Length_Low >> 24);
+ context->Message_Block[61] = (uint8_t) (context->Length_Low >> 16);
+ context->Message_Block[62] = (uint8_t) (context->Length_Low >> 8);
+ context->Message_Block[63] = (uint8_t) (context->Length_Low);
+
+ SHA1ProcessMessageBlock(context);
+}
+
+8.2.2. sha224-256.c
+
+/************************* sha224-256.c ************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithms SHA-224 and
+ * SHA-256 as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+ * The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit
+ * message digests for a given data stream. It should take about
+ * 2**n steps to find a message with the same digest as a given
+ * message and 2**(n/2) to find any two messages with the same
+ * digest, when n is the digest size in bits. Therefore, this
+ * algorithm can serve as a means of providing a
+ * "fingerprint" for a message.
+ *
+ * Portability Issues:
+ * SHA-224 and SHA-256 are defined in terms of 32-bit "words".
+ * This code uses <stdint.h> (included via "sha.h") to define 32-
+ * and 8-bit unsigned integer types. If your C compiler does not
+
+
+
+Eastlake & Hansen Informational [Page 39]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * support 32-bit unsigned integers, this code is not
+ * appropriate.
+ *
+ * Caveats:
+ * SHA-224 and SHA-256 are designed to work with messages less
+ * than 2^64 bits long. This implementation uses SHA224/256Input()
+ * to hash the bits that are a multiple of the size of an 8-bit
+ * octet, and then optionally uses SHA224/256FinalBits()
+ * to hash the final few bits of the input.
+ */
+
+#include "sha.h"
+#include "sha-private.h"
+
+/* Define the SHA shift, rotate left, and rotate right macros */
+#define SHA256_SHR(bits,word) ((word) >> (bits))
+#define SHA256_ROTL(bits,word) \
+ (((word) << (bits)) | ((word) >> (32-(bits))))
+#define SHA256_ROTR(bits,word) \
+ (((word) >> (bits)) | ((word) << (32-(bits))))
+
+/* Define the SHA SIGMA and sigma macros */
+#define SHA256_SIGMA0(word) \
+ (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word))
+#define SHA256_SIGMA1(word) \
+ (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word))
+#define SHA256_sigma0(word) \
+ (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word))
+#define SHA256_sigma1(word) \
+ (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word))
+
+/*
+ * Add "length" to the length.
+ * Set Corrupted when overflow has occurred.
+ */
+static uint32_t addTemp;
+#define SHA224_256AddLength(context, length) \
+ (addTemp = (context)->Length_Low, (context)->Corrupted = \
+ (((context)->Length_Low += (length)) < addTemp) && \
+ (++(context)->Length_High == 0) ? shaInputTooLong : \
+ (context)->Corrupted )
+
+/* Local Function Prototypes */
+static int SHA224_256Reset(SHA256Context *context, uint32_t *H0);
+static void SHA224_256ProcessMessageBlock(SHA256Context *context);
+static void SHA224_256Finalize(SHA256Context *context,
+ uint8_t Pad_Byte);
+static void SHA224_256PadMessage(SHA256Context *context,
+
+
+
+Eastlake & Hansen Informational [Page 40]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ uint8_t Pad_Byte);
+static int SHA224_256ResultN(SHA256Context *context,
+ uint8_t Message_Digest[ ], int HashSize);
+
+/* Initial Hash Values: FIPS 180-3 section 5.3.2 */
+static uint32_t SHA224_H0[SHA256HashSize/4] = {
+ 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939,
+ 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4
+};
+
+/* Initial Hash Values: FIPS 180-3 section 5.3.3 */
+static uint32_t SHA256_H0[SHA256HashSize/4] = {
+ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
+ 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
+};
+
+/*
+ * SHA224Reset
+ *
+ * Description:
+ * This function will initialize the SHA224Context in preparation
+ * for computing a new SHA224 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224Reset(SHA224Context *context)
+{
+ return SHA224_256Reset(context, SHA224_H0);
+}
+
+/*
+ * SHA224Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+
+
+
+Eastlake & Hansen Informational [Page 41]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA224Input(SHA224Context *context, const uint8_t *message_array,
+ unsigned int length)
+{
+ return SHA256Input(context, message_array, length);
+}
+
+/*
+ * SHA224FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224FinalBits(SHA224Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ return SHA256FinalBits(context, message_bits, length);
+}
+
+/*
+ * SHA224Result
+ *
+ * Description:
+ * This function will return the 224-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 27.
+ *
+
+
+
+Eastlake & Hansen Informational [Page 42]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224Result(SHA224Context *context,
+ uint8_t Message_Digest[SHA224HashSize])
+{
+ return SHA224_256ResultN(context, Message_Digest, SHA224HashSize);
+}
+
+/*
+ * SHA256Reset
+ *
+ * Description:
+ * This function will initialize the SHA256Context in preparation
+ * for computing a new SHA256 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256Reset(SHA256Context *context)
+{
+ return SHA224_256Reset(context, SHA256_H0);
+}
+
+/*
+ * SHA256Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+
+
+
+
+Eastlake & Hansen Informational [Page 43]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256Input(SHA256Context *context, const uint8_t *message_array,
+ unsigned int length)
+{
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (!message_array) return shaNull;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (context->Corrupted) return context->Corrupted;
+
+ while (length--) {
+ context->Message_Block[context->Message_Block_Index++] =
+ *message_array;
+
+ if ((SHA224_256AddLength(context, 8) == shaSuccess) &&
+ (context->Message_Block_Index == SHA256_Message_Block_Size))
+ SHA224_256ProcessMessageBlock(context);
+
+ message_array++;
+ }
+
+ return context->Corrupted;
+
+}
+
+/*
+ * SHA256FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+
+
+
+Eastlake & Hansen Informational [Page 44]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ */
+int SHA256FinalBits(SHA256Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ static uint8_t masks[8] = {
+ /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80,
+ /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0,
+ /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8,
+ /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE
+ };
+ static uint8_t markbit[8] = {
+ /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40,
+ /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10,
+ /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04,
+ /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01
+ };
+
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (length >= 8) return context->Corrupted = shaBadParam;
+
+ SHA224_256AddLength(context, length);
+ SHA224_256Finalize(context, (uint8_t)
+ ((message_bits & masks[length]) | markbit[length]));
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA256Result
+ *
+ * Description:
+ * This function will return the 256-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 31.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+
+
+
+Eastlake & Hansen Informational [Page 45]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ */
+int SHA256Result(SHA256Context *context,
+ uint8_t Message_Digest[SHA256HashSize])
+{
+ return SHA224_256ResultN(context, Message_Digest, SHA256HashSize);
+}
+
+/*
+ * SHA224_256Reset
+ *
+ * Description:
+ * This helper function will initialize the SHA256Context in
+ * preparation for computing a new SHA-224 or SHA-256 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * H0[ ]: [in]
+ * The initial hash value array to use.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static int SHA224_256Reset(SHA256Context *context, uint32_t *H0)
+{
+ if (!context) return shaNull;
+
+ context->Length_High = context->Length_Low = 0;
+ context->Message_Block_Index = 0;
+
+ context->Intermediate_Hash[0] = H0[0];
+ context->Intermediate_Hash[1] = H0[1];
+ context->Intermediate_Hash[2] = H0[2];
+ context->Intermediate_Hash[3] = H0[3];
+ context->Intermediate_Hash[4] = H0[4];
+ context->Intermediate_Hash[5] = H0[5];
+ context->Intermediate_Hash[6] = H0[6];
+ context->Intermediate_Hash[7] = H0[7];
+
+ context->Computed = 0;
+ context->Corrupted = shaSuccess;
+
+ return shaSuccess;
+}
+
+/*
+ * SHA224_256ProcessMessageBlock
+ *
+
+
+
+Eastlake & Hansen Informational [Page 46]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * Description:
+ * This helper function will process the next 512 bits of the
+ * message stored in the Message_Block array.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ *
+ * Returns:
+ * Nothing.
+ *
+ * Comments:
+ * Many of the variable names in this code, especially the
+ * single character names, were used because those were the
+ * names used in the Secure Hash Standard.
+ */
+static void SHA224_256ProcessMessageBlock(SHA256Context *context)
+{
+ /* Constants defined in FIPS 180-3, section 4.2.2 */
+ static const uint32_t K[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
+ 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
+ 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
+ 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
+ 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
+ 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
+ 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
+ 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
+ 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ };
+ int t, t4; /* Loop counter */
+ uint32_t temp1, temp2; /* Temporary word value */
+ uint32_t W[64]; /* Word sequence */
+ uint32_t A, B, C, D, E, F, G, H; /* Word buffers */
+
+ /*
+ * Initialize the first 16 words in the array W
+ */
+ for (t = t4 = 0; t < 16; t++, t4 += 4)
+ W[t] = (((uint32_t)context->Message_Block[t4]) << 24) |
+ (((uint32_t)context->Message_Block[t4 + 1]) << 16) |
+ (((uint32_t)context->Message_Block[t4 + 2]) << 8) |
+ (((uint32_t)context->Message_Block[t4 + 3]));
+
+
+
+
+Eastlake & Hansen Informational [Page 47]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ for (t = 16; t < 64; t++)
+ W[t] = SHA256_sigma1(W[t-2]) + W[t-7] +
+ SHA256_sigma0(W[t-15]) + W[t-16];
+
+ A = context->Intermediate_Hash[0];
+ B = context->Intermediate_Hash[1];
+ C = context->Intermediate_Hash[2];
+ D = context->Intermediate_Hash[3];
+ E = context->Intermediate_Hash[4];
+ F = context->Intermediate_Hash[5];
+ G = context->Intermediate_Hash[6];
+ H = context->Intermediate_Hash[7];
+
+ for (t = 0; t < 64; t++) {
+ temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+ temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C);
+ H = G;
+ G = F;
+ F = E;
+ E = D + temp1;
+ D = C;
+ C = B;
+ B = A;
+ A = temp1 + temp2;
+ }
+
+ context->Intermediate_Hash[0] += A;
+ context->Intermediate_Hash[1] += B;
+ context->Intermediate_Hash[2] += C;
+ context->Intermediate_Hash[3] += D;
+ context->Intermediate_Hash[4] += E;
+ context->Intermediate_Hash[5] += F;
+ context->Intermediate_Hash[6] += G;
+ context->Intermediate_Hash[7] += H;
+
+ context->Message_Block_Index = 0;
+}
+
+/*
+ * SHA224_256Finalize
+ *
+ * Description:
+ * This helper function finishes off the digest calculations.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * Pad_Byte: [in]
+
+
+
+Eastlake & Hansen Informational [Page 48]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static void SHA224_256Finalize(SHA256Context *context,
+ uint8_t Pad_Byte)
+{
+ int i;
+ SHA224_256PadMessage(context, Pad_Byte);
+ /* message may be sensitive, so clear it out */
+ for (i = 0; i < SHA256_Message_Block_Size; ++i)
+ context->Message_Block[i] = 0;
+ context->Length_High = 0; /* and clear length */
+ context->Length_Low = 0;
+ context->Computed = 1;
+}
+
+/*
+ * SHA224_256PadMessage
+ *
+ * Description:
+ * According to the standard, the message must be padded to the next
+ * even multiple of 512 bits. The first padding bit must be a '1'.
+ * The last 64 bits represent the length of the original message.
+ * All bits in between should be 0. This helper function will pad
+ * the message according to those rules by filling the
+ * Message_Block array accordingly. When it returns, it can be
+ * assumed that the message digest has been computed.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to pad.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * Nothing.
+ */
+static void SHA224_256PadMessage(SHA256Context *context,
+ uint8_t Pad_Byte)
+{
+
+
+
+Eastlake & Hansen Informational [Page 49]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /*
+ * Check to see if the current message block is too small to hold
+ * the initial padding bits and length. If so, we will pad the
+ * block, process it, and then continue padding into a second
+ * block.
+ */
+ if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) {
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+ while (context->Message_Block_Index < SHA256_Message_Block_Size)
+ context->Message_Block[context->Message_Block_Index++] = 0;
+ SHA224_256ProcessMessageBlock(context);
+ } else
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+
+ while (context->Message_Block_Index < (SHA256_Message_Block_Size-8))
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+ /*
+ * Store the message length as the last 8 octets
+ */
+ context->Message_Block[56] = (uint8_t)(context->Length_High >> 24);
+ context->Message_Block[57] = (uint8_t)(context->Length_High >> 16);
+ context->Message_Block[58] = (uint8_t)(context->Length_High >> 8);
+ context->Message_Block[59] = (uint8_t)(context->Length_High);
+ context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24);
+ context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16);
+ context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8);
+ context->Message_Block[63] = (uint8_t)(context->Length_Low);
+
+ SHA224_256ProcessMessageBlock(context);
+}
+
+/*
+ * SHA224_256ResultN
+ *
+ * Description:
+ * This helper function will return the 224-bit or 256-bit message
+ * digest into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 27/31.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ * HashSize: [in]
+
+
+
+Eastlake & Hansen Informational [Page 50]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The size of the hash, either 28 or 32.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static int SHA224_256ResultN(SHA256Context *context,
+ uint8_t Message_Digest[ ], int HashSize)
+{
+ int i;
+
+ if (!context) return shaNull;
+ if (!Message_Digest) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+
+ if (!context->Computed)
+ SHA224_256Finalize(context, 0x80);
+
+ for (i = 0; i < HashSize; ++i)
+ Message_Digest[i] = (uint8_t)
+ (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ));
+
+ return shaSuccess;
+}
+
+8.2.3. sha384-512.c
+
+/************************* sha384-512.c ************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithms SHA-384 and
+ * SHA-512 as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+ * The SHA-384 and SHA-512 algorithms produce 384-bit and 512-bit
+ * message digests for a given data stream. It should take about
+ * 2**n steps to find a message with the same digest as a given
+
+
+
+Eastlake & Hansen Informational [Page 51]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * message and 2**(n/2) to find any two messages with the same
+ * digest, when n is the digest size in bits. Therefore, this
+ * algorithm can serve as a means of providing a
+ * "fingerprint" for a message.
+ *
+ * Portability Issues:
+ * SHA-384 and SHA-512 are defined in terms of 64-bit "words",
+ * but if USE_32BIT_ONLY is #defined, this code is implemented in
+ * terms of 32-bit "words". This code uses <stdint.h> (included
+ * via "sha.h") to define the 64-, 32- and 8-bit unsigned integer
+ * types. If your C compiler does not support 64-bit unsigned
+ * integers and you do not #define USE_32BIT_ONLY, this code is
+ * not appropriate.
+ *
+ * Caveats:
+ * SHA-384 and SHA-512 are designed to work with messages less
+ * than 2^128 bits long. This implementation uses SHA384/512Input()
+ * to hash the bits that are a multiple of the size of an 8-bit
+ * octet, and then optionally uses SHA384/256FinalBits()
+ * to hash the final few bits of the input.
+ *
+ */
+
+#include "sha.h"
+
+#ifdef USE_32BIT_ONLY
+/*
+ * Define 64-bit arithmetic in terms of 32-bit arithmetic.
+ * Each 64-bit number is represented in a 2-word array.
+ * All macros are defined such that the result is the last parameter.
+ */
+
+/*
+ * Define shift, rotate left, and rotate right functions
+ */
+#define SHA512_SHR(bits, word, ret) ( \
+ /* (((uint64_t)((word))) >> (bits)) */ \
+ (ret)[0] = (((bits) < 32) && ((bits) >= 0)) ? \
+ ((word)[0] >> (bits)) : 0, \
+ (ret)[1] = ((bits) > 32) ? ((word)[0] >> ((bits) - 32)) : \
+ ((bits) == 32) ? (word)[0] : \
+ ((bits) >= 0) ? \
+ (((word)[0] << (32 - (bits))) | \
+ ((word)[1] >> (bits))) : 0 )
+
+#define SHA512_SHL(bits, word, ret) ( \
+ /* (((uint64_t)(word)) << (bits)) */ \
+ (ret)[0] = ((bits) > 32) ? ((word)[1] << ((bits) - 32)) : \
+
+
+
+Eastlake & Hansen Informational [Page 52]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ ((bits) == 32) ? (word)[1] : \
+ ((bits) >= 0) ? \
+ (((word)[0] << (bits)) | \
+ ((word)[1] >> (32 - (bits)))) : \
+ 0, \
+ (ret)[1] = (((bits) < 32) && ((bits) >= 0)) ? \
+ ((word)[1] << (bits)) : 0 )
+
+/*
+ * Define 64-bit OR
+ */
+#define SHA512_OR(word1, word2, ret) ( \
+ (ret)[0] = (word1)[0] | (word2)[0], \
+ (ret)[1] = (word1)[1] | (word2)[1] )
+
+/*
+ * Define 64-bit XOR
+ */
+#define SHA512_XOR(word1, word2, ret) ( \
+ (ret)[0] = (word1)[0] ^ (word2)[0], \
+ (ret)[1] = (word1)[1] ^ (word2)[1] )
+
+/*
+ * Define 64-bit AND
+ */
+#define SHA512_AND(word1, word2, ret) ( \
+ (ret)[0] = (word1)[0] & (word2)[0], \
+ (ret)[1] = (word1)[1] & (word2)[1] )
+
+/*
+ * Define 64-bit TILDA
+ */
+#define SHA512_TILDA(word, ret) \
+ ( (ret)[0] = ~(word)[0], (ret)[1] = ~(word)[1] )
+
+/*
+ * Define 64-bit ADD
+ */
+#define SHA512_ADD(word1, word2, ret) ( \
+ (ret)[1] = (word1)[1], (ret)[1] += (word2)[1], \
+ (ret)[0] = (word1)[0] + (word2)[0] + ((ret)[1] < (word1)[1]) )
+
+/*
+ * Add the 4word value in word2 to word1.
+ */
+static uint32_t ADDTO4_temp, ADDTO4_temp2;
+#define SHA512_ADDTO4(word1, word2) ( \
+ ADDTO4_temp = (word1)[3], \
+
+
+
+Eastlake & Hansen Informational [Page 53]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ (word1)[3] += (word2)[3], \
+ ADDTO4_temp2 = (word1)[2], \
+ (word1)[2] += (word2)[2] + ((word1)[3] < ADDTO4_temp), \
+ ADDTO4_temp = (word1)[1], \
+ (word1)[1] += (word2)[1] + ((word1)[2] < ADDTO4_temp2), \
+ (word1)[0] += (word2)[0] + ((word1)[1] < ADDTO4_temp) )
+
+/*
+ * Add the 2word value in word2 to word1.
+ */
+static uint32_t ADDTO2_temp;
+#define SHA512_ADDTO2(word1, word2) ( \
+ ADDTO2_temp = (word1)[1], \
+ (word1)[1] += (word2)[1], \
+ (word1)[0] += (word2)[0] + ((word1)[1] < ADDTO2_temp) )
+
+/*
+ * SHA rotate ((word >> bits) | (word << (64-bits)))
+ */
+static uint32_t ROTR_temp1[2], ROTR_temp2[2];
+#define SHA512_ROTR(bits, word, ret) ( \
+ SHA512_SHR((bits), (word), ROTR_temp1), \
+ SHA512_SHL(64-(bits), (word), ROTR_temp2), \
+ SHA512_OR(ROTR_temp1, ROTR_temp2, (ret)) )
+
+/*
+ * Define the SHA SIGMA and sigma macros
+ *
+ * SHA512_ROTR(28,word) ^ SHA512_ROTR(34,word) ^ SHA512_ROTR(39,word)
+ */
+static uint32_t SIGMA0_temp1[2], SIGMA0_temp2[2],
+ SIGMA0_temp3[2], SIGMA0_temp4[2];
+#define SHA512_SIGMA0(word, ret) ( \
+ SHA512_ROTR(28, (word), SIGMA0_temp1), \
+ SHA512_ROTR(34, (word), SIGMA0_temp2), \
+ SHA512_ROTR(39, (word), SIGMA0_temp3), \
+ SHA512_XOR(SIGMA0_temp2, SIGMA0_temp3, SIGMA0_temp4), \
+ SHA512_XOR(SIGMA0_temp1, SIGMA0_temp4, (ret)) )
+
+/*
+ * SHA512_ROTR(14,word) ^ SHA512_ROTR(18,word) ^ SHA512_ROTR(41,word)
+ */
+static uint32_t SIGMA1_temp1[2], SIGMA1_temp2[2],
+ SIGMA1_temp3[2], SIGMA1_temp4[2];
+#define SHA512_SIGMA1(word, ret) ( \
+ SHA512_ROTR(14, (word), SIGMA1_temp1), \
+ SHA512_ROTR(18, (word), SIGMA1_temp2), \
+ SHA512_ROTR(41, (word), SIGMA1_temp3), \
+
+
+
+Eastlake & Hansen Informational [Page 54]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ SHA512_XOR(SIGMA1_temp2, SIGMA1_temp3, SIGMA1_temp4), \
+ SHA512_XOR(SIGMA1_temp1, SIGMA1_temp4, (ret)) )
+
+/*
+ * (SHA512_ROTR( 1,word) ^ SHA512_ROTR( 8,word) ^ SHA512_SHR( 7,word))
+ */
+static uint32_t sigma0_temp1[2], sigma0_temp2[2],
+ sigma0_temp3[2], sigma0_temp4[2];
+#define SHA512_sigma0(word, ret) ( \
+ SHA512_ROTR( 1, (word), sigma0_temp1), \
+ SHA512_ROTR( 8, (word), sigma0_temp2), \
+ SHA512_SHR( 7, (word), sigma0_temp3), \
+ SHA512_XOR(sigma0_temp2, sigma0_temp3, sigma0_temp4), \
+ SHA512_XOR(sigma0_temp1, sigma0_temp4, (ret)) )
+
+/*
+ * (SHA512_ROTR(19,word) ^ SHA512_ROTR(61,word) ^ SHA512_SHR( 6,word))
+ */
+static uint32_t sigma1_temp1[2], sigma1_temp2[2],
+ sigma1_temp3[2], sigma1_temp4[2];
+#define SHA512_sigma1(word, ret) ( \
+ SHA512_ROTR(19, (word), sigma1_temp1), \
+ SHA512_ROTR(61, (word), sigma1_temp2), \
+ SHA512_SHR( 6, (word), sigma1_temp3), \
+ SHA512_XOR(sigma1_temp2, sigma1_temp3, sigma1_temp4), \
+ SHA512_XOR(sigma1_temp1, sigma1_temp4, (ret)) )
+
+#ifndef USE_MODIFIED_MACROS
+/*
+ * These definitions are the ones used in FIPS 180-3, section 4.1.3
+ * Ch(x,y,z) ((x & y) ^ (~x & z))
+ */
+static uint32_t Ch_temp1[2], Ch_temp2[2], Ch_temp3[2];
+#define SHA_Ch(x, y, z, ret) ( \
+ SHA512_AND(x, y, Ch_temp1), \
+ SHA512_TILDA(x, Ch_temp2), \
+ SHA512_AND(Ch_temp2, z, Ch_temp3), \
+ SHA512_XOR(Ch_temp1, Ch_temp3, (ret)) )
+
+/*
+ * Maj(x,y,z) (((x)&(y)) ^ ((x)&(z)) ^ ((y)&(z)))
+ */
+static uint32_t Maj_temp1[2], Maj_temp2[2],
+ Maj_temp3[2], Maj_temp4[2];
+#define SHA_Maj(x, y, z, ret) ( \
+ SHA512_AND(x, y, Maj_temp1), \
+ SHA512_AND(x, z, Maj_temp2), \
+ SHA512_AND(y, z, Maj_temp3), \
+
+
+
+Eastlake & Hansen Informational [Page 55]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ SHA512_XOR(Maj_temp2, Maj_temp3, Maj_temp4), \
+ SHA512_XOR(Maj_temp1, Maj_temp4, (ret)) )
+#else /* !USE_MODIFIED_MACROS */
+/*
+ * These definitions are potentially faster equivalents for the ones
+ * used in FIPS 180-3, section 4.1.3.
+ * ((x & y) ^ (~x & z)) becomes
+ * ((x & (y ^ z)) ^ z)
+ */
+#define SHA_Ch(x, y, z, ret) ( \
+ (ret)[0] = (((x)[0] & ((y)[0] ^ (z)[0])) ^ (z)[0]), \
+ (ret)[1] = (((x)[1] & ((y)[1] ^ (z)[1])) ^ (z)[1]) )
+
+/*
+ * ((x & y) ^ (x & z) ^ (y & z)) becomes
+ * ((x & (y | z)) | (y & z))
+ */
+#define SHA_Maj(x, y, z, ret) ( \
+ ret[0] = (((x)[0] & ((y)[0] | (z)[0])) | ((y)[0] & (z)[0])), \
+ ret[1] = (((x)[1] & ((y)[1] | (z)[1])) | ((y)[1] & (z)[1])) )
+#endif /* USE_MODIFIED_MACROS */
+
+/*
+ * Add "length" to the length.
+ * Set Corrupted when overflow has occurred.
+ */
+static uint32_t addTemp[4] = { 0, 0, 0, 0 };
+#define SHA384_512AddLength(context, length) ( \
+ addTemp[3] = (length), SHA512_ADDTO4((context)->Length, addTemp), \
+ (context)->Corrupted = (((context)->Length[3] < (length)) && \
+ ((context)->Length[2] == 0) && ((context)->Length[1] == 0) && \
+ ((context)->Length[0] == 0)) ? shaInputTooLong : \
+ (context)->Corrupted )
+
+/* Local Function Prototypes */
+static int SHA384_512Reset(SHA512Context *context,
+ uint32_t H0[SHA512HashSize/4]);
+static void SHA384_512ProcessMessageBlock(SHA512Context *context);
+static void SHA384_512Finalize(SHA512Context *context,
+ uint8_t Pad_Byte);
+static void SHA384_512PadMessage(SHA512Context *context,
+ uint8_t Pad_Byte);
+static int SHA384_512ResultN( SHA512Context *context,
+ uint8_t Message_Digest[ ], int HashSize);
+
+/* Initial Hash Values: FIPS 180-3 sections 5.3.4 and 5.3.5 */
+static uint32_t SHA384_H0[SHA512HashSize/4] = {
+ 0xCBBB9D5D, 0xC1059ED8, 0x629A292A, 0x367CD507, 0x9159015A,
+
+
+
+Eastlake & Hansen Informational [Page 56]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 0x3070DD17, 0x152FECD8, 0xF70E5939, 0x67332667, 0xFFC00B31,
+ 0x8EB44A87, 0x68581511, 0xDB0C2E0D, 0x64F98FA7, 0x47B5481D,
+ 0xBEFA4FA4
+};
+static uint32_t SHA512_H0[SHA512HashSize/4] = {
+ 0x6A09E667, 0xF3BCC908, 0xBB67AE85, 0x84CAA73B, 0x3C6EF372,
+ 0xFE94F82B, 0xA54FF53A, 0x5F1D36F1, 0x510E527F, 0xADE682D1,
+ 0x9B05688C, 0x2B3E6C1F, 0x1F83D9AB, 0xFB41BD6B, 0x5BE0CD19,
+ 0x137E2179
+};
+
+#else /* !USE_32BIT_ONLY */
+
+#include "sha-private.h"
+
+/* Define the SHA shift, rotate left and rotate right macros */
+#define SHA512_SHR(bits,word) (((uint64_t)(word)) >> (bits))
+#define SHA512_ROTR(bits,word) ((((uint64_t)(word)) >> (bits)) | \
+ (((uint64_t)(word)) << (64-(bits))))
+
+/*
+ * Define the SHA SIGMA and sigma macros
+ *
+ * SHA512_ROTR(28,word) ^ SHA512_ROTR(34,word) ^ SHA512_ROTR(39,word)
+ */
+#define SHA512_SIGMA0(word) \
+ (SHA512_ROTR(28,word) ^ SHA512_ROTR(34,word) ^ SHA512_ROTR(39,word))
+#define SHA512_SIGMA1(word) \
+ (SHA512_ROTR(14,word) ^ SHA512_ROTR(18,word) ^ SHA512_ROTR(41,word))
+#define SHA512_sigma0(word) \
+ (SHA512_ROTR( 1,word) ^ SHA512_ROTR( 8,word) ^ SHA512_SHR( 7,word))
+#define SHA512_sigma1(word) \
+ (SHA512_ROTR(19,word) ^ SHA512_ROTR(61,word) ^ SHA512_SHR( 6,word))
+
+/*
+ * Add "length" to the length.
+ * Set Corrupted when overflow has occurred.
+ */
+static uint64_t addTemp;
+#define SHA384_512AddLength(context, length) \
+ (addTemp = context->Length_Low, context->Corrupted = \
+ ((context->Length_Low += length) < addTemp) && \
+ (++context->Length_High == 0) ? shaInputTooLong : \
+ (context)->Corrupted)
+
+/* Local Function Prototypes */
+static int SHA384_512Reset(SHA512Context *context,
+ uint64_t H0[SHA512HashSize/8]);
+
+
+
+Eastlake & Hansen Informational [Page 57]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+static void SHA384_512ProcessMessageBlock(SHA512Context *context);
+static void SHA384_512Finalize(SHA512Context *context,
+ uint8_t Pad_Byte);
+static void SHA384_512PadMessage(SHA512Context *context,
+ uint8_t Pad_Byte);
+static int SHA384_512ResultN(SHA512Context *context,
+ uint8_t Message_Digest[ ], int HashSize);
+
+/* Initial Hash Values: FIPS 180-3 sections 5.3.4 and 5.3.5 */
+static uint64_t SHA384_H0[ ] = {
+ 0xCBBB9D5DC1059ED8ll, 0x629A292A367CD507ll, 0x9159015A3070DD17ll,
+ 0x152FECD8F70E5939ll, 0x67332667FFC00B31ll, 0x8EB44A8768581511ll,
+ 0xDB0C2E0D64F98FA7ll, 0x47B5481DBEFA4FA4ll
+};
+static uint64_t SHA512_H0[ ] = {
+ 0x6A09E667F3BCC908ll, 0xBB67AE8584CAA73Bll, 0x3C6EF372FE94F82Bll,
+ 0xA54FF53A5F1D36F1ll, 0x510E527FADE682D1ll, 0x9B05688C2B3E6C1Fll,
+ 0x1F83D9ABFB41BD6Bll, 0x5BE0CD19137E2179ll
+};
+
+#endif /* USE_32BIT_ONLY */
+
+/*
+ * SHA384Reset
+ *
+ * Description:
+ * This function will initialize the SHA384Context in preparation
+ * for computing a new SHA384 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA384Reset(SHA384Context *context)
+{
+ return SHA384_512Reset(context, SHA384_H0);
+}
+
+/*
+ * SHA384Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+
+
+
+Eastlake & Hansen Informational [Page 58]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA384Input(SHA384Context *context,
+ const uint8_t *message_array, unsigned int length)
+{
+ return SHA512Input(context, message_array, length);
+}
+
+/*
+ * SHA384FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA384FinalBits(SHA384Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ return SHA512FinalBits(context, message_bits, length);
+}
+
+/*
+ * SHA384Result
+
+
+
+Eastlake & Hansen Informational [Page 59]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ *
+ * Description:
+ * This function will return the 384-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 47.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA384Result(SHA384Context *context,
+ uint8_t Message_Digest[SHA384HashSize])
+{
+ return SHA384_512ResultN(context, Message_Digest, SHA384HashSize);
+}
+
+/*
+ * SHA512Reset
+ *
+ * Description:
+ * This function will initialize the SHA512Context in preparation
+ * for computing a new SHA512 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA512Reset(SHA512Context *context)
+{
+ return SHA384_512Reset(context, SHA512_H0);
+}
+
+/*
+ * SHA512Input
+ *
+ * Description:
+
+
+
+Eastlake & Hansen Informational [Page 60]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA512Input(SHA512Context *context,
+ const uint8_t *message_array,
+ unsigned int length)
+{
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (!message_array) return shaNull;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (context->Corrupted) return context->Corrupted;
+
+ while (length--) {
+ context->Message_Block[context->Message_Block_Index++] =
+ *message_array;
+
+ if ((SHA384_512AddLength(context, 8) == shaSuccess) &&
+ (context->Message_Block_Index == SHA512_Message_Block_Size))
+ SHA384_512ProcessMessageBlock(context);
+
+ message_array++;
+ }
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA512FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+
+
+
+Eastlake & Hansen Informational [Page 61]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA512FinalBits(SHA512Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ static uint8_t masks[8] = {
+ /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80,
+ /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0,
+ /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8,
+ /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE
+ };
+ static uint8_t markbit[8] = {
+ /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40,
+ /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10,
+ /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04,
+ /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01
+ };
+
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (length >= 8) return context->Corrupted = shaBadParam;
+
+ SHA384_512AddLength(context, length);
+ SHA384_512Finalize(context, (uint8_t)
+ ((message_bits & masks[length]) | markbit[length]));
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA512Result
+ *
+ * Description:
+ * This function will return the 512-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+
+
+
+Eastlake & Hansen Informational [Page 62]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 63.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA512Result(SHA512Context *context,
+ uint8_t Message_Digest[SHA512HashSize])
+{
+ return SHA384_512ResultN(context, Message_Digest, SHA512HashSize);
+}
+
+/*
+ * SHA384_512Reset
+ *
+ * Description:
+ * This helper function will initialize the SHA512Context in
+ * preparation for computing a new SHA384 or SHA512 message
+ * digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * H0[ ]: [in]
+ * The initial hash value array to use.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+#ifdef USE_32BIT_ONLY
+static int SHA384_512Reset(SHA512Context *context,
+ uint32_t H0[SHA512HashSize/4])
+#else /* !USE_32BIT_ONLY */
+static int SHA384_512Reset(SHA512Context *context,
+ uint64_t H0[SHA512HashSize/8])
+#endif /* USE_32BIT_ONLY */
+{
+ int i;
+ if (!context) return shaNull;
+
+
+
+
+Eastlake & Hansen Informational [Page 63]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ context->Message_Block_Index = 0;
+
+#ifdef USE_32BIT_ONLY
+ context->Length[0] = context->Length[1] =
+ context->Length[2] = context->Length[3] = 0;
+
+ for (i = 0; i < SHA512HashSize/4; i++)
+ context->Intermediate_Hash[i] = H0[i];
+#else /* !USE_32BIT_ONLY */
+ context->Length_High = context->Length_Low = 0;
+
+ for (i = 0; i < SHA512HashSize/8; i++)
+ context->Intermediate_Hash[i] = H0[i];
+#endif /* USE_32BIT_ONLY */
+
+ context->Computed = 0;
+ context->Corrupted = shaSuccess;
+
+ return shaSuccess;
+}
+
+/*
+ * SHA384_512ProcessMessageBlock
+ *
+ * Description:
+ * This helper function will process the next 1024 bits of the
+ * message stored in the Message_Block array.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ *
+ * Returns:
+ * Nothing.
+ *
+ * Comments:
+ * Many of the variable names in this code, especially the
+ * single character names, were used because those were the
+ * names used in the Secure Hash Standard.
+ *
+ *
+ */
+static void SHA384_512ProcessMessageBlock(SHA512Context *context)
+{
+#ifdef USE_32BIT_ONLY
+ /* Constants defined in FIPS 180-3, section 4.2.3 */
+ static const uint32_t K[80*2] = {
+ 0x428A2F98, 0xD728AE22, 0x71374491, 0x23EF65CD, 0xB5C0FBCF,
+
+
+
+Eastlake & Hansen Informational [Page 64]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 0xEC4D3B2F, 0xE9B5DBA5, 0x8189DBBC, 0x3956C25B, 0xF348B538,
+ 0x59F111F1, 0xB605D019, 0x923F82A4, 0xAF194F9B, 0xAB1C5ED5,
+ 0xDA6D8118, 0xD807AA98, 0xA3030242, 0x12835B01, 0x45706FBE,
+ 0x243185BE, 0x4EE4B28C, 0x550C7DC3, 0xD5FFB4E2, 0x72BE5D74,
+ 0xF27B896F, 0x80DEB1FE, 0x3B1696B1, 0x9BDC06A7, 0x25C71235,
+ 0xC19BF174, 0xCF692694, 0xE49B69C1, 0x9EF14AD2, 0xEFBE4786,
+ 0x384F25E3, 0x0FC19DC6, 0x8B8CD5B5, 0x240CA1CC, 0x77AC9C65,
+ 0x2DE92C6F, 0x592B0275, 0x4A7484AA, 0x6EA6E483, 0x5CB0A9DC,
+ 0xBD41FBD4, 0x76F988DA, 0x831153B5, 0x983E5152, 0xEE66DFAB,
+ 0xA831C66D, 0x2DB43210, 0xB00327C8, 0x98FB213F, 0xBF597FC7,
+ 0xBEEF0EE4, 0xC6E00BF3, 0x3DA88FC2, 0xD5A79147, 0x930AA725,
+ 0x06CA6351, 0xE003826F, 0x14292967, 0x0A0E6E70, 0x27B70A85,
+ 0x46D22FFC, 0x2E1B2138, 0x5C26C926, 0x4D2C6DFC, 0x5AC42AED,
+ 0x53380D13, 0x9D95B3DF, 0x650A7354, 0x8BAF63DE, 0x766A0ABB,
+ 0x3C77B2A8, 0x81C2C92E, 0x47EDAEE6, 0x92722C85, 0x1482353B,
+ 0xA2BFE8A1, 0x4CF10364, 0xA81A664B, 0xBC423001, 0xC24B8B70,
+ 0xD0F89791, 0xC76C51A3, 0x0654BE30, 0xD192E819, 0xD6EF5218,
+ 0xD6990624, 0x5565A910, 0xF40E3585, 0x5771202A, 0x106AA070,
+ 0x32BBD1B8, 0x19A4C116, 0xB8D2D0C8, 0x1E376C08, 0x5141AB53,
+ 0x2748774C, 0xDF8EEB99, 0x34B0BCB5, 0xE19B48A8, 0x391C0CB3,
+ 0xC5C95A63, 0x4ED8AA4A, 0xE3418ACB, 0x5B9CCA4F, 0x7763E373,
+ 0x682E6FF3, 0xD6B2B8A3, 0x748F82EE, 0x5DEFB2FC, 0x78A5636F,
+ 0x43172F60, 0x84C87814, 0xA1F0AB72, 0x8CC70208, 0x1A6439EC,
+ 0x90BEFFFA, 0x23631E28, 0xA4506CEB, 0xDE82BDE9, 0xBEF9A3F7,
+ 0xB2C67915, 0xC67178F2, 0xE372532B, 0xCA273ECE, 0xEA26619C,
+ 0xD186B8C7, 0x21C0C207, 0xEADA7DD6, 0xCDE0EB1E, 0xF57D4F7F,
+ 0xEE6ED178, 0x06F067AA, 0x72176FBA, 0x0A637DC5, 0xA2C898A6,
+ 0x113F9804, 0xBEF90DAE, 0x1B710B35, 0x131C471B, 0x28DB77F5,
+ 0x23047D84, 0x32CAAB7B, 0x40C72493, 0x3C9EBE0A, 0x15C9BEBC,
+ 0x431D67C4, 0x9C100D4C, 0x4CC5D4BE, 0xCB3E42B6, 0x597F299C,
+ 0xFC657E2A, 0x5FCB6FAB, 0x3AD6FAEC, 0x6C44198C, 0x4A475817
+ };
+ int t, t2, t8; /* Loop counter */
+ uint32_t temp1[2], temp2[2], /* Temporary word values */
+ temp3[2], temp4[2], temp5[2];
+ uint32_t W[2*80]; /* Word sequence */
+ uint32_t A[2], B[2], C[2], D[2], /* Word buffers */
+ E[2], F[2], G[2], H[2];
+
+ /* Initialize the first 16 words in the array W */
+ for (t = t2 = t8 = 0; t < 16; t++, t8 += 8) {
+ W[t2++] = ((((uint32_t)context->Message_Block[t8 ])) << 24) |
+ ((((uint32_t)context->Message_Block[t8 + 1])) << 16) |
+ ((((uint32_t)context->Message_Block[t8 + 2])) << 8) |
+ ((((uint32_t)context->Message_Block[t8 + 3])));
+ W[t2++] = ((((uint32_t)context->Message_Block[t8 + 4])) << 24) |
+ ((((uint32_t)context->Message_Block[t8 + 5])) << 16) |
+ ((((uint32_t)context->Message_Block[t8 + 6])) << 8) |
+
+
+
+Eastlake & Hansen Informational [Page 65]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ ((((uint32_t)context->Message_Block[t8 + 7])));
+ }
+
+ for (t = 16; t < 80; t++, t2 += 2) {
+ /* W[t] = SHA512_sigma1(W[t-2]) + W[t-7] +
+ SHA512_sigma0(W[t-15]) + W[t-16]; */
+ uint32_t *Wt2 = &W[t2-2*2];
+ uint32_t *Wt7 = &W[t2-7*2];
+ uint32_t *Wt15 = &W[t2-15*2];
+ uint32_t *Wt16 = &W[t2-16*2];
+ SHA512_sigma1(Wt2, temp1);
+ SHA512_ADD(temp1, Wt7, temp2);
+ SHA512_sigma0(Wt15, temp1);
+ SHA512_ADD(temp1, Wt16, temp3);
+ SHA512_ADD(temp2, temp3, &W[t2]);
+ }
+
+ A[0] = context->Intermediate_Hash[0];
+ A[1] = context->Intermediate_Hash[1];
+ B[0] = context->Intermediate_Hash[2];
+ B[1] = context->Intermediate_Hash[3];
+ C[0] = context->Intermediate_Hash[4];
+ C[1] = context->Intermediate_Hash[5];
+ D[0] = context->Intermediate_Hash[6];
+ D[1] = context->Intermediate_Hash[7];
+ E[0] = context->Intermediate_Hash[8];
+ E[1] = context->Intermediate_Hash[9];
+ F[0] = context->Intermediate_Hash[10];
+ F[1] = context->Intermediate_Hash[11];
+ G[0] = context->Intermediate_Hash[12];
+ G[1] = context->Intermediate_Hash[13];
+ H[0] = context->Intermediate_Hash[14];
+ H[1] = context->Intermediate_Hash[15];
+
+ for (t = t2 = 0; t < 80; t++, t2 += 2) {
+ /*
+ * temp1 = H + SHA512_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+ */
+ SHA512_SIGMA1(E,temp1);
+ SHA512_ADD(H, temp1, temp2);
+ SHA_Ch(E,F,G,temp3);
+ SHA512_ADD(temp2, temp3, temp4);
+ SHA512_ADD(&K[t2], &W[t2], temp5);
+ SHA512_ADD(temp4, temp5, temp1);
+ /*
+ * temp2 = SHA512_SIGMA0(A) + SHA_Maj(A,B,C);
+ */
+ SHA512_SIGMA0(A,temp3);
+
+
+
+Eastlake & Hansen Informational [Page 66]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ SHA_Maj(A,B,C,temp4);
+ SHA512_ADD(temp3, temp4, temp2);
+ H[0] = G[0]; H[1] = G[1];
+ G[0] = F[0]; G[1] = F[1];
+ F[0] = E[0]; F[1] = E[1];
+ SHA512_ADD(D, temp1, E);
+ D[0] = C[0]; D[1] = C[1];
+ C[0] = B[0]; C[1] = B[1];
+ B[0] = A[0]; B[1] = A[1];
+ SHA512_ADD(temp1, temp2, A);
+ }
+
+ SHA512_ADDTO2(&context->Intermediate_Hash[0], A);
+ SHA512_ADDTO2(&context->Intermediate_Hash[2], B);
+ SHA512_ADDTO2(&context->Intermediate_Hash[4], C);
+ SHA512_ADDTO2(&context->Intermediate_Hash[6], D);
+ SHA512_ADDTO2(&context->Intermediate_Hash[8], E);
+ SHA512_ADDTO2(&context->Intermediate_Hash[10], F);
+ SHA512_ADDTO2(&context->Intermediate_Hash[12], G);
+ SHA512_ADDTO2(&context->Intermediate_Hash[14], H);
+
+#else /* !USE_32BIT_ONLY */
+ /* Constants defined in FIPS 180-3, section 4.2.3 */
+ static const uint64_t K[80] = {
+ 0x428A2F98D728AE22ll, 0x7137449123EF65CDll, 0xB5C0FBCFEC4D3B2Fll,
+ 0xE9B5DBA58189DBBCll, 0x3956C25BF348B538ll, 0x59F111F1B605D019ll,
+ 0x923F82A4AF194F9Bll, 0xAB1C5ED5DA6D8118ll, 0xD807AA98A3030242ll,
+ 0x12835B0145706FBEll, 0x243185BE4EE4B28Cll, 0x550C7DC3D5FFB4E2ll,
+ 0x72BE5D74F27B896Fll, 0x80DEB1FE3B1696B1ll, 0x9BDC06A725C71235ll,
+ 0xC19BF174CF692694ll, 0xE49B69C19EF14AD2ll, 0xEFBE4786384F25E3ll,
+ 0x0FC19DC68B8CD5B5ll, 0x240CA1CC77AC9C65ll, 0x2DE92C6F592B0275ll,
+ 0x4A7484AA6EA6E483ll, 0x5CB0A9DCBD41FBD4ll, 0x76F988DA831153B5ll,
+ 0x983E5152EE66DFABll, 0xA831C66D2DB43210ll, 0xB00327C898FB213Fll,
+ 0xBF597FC7BEEF0EE4ll, 0xC6E00BF33DA88FC2ll, 0xD5A79147930AA725ll,
+ 0x06CA6351E003826Fll, 0x142929670A0E6E70ll, 0x27B70A8546D22FFCll,
+ 0x2E1B21385C26C926ll, 0x4D2C6DFC5AC42AEDll, 0x53380D139D95B3DFll,
+ 0x650A73548BAF63DEll, 0x766A0ABB3C77B2A8ll, 0x81C2C92E47EDAEE6ll,
+ 0x92722C851482353Bll, 0xA2BFE8A14CF10364ll, 0xA81A664BBC423001ll,
+ 0xC24B8B70D0F89791ll, 0xC76C51A30654BE30ll, 0xD192E819D6EF5218ll,
+ 0xD69906245565A910ll, 0xF40E35855771202All, 0x106AA07032BBD1B8ll,
+ 0x19A4C116B8D2D0C8ll, 0x1E376C085141AB53ll, 0x2748774CDF8EEB99ll,
+ 0x34B0BCB5E19B48A8ll, 0x391C0CB3C5C95A63ll, 0x4ED8AA4AE3418ACBll,
+ 0x5B9CCA4F7763E373ll, 0x682E6FF3D6B2B8A3ll, 0x748F82EE5DEFB2FCll,
+ 0x78A5636F43172F60ll, 0x84C87814A1F0AB72ll, 0x8CC702081A6439ECll,
+ 0x90BEFFFA23631E28ll, 0xA4506CEBDE82BDE9ll, 0xBEF9A3F7B2C67915ll,
+ 0xC67178F2E372532Bll, 0xCA273ECEEA26619Cll, 0xD186B8C721C0C207ll,
+ 0xEADA7DD6CDE0EB1Ell, 0xF57D4F7FEE6ED178ll, 0x06F067AA72176FBAll,
+ 0x0A637DC5A2C898A6ll, 0x113F9804BEF90DAEll, 0x1B710B35131C471Bll,
+
+
+
+Eastlake & Hansen Informational [Page 67]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ 0x28DB77F523047D84ll, 0x32CAAB7B40C72493ll, 0x3C9EBE0A15C9BEBCll,
+ 0x431D67C49C100D4Cll, 0x4CC5D4BECB3E42B6ll, 0x597F299CFC657E2All,
+ 0x5FCB6FAB3AD6FAECll, 0x6C44198C4A475817ll
+ };
+ int t, t8; /* Loop counter */
+ uint64_t temp1, temp2; /* Temporary word value */
+ uint64_t W[80]; /* Word sequence */
+ uint64_t A, B, C, D, E, F, G, H; /* Word buffers */
+
+ /*
+ * Initialize the first 16 words in the array W
+ */
+ for (t = t8 = 0; t < 16; t++, t8 += 8)
+ W[t] = ((uint64_t)(context->Message_Block[t8 ]) << 56) |
+ ((uint64_t)(context->Message_Block[t8 + 1]) << 48) |
+ ((uint64_t)(context->Message_Block[t8 + 2]) << 40) |
+ ((uint64_t)(context->Message_Block[t8 + 3]) << 32) |
+ ((uint64_t)(context->Message_Block[t8 + 4]) << 24) |
+ ((uint64_t)(context->Message_Block[t8 + 5]) << 16) |
+ ((uint64_t)(context->Message_Block[t8 + 6]) << 8) |
+ ((uint64_t)(context->Message_Block[t8 + 7]));
+
+ for (t = 16; t < 80; t++)
+ W[t] = SHA512_sigma1(W[t-2]) + W[t-7] +
+ SHA512_sigma0(W[t-15]) + W[t-16];
+ A = context->Intermediate_Hash[0];
+ B = context->Intermediate_Hash[1];
+ C = context->Intermediate_Hash[2];
+ D = context->Intermediate_Hash[3];
+ E = context->Intermediate_Hash[4];
+ F = context->Intermediate_Hash[5];
+ G = context->Intermediate_Hash[6];
+ H = context->Intermediate_Hash[7];
+
+ for (t = 0; t < 80; t++) {
+ temp1 = H + SHA512_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+ temp2 = SHA512_SIGMA0(A) + SHA_Maj(A,B,C);
+ H = G;
+ G = F;
+ F = E;
+ E = D + temp1;
+ D = C;
+ C = B;
+ B = A;
+ A = temp1 + temp2;
+ }
+
+ context->Intermediate_Hash[0] += A;
+
+
+
+Eastlake & Hansen Informational [Page 68]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ context->Intermediate_Hash[1] += B;
+ context->Intermediate_Hash[2] += C;
+ context->Intermediate_Hash[3] += D;
+ context->Intermediate_Hash[4] += E;
+ context->Intermediate_Hash[5] += F;
+ context->Intermediate_Hash[6] += G;
+ context->Intermediate_Hash[7] += H;
+#endif /* USE_32BIT_ONLY */
+
+ context->Message_Block_Index = 0;
+}
+
+/*
+ * SHA384_512Finalize
+ *
+ * Description:
+ * This helper function finishes off the digest calculations.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+static void SHA384_512Finalize(SHA512Context *context,
+ uint8_t Pad_Byte)
+{
+ int_least16_t i;
+ SHA384_512PadMessage(context, Pad_Byte);
+ /* message may be sensitive, clear it out */
+ for (i = 0; i < SHA512_Message_Block_Size; ++i)
+ context->Message_Block[i] = 0;
+#ifdef USE_32BIT_ONLY /* and clear length */
+ context->Length[0] = context->Length[1] = 0;
+ context->Length[2] = context->Length[3] = 0;
+#else /* !USE_32BIT_ONLY */
+ context->Length_High = context->Length_Low = 0;
+#endif /* USE_32BIT_ONLY */
+ context->Computed = 1;
+}
+
+
+
+
+Eastlake & Hansen Informational [Page 69]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * SHA384_512PadMessage
+ *
+ * Description:
+ * According to the standard, the message must be padded to the next
+ * even multiple of 1024 bits. The first padding bit must be a '1'.
+ * The last 128 bits represent the length of the original message.
+ * All bits in between should be 0. This helper function will
+ * pad the message according to those rules by filling the
+ * Message_Block array accordingly. When it returns, it can be
+ * assumed that the message digest has been computed.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to pad.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * Nothing.
+ *
+ */
+static void SHA384_512PadMessage(SHA512Context *context,
+ uint8_t Pad_Byte)
+{
+ /*
+ * Check to see if the current message block is too small to hold
+ * the initial padding bits and length. If so, we will pad the
+ * block, process it, and then continue padding into a second
+ * block.
+ */
+ if (context->Message_Block_Index >= (SHA512_Message_Block_Size-16)) {
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+ while (context->Message_Block_Index < SHA512_Message_Block_Size)
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+ SHA384_512ProcessMessageBlock(context);
+ } else
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+
+ while (context->Message_Block_Index < (SHA512_Message_Block_Size-16))
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+ /*
+ * Store the message length as the last 16 octets
+
+
+
+Eastlake & Hansen Informational [Page 70]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ */
+#ifdef USE_32BIT_ONLY
+ context->Message_Block[112] = (uint8_t)(context->Length[0] >> 24);
+ context->Message_Block[113] = (uint8_t)(context->Length[0] >> 16);
+ context->Message_Block[114] = (uint8_t)(context->Length[0] >> 8);
+ context->Message_Block[115] = (uint8_t)(context->Length[0]);
+ context->Message_Block[116] = (uint8_t)(context->Length[1] >> 24);
+ context->Message_Block[117] = (uint8_t)(context->Length[1] >> 16);
+ context->Message_Block[118] = (uint8_t)(context->Length[1] >> 8);
+ context->Message_Block[119] = (uint8_t)(context->Length[1]);
+
+ context->Message_Block[120] = (uint8_t)(context->Length[2] >> 24);
+ context->Message_Block[121] = (uint8_t)(context->Length[2] >> 16);
+ context->Message_Block[122] = (uint8_t)(context->Length[2] >> 8);
+ context->Message_Block[123] = (uint8_t)(context->Length[2]);
+ context->Message_Block[124] = (uint8_t)(context->Length[3] >> 24);
+ context->Message_Block[125] = (uint8_t)(context->Length[3] >> 16);
+ context->Message_Block[126] = (uint8_t)(context->Length[3] >> 8);
+ context->Message_Block[127] = (uint8_t)(context->Length[3]);
+#else /* !USE_32BIT_ONLY */
+ context->Message_Block[112] = (uint8_t)(context->Length_High >> 56);
+ context->Message_Block[113] = (uint8_t)(context->Length_High >> 48);
+ context->Message_Block[114] = (uint8_t)(context->Length_High >> 40);
+ context->Message_Block[115] = (uint8_t)(context->Length_High >> 32);
+ context->Message_Block[116] = (uint8_t)(context->Length_High >> 24);
+ context->Message_Block[117] = (uint8_t)(context->Length_High >> 16);
+ context->Message_Block[118] = (uint8_t)(context->Length_High >> 8);
+ context->Message_Block[119] = (uint8_t)(context->Length_High);
+
+ context->Message_Block[120] = (uint8_t)(context->Length_Low >> 56);
+ context->Message_Block[121] = (uint8_t)(context->Length_Low >> 48);
+ context->Message_Block[122] = (uint8_t)(context->Length_Low >> 40);
+ context->Message_Block[123] = (uint8_t)(context->Length_Low >> 32);
+ context->Message_Block[124] = (uint8_t)(context->Length_Low >> 24);
+ context->Message_Block[125] = (uint8_t)(context->Length_Low >> 16);
+ context->Message_Block[126] = (uint8_t)(context->Length_Low >> 8);
+ context->Message_Block[127] = (uint8_t)(context->Length_Low);
+#endif /* USE_32BIT_ONLY */
+
+ SHA384_512ProcessMessageBlock(context);
+}
+
+/*
+ * SHA384_512ResultN
+ *
+ * Description:
+ * This helper function will return the 384-bit or 512-bit message
+ * digest into the Message_Digest array provided by the caller.
+
+
+
+Eastlake & Hansen Informational [Page 71]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 47/63.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ * HashSize: [in]
+ * The size of the hash, either 48 or 64.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+static int SHA384_512ResultN(SHA512Context *context,
+ uint8_t Message_Digest[ ], int HashSize)
+{
+ int i;
+#ifdef USE_32BIT_ONLY
+ int i2;
+#endif /* USE_32BIT_ONLY */
+
+ if (!context) return shaNull;
+ if (!Message_Digest) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+
+ if (!context->Computed)
+ SHA384_512Finalize(context, 0x80);
+
+#ifdef USE_32BIT_ONLY
+ for (i = i2 = 0; i < HashSize; ) {
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>24);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>16);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>8);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2++]);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>24);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>16);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>8);
+ Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2++]);
+ }
+#else /* !USE_32BIT_ONLY */
+ for (i = 0; i < HashSize; ++i)
+ Message_Digest[i] = (uint8_t)
+ (context->Intermediate_Hash[i>>3] >> 8 * ( 7 - ( i % 8 ) ));
+#endif /* USE_32BIT_ONLY */
+
+
+
+
+Eastlake & Hansen Informational [Page 72]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ return shaSuccess;
+}
+
+8.2.4. usha.c
+
+/**************************** usha.c ***************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements a unified interface to the SHA algorithms.
+ */
+
+#include "sha.h"
+
+/*
+ * USHAReset
+ *
+ * Description:
+ * This function will initialize the SHA Context in preparation
+ * for computing a new SHA message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * whichSha: [in]
+ * Selects which SHA reset to call
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int USHAReset(USHAContext *context, enum SHAversion whichSha)
+{
+ if (!context) return shaNull;
+ context->whichSha = whichSha;
+ switch (whichSha) {
+ case SHA1: return SHA1Reset((SHA1Context*)&context->ctx);
+ case SHA224: return SHA224Reset((SHA224Context*)&context->ctx);
+ case SHA256: return SHA256Reset((SHA256Context*)&context->ctx);
+ case SHA384: return SHA384Reset((SHA384Context*)&context->ctx);
+ case SHA512: return SHA512Reset((SHA512Context*)&context->ctx);
+ default: return shaBadParam;
+ }
+}
+
+
+
+Eastlake & Hansen Informational [Page 73]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * USHAInput
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int USHAInput(USHAContext *context,
+ const uint8_t *bytes, unsigned int bytecount)
+{
+ if (!context) return shaNull;
+ switch (context->whichSha) {
+ case SHA1:
+ return SHA1Input((SHA1Context*)&context->ctx, bytes,
+ bytecount);
+ case SHA224:
+ return SHA224Input((SHA224Context*)&context->ctx, bytes,
+ bytecount);
+ case SHA256:
+ return SHA256Input((SHA256Context*)&context->ctx, bytes,
+ bytecount);
+ case SHA384:
+ return SHA384Input((SHA384Context*)&context->ctx, bytes,
+ bytecount);
+ case SHA512:
+ return SHA512Input((SHA512Context*)&context->ctx, bytes,
+ bytecount);
+ default: return shaBadParam;
+ }
+}
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 74]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * USHAFinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int USHAFinalBits(USHAContext *context,
+ uint8_t bits, unsigned int bit_count)
+{
+ if (!context) return shaNull;
+ switch (context->whichSha) {
+ case SHA1:
+ return SHA1FinalBits((SHA1Context*)&context->ctx, bits,
+ bit_count);
+ case SHA224:
+ return SHA224FinalBits((SHA224Context*)&context->ctx, bits,
+ bit_count);
+ case SHA256:
+ return SHA256FinalBits((SHA256Context*)&context->ctx, bits,
+ bit_count);
+ case SHA384:
+ return SHA384FinalBits((SHA384Context*)&context->ctx, bits,
+ bit_count);
+ case SHA512:
+ return SHA512FinalBits((SHA512Context*)&context->ctx, bits,
+ bit_count);
+ default: return shaBadParam;
+ }
+}
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 75]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * USHAResult
+ *
+ * Description:
+ * This function will return the message digest of the appropriate
+ * bit size, as returned by USHAHashSizeBits(whichSHA) for the
+ * 'whichSHA' value used in the preceeding call to USHAReset,
+ * into the Message_Digest array provided by the caller.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA-1 hash.
+ * Message_Digest: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int USHAResult(USHAContext *context,
+ uint8_t Message_Digest[USHAMaxHashSize])
+{
+ if (!context) return shaNull;
+ switch (context->whichSha) {
+ case SHA1:
+ return SHA1Result((SHA1Context*)&context->ctx, Message_Digest);
+ case SHA224:
+ return SHA224Result((SHA224Context*)&context->ctx,
+ Message_Digest);
+ case SHA256:
+ return SHA256Result((SHA256Context*)&context->ctx,
+ Message_Digest);
+ case SHA384:
+ return SHA384Result((SHA384Context*)&context->ctx,
+ Message_Digest);
+ case SHA512:
+ return SHA512Result((SHA512Context*)&context->ctx,
+ Message_Digest);
+ default: return shaBadParam;
+ }
+}
+
+/*
+ * USHABlockSize
+ *
+ * Description:
+ * This function will return the blocksize for the given SHA
+ * algorithm.
+
+
+
+Eastlake & Hansen Informational [Page 76]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ *
+ * Parameters:
+ * whichSha:
+ * which SHA algorithm to query
+ *
+ * Returns:
+ * block size
+ *
+ */
+int USHABlockSize(enum SHAversion whichSha)
+{
+ switch (whichSha) {
+ case SHA1: return SHA1_Message_Block_Size;
+ case SHA224: return SHA224_Message_Block_Size;
+ case SHA256: return SHA256_Message_Block_Size;
+ case SHA384: return SHA384_Message_Block_Size;
+ default:
+ case SHA512: return SHA512_Message_Block_Size;
+ }
+}
+
+/*
+ * USHAHashSize
+ *
+ * Description:
+ * This function will return the hashsize for the given SHA
+ * algorithm.
+ *
+ * Parameters:
+ * whichSha:
+ * which SHA algorithm to query
+ *
+ * Returns:
+ * hash size
+ *
+ */
+int USHAHashSize(enum SHAversion whichSha)
+{
+ switch (whichSha) {
+ case SHA1: return SHA1HashSize;
+ case SHA224: return SHA224HashSize;
+ case SHA256: return SHA256HashSize;
+ case SHA384: return SHA384HashSize;
+ default:
+ case SHA512: return SHA512HashSize;
+ }
+}
+
+
+
+
+Eastlake & Hansen Informational [Page 77]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+/*
+ * USHAHashSizeBits
+ *
+ * Description:
+ * This function will return the hashsize for the given SHA
+ * algorithm, expressed in bits.
+ *
+ * Parameters:
+ * whichSha:
+ * which SHA algorithm to query
+ *
+ * Returns:
+ * hash size in bits
+ *
+ */
+int USHAHashSizeBits(enum SHAversion whichSha)
+{
+ switch (whichSha) {
+ case SHA1: return SHA1HashSizeBits;
+ case SHA224: return SHA224HashSizeBits;
+ case SHA256: return SHA256HashSizeBits;
+ case SHA384: return SHA384HashSizeBits;
+ default:
+ case SHA512: return SHA512HashSizeBits;
+ }
+}
+
+/*
+ * USHAHashName
+ *
+ * Description:
+ * This function will return the name of the given SHA algorithm
+ * as a string.
+ *
+ * Parameters:
+ * whichSha:
+ * which SHA algorithm to query
+ *
+ * Returns:
+ * character string with the name in it
+ *
+ */
+const char *USHAHashName(enum SHAversion whichSha)
+{
+ switch (whichSha) {
+ case SHA1: return "SHA1";
+ case SHA224: return "SHA224";
+ case SHA256: return "SHA256";
+
+
+
+Eastlake & Hansen Informational [Page 78]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ case SHA384: return "SHA384";
+ default:
+ case SHA512: return "SHA512";
+ }
+}
+
+8.3. The HMAC Code
+
+/**************************** hmac.c ***************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the HMAC algorithm (Keyed-Hashing for
+ * Message Authentication, [RFC 2104]), expressed in terms of
+ * the various SHA algorithms.
+ */
+
+#include "sha.h"
+
+/*
+ * hmac
+ *
+ * Description:
+ * This function will compute an HMAC message digest.
+ *
+ * Parameters:
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * message_array[ ]: [in]
+ * An array of octets representing the message.
+ * Note: in RFC 2104, this parameter is known
+ * as 'text'.
+ * length: [in]
+ * The length of the message in message_array.
+ * key[ ]: [in]
+ * The secret shared key.
+ * key_len: [in]
+ * The length of the secret shared key.
+ * digest[ ]: [out]
+ * Where the digest is to be returned.
+ * NOTE: The length of the digest is determined by
+ * the value of whichSha.
+ *
+
+
+
+
+Eastlake & Hansen Informational [Page 79]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * Returns:
+ * sha Error Code.
+ *
+ */
+
+int hmac(SHAversion whichSha,
+ const unsigned char *message_array, int length,
+ const unsigned char *key, int key_len,
+ uint8_t digest[USHAMaxHashSize])
+{
+ HMACContext context;
+ return hmacReset(&context, whichSha, key, key_len) ||
+ hmacInput(&context, message_array, length) ||
+ hmacResult(&context, digest);
+}
+
+/*
+ * hmacReset
+ *
+ * Description:
+ * This function will initialize the hmacContext in preparation
+ * for computing a new HMAC message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * key[ ]: [in]
+ * The secret shared key.
+ * key_len: [in]
+ * The length of the secret shared key.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hmacReset(HMACContext *context, enum SHAversion whichSha,
+ const unsigned char *key, int key_len)
+{
+ int i, blocksize, hashsize, ret;
+
+ /* inner padding - key XORd with ipad */
+ unsigned char k_ipad[USHA_Max_Message_Block_Size];
+
+ /* temporary buffer when keylen > blocksize */
+ unsigned char tempkey[USHAMaxHashSize];
+
+
+
+
+Eastlake & Hansen Informational [Page 80]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ if (!context) return shaNull;
+ context->Computed = 0;
+ context->Corrupted = shaSuccess;
+
+ blocksize = context->blockSize = USHABlockSize(whichSha);
+ hashsize = context->hashSize = USHAHashSize(whichSha);
+ context->whichSha = whichSha;
+
+ /*
+ * If key is longer than the hash blocksize,
+ * reset it to key = HASH(key).
+ */
+ if (key_len > blocksize) {
+ USHAContext tcontext;
+ int err = USHAReset(&tcontext, whichSha) ||
+ USHAInput(&tcontext, key, key_len) ||
+ USHAResult(&tcontext, tempkey);
+ if (err != shaSuccess) return err;
+
+ key = tempkey;
+ key_len = hashsize;
+ }
+
+ /*
+ * The HMAC transform looks like:
+ *
+ * SHA(K XOR opad, SHA(K XOR ipad, text))
+ *
+ * where K is an n byte key, 0-padded to a total of blocksize bytes,
+ * ipad is the byte 0x36 repeated blocksize times,
+ * opad is the byte 0x5c repeated blocksize times,
+ * and text is the data being protected.
+ */
+
+ /* store key into the pads, XOR'd with ipad and opad values */
+ for (i = 0; i < key_len; i++) {
+ k_ipad[i] = key[i] ^ 0x36;
+ context->k_opad[i] = key[i] ^ 0x5c;
+ }
+ /* remaining pad bytes are '\0' XOR'd with ipad and opad values */
+ for ( ; i < blocksize; i++) {
+ k_ipad[i] = 0x36;
+ context->k_opad[i] = 0x5c;
+ }
+
+ /* perform inner hash */
+ /* init context for 1st pass */
+ ret = USHAReset(&context->shaContext, whichSha) ||
+
+
+
+Eastlake & Hansen Informational [Page 81]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /* and start with inner pad */
+ USHAInput(&context->shaContext, k_ipad, blocksize);
+ return context->Corrupted = ret;
+}
+
+/*
+ * hmacInput
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message. It may be called multiple times.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The HMAC context to update.
+ * text[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * text_len: [in]
+ * The length of the message in text.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hmacInput(HMACContext *context, const unsigned char *text,
+ int text_len)
+{
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ /* then text of datagram */
+ return context->Corrupted =
+ USHAInput(&context->shaContext, text, text_len);
+}
+
+/*
+ * hmacFinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The HMAC context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+
+
+
+Eastlake & Hansen Informational [Page 82]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int hmacFinalBits(HMACContext *context,
+ uint8_t bits, unsigned int bit_count)
+{
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ /* then final bits of datagram */
+ return context->Corrupted =
+ USHAFinalBits(&context->shaContext, bits, bit_count);
+}
+
+/*
+ * hmacResult
+ *
+ * Description:
+ * This function will return the N-byte message digest into the
+ * Message_Digest array provided by the caller.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the HMAC hash.
+ * digest[ ]: [out]
+ * Where the digest is returned.
+ * NOTE 2: The length of the hash is determined by the value of
+ * whichSha that was passed to hmacReset().
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hmacResult(HMACContext *context, uint8_t *digest)
+{
+ int ret;
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+
+ /* finish up 1st pass */
+ /* (Use digest here as a temporary buffer.) */
+ ret =
+ USHAResult(&context->shaContext, digest) ||
+
+
+
+Eastlake & Hansen Informational [Page 83]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /* perform outer SHA */
+ /* init context for 2nd pass */
+ USHAReset(&context->shaContext, context->whichSha) ||
+
+ /* start with outer pad */
+ USHAInput(&context->shaContext, context->k_opad,
+ context->blockSize) ||
+
+ /* then results of 1st hash */
+ USHAInput(&context->shaContext, digest, context->hashSize) ||
+ /* finish up 2nd pass */
+ USHAResult(&context->shaContext, digest);
+
+ context->Computed = 1;
+ return context->Corrupted = ret;
+}
+
+8.4. The HKDF Code
+
+/**************************** hkdf.c ***************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the HKDF algorithm (HMAC-based
+ * Extract-and-Expand Key Derivation Function, RFC 5869),
+ * expressed in terms of the various SHA algorithms.
+ */
+
+#include "sha.h"
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * hkdf
+ *
+ * Description:
+ * This function will generate keying material using HKDF.
+ *
+ * Parameters:
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * salt[ ]: [in]
+ * The optional salt value (a non-secret random value);
+ * if not provided (salt == NULL), it is set internally
+
+
+
+Eastlake & Hansen Informational [Page 84]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * to a string of HashLen(whichSha) zeros.
+ * salt_len: [in]
+ * The length of the salt value. (Ignored if salt == NULL.)
+ * ikm[ ]: [in]
+ * Input keying material.
+ * ikm_len: [in]
+ * The length of the input keying material.
+ * info[ ]: [in]
+ * The optional context and application specific information.
+ * If info == NULL or a zero-length string, it is ignored.
+ * info_len: [in]
+ * The length of the optional context and application specific
+ * information. (Ignored if info == NULL.)
+ * okm[ ]: [out]
+ * Where the HKDF is to be stored.
+ * okm_len: [in]
+ * The length of the buffer to hold okm.
+ * okm_len must be <= 255 * USHABlockSize(whichSha)
+ *
+ * Notes:
+ * Calls hkdfExtract() and hkdfExpand().
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdf(SHAversion whichSha,
+ const unsigned char *salt, int salt_len,
+ const unsigned char *ikm, int ikm_len,
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len)
+{
+ uint8_t prk[USHAMaxHashSize];
+ return hkdfExtract(whichSha, salt, salt_len, ikm, ikm_len, prk) ||
+ hkdfExpand(whichSha, prk, USHAHashSize(whichSha), info,
+ info_len, okm, okm_len);
+}
+
+/*
+ * hkdfExtract
+ *
+ * Description:
+ * This function will perform HKDF extraction.
+ *
+ * Parameters:
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * salt[ ]: [in]
+
+
+
+Eastlake & Hansen Informational [Page 85]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The optional salt value (a non-secret random value);
+ * if not provided (salt == NULL), it is set internally
+ * to a string of HashLen(whichSha) zeros.
+ * salt_len: [in]
+ * The length of the salt value. (Ignored if salt == NULL.)
+ * ikm[ ]: [in]
+ * Input keying material.
+ * ikm_len: [in]
+ * The length of the input keying material.
+ * prk[ ]: [out]
+ * Array where the HKDF extraction is to be stored.
+ * Must be larger than USHAHashSize(whichSha);
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdfExtract(SHAversion whichSha,
+ const unsigned char *salt, int salt_len,
+ const unsigned char *ikm, int ikm_len,
+ uint8_t prk[USHAMaxHashSize])
+{
+ unsigned char nullSalt[USHAMaxHashSize];
+ if (salt == 0) {
+ salt = nullSalt;
+ salt_len = USHAHashSize(whichSha);
+ memset(nullSalt, '\0', salt_len);
+ } else if (salt_len < 0) {
+ return shaBadParam;
+ }
+ return hmac(whichSha, ikm, ikm_len, salt, salt_len, prk);
+}
+
+/*
+ * hkdfExpand
+ *
+ * Description:
+ * This function will perform HKDF expansion.
+ *
+ * Parameters:
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * prk[ ]: [in]
+ * The pseudo-random key to be expanded; either obtained
+ * directly from a cryptographically strong, uniformly
+ * distributed pseudo-random number generator, or as the
+ * output from hkdfExtract().
+ * prk_len: [in]
+
+
+
+Eastlake & Hansen Informational [Page 86]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * The length of the pseudo-random key in prk;
+ * should at least be equal to USHAHashSize(whichSHA).
+ * info[ ]: [in]
+ * The optional context and application specific information.
+ * If info == NULL or a zero-length string, it is ignored.
+ * info_len: [in]
+ * The length of the optional context and application specific
+ * information. (Ignored if info == NULL.)
+ * okm[ ]: [out]
+ * Where the HKDF is to be stored.
+ * okm_len: [in]
+ * The length of the buffer to hold okm.
+ * okm_len must be <= 255 * USHABlockSize(whichSha)
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ], int prk_len,
+ const unsigned char *info, int info_len,
+ uint8_t okm[ ], int okm_len)
+{
+ int hash_len, N;
+ unsigned char T[USHAMaxHashSize];
+ int Tlen, where, i;
+
+ if (info == 0) {
+ info = (const unsigned char *)"";
+ info_len = 0;
+ } else if (info_len < 0) {
+ return shaBadParam;
+ }
+ if (okm_len <= 0) return shaBadParam;
+ if (!okm) return shaBadParam;
+
+ hash_len = USHAHashSize(whichSha);
+ if (prk_len < hash_len) return shaBadParam;
+ N = okm_len / hash_len;
+ if ((okm_len % hash_len) != 0) N++;
+ if (N > 255) return shaBadParam;
+
+ Tlen = 0;
+ where = 0;
+ for (i = 1; i <= N; i++) {
+ HMACContext context;
+ unsigned char c = i;
+ int ret = hmacReset(&context, whichSha, prk, prk_len) ||
+ hmacInput(&context, T, Tlen) ||
+
+
+
+Eastlake & Hansen Informational [Page 87]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ hmacInput(&context, info, info_len) ||
+ hmacInput(&context, &c, 1) ||
+ hmacResult(&context, T);
+ if (ret != shaSuccess) return ret;
+ memcpy(okm + where, T,
+ (i != N) ? hash_len : (okm_len - where));
+ where += hash_len;
+ Tlen = hash_len;
+ }
+ return shaSuccess;
+}
+
+/*
+ * hkdfReset
+ *
+ * Description:
+ * This function will initialize the hkdfContext in preparation
+ * for key derivation using the modular HKDF interface for
+ * arbitrary length inputs.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * whichSha: [in]
+ * One of SHA1, SHA224, SHA256, SHA384, SHA512
+ * salt[ ]: [in]
+ * The optional salt value (a non-secret random value);
+ * if not provided (salt == NULL), it is set internally
+ * to a string of HashLen(whichSha) zeros.
+ * salt_len: [in]
+ * The length of the salt value. (Ignored if salt == NULL.)
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdfReset(HKDFContext *context, enum SHAversion whichSha,
+ const unsigned char *salt, int salt_len)
+{
+ unsigned char nullSalt[USHAMaxHashSize];
+ if (!context) return shaNull;
+
+ context->whichSha = whichSha;
+ context->hashSize = USHAHashSize(whichSha);
+ if (salt == 0) {
+ salt = nullSalt;
+ salt_len = context->hashSize;
+ memset(nullSalt, '\0', salt_len);
+
+
+
+Eastlake & Hansen Informational [Page 88]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ }
+
+ return hmacReset(&context->hmacContext, whichSha, salt, salt_len);
+}
+
+/*
+ * hkdfInput
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the input keying material. It may be called multiple times.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The HKDF context to update.
+ * ikm[ ]: [in]
+ * An array of octets representing the next portion of
+ * the input keying material.
+ * ikm_len: [in]
+ * The length of ikm.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdfInput(HKDFContext *context, const unsigned char *ikm,
+ int ikm_len)
+{
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ return hmacInput(&context->hmacContext, ikm, ikm_len);
+}
+
+/*
+ * hkdfFinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the
+ * input keying material.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The HKDF context to update
+ * ikm_bits: [in]
+ * The final bits of the input keying material, in the upper
+ * portion of the byte. (Use 0b###00000 instead of 0b00000###
+ * to input the three bits ###.)
+
+
+
+Eastlake & Hansen Informational [Page 89]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * ikm_bit_count: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits,
+ unsigned int ikm_bit_count)
+{
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ return hmacFinalBits(&context->hmacContext, ikm_bits, ikm_bit_count);
+}
+
+/*
+ * hkdfResult
+ *
+ * Description:
+ * This function will finish the HKDF extraction and perform the
+ * final HKDF expansion.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The HKDF context to use to calculate the HKDF hash.
+ * prk[ ]: [out]
+ * An optional location to store the HKDF extraction.
+ * Either NULL, or pointer to a buffer that must be
+ * larger than USHAHashSize(whichSha);
+ * info[ ]: [in]
+ * The optional context and application specific information.
+ * If info == NULL or a zero-length string, it is ignored.
+ * info_len: [in]
+ * The length of the optional context and application specific
+ * information. (Ignored if info == NULL.)
+ * okm[ ]: [out]
+ * Where the HKDF is to be stored.
+ * okm_len: [in]
+ * The length of the buffer to hold okm.
+ * okm_len must be <= 255 * USHABlockSize(whichSha)
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int hkdfResult(HKDFContext *context,
+ uint8_t prk[USHAMaxHashSize],
+ const unsigned char *info, int info_len,
+
+
+
+Eastlake & Hansen Informational [Page 90]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ uint8_t okm[ ], int okm_len)
+{
+ uint8_t prkbuf[USHAMaxHashSize];
+ int ret;
+
+ if (!context) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (!okm) return context->Corrupted = shaBadParam;
+ if (!prk) prk = prkbuf;
+
+ ret = hmacResult(&context->hmacContext, prk) ||
+ hkdfExpand(context->whichSha, prk, context->hashSize, info,
+ info_len, okm, okm_len);
+ context->Computed = 1;
+ return context->Corrupted = ret;
+}
+
+8.5. The Test Driver
+
+ The following code is a main program test driver to exercise the code
+ in sha1.c, sha224-256.c, sha384-512.c, hmac.c, and hkdf.c. The test
+ driver can also be used as a standalone program for generating the
+ hashes. Note that the tests assume that character values are as in
+ [US-ASCII] and a run time check warns if the code appears to have
+ been compiled with some other character system.
+
+ See also [SHAVS].
+
+/************************** shatest.c **************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file will exercise the SHA code performing
+ * the three tests documented in FIPS PUB 180-3
+ * (http://csrc.nist.gov/publications/fips/
+ * fips180-2/fips180-2withchangenotice.pdf)
+ * one that calls SHAInput with an exact multiple of 512 bits
+ * the seven tests documented for each algorithm in
+ * "The Secure Hash Algorithm Validation System (SHAVS)"
+ * (http://csrc.nist.gov/cryptval/shs/SHAVS.pdf),
+ * three of which are bit-level tests
+ *
+
+
+
+
+Eastlake & Hansen Informational [Page 91]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ * These tests have subsequently been moved to pages linked from
+ * http://csrc.nist.gov/groups/ST/toolkit/examples.html
+ *
+ * This file will exercise the HMAC SHA1 code performing
+ * the seven tests documented in RFCs [RFC 2202] and [RFC 4231].
+ *
+ * This file will exercise the HKDF code performing
+ * the seven tests documented in RFC 4869.
+ *
+ * To run the tests and just see PASSED/FAILED, use the -p option.
+ *
+ * Other options exercise:
+ * hashing an arbitrary string
+ * hashing a file's contents
+ * a few error test checks
+ * printing the results in raw format
+ *
+ * Portability Issues:
+ * None.
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h> /* defines getopt() and optarg */
+#include "sha.h"
+
+static int scasecmp(const char *s1, const char *s2);
+
+/*
+ * Define patterns for testing
+ */
+#define TEST1 "abc"
+#define TEST2_1 \
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+#define TEST2_2a \
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+#define TEST2_2b \
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
+#define TEST2_2 TEST2_2a TEST2_2b
+#define TEST3 "a" /* times 1000000 */
+#define TEST4a "01234567012345670123456701234567"
+#define TEST4b "01234567012345670123456701234567"
+ /* an exact multiple of 512 bits */
+#define TEST4 TEST4a TEST4b /* times 10 */
+
+
+
+Eastlake & Hansen Informational [Page 92]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+#define TEST7_1 \
+ "\x49\xb2\xae\xc2\x59\x4b\xbe\x3a\x3b\x11\x75\x42\xd9\x4a\xc8"
+#define TEST8_1 \
+ "\x9a\x7d\xfd\xf1\xec\xea\xd0\x6e\xd6\x46\xaa\x55\xfe\x75\x71\x46"
+#define TEST9_1 \
+ "\x65\xf9\x32\x99\x5b\xa4\xce\x2c\xb1\xb4\xa2\xe7\x1a\xe7\x02\x20" \
+ "\xaa\xce\xc8\x96\x2d\xd4\x49\x9c\xbd\x7c\x88\x7a\x94\xea\xaa\x10" \
+ "\x1e\xa5\xaa\xbc\x52\x9b\x4e\x7e\x43\x66\x5a\x5a\xf2\xcd\x03\xfe" \
+ "\x67\x8e\xa6\xa5\x00\x5b\xba\x3b\x08\x22\x04\xc2\x8b\x91\x09\xf4" \
+ "\x69\xda\xc9\x2a\xaa\xb3\xaa\x7c\x11\xa1\xb3\x2a"
+#define TEST10_1 \
+ "\xf7\x8f\x92\x14\x1b\xcd\x17\x0a\xe8\x9b\x4f\xba\x15\xa1\xd5\x9f" \
+ "\x3f\xd8\x4d\x22\x3c\x92\x51\xbd\xac\xbb\xae\x61\xd0\x5e\xd1\x15" \
+ "\xa0\x6a\x7c\xe1\x17\xb7\xbe\xea\xd2\x44\x21\xde\xd9\xc3\x25\x92" \
+ "\xbd\x57\xed\xea\xe3\x9c\x39\xfa\x1f\xe8\x94\x6a\x84\xd0\xcf\x1f" \
+ "\x7b\xee\xad\x17\x13\xe2\xe0\x95\x98\x97\x34\x7f\x67\xc8\x0b\x04" \
+ "\x00\xc2\x09\x81\x5d\x6b\x10\xa6\x83\x83\x6f\xd5\x56\x2a\x56\xca" \
+ "\xb1\xa2\x8e\x81\xb6\x57\x66\x54\x63\x1c\xf1\x65\x66\xb8\x6e\x3b" \
+ "\x33\xa1\x08\xb0\x53\x07\xc0\x0a\xff\x14\xa7\x68\xed\x73\x50\x60" \
+ "\x6a\x0f\x85\xe6\xa9\x1d\x39\x6f\x5b\x5c\xbe\x57\x7f\x9b\x38\x80" \
+ "\x7c\x7d\x52\x3d\x6d\x79\x2f\x6e\xbc\x24\xa4\xec\xf2\xb3\xa4\x27" \
+ "\xcd\xbb\xfb"
+#define TEST7_224 \
+ "\xf0\x70\x06\xf2\x5a\x0b\xea\x68\xcd\x76\xa2\x95\x87\xc2\x8d"
+#define TEST8_224 \
+ "\x18\x80\x40\x05\xdd\x4f\xbd\x15\x56\x29\x9d\x6f\x9d\x93\xdf\x62"
+#define TEST9_224 \
+ "\xa2\xbe\x6e\x46\x32\x81\x09\x02\x94\xd9\xce\x94\x82\x65\x69\x42" \
+ "\x3a\x3a\x30\x5e\xd5\xe2\x11\x6c\xd4\xa4\xc9\x87\xfc\x06\x57\x00" \
+ "\x64\x91\xb1\x49\xcc\xd4\xb5\x11\x30\xac\x62\xb1\x9d\xc2\x48\xc7" \
+ "\x44\x54\x3d\x20\xcd\x39\x52\xdc\xed\x1f\x06\xcc\x3b\x18\xb9\x1f" \
+ "\x3f\x55\x63\x3e\xcc\x30\x85\xf4\x90\x70\x60\xd2"
+#define TEST10_224 \
+ "\x55\xb2\x10\x07\x9c\x61\xb5\x3a\xdd\x52\x06\x22\xd1\xac\x97\xd5" \
+ "\xcd\xbe\x8c\xb3\x3a\xa0\xae\x34\x45\x17\xbe\xe4\xd7\xba\x09\xab" \
+ "\xc8\x53\x3c\x52\x50\x88\x7a\x43\xbe\xbb\xac\x90\x6c\x2e\x18\x37" \
+ "\xf2\x6b\x36\xa5\x9a\xe3\xbe\x78\x14\xd5\x06\x89\x6b\x71\x8b\x2a" \
+ "\x38\x3e\xcd\xac\x16\xb9\x61\x25\x55\x3f\x41\x6f\xf3\x2c\x66\x74" \
+ "\xc7\x45\x99\xa9\x00\x53\x86\xd9\xce\x11\x12\x24\x5f\x48\xee\x47" \
+ "\x0d\x39\x6c\x1e\xd6\x3b\x92\x67\x0c\xa5\x6e\xc8\x4d\xee\xa8\x14" \
+ "\xb6\x13\x5e\xca\x54\x39\x2b\xde\xdb\x94\x89\xbc\x9b\x87\x5a\x8b" \
+ "\xaf\x0d\xc1\xae\x78\x57\x36\x91\x4a\xb7\xda\xa2\x64\xbc\x07\x9d" \
+ "\x26\x9f\x2c\x0d\x7e\xdd\xd8\x10\xa4\x26\x14\x5a\x07\x76\xf6\x7c" \
+ "\x87\x82\x73"
+#define TEST7_256 \
+ "\xbe\x27\x46\xc6\xdb\x52\x76\x5f\xdb\x2f\x88\x70\x0f\x9a\x73"
+#define TEST8_256 \
+ "\xe3\xd7\x25\x70\xdc\xdd\x78\x7c\xe3\x88\x7a\xb2\xcd\x68\x46\x52"
+
+
+
+Eastlake & Hansen Informational [Page 93]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+#define TEST9_256 \
+ "\x3e\x74\x03\x71\xc8\x10\xc2\xb9\x9f\xc0\x4e\x80\x49\x07\xef\x7c" \
+ "\xf2\x6b\xe2\x8b\x57\xcb\x58\xa3\xe2\xf3\xc0\x07\x16\x6e\x49\xc1" \
+ "\x2e\x9b\xa3\x4c\x01\x04\x06\x91\x29\xea\x76\x15\x64\x25\x45\x70" \
+ "\x3a\x2b\xd9\x01\xe1\x6e\xb0\xe0\x5d\xeb\xa0\x14\xeb\xff\x64\x06" \
+ "\xa0\x7d\x54\x36\x4e\xff\x74\x2d\xa7\x79\xb0\xb3"
+#define TEST10_256 \
+ "\x83\x26\x75\x4e\x22\x77\x37\x2f\x4f\xc1\x2b\x20\x52\x7a\xfe\xf0" \
+ "\x4d\x8a\x05\x69\x71\xb1\x1a\xd5\x71\x23\xa7\xc1\x37\x76\x00\x00" \
+ "\xd7\xbe\xf6\xf3\xc1\xf7\xa9\x08\x3a\xa3\x9d\x81\x0d\xb3\x10\x77" \
+ "\x7d\xab\x8b\x1e\x7f\x02\xb8\x4a\x26\xc7\x73\x32\x5f\x8b\x23\x74" \
+ "\xde\x7a\x4b\x5a\x58\xcb\x5c\x5c\xf3\x5b\xce\xe6\xfb\x94\x6e\x5b" \
+ "\xd6\x94\xfa\x59\x3a\x8b\xeb\x3f\x9d\x65\x92\xec\xed\xaa\x66\xca" \
+ "\x82\xa2\x9d\x0c\x51\xbc\xf9\x33\x62\x30\xe5\xd7\x84\xe4\xc0\xa4" \
+ "\x3f\x8d\x79\xa3\x0a\x16\x5c\xba\xbe\x45\x2b\x77\x4b\x9c\x71\x09" \
+ "\xa9\x7d\x13\x8f\x12\x92\x28\x96\x6f\x6c\x0a\xdc\x10\x6a\xad\x5a" \
+ "\x9f\xdd\x30\x82\x57\x69\xb2\xc6\x71\xaf\x67\x59\xdf\x28\xeb\x39" \
+ "\x3d\x54\xd6"
+#define TEST7_384 \
+ "\x8b\xc5\x00\xc7\x7c\xee\xd9\x87\x9d\xa9\x89\x10\x7c\xe0\xaa"
+#define TEST8_384 \
+ "\xa4\x1c\x49\x77\x79\xc0\x37\x5f\xf1\x0a\x7f\x4e\x08\x59\x17\x39"
+#define TEST9_384 \
+ "\x68\xf5\x01\x79\x2d\xea\x97\x96\x76\x70\x22\xd9\x3d\xa7\x16\x79" \
+ "\x30\x99\x20\xfa\x10\x12\xae\xa3\x57\xb2\xb1\x33\x1d\x40\xa1\xd0" \
+ "\x3c\x41\xc2\x40\xb3\xc9\xa7\x5b\x48\x92\xf4\xc0\x72\x4b\x68\xc8" \
+ "\x75\x32\x1a\xb8\xcf\xe5\x02\x3b\xd3\x75\xbc\x0f\x94\xbd\x89\xfe" \
+ "\x04\xf2\x97\x10\x5d\x7b\x82\xff\xc0\x02\x1a\xeb\x1c\xcb\x67\x4f" \
+ "\x52\x44\xea\x34\x97\xde\x26\xa4\x19\x1c\x5f\x62\xe5\xe9\xa2\xd8" \
+ "\x08\x2f\x05\x51\xf4\xa5\x30\x68\x26\xe9\x1c\xc0\x06\xce\x1b\xf6" \
+ "\x0f\xf7\x19\xd4\x2f\xa5\x21\xc8\x71\xcd\x23\x94\xd9\x6e\xf4\x46" \
+ "\x8f\x21\x96\x6b\x41\xf2\xba\x80\xc2\x6e\x83\xa9"
+#define TEST10_384 \
+ "\x39\x96\x69\xe2\x8f\x6b\x9c\x6d\xbc\xbb\x69\x12\xec\x10\xff\xcf" \
+ "\x74\x79\x03\x49\xb7\xdc\x8f\xbe\x4a\x8e\x7b\x3b\x56\x21\xdb\x0f" \
+ "\x3e\x7d\xc8\x7f\x82\x32\x64\xbb\xe4\x0d\x18\x11\xc9\xea\x20\x61" \
+ "\xe1\xc8\x4a\xd1\x0a\x23\xfa\xc1\x72\x7e\x72\x02\xfc\x3f\x50\x42" \
+ "\xe6\xbf\x58\xcb\xa8\xa2\x74\x6e\x1f\x64\xf9\xb9\xea\x35\x2c\x71" \
+ "\x15\x07\x05\x3c\xf4\xe5\x33\x9d\x52\x86\x5f\x25\xcc\x22\xb5\xe8" \
+ "\x77\x84\xa1\x2f\xc9\x61\xd6\x6c\xb6\xe8\x95\x73\x19\x9a\x2c\xe6" \
+ "\x56\x5c\xbd\xf1\x3d\xca\x40\x38\x32\xcf\xcb\x0e\x8b\x72\x11\xe8" \
+ "\x3a\xf3\x2a\x11\xac\x17\x92\x9f\xf1\xc0\x73\xa5\x1c\xc0\x27\xaa" \
+ "\xed\xef\xf8\x5a\xad\x7c\x2b\x7c\x5a\x80\x3e\x24\x04\xd9\x6d\x2a" \
+ "\x77\x35\x7b\xda\x1a\x6d\xae\xed\x17\x15\x1c\xb9\xbc\x51\x25\xa4" \
+ "\x22\xe9\x41\xde\x0c\xa0\xfc\x50\x11\xc2\x3e\xcf\xfe\xfd\xd0\x96" \
+ "\x76\x71\x1c\xf3\xdb\x0a\x34\x40\x72\x0e\x16\x15\xc1\xf2\x2f\xbc" \
+ "\x3c\x72\x1d\xe5\x21\xe1\xb9\x9b\xa1\xbd\x55\x77\x40\x86\x42\x14" \
+ "\x7e\xd0\x96"
+
+
+
+Eastlake & Hansen Informational [Page 94]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+#define TEST7_512 \
+ "\x08\xec\xb5\x2e\xba\xe1\xf7\x42\x2d\xb6\x2b\xcd\x54\x26\x70"
+#define TEST8_512 \
+ "\x8d\x4e\x3c\x0e\x38\x89\x19\x14\x91\x81\x6e\x9d\x98\xbf\xf0\xa0"
+#define TEST9_512 \
+ "\x3a\xdd\xec\x85\x59\x32\x16\xd1\x61\x9a\xa0\x2d\x97\x56\x97\x0b" \
+ "\xfc\x70\xac\xe2\x74\x4f\x7c\x6b\x27\x88\x15\x10\x28\xf7\xb6\xa2" \
+ "\x55\x0f\xd7\x4a\x7e\x6e\x69\xc2\xc9\xb4\x5f\xc4\x54\x96\x6d\xc3" \
+ "\x1d\x2e\x10\xda\x1f\x95\xce\x02\xbe\xb4\xbf\x87\x65\x57\x4c\xbd" \
+ "\x6e\x83\x37\xef\x42\x0a\xdc\x98\xc1\x5c\xb6\xd5\xe4\xa0\x24\x1b" \
+ "\xa0\x04\x6d\x25\x0e\x51\x02\x31\xca\xc2\x04\x6c\x99\x16\x06\xab" \
+ "\x4e\xe4\x14\x5b\xee\x2f\xf4\xbb\x12\x3a\xab\x49\x8d\x9d\x44\x79" \
+ "\x4f\x99\xcc\xad\x89\xa9\xa1\x62\x12\x59\xed\xa7\x0a\x5b\x6d\xd4" \
+ "\xbd\xd8\x77\x78\xc9\x04\x3b\x93\x84\xf5\x49\x06"
+#define TEST10_512 \
+ "\xa5\x5f\x20\xc4\x11\xaa\xd1\x32\x80\x7a\x50\x2d\x65\x82\x4e\x31" \
+ "\xa2\x30\x54\x32\xaa\x3d\x06\xd3\xe2\x82\xa8\xd8\x4e\x0d\xe1\xde" \
+ "\x69\x74\xbf\x49\x54\x69\xfc\x7f\x33\x8f\x80\x54\xd5\x8c\x26\xc4" \
+ "\x93\x60\xc3\xe8\x7a\xf5\x65\x23\xac\xf6\xd8\x9d\x03\xe5\x6f\xf2" \
+ "\xf8\x68\x00\x2b\xc3\xe4\x31\xed\xc4\x4d\xf2\xf0\x22\x3d\x4b\xb3" \
+ "\xb2\x43\x58\x6e\x1a\x7d\x92\x49\x36\x69\x4f\xcb\xba\xf8\x8d\x95" \
+ "\x19\xe4\xeb\x50\xa6\x44\xf8\xe4\xf9\x5e\xb0\xea\x95\xbc\x44\x65" \
+ "\xc8\x82\x1a\xac\xd2\xfe\x15\xab\x49\x81\x16\x4b\xbb\x6d\xc3\x2f" \
+ "\x96\x90\x87\xa1\x45\xb0\xd9\xcc\x9c\x67\xc2\x2b\x76\x32\x99\x41" \
+ "\x9c\xc4\x12\x8b\xe9\xa0\x77\xb3\xac\xe6\x34\x06\x4e\x6d\x99\x28" \
+ "\x35\x13\xdc\x06\xe7\x51\x5d\x0d\x73\x13\x2e\x9a\x0d\xc6\xd3\xb1" \
+ "\xf8\xb2\x46\xf1\xa9\x8a\x3f\xc7\x29\x41\xb1\xe3\xbb\x20\x98\xe8" \
+ "\xbf\x16\xf2\x68\xd6\x4f\x0b\x0f\x47\x07\xfe\x1e\xa1\xa1\x79\x1b" \
+ "\xa2\xf3\xc0\xc7\x58\xe5\xf5\x51\x86\x3a\x96\xc9\x49\xad\x47\xd7" \
+ "\xfb\x40\xd2"
+#define SHA1_SEED "\xd0\x56\x9c\xb3\x66\x5a\x8a\x43\xeb\x6e\xa2\x3d" \
+ "\x75\xa3\xc4\xd2\x05\x4a\x0d\x7d"
+#define SHA224_SEED "\xd0\x56\x9c\xb3\x66\x5a\x8a\x43\xeb\x6e\xa2" \
+ "\x3d\x75\xa3\xc4\xd2\x05\x4a\x0d\x7d\x66\xa9\xca\x99\xc9\xce\xb0" \
+ "\x27"
+#define SHA256_SEED "\xf4\x1e\xce\x26\x13\xe4\x57\x39\x15\x69\x6b" \
+ "\x5a\xdc\xd5\x1c\xa3\x28\xbe\x3b\xf5\x66\xa9\xca\x99\xc9\xce\xb0" \
+ "\x27\x9c\x1c\xb0\xa7"
+#define SHA384_SEED "\x82\x40\xbc\x51\xe4\xec\x7e\xf7\x6d\x18\xe3" \
+ "\x52\x04\xa1\x9f\x51\xa5\x21\x3a\x73\xa8\x1d\x6f\x94\x46\x80\xd3" \
+ "\x07\x59\x48\xb7\xe4\x63\x80\x4e\xa3\xd2\x6e\x13\xea\x82\x0d\x65" \
+ "\xa4\x84\xbe\x74\x53"
+#define SHA512_SEED "\x47\x3f\xf1\xb9\xb3\xff\xdf\xa1\x26\x69\x9a" \
+ "\xc7\xef\x9e\x8e\x78\x77\x73\x09\x58\x24\xc6\x42\x55\x7c\x13\x99" \
+ "\xd9\x8e\x42\x20\x44\x8d\xc3\x5b\x99\xbf\xdd\x44\x77\x95\x43\x92" \
+ "\x4c\x1c\xe9\x3b\xc5\x94\x15\x38\x89\x5d\xb9\x88\x26\x1b\x00\x77" \
+ "\x4b\x12\x27\x20\x39"
+
+
+
+
+Eastlake & Hansen Informational [Page 95]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+#define TESTCOUNT 10
+#define HASHCOUNT 5
+#define RANDOMCOUNT 4
+#define HMACTESTCOUNT 7
+#define HKDFTESTCOUNT 7
+
+#define PRINTNONE 0
+#define PRINTTEXT 1
+#define PRINTRAW 2
+#define PRINTHEX 3
+#define PRINTBASE64 4
+
+#define PRINTPASSFAIL 1
+#define PRINTFAIL 2
+
+#define length(x) (sizeof(x)-1)
+
+/* Test arrays for hashes. */
+struct hash {
+ const char *name;
+ SHAversion whichSha;
+ int hashsize;
+ struct {
+ const char *testarray;
+ int length;
+ long repeatcount;
+ int extrabits;
+ int numberExtrabits;
+ const char *resultarray;
+ } tests[TESTCOUNT];
+ const char *randomtest;
+ const char *randomresults[RANDOMCOUNT];
+} hashes[HASHCOUNT] = {
+ { "SHA1", SHA1, SHA1HashSize,
+ {
+ /* 1 */ { TEST1, length(TEST1), 1, 0, 0,
+ "A9993E364706816ABA3E25717850C26C9CD0D89D" },
+ /* 2 */ { TEST2_1, length(TEST2_1), 1, 0, 0,
+ "84983E441C3BD26EBAAE4AA1F95129E5E54670F1" },
+ /* 3 */ { TEST3, length(TEST3), 1000000, 0, 0,
+ "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F" },
+ /* 4 */ { TEST4, length(TEST4), 10, 0, 0,
+ "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452" },
+ /* 5 */ { "", 0, 0, 0x98, 5,
+ "29826B003B906E660EFF4027CE98AF3531AC75BA" },
+ /* 6 */ { "\x5e", 1, 1, 0, 0,
+ "5E6F80A34A9798CAFC6A5DB96CC57BA4C4DB59C2" },
+ /* 7 */ { TEST7_1, length(TEST7_1), 1, 0x80, 3,
+
+
+
+Eastlake & Hansen Informational [Page 96]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "6239781E03729919C01955B3FFA8ACB60B988340" },
+ /* 8 */ { TEST8_1, length(TEST8_1), 1, 0, 0,
+ "82ABFF6605DBE1C17DEF12A394FA22A82B544A35" },
+ /* 9 */ { TEST9_1, length(TEST9_1), 1, 0xE0, 3,
+ "8C5B2A5DDAE5A97FC7F9D85661C672ADBF7933D4" },
+ /* 10 */ { TEST10_1, length(TEST10_1), 1, 0, 0,
+ "CB0082C8F197D260991BA6A460E76E202BAD27B3" }
+ }, SHA1_SEED, { "E216836819477C7F78E0D843FE4FF1B6D6C14CD4",
+ "A2DBC7A5B1C6C0A8BCB7AAA41252A6A7D0690DBC",
+ "DB1F9050BB863DFEF4CE37186044E2EEB17EE013",
+ "127FDEDF43D372A51D5747C48FBFFE38EF6CDF7B"
+ } },
+ { "SHA224", SHA224, SHA224HashSize,
+ {
+ /* 1 */ { TEST1, length(TEST1), 1, 0, 0,
+ "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7" },
+ /* 2 */ { TEST2_1, length(TEST2_1), 1, 0, 0,
+ "75388B16512776CC5DBA5DA1FD890150B0C6455CB4F58B1952522525" },
+ /* 3 */ { TEST3, length(TEST3), 1000000, 0, 0,
+ "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2EE4EE7AD67" },
+ /* 4 */ { TEST4, length(TEST4), 10, 0, 0,
+ "567F69F168CD7844E65259CE658FE7AADFA25216E68ECA0EB7AB8262" },
+ /* 5 */ { "", 0, 0, 0x68, 5,
+ "E3B048552C3C387BCAB37F6EB06BB79B96A4AEE5FF27F51531A9551C" },
+ /* 6 */ { "\x07", 1, 1, 0, 0,
+ "00ECD5F138422B8AD74C9799FD826C531BAD2FCABC7450BEE2AA8C2A" },
+ /* 7 */ { TEST7_224, length(TEST7_224), 1, 0xA0, 3,
+ "1B01DB6CB4A9E43DED1516BEB3DB0B87B6D1EA43187462C608137150" },
+ /* 8 */ { TEST8_224, length(TEST8_224), 1, 0, 0,
+ "DF90D78AA78821C99B40BA4C966921ACCD8FFB1E98AC388E56191DB1" },
+ /* 9 */ { TEST9_224, length(TEST9_224), 1, 0xE0, 3,
+ "54BEA6EAB8195A2EB0A7906A4B4A876666300EEFBD1F3B8474F9CD57" },
+ /* 10 */ { TEST10_224, length(TEST10_224), 1, 0, 0,
+ "0B31894EC8937AD9B91BDFBCBA294D9ADEFAA18E09305E9F20D5C3A4" }
+ }, SHA224_SEED, { "100966A5B4FDE0B42E2A6C5953D4D7F41BA7CF79FD"
+ "2DF431416734BE", "1DCA396B0C417715DEFAAE9641E10A2E99D55A"
+ "BCB8A00061EB3BE8BD", "1864E627BDB2319973CD5ED7D68DA71D8B"
+ "F0F983D8D9AB32C34ADB34", "A2406481FC1BCAF24DD08E6752E844"
+ "709563FB916227FED598EB621F"
+ } },
+ { "SHA256", SHA256, SHA256HashSize,
+ {
+ /* 1 */ { TEST1, length(TEST1), 1, 0, 0, "BA7816BF8F01CFEA4141"
+ "40DE5DAE2223B00361A396177A9CB410FF61F20015AD" },
+ /* 2 */ { TEST2_1, length(TEST2_1), 1, 0, 0, "248D6A61D20638B8"
+ "E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1" },
+ /* 3 */ { TEST3, length(TEST3), 1000000, 0, 0, "CDC76E5C9914FB92"
+ "81A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0" },
+
+
+
+Eastlake & Hansen Informational [Page 97]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /* 4 */ { TEST4, length(TEST4), 10, 0, 0, "594847328451BDFA"
+ "85056225462CC1D867D877FB388DF0CE35F25AB5562BFBB5" },
+ /* 5 */ { "", 0, 0, 0x68, 5, "D6D3E02A31A84A8CAA9718ED6C2057BE"
+ "09DB45E7823EB5079CE7A573A3760F95" },
+ /* 6 */ { "\x19", 1, 1, 0, 0, "68AA2E2EE5DFF96E3355E6C7EE373E3D"
+ "6A4E17F75F9518D843709C0C9BC3E3D4" },
+ /* 7 */ { TEST7_256, length(TEST7_256), 1, 0x60, 3, "77EC1DC8"
+ "9C821FF2A1279089FA091B35B8CD960BCAF7DE01C6A7680756BEB972" },
+ /* 8 */ { TEST8_256, length(TEST8_256), 1, 0, 0, "175EE69B02BA"
+ "9B58E2B0A5FD13819CEA573F3940A94F825128CF4209BEABB4E8" },
+ /* 9 */ { TEST9_256, length(TEST9_256), 1, 0xA0, 3, "3E9AD646"
+ "8BBBAD2AC3C2CDC292E018BA5FD70B960CF1679777FCE708FDB066E9" },
+ /* 10 */ { TEST10_256, length(TEST10_256), 1, 0, 0, "97DBCA7D"
+ "F46D62C8A422C941DD7E835B8AD3361763F7E9B2D95F4F0DA6E1CCBC" },
+ }, SHA256_SEED, { "83D28614D49C3ADC1D6FC05DB5F48037C056F8D2A4CE44"
+ "EC6457DEA5DD797CD1", "99DBE3127EF2E93DD9322D6A07909EB33B6399"
+ "5E529B3F954B8581621BB74D39", "8D4BE295BB64661CA3C7EFD129A2F7"
+ "25B33072DBDDE32385B9A87B9AF88EA76F", "40AF5D3F9716B040DF9408"
+ "E31536B70FF906EC51B00447CA97D7DD97C12411F4"
+ } },
+ { "SHA384", SHA384, SHA384HashSize,
+ {
+ /* 1 */ { TEST1, length(TEST1), 1, 0, 0,
+ "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED163"
+ "1A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7" },
+ /* 2 */ { TEST2_2, length(TEST2_2), 1, 0, 0,
+ "09330C33F71147E83D192FC782CD1B4753111B173B3B05D2"
+ "2FA08086E3B0F712FCC7C71A557E2DB966C3E9FA91746039" },
+ /* 3 */ { TEST3, length(TEST3), 1000000, 0, 0,
+ "9D0E1809716474CB086E834E310A4A1CED149E9C00F24852"
+ "7972CEC5704C2A5B07B8B3DC38ECC4EBAE97DDD87F3D8985" },
+ /* 4 */ { TEST4, length(TEST4), 10, 0, 0,
+ "2FC64A4F500DDB6828F6A3430B8DD72A368EB7F3A8322A70"
+ "BC84275B9C0B3AB00D27A5CC3C2D224AA6B61A0D79FB4596" },
+ /* 5 */ { "", 0, 0, 0x10, 5,
+ "8D17BE79E32B6718E07D8A603EB84BA0478F7FCFD1BB9399"
+ "5F7D1149E09143AC1FFCFC56820E469F3878D957A15A3FE4" },
+ /* 6 */ { "\xb9", 1, 1, 0, 0,
+ "BC8089A19007C0B14195F4ECC74094FEC64F01F90929282C"
+ "2FB392881578208AD466828B1C6C283D2722CF0AD1AB6938" },
+ /* 7 */ { TEST7_384, length(TEST7_384), 1, 0xA0, 3,
+ "D8C43B38E12E7C42A7C9B810299FD6A770BEF30920F17532"
+ "A898DE62C7A07E4293449C0B5FA70109F0783211CFC4BCE3" },
+ /* 8 */ { TEST8_384, length(TEST8_384), 1, 0, 0,
+ "C9A68443A005812256B8EC76B00516F0DBB74FAB26D66591"
+ "3F194B6FFB0E91EA9967566B58109CBC675CC208E4C823F7" },
+ /* 9 */ { TEST9_384, length(TEST9_384), 1, 0xE0, 3,
+ "5860E8DE91C21578BB4174D227898A98E0B45C4C760F0095"
+
+
+
+Eastlake & Hansen Informational [Page 98]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "49495614DAEDC0775D92D11D9F8CE9B064EEAC8DAFC3A297" },
+ /* 10 */ { TEST10_384, length(TEST10_384), 1, 0, 0,
+ "4F440DB1E6EDD2899FA335F09515AA025EE177A79F4B4AAF"
+ "38E42B5C4DE660F5DE8FB2A5B2FBD2A3CBFFD20CFF1288C0" }
+ }, SHA384_SEED, { "CE44D7D63AE0C91482998CF662A51EC80BF6FC68661A3C"
+ "57F87566112BD635A743EA904DEB7D7A42AC808CABE697F38F", "F9C6D2"
+ "61881FEE41ACD39E67AA8D0BAD507C7363EB67E2B81F45759F9C0FD7B503"
+ "DF1A0B9E80BDE7BC333D75B804197D", "D96512D8C9F4A7A4967A366C01"
+ "C6FD97384225B58343A88264847C18E4EF8AB7AEE4765FFBC3E30BD485D3"
+ "638A01418F", "0CA76BD0813AF1509E170907A96005938BC985628290B2"
+ "5FEF73CF6FAD68DDBA0AC8920C94E0541607B0915A7B4457F7"
+ } },
+ { "SHA512", SHA512, SHA512HashSize,
+ {
+ /* 1 */ { TEST1, length(TEST1), 1, 0, 0,
+ "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA2"
+ "0A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD"
+ "454D4423643CE80E2A9AC94FA54CA49F" },
+ /* 2 */ { TEST2_2, length(TEST2_2), 1, 0, 0,
+ "8E959B75DAE313DA8CF4F72814FC143F8F7779C6EB9F7FA1"
+ "7299AEADB6889018501D289E4900F7E4331B99DEC4B5433A"
+ "C7D329EEB6DD26545E96E55B874BE909" },
+ /* 3 */ { TEST3, length(TEST3), 1000000, 0, 0,
+ "E718483D0CE769644E2E42C7BC15B4638E1F98B13B204428"
+ "5632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31B"
+ "EB009C5C2C49AA2E4EADB217AD8CC09B" },
+ /* 4 */ { TEST4, length(TEST4), 10, 0, 0,
+ "89D05BA632C699C31231DED4FFC127D5A894DAD412C0E024"
+ "DB872D1ABD2BA8141A0F85072A9BE1E2AA04CF33C765CB51"
+ "0813A39CD5A84C4ACAA64D3F3FB7BAE9" },
+ /* 5 */ { "", 0, 0, 0xB0, 5,
+ "D4EE29A9E90985446B913CF1D1376C836F4BE2C1CF3CADA0"
+ "720A6BF4857D886A7ECB3C4E4C0FA8C7F95214E41DC1B0D2"
+ "1B22A84CC03BF8CE4845F34DD5BDBAD4" },
+ /* 6 */ { "\xD0", 1, 1, 0, 0,
+ "9992202938E882E73E20F6B69E68A0A7149090423D93C81B"
+ "AB3F21678D4ACEEEE50E4E8CAFADA4C85A54EA8306826C4A"
+ "D6E74CECE9631BFA8A549B4AB3FBBA15" },
+ /* 7 */ { TEST7_512, length(TEST7_512), 1, 0x80, 3,
+ "ED8DC78E8B01B69750053DBB7A0A9EDA0FB9E9D292B1ED71"
+ "5E80A7FE290A4E16664FD913E85854400C5AF05E6DAD316B"
+ "7359B43E64F8BEC3C1F237119986BBB6" },
+ /* 8 */ { TEST8_512, length(TEST8_512), 1, 0, 0,
+ "CB0B67A4B8712CD73C9AABC0B199E9269B20844AFB75ACBD"
+ "D1C153C9828924C3DDEDAAFE669C5FDD0BC66F630F677398"
+ "8213EB1B16F517AD0DE4B2F0C95C90F8" },
+ /* 9 */ { TEST9_512, length(TEST9_512), 1, 0x80, 3,
+ "32BA76FC30EAA0208AEB50FFB5AF1864FDBF17902A4DC0A6"
+
+
+
+Eastlake & Hansen Informational [Page 99]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "82C61FCEA6D92B783267B21080301837F59DE79C6B337DB2"
+ "526F8A0A510E5E53CAFED4355FE7C2F1" },
+ /* 10 */ { TEST10_512, length(TEST10_512), 1, 0, 0,
+ "C665BEFB36DA189D78822D10528CBF3B12B3EEF726039909"
+ "C1A16A270D48719377966B957A878E720584779A62825C18"
+ "DA26415E49A7176A894E7510FD1451F5" }
+ }, SHA512_SEED, { "2FBB1E7E00F746BA514FBC8C421F36792EC0E11FF5EFC3"
+ "78E1AB0C079AA5F0F66A1E3EDBAEB4F9984BE14437123038A452004A5576"
+ "8C1FD8EED49E4A21BEDCD0", "25CBE5A4F2C7B1D7EF07011705D50C62C5"
+ "000594243EAFD1241FC9F3D22B58184AE2FEE38E171CF8129E29459C9BC2"
+ "EF461AF5708887315F15419D8D17FE7949", "5B8B1F2687555CE2D7182B"
+ "92E5C3F6C36547DA1C13DBB9EA4F73EA4CBBAF89411527906D35B1B06C1B"
+ "6A8007D05EC66DF0A406066829EAB618BDE3976515AAFC", "46E36B007D"
+ "19876CDB0B29AD074FE3C08CDD174D42169D6ABE5A1414B6E79707DF5877"
+ "6A98091CF431854147BB6D3C66D43BFBC108FD715BDE6AA127C2B0E79F"
+ }
+ }
+};
+
+/* Test arrays for HMAC. */
+struct hmachash {
+ const char *keyarray[5];
+ int keylength[5];
+ const char *dataarray[5];
+ int datalength[5];
+ const char *resultarray[5];
+ int resultlength[5];
+} hmachashes[HMACTESTCOUNT] = {
+ { /* 1 */ {
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b"
+ }, { 20 }, {
+ "\x48\x69\x20\x54\x68\x65\x72\x65" /* "Hi There" */
+ }, { 8 }, {
+ /* HMAC-SHA-1 */
+ "B617318655057264E28BC0B6FB378C8EF146BE00",
+ /* HMAC-SHA-224 */
+ "896FB1128ABBDF196832107CD49DF33F47B4B1169912BA4F53684B22",
+ /* HMAC-SHA-256 */
+ "B0344C61D8DB38535CA8AFCEAF0BF12B881DC200C9833DA726E9376C2E32"
+ "CFF7",
+ /* HMAC-SHA-384 */
+ "AFD03944D84895626B0825F4AB46907F15F9DADBE4101EC682AA034C7CEB"
+ "C59CFAEA9EA9076EDE7F4AF152E8B2FA9CB6",
+ /* HMAC-SHA-512 */
+ "87AA7CDEA5EF619D4FF0B4241A1D6CB02379F4E2CE4EC2787AD0B30545E1"
+ "7CDEDAA833B7D6B8A702038B274EAEA3F4E4BE9D914EEB61F1702E696C20"
+ "3A126854"
+
+
+
+Eastlake & Hansen Informational [Page 100]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ },
+ { /* 2 */ {
+ "\x4a\x65\x66\x65" /* "Jefe" */
+ }, { 4 }, {
+ "\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61\x20\x77\x61\x6e\x74"
+ "\x20\x66\x6f\x72\x20\x6e\x6f\x74\x68\x69\x6e\x67\x3f"
+ /* "what do ya want for nothing?" */
+ }, { 28 }, {
+ /* HMAC-SHA-1 */
+ "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79",
+ /* HMAC-SHA-224 */
+ "A30E01098BC6DBBF45690F3A7E9E6D0F8BBEA2A39E6148008FD05E44",
+ /* HMAC-SHA-256 */
+ "5BDCC146BF60754E6A042426089575C75A003F089D2739839DEC58B964EC"
+ "3843",
+ /* HMAC-SHA-384 */
+ "AF45D2E376484031617F78D2B58A6B1B9C7EF464F5A01B47E42EC3736322"
+ "445E8E2240CA5E69E2C78B3239ECFAB21649",
+ /* HMAC-SHA-512 */
+ "164B7A7BFCF819E2E395FBE73B56E0A387BD64222E831FD610270CD7EA25"
+ "05549758BF75C05A994A6D034F65F8F0E6FDCAEAB1A34D4A6B4B636E070A"
+ "38BCE737"
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ },
+ { /* 3 */
+ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa"
+ }, { 20 }, {
+ "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+ "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+ "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+ "\xdd\xdd\xdd\xdd\xdd"
+ }, { 50 }, {
+ /* HMAC-SHA-1 */
+ "125D7342B9AC11CD91A39AF48AA17B4F63F175D3",
+ /* HMAC-SHA-224 */
+ "7FB3CB3588C6C1F6FFA9694D7D6AD2649365B0C1F65D69D1EC8333EA",
+ /* HMAC-SHA-256 */
+ "773EA91E36800E46854DB8EBD09181A72959098B3EF8C122D9635514CED5"
+ "65FE",
+ /* HMAC-SHA-384 */
+ "88062608D3E6AD8A0AA2ACE014C8A86F0AA635D947AC9FEBE83EF4E55966"
+ "144B2A5AB39DC13814B94E3AB6E101A34F27",
+ /* HMAC-SHA-512 */
+
+
+
+Eastlake & Hansen Informational [Page 101]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "FA73B0089D56A284EFB0F0756C890BE9B1B5DBDD8EE81A3655F83E33B227"
+ "9D39BF3E848279A722C806B485A47E67C807B946A337BEE8942674278859"
+ "E13292FB"
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ },
+ { /* 4 */ {
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
+ }, { 25 }, {
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+ "\xcd\xcd\xcd\xcd\xcd"
+ }, { 50 }, {
+ /* HMAC-SHA-1 */
+ "4C9007F4026250C6BC8414F9BF50C86C2D7235DA",
+ /* HMAC-SHA-224 */
+ "6C11506874013CAC6A2ABC1BB382627CEC6A90D86EFC012DE7AFEC5A",
+ /* HMAC-SHA-256 */
+ "82558A389A443C0EA4CC819899F2083A85F0FAA3E578F8077A2E3FF46729"
+ "665B",
+ /* HMAC-SHA-384 */
+ "3E8A69B7783C25851933AB6290AF6CA77A9981480850009CC5577C6E1F57"
+ "3B4E6801DD23C4A7D679CCF8A386C674CFFB",
+ /* HMAC-SHA-512 */
+ "B0BA465637458C6990E5A8C5F61D4AF7E576D97FF94B872DE76F8050361E"
+ "E3DBA91CA5C11AA25EB4D679275CC5788063A5F19741120C4F2DE2ADEBEB"
+ "10A298DD"
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ },
+ { /* 5 */ {
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c"
+ }, { 20 }, {
+ "Test With Truncation"
+ }, { 20 }, {
+ /* HMAC-SHA-1 */
+ "4C1A03424B55E07FE7F27BE1",
+ /* HMAC-SHA-224 */
+ "0E2AEA68A90C8D37C988BCDB9FCA6FA8",
+ /* HMAC-SHA-256 */
+ "A3B6167473100EE06E0C796C2955552B",
+ /* HMAC-SHA-384 */
+ "3ABF34C3503B2A23A46EFC619BAEF897",
+ /* HMAC-SHA-512 */
+ "415FAD6271580A531D4179BC891D87A6"
+
+
+
+Eastlake & Hansen Informational [Page 102]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ }, { 12, 16, 16, 16, 16 }
+ },
+ { /* 6 */ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ }, { 80, 131 }, {
+ "Test Using Larger Than Block-Size Key - Hash Key First"
+ }, { 54 }, {
+ /* HMAC-SHA-1 */
+ "AA4AE5E15272D00E95705637CE8A3B55ED402112",
+ /* HMAC-SHA-224 */
+ "95E9A0DB962095ADAEBE9B2D6F0DBCE2D499F112F2D2B7273FA6870E",
+ /* HMAC-SHA-256 */
+ "60E431591EE0B67F0D8A26AACBF5B77F8E0BC6213728C5140546040F0EE3"
+ "7F54",
+ /* HMAC-SHA-384 */
+ "4ECE084485813E9088D2C63A041BC5B44F9EF1012A2B588F3CD11F05033A"
+ "C4C60C2EF6AB4030FE8296248DF163F44952",
+ /* HMAC-SHA-512 */
+ "80B24263C7C1A3EBB71493C1DD7BE8B49B46D1F41B4AEEC1121B013783F8"
+ "F3526B56D037E05F2598BD0FD2215D6A1E5295E64F73F63F0AEC8B915A98"
+ "5D786598"
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ },
+ { /* 7 */ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+ }, { 80, 131 }, {
+ "Test Using Larger Than Block-Size Key and "
+ "Larger Than One Block-Size Data",
+ "\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20"
+ "\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+ "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65"
+
+
+
+Eastlake & Hansen Informational [Page 103]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+ "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73"
+ "\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+ "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20"
+ "\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+ "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68"
+ "\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+ "\x6d\x2e"
+ /* "This is a test using a larger than block-size key and a "
+ "larger than block-size data. The key needs to be hashed "
+ "before being used by the HMAC algorithm." */
+ }, { 73, 152 }, {
+ /* HMAC-SHA-1 */
+ "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91",
+ /* HMAC-SHA-224 */
+ "3A854166AC5D9F023F54D517D0B39DBD946770DB9C2B95C9F6F565D1",
+ /* HMAC-SHA-256 */
+ "9B09FFA71B942FCB27635FBCD5B0E944BFDC63644F0713938A7F51535C3A"
+ "35E2",
+ /* HMAC-SHA-384 */
+ "6617178E941F020D351E2F254E8FD32C602420FEB0B8FB9ADCCEBB82461E"
+ "99C5A678CC31E799176D3860E6110C46523E",
+ /* HMAC-SHA-512 */
+ "E37B6A775DC87DBAA4DFA9F96E5E3FFDDEBD71F8867289865DF5A32D20CD"
+ "C944B6022CAC3C4982B10D5EEB55C3E4DE15134676FB6DE0446065C97440"
+ "FA8C6A58"
+ }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+ SHA384HashSize, SHA512HashSize }
+ }
+};
+
+/* Test arrays for HKDF. */
+struct hkdfhash {
+ SHAversion whichSha;
+ int ikmlength;
+ const char *ikmarray;
+ int saltlength;
+ const char *saltarray;
+ int infolength;
+ const char *infoarray;
+ int prklength;
+ const char *prkarray;
+ int okmlength;
+ const char *okmarray;
+} hkdfhashes[HKDFTESTCOUNT] = {
+ { /* RFC 5869 A.1. Test Case 1 */
+ SHA256,
+ 22, "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+
+
+
+Eastlake & Hansen Informational [Page 104]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+ 13, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c",
+ 10, "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9",
+ 32, "077709362C2E32DF0DDC3F0DC47BBA6390B6C73BB50F9C3122EC844A"
+ "D7C2B3E5",
+ 42, "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56"
+ "ECC4C5BF34007208D5B887185865"
+ },
+ { /* RFC 5869 A.2. Test Case 2 */
+ SHA256,
+ 80, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
+ "\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b"
+ "\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29"
+ "\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37"
+ "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45"
+ "\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+ 80, "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d"
+ "\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b"
+ "\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89"
+ "\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97"
+ "\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5"
+ "\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
+ 80, "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd"
+ "\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb"
+ "\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9"
+ "\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7"
+ "\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5"
+ "\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+ 32, "06A6B88C5853361A06104C9CEB35B45C"
+ "EF760014904671014A193F40C15FC244",
+ 82, "B11E398DC80327A1C8E7F78C596A4934"
+ "4F012EDA2D4EFAD8A050CC4C19AFA97C"
+ "59045A99CAC7827271CB41C65E590E09"
+ "DA3275600C2F09B8367793A9ACA3DB71"
+ "CC30C58179EC3E87C14C01D5C1F3434F"
+ "1D87"
+ },
+ { /* RFC 5869 A.3. Test Case 3 */
+ SHA256,
+ 22, "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+ 0, "",
+ 0, "",
+ 32, "19EF24A32C717B167F33A91D6F648BDF"
+ "96596776AFDB6377AC434C1C293CCB04",
+ 42, "8DA4E775A563C18F715F802A063C5A31"
+ "B8A11F5C5EE1879EC3454E5F3C738D2D"
+ "9D201395FAA4B61A96C8"
+
+
+
+Eastlake & Hansen Informational [Page 105]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ },
+ { /* RFC 5869 A.4. Test Case 4 */
+ SHA1,
+ 11, "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+ 13, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c",
+ 10, "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9",
+ 20, "9B6C18C432A7BF8F0E71C8EB88F4B30BAA2BA243",
+ 42, "085A01EA1B10F36933068B56EFA5AD81"
+ "A4F14B822F5B091568A9CDD4F155FDA2"
+ "C22E422478D305F3F896"
+ },
+ { /* RFC 5869 A.5. Test Case 5 */
+ SHA1,
+ 80, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
+ "\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b"
+ "\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29"
+ "\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37"
+ "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45"
+ "\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+ 80, "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D"
+ "\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B"
+ "\x7C\x7D\x7E\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89"
+ "\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97"
+ "\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5"
+ "\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF",
+ 80, "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD"
+ "\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB"
+ "\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9"
+ "\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7"
+ "\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5"
+ "\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF",
+ 20, "8ADAE09A2A307059478D309B26C4115A224CFAF6",
+ 82, "0BD770A74D1160F7C9F12CD5912A06EB"
+ "FF6ADCAE899D92191FE4305673BA2FFE"
+ "8FA3F1A4E5AD79F3F334B3B202B2173C"
+ "486EA37CE3D397ED034C7F9DFEB15C5E"
+ "927336D0441F4C4300E2CFF0D0900B52"
+ "D3B4"
+ },
+ { /* RFC 5869 A.6. Test Case 6 */
+ SHA1,
+ 22, "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+ 0, "",
+ 0, "",
+ 20, "DA8C8A73C7FA77288EC6F5E7C297786AA0D32D01",
+ 42, "0AC1AF7002B3D761D1E55298DA9D0506"
+ "B9AE52057220A306E07B6B87E8DF21D0"
+
+
+
+Eastlake & Hansen Informational [Page 106]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "EA00033DE03984D34918"
+ },
+ { /* RFC 5869 A.7. Test Case 7. */
+ SHA1,
+ 22, "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
+ 0, 0,
+ 0, "",
+ 20, "2ADCCADA18779E7C2077AD2EB19D3F3E731385DD",
+ 42, "2C91117204D745F3500D636A62F64F0A"
+ "B3BAE548AA53D423B0D1F27EBBA6F5E5"
+ "673A081D70CCE7ACFC48"
+ }
+};
+
+/*
+ * Check the hash value against the expected string, expressed in hex
+ */
+static const char hexdigits[ ] = "0123456789ABCDEF";
+int checkmatch(const unsigned char *hashvalue,
+ const char *hexstr, int hashsize)
+{
+ int i;
+ for (i = 0; i < hashsize; ++i) {
+ if (*hexstr++ != hexdigits[(hashvalue[i] >> 4) & 0xF])
+ return 0;
+ if (*hexstr++ != hexdigits[hashvalue[i] & 0xF]) return 0;
+ }
+ return 1;
+}
+
+/*
+ * Print the string, converting non-printable characters to "."
+ */
+void printstr(const char *str, int len)
+{
+ for ( ; len-- > 0; str++)
+ putchar(isprint((unsigned char)*str) ? *str : '.');
+}
+
+/*
+ * Print the string, converting all characters to hex "## ".
+ */
+void printxstr(const char *str, int len)
+{
+ char *sep = "";
+ for ( ; len-- > 0; str++) {
+ printf("%s%c%c", sep, hexdigits[(*str >> 4) & 0xF],
+
+
+
+Eastlake & Hansen Informational [Page 107]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ hexdigits[*str & 0xF]);
+ sep = " ";
+ }
+}
+
+/*
+ * Print a usage message.
+ */
+void usage(const char *argv0)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "Common options: [-h hash] [-w|-x|-6] [-H]\n"
+ "Hash a string:\n"
+ "\t%s [-S expectedresult] -s hashstr [-k key] "
+ "[-i info -L okm-len]\n"
+ "Hash a file:\n"
+ "\t%s [-S expectedresult] -f file [-k key] "
+ "[-i info -L okm-len]\n"
+ "Hash a file, ignoring whitespace:\n"
+ "\t%s [-S expectedresult] -F file [-k key] "
+ "[-i info -L okm-len]\n"
+ "Additional bits to add in: [-B bitcount -b bits]\n"
+ "(If -k,-i&-L are used, run HKDF-SHA###.\n"
+ " If -k is used, but not -i&-L, run HMAC-SHA###.\n"
+ " Otherwise, run SHA###.)\n"
+ "Standard tests:\n"
+ "\t%s [-m | -d] [-l loopcount] [-t test#] [-e]\n"
+ "\t\t[-r randomseed] [-R randomloop-count] "
+ "[-p] [-P|-X]\n"
+ "-h\thash to test: "
+ "0|SHA1, 1|SHA224, 2|SHA256, 3|SHA384, 4|SHA512\n"
+ "-m\tperform hmac standard tests\n"
+ "-k\tkey for hmac test\n"
+ "-d\tperform hkdf standard tests\n"
+ "-t\ttest case to run, 1-10\n"
+ "-l\thow many times to run the test\n"
+ "-e\ttest error returns\n"
+ "-p\tdo not print results\n"
+ "-P\tdo not print PASSED/FAILED\n"
+ "-X\tprint FAILED, but not PASSED\n"
+ "-r\tseed for random test\n"
+ "-R\thow many times to run random test\n"
+ "-s\tstring to hash\n"
+ "-S\texpected result of hashed string, in hex\n"
+ "-w\toutput hash in raw format\n"
+ "-x\toutput hash in hex format\n"
+ "-6\toutput hash in base64 format\n"
+
+
+
+Eastlake & Hansen Informational [Page 108]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "-B\t# extra bits to add in after string or file input\n"
+ "-b\textra bits to add (high order bits of #, 0# or 0x#)\n"
+ "-H\tinput hashstr or randomseed is in hex\n"
+ , argv0, argv0, argv0, argv0);
+ exit(1);
+}
+
+/*
+ * Print the results and PASS/FAIL.
+ */
+void printResult(uint8_t *Message_Digest, int hashsize,
+ const char *hashname, const char *testtype, const char *testname,
+ const char *resultarray, int printResults, int printPassFail)
+{
+ int i, k;
+ if (printResults == PRINTTEXT) {
+ printf("\nhashsize=%d\n", hashsize);
+ putchar('\t');
+ for (i = 0; i < hashsize; ++i) {
+ putchar(hexdigits[(Message_Digest[i] >> 4) & 0xF]);
+ putchar(hexdigits[Message_Digest[i] & 0xF]);
+ putchar(' ');
+ }
+ putchar('\n');
+ } else if (printResults == PRINTRAW) {
+ fwrite(Message_Digest, 1, hashsize, stdout);
+ } else if (printResults == PRINTHEX) {
+ for (i = 0; i < hashsize; ++i) {
+ putchar(hexdigits[(Message_Digest[i] >> 4) & 0xF]);
+ putchar(hexdigits[Message_Digest[i] & 0xF]);
+ }
+ putchar('\n');
+ } else if (printResults == PRINTBASE64) {
+ unsigned char b;
+ char *sm = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ for (i = 0; i < hashsize; i += 3) {
+ putchar(sm[Message_Digest[i] >> 2]);
+ b = (Message_Digest[i] & 0x03) << 4;
+ if (i+1 < hashsize) b |= Message_Digest[i+1] >> 4;
+ putchar(sm[b]);
+ if (i+1 < hashsize) {
+ b = (Message_Digest[i+1] & 0x0f) << 2;
+ if (i+2 < hashsize) b |= Message_Digest[i+2] >> 6;
+ putchar(sm[b]);
+ } else putchar('=');
+ if (i+2 < hashsize) putchar(sm[Message_Digest[i+2] & 0x3f]);
+ else putchar('=');
+
+
+
+Eastlake & Hansen Informational [Page 109]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ }
+ putchar('\n');
+ }
+
+ if (printResults && resultarray) {
+ printf(" Should match:\n\t");
+ for (i = 0, k = 0; i < hashsize; i++, k += 2) {
+ putchar(resultarray[k]);
+ putchar(resultarray[k+1]);
+ putchar(' ');
+ }
+ putchar('\n');
+ }
+
+ if (printPassFail && resultarray) {
+ int ret = checkmatch(Message_Digest, resultarray, hashsize);
+ if ((printPassFail == PRINTPASSFAIL) || !ret)
+ printf("%s %s %s: %s\n", hashname, testtype, testname,
+ ret ? "PASSED" : "FAILED");
+ }
+}
+
+/*
+ * Exercise a hash series of functions. The input is the testarray,
+ * repeated repeatcount times, followed by the extrabits. If the
+ * result is known, it is in resultarray in uppercase hex.
+ */
+int hash(int testno, int loopno, int hashno,
+ const char *testarray, int length, long repeatcount,
+ int numberExtrabits, int extrabits, const unsigned char *keyarray,
+ int keylen, const unsigned char *info, int infolen, int okmlen,
+ const char *resultarray, int hashsize, int printResults,
+ int printPassFail)
+{
+ USHAContext sha;
+ HMACContext hmac;
+ HKDFContext hkdf;
+ int err, i;
+ uint8_t Message_Digest_Buf[USHAMaxHashSize];
+ uint8_t *Message_Digest = Message_Digest_Buf;
+ char buf[20];
+
+ if (printResults == PRINTTEXT) {
+ printf("\nTest %d: Iteration %d, Repeat %ld\n\t'", testno+1,
+ loopno, repeatcount);
+ printstr(testarray, length);
+ printf("'\n\t'");
+ printxstr(testarray, length);
+
+
+
+Eastlake & Hansen Informational [Page 110]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ printf("'\n");
+ printf(" Length=%d bytes (%d bits), ", length, length * 8);
+ printf("ExtraBits %d: %2.2x\n", numberExtrabits, extrabits);
+ }
+
+ if (info) Message_Digest = malloc(okmlen);
+ memset(&sha, '\343', sizeof(sha)); /* force bad data into struct */
+ memset(&hmac, '\343', sizeof(hmac));
+ memset(&hkdf, '\343', sizeof(hkdf));
+
+ err = info ? hkdfReset(&hkdf, hashes[hashno].whichSha,
+ keyarray, keylen) :
+ keyarray ? hmacReset(&hmac, hashes[hashno].whichSha,
+ keyarray, keylen) :
+ USHAReset(&sha, hashes[hashno].whichSha);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hash(): %sReset Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ return err;
+ }
+
+ for (i = 0; i < repeatcount; ++i) {
+ err = info ? hkdfInput(&hkdf, (const uint8_t *)testarray, length) :
+ keyarray ? hmacInput(&hmac, (const uint8_t *) testarray,
+ length) :
+ USHAInput(&sha, (const uint8_t *) testarray,
+ length);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hash(): %sInput Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ return err;
+ }
+ }
+
+ if (numberExtrabits > 0) {
+ err = info ? hkdfFinalBits(&hkdf, extrabits, numberExtrabits) :
+ keyarray ? hmacFinalBits(&hmac, (uint8_t) extrabits,
+ numberExtrabits) :
+ USHAFinalBits(&sha, (uint8_t) extrabits,
+ numberExtrabits);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hash(): %sFinalBits Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ return err;
+ }
+ }
+
+ err = info ? hkdfResult(&hkdf, 0, info, infolen,
+
+
+
+Eastlake & Hansen Informational [Page 111]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ Message_Digest, okmlen) :
+ keyarray ? hmacResult(&hmac, Message_Digest) :
+ USHAResult(&sha, Message_Digest);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hash(): %s Result Error %d, could not compute "
+ "message digest.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ return err;
+ }
+
+ sprintf(buf, "%d", testno+1);
+ printResult(Message_Digest, info ? okmlen : hashsize,
+ hashes[hashno].name, info ? "hkdf standard test" :
+ keyarray ? "hmac standard test" : "sha standard test", buf,
+ resultarray, printResults, printPassFail);
+
+ return err;
+}
+
+/*
+ * Exercise an HKDF series. The input is the testarray,
+ * repeated repeatcount times, followed by the extrabits. If the
+ * result is known, it is in resultarray in uppercase hex.
+ */
+int hashHkdf(int testno, int loopno, int hashno,
+ int printResults, int printPassFail)
+{
+ int err;
+ unsigned char prk[USHAMaxHashSize+1];
+ uint8_t okm[255 * USHAMaxHashSize+1];
+ char buf[20];
+
+ if (printResults == PRINTTEXT) {
+ printf("\nTest %d: Iteration %d\n\tSALT\t'", testno+1, loopno);
+ printxstr(hkdfhashes[testno].saltarray,
+ hkdfhashes[testno].saltlength);
+ printf("'\n\tIKM\t'");
+ printxstr(hkdfhashes[testno].ikmarray,
+ hkdfhashes[testno].ikmlength);
+ printf("'\n\tINFO\t'");
+ printxstr(hkdfhashes[testno].infoarray,
+ hkdfhashes[testno].infolength);
+ printf("'\n");
+ printf(" L=%d bytes\n", hkdfhashes[testno].okmlength);
+ }
+
+ /* Run hkdf() against the test vectors */
+ err = hkdf(hkdfhashes[testno].whichSha,
+
+
+
+Eastlake & Hansen Informational [Page 112]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ (const uint8_t *) hkdfhashes[testno].saltarray,
+ hkdfhashes[testno].saltlength,
+ (const uint8_t *) hkdfhashes[testno].ikmarray,
+ hkdfhashes[testno].ikmlength,
+ (const uint8_t *) hkdfhashes[testno].infoarray,
+ hkdfhashes[testno].infolength, okm,
+ hkdfhashes[testno].okmlength);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashHkdf(): hkdf Error %d.\n", err);
+ return err;
+ }
+ sprintf(buf, "hkdf %d", testno+1);
+ printResult(okm, hkdfhashes[testno].okmlength,
+ USHAHashName(hkdfhashes[testno].whichSha), "hkdf standard test",
+ buf, hkdfhashes[testno].okmarray, printResults, printPassFail);
+
+ /* Now run hkdfExtract() by itself against the test vectors */
+ /* to verify the intermediate results. */
+ err = hkdfExtract(hkdfhashes[testno].whichSha,
+ (const uint8_t *) hkdfhashes[testno].saltarray,
+ hkdfhashes[testno].saltlength,
+ (const uint8_t *) hkdfhashes[testno].ikmarray,
+ hkdfhashes[testno].ikmlength, prk);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashHkdf(): hkdfExtract Error %d.\n", err);
+ return err;
+ }
+ sprintf(buf, "hkdfExtract %d", testno+1);
+ printResult(prk, USHAHashSize(hkdfhashes[testno].whichSha),
+ USHAHashName(hkdfhashes[testno].whichSha), "hkdf standard test",
+ buf, hkdfhashes[testno].prkarray, printResults, printPassFail);
+
+ /* Now run hkdfExpand() by itself against the test vectors */
+ /* using the intermediate results from hkdfExtract. */
+ err = hkdfExpand(hkdfhashes[testno].whichSha, prk,
+ USHAHashSize(hkdfhashes[testno].whichSha),
+ (const uint8_t *)hkdfhashes[testno].infoarray,
+ hkdfhashes[testno].infolength, okm, hkdfhashes[testno].okmlength);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashHkdf(): hkdfExpand Error %d.\n", err);
+ return err;
+ }
+ sprintf(buf, "hkdfExpand %d", testno+1);
+ printResult(okm, hkdfhashes[testno].okmlength,
+ USHAHashName(hkdfhashes[testno].whichSha), "hkdf standard test",
+ buf, hkdfhashes[testno].okmarray, printResults, printPassFail);
+
+ return err;
+
+
+
+Eastlake & Hansen Informational [Page 113]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+}
+
+/*
+ * Exercise a hash series of functions. The input is a filename.
+ * If the result is known, it is in resultarray in uppercase hex.
+ */
+int hashfile(int hashno, const char *hashfilename, int bits,
+ int bitcount, int skipSpaces, const unsigned char *keyarray,
+ int keylen, const unsigned char *info, int infolen, int okmlen,
+ const char *resultarray, int hashsize,
+ int printResults, int printPassFail)
+{
+ USHAContext sha;
+ HMACContext hmac;
+ HKDFContext hkdf;
+ int err, nread, c;
+ unsigned char buf[4096];
+ uint8_t Message_Digest_Buf[USHAMaxHashSize];
+ uint8_t *Message_Digest = Message_Digest_Buf;
+ unsigned char cc;
+ FILE *hashfp = (strcmp(hashfilename, "-") == 0) ? stdin :
+ fopen(hashfilename, "r");
+
+ if (!hashfp) {
+ fprintf(stderr, "cannot open file '%s'\n", hashfilename);
+ return shaStateError;
+ }
+
+ if (info) Message_Digest = malloc(okmlen);
+ memset(&sha, '\343', sizeof(sha)); /* force bad data into struct */
+ memset(&hmac, '\343', sizeof(hmac));
+ memset(&hkdf, '\343', sizeof(hkdf));
+ err = info ? hkdfReset(&hkdf, hashes[hashno].whichSha,
+ keyarray, keylen) :
+ keyarray ? hmacReset(&hmac, hashes[hashno].whichSha,
+ keyarray, keylen) :
+ USHAReset(&sha, hashes[hashno].whichSha);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashfile(): %sReset Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ return err;
+ }
+
+ if (skipSpaces)
+ while ((c = getc(hashfp)) != EOF) {
+ if (!isspace(c)) {
+ cc = (unsigned char)c;
+ err = info ? hkdfInput(&hkdf, &cc, 1) :
+
+
+
+Eastlake & Hansen Informational [Page 114]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ keyarray ? hmacInput(&hmac, &cc, 1) :
+ USHAInput(&sha, &cc, 1);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashfile(): %sInput Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmac" : "sha", err);
+ if (hashfp != stdin) fclose(hashfp);
+ return err;
+ }
+ }
+ }
+ else
+ while ((nread = fread(buf, 1, sizeof(buf), hashfp)) > 0) {
+ err = info ? hkdfInput(&hkdf, buf, nread) :
+ keyarray ? hmacInput(&hmac, buf, nread) :
+ USHAInput(&sha, buf, nread);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashfile(): %s Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmacInput" :
+ "shaInput", err);
+ if (hashfp != stdin) fclose(hashfp);
+ return err;
+ }
+ }
+
+ if (bitcount > 0)
+ err = info ? hkdfFinalBits(&hkdf, bits, bitcount) :
+ keyarray ? hmacFinalBits(&hmac, bits, bitcount) :
+ USHAFinalBits(&sha, bits, bitcount);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashfile(): %s Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmacFinalBits" :
+ "shaFinalBits", err);
+ if (hashfp != stdin) fclose(hashfp);
+ return err;
+ }
+
+ err = info ? hkdfResult(&hkdf, 0, info, infolen,
+ Message_Digest, okmlen) :
+ keyarray ? hmacResult(&hmac, Message_Digest) :
+ USHAResult(&sha, Message_Digest);
+ if (err != shaSuccess) {
+ fprintf(stderr, "hashfile(): %s Error %d.\n",
+ info ? "hkdf" : keyarray ? "hmacResult" :
+ "shaResult", err);
+ if (hashfp != stdin) fclose(hashfp);
+ return err;
+ }
+
+
+
+
+Eastlake & Hansen Informational [Page 115]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ printResult(Message_Digest, info ? okmlen : hashsize,
+ hashes[hashno].name, "file", hashfilename, resultarray,
+ printResults, printPassFail);
+
+ if (hashfp != stdin) fclose(hashfp);
+ if (info) free(Message_Digest);
+ return err;
+}
+
+/*
+ * Exercise a hash series of functions through multiple permutations.
+ * The input is an initial seed. That seed is replicated 3 times.
+ * For 1000 rounds, the previous three results are used as the input.
+ * This result is then checked, and used to seed the next cycle.
+ * If the result is known, it is in resultarrays in uppercase hex.
+ */
+void randomtest(int hashno, const char *seed, int hashsize,
+ const char **resultarrays, int randomcount,
+ int printResults, int printPassFail)
+{
+ int i, j; char buf[20];
+ unsigned char SEED[USHAMaxHashSize], MD[1003][USHAMaxHashSize];
+
+ /* INPUT: Seed - A random seed n bits long */
+ memcpy(SEED, seed, hashsize);
+ if (printResults == PRINTTEXT) {
+ printf("%s random test seed= '", hashes[hashno].name);
+ printxstr(seed, hashsize);
+ printf("'\n");
+ }
+
+ for (j = 0; j < randomcount; j++) {
+ /* MD0 = MD1 = MD2 = Seed; */
+ memcpy(MD[0], SEED, hashsize);
+ memcpy(MD[1], SEED, hashsize);
+ memcpy(MD[2], SEED, hashsize);
+ for (i=3; i<1003; i++) {
+ /* Mi = MDi-3 || MDi-2 || MDi-1; */
+ USHAContext Mi;
+ memset(&Mi, '\343', sizeof(Mi)); /* force bad data into struct */
+ USHAReset(&Mi, hashes[hashno].whichSha);
+ USHAInput(&Mi, MD[i-3], hashsize);
+ USHAInput(&Mi, MD[i-2], hashsize);
+ USHAInput(&Mi, MD[i-1], hashsize);
+ /* MDi = SHA(Mi); */
+ USHAResult(&Mi, MD[i]);
+ }
+
+
+
+
+Eastlake & Hansen Informational [Page 116]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /* MDj = Seed = MDi; */
+ memcpy(SEED, MD[i-1], hashsize);
+
+ /* OUTPUT: MDj */
+ sprintf(buf, "%d", j);
+ printResult(SEED, hashsize, hashes[hashno].name, "random test",
+ buf, resultarrays ? resultarrays[j] : 0, printResults,
+ (j < RANDOMCOUNT) ? printPassFail : 0);
+ }
+}
+
+/*
+ * Look up a hash name.
+ */
+int findhash(const char *argv0, const char *opt)
+{
+ int i;
+ const char *names[HASHCOUNT][2] = {
+ { "0", "sha1" }, { "1", "sha224" }, { "2", "sha256" },
+ { "3", "sha384" }, { "4", "sha512" }
+ };
+ for (i = 0; i < HASHCOUNT; i++)
+ if ((strcmp(opt, names[i][0]) == 0) ||
+ (scasecmp(opt, names[i][1]) == 0))
+ return i;
+
+ fprintf(stderr, "%s: Unknown hash name: '%s'\n", argv0, opt);
+ usage(argv0);
+ return 0;
+}
+
+/*
+ * Run some tests that should invoke errors.
+ */
+void testErrors(int hashnolow, int hashnohigh, int printResults,
+ int printPassFail)
+{
+ USHAContext usha;
+ uint8_t Message_Digest[USHAMaxHashSize];
+ int hashno, err;
+
+ for (hashno = hashnolow; hashno <= hashnohigh; hashno++) {
+ memset(&usha, '\343', sizeof(usha)); /* force bad data */
+ USHAReset(&usha, hashno);
+ USHAResult(&usha, Message_Digest);
+ err = USHAInput(&usha, (const unsigned char *)"foo", 3);
+ if (printResults == PRINTTEXT)
+ printf ("\nError %d. Should be %d.\n", err, shaStateError);
+
+
+
+Eastlake & Hansen Informational [Page 117]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ if ((printPassFail == PRINTPASSFAIL) ||
+ ((printPassFail == PRINTFAIL) && (err != shaStateError)))
+ printf("%s se: %s\n", hashes[hashno].name,
+ (err == shaStateError) ? "PASSED" : "FAILED");
+
+ err = USHAFinalBits(&usha, 0x80, 3);
+ if (printResults == PRINTTEXT)
+ printf ("\nError %d. Should be %d.\n", err, shaStateError);
+ if ((printPassFail == PRINTPASSFAIL) ||
+ ((printPassFail == PRINTFAIL) && (err != shaStateError)))
+ printf("%s se: %s\n", hashes[hashno].name,
+ (err == shaStateError) ? "PASSED" : "FAILED");
+
+ err = USHAReset(0, hashes[hashno].whichSha);
+ if (printResults == PRINTTEXT)
+ printf("\nError %d. Should be %d.\n", err, shaNull);
+ if ((printPassFail == PRINTPASSFAIL) ||
+ ((printPassFail == PRINTFAIL) && (err != shaNull)))
+ printf("%s usha null: %s\n", hashes[hashno].name,
+ (err == shaNull) ? "PASSED" : "FAILED");
+
+ switch (hashno) {
+ case SHA1: err = SHA1Reset(0); break;
+ case SHA224: err = SHA224Reset(0); break;
+ case SHA256: err = SHA256Reset(0); break;
+ case SHA384: err = SHA384Reset(0); break;
+ case SHA512: err = SHA512Reset(0); break;
+ }
+ if (printResults == PRINTTEXT)
+ printf("\nError %d. Should be %d.\n", err, shaNull);
+ if ((printPassFail == PRINTPASSFAIL) ||
+ ((printPassFail == PRINTFAIL) && (err != shaNull)))
+ printf("%s sha null: %s\n", hashes[hashno].name,
+ (err == shaNull) ? "PASSED" : "FAILED");
+ }
+}
+
+/* replace a hex string in place with its value */
+int unhexStr(char *hexstr)
+{
+ char *o = hexstr;
+ int len = 0, nibble1 = 0, nibble2 = 0;
+ if (!hexstr) return 0;
+ for ( ; *hexstr; hexstr++) {
+ if (isalpha((int)(unsigned char)(*hexstr))) {
+ nibble1 = tolower((int)(unsigned char)(*hexstr)) - 'a' + 10;
+ } else if (isdigit((int)(unsigned char)(*hexstr))) {
+ nibble1 = *hexstr - '0';
+
+
+
+Eastlake & Hansen Informational [Page 118]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ } else {
+ printf("\nError: bad hex character '%c'\n", *hexstr);
+ }
+ if (!*++hexstr) break;
+ if (isalpha((int)(unsigned char)(*hexstr))) {
+ nibble2 = tolower((int)(unsigned char)(*hexstr)) - 'a' + 10;
+ } else if (isdigit((int)(unsigned char)(*hexstr))) {
+ nibble2 = *hexstr - '0';
+ } else {
+ printf("\nError: bad hex character '%c'\n", *hexstr);
+ }
+ *o++ = (char)((nibble1 << 4) | nibble2);
+ len++;
+ }
+ return len;
+}
+
+int main(int argc, char **argv)
+{
+ int i, err;
+ int loopno, loopnohigh = 1;
+ int hashno, hashnolow = 0, hashnohigh = HASHCOUNT - 1;
+ int testno, testnolow = 0, testnohigh;
+ int ntestnohigh = 0;
+ int printResults = PRINTTEXT;
+ int printPassFail = 1;
+ int checkErrors = 0;
+ char *hashstr = 0;
+ int hashlen = 0;
+ const char *resultstr = 0;
+ char *randomseedstr = 0;
+ int runHmacTests = 0;
+ int runHkdfTests = 0;
+ char *hmacKey = 0;
+ int hmaclen = 0;
+ char *info = 0;
+ int infolen = 0, okmlen = 0;
+ int randomcount = RANDOMCOUNT;
+ const char *hashfilename = 0;
+ const char *hashFilename = 0;
+ int extrabits = 0, numberExtrabits = 0;
+ int strIsHex = 0;
+
+ if ('A' != 0x41) {
+ fprintf(stderr, "%s: these tests require ASCII\n", argv[0]);
+ }
+
+ while ((i = getopt(argc, argv,
+
+
+
+Eastlake & Hansen Informational [Page 119]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ "6b:B:def:F:h:i:Hk:l:L:mpPr:R:s:S:t:wxX")) != -1)
+ switch (i) {
+ case 'b': extrabits = strtol(optarg, 0, 0); break;
+ case 'B': numberExtrabits = atoi(optarg); break;
+ case 'd': runHkdfTests = 1; break;
+ case 'e': checkErrors = 1; break;
+ case 'f': hashfilename = optarg; break;
+ case 'F': hashFilename = optarg; break;
+ case 'h': hashnolow = hashnohigh = findhash(argv[0], optarg);
+ break;
+ case 'H': strIsHex = 1; break;
+ case 'i': info = optarg; infolen = strlen(optarg); break;
+ case 'k': hmacKey = optarg; hmaclen = strlen(optarg); break;
+ case 'l': loopnohigh = atoi(optarg); break;
+ case 'L': okmlen = strtol(optarg, 0, 0); break;
+ case 'm': runHmacTests = 1; break;
+ case 'P': printPassFail = 0; break;
+ case 'p': printResults = PRINTNONE; break;
+ case 'R': randomcount = atoi(optarg); break;
+ case 'r': randomseedstr = optarg; break;
+ case 's': hashstr = optarg; hashlen = strlen(hashstr); break;
+ case 'S': resultstr = optarg; break;
+ case 't': testnolow = ntestnohigh = atoi(optarg) - 1; break;
+ case 'w': printResults = PRINTRAW; break;
+ case 'x': printResults = PRINTHEX; break;
+ case 'X': printPassFail = 2; break;
+ case '6': printResults = PRINTBASE64; break;
+ default: usage(argv[0]);
+ }
+
+ if (strIsHex) {
+ hashlen = unhexStr(hashstr);
+ unhexStr(randomseedstr);
+ hmaclen = unhexStr(hmacKey);
+ infolen = unhexStr(info);
+ }
+ testnohigh = (ntestnohigh != 0) ? ntestnohigh:
+ runHmacTests ? (HMACTESTCOUNT-1) :
+ runHkdfTests ? (HKDFTESTCOUNT-1) :
+ (TESTCOUNT-1);
+ if ((testnolow < 0) ||
+ (testnohigh >= (runHmacTests ? HMACTESTCOUNT : TESTCOUNT)) ||
+ (hashnolow < 0) || (hashnohigh >= HASHCOUNT) ||
+ (hashstr && (testnolow == testnohigh)) ||
+ (randomcount < 0) ||
+ (resultstr && (!hashstr && !hashfilename && !hashFilename)) ||
+ ((runHmacTests || hmacKey) && randomseedstr) ||
+ (hashfilename && hashFilename) ||
+
+
+
+Eastlake & Hansen Informational [Page 120]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ (info && ((infolen <= 0) || (okmlen <= 0))) ||
+ (info && !hmacKey))
+ usage(argv[0]);
+
+ /*
+ * Perform SHA/HMAC tests
+ */
+ for (hashno = hashnolow; hashno <= hashnohigh; ++hashno) {
+ if (printResults == PRINTTEXT)
+ printf("Hash %s\n", hashes[hashno].name);
+ err = shaSuccess;
+
+ for (loopno = 1; (loopno <= loopnohigh) && (err == shaSuccess);
+ ++loopno) {
+ if (hashstr)
+ err = hash(0, loopno, hashno, hashstr, hashlen, 1,
+ numberExtrabits, extrabits, (const unsigned char *)hmacKey,
+ hmaclen, (const uint8_t *) info, infolen, okmlen, resultstr,
+ hashes[hashno].hashsize, printResults, printPassFail);
+
+ else if (randomseedstr)
+ randomtest(hashno, randomseedstr, hashes[hashno].hashsize, 0,
+ randomcount, printResults, printPassFail);
+
+ else if (hashfilename)
+ err = hashfile(hashno, hashfilename, extrabits,
+ numberExtrabits, 0,
+ (const unsigned char *)hmacKey, hmaclen,
+ (const uint8_t *) info, infolen, okmlen,
+ resultstr, hashes[hashno].hashsize,
+ printResults, printPassFail);
+
+ else if (hashFilename)
+ err = hashfile(hashno, hashFilename, extrabits,
+ numberExtrabits, 1,
+ (const unsigned char *)hmacKey, hmaclen,
+ (const uint8_t *) info, infolen, okmlen,
+ resultstr, hashes[hashno].hashsize,
+ printResults, printPassFail);
+
+ else /* standard tests */ {
+ for (testno = testnolow;
+ (testno <= testnohigh) && (err == shaSuccess); ++testno) {
+ if (runHmacTests) {
+ err = hash(testno, loopno, hashno,
+ hmachashes[testno].dataarray[hashno] ?
+ hmachashes[testno].dataarray[hashno] :
+ hmachashes[testno].dataarray[1] ?
+
+
+
+Eastlake & Hansen Informational [Page 121]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ hmachashes[testno].dataarray[1] :
+ hmachashes[testno].dataarray[0],
+ hmachashes[testno].datalength[hashno] ?
+ hmachashes[testno].datalength[hashno] :
+ hmachashes[testno].datalength[1] ?
+ hmachashes[testno].datalength[1] :
+ hmachashes[testno].datalength[0],
+ 1, 0, 0,
+ (const unsigned char *)(
+ hmachashes[testno].keyarray[hashno] ?
+ hmachashes[testno].keyarray[hashno] :
+ hmachashes[testno].keyarray[1] ?
+ hmachashes[testno].keyarray[1] :
+ hmachashes[testno].keyarray[0]),
+ hmachashes[testno].keylength[hashno] ?
+ hmachashes[testno].keylength[hashno] :
+ hmachashes[testno].keylength[1] ?
+ hmachashes[testno].keylength[1] :
+ hmachashes[testno].keylength[0],
+ 0, 0, 0,
+ hmachashes[testno].resultarray[hashno],
+ hmachashes[testno].resultlength[hashno],
+ printResults, printPassFail);
+ } else if (runHkdfTests) {
+ err = hashHkdf(testno, loopno, hashno,
+ printResults, printPassFail);
+ } else { /* sha tests */
+ err = hash(testno, loopno, hashno,
+ hashes[hashno].tests[testno].testarray,
+ hashes[hashno].tests[testno].length,
+ hashes[hashno].tests[testno].repeatcount,
+ hashes[hashno].tests[testno].numberExtrabits,
+ hashes[hashno].tests[testno].extrabits,
+ 0, 0, 0, 0, 0,
+ hashes[hashno].tests[testno].resultarray,
+ hashes[hashno].hashsize,
+ printResults, printPassFail);
+ }
+ }
+ if (!runHmacTests && !runHkdfTests) {
+ randomtest(hashno, hashes[hashno].randomtest,
+ hashes[hashno].hashsize, hashes[hashno].randomresults,
+ RANDOMCOUNT, printResults, printPassFail);
+ }
+ }
+ }
+ }
+
+
+
+
+Eastlake & Hansen Informational [Page 122]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ /* Test some error returns */
+ if (checkErrors) {
+ testErrors(hashnolow, hashnohigh, printResults, printPassFail);
+ }
+
+ return 0;
+}
+
+/*
+ * Compare two strings, case independently.
+ * Equivalent to strcasecmp() found on some systems.
+ */
+int scasecmp(const char *s1, const char *s2)
+{
+ for (;;) {
+ char u1 = tolower((int)(unsigned char)(*s1++));
+ char u2 = tolower((int)(unsigned char)(*s2++));
+ if (u1 != u2)
+ return u1 - u2;
+ if (u1 == '\0')
+ return 0;
+ }
+}
+
+9. Security Considerations
+
+ This document is intended to provide convenient open source access by
+ the Internet community to the United States of America Federal
+ Information Processing Standard Secure Hash Algorithms (SHAs) [FIPS
+ 180-2], HMACs based thereon, and HKDF. No independent assertion of
+ the security of these functions by the authors for any particular use
+ is intended.
+
+ See [RFC6194] for a discussion of SHA-1 Security Considerations.
+
+10. Acknowledgements
+
+ Thanks for the corrections to [RFC4634] that were provided by Alfred
+ Hoenes and Jan Andres and to Alfred's comments on the document
+ hereof.
+
+ Also to the following in alphabetic order, whose comments lead to
+ improvements in the document: James Carlson, Russ Housley, Tero
+ Kivinen, Juergen Quittek, and Sean Turner.
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 123]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+11. References
+
+11.1. Normative References
+
+ [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
+ Hashing for Message Authentication", RFC 2104, February
+ 1997.
+
+ [RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand
+ Key Derivation Function (HKDF)", RFC 5869, May 2010.
+
+ [SHS] "Secure Hash Standard", United States of American,
+ National Institute of Science and Technology, Federal
+ Information Processing Standard (FIPS) 180-3,
+ http://csrc.nist.gov/publications/fips/fips180-3/
+ fips180-3_final.pdf.
+
+ [US-ASCII] ANSI, "USA Standard Code for Information Interchange",
+ X3.4, American National Standards Institute: New York,
+ 1968.
+
+11.2. Informative References
+
+ [RFC3174] Eastlake 3rd, D. and P. Jones, "US Secure Hash Algorithm 1
+ (SHA1)", RFC 3174, September 2001.
+
+ [RFC3874] Housley, R., "A 224-bit One-way Hash Function:
+ SHA-224", RFC 3874, September 2004.
+
+ [RFC4055] Schaad, J., Kaliski, B., and R. Housley, "Additional
+ Algorithms and Identifiers for RSA Cryptography for use in
+ the Internet X.509 Public Key Infrastructure Certificate
+ and Certificate Revocation List (CRL) Profile", RFC 4055,
+ June 2005.
+
+ [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker,
+ "Randomness Requirements for Security", BCP 106, RFC 4086,
+ June 2005.
+
+ [RFC4634] Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms
+ (SHA and HMAC-SHA)", RFC 4634, July 2006.
+
+ [RFC6194] Polk, T., Chen, L., Turner, S., and P. Hoffman, "Security
+ Considerations for the SHA-0 and SHA-1 Message-Digest
+ Algorithms", RFC 6194, March 2011.
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 124]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+ [SHAVS] "The Secure Hash Algorithm Validation System (SHAVS)",
+ http://csrc.nist.gov/groups/STM/cavp/documents/shs/
+ SHAVS.pdf, July 2004.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 125]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+Appendix: Changes from RFC 4634
+
+ The following changes were made to RFC 4634 to produce this document:
+
+ 1. Add code for HKDF and brief text about HKDF with pointer to
+ [RFC5869].
+
+ 2. Fix numerous errata filed against [RFC4634] as included below.
+ Note that in no case did the old code return an incorrect hash
+ value.
+
+ 2.a. Correct some of the error return values which has erroneously
+ been "shaNull" to the correct "shaInputTooLong" error.
+
+ 2.b. Update comments and variable names within the code for
+ consistency and clarity and other editorial changes.
+
+ 2.c. The previous code for SHA-384 and SHA-512 would stop after
+ 2^93 bytes (2^96 bits). The fixed code handles up to 2^125
+ bytes (2^128 bits).
+
+ 2.d. Add additional error checking including a run time check in
+ the test driver to detect attempts to run the test driver
+ after compilation using some other character set instead of
+ [US-ASCII].
+
+ 3. Update boilerplate, remove special license in [RFC4634] as new
+ boilerplate mandates simplified BSD license.
+
+ 4. Replace MIT version of getopt with new code to satisfy IETF
+ incoming and outgoing license restrictions.
+
+ 5. Add references to [RFC6194].
+
+ 6. Other assorted editorial improvements.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 126]
+
+RFC 6234 SHAs, HMAC-SHAs, and HKDF May 2011
+
+
+Author's Address
+
+ Donald Eastlake
+ Huawei
+ 155 Beaver Street
+ Milford, MA 01757 USA
+
+ Telephone: +1-508-333-2270
+ EMail: d3e3e3@gmail.com
+
+
+ Tony Hansen
+ AT&T Laboratories
+ 200 Laurel Ave.
+ Middletown, NJ 07748 USA
+
+ Telephone: +1-732-420-8934
+ EMail: tony+shs@maillennium.att.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Eastlake & Hansen Informational [Page 127]
+
diff --git a/src/common/libManageSieve/tests/SieveBase64Test.mjs b/src/common/libManageSieve/tests/SieveBase64Test.mjs
new file mode 100644
index 00000000..ee1a61a5
--- /dev/null
+++ b/src/common/libManageSieve/tests/SieveBase64Test.mjs
@@ -0,0 +1,273 @@
+/*
+ * The contents of this file are licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ *
+ */
+
+/* eslint-disable no-magic-numbers */
+
+/* global net */
+const suite = net.tschmid.yautt.test;
+
+if (!suite)
+ throw new Error("Could not initialize test suite");
+
+import {
+ SieveBase64Decoder,
+ SieveBase64Encoder
+} from "./../SieveBase64.mjs";
+
+
+// Test vectors from RFC
+suite.add("Base64 Decoder - Empty String", function () {
+ suite.assertArrayEquals("",
+ (new SieveBase64Decoder("")).toArray());
+
+ suite.assertEquals("",
+ (new SieveBase64Decoder("")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zg==", function () {
+ suite.assertArrayEquals("f",
+ (new SieveBase64Decoder("Zg==")).toArray());
+
+ suite.assertEquals("f",
+ (new SieveBase64Decoder("Zg==")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zm8=", function () {
+ suite.assertArrayEquals("fo",
+ (new SieveBase64Decoder("Zm8=")).toArray());
+
+ suite.assertEquals("fo",
+ (new SieveBase64Decoder("Zm8=")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zm9v", function () {
+ suite.assertArrayEquals("foo",
+ (new SieveBase64Decoder("Zm9v")).toArray());
+
+ suite.assertEquals("foo",
+ (new SieveBase64Decoder("Zm9v")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zm9vYg==", function () {
+ suite.assertArrayEquals("foob",
+ (new SieveBase64Decoder("Zm9vYg==")).toArray());
+
+ suite.assertEquals("foob",
+ (new SieveBase64Decoder("Zm9vYg==")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zm9vYmE=", function () {
+ suite.assertArrayEquals("fooba",
+ (new SieveBase64Decoder("Zm9vYmE=")).toArray());
+
+ suite.assertEquals("fooba",
+ (new SieveBase64Decoder("Zm9vYmE=")).toUtf8());
+});
+
+suite.add("Base64 Decoder - Zm9vYmFy", function () {
+ suite.assertArrayEquals("foobar",
+ (new SieveBase64Decoder("Zm9vYmFy")).toArray());
+
+ suite.assertEquals("foobar",
+ (new SieveBase64Decoder("Zm9vYmFy")).toUtf8());
+});
+
+// Test vectors from Wikipedia
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhcw", function () {
+ suite.assertArrayEquals("any carnal pleas",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhcw")).toArray());
+
+ suite.assertEquals("any carnal pleas",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhcw")).toUtf8());
+});
+
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhcw==", function () {
+ suite.assertArrayEquals("any carnal pleas",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhcw==")).toArray());
+
+ suite.assertEquals("any carnal pleas",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhcw==")).toUtf8());
+});
+
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhc3U", function () {
+ suite.assertArrayEquals("any carnal pleasu",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3U")).toArray());
+
+ suite.assertEquals("any carnal pleasu",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3U")).toUtf8());
+});
+
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhc3U=", function () {
+ suite.assertArrayEquals("any carnal pleasu",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3U=")).toArray());
+
+ suite.assertEquals("any carnal pleasu",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3U=")).toUtf8());
+});
+
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhc3Vy", function () {
+ suite.assertArrayEquals("any carnal pleasur",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3Vy")).toArray());
+
+ suite.assertEquals("any carnal pleasur",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3Vy")).toUtf8());
+});
+
+suite.add("Base64 Decoder - YW55IGNhcm5hbCBwbGVhc3VyZQ==", function () {
+ suite.assertArrayEquals("any carnal pleasure",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3VyZQ==")).toArray());
+
+ suite.assertEquals("any carnal pleasure",
+ (new SieveBase64Decoder("YW55IGNhcm5hbCBwbGVhc3VyZQ==")).toUtf8());
+});
+
+suite.add("Base64 Decoder - dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ=", function () {
+ suite.assertArrayEquals("v=peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=",
+ (new SieveBase64Decoder("dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ=")).toArray());
+
+ suite.assertEquals("v=peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=",
+ (new SieveBase64Decoder("dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ=")).toUtf8());
+});
+
+suite.add("Base64 Decoder - peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=", function () {
+
+ const expectation = new Uint8Array([0xA5, 0xE4, 0x7C, 0x11, 0xBE, 0xAE, 0x35, 0xFE, 0x19,
+ 0x77, 0xB1, 0xF3, 0x27, 0x4B, 0xFB, 0x38, 0x18, 0x3F, 0x0B, 0x97, 0x31,
+ 0xA4, 0xA5, 0x6E, 0x31, 0x1C, 0x77, 0x95, 0xC9, 0xDA, 0x3A, 0x11]);
+
+ suite.assertArrayEquals(expectation,
+ (new SieveBase64Decoder("peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=")).toArray());
+});
+
+suite.add("Base64 Decoder - peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE", function () {
+
+ const expectation = new Uint8Array([0xA5, 0xE4, 0x7C, 0x11, 0xBE, 0xAE, 0x35, 0xFE, 0x19,
+ 0x77, 0xB1, 0xF3, 0x27, 0x4B, 0xFB, 0x38, 0x18, 0x3F, 0x0B, 0x97, 0x31,
+ 0xA4, 0xA5, 0x6E, 0x31, 0x1C, 0x77, 0x95, 0xC9, 0xDA, 0x3A, 0x11]);
+
+ suite.assertArrayEquals(expectation,
+ (new SieveBase64Decoder("peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE")).toArray());
+});
+
+// Test vectors from RFC
+suite.add("Base64 Encoder - Empty String", function () {
+ suite.assertArrayEquals("",
+ (new SieveBase64Encoder("")).toArray());
+
+ suite.assertEquals("",
+ (new SieveBase64Encoder("")).toUtf8());
+});
+
+suite.add("Base64 Encoder - Zg==", function () {
+ suite.assertArrayEquals("Zg==",
+ (new SieveBase64Encoder("f")).toArray());
+
+ suite.assertEquals("Zg==",
+ (new SieveBase64Encoder("f")).toUtf8());
+});
+
+suite.add("Base64 Encoder - Zm8=", function () {
+ suite.assertArrayEquals("Zm8=",
+ (new SieveBase64Encoder("fo")).toArray());
+
+ suite.assertEquals("Zm8=",
+ (new SieveBase64Encoder("fo")).toUtf8());
+});
+
+suite.add("Base64 Encoder - Zm9v", function () {
+ suite.assertArrayEquals("Zm9v",
+ (new SieveBase64Encoder("foo")).toArray());
+
+ suite.assertEquals("Zm9v",
+ (new SieveBase64Encoder("foo")).toUtf8());
+});
+
+suite.add("Base64 Encoder - Zm9vYg==", function () {
+ suite.assertArrayEquals("Zm9vYg==",
+ (new SieveBase64Encoder("foob")).toArray());
+
+ suite.assertEquals("Zm9vYg==",
+ (new SieveBase64Encoder("foob")).toUtf8());
+});
+
+suite.add("Base64 Encoder - Zm9vYmE=", function () {
+ suite.assertArrayEquals("Zm9vYmE=",
+ (new SieveBase64Encoder("fooba")).toArray() );
+
+ suite.assertEquals("Zm9vYmE=",
+ (new SieveBase64Encoder("fooba")).toUtf8() );
+});
+
+suite.add("Base64 Encoder - Zm9vYmFy", function () {
+ suite.assertArrayEquals("Zm9vYmFy",
+ (new SieveBase64Encoder("foobar")).toArray());
+
+ suite.assertEquals("Zm9vYmFy",
+ (new SieveBase64Encoder("foobar")).toUtf8());
+});
+
+
+// Test vectors from Wikipedia
+
+suite.add("Base64 Encoder - YW55IGNhcm5hbCBwbGVhcw==", function () {
+ suite.assertArrayEquals("YW55IGNhcm5hbCBwbGVhcw==",
+ (new SieveBase64Encoder("any carnal pleas")).toArray());
+
+ suite.assertEquals("YW55IGNhcm5hbCBwbGVhcw==",
+ (new SieveBase64Encoder("any carnal pleas")).toUtf8());
+});
+
+suite.add("Base64 Encoder - YW55IGNhcm5hbCBwbGVhc3U=", function () {
+ suite.assertArrayEquals("YW55IGNhcm5hbCBwbGVhc3U=",
+ (new SieveBase64Encoder("any carnal pleasu")).toArray());
+
+ suite.assertEquals("YW55IGNhcm5hbCBwbGVhc3U=",
+ (new SieveBase64Encoder("any carnal pleasu")).toUtf8());
+});
+
+suite.add("Base64 Encoder - YW55IGNhcm5hbCBwbGVhc3Vy", function () {
+ suite.assertArrayEquals("YW55IGNhcm5hbCBwbGVhc3Vy",
+ (new SieveBase64Encoder("any carnal pleasur")).toArray());
+
+ suite.assertEquals("YW55IGNhcm5hbCBwbGVhc3Vy",
+ (new SieveBase64Encoder("any carnal pleasur")).toUtf8());
+});
+
+suite.add("Base64 Encoder - YW55IGNhcm5hbCBwbGVhc3VyZQ==", function () {
+ suite.assertArrayEquals("YW55IGNhcm5hbCBwbGVhc3VyZQ==",
+ (new SieveBase64Encoder("any carnal pleasure")).toArray());
+
+ suite.assertEquals("YW55IGNhcm5hbCBwbGVhc3VyZQ==",
+ (new SieveBase64Encoder("any carnal pleasure")).toUtf8());
+});
+
+suite.add("Base64 Encoder - dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ==", function () {
+ suite.assertArrayEquals("dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ==",
+ (new SieveBase64Encoder("v=peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=")).toArray());
+
+ suite.assertEquals("dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ==",
+ (new SieveBase64Encoder("v=peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=")).toUtf8());
+});
+
+suite.add("Base64 Encoder - peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=", function () {
+
+ const actual = new Uint8Array([0xA5, 0xE4, 0x7C, 0x11, 0xBE, 0xAE, 0x35, 0xFE, 0x19,
+ 0x77, 0xB1, 0xF3, 0x27, 0x4B, 0xFB, 0x38, 0x18, 0x3F, 0x0B, 0x97, 0x31,
+ 0xA4, 0xA5, 0x6E, 0x31, 0x1C, 0x77, 0x95, 0xC9, 0xDA, 0x3A, 0x11]);
+
+ suite.assertArrayEquals("peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=",
+ (new SieveBase64Encoder(actual)).toArray());
+
+ suite.assertEquals("peR8Eb6uNf4Zd7HzJ0v7OBg/C5cxpKVuMRx3lcnaOhE=",
+ (new SieveBase64Encoder(actual)).toUtf8());
+});
+
diff --git a/src/common/libManageSieve/tests/SieveCryptoTest.mjs b/src/common/libManageSieve/tests/SieveCryptoTest.mjs
new file mode 100644
index 00000000..678b4c82
--- /dev/null
+++ b/src/common/libManageSieve/tests/SieveCryptoTest.mjs
@@ -0,0 +1,50 @@
+/*
+ * The contents of this file are licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ *
+ */
+
+/* eslint-disable no-magic-numbers */
+
+/* global net */
+const suite = net.tschmid.yautt.test;
+
+if (!suite)
+ throw new Error("Could not initialize test suite");
+
+import { SieveCrypto } from "./../SieveCrypto.mjs";
+
+const DEFAULT_PASSWORD = new Uint8Array([112, 101, 110, 99, 105, 108]);
+const DEFAULT_SALT = new Uint8Array([65, 37, 194, 71, 228, 58, 177, 233, 60, 109, 255, 118]);
+
+const DEFAULT_SALTED_PASSWORD = [
+ 29, 150, 238, 58, 82, 155, 90, 95, 158, 71,
+ 192, 31, 34, 154, 44, 184, 166, 225, 95, 125];
+
+suite.description(
+ "Testing Cryptographic functions...");
+
+suite.add("Crypto SHA1 Hi()", async function () {
+
+ const crypto = new SieveCrypto("SHA-1");
+
+ const password = DEFAULT_PASSWORD;
+ const iter = "4096";
+ const salt = DEFAULT_SALT;
+
+
+ const saltedPassword = await (crypto.Hi(password, salt, iter));
+
+ suite.assertEquals(20, saltedPassword.length);
+
+ suite.assertEquals(saltedPassword.toString(), DEFAULT_SALTED_PASSWORD.toString());
+});
+
+suite.add("Normalize(str)", function () {
+});
diff --git a/src/common/libManageSieve/tests/SieveSaslRequestTest.mjs b/src/common/libManageSieve/tests/SieveSaslRequestTest.mjs
new file mode 100644
index 00000000..5e4a5990
--- /dev/null
+++ b/src/common/libManageSieve/tests/SieveSaslRequestTest.mjs
@@ -0,0 +1,456 @@
+/*
+ * The contents of this file are licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ *
+ */
+
+/* global net */
+const suite = net.tschmid.yautt.test;
+
+if (!suite)
+ throw new Error("Could not initialize test suite");
+
+import {
+ SieveSaslPlainRequest,
+ SieveSaslScramSha1Request,
+ SieveSaslScramSha256Request,
+ SieveSaslExternalRequest,
+ SieveSaslLoginRequest
+} from "./../SieveRequest.mjs";
+
+import { SieveRequestBuilder } from "./../SieveRequestBuilder.mjs";
+import { SieveResponseParser } from "./../SieveResponseParser.mjs";
+
+const SIMPLE_PASSWORD = "pencil";
+const COMPLEX_PASSWORD = "abc§123";
+const INSANE_PASSWORD = "f>¤¨&ú/N¸Ýì_`ÛÄ*gÅß]Ö¯Xq¼/±Æ_û.Q*¤ú½kat©z×\\\\®DèÍñ(_d©.Éê³BSv÷{fÊÚõp·ÅähBÏ)YÕý=ýtZ+í(a'8¶Y´HV(m´ûÂ$äK2]*ûöìµ.+^µÈ6ÛðÄ/ÝÉÐo¡%+49";
+
+
+suite.description(
+ "Testing Sasl Mechanisms...");
+
+/**
+ * Simulates a request.
+ *
+ * @param {SieveAbstractRequest} request
+ * the request which should be used.
+ * @param {boolean} completed
+ * if true the request is completed after this call.
+ * @param {string} expectation
+ * the expected request
+ */
+async function expectRequest(request, completed, expectation) {
+
+ suite.assertEquals(request.hasNextRequest(), !completed);
+
+ const requestBuilder = new SieveRequestBuilder();
+ await request.getNextRequest(requestBuilder);
+
+ suite.assertEquals(requestBuilder.getBytes(), expectation);
+}
+
+/**
+ * Simulates a response
+ *
+ * @param {SieveAbstractRequest} request
+ * the request which expects the response.
+ * @param {string} expectation
+ * the requests expected response.
+ */
+async function expectResponse(request, expectation) {
+ expectation = Array.from(expectation, (x) => { return x.charCodeAt(0);} );
+ await request.onResponse(new SieveResponseParser(expectation));
+}
+
+suite.add("SASL Plain", async function() {
+ const request = new SieveSaslPlainRequest();
+
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user");
+ request.setPassword(SIMPLE_PASSWORD);
+
+ await expectRequest(request, true,
+ `AUTHENTICATE "PLAIN" "AHVzZXIAcGVuY2ls"\r\n`);
+
+ await expectResponse(request,
+ `OK "Logged in."\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Plain with special Characters", async function() {
+ const request = new SieveSaslPlainRequest();
+
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user2");
+ request.setPassword(COMPLEX_PASSWORD);
+
+ await expectRequest(request, true,
+ `AUTHENTICATE "PLAIN" "AHVzZXIyAGFiY8KnMTIz"\r\n`);
+
+ await expectResponse(request,
+ `OK "Logged in."\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Plain with many Characters", async function() {
+ const request = new SieveSaslPlainRequest();
+
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user3");
+ request.setPassword(INSANE_PASSWORD);
+
+ await expectRequest(request, true,
+ `AUTHENTICATE "PLAIN" "AHVzZXIzAGY+wqTCqCbDui9OwrjDncOsX2DDm8OEKmfDhcOfXcOWwq9YccK8L8Kxw4Zfw7suUSrCpMO6wr1rYXTCqXrDl1xcwq5Ew6jDjcOxKF9kwqkuw4nDqsKzQlN2w7d7ZsOKw5rDtXDCt8OFw6RoQsOPKVnDlcO9PcO9dForw60oYSc4wrZZwrRIVihtwrTDu8OCJMOkSzJdKsO7w7bDrMK1LitewrXDiDbDm8Oww4Qvw53DicOQb8KhJSs0OQ=="\r\n`);
+
+ await expectResponse(request,
+ `OK "Logged in."\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+
+suite.add("SASL Scram SHA1 - Short", async function () {
+ const request = new SieveSaslScramSha1Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-1");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user");
+ request.setPassword(SIMPLE_PASSWORD);
+
+ request.generateNonce = async () => { return "fyko+d2lbbFgONRv9qkxdawL"; };
+
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-1" "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng=="\r\n`);
+
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMM3JmY05IWUpZMVpWdldWczdqLHA9djBYOHYzQnoyVDBDSkdiSlF5RjBYK0hJNFRzPQ=="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `OK (SASL "dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9")\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA1 - Long", async function () {
+ const request = new SieveSaslScramSha1Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-1");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user");
+ request.setPassword(SIMPLE_PASSWORD);
+
+ request.generateNonce = async () => { return "fyko+d2lbbFgONRv9qkxdawL"; };
+
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-1" "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng=="\r\n`);
+
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMM3JmY05IWUpZMVpWdldWczdqLHA9djBYOHYzQnoyVDBDSkdiSlF5RjBYK0hJNFRzPQ=="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9"\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA1 with Special Characters - Long", async function () {
+ const request = new SieveSaslScramSha1Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-1");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user2");
+ request.setPassword(COMPLEX_PASSWORD);
+
+ request.generateNonce = async () => { return "4Gn+oXMVuHyu9RVYooRFMw+x"; };
+
+ // n,,n=user2,r=6cb260fdd390fcb04ae5bd1edf7d25d207db01c9
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-1" "biwsbj11c2VyMixyPTRHbitvWE1WdUh5dTlSVllvb1JGTXcreA=="\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj00R24rb1hNVnVIeXU5UlZZb29SRk13K3hqJy0kZ0g5aXVjRGBDS1dEOCNOWT46TzF2NnI2I25VKDVXSyFEY3ROaj4/Zn40Y0k0QSMiVUB3TjMoMjtdZls/LHM9UUR2V2hqUTJEYjVVUFJ0RFZaYWROZz09LGk9NDA5Ng=="\r\n`);
+
+ // c=biws,r=6cb260fdd390fcb04ae5bd1edf7d25d207db01c9'Uv)RBWr>8Y~8NktH`Y9A$Zh!*'a.Q'%;8=!CS'L[W&K`H3LdOc6uV5+nHG.>(x^,p=4w7qjWbJv2IRU+XcjysMxhJYJUI=
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9NEduK29YTVZ1SHl1OVJWWW9vUkZNdyt4aictJGdIOWl1Y0RgQ0tXRDgjTlk+Ok8xdjZyNiNuVSg1V0shRGN0Tmo+P2Z+NGNJNEEjIlVAd04zKDI7XWZbPyxwPXF4T09ndzRDOFJ5Q25LODQ5Y2hIMU95bEpvaz0="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj1xem1XN1NSL0FObGVYbnBRSXorckhQOGt4ODA9"\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA1 with many special characters - Long", async function () {
+ const request = new SieveSaslScramSha1Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-1");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user3");
+ request.setPassword(INSANE_PASSWORD);
+
+ request.generateNonce = async () => { return "c96a5d9a095401657971d4ad44c51e0147bf52e3"; };
+
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-1" "biwsbj11c2VyMyxyPWM5NmE1ZDlhMDk1NDAxNjU3OTcxZDRhZDQ0YzUxZTAxNDdiZjUyZTM="\r\n`);
+
+ await expectResponse(request,
+ `"cj1jOTZhNWQ5YTA5NTQwMTY1Nzk3MWQ0YWQ0NGM1MWUwMTQ3YmY1MmUzIyQyezZWazlhKmskTiNQUGsrNFd3djZyYmcjITZobCc4VVxwY0Q/bVRFS2BiSV5+RCJsRXNjXmNmY2Eoai40bSxzPTZlRFlQTUhCZmZKMk9mVVdTTUIxTmc9PSxpPTQwOTY="\r\n`);
+
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9Yzk2YTVkOWEwOTU0MDE2NTc5NzFkNGFkNDRjNTFlMDE0N2JmNTJlMyMkMns2Vms5YSprJE4jUFBrKzRXd3Y2cmJnIyE2aGwnOFVccGNEP21URUtgYklefkQibEVzY15jZmNhKGouNG0scD1MeFdVZEJBckVPUXZtRHdXbG5oeXBpMDFJN0U9"\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj04ZzdKMUZKY3NsMTF1U0o4NG5ma3p1WXBBSXM9"\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA256 - Short", async function () {
+ const request = new SieveSaslScramSha256Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-256");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user");
+ request.setPassword(SIMPLE_PASSWORD);
+
+ request.generateNonce = async () => { return "rOprNGfwEbeRWgbNEkqO"; };
+
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-256" "biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8="\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY="\r\n`);
+
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ=="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `OK (SASL "dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ==")\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+
+suite.add("SASL Scram SHA256 - Long", async function () {
+ const request = new SieveSaslScramSha256Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-256");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user");
+ request.setPassword(SIMPLE_PASSWORD);
+
+ request.generateNonce = async () => { return "rOprNGfwEbeRWgbNEkqO"; };
+
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-256" "biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8="\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY="\r\n`);
+
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ=="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ=="\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA256 with Special Characters - Long", async function () {
+ const request = new SieveSaslScramSha256Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-256");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user2");
+ request.setPassword(COMPLEX_PASSWORD);
+
+ request.generateNonce = async () => { return "uGzHUcuMpP47rPcnmk3+WiMU"; };
+
+ // C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-256" "biwsbj11c2VyMixyPXVHekhVY3VNcFA0N3JQY25tazMrV2lNVQ=="\r\n`);
+
+ // S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ await expectResponse(request,
+ `"cj11R3pIVWN1TXBQNDdyUGNubWszK1dpTVVMVWUzXz9LN1BFKzk0ZUo4cjNPZjplfis7RjIla2k/fUFfPC5zTypuUHxbR2hCUWRcWFgyQmsyLVBdejkkZiNdLHM9RVZodlRpZ2swWTRnNWljTzJQMDZDdz09LGk9NDA5Ng=="\r\n`);
+
+ // C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9dUd6SFVjdU1wUDQ3clBjbm1rMytXaU1VTFVlM18/SzdQRSs5NGVKOHIzT2Y6ZX4rO0YyJWtpP31BXzwuc08qblB8W0doQlFkXFhYMkJrMi1QXXo5JGYjXSxwPTU4SUE3TWt3OXhBUUxVTGdvc3dVaTNEM1M3QkdMRkpwTVd3N3p0a3gwVjA9"\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj1wZVI4RWI2dU5mNFpkN0h6SjB2N09CZy9DNWN4cEtWdU1SeDNsY25hT2hFPQ="\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL Scram SHA256 with many special characters - Long", async function () {
+ const request = new SieveSaslScramSha256Request();
+
+ suite.assertEquals(request.getSaslName(), "SCRAM-SHA-256");
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("user3");
+ request.setPassword(INSANE_PASSWORD);
+
+ request.generateNonce = async () => { return "fc88adb75eb0151cfbc116b6cca317343b08b68a1411a889c77d60df9180cf95"; };
+
+ await expectRequest(request, false,
+ `AUTHENTICATE "SCRAM-SHA-256" "biwsbj11c2VyMyxyPWZjODhhZGI3NWViMDE1MWNmYmMxMTZiNmNjYTMxNzM0M2IwOGI2OGExNDExYTg4OWM3N2Q2MGRmOTE4MGNmOTU="\r\n`);
+
+ await expectResponse(request,
+ `"cj1mYzg4YWRiNzVlYjAxNTFjZmJjMTE2YjZjY2EzMTczNDNiMDhiNjhhMTQxMWE4ODljNzdkNjBkZjkxODBjZjk1OEwhdEg3XG1xeSJCXEc+UEU7Py0pTjhTZkdRd1BqbXwrLUBeXl4rcExPcy1meEoyQFRcIUtOVFZRcH54cysqUixzPVBSNU5iSWtPWDMzUHVHMHhhck1NMVE9PSxpPTQwOTY="\r\n`);
+
+ await expectRequest(request, false,
+ `"Yz1iaXdzLHI9ZmM4OGFkYjc1ZWIwMTUxY2ZiYzExNmI2Y2NhMzE3MzQzYjA4YjY4YTE0MTFhODg5Yzc3ZDYwZGY5MTgwY2Y5NThMIXRIN1xtcXkiQlxHPlBFOz8tKU44U2ZHUXdQam18Ky1AXl5eK3BMT3MtZnhKMkBUXCFLTlRWUXB+eHMrKlIscD1ERUw2RGh0bTV5VDg3QWNZRDRuNHNNbjhwZmlnamF2dGpFRURMZ0ROUHc4PQ=="\r\n`);
+
+ // S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ await expectResponse(request,
+ `"dj1zcndEdFRzWmxQQlVuSFk0Vko4UzVNZ0gxaEx6UU9rS1lVREFoNHdqK0pZPQ=="\r\n`);
+
+ await expectRequest(request, false,
+ `""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+});
+
+suite.add("SASL External", async function() {
+ const request = new SieveSaslExternalRequest();
+
+ suite.assertTrue(request.isAuthorizable());
+ suite.assertFalse(request.hasPassword());
+
+ await expectRequest(request, true,
+ `AUTHENTICATE "EXTERNAL" ""\r\n`);
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+
+});
+
+suite.add("SASL Login", async function() {
+ const request = new SieveSaslLoginRequest();
+
+ suite.assertFalse(request.isAuthorizable());
+ suite.assertTrue(request.hasPassword());
+
+ request.setUsername("blubb");
+ request.setPassword("bla");
+
+ await expectRequest(request, false,
+ 'AUTHENTICATE "LOGIN"\r\n');
+
+ // Server responds with a "VXNlcm5hbWU6" which is a "USERNAME:"
+ await expectResponse(request,
+ '"VXNlcm5hbWU6"\r\n');
+
+ // Client sends the username, Ymx1YmI= equals blubb
+ await expectRequest(request, false,
+ '"Ymx1YmI="\r\n');
+
+ // Server responds with a "UGFzc3dvcmQ6" which is a "PASSWORD:"
+ await expectResponse(request,
+ '"UGFzc3dvcmQ6"\r\n');
+
+ // Client sends the password, "Ymxh" equals bla
+ await expectRequest(request, false,
+ '"Ymxh"\r\n');
+
+ await expectResponse(request,
+ `OK\r\n`);
+
+ suite.assertFalse(request.hasNextRequest());
+
+});
diff --git a/src/common/libSieve/SieveGui.html b/src/common/libSieve/SieveGui.html
index 9ff0f4da..ab469d69 100755
--- a/src/common/libSieve/SieveGui.html
+++ b/src/common/libSieve/SieveGui.html
@@ -119,7 +119,7 @@
<body>
<div class="container-xl mt-2">
<!-- the toolvar -->
- <div id="toolbar" class="ml-2"></div>
+ <div id="toolbar" class="ms-2"></div>
<div id="content">
@@ -142,7 +142,7 @@
<ul id="sivDialogTabs" class="nav nav-tabs card-header-tabs my-0 pt-3" role="tablist">
</ul>
<div class="align-self-center">
- <button type="button" class=".btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class=".btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
@@ -150,7 +150,7 @@
</div>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="sivDialogSave" type="button" class="btn btn-primary">Apply</button>
</div>
</div>
@@ -159,7 +159,7 @@
<div id="debug2"></div>
- <div id="divOutput" class="card p-2 pr-4">
+ <div id="divOutput" class="card p-2 pe-4">
</div>
<script type="module" src="SieveGui.mjs"></script>
diff --git a/src/common/libSieve/SieveGui.mjs b/src/common/libSieve/SieveGui.mjs
index ff5a7f92..a877800e 100644
--- a/src/common/libSieve/SieveGui.mjs
+++ b/src/common/libSieve/SieveGui.mjs
@@ -81,37 +81,37 @@ function init() {
// populate the action section
const actions = document.querySelector("#sivActions");
while (actions.firstChild)
- actions.removeChild(actions.firstChild);
+ actions.firstChild.remove();
for (key in SieveLexer.types["action"])
if (SieveLexer.types["action"][key].onCapable(SieveLexer.capabilities()))
- actions.appendChild(createMenuItem(key, "sieve/action", docShell));
+ actions.append(createMenuItem(key, "sieve/action", docShell));
// populate the test section
const tests = document.querySelector("#sivTests");
while (tests.firstChild)
- tests.removeChild(tests.firstChild);
+ tests.firstChild.remove();
for (key in SieveLexer.types["test"])
if (SieveLexer.types["test"][key].onCapable(SieveLexer.capabilities()))
if (key !== "test/boolean")
- tests.appendChild(createMenuItem(key, "sieve/test", docShell));
+ tests.append(createMenuItem(key, "sieve/test", docShell));
// populate the operator section
const operators = document.querySelector("#sivOperators");
while (operators.firstChild)
- operators.removeChild(operators.firstChild);
+ operators.firstChild.remove();
for (key in SieveLexer.types["operator"])
if (SieveLexer.types["operator"][key].onCapable(SieveLexer.capabilities()))
- operators.appendChild(createMenuItem(key, "sieve/operator", docShell));
+ operators.append(createMenuItem(key, "sieve/operator", docShell));
// create the trash bin
const trash = document.querySelector("#sivTrash");
while (trash.firstChild)
- trash.removeChild(trash.firstChild);
+ trash.firstChild.remove();
- trash.appendChild(
+ trash.append(
new SieveTrashBoxUI(docShell).html());
}
@@ -169,9 +169,9 @@ function setSieveScript(script, capabilities) {
const output = document.querySelector(`#divOutput`);
while (output.firstChild)
- output.removeChild(output.firstChild);
+ output.firstChild.remove();
- output.appendChild(dom2.html());
+ output.append(dom2.html());
}
/**
@@ -274,19 +274,19 @@ async function main() {
const debug = document.querySelector(`#debug2`);
while (debug.firstChild)
- debug.removeChild(debug.firstChild);
+ debug.firstChild.remove();
// ... and append the new element
- debug.appendChild(
+ debug.append(
(await (new SieveTemplate()).load("./templates/debug.html")));
// Clear any existing left overs...
const sidebar = document.querySelector(`#toolbar`);
while (sidebar.firstChild)
- sidebar.removeChild(sidebar.firstChild);
+ sidebar.firstChild.remove();
// ... and append the new element
- sidebar.appendChild(
+ sidebar.append(
(await (new SieveTemplate()).load("./templates/sidebar.html")));
init();
@@ -300,7 +300,7 @@ async function main() {
document.querySelector("#CapabilitiesReset")
.addEventListener("click", () => { loadCapabilities(); });
- document.querySelector('a[data-toggle="tab"][href="#debugcapabilities"]')
+ document.querySelector('a[data-bs-toggle="tab"][href="#debugcapabilities"]')
.addEventListener('show.bs.tab', function () { loadCapabilities(); });
diff --git a/src/common/libSieve/extensions/RFC5228/logic/SieveConditions.mjs b/src/common/libSieve/extensions/RFC5228/logic/SieveConditions.mjs
index 5d24eb91..47c85131 100644
--- a/src/common/libSieve/extensions/RFC5228/logic/SieveConditions.mjs
+++ b/src/common/libSieve/extensions/RFC5228/logic/SieveConditions.mjs
@@ -154,6 +154,8 @@ class SieveIf extends SieveBlock {
* @returns {SieveAbstractElement}
*/
removeChild(childId, cascade, stop) {
+
+ // eslint-disable-next-line unicorn/prefer-node-remove
const elm = super.removeChild(childId);
if (cascade && elm)
return this;
diff --git a/src/common/libSieve/extensions/RFC5228/logic/SieveMatchTypes.mjs b/src/common/libSieve/extensions/RFC5228/logic/SieveMatchTypes.mjs
index b6a6b20d..9e573742 100644
--- a/src/common/libSieve/extensions/RFC5228/logic/SieveMatchTypes.mjs
+++ b/src/common/libSieve/extensions/RFC5228/logic/SieveMatchTypes.mjs
@@ -12,7 +12,7 @@
import { SieveGrammar } from "./../../../toolkit/logic/GenericElements.mjs";
-// TODO match-type items (matchtype/) should not eat tailing whitespaces...
+// TODO match-type items (matchtype/) should not eat trailing whitespaces...
// they this should be done my the match-type group
SieveGrammar.addTag({
diff --git a/src/common/libSieve/extensions/RFC5228/logic/SieveNumbers.mjs b/src/common/libSieve/extensions/RFC5228/logic/SieveNumbers.mjs
index 80694993..f57c74f4 100644
--- a/src/common/libSieve/extensions/RFC5228/logic/SieveNumbers.mjs
+++ b/src/common/libSieve/extensions/RFC5228/logic/SieveNumbers.mjs
@@ -84,9 +84,9 @@ class SieveNumber extends SieveAbstractElement {
if (typeof (number) === "undefined" || number === null)
throw new Error("Invalid Number");
- number = parseInt(number, 10);
+ number = Number.parseInt(number, 10);
- if (isNaN(number))
+ if (Number.isNaN(number))
throw new Error("Not a number: " + number);
this._number = number;
diff --git a/src/common/libSieve/extensions/RFC5228/logic/SieveOperators.mjs b/src/common/libSieve/extensions/RFC5228/logic/SieveOperators.mjs
index 130ffec7..55aae6f0 100644
--- a/src/common/libSieve/extensions/RFC5228/logic/SieveOperators.mjs
+++ b/src/common/libSieve/extensions/RFC5228/logic/SieveOperators.mjs
@@ -202,8 +202,10 @@ SieveAnyOfAllOfTest.prototype.test
// Release old test...
this.append(item, old);
- if (typeof (old) !== "undefined")
+ if (typeof (old) !== "undefined") {
+ // eslint-disable-next-line unicorn/prefer-node-remove
this.removeChild(old.id());
+ }
/* if (this._test)
this._test.parent(null);
diff --git a/src/common/libSieve/extensions/RFC5228/logic/SieveStrings.mjs b/src/common/libSieve/extensions/RFC5228/logic/SieveStrings.mjs
index e94560a1..64188670 100644
--- a/src/common/libSieve/extensions/RFC5228/logic/SieveStrings.mjs
+++ b/src/common/libSieve/extensions/RFC5228/logic/SieveStrings.mjs
@@ -86,7 +86,7 @@ class SieveMultiLineString extends SieveAbstractElement {
this.text = parser.extractUntil("\r\n.\r\n");
// dump the first linebreak and remove dot stuffing
- this.text = this.text.substr("\r\n".length).replace(/^\.\./mg, ".");
+ this.text = this.text.substr("\r\n".length).replace(/^\.\./gm, ".");
return this;
}
@@ -117,7 +117,7 @@ class SieveMultiLineString extends SieveAbstractElement {
text += "\r\n";
// Dot stuffing...
- text = text.replace(/^\./mg, "..");
+ text = text.replace(/^\./gm, "..");
return "text:"
+ this.whiteSpace.toScript()
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveAddressTestUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveAddressTestUI.html
index 06a2d2ba..976a3110 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveAddressTestUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveAddressTestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="address.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-addresstest" role="tab"></a>
+ <a data-i18n="address.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-addresstest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="address.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="address.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="address.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="address.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,7 +22,7 @@
</div>
<div id="sivAddressHeaderListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">To</button>
<button class="dropdown-item" type="button">From</button>
<button class="dropdown-item" type="button">Cc</button>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveAllOfAnyOfOperator.html b/src/common/libSieve/extensions/RFC5228/templates/SieveAllOfAnyOfOperator.html
index 1edf6bb0..cc5aa0c2 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveAllOfAnyOfOperator.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveAllOfAnyOfOperator.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="allofanyof.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-allofanyof" role="tab"></a>
+ <a data-i18n="allofanyof.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-allofanyof" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="allofanyof.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="allofanyof.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveBooleanTest.html b/src/common/libSieve/extensions/RFC5228/templates/SieveBooleanTest.html
index 9c8af7ea..2c64534b 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveBooleanTest.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveBooleanTest.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="boolean.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-test"
+ <a data-i18n="boolean.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-test"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="boolean.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="boolean.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveDiscardActionUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveDiscardActionUI.html
index fb889c92..c52f580f 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveDiscardActionUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveDiscardActionUI.html
@@ -1,7 +1,7 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-toggle="tab" href="#sieve-widget-help" data-i18n="discard.tab.help" role="tab"></a>
+ <a class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-help" data-i18n="discard.tab.help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveEnvelopeTestUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveEnvelopeTestUI.html
index 6f7d34e8..2ae9b1ac 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveEnvelopeTestUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveEnvelopeTestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="envelope.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-envelope-test" role="tab"></a>
+ <a data-i18n="envelope.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-envelope-test" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="envelope.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="envelope.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="envelope.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="envelope.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,7 +22,7 @@
</div>
<div id="sivEnvelopeListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">From</button>
<button class="dropdown-item" type="button">To</button>
</div>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveExistsTestUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveExistsTestUI.html
index 34868778..2dd5d2a0 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveExistsTestUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveExistsTestUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-i18n="exists.tab.home" data-toggle="tab" href="#sieve-widget-exists-test"
+ <a class="nav-link active" data-i18n="exists.tab.home" data-bs-toggle="tab" href="#sieve-widget-exists-test"
role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-i18n="exists.tab.help" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a class="nav-link" data-i18n="exists.tab.help" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -18,7 +18,7 @@
</div>
<div id="sivExistsHeaderListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">To</button>
<button class="dropdown-item" type="button">From</button>
<button class="dropdown-item" type="button">Cc</button>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveFileIntoActionUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveFileIntoActionUI.html
index 1c0f7b7b..111297c9 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveFileIntoActionUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveFileIntoActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-toggle="tab" href="#sieve-widget-fileinto" data-i18n="fileinto.tab.home" role="tab"></a>
+ <a class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-fileinto" data-i18n="fileinto.tab.home" role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-toggle="tab" href="#sieve-widget-help" data-i18n="fileinto.tab.help" role="tab"></a>
+ <a class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" data-i18n="fileinto.tab.help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveHeaderTestUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveHeaderTestUI.html
index 391f7f90..16460ad3 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveHeaderTestUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveHeaderTestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-i18n="header.tab.home" data-toggle="tab" href="#sieve-widget-headertest" role="tab"></a>
+ <a class="nav-link active" data-i18n="header.tab.home" data-bs-toggle="tab" href="#sieve-widget-headertest" role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-i18n="header.tab.advanced" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a class="nav-link" data-i18n="header.tab.advanced" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-i18n="header.tab.help" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a class="nav-link" data-i18n="header.tab.help" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,7 +22,7 @@
</div>
<div id="sivHeaderHeaderListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">Subject</button>
<button class="dropdown-item" type="button">Date</button>
<button class="dropdown-item" type="button">Message-ID</button>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveKeepActionUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveKeepActionUI.html
index 154f68d4..d3e3972c 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveKeepActionUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveKeepActionUI.html
@@ -1,7 +1,7 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-toggle="tab" href="#sieve-widget-help" data-i18n="keep.tab.help" role="tab"></a>
+ <a class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-help" data-i18n="keep.tab.help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveRedirectActionUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveRedirectActionUI.html
index 2d36cfb9..350899fd 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveRedirectActionUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveRedirectActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-i18n="redirect.tab.home" data-toggle="tab" href="#sieve-widget-redirect" role="tab"></a>
+ <a class="nav-link active" data-i18n="redirect.tab.home" data-bs-toggle="tab" href="#sieve-widget-redirect" role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-i18n="redirect.tab.help" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a class="nav-link" data-i18n="redirect.tab.help" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveSizeTestUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveSizeTestUI.html
index e96b9b83..de265c84 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveSizeTestUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveSizeTestUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-i18n="size.tab.home" data-toggle="tab" href="#sieve-widget-size" role="tab"></a>
+ <a class="nav-link active" data-i18n="size.tab.home" data-bs-toggle="tab" href="#sieve-widget-size" role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link" data-i18n="size.tab.help" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a class="nav-link" data-i18n="size.tab.help" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/templates/SieveStopActionUI.html b/src/common/libSieve/extensions/RFC5228/templates/SieveStopActionUI.html
index b15bd97e..db406803 100644
--- a/src/common/libSieve/extensions/RFC5228/templates/SieveStopActionUI.html
+++ b/src/common/libSieve/extensions/RFC5228/templates/SieveStopActionUI.html
@@ -1,7 +1,7 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a class="nav-link active" data-toggle="tab" href="#sieve-widget-help" data-i18n="stop.tab.help" role="tab"></a>
+ <a class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-help" data-i18n="stop.tab.help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/RFC5228/widgets/SieveBlocksUI.mjs b/src/common/libSieve/extensions/RFC5228/widgets/SieveBlocksUI.mjs
index 0237e718..75fa428d 100644
--- a/src/common/libSieve/extensions/RFC5228/widgets/SieveBlocksUI.mjs
+++ b/src/common/libSieve/extensions/RFC5228/widgets/SieveBlocksUI.mjs
@@ -31,7 +31,7 @@ class SieveRootNodeUI extends SieveAbstractBoxUI {
* @inheritdoc
*/
createHtml(parent) {
- parent.appendChild(
+ parent.append(
this.getSieve().elms[FIRST_ELEMENT].html());
return parent;
@@ -57,7 +57,7 @@ class SieveBlockUI extends SieveAbstractBoxUI {
*/
createBlockChild(item) {
const child = document.createElement('div');
- child.appendChild(item);
+ child.append(item);
child.classList.add("sivBlockChild");
return child;
@@ -76,17 +76,17 @@ class SieveBlockUI extends SieveAbstractBoxUI {
if (!item)
continue;
- elm.appendChild((new SieveDropBoxUI(this, "sivBlockSpacer"))
+ elm.append((new SieveDropBoxUI(this, "sivBlockSpacer"))
.drop(new SieveBlockDropHandler(), sivElm)
.html());
- elm.appendChild(this.createBlockChild(item));
+ elm.append(this.createBlockChild(item));
}
- elm.appendChild((new SieveDropBoxUI(this, "sivBlockSpacer"))
+ elm.append((new SieveDropBoxUI(this, "sivBlockSpacer"))
.drop(new SieveBlockDropHandler())
.html());
- parent.appendChild(elm);
+ parent.append(elm);
return parent;
}
}
diff --git a/src/common/libSieve/extensions/RFC5228/widgets/SieveConditionsUI.mjs b/src/common/libSieve/extensions/RFC5228/widgets/SieveConditionsUI.mjs
index f5766bc6..b93ba2f1 100644
--- a/src/common/libSieve/extensions/RFC5228/widgets/SieveConditionsUI.mjs
+++ b/src/common/libSieve/extensions/RFC5228/widgets/SieveConditionsUI.mjs
@@ -34,14 +34,14 @@ class SieveIfUI extends SieveBlockUI {
createHtml(parent) {
const test = document.createElement("div");
- test.appendChild(this.getSieve().test().html());
+ test.append(this.getSieve().test().html());
test.classList.add("sivConditionalChild");
const elm = document.createElement("div");
elm.id = `sivElm${this.id()}`;
elm.classList.add("sivConditional");
- elm.appendChild(test);
- elm.appendChild(super.createHtml(parent));
+ elm.append(test);
+ elm.append(super.createHtml(parent));
return elm;
}
@@ -60,7 +60,7 @@ class SieveElseUI extends SieveBlockUI {
const elm = document.createElement("div");
elm.id = `sivElm${this.id()}`;
elm.classList.add("sivConditional");
- elm.appendChild(super.createHtml(parent));
+ elm.append(super.createHtml(parent));
return elm;
}
@@ -129,7 +129,7 @@ class SieveConditionUI extends SieveSourceBoxUI {
const children = this.getSieve().children();
for (let i = 0; i < children.length; i++) {
- elm2.appendChild((new SieveDropBoxUI(this, "sivConditionSpacer"))
+ elm2.append((new SieveDropBoxUI(this, "sivConditionSpacer"))
.drop(new SieveConditionDropHandler(), children[i])
.html());
@@ -145,14 +145,14 @@ class SieveConditionUI extends SieveSourceBoxUI {
condition.querySelector(".sivIconCode").addEventListener("click", (e) => {
return this.onToggleView(e);
});
- elm2.appendChild(condition);
+ elm2.append(condition);
const child = item.querySelector(".sivConditionChild").cloneNode(true);
- child.appendChild(children[i].html());
- elm2.appendChild(child);
+ child.append(children[i].html());
+ elm2.append(child);
}
- elm2.appendChild((new SieveDropBoxUI(this, "sivConditionSpacer"))
+ elm2.append((new SieveDropBoxUI(this, "sivConditionSpacer"))
.drop(new SieveConditionDropHandler())
.html());
@@ -160,7 +160,7 @@ class SieveConditionUI extends SieveSourceBoxUI {
content.id = `${this.uniqueId}-summary`;
while (elm2.children.length)
- content.appendChild(elm2.firstChild);
+ content.append(elm2.firstChild);
const code = item.querySelector(".sivConditionCode");
code.id = `${this.uniqueId}-code`;
@@ -171,8 +171,8 @@ class SieveConditionUI extends SieveSourceBoxUI {
return this.onToggleView(e);
});
- parent.appendChild(content);
- parent.appendChild(code);
+ parent.append(content);
+ parent.append(code);
return parent;
}
diff --git a/src/common/libSieve/extensions/RFC5228/widgets/SieveOperatorsUI.mjs b/src/common/libSieve/extensions/RFC5228/widgets/SieveOperatorsUI.mjs
index 5d66512c..d58bec8f 100644
--- a/src/common/libSieve/extensions/RFC5228/widgets/SieveOperatorsUI.mjs
+++ b/src/common/libSieve/extensions/RFC5228/widgets/SieveOperatorsUI.mjs
@@ -59,7 +59,7 @@ class SieveNotUI extends SieveSimpleBoxUI {
const elm = (new SieveTemplate()).convert(FRAGMENT);
elm
.querySelector(".sivNotTest")
- .appendChild(this.getSieve().test().html());
+ .append(this.getSieve().test().html());
return elm;
}
@@ -158,22 +158,22 @@ class SieveAnyOfAllOfUI extends SieveDialogBoxUI {
.drop(new SieveMultaryDropHandler(), test[TEST_ELEMENT])
.html();
- testElms.appendChild(dropbox);
+ testElms.append(dropbox);
const ul = document.createElement("ul");
ul.classList.add("mb-0");
- ul.classList.add("pl-3");
+ ul.classList.add("ps-3");
const li = document.createElement("li");
- li.appendChild(test[TEST_ELEMENT].html());
+ li.append(test[TEST_ELEMENT].html());
li.classList.add("sivOperatorChild");
- ul.appendChild(li);
+ ul.append(li);
- testElms.appendChild(ul);
+ testElms.append(ul);
}
- testElms.appendChild(
+ testElms.append(
(new SieveDropBoxUI(this, "sivOperatorSpacer"))
.drop(new SieveMultaryDropHandler())
.html());
diff --git a/src/common/libSieve/extensions/body/templates/SieveBodyTestUI.html b/src/common/libSieve/extensions/body/templates/SieveBodyTestUI.html
index 304b4221..37dcbf94 100644
--- a/src/common/libSieve/extensions/body/templates/SieveBodyTestUI.html
+++ b/src/common/libSieve/extensions/body/templates/SieveBodyTestUI.html
@@ -1,17 +1,17 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="body.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-bodytest"
+ <a data-i18n="body.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-bodytest"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="body.tab.transform" class="nav-link" data-toggle="tab" href="#sieve-widget-transform" role="tab"></a>
+ <a data-i18n="body.tab.transform" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-transform" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="body.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="body.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="body.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="body.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/convert/templates/SieveConvertUI.html b/src/common/libSieve/extensions/convert/templates/SieveConvertUI.html
index e8ffeedc..ea1d9b9b 100644
--- a/src/common/libSieve/extensions/convert/templates/SieveConvertUI.html
+++ b/src/common/libSieve/extensions/convert/templates/SieveConvertUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="convert.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-convert"
+ <a data-i18n="convert.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-convert"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="convert.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="convert.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -21,7 +21,7 @@
<div id="sivConvertTo" data-list-dropdown="#sivConvertTemplate"></div>
<div id="sivConvertTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button" data-value="image/tiff">
<div>image/tiff</div>
</button>
diff --git a/src/common/libSieve/extensions/date/templates/SieveCurrentDateTestUI.html b/src/common/libSieve/extensions/date/templates/SieveCurrentDateTestUI.html
index 11076a58..38de2c30 100644
--- a/src/common/libSieve/extensions/date/templates/SieveCurrentDateTestUI.html
+++ b/src/common/libSieve/extensions/date/templates/SieveCurrentDateTestUI.html
@@ -1,16 +1,16 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="currentdate.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-date" role="tab"></a>
+ <a data-i18n="currentdate.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-date" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="currentdate.tab.timezone" class="nav-link" data-toggle="tab" href="#sieve-widget-timezone" role="tab"></a>
+ <a data-i18n="currentdate.tab.timezone" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-timezone" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="currentdate.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="currentdate.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="currentdate.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="currentdate.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,7 +22,7 @@
<div id="sivDateDatepart" data-list-dropdown="#sivDateDatepartTemplate"></div>
<div id="sivDateDatepartTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-end" style="min-width:100%">
<button class="dropdown-item" type="button" data-value="year">
<span data-i18n="datepart.year"></span>
<small class="text-muted">- <span data-i18n="datepart.year.text"></span></small>
diff --git a/src/common/libSieve/extensions/date/templates/SieveDateTestUI.html b/src/common/libSieve/extensions/date/templates/SieveDateTestUI.html
index 20fe33f2..2c3183d0 100644
--- a/src/common/libSieve/extensions/date/templates/SieveDateTestUI.html
+++ b/src/common/libSieve/extensions/date/templates/SieveDateTestUI.html
@@ -1,16 +1,16 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="date.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-date" role="tab"></a>
+ <a data-i18n="date.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-date" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="date.tab.timezone" class="nav-link" data-toggle="tab" href="#sieve-widget-timezone" role="tab"></a>
+ <a data-i18n="date.tab.timezone" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-timezone" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="date.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="date.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="date.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="date.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -26,7 +26,7 @@
<div id="sivDateDatepart" data-list-dropdown="#sivDateDatepartTemplate"></div>
<div id="sivDateDatepartTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-end" style="min-width:100%">
<button class="dropdown-item" type="button" data-value="year">
<span data-i18n="datepart.year"></span>
<small class="text-muted">- <span data-i18n="datepart.year.text"></span></small>
diff --git a/src/common/libSieve/extensions/duplicate/templates/SieveDuplicateTestUI.html b/src/common/libSieve/extensions/duplicate/templates/SieveDuplicateTestUI.html
index ae182fba..e0c86e28 100644
--- a/src/common/libSieve/extensions/duplicate/templates/SieveDuplicateTestUI.html
+++ b/src/common/libSieve/extensions/duplicate/templates/SieveDuplicateTestUI.html
@@ -1,19 +1,19 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="duplicate.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-duplicatetest"
+ <a data-i18n="duplicate.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-duplicatetest"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="duplicate.tab.tracking" class="nav-link" data-toggle="tab" href="#sieve-widget-tracking"
+ <a data-i18n="duplicate.tab.tracking" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-tracking"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="duplicate.tab.expiration" class="nav-link" data-toggle="tab" href="#sieve-widget-expiration"
+ <a data-i18n="duplicate.tab.expiration" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-expiration"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="duplicate.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="duplicate.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/editheader/templates/SieveAddHeaderActionUI.html b/src/common/libSieve/extensions/editheader/templates/SieveAddHeaderActionUI.html
index a6381663..fafdd1d1 100644
--- a/src/common/libSieve/extensions/editheader/templates/SieveAddHeaderActionUI.html
+++ b/src/common/libSieve/extensions/editheader/templates/SieveAddHeaderActionUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="addheader.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-addheader"
+ <a data-i18n="addheader.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-addheader"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="addheader.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="addheader.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -27,12 +27,12 @@
</form>
<h5 data-i18n="addheader.position"></h5>
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="last" value="false" id="sieve-header-first" />
<label data-i18n="addheader.first" class="form-check-label" for="sieve-header-first"></label>
</div>
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="last" value="true" id="sieve-header-last" />
<label data-i18n="addheader.last" class="form-check-label" for="sieve-header-last"></label>
</div>
diff --git a/src/common/libSieve/extensions/editheader/templates/SieveDeleteHeaderActionUI.html b/src/common/libSieve/extensions/editheader/templates/SieveDeleteHeaderActionUI.html
index 25527273..0e1f638e 100644
--- a/src/common/libSieve/extensions/editheader/templates/SieveDeleteHeaderActionUI.html
+++ b/src/common/libSieve/extensions/editheader/templates/SieveDeleteHeaderActionUI.html
@@ -1,15 +1,15 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="deleteheader.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-deleteheader"
+ <a data-i18n="deleteheader.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-deleteheader"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="deleteheader.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced"
+ <a data-i18n="deleteheader.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="deleteheader.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="deleteheader.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -19,14 +19,14 @@
<div class="mb-3">
<h5 data-i18n="deleteheader.position"></h5>
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="header-index" value="all" id="sieve-header-all" />
<label data-i18n="deleteheader.all" class="form-check-label" for="sieve-header-all"></label>
</div>
<div class="row align-items-center mt-2">
<div class="col-auto">
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="header-index" value="first"
id="sieve-header-first" />
<label data-i18n="deleteheader.first" class="form-check-label" for="sieve-header-first"></label>
@@ -40,7 +40,7 @@
<div class="row align-items-center mt-2">
<div class="col-auto">
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="header-index" value="last"
id="sieve-header-last" />
<label data-i18n="deleteheader.last" class="form-check-label" for="sieve-header-last"></label>
@@ -61,13 +61,13 @@
</form>
<h5 data-i18n="deleteheader.value"></h5>
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="header-value" value="any"
id="sieve-header-value-any" />
<label data-i18n="deleteheader.any" class="form-check-label" for="sieve-header-value-any"></label>
</div>
- <div class="form-check mr-sm-2">
+ <div class="form-check me-sm-2">
<input type="radio" class="form-check-input" name="header-value" value="some"
id="sieve-header-value-some" />
<label data-i18n="deleteheader.some" class="form-check-label" for="sieve-header-value-some"></label>
diff --git a/src/common/libSieve/extensions/environment/templates/SieveEnvironmentUI.html b/src/common/libSieve/extensions/environment/templates/SieveEnvironmentUI.html
index 52594f70..690b4127 100644
--- a/src/common/libSieve/extensions/environment/templates/SieveEnvironmentUI.html
+++ b/src/common/libSieve/extensions/environment/templates/SieveEnvironmentUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="environment.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-environment" role="tab"></a>
+ <a data-i18n="environment.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-environment" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="environment.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="environment.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="environment.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="environment.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,7 +22,7 @@
</div>
<div id="sivEnvironmentNameTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button" data-value="domain">
<div>domain</div>
<small data-i18n="environment.name.domain" class="text-muted"></small>
diff --git a/src/common/libSieve/extensions/imapflags/templates/SieveAddFlagActionUI.html b/src/common/libSieve/extensions/imapflags/templates/SieveAddFlagActionUI.html
index f6237672..b9bcbb33 100644
--- a/src/common/libSieve/extensions/imapflags/templates/SieveAddFlagActionUI.html
+++ b/src/common/libSieve/extensions/imapflags/templates/SieveAddFlagActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="addflag.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-addflag" role="tab"></a>
+ <a data-i18n="addflag.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-addflag" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="addflag.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="addflag.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -21,7 +21,7 @@
</div>
<div id="sivFlagKeywordListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">\Seen</button>
<button class="dropdown-item" type="button">\Answered</button>
<button class="dropdown-item" type="button">\Flagged</button>
diff --git a/src/common/libSieve/extensions/imapflags/templates/SieveFlagsTag.html b/src/common/libSieve/extensions/imapflags/templates/SieveFlagsTag.html
index 2ffaad84..e678e846 100644
--- a/src/common/libSieve/extensions/imapflags/templates/SieveFlagsTag.html
+++ b/src/common/libSieve/extensions/imapflags/templates/SieveFlagsTag.html
@@ -13,7 +13,7 @@
</div>
<div id="sivFlagKeyListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">\Seen</button>
<button class="dropdown-item" type="button">\Answered</button>
<button class="dropdown-item" type="button">\Flagged</button>
diff --git a/src/common/libSieve/extensions/imapflags/templates/SieveHasFlagTestUI.html b/src/common/libSieve/extensions/imapflags/templates/SieveHasFlagTestUI.html
index d6a45ddf..75dfacf5 100644
--- a/src/common/libSieve/extensions/imapflags/templates/SieveHasFlagTestUI.html
+++ b/src/common/libSieve/extensions/imapflags/templates/SieveHasFlagTestUI.html
@@ -1,15 +1,15 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="hasflag.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-hasflag"
+ <a data-i18n="hasflag.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-hasflag"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="hasflag.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced"
+ <a data-i18n="hasflag.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="hasflag.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="hasflag.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -27,7 +27,7 @@
</div>
<div id="sivHasFlagKeyListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">\Seen</button>
<button class="dropdown-item" type="button">\Answered</button>
<button class="dropdown-item" type="button">\Flagged</button>
diff --git a/src/common/libSieve/extensions/imapflags/templates/SieveRemoveFlagActionUI.html b/src/common/libSieve/extensions/imapflags/templates/SieveRemoveFlagActionUI.html
index 416af90c..08b23ea4 100644
--- a/src/common/libSieve/extensions/imapflags/templates/SieveRemoveFlagActionUI.html
+++ b/src/common/libSieve/extensions/imapflags/templates/SieveRemoveFlagActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="removeflag.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-removeflag" role="tab"></a>
+ <a data-i18n="removeflag.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-removeflag" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="removeflag.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="removeflag.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -21,7 +21,7 @@
</div>
<div id="sivFlagKeywordListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">\Seen</button>
<button class="dropdown-item" type="button">\Answered</button>
<button class="dropdown-item" type="button">\Flagged</button>
diff --git a/src/common/libSieve/extensions/imapflags/templates/SieveSetFlagActionUI.html b/src/common/libSieve/extensions/imapflags/templates/SieveSetFlagActionUI.html
index 1fcbaea8..f54ee64f 100644
--- a/src/common/libSieve/extensions/imapflags/templates/SieveSetFlagActionUI.html
+++ b/src/common/libSieve/extensions/imapflags/templates/SieveSetFlagActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="setflags.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-setflag" role="tab"></a>
+ <a data-i18n="setflags.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-setflag" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="setflags.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="setflags.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -20,7 +20,7 @@
</div>
<div id="sivFlagKeywordListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button">\Seen</button>
<button class="dropdown-item" type="button">\Answered</button>
<button class="dropdown-item" type="button">\Flagged</button>
diff --git a/src/common/libSieve/extensions/include/template/SieveGlobalActionUI.html b/src/common/libSieve/extensions/include/template/SieveGlobalActionUI.html
index 5a943a60..f2baf670 100644
--- a/src/common/libSieve/extensions/include/template/SieveGlobalActionUI.html
+++ b/src/common/libSieve/extensions/include/template/SieveGlobalActionUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="global.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-global" role="tab"></a>
+ <a data-i18n="global.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-global" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="global.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="global.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
<div id="template-content">
diff --git a/src/common/libSieve/extensions/include/template/SieveIncludeActionUI.html b/src/common/libSieve/extensions/include/template/SieveIncludeActionUI.html
index 70610d6c..0d219df4 100644
--- a/src/common/libSieve/extensions/include/template/SieveIncludeActionUI.html
+++ b/src/common/libSieve/extensions/include/template/SieveIncludeActionUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="include.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-include" role="tab"></a>
+ <a data-i18n="include.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-include" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="include.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="include.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="include.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="include.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/include/template/SieveReturnActionUI.html b/src/common/libSieve/extensions/include/template/SieveReturnActionUI.html
index 2c76bab5..3e31f4d3 100644
--- a/src/common/libSieve/extensions/include/template/SieveReturnActionUI.html
+++ b/src/common/libSieve/extensions/include/template/SieveReturnActionUI.html
@@ -1,7 +1,7 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="return.tab.help" class="nav-link active" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="return.tab.help" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
<div id="template-content">
diff --git a/src/common/libSieve/extensions/mailbox/templates/SieveMailboxExistsTest.html b/src/common/libSieve/extensions/mailbox/templates/SieveMailboxExistsTest.html
index dea74ec9..e43fcf34 100644
--- a/src/common/libSieve/extensions/mailbox/templates/SieveMailboxExistsTest.html
+++ b/src/common/libSieve/extensions/mailbox/templates/SieveMailboxExistsTest.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="mailboxexists.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-mailboxexiststest" role="tab"></a>
+ <a data-i18n="mailboxexists.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-mailboxexiststest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="mailboxexists.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="mailboxexists.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataExistsTest.html b/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataExistsTest.html
index e81cb924..267a7180 100644
--- a/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataExistsTest.html
+++ b/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataExistsTest.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="metadataexists.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-metadataexiststest" role="tab"></a>
+ <a data-i18n="metadataexists.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-metadataexiststest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="metadataexists.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab">Help</a>
+ <a data-i18n="metadataexists.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab">Help</a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataTest.html b/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataTest.html
index 90f70449..f90974c8 100644
--- a/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataTest.html
+++ b/src/common/libSieve/extensions/mailbox/templates/SieveMetaDataTest.html
@@ -1,15 +1,15 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="metadata.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-stringtest"
+ <a data-i18n="metadata.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-stringtest"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="metadata.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced"
+ <a data-i18n="metadata.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="metadata.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="metadata.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataExistsTest.html b/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataExistsTest.html
index 0f626525..cb8faa01 100644
--- a/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataExistsTest.html
+++ b/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataExistsTest.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="servermetadataexists.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-servermetadataexiststest" role="tab"></a>
+ <a data-i18n="servermetadataexists.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-servermetadataexiststest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="servermetadataexists.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="servermetadataexists.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataTest.html b/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataTest.html
index cb6af75f..a20837b5 100644
--- a/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataTest.html
+++ b/src/common/libSieve/extensions/mailbox/templates/SieveServerMetaDataTest.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="servermetadata.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-stringtest" role="tab"></a>
+ <a data-i18n="servermetadata.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-stringtest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="servermetadata.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="servermetadata.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="servermetadata.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="servermetadata.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/notify/templates/SieveNotifyActionUI.html b/src/common/libSieve/extensions/notify/templates/SieveNotifyActionUI.html
index e787f591..6ae4ceed 100644
--- a/src/common/libSieve/extensions/notify/templates/SieveNotifyActionUI.html
+++ b/src/common/libSieve/extensions/notify/templates/SieveNotifyActionUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="notify.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-notifyaction" role="tab"></a>
+ <a data-i18n="notify.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-notifyaction" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="notify.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="notify.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="notify.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="notify.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -19,7 +19,7 @@
</div>
<div id="sivNotifyMethodTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-end" style="min-width:100%">
<button class="dropdown-item" type="button" data-value="tel:+1234567890">
<div data-i18n="notify.method.tel"></div>
<div data-i18n="notify.method.tel.text" class="text-muted"></div>
diff --git a/src/common/libSieve/extensions/notify/templates/SieveNotifyMethodCapabilityTestUI.html b/src/common/libSieve/extensions/notify/templates/SieveNotifyMethodCapabilityTestUI.html
index 59112ede..bd814c9f 100644
--- a/src/common/libSieve/extensions/notify/templates/SieveNotifyMethodCapabilityTestUI.html
+++ b/src/common/libSieve/extensions/notify/templates/SieveNotifyMethodCapabilityTestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="notifymethodcapability.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-notifymethodcapability" role="tab"></a>
+ <a data-i18n="notifymethodcapability.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-notifymethodcapability" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="notifymethodcapability.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="notifymethodcapability.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="notifymethodcapability.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="notifymethodcapability.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -20,7 +20,7 @@
</div>
<div id="sivNotifyUriTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-end" style="min-width:100%">
<button class="dropdown-item" type="button" data-value="tel:+1234567890">
<div data-i18n="notifymethodcapability.method.tel"></div>
<div data-i18n="notifymethodcapability.method.tel.text" class="text-muted"></div>
@@ -52,7 +52,7 @@
</div>
<div id="sivNotifyKeyListTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-start" style="min-width:100%">
<button class="dropdown-item" type="button" data-value="yes">
<span data-i18n="notifymethodcapability.yes"></span><br />
<small class="text-muted" data-i18n="notifymethodcapability.yes.text">
diff --git a/src/common/libSieve/extensions/notify/templates/SieveValidNotifyMethodTestUI.html b/src/common/libSieve/extensions/notify/templates/SieveValidNotifyMethodTestUI.html
index 82ea91d7..cef039c0 100644
--- a/src/common/libSieve/extensions/notify/templates/SieveValidNotifyMethodTestUI.html
+++ b/src/common/libSieve/extensions/notify/templates/SieveValidNotifyMethodTestUI.html
@@ -1,10 +1,10 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="validnotifymethod.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-validnotifymethodtest" role="tab"></a>
+ <a data-i18n="validnotifymethod.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-validnotifymethodtest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="validnotifymethod.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="validnotifymethod.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/pipe/templates/SieveExecuteUI.html b/src/common/libSieve/extensions/pipe/templates/SieveExecuteUI.html
index 28e604ad..15bbba3d 100644
--- a/src/common/libSieve/extensions/pipe/templates/SieveExecuteUI.html
+++ b/src/common/libSieve/extensions/pipe/templates/SieveExecuteUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="execute.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-execute"
+ <a data-i18n="execute.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-execute"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="execute.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="execute.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/pipe/templates/SieveFilterUI.html b/src/common/libSieve/extensions/pipe/templates/SieveFilterUI.html
index 177cf760..78747701 100644
--- a/src/common/libSieve/extensions/pipe/templates/SieveFilterUI.html
+++ b/src/common/libSieve/extensions/pipe/templates/SieveFilterUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="filter.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-filter"
+ <a data-i18n="filter.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-filter"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="filter.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="filter.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/pipe/templates/SievePipeUI.html b/src/common/libSieve/extensions/pipe/templates/SievePipeUI.html
index 46975c08..b6caa877 100644
--- a/src/common/libSieve/extensions/pipe/templates/SievePipeUI.html
+++ b/src/common/libSieve/extensions/pipe/templates/SievePipeUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="pipe.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-pipe"
+ <a data-i18n="pipe.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-pipe"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="pipe.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="pipe.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/reject/templates/SieveExtendedRejectActionUI.html b/src/common/libSieve/extensions/reject/templates/SieveExtendedRejectActionUI.html
index 3c5c2d77..f905a348 100644
--- a/src/common/libSieve/extensions/reject/templates/SieveExtendedRejectActionUI.html
+++ b/src/common/libSieve/extensions/reject/templates/SieveExtendedRejectActionUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="ereject.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-reject"
+ <a data-i18n="ereject.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-reject"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="ereject.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="ereject.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/reject/templates/SieveRejectActionUI.html b/src/common/libSieve/extensions/reject/templates/SieveRejectActionUI.html
index e81b3944..513ed4c1 100644
--- a/src/common/libSieve/extensions/reject/templates/SieveRejectActionUI.html
+++ b/src/common/libSieve/extensions/reject/templates/SieveRejectActionUI.html
@@ -1,11 +1,11 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="reject.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-reject"
+ <a data-i18n="reject.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-reject"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="reject.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="reject.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestPlusValue.html b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestPlusValue.html
index 85d9b954..58b1d05e 100644
--- a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestPlusValue.html
+++ b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestPlusValue.html
@@ -9,7 +9,7 @@
</div>
<div id="sivSpamtestTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button" data-value="0">0
<small data-i18n="spamtest.absolute.nottested" class="text-muted"></small>
</button>
@@ -43,7 +43,7 @@
</div>
<div id="sivSpamtestPercentTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-start">
<button class="dropdown-item" type="button" data-value="0">0%
<small data-i18n="spamtest.percent.nospam" class="text-muted"></small>
</button>
diff --git a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestUI.html b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestUI.html
index cc31988a..4558e6fa 100644
--- a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestUI.html
+++ b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="spamtest.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-spamtest" role="tab"></a>
+ <a data-i18n="spamtest.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-spamtest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="spamtest.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="spamtest.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="spamtest.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="spamtest.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestValue.html b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestValue.html
index c171bb25..d488d4eb 100644
--- a/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestValue.html
+++ b/src/common/libSieve/extensions/spamtest/templates/SieveSpamtestValue.html
@@ -3,7 +3,7 @@
</div>
<div id="sivSpamtestTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button" data-value="0">0
<small data-i18n="spamtest.absolute.nottested" class="text-muted"></small>
</button>
diff --git a/src/common/libSieve/extensions/spamtest/templates/SieveVirustestUI.html b/src/common/libSieve/extensions/spamtest/templates/SieveVirustestUI.html
index 40e3f588..b469698d 100644
--- a/src/common/libSieve/extensions/spamtest/templates/SieveVirustestUI.html
+++ b/src/common/libSieve/extensions/spamtest/templates/SieveVirustestUI.html
@@ -1,13 +1,13 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="virustest.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-virustest" role="tab"></a>
+ <a data-i18n="virustest.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-virustest" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="virustest.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="virustest.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="virustest.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="virustest.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -24,7 +24,7 @@
</div>
<div id="sivVirustestTemplate" class="d-none">
- <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item" type="button" data-value="0">0
<small data-i18n="virustest.nottested" class="text-muted"></small>
</button>
diff --git a/src/common/libSieve/extensions/spamtest/widgets/SieveSpamtestUI.mjs b/src/common/libSieve/extensions/spamtest/widgets/SieveSpamtestUI.mjs
index 1609367f..d7feb9b4 100644
--- a/src/common/libSieve/extensions/spamtest/widgets/SieveSpamtestUI.mjs
+++ b/src/common/libSieve/extensions/spamtest/widgets/SieveSpamtestUI.mjs
@@ -131,7 +131,7 @@ class SieveSpamtestUI extends SieveTestDialogBoxUI {
const elm = await ((new SieveTemplate())
.load("./extensions/spamtest/templates/SieveSpamtestPlusValue.html"));
- document.querySelector("#sivSpamtestPlaceholder").appendChild(elm);
+ document.querySelector("#sivSpamtestPlaceholder").append(elm);
this.onLoadPercentualValue();
})();
@@ -142,7 +142,7 @@ class SieveSpamtestUI extends SieveTestDialogBoxUI {
const elm = await ((new SieveTemplate())
.load("./extensions/spamtest/templates/SieveSpamtestValue.html"));
- document.querySelector("#sivSpamtestPlaceholder").appendChild(elm);
+ document.querySelector("#sivSpamtestPlaceholder").append(elm);
this.onLoadValue();
})();
diff --git a/src/common/libSieve/extensions/vacation-seconds/template/SieveVacationIntervalSecondsUI.html b/src/common/libSieve/extensions/vacation-seconds/template/SieveVacationIntervalSecondsUI.html
index d39a7e1e..91a5bb6c 100644
--- a/src/common/libSieve/extensions/vacation-seconds/template/SieveVacationIntervalSecondsUI.html
+++ b/src/common/libSieve/extensions/vacation-seconds/template/SieveVacationIntervalSecondsUI.html
@@ -4,7 +4,7 @@
value=":seconds" />
<label class="form-label d-flex align-items-baseline flex-wrap">
<span style="white-space: pre" class="form-label" data-i18n="interval.seconds.label.pre"></span>
- <input class="form-control mr-1 ml-1" style="width:7em" type="number" min="0" id="txtVacationIntervalSeconds" />
+ <input class="form-control me-1 ms-1" style="width:7em" type="number" min="0" id="txtVacationIntervalSeconds" />
<span class="form-label" data-i18n="interval.seconds.label.post"></span>
</label>
</div>
diff --git a/src/common/libSieve/extensions/vacation/template/SieveVacationIntervalDaysUI.html b/src/common/libSieve/extensions/vacation/template/SieveVacationIntervalDaysUI.html
index a0d491e0..d9b3ad4f 100644
--- a/src/common/libSieve/extensions/vacation/template/SieveVacationIntervalDaysUI.html
+++ b/src/common/libSieve/extensions/vacation/template/SieveVacationIntervalDaysUI.html
@@ -3,7 +3,7 @@
<input class="form-check-input mt-2" type="radio" id="cbxVacationIntervalDays" name="interval" value=":days" />
<label class="form-label d-flex align-items-baseline flex-wrap">
<span style="white-space: pre" class="form-label" data-i18n="interval.days.label.pre"></span>
- <input class="form-control mr-1 ml-1" style="width:7em" type="number" id="txtVacationIntervalDays" min="0" />
+ <input class="form-control me-1 ms-1" style="width:7em" type="number" id="txtVacationIntervalDays" min="0" />
<span class="form-label" data-i18n="interval.days.label.post"></span>
</label>
</div>
diff --git a/src/common/libSieve/extensions/vacation/template/SieveVacationUI.html b/src/common/libSieve/extensions/vacation/template/SieveVacationUI.html
index 3a550c0b..b0214265 100644
--- a/src/common/libSieve/extensions/vacation/template/SieveVacationUI.html
+++ b/src/common/libSieve/extensions/vacation/template/SieveVacationUI.html
@@ -1,16 +1,16 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="vacation.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-vacation" role="tab"></a>
+ <a data-i18n="vacation.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-vacation" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="vacation.tab.envelope" class="nav-link" data-toggle="tab" href="#sieve-widget-envelope" role="tab"></a>
+ <a data-i18n="vacation.tab.envelope" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-envelope" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="vacation.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
+ <a data-i18n="vacation.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced" role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="vacation.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="vacation.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
@@ -22,21 +22,21 @@
<div id="vacationEnvelopeEdit">
<div class="d-flex">
<div data-i18n="vacation.envelope.subject"></div>
- <div class="flex-grow-1 text-truncate text-secondary pl-1">
+ <div class="flex-grow-1 text-truncate text-secondary ps-1">
<span id="vacationSubjectDesc"></span>
<span class="d-none" id="vacationSubjectDescDefault" data-i18n="vacation.envelope.subject.default"></span>
</div>
</div>
<div class="d-flex">
<div data-i18n="vacation.envelope.from"></div>
- <div class="flex-grow-1 text-truncate text-secondary pl-1">
+ <div class="flex-grow-1 text-truncate text-secondary ps-1">
<span id="vacationFromDesc"></span>
<span class="d-none" id="vacationFromDescDefault" data-i18n="vacation.envelope.from.default"></span>
</div>
</div>
<div class="d-flex">
<div data-i18n="vacation.envelope.addresses"></div>
- <div id="vacationAddressesDesc" class="flex-grow-1 text-truncate text-secondary pl-1"></div>
+ <div id="vacationAddressesDesc" class="flex-grow-1 text-truncate text-secondary ps-1"></div>
</div>
</div>
<br/>
diff --git a/src/common/libSieve/extensions/vacation/widgets/SieveVacationUI.mjs b/src/common/libSieve/extensions/vacation/widgets/SieveVacationUI.mjs
index 44c91ac2..bb46155f 100644
--- a/src/common/libSieve/extensions/vacation/widgets/SieveVacationUI.mjs
+++ b/src/common/libSieve/extensions/vacation/widgets/SieveVacationUI.mjs
@@ -179,14 +179,14 @@ class SieveVacationUI extends SieveActionDialogBoxUI {
(new SieveOverlayWidget("action/vacation/interval/", "#sivVacationIntervalOverlay"))
.init(this.getSieve());
- document.querySelector('a[data-toggle="tab"][href="#sieve-widget-envelope"]')
+ document.querySelector('a[data-bs-toggle="tab"][href="#sieve-widget-envelope"]')
.addEventListener('hide.bs.tab', () => {
this.onEnvelopeChanged();
});
document.querySelector("#vacationEnvelopeEdit").addEventListener("click", () => {
document
- .querySelector(`a[data-toggle="tab"][href="#sieve-widget-envelope"]`)
+ .querySelector(`a[data-bs-toggle="tab"][href="#sieve-widget-envelope"]`)
.click();
});
diff --git a/src/common/libSieve/extensions/variables/templates/SieveSetActionUI.html b/src/common/libSieve/extensions/variables/templates/SieveSetActionUI.html
index 91dc4b95..eab18c86 100644
--- a/src/common/libSieve/extensions/variables/templates/SieveSetActionUI.html
+++ b/src/common/libSieve/extensions/variables/templates/SieveSetActionUI.html
@@ -1,15 +1,15 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="set.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-setaction"
+ <a data-i18n="set.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-setaction"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="set.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced"
+ <a data-i18n="set.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="set.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="set.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/variables/templates/SieveStringTestUI.html b/src/common/libSieve/extensions/variables/templates/SieveStringTestUI.html
index 36d5ca58..90565ed5 100644
--- a/src/common/libSieve/extensions/variables/templates/SieveStringTestUI.html
+++ b/src/common/libSieve/extensions/variables/templates/SieveStringTestUI.html
@@ -1,15 +1,15 @@
<div>
<ul id="template-tabs">
<li class="nav-item">
- <a data-i18n="string.tab.home" class="nav-link active" data-toggle="tab" href="#sieve-widget-stringtest"
+ <a data-i18n="string.tab.home" class="nav-link active" data-bs-toggle="tab" href="#sieve-widget-stringtest"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="string.tab.advanced" class="nav-link" data-toggle="tab" href="#sieve-widget-advanced"
+ <a data-i18n="string.tab.advanced" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-advanced"
role="tab"></a>
</li>
<li class="nav-item">
- <a data-i18n="string.tab.help" class="nav-link" data-toggle="tab" href="#sieve-widget-help" role="tab"></a>
+ <a data-i18n="string.tab.help" class="nav-link" data-bs-toggle="tab" href="#sieve-widget-help" role="tab"></a>
</li>
</ul>
diff --git a/src/common/libSieve/extensions/variables/widgets/SieveVariablesUI.mjs b/src/common/libSieve/extensions/variables/widgets/SieveVariablesUI.mjs
index 4916b006..f9f72772 100644
--- a/src/common/libSieve/extensions/variables/widgets/SieveVariablesUI.mjs
+++ b/src/common/libSieve/extensions/variables/widgets/SieveVariablesUI.mjs
@@ -96,7 +96,7 @@ class SieveSetActionUI extends SieveActionDialogBoxUI {
// Sort the modifiers...
let modifiers = document.querySelectorAll(`${widget.selector} .sieve-modifier`);
- modifiers = Array.from(modifiers).sort((lhs, rhs) => {
+ modifiers = [...modifiers].sort((lhs, rhs) => {
rhs = rhs.querySelector("input[type='checkbox'][name^='modifier/']").name;
lhs = lhs.querySelector("input[type='checkbox'][name^='modifier/']").name;
@@ -104,7 +104,7 @@ class SieveSetActionUI extends SieveActionDialogBoxUI {
});
for (const modifier of modifiers)
- document.querySelector(`${widget.selector}`).appendChild(modifier);
+ document.querySelector(`${widget.selector}`).append(modifier);
}
/**
diff --git a/src/common/libSieve/templates/debug.html b/src/common/libSieve/templates/debug.html
index 6a6e8148..c4985cd1 100644
--- a/src/common/libSieve/templates/debug.html
+++ b/src/common/libSieve/templates/debug.html
@@ -2,15 +2,15 @@
<div class="card-header d-flex justify-content-between py-0">
<ul class="nav nav-tabs card-header-tabs my-0 pt-3" role="tablist">
<li class="nav-item">
- <a data-i18n="debug.tab.script" class="sieve-accounts-tab nav-link active" data-toggle="tab" role="tab"
+ <a data-i18n="debug.tab.script" class="sieve-accounts-tab nav-link active" data-bs-toggle="tab" role="tab"
href="#debugscript"></a>
</li>
<li class="nav-item">
- <a data-i18n="debug.tab.capabilities" class="sieve-settings-tab nav-link" data-toggle="tab" role="tab"
+ <a data-i18n="debug.tab.capabilities" class="sieve-settings-tab nav-link" data-bs-toggle="tab" role="tab"
href="#debugcapabilities"></a>
</li>
<li class="nav-item">
- <a data-i18n="debug.tab.advanced" class="sieve-settings-tab nav-link" data-toggle="tab" role="tab"
+ <a data-i18n="debug.tab.advanced" class="sieve-settings-tab nav-link" data-bs-toggle="tab" role="tab"
href="#debugadvanced"></a>
</li>
</ul>
diff --git a/src/common/libSieve/templates/sidebar.html b/src/common/libSieve/templates/sidebar.html
index b544d71a..b93113ea 100644
--- a/src/common/libSieve/templates/sidebar.html
+++ b/src/common/libSieve/templates/sidebar.html
@@ -2,7 +2,7 @@
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
- <button data-i18n="sidebar.actions" class="accordion-button bg-light" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
+ <button data-i18n="sidebar.actions" class="accordion-button bg-light" type="button" data-bs-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
@@ -12,7 +12,7 @@
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
- <button data-i18n="sidebar.tests" class="accordion-button collapsed bg-light" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
+ <button data-i18n="sidebar.tests" class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
@@ -22,7 +22,7 @@
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
- <button data-i18n="sidebar.operators" class="accordion-button collapsed bg-light" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
+ <button data-i18n="sidebar.operators" class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-parent="#accordionExample">
diff --git a/src/common/libSieve/toolkit/SieveParser.mjs b/src/common/libSieve/toolkit/SieveParser.mjs
index 08106f80..f68b51f2 100644
--- a/src/common/libSieve/toolkit/SieveParser.mjs
+++ b/src/common/libSieve/toolkit/SieveParser.mjs
@@ -191,7 +191,7 @@ class SieveParser {
return result;
}
- if (isNaN(parseInt(length, 10)))
+ if (Number.isNaN(Number.parseInt(length, 10)))
throw new Error("Extract failed, length parameter is not a number");
result = this.bytes(length);
@@ -242,7 +242,7 @@ class SieveParser {
if (this._pos + offset > this._data.length)
throw new Error("Parser out of bounds");
- return !isNaN(parseInt(this._data.charAt(this._pos + offset), 10));
+ return !Number.isNaN(Number.parseInt(this._data.charAt(this._pos + offset), 10));
}
/**
diff --git a/src/common/libSieve/toolkit/SieveScriptDOM.mjs b/src/common/libSieve/toolkit/SieveScriptDOM.mjs
index 25ba8b4b..70843323 100644
--- a/src/common/libSieve/toolkit/SieveScriptDOM.mjs
+++ b/src/common/libSieve/toolkit/SieveScriptDOM.mjs
@@ -292,7 +292,7 @@ class SieveDocument {
// scan for null nodes..
for (item in this._nodes)
- if (whitelist.indexOf(this._nodes[item]) === NO_ELEMENT)
+ if (!whitelist.includes(this._nodes[item]))
if (this._nodes[item].parent() === null)
items.push(item);
@@ -305,7 +305,7 @@ class SieveDocument {
const it = items.shift();
for (item in this._nodes)
- if (whitelist.indexOf(this._nodes[item]) === NO_ELEMENT)
+ if (!whitelist.includes(this._nodes[item]))
if (this._nodes[item].parent().id() === it)
items.push(item);
diff --git a/src/common/libSieve/toolkit/events/DataTransfer.mjs b/src/common/libSieve/toolkit/events/DataTransfer.mjs
index e2cc11d1..a001cc3c 100644
--- a/src/common/libSieve/toolkit/events/DataTransfer.mjs
+++ b/src/common/libSieve/toolkit/events/DataTransfer.mjs
@@ -53,10 +53,10 @@ class SieveDataTransfer {
isBugFree() {
// Check if we are running in electron...
- if (window.navigator.userAgent.toLowerCase().indexOf(" electron/"))
+ if (window.navigator.userAgent.toLowerCase().includes(" electron/"))
return false;
- if (window.navigator.userAgent.toLowerCase().indexOf(" chrome/"))
+ if (window.navigator.userAgent.toLowerCase().includes(" chrome/"))
return false;
return true;
diff --git a/src/common/libSieve/toolkit/logic/GenericAtoms.mjs b/src/common/libSieve/toolkit/logic/GenericAtoms.mjs
index b343125b..4c11c8b5 100644
--- a/src/common/libSieve/toolkit/logic/GenericAtoms.mjs
+++ b/src/common/libSieve/toolkit/logic/GenericAtoms.mjs
@@ -415,7 +415,7 @@ class SieveGenericDependentItem extends SieveGenericMandatoryItem {
* This also means they have an implicit default value which makes parsing awkward.
*
* In case the tag is missing (which means using the implicit default) the class is fully transparent.
- * Otherwise it is greedy and eats leading and tailing whitespaces.
+ * Otherwise it is greedy and eats leading and trailing whitespaces.
*/
class SieveGenericOptionalItem extends SieveAbstractGeneric {
@@ -749,7 +749,7 @@ class SieveGenericStructure extends SieveAbstractElement {
try {
element.parse(parser);
}
- catch (ex) {
+ catch {
// TODO reset item
// Reset the position as if nothing happened
parser.pos(pos);
@@ -769,7 +769,7 @@ class SieveGenericStructure extends SieveAbstractElement {
try {
element.parse(parser);
- } catch (ex) {
+ } catch {
prev.enabled = false;
diff --git a/src/common/libSieve/toolkit/logic/GenericElements.mjs b/src/common/libSieve/toolkit/logic/GenericElements.mjs
index 78730f68..9e987be9 100644
--- a/src/common/libSieve/toolkit/logic/GenericElements.mjs
+++ b/src/common/libSieve/toolkit/logic/GenericElements.mjs
@@ -326,6 +326,7 @@ function initTests() {
*
* @param {*} capabilities
*/
+// eslint-disable-next-line no-unused-vars
function createGrammar(capabilities) {
initActions();
initTests();
diff --git a/src/common/libSieve/toolkit/style/layout.css b/src/common/libSieve/toolkit/style/layout.css
index d359b938..6fa059b0 100755
--- a/src/common/libSieve/toolkit/style/layout.css
+++ b/src/common/libSieve/toolkit/style/layout.css
@@ -1,9 +1,9 @@
body {
- margin:0px;
+ margin: 0;
}
#content {
- padding: 0px 10px;
+ padding: 0 10px;
margin-left: 180px;
}
@@ -14,20 +14,18 @@ body {
}
#toolbar .SivElement {
- width:140px;
+ width: 140px;
}
-#sivTrash .sivTrashBin
-{
+#sivTrash .sivTrashBin {
height: 80px;
- min-width:80px;
- background-color:transparent;
- background-image:url('trash.png');
- background-repeat:no-repeat;
- background-position:center center;
+ min-width: 80px;
+ background-color: transparent;
+ background-image: url('trash.png');
+ background-repeat: no-repeat;
+ background-position: center center;
}
-#sivTrash .sivTrashBin[data-sieve-dragging]
-{
- background-image:url('trash-full.png');
+#sivTrash .sivTrashBin[data-sieve-dragging] {
+ background-image: url('trash-full.png');
}
diff --git a/src/common/libSieve/toolkit/style/style.css b/src/common/libSieve/toolkit/style/style.css
index c52fb3e5..6e1f46cc 100755
--- a/src/common/libSieve/toolkit/style/style.css
+++ b/src/common/libSieve/toolkit/style/style.css
@@ -1,17 +1,15 @@
.sivAction[data-sieve-flavour],
.sivConditionText,
-.sivConditionCode
-{
- border: 1px #DDE4E9 solid;
- background-color: #F3F6F7;
- padding-left:3px;
+.sivConditionCode {
+ border: 1px #dde4e9 solid;
+ background-color: #f3f6f7;
+ padding-left: 3px;
cursor: default;
}
-.sivConditionCode code
-{
+.sivConditionCode code {
flex: auto 1 1;
white-space: pre-wrap;
}
@@ -19,17 +17,17 @@
.sivConditionCode,
.sivConditionText,
.sivEditableElement[data-sieve-flavour],
-.sivEditableElement
-{
+.sivEditableElement {
display: flex;
flex-direction: row;
}
+
.sivEditableElement > .sivSummaryContent,
.sivEditableElement > .sivSummaryCode {
flex: 1 1 auto;
}
-.sivEditableElement > .sivSummaryCode > code{
+.sivEditableElement > .sivSummaryCode > code {
white-space: pre-wrap;
}
@@ -41,10 +39,8 @@
margin-bottom: inherit;
}
-
-.sivEditableElement:hover
-{
- border: 1px red #EDEDED solid;
+.sivEditableElement:hover {
+ border: 1px red #ededed solid;
background-color: white;
box-shadow: 0 0 7px gray;
}
@@ -55,102 +51,91 @@ div[data-sieve-flavour][data-sieve-dragging] {
box-shadow: 0 0 5px red;
}
-.sivSummaryContent em{
- font-family:courier;
- font-style:normal;
+.sivSummaryContent em {
+ font-family: courier, serif;
+ font-style: normal;
}
-
- .sivEditableElement h1 {
- font-size:1em;
- padding:0px;
- margin:0px;
- }
-
+.sivEditableElement h1 {
+ font-size: 1em;
+ padding: 0;
+ margin: 0;
+}
/* Blocks */
-.sivBlock > .sivBlockChild:nth-last-of-type(2)
-{
+.sivBlockChild {
+ background-image: url('vline.png'), url('hline.png');
+ background-position: -7px center, 2px top;
+ background-repeat: repeat-y, no-repeat;
+}
+
+.sivBlock > .sivBlockChild:nth-last-of-type(2) {
background-image: url('hline.png');
background-position: 2px top;
background-repeat: no-repeat;
}
+/* Dropmarkers */
+.sivBlockSpacer {
+ background-image: url('vline.png');
+ background-position: -7px top;
+ background-repeat: repeat-y;
-.sivBlock > .sivBlockSpacer:nth-last-of-type(1)
-{
- background-image: none;
- padding-bottom: 0px;
+ padding: 2px 0 2px 0;
}
-.sivBlockChild
-{
- background-image: url('vline.png'), url('hline.png');
- background-position: -7px center, 2px top;
- background-repeat: repeat-y, no-repeat;
+.sivBlock > .sivBlockSpacer:nth-last-of-type(1) {
+ background-image: none;
+ padding-bottom: 0;
}
.sivConditionChild,
.sivConditionSpacer:not(:first-of-type) {
background-image: url('vline2.png');
- background-position: -4px center;
+ background-position: -4px center;
background-repeat: repeat-y;
}
.sivBlockChild,
.sivConditionChild {
- padding-left:15px;
+ padding-left: 15px;
}
.sivConditionalChild {
- padding:3px 0px 3px 3px;
- background-color: #E8EDF0;
- border: 1px #DDE4E9 solid
-}
-
-/* Dropmarkers*/
-.sivBlockSpacer
-{
- background-image: url('vline.png');
- background-position: -7px top;
- background-repeat: repeat-y;
-
- padding:2px 0px 2px 0px;
+ padding: 3px 0 3px 3px;
+ background-color: #e8edf0;
+ border: 1px #dde4e9 solid;
}
-.sivConditionSpacer{
- padding:0px;
+.sivConditionSpacer {
+ padding: 0;
}
.sivBlockSpacer,
.sivOperatorSpacer,
-.sivConditionSpacer
-{
+.sivConditionSpacer {
height: 6px;
}
.sivBlockSpacer > div,
.sivConditionSpacer > div,
-.sivOperatorSpacer > div
-{
+.sivOperatorSpacer > div {
height: 3px;
}
.sivBlockSpacer[data-sieve-dragging] > div,
.sivConditionSpacer[data-sieve-dragging] > div,
-.sivOperatorSpacer[data-sieve-dragging] > div
-{
- background-color : red;
- background-image: linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
+.sivOperatorSpacer[data-sieve-dragging] > div {
+ background-color: red;
+ background-image: linear-gradient(rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.15));
box-shadow: 0 0 5px red;
}
-#toolbar div[data-sieve-flavour]
-{
- margin:2px 5px;
- padding:0px 3px;
+#toolbar div[data-sieve-flavour] {
+ margin: 2px 5px;
+ padding: 0 3px;
background-color: lightgray;
- border-radius:3px;
+ border-radius: 3px;
border: 1px solid gray;
}
diff --git a/src/common/libSieve/toolkit/templates/SieveDropDownWidget.html b/src/common/libSieve/toolkit/templates/SieveDropDownWidget.html
index 1febaaa8..565a5e69 100644
--- a/src/common/libSieve/toolkit/templates/SieveDropDownWidget.html
+++ b/src/common/libSieve/toolkit/templates/SieveDropDownWidget.html
@@ -4,9 +4,9 @@
<div class="sivDropDownWidget-active">
</div>
</div>
- <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
+ <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"> </button>
- <div class="dropdown-menu dropdown-menu-right sivDropDownWidget-menu" style="min-width:100%">
+ <div class="dropdown-menu dropdown-menu-end sivDropDownWidget-menu" style="min-width:100%">
</div>
</div>
</div> \ No newline at end of file
diff --git a/src/common/libSieve/toolkit/templates/SieveNumericWidget.html b/src/common/libSieve/toolkit/templates/SieveNumericWidget.html
index a2de597a..c8a720c6 100644
--- a/src/common/libSieve/toolkit/templates/SieveNumericWidget.html
+++ b/src/common/libSieve/toolkit/templates/SieveNumericWidget.html
@@ -1,8 +1,8 @@
<div>
<div class="input-group col-md-8">
- <input class="form-control sieve-numeric-value text-right" aria-label="Text input with dropdown button"
+ <input class="form-control sieve-numeric-value text-end" aria-label="Text input with dropdown button"
type="number" min="0" step="1"> </input>
- <button class="btn btn-outline-secondary dropdown-toggle sieve-numeric-unit" type="button" data-toggle="dropdown"
+ <button class="btn btn-outline-secondary dropdown-toggle sieve-numeric-unit" type="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">Dropdown</button>
<div class="dropdown-menu">
<button class="dropdown-item" type="button" data-value="">
diff --git a/src/common/libSieve/toolkit/templates/SieveStringListWidget.html b/src/common/libSieve/toolkit/templates/SieveStringListWidget.html
index b98e5733..332a11e9 100644
--- a/src/common/libSieve/toolkit/templates/SieveStringListWidget.html
+++ b/src/common/libSieve/toolkit/templates/SieveStringListWidget.html
@@ -2,7 +2,7 @@
<div class="input-group input-group mb-1 string-list-item-template">
<input type="text" class="form-control col-lg-8" />
<button type="button" class="sieve-stringlist-dropdown d-none btn btn-outline-secondary dropdown-toggle-split"
- data-toggle="dropdown">
+ data-bs-toggle="dropdown">
<span class="sivIconMore"></span>
</button>
<button type="button" class="sieve-stringlist-delete btn btn-outline-secondary">
diff --git a/src/common/libSieve/toolkit/templates/SieveStringWidget.html b/src/common/libSieve/toolkit/templates/SieveStringWidget.html
index a1c6162a..486d2c37 100644
--- a/src/common/libSieve/toolkit/templates/SieveStringWidget.html
+++ b/src/common/libSieve/toolkit/templates/SieveStringWidget.html
@@ -3,7 +3,7 @@
<input type="text" class="form-control col-lg-8 sieve-string-item" />
<button type="button"
class="sieve-string-dropdown d-none btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
- data-toggle="dropdown">
+ data-bs-toggle="dropdown">
<span class="sr-only">Toggle Dropdown</span>
</button>
</div>
diff --git a/src/common/libSieve/toolkit/utils/SieveI18n.mjs b/src/common/libSieve/toolkit/utils/SieveI18n.mjs
index 39742117..97e11950 100644
--- a/src/common/libSieve/toolkit/utils/SieveI18n.mjs
+++ b/src/common/libSieve/toolkit/utils/SieveI18n.mjs
@@ -132,7 +132,7 @@ class SieveI18n {
try {
await this.loadDictionary(`${path}${locale}.json`);
- } catch (ex) {
+ } catch {
// In case loading the dictionary failed e.g. due to a parsing error
// we try falling back to our default one which is used during development.
await this.loadDictionary(`${path}${DEFAULT_LOCALE}.json`);
@@ -158,7 +158,7 @@ class SieveI18n {
try {
data = await (await fetch(dictionary, { cache: "no-store" })).text();
} catch (ex) {
- this.getLogger().logI18n(`Failed to load dictionary ${dictionary}`);
+ this.getLogger().logI18n(`Loading dictionary ${dictionary} failed with error ${ex}`);
throw new Error(`Failed to load dictionary ${dictionary}`);
}
diff --git a/src/common/libSieve/toolkit/widgets/Boxes.mjs b/src/common/libSieve/toolkit/widgets/Boxes.mjs
index 46df0954..bb6f2eae 100644
--- a/src/common/libSieve/toolkit/widgets/Boxes.mjs
+++ b/src/common/libSieve/toolkit/widgets/Boxes.mjs
@@ -253,7 +253,7 @@ class SieveDropBoxUI extends SieveAbstractBoxUI {
createHtml(parent) {
parent.classList.add("sivDropBox");
parent.classList.add(this.name);
- parent.appendChild(document.createElement("div"));
+ parent.append(document.createElement("div"));
return parent;
}
@@ -335,7 +335,7 @@ class SieveSourceBoxUI extends SieveAbstractBoxUI {
// update the code section
const code = document.querySelector(`#${this.uniqueId}-code > code`);
while (code.firstChild)
- code.removeChild(code.firstChild);
+ code.firstChild.remove();
code.textContent = this.getSieve().toScript();
@@ -363,9 +363,9 @@ class SieveSimpleBoxUI extends SieveAbstractBoxUI {
const summary = document.createElement("div");
summary.classList.add("sivSummaryContent");
summary.id = `${this.uniqueId}-summary`;
- summary.appendChild(this.getSummary());
+ summary.append(this.getSummary());
- parent.appendChild(summary);
+ parent.append(summary);
return parent;
}
@@ -391,7 +391,7 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
const body = document.querySelector("#sivDialogBody");
while (body.firstChild)
- body.removeChild(body.firstChild);
+ body.firstChild.remove();
// Hide the dialog...
bootstrap.Modal
@@ -401,14 +401,14 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
// update the summary section
const summary = document.querySelector(`#${this.uniqueId}-summary`);
while (summary.firstChild)
- summary.removeChild(summary.firstChild);
+ summary.firstChild.remove();
- summary.appendChild(this.getSummary());
+ summary.append(this.getSummary());
// update the code section
const code = document.querySelector(`#${this.uniqueId}-code > code`);
while (code.firstChild)
- code.removeChild(code.firstChild);
+ code.firstChild.remove();
code.textContent = this.getSieve().toScript();
}
@@ -419,7 +419,7 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
async showEditor() {
// TODO hide the save button in case we have only a help tab...
- (new bootstrap.Modal(document.querySelector('#sivDialog2'))).show();
+ (new bootstrap.Modal('#sivDialog2')).show();
const save = () => { this.save(); };
@@ -436,11 +436,11 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
// Empty the existing dialog.
const dialogTabs = document.querySelector("#sivDialogTabs");
while (dialogTabs.firstChild)
- dialogTabs.removeChild(dialogTabs.firstChild);
+ dialogTabs.firstChild.remove();
const dialogBody = document.querySelector("#sivDialogBody");
while (dialogBody.firstChild)
- dialogBody.removeChild(dialogBody.firstChild);
+ dialogBody.firstChild.remove();
const template = await (new SieveTemplate()).load(this.getTemplate());
@@ -450,7 +450,7 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
throw new Error("Failed to load template, no tab section specified");
while (tabs.children.length)
- dialogTabs.appendChild(tabs.firstChild);
+ dialogTabs.append(tabs.firstChild);
const content = template.querySelector("#template-content");
@@ -458,7 +458,7 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
throw new Error("Failed to load template, no content section specified");
while (content.children.length)
- dialogBody.appendChild(content.firstChild);
+ dialogBody.append(content.firstChild);
this.onLoad();
}
@@ -508,18 +508,18 @@ class SieveDialogBoxUI extends SieveSourceBoxUI {
return true;
});
- content.appendChild(this.getSummary());
+ content.append(this.getSummary());
// We need this container to make customizing the box easier.
// e.g. for the allof/anyof operator.
const div = document.createElement("div");
div.classList.add("sivEditableElement");
- div.appendChild(content);
- div.appendChild(code);
- div.appendChild(controls);
+ div.append(content);
+ div.append(code);
+ div.append(controls);
- parent.appendChild(div);
+ parent.append(div);
return parent;
}
diff --git a/src/common/libSieve/toolkit/widgets/Widgets.mjs b/src/common/libSieve/toolkit/widgets/Widgets.mjs
index d2586d93..83c4ecc1 100644
--- a/src/common/libSieve/toolkit/widgets/Widgets.mjs
+++ b/src/common/libSieve/toolkit/widgets/Widgets.mjs
@@ -96,7 +96,7 @@ class SieveStringListWidget {
if (this._min >= this.items().length)
return;
- item.parentNode.removeChild(item);
+ item.remove();
});
// connect the drop down menu...
@@ -171,13 +171,13 @@ class SieveStringListWidget {
const container = document.createElement("div");
const item = document.querySelector(`${this._selector} .sieve-stringlist-items`);
- item.appendChild(container);
+ item.append(container);
const template = (await (new SieveTemplate())
.load("./toolkit/templates/SieveStringListWidget.html"))
.querySelector(".string-list-item-template");
- container.appendChild(template);
+ container.append(template);
this.onItemAdded(container, value);
return this;
@@ -196,7 +196,7 @@ class SieveStringListWidget {
const elm = document.querySelector(this._selector);
while (elm.firstChild)
- elm.removeChild(elm.firstChild);
+ elm.firstChild.remove();
const items = document.createElement("div");
items.classList.add("sieve-stringlist-items");
@@ -204,8 +204,8 @@ class SieveStringListWidget {
const controls = document.createElement("div");
controls.classList.add("sieve-stringlist-control");
- elm.appendChild(items);
- elm.appendChild(controls);
+ elm.append(items);
+ elm.append(controls);
(async () => {
@@ -213,14 +213,14 @@ class SieveStringListWidget {
.load("./toolkit/templates/SieveStringListWidget.html"))
.querySelector(".sieve-stringlist-add");
- controls.appendChild(template);
+ controls.append(template);
controls
.addEventListener("click", () => { this.addItem(); });
})();
- this._min = parseInt(elm.dataset.listMin, 10);
+ this._min = Number.parseInt(elm.dataset.listMin, 10);
- if (isNaN(this._min))
+ if (Number.isNaN(this._min))
this._min = DEFAULT_STRING_LIST_MIN;
// init values if possible
@@ -334,10 +334,10 @@ class SieveDropDownWidget {
const elm = document.querySelector(this.selector);
while (elm.firstChild)
- elm.removeChild(elm.firstChild);
+ elm.firstChild.remove();
while (template.children.length)
- elm.appendChild(template.firstChild);
+ elm.append(template.firstChild);
this.initWidgets(sivElement);
}
@@ -448,11 +448,11 @@ class SieveAbstractItemWidget {
const container = document.createElement("div");
while (template.children.length)
- container.appendChild(template.firstChild);
+ container.append(template.firstChild);
container.dataset.nodename = this.constructor.nodeName();
- this.getElement().appendChild(container);
+ this.getElement().append(container);
this.load(sivElement);
@@ -518,10 +518,10 @@ class SieveDropDownItemWidget extends SieveAbstractItemWidget {
const activeElement = this.getActiveItem();
while (activeElement.firstChild)
- activeElement.removeChild(activeElement.firstChild);
+ activeElement.firstChild.remove();
while (menuElement.firstChild)
- activeElement.appendChild(menuElement.firstChild);
+ activeElement.append(menuElement.firstChild);
activeElement.dataset.nodename = this.constructor.nodeName();
activeElement.dataset.value = menuElement.dataset.value;
@@ -828,7 +828,7 @@ class SieveStringWidget {
.load("./toolkit/templates/SieveStringWidget.html"))
.querySelector(".string-item-template");
- document.querySelector(this._selector).appendChild(template);
+ document.querySelector(this._selector).append(template);
this.setValue(value);
}
@@ -850,7 +850,7 @@ class SieveStringWidget {
.querySelector(".sieve-string-dropdown");
button.classList.remove("d-none");
- button.insertAdjacentElement('afterend', menu);
+ button.after(menu);
this.initClickHandler(menu);
this.initUpdatables(menu);
@@ -971,10 +971,10 @@ class SieveNumericWidget {
const elm = document.querySelector(this._selector);
while (elm.firstChild)
- elm.removeChild(elm.firstChild);
+ elm.firstChild.remove();
while (template.children.length)
- elm.appendChild(template.firstChild);
+ elm.append(template.firstChild);
document
.querySelector(`${this._selector} .sieve-numeric-value`)
diff --git a/src/common/managesieve.ui/accounts/SieveAbstractAccountUI.mjs b/src/common/managesieve.ui/accounts/SieveAbstractAccountUI.mjs
new file mode 100644
index 00000000..b6485744
--- /dev/null
+++ b/src/common/managesieve.ui/accounts/SieveAbstractAccountUI.mjs
@@ -0,0 +1,333 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+import { SieveLogger } from "./../utils/SieveLogger.mjs";
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+import { SieveScriptUI } from "./SieveScriptUI.mjs";
+import { SieveDebugSettingsUI } from "./../settings/ui/SieveDebugSettingsUI.mjs";
+import { SieveCapabilities } from "./SieveCapabilities.mjs";
+
+const IS_SMALLER = -1;
+const IS_EQUAL = 0;
+const IS_LARGER = 1;
+
+/**
+ * A UI renderer for a sieve account
+ */
+class SieveAbstractAccountUI {
+
+ /**
+ * Creates a new renderer for a sieve account.
+ *
+ * @param {SieveAccountsUI} accounts
+ * the parent sieve accounts renderer
+ * @param {string} id
+ * the account unique id.
+ */
+ constructor(accounts, id) {
+ this.accounts = accounts;
+ this.id = id;
+ }
+
+ /**
+ * Gets an instance to the logger.
+ *
+ * @returns {SieveLogger}
+ * an reference to the logger instance.
+ **/
+ getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+ /**
+ * Executes an action on the communication process.
+ *
+ * @param {string} action
+ * the actions unique name
+ * @param {object} [payload]
+ * the payload which should be send
+ *
+ * @returns {Promise<object>}
+ * the result received for this action.
+ */
+ async send(action, payload) {
+
+ if (typeof (payload) === "undefined" || payload === null)
+ payload = {};
+
+ if (typeof (payload) !== "object")
+ payload = { "data": payload };
+
+ payload["account"] = this.id;
+
+ return await SieveIpcClient.sendMessage("core", action, payload);
+ }
+
+ /**
+ * Checks if the current account has an active connection to the server
+ *
+ * @returns {boolean} true in case tha account is connected otherwise false
+ */
+ async isConnected() {
+ return await this.send("account-connected");
+ }
+
+ /**
+ * Establishes a connection to the server
+ */
+ async connect() {
+
+ const item = await (new SieveTemplate()).load(`./accounts/account.connecting.html`);
+
+ const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
+ while (scripts.firstChild)
+ scripts.firstChild.remove();
+
+ scripts.append(item);
+
+ await this.send("account-connect");
+ await this.render();
+ }
+
+ /**
+ * Disconnects the account from the server.
+ */
+ async disconnect() {
+ const item = await (new SieveTemplate()).load(`./accounts/account.disconnecting.html`);
+
+ const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
+ while (scripts.firstChild)
+ scripts.firstChild.remove();
+
+ scripts.append(item);
+
+ await this.send("account-disconnect");
+ await this.render();
+ }
+
+ /**
+ * Renders the settings pane
+ *
+ */
+ async renderSettings() {
+
+ const elm = await (new SieveTemplate()).load("./accounts/account.settings.html");
+
+
+ const account = await this.send("account-get-settings");
+
+
+ elm.querySelector(".sieve-settings-hostname")
+ .textContent = account.hostname;
+ elm.querySelector(".sieve-settings-port")
+ .textContent = account.port;
+
+ if (!account.secure)
+ elm.querySelector(".sieve-settings-secure").style.display = 'none';
+
+ elm.querySelector(".sieve-settings-username")
+ .textContent = account.username;
+
+ if (elm.querySelector(".sieve-settings-mechanism")) {
+ elm.querySelector(".sieve-settings-mechanism")
+ .textContent = account.mechanism;
+ }
+
+ if (elm.querySelector(".sieve-settings-fingerprint")) {
+ elm.querySelector(".sieve-settings-fingerprint")
+ .textContent = account.fingerprint;
+
+ if (account.fingerprint !== "")
+ elm.querySelector(".sieve-settings-fingerprint-item").classList.remove("d-none");
+ }
+
+ // Clear any existing left overs...
+ const settings = document.querySelector(`#siv-account-${this.id} .sieve-settings-content`);
+ while (settings.firstChild)
+ settings.firstChild.remove();
+
+ // ... and append the new element
+ settings.append(elm);
+
+ if (elm.querySelector(".sieve-account-edit-debug")) {
+ elm.querySelector(".sieve-account-edit-debug")
+ .addEventListener("click", () => { this.showAdvancedSettings(); });
+ }
+ }
+
+ /**
+ * Renders the accounts outer ui
+ *
+ */
+ async renderAccount() {
+ const elm = await (new SieveTemplate()).load("./accounts/account.html");
+
+ elm.id = `siv-account-${this.id}`;
+
+ elm.querySelector(".sieve-accounts-content").id = `sieve-accounts-content-${this.id}`;
+ elm.querySelector(".sieve-accounts-tab").href = `#sieve-accounts-content-${this.id}`;
+
+ elm.querySelector(".sieve-settings-content").id = `sieve-settings-content-${this.id}`;
+ elm.querySelector(".sieve-settings-tab").href = `#sieve-settings-content-${this.id}`;
+ elm.querySelector(".sieve-settings-tab").addEventListener('shown.bs.tab', () => { this.renderSettings(); });
+
+ elm.querySelector(".siv-account-name").textContent
+ = await this.send("account-get-displayname");
+
+ document.querySelector(".siv-accounts-items").append(elm);
+
+ elm
+ .querySelector(".siv-account-create")
+ .addEventListener("click", () => { this.createScript(); });
+ elm
+ .querySelector(".sieve-account-edit-settings")
+ .addEventListener("click", () => { this.showSettings(); });
+ elm
+ .querySelector(".sieve-account-capabilities")
+ .addEventListener("click", () => { this.showCapabilities(); });
+ elm
+ .querySelector(".sieve-account-reconnect-server")
+ .addEventListener("click", () => { this.connect(); });
+ elm
+ .querySelector(".sieve-account-disconnect-server")
+ .addEventListener("click", () => { this.disconnect(); });
+ }
+
+ /**
+ * Renders the account ui's script pane
+ */
+ async onRenderConnected() {
+ const data = await this.send("account-list");
+
+ const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
+
+ while (scripts.firstChild)
+ scripts.firstChild.remove();
+
+ if (!data.length) {
+ const elm = await (new SieveTemplate()).load(`./accounts/account.empty.html`);
+ scripts.append(elm);
+
+ elm
+ .querySelector(".sieve-script-empty-create")
+ .addEventListener("click", () => { this.createScript(); });
+ }
+
+ // Sort the script names by their name...
+ data.sort((a, b) => {
+ const scriptA = a.script.toUpperCase();
+ const scriptB = b.script.toUpperCase();
+
+ if (scriptA < scriptB)
+ return IS_SMALLER;
+
+ if (scriptA > scriptB)
+ return IS_LARGER;
+
+ return IS_EQUAL;
+ });
+
+ data.forEach(async (item) => {
+ this.getLogger().logWidget(`Rendering ${this.id}/${item.script}`);
+ await ((new SieveScriptUI(this, item.script, item.active)).render());
+ });
+
+ }
+
+ /**
+ * Called when the account should be rendered because of a disconnect.
+ */
+ async onRenderDisconnected() {
+
+ const elm = await (new SieveTemplate()).load(`./accounts/account.disconnected.html`);
+
+ const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
+
+ while (scripts.firstChild)
+ scripts.firstChild.remove();
+
+ scripts.append(elm);
+
+ elm.querySelector(".sieve-script-connect")
+ .addEventListener("click", () => { this.connect(); });
+ }
+
+ /**
+ * Renders the UI for this component.
+ */
+ async render() {
+ this.getLogger().logWidget(`Rendering Account ${this.id}`);
+
+ if (!document.querySelector(`#siv-account-${this.id}`)) {
+ await this.renderAccount();
+ this.renderSettings();
+ }
+
+ if (await this.isConnected()) {
+ await this.onRenderConnected();
+ return;
+ }
+
+ await this.onRenderDisconnected("disconnected");
+ }
+
+ /**
+ * Shows the settings dialog
+ */
+ showSettings() {
+ (new bootstrap.Tab(`#siv-account-${this.id} .sieve-settings-tab`)).show();
+ }
+
+ /**
+ * Show the advanced settings dialog
+ */
+ showAdvancedSettings() {
+ (new SieveDebugSettingsUI(this)).show();
+ }
+
+ /**
+ * Shows the account's capabilities
+ *
+ * @returns {SieveAccountUI}
+ * a self reference.
+ */
+ async showCapabilities() {
+
+ if (await this.isConnected() === false)
+ return this;
+
+ const capabilities = await this.send("account-capabilities");
+ await (new SieveCapabilities()).show(capabilities);
+
+ return this;
+ }
+
+ /**
+ * Prompts for the new script name an creates the script
+ */
+ async createScript() {
+
+ if (await this.isConnected() === false)
+ return;
+
+ const name = await this.send("script-create");
+
+ if (name !== "")
+ await this.render();
+ }
+
+}
+
+export { SieveAbstractAccountUI };
diff --git a/src/common/managesieve.ui/accounts/SieveAbstractAccounts.js b/src/common/managesieve.ui/accounts/SieveAbstractAccounts.js
deleted file mode 100644
index f053e016..00000000
--- a/src/common/managesieve.ui/accounts/SieveAbstractAccounts.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveAccountUI */
- /* global SieveIpcClient */
- /* global SieveLogger */
-
- /**
- * A UI renderer for a list of sieve accounts
- **/
- class SieveAbstractAccounts {
-
- /**
- * Gets an instance to the logger.
- *
- * @returns {SieveLogger}
- * an reference to the logger instance.
- **/
- getLogger() {
- return SieveLogger.getInstance();
- }
-
- /**
- * Renders the UI for this component.
- */
- async render() {
-
- this.getLogger().logWidget("Rendering Accounts...");
-
- const items = document.querySelector(".siv-accounts-items");
- while (items.firstChild)
- items.removeChild(items.firstChild);
-
- const accounts = await SieveIpcClient.sendMessage("core", "accounts-list");
-
- for (const account of accounts) {
- this.getLogger().logWidget(` + Accounts ${account}`);
- await ((new SieveAccountUI(this, account)).render());
- }
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractAccounts = SieveAbstractAccounts;
- else
- exports.SieveAbstractAccounts = SieveAbstractAccounts;
-
-})(this);
diff --git a/src/common/managesieve.ui/accounts/SieveAbstractAccounts.mjs b/src/common/managesieve.ui/accounts/SieveAbstractAccounts.mjs
new file mode 100644
index 00000000..5d27120a
--- /dev/null
+++ b/src/common/managesieve.ui/accounts/SieveAbstractAccounts.mjs
@@ -0,0 +1,51 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAccountUI } from "./SieveAccountUI.mjs";
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+import { SieveLogger } from "./../utils/SieveLogger.mjs";
+
+/**
+ * A UI renderer for a list of sieve accounts
+ **/
+class SieveAbstractAccounts {
+
+ /**
+ * Gets an instance to the logger.
+ *
+ * @returns {SieveLogger}
+ * an reference to the logger instance.
+ **/
+ getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+ /**
+ * Renders the UI for this component.
+ */
+ async render() {
+
+ this.getLogger().logWidget("Rendering Accounts...");
+
+ const items = document.querySelector(".siv-accounts-items");
+ while (items.firstChild)
+ items.firstChild.remove();
+
+ const accounts = await SieveIpcClient.sendMessage("core", "accounts-list");
+
+ for (const account of accounts) {
+ this.getLogger().logWidget(` + Accounts ${account}`);
+ await ((new SieveAccountUI(this, account)).render());
+ }
+ }
+}
+
+export { SieveAbstractAccounts };
diff --git a/src/common/managesieve.ui/accounts/SieveAccountUI.js b/src/common/managesieve.ui/accounts/SieveAccountUI.js
deleted file mode 100644
index ec2db189..00000000
--- a/src/common/managesieve.ui/accounts/SieveAccountUI.js
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
-
- /* global SieveLogger */
- /* global SieveTemplate */
- /* global SieveScriptUI */
- /* global SieveIpcClient */
- /* global SieveServerSettingsUI */
- /* global SieveCredentialsSettingsUI */
- /* global SieveDebugSettingsUI */
- /* global SieveCapabilities */
-
- const IS_SMALLER = -1;
- const IS_EQUAL = 0;
- const IS_LARGER = 1;
-
- /**
- * A UI renderer for a sieve account
- */
- class SieveAccountUI {
-
- /**
- * Creates a new renderer for a sieve account.
- *
- * @param {SieveAccountsUI} accounts
- * the parent sieve accounts renderer
- * @param {string} id
- * the account unique id.
- */
- constructor(accounts, id) {
- this.accounts = accounts;
- this.id = id;
- }
-
- /**
- * Gets an instance to the logger.
- *
- * @returns {SieveLogger}
- * an reference to the logger instance.
- **/
- getLogger() {
- return SieveLogger.getInstance();
- }
-
- /**
- * Executes an action on the communication process.
- *
- * @param {string} action
- * the actions unique name
- * @param {object} [payload]
- * the payload which should be send
- *
- * @returns {Promise<object>}
- * the result received for this action.
- */
- async send(action, payload) {
-
- if (typeof (payload) === "undefined" || payload === null)
- payload = {};
-
- if (typeof (payload) !== "object")
- payload = { "data": payload };
-
- payload["account"] = this.id;
-
- return await SieveIpcClient.sendMessage("core", action, payload);
- }
-
- /**
- * Checks if the current account has an active connection to the server
- *
- * @returns {boolean} true in case tha account is connected otherwise false
- */
- async isConnected() {
- return await this.send("account-connected");
- }
-
- /**
- * Establishes a connection to the server
- */
- async connect() {
-
- const item = await (new SieveTemplate()).load(`./accounts/account.connecting.tpl`);
-
- const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
- while (scripts.firstChild)
- scripts.removeChild(scripts.firstChild);
-
- scripts.appendChild(item);
-
- try {
- await this.send("account-connect");
- }
- catch (ex) {
- await this.disconnect();
- }
-
- await this.render();
- }
-
- /**
- * Disconnects the account from the server.
- */
- async disconnect() {
- const item = await (new SieveTemplate()).load(`./accounts/account.disconnecting.tpl`);
-
- const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
- while (scripts.firstChild)
- scripts.removeChild(scripts.firstChild);
-
- scripts.appendChild(item);
-
- await this.send("account-disconnect");
- await this.render();
- }
-
- /**
- * Renders the settings pane
- *
- */
- async renderSettings() {
-
- const elm = await (new SieveTemplate()).load("./accounts/account.settings.tpl");
-
-
- const account = await this.send("account-get-settings");
-
-
- elm.querySelector(".sieve-settings-hostname")
- .textContent = account.hostname;
- elm.querySelector(".sieve-settings-port")
- .textContent = account.port;
-
- if (!account.secure)
- elm.querySelector(".sieve-settings-secure").style.display = 'none';
-
- elm.querySelector(".sieve-settings-username")
- .textContent = account.username;
-
- if (elm.querySelector(".sieve-settings-mechanism")) {
- elm.querySelector(".sieve-settings-mechanism")
- .textContent = account.mechanism;
- }
-
- if (elm.querySelector(".sieve-settings-fingerprint")) {
- elm.querySelector(".sieve-settings-fingerprint")
- .textContent = account.fingerprint;
-
- if (account.fingerprint !== "")
- elm.querySelector(".sieve-settings-fingerprint-item").classList.remove("d-none");
- }
-
- // Clear any existing left overs...
- const settings = document.querySelector(`#siv-account-${this.id} .sieve-settings-content`);
- while (settings.firstChild)
- settings.removeChild(settings.firstChild);
-
- // ... and append the new element
- settings.appendChild(elm);
-
- // ... finally connect the listeners.
- if (elm.querySelector(".sieve-account-delete-server")) {
- elm.querySelector(".sieve-account-delete-server")
- .addEventListener("click", () => { this.remove(); });
- }
-
- if (elm.querySelector(".sieve-account-edit-server")) {
- elm.querySelector(".sieve-account-edit-server")
- .addEventListener("click", () => { this.showServerSettings(); });
- }
-
- if (elm.querySelector(".sieve-account-edit-credentials")) {
- elm.querySelector(".sieve-account-edit-credentials")
- .addEventListener("click", () => { this.showCredentialSettings(); });
- }
-
- if (elm.querySelector(".sieve-account-edit-debug")) {
- elm.querySelector(".sieve-account-edit-debug")
- .addEventListener("click", () => { this.showAdvancedSettings(); });
- }
-
- if (elm.querySelector(".sieve-account-export")) {
- elm.querySelector(".sieve-account-export")
- .addEventListener("click", () => { this.exportSettings(); });
- }
-
- }
-
- /**
- * Renders the accounts outer ui
- *
- */
- async renderAccount() {
- const elm = await (new SieveTemplate()).load("./accounts/account.tpl");
-
- elm.id = `siv-account-${this.id}`;
-
- elm.querySelector(".sieve-accounts-content").id = `sieve-accounts-content-${this.id}`;
- elm.querySelector(".sieve-accounts-tab").href = `#sieve-accounts-content-${this.id}`;
-
- elm.querySelector(".sieve-settings-content").id = `sieve-settings-content-${this.id}`;
- elm.querySelector(".sieve-settings-tab").href = `#sieve-settings-content-${this.id}`;
- elm.querySelector(".sieve-settings-tab").addEventListener('shown.bs.tab', () => { this.renderSettings(); });
-
- elm.querySelector(".siv-account-name").textContent
- = await this.send("account-get-displayname");
-
- document.querySelector(".siv-accounts-items").appendChild(elm);
-
- elm
- .querySelector(".siv-account-create")
- .addEventListener("click", () => { this.createScript(); });
- elm
- .querySelector(".sieve-account-edit-settings")
- .addEventListener("click", () => { this.showSettings(); });
- elm
- .querySelector(".sieve-account-capabilities")
- .addEventListener("click", () => { this.showCapabilities(); });
- elm
- .querySelector(".sieve-account-reconnect-server")
- .addEventListener("click", () => { this.connect(); });
- elm
- .querySelector(".sieve-account-disconnect-server")
- .addEventListener("click", () => { this.disconnect(); });
- }
-
- /**
- * Renders the account ui's script pane
- */
- async onRenderConnected() {
- const data = await this.send("account-list");
-
- const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
-
- while (scripts.firstChild)
- scripts.removeChild(scripts.firstChild);
-
- if (!data.length) {
- const elm = await (new SieveTemplate()).load(`./accounts/account.empty.html`);
- scripts.appendChild(elm);
-
- elm
- .querySelector(".sieve-script-empty-create")
- .addEventListener("click", () => { this.createScript(); });
- }
-
- // Sort the script names by their name...
- data.sort((a, b) => {
- const scriptA = a.script.toUpperCase();
- const scriptB = b.script.toUpperCase();
-
- if (scriptA < scriptB)
- return IS_SMALLER;
-
- if (scriptA > scriptB)
- return IS_LARGER;
-
- return IS_EQUAL;
- });
-
- data.forEach(async (item) => {
- this.getLogger().logWidget(`Rendering ${this.id}/${item.script}`);
- await ((new SieveScriptUI(this, item.script, item.active)).render());
- });
-
- }
-
- /**
- * Called when the account should be rendered because of a disconnect.
- */
- async onRenderDisconnected() {
-
- const elm = await (new SieveTemplate()).load(`./accounts/account.disconnected.tpl`);
-
- const scripts = document.querySelector(`#siv-account-${this.id} .siv-tpl-scripts`);
-
- while (scripts.firstChild)
- scripts.removeChild(scripts.firstChild);
-
- scripts.appendChild(elm);
-
- elm.querySelector(".sieve-script-connect")
- .addEventListener("click", () => { this.connect(); });
- }
-
- /**
- * Renders the UI for this component.
- */
- async render() {
- this.getLogger().logWidget(`Rendering Account ${this.id}`);
-
- if (!document.querySelector(`#siv-account-${this.id}`)) {
- await this.renderAccount();
- this.renderSettings();
- }
-
- const status = await this.isConnected();
-
- if (status === false) {
- await this.onRenderDisconnected("disconnected");
- return;
- }
-
- this.onRenderConnected();
- }
-
- /**
- * Asks the user if he is sure to delete the account.
- * If yes it triggers expunging the account settings.
- * This can not be undone.
- */
- async remove() {
- await this.accounts.remove(this);
- }
-
- /**
- * Shows the settings dialog
- */
- showSettings() {
- const tab = document.querySelector(`#siv-account-${this.id} .sieve-settings-tab`);
- (new bootstrap.Tab(tab)).show();
- }
-
- /**
- * Shows the server settings dialog.
- */
- async showServerSettings() {
-
- await (new SieveServerSettingsUI(this)).show();
-
- this.renderSettings();
-
- // Update the account name it may have changed.
- document
- .querySelector(`#siv-account-${this.id} .siv-account-name`)
- .textContent = await this.send("account-get-displayname");
- }
-
- /**
- * Shows the credential settings dialog.
- **/
- showCredentialSettings() {
- (new SieveCredentialsSettingsUI(this)).show();
- }
-
- /**
- * Show the advanced settings dialog
- */
- showAdvancedSettings() {
- (new SieveDebugSettingsUI(this)).show();
- }
-
- /**
- * Exports the account's settings to a file.
- */
- async exportSettings() {
- await this.send("account-export");
- }
-
- /**
- * Shows the account's capabilities
- *
- * @returns {SieveAccountUI}
- * a self reference.
- */
- async showCapabilities() {
-
- if (await this.isConnected() === false)
- return this;
-
- const capabilities = await this.send("account-capabilities");
- await (new SieveCapabilities()).show(capabilities);
-
- return this;
- }
-
- /**
- * Prompts for the new script name an creates the script
- */
- async createScript() {
-
- if (await this.isConnected() === false)
- return;
-
- const name = await this.send("script-create");
-
- if (name !== "")
- await this.render();
- }
-
- }
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveAccountUI;
- else
- exports.SieveAccountUI = SieveAccountUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/accounts/SieveCapabilities.js b/src/common/managesieve.ui/accounts/SieveCapabilities.js
deleted file mode 100644
index 7d318ab1..00000000
--- a/src/common/managesieve.ui/accounts/SieveCapabilities.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveTemplate */
-
- /**
- * Implements a dialog which displays the accounts capabilities.
- */
- class SieveCapabilities {
-
- /**
- * Shows the capability dialog.
- *
- * @param {object} capabilities
- * the server's capabilities.
- */
- async show(capabilities) {
-
- document.querySelector("#ctx").appendChild(
- await (new SieveTemplate()).load("./accounts/account.capabilities.tpl"));
-
- document.querySelector("#sieve-capabilities-server").textContent
- = capabilities.implementation;
- document.querySelector("#sieve-capabilities-version").textContent
- = capabilities.version;
- document.querySelector("#sieve-capabilities-sasl").textContent
- = Object.values(capabilities.sasl).join(" ");
- document.querySelector("#sieve-capabilities-extensions").textContent
- = Object.keys(capabilities.extensions).join(" ");
- document.querySelector("#sieve-capabilities-language").textContent
- = capabilities.language;
-
- await new Promise((resolve) => {
-
- const dialog = document.querySelector('#sieve-dialog-capabilities');
-
- const modal = new bootstrap.Modal(dialog);
- modal.show();
-
- dialog.addEventListener("hidden.bs.modal", () => {
- resolve();
-
- const elm = document.querySelector('#sieve-dialog-capabilities');
- elm.parentNode.removeChild(elm);
-
- modal.dispose();
- });
- });
-
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveCapabilities = SieveCapabilities;
- else
- exports.SieveCapabilities = SieveCapabilities;
-
-})(this);
diff --git a/src/common/managesieve.ui/accounts/SieveCapabilities.mjs b/src/common/managesieve.ui/accounts/SieveCapabilities.mjs
new file mode 100644
index 00000000..8d2c0c1d
--- /dev/null
+++ b/src/common/managesieve.ui/accounts/SieveCapabilities.mjs
@@ -0,0 +1,62 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+/**
+ * Implements a dialog which displays the accounts capabilities.
+ */
+class SieveCapabilities {
+
+ /**
+ * Shows the capability dialog.
+ *
+ * @param {object} capabilities
+ * the server's capabilities.
+ */
+ async show(capabilities) {
+
+ document.querySelector("#ctx").append(
+ await (new SieveTemplate()).load("./accounts/account.capabilities.html"));
+
+ document.querySelector("#sieve-capabilities-server").textContent
+ = capabilities.implementation;
+ document.querySelector("#sieve-capabilities-version").textContent
+ = capabilities.version;
+ document.querySelector("#sieve-capabilities-sasl").textContent
+ = Object.values(capabilities.sasl).join(" ");
+ document.querySelector("#sieve-capabilities-extensions").textContent
+ = Object.keys(capabilities.extensions).join(" ");
+ document.querySelector("#sieve-capabilities-language").textContent
+ = capabilities.language;
+
+ await new Promise((resolve) => {
+
+ const dialog = document.querySelector('#sieve-dialog-capabilities');
+
+ const modal = new bootstrap.Modal(dialog);
+ modal.show();
+
+ dialog.addEventListener("hidden.bs.modal", () => {
+ resolve();
+
+ const elm = document.querySelector('#sieve-dialog-capabilities');
+ elm.remove();
+
+ modal.dispose();
+ });
+ });
+
+ }
+}
+
+export { SieveCapabilities };
diff --git a/src/common/managesieve.ui/accounts/SieveScriptUI.tpl b/src/common/managesieve.ui/accounts/SieveScriptUI.html
index 296c5cc1..0788404d 100644
--- a/src/common/managesieve.ui/accounts/SieveScriptUI.tpl
+++ b/src/common/managesieve.ui/accounts/SieveScriptUI.html
@@ -3,11 +3,11 @@
<span class="sieve-list-script-name"></span>
<span class="sieve-list-script-active badge bg-success" data-i18n="account.script.active"></span>
</div>
- <div class="float-right">
- <button class="btn btn-sm btn-outline-secondary mr-1 sieve-script-deactivate" data-i18n="account.script.deactivate"></button>
- <button class="btn btn-sm btn-outline-secondary mr-1 sieve-script-activate" data-i18n="account.script.activate"></button>
- <button class="btn btn-sm btn-outline-secondary mr-1 sieve-script-rename" data-i18n="account.script.rename"></button>
- <button class="btn btn-sm btn-outline-secondary mr-1 sieve-script-delete" data-i18n="account.script.delete"></button>
- <button class="btn btn-sm btn-outline-secondary mr-1 sieve-script-edit" data-i18n="account.script.edit"></button>
+ <div class="float-end">
+ <button class="btn btn-sm btn-outline-secondary me-1 sieve-script-deactivate" data-i18n="account.script.deactivate"></button>
+ <button class="btn btn-sm btn-outline-secondary me-1 sieve-script-activate" data-i18n="account.script.activate"></button>
+ <button class="btn btn-sm btn-outline-secondary me-1 sieve-script-rename" data-i18n="account.script.rename"></button>
+ <button class="btn btn-sm btn-outline-secondary me-1 sieve-script-delete" data-i18n="account.script.delete"></button>
+ <button class="btn btn-sm btn-outline-secondary me-1 sieve-script-edit" data-i18n="account.script.edit"></button>
</div>
</li> \ No newline at end of file
diff --git a/src/common/managesieve.ui/accounts/SieveScriptUI.js b/src/common/managesieve.ui/accounts/SieveScriptUI.js
deleted file mode 100644
index c4b190c3..00000000
--- a/src/common/managesieve.ui/accounts/SieveScriptUI.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- /* global SieveTemplate */
-
- "use strict";
-
- const HEX = 16;
- const HEX_OFFSET = -2;
-
- /**
- * An UI elements which handles displaying details for a sieve script.
- * It does not provide any support for editing the scripts content.
- */
- class SieveScriptUI {
-
- /**
- * Creates a new instance
- * @param {SieveAccount} account
- * the account UI element which owns this script
- * @param {string} name
- * the scripts name
- * @param {boolean} active
- * indicates if the script should be rendered as active.
- */
- constructor(account, name, active) {
- this.name = name;
- this.isActive = active;
- this.account = account;
- }
-
- /**
- * Returns a unique id for this script.
- * It is the account id concatenated to the script name in hex.
- *
- * @returns {string}
- * the unique id which identifies this element..
- */
- getId() {
- // Convert the name into hex to escape dangerous characters.
- let str = "";
- for (let i = 0; i < this.name.length; i++)
- str += ("0" + this.name.charCodeAt(i).toString(HEX)).slice(HEX_OFFSET);
-
- return `${this.account.id}-${str}`;
- }
-
-
- /**
- * Renders the UI element into the dom.
- */
- async render() {
-
- const id = this.getId();
-
- let elm = document.querySelector(`#siv-script-${id}`);
- // Check if the element exists...
- if (!elm) {
-
- elm = await (new SieveTemplate()).load("./accounts/SieveScriptUI.tpl");
-
- elm.id = `siv-script-${id}`;
-
- document
- .querySelector(`#siv-account-${this.account.id} .siv-tpl-scripts`)
- .appendChild(elm);
-
- elm.querySelector(".sieve-list-script-name").textContent = this.name;
-
- elm.querySelector(".sieve-script-rename")
- .addEventListener("click", () => { this.rename(); });
- elm.querySelector(".sieve-script-delete")
- .addEventListener("click", () => { this.remove(); });
- elm.querySelector(".sieve-script-edit")
- .addEventListener("click", () => { this.edit(); });
- elm.querySelector(".sieve-script-activate")
- .addEventListener("click", () => { this.activate(); });
- elm.querySelector(".sieve-script-deactivate")
- .addEventListener("click", () => { this.deactivate(); });
- }
-
- if (this.isActive === false) {
- elm.querySelector(".sieve-list-script-active").classList.add("d-none");
- elm.querySelector(".sieve-script-activate").classList.remove("d-none");
- elm.querySelector(".sieve-script-deactivate").classList.add("d-none");
- }
- else {
- elm.querySelector(".sieve-list-script-active").classList.remove("d-none");
- elm.querySelector(".sieve-script-activate").classList.add("d-none");
- elm.querySelector(".sieve-script-deactivate").classList.remove("d-none");
- }
- }
-
- /**
- * Renames the script.
- * A prompt will be show which ask the user about the new name
- */
- async rename() {
-
- const rv = await this.account.send("script-rename", this.name);
- if (rv === true)
- this.account.render();
- }
-
- /**
- * Removes the script
- * A verification prompt will be shown before the script is deleted
- */
- async remove() {
- const rv = await this.account.send("script-delete", this.name);
- if (rv === true)
- await this.account.render();
- }
-
- /**
- * Open the script in a new tab. In order to edit it.
- */
- async edit() {
- await this.account.send("script-edit", this.name);
- }
-
- /**
- * Marks the script as active.
- */
- async activate() {
- await this.account.send("script-activate", this.name);
- await this.account.render();
- }
-
- /**
- * Marks the script as in active.
- */
- async deactivate() {
- await this.account.send("script-deactivate", this.name);
- await this.account.render();
- }
-
- }
-
-
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveScriptUI;
- else
- exports.SieveScriptUI = SieveScriptUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/accounts/SieveScriptUI.mjs b/src/common/managesieve.ui/accounts/SieveScriptUI.mjs
new file mode 100644
index 00000000..c3c0618a
--- /dev/null
+++ b/src/common/managesieve.ui/accounts/SieveScriptUI.mjs
@@ -0,0 +1,146 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+const HEX = 16;
+const HEX_OFFSET = -2;
+
+/**
+ * An UI elements which handles displaying details for a sieve script.
+ * It does not provide any support for editing the scripts content.
+ */
+class SieveScriptUI {
+
+ /**
+ * Creates a new instance
+ * @param {SieveAccount} account
+ * the account UI element which owns this script
+ * @param {string} name
+ * the scripts name
+ * @param {boolean} active
+ * indicates if the script should be rendered as active.
+ */
+ constructor(account, name, active) {
+ this.name = name;
+ this.isActive = active;
+ this.account = account;
+ }
+
+ /**
+ * Returns a unique id for this script.
+ * It is the account id concatenated to the script name in hex.
+ *
+ * @returns {string}
+ * the unique id which identifies this element..
+ */
+ getId() {
+ // Convert the name into hex to escape dangerous characters.
+ let str = "";
+ for (let i = 0; i < this.name.length; i++)
+ str += ("0" + this.name.charCodeAt(i).toString(HEX)).slice(HEX_OFFSET);
+
+ return `${this.account.id}-${str}`;
+ }
+
+
+ /**
+ * Renders the UI element into the dom.
+ */
+ async render() {
+
+ const id = this.getId();
+
+ let elm = document.querySelector(`#siv-script-${id}`);
+ // Check if the element exists...
+ if (!elm) {
+
+ elm = await (new SieveTemplate()).load("./accounts/SieveScriptUI.html");
+
+ elm.id = `siv-script-${id}`;
+
+ document
+ .querySelector(`#siv-account-${this.account.id} .siv-tpl-scripts`)
+ .append(elm);
+
+ elm.querySelector(".sieve-list-script-name").textContent = this.name;
+
+ elm.querySelector(".sieve-script-rename")
+ .addEventListener("click", () => { this.rename(); });
+ elm.querySelector(".sieve-script-delete")
+ .addEventListener("click", () => { this.remove(); });
+ elm.querySelector(".sieve-script-edit")
+ .addEventListener("click", () => { this.edit(); });
+ elm.querySelector(".sieve-script-activate")
+ .addEventListener("click", () => { this.activate(); });
+ elm.querySelector(".sieve-script-deactivate")
+ .addEventListener("click", () => { this.deactivate(); });
+ }
+
+ if (this.isActive === false) {
+ elm.querySelector(".sieve-list-script-active").classList.add("d-none");
+ elm.querySelector(".sieve-script-activate").classList.remove("d-none");
+ elm.querySelector(".sieve-script-deactivate").classList.add("d-none");
+ }
+ else {
+ elm.querySelector(".sieve-list-script-active").classList.remove("d-none");
+ elm.querySelector(".sieve-script-activate").classList.add("d-none");
+ elm.querySelector(".sieve-script-deactivate").classList.remove("d-none");
+ }
+ }
+
+ /**
+ * Renames the script.
+ * A prompt will be show which ask the user about the new name
+ */
+ async rename() {
+
+ const rv = await this.account.send("script-rename", this.name);
+ if (rv === true)
+ this.account.render();
+ }
+
+ /**
+ * Removes the script
+ * A verification prompt will be shown before the script is deleted
+ */
+ async remove() {
+ const rv = await this.account.send("script-delete", this.name);
+ if (rv === true)
+ await this.account.render();
+ }
+
+ /**
+ * Open the script in a new tab. In order to edit it.
+ */
+ async edit() {
+ await this.account.send("script-edit", this.name);
+ }
+
+ /**
+ * Marks the script as active.
+ */
+ async activate() {
+ await this.account.send("script-activate", this.name);
+ await this.account.render();
+ }
+
+ /**
+ * Marks the script as in active.
+ */
+ async deactivate() {
+ await this.account.send("script-deactivate", this.name);
+ await this.account.render();
+ }
+
+}
+
+export { SieveScriptUI };
diff --git a/src/common/managesieve.ui/accounts/account.capabilities.tpl b/src/common/managesieve.ui/accounts/account.capabilities.html
index dc21bdd4..56396de3 100644
--- a/src/common/managesieve.ui/accounts/account.capabilities.tpl
+++ b/src/common/managesieve.ui/accounts/account.capabilities.html
@@ -3,7 +3,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="account.capabilities.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table class="table-sm">
diff --git a/src/common/managesieve.ui/accounts/account.connecting.tpl b/src/common/managesieve.ui/accounts/account.connecting.html
index aed186da..aed186da 100644
--- a/src/common/managesieve.ui/accounts/account.connecting.tpl
+++ b/src/common/managesieve.ui/accounts/account.connecting.html
diff --git a/src/common/managesieve.ui/accounts/account.disconnected.tpl b/src/common/managesieve.ui/accounts/account.disconnected.html
index 92febd67..efd4cf46 100644
--- a/src/common/managesieve.ui/accounts/account.disconnected.tpl
+++ b/src/common/managesieve.ui/accounts/account.disconnected.html
@@ -3,5 +3,5 @@
<h4 class="card-title" data-i18n="account.disconnected.title"></h4>
<p class="card-text" data-i18n="account.disconnected.description"></p>
<button data-i18n="account.connect"
- type="button" class="btn btn-outline-primary mr-1 sieve-script-connect"></button>
+ type="button" class="btn btn-outline-primary me-1 sieve-script-connect"></button>
</div>
diff --git a/src/common/managesieve.ui/accounts/account.disconnecting.tpl b/src/common/managesieve.ui/accounts/account.disconnecting.html
index d88d2539..d88d2539 100644
--- a/src/common/managesieve.ui/accounts/account.disconnecting.tpl
+++ b/src/common/managesieve.ui/accounts/account.disconnecting.html
diff --git a/src/common/managesieve.ui/accounts/account.empty.html b/src/common/managesieve.ui/accounts/account.empty.html
index 52fdc75d..02f4434c 100644
--- a/src/common/managesieve.ui/accounts/account.empty.html
+++ b/src/common/managesieve.ui/accounts/account.empty.html
@@ -2,5 +2,5 @@
<h4 class="card-title" data-i18n="account.empty.title"></h4>
<p class="card-text" data-i18n="account.empty.description"></p>
<button data-i18n="account.empty.create"
- type="button" class="btn btn-outline-primary mr-1 sieve-script-empty-create"></button>
+ type="button" class="btn btn-outline-primary me-1 sieve-script-empty-create"></button>
</div> \ No newline at end of file
diff --git a/src/common/managesieve.ui/accounts/account.tpl b/src/common/managesieve.ui/accounts/account.html
index 5f67a1d3..e966e2df 100644
--- a/src/common/managesieve.ui/accounts/account.tpl
+++ b/src/common/managesieve.ui/accounts/account.html
@@ -4,26 +4,26 @@
<div class="card-header d-flex justify-content-between py-0">
<ul class="nav nav-tabs card-header-tabs my-0 pt-3" role="tablist">
<li class="nav-item">
- <a class="sieve-accounts-tab nav-link active" data-toggle="tab" role="tab">
+ <a class="sieve-accounts-tab nav-link active" data-bs-toggle="tab" role="tab">
<span class="siv-account-name"></span>
</a>
</li>
<li class="nav-item">
- <a class="sieve-settings-tab nav-link" data-i18n="account.settings" data-toggle="tab" role="tab"></a>
+ <a class="sieve-settings-tab nav-link" data-i18n="account.settings" data-bs-toggle="tab" role="tab"></a>
</li>
</ul>
<div class="align-self-center">
- <button type="button" class="btn btn-sm btn-outline-secondary mr-1 siv-account-create" data-i18n="account.newscript"></button>
- <a class="btn btn-sm btn-outline-info mr-1 "
+ <button type="button" class="btn btn-sm btn-outline-secondary me-1 siv-account-create" data-i18n="account.newscript"></button>
+ <a class="btn btn-sm btn-outline-info me-1 "
href="https://www.paypal.com/paypalme2/thsmi"
target="_blank" role="button" data-i18n="account.donate"></a>
<div id="sieve-editor-settings" class="btn-group dropdown">
- <button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
+ <button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
</button>
- <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
+ <div class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item sieve-account-edit-settings" data-i18n="account.settings.edit"></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item sieve-account-disconnect-server" data-i18n="account.disconnect"></a>
diff --git a/src/common/managesieve.ui/dialogs/SieveDialogUI.js b/src/common/managesieve.ui/dialogs/SieveDialogUI.js
deleted file mode 100644
index a517723d..00000000
--- a/src/common/managesieve.ui/dialogs/SieveDialogUI.js
+++ /dev/null
@@ -1,706 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const KEY_RETURN = 13;
-
- const DIALOG_CANCELED = 0;
- const DIALOG_ACCEPTED = 1;
- const DIALOG_DISCARDED = 2;
-
- /* global bootstrap */
- const { SieveTemplate } = require("./../utils/SieveTemplate.js");
- const { SieveUniqueId } = require("./../utils/SieveUniqueId.js");
-
- /**
- * Displays a simple dialog with an action button.
- */
- class SieveDialog {
-
- /**
- * The path to the html template which should be used for this dialog
- * @abstract
- *
- * @returns {string}
- * the path to the html template.
- */
- getTemplate() {
- throw new Error("Implement getTemplate");
- }
-
- /**
- * Get the current dialogs root element
- *
- * @returns {HTMLElement}
- * the dialogs root element.
- */
- getDialog() {
- return document.querySelector(`#${this.id}`);
- }
-
- /**
- * Called when the dialog is initialized.
- * It is used to populate it with html elements.
- *
- * You can use getDialog to retrieve the dialogs root element.
- * E.g. when adding new elements.
- *
- */
- onInit() {
- throw new Error("Implement on Init");
- }
-
- /**
- * Called when the user clicks the accept button.
- * Which is any button marked with the "sieve-dialog-resolve" class.
- *
- * The result should be typically true for simple dialogs or
- * with more complex dialogs just return the desired value.
- *
- * @returns {object}
- * returns true or the accept value
- *
- */
- onAccept() {
- return true;
- }
-
- /**
- * Called when the user clicks the cancel button.
- * Which is any button marked with the "sieve-dialog-reject" class.
- *
- * The result should be false for simple dialogs or a default value for
- * more complex dialogs. In case there is no default value, the best strategy
- * is to throw an exception.
- *
- * @returns {object}
- * returns false or the reject result.
- */
- onCancel() {
- return false;
- }
-
- /**
- * Called when the dialog is shown.
- * This can be used e.g. to move the focus to the desired text box.
- */
- onShown() {
- }
-
- /**
- * Generates an if made of alphanumerical characters and dashes.
- * @returns {string}
- * a string with an html compatible unique id
- */
- generateId() {
- return (new SieveUniqueId()).generate();
- }
-
- /**
- * Removes the dialog window from the UI.
- */
- destroy() {
- const elm = this.getDialog();
- elm.parentNode.removeChild(elm);
- }
-
- /**
- * Shows the dialog.
- *
- * @returns {object}
- * the value returned from the dialog.
- */
- async show() {
-
- this.id = this.generateId();
-
- const dialog = await (new SieveTemplate()).load(this.getTemplate());
- dialog.id = this.id;
- document.querySelector("#ctx").appendChild(dialog);
-
- this.onInit();
-
- return await new Promise((resolve, reject) => {
-
- const modal = new bootstrap.Modal(this.getDialog());
-
- const buttons = this.getDialog()
- .querySelectorAll(".sieve-dialog-resolve");
-
- for (const button of buttons) {
- button.addEventListener("click", async () => {
- try {
- resolve(await this.onAccept(event.target));
- } catch (ex) {
- reject(ex);
- }
-
- modal.hide();
- });
- }
-
-
- modal.show();
-
- this.getDialog().addEventListener('hidden.bs.modal', async () => {
-
- this.destroy();
-
- try {
- resolve(await this.onCancel());
- } catch (ex) {
- reject(ex);
- }
- });
-
- this.getDialog().addEventListener('shown.bs.modal', () => {
- this.onShown();
- });
- });
- }
- }
-
- /**
- * Prompts if the given account shall be deleted.
- */
- class SieveDeleteAccountDialog extends SieveDialog {
-
- /**
- * Creates a new dialog instance
- *
- * @param {string} displayName
- * the accounts display name
- */
- constructor(displayName) {
- super();
- this.displayName = displayName;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.account.delete.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector("#sieve-dialog-account-remove-name")
- .textContent = this.displayName;
- }
- }
-
- /**
- * Shows the dialog asking for the server's fingerprint.
- */
- class SieveFingerprintDialog extends SieveDialog {
-
- /**
- * Creates a new instance
- *
- * @param {object} secInfo
- * the security info object with details about the validation error.
- */
- constructor(secInfo) {
- super();
- this.fingerprint = secInfo.fingerprint;
- this.error = secInfo.message;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.account.cert.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector(".sieve-dialog-fingerprint")
- .textContent = this.fingerprint;
-
- this.getDialog()
- .querySelector(".sieve-dialog-certerror")
- .textContent = this.error;
- }
- }
-
- /**
- * Aks if the given script should be deleted or not.
- */
- class SieveDeleteScriptDialog extends SieveDialog {
-
- /**
- * Create a new delete script dialog instance.
- *
- * @param {string} name
- * the script name as string
- */
- constructor(name) {
- super();
- this.name = name;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.script.delete.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector('.sieve-delete-dialog-name')
- .textContent = this.name;
- }
- }
-
- /**
- * Asks for a name, which should be used to create the new script.
- */
- class SieveCreateScriptDialog extends SieveDialog {
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.script.create.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog().querySelector('.sieve-create-dialog-name').addEventListener("keypress", (e) => {
- if (e.which === KEY_RETURN) {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, false);
- this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
- }
- });
- }
-
- /**
- * @inheritdoc
- */
- onShown() {
- this.getDialog().querySelector('.sieve-create-dialog-name').focus();
- }
-
- /**
- * @inheritdoc
- */
- onAccept() {
- return this.getDialog().querySelector('.sieve-create-dialog-name').value;
- }
-
- /**
- * @inheritdoc
- */
- onCancel() {
- return "";
- }
- }
-
- /**
- * Asks for a scripts new name
- */
- class SieveRenameScriptDialog extends SieveDialog {
-
- /**
- * Create a rename script dialog instance.
- * @param {string} name
- * the scripts old name
- */
- constructor(name) {
- super();
- this.name = name;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.script.rename.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog().querySelector('.sieve-rename-dialog-newname').addEventListener("keypress", (e) => {
- if (e.which === KEY_RETURN) {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, false);
- this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
- }
- });
-
- this.getDialog()
- .querySelector(".sieve-rename-dialog-newname").value = this.name;
- }
-
- /**
- * @inheritdoc
- */
- onShown() {
- this.getDialog().querySelector('.sieve-rename-dialog-newname').focus();
- }
-
- /**
- * @inheritdoc
- */
- onAccept() {
- return this.getDialog()
- .querySelector(".sieve-rename-dialog-newname").value;
- }
-
- /**
- * @inheritdoc
- */
- onCancel() {
- return this.name;
- }
- }
-
- /**
- * Asks for a password.
- * The show method returns null or the authorization.
- */
- class SievePasswordDialog extends SieveDialog {
-
- /**
- * Creates a password dialog.
- *
- * @param {string} username
- * the username for which the password is requested
- * @param {string} displayName
- * the accounts display name.
- * @param {{ remember : boolean }} [options]
- * extended additional options.
- * In case "remember" is set to true a switch will be rendered which allows
- * the user to select if the password should be stored.
- */
- constructor(username, displayName, options) {
- super();
- this.username = username;
- this.displayName = displayName;
-
- if (typeof(options) === "undefined" || options === null)
- options = {};
-
- this.options = options;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.account.password.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- const dialog = this.getDialog();
-
- if (!this.options.remember)
- dialog.querySelector(".sieve-password-remember").style.display = "none";
-
- dialog.querySelector(".sieve-username").textContent = this.username;
- dialog.querySelector(".sieve-displayname").textContent = this.displayName;
-
- this.getDialog().querySelector('.sieve-password').addEventListener("keypress", (e) => {
- if (e.which === KEY_RETURN) {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, false);
- this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
- }
- });
- }
-
- /**
- * @inheritdoc
- */
- onShown() {
- this.getDialog().querySelector('.sieve-password').focus();
- }
-
- /**
- * @inheritdoc
- */
- onAccept() {
- return {
- "username" : this.username,
- "password" : this.getDialog().querySelector(".sieve-password").value,
- "remember" : document.querySelector("#sieve-password-remember").checked
- };
- }
-
- /**
- * @inheritdoc
- */
- onCancel() {
- return {};
- }
- }
-
- /**
- * Asks for a authorization.
- * The show method returns null or the authorization.
- */
- class SieveAuthorizationDialog extends SieveDialog {
-
- /**
- * Creates a authorization request dialog.
- *
- * @param {string} displayName
- * the account's display name.
- */
- constructor(displayName) {
- super();
- this.displayName = displayName;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.account.authorization.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- const dialog = this.getDialog();
-
- dialog.querySelector(".sieve-displayname").textContent = this.displayName;
-
- dialog.querySelector('.sieve-authorization').addEventListener("keypress", (e) => {
- if (e.which === KEY_RETURN) {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, false);
- this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
- }
- });
- }
-
- /**
- * @inheritdoc
- */
- onShown() {
- this.getDialog().querySelector('.sieve-authorization').focus();
- }
-
- /**
- * @inheritdoc
- */
- onAccept() {
- return this.getDialog().querySelector(".sieve-authorization").value;
- }
-
- /**
- * @inheritdoc
- */
- onCancel() {
- return null;
- }
- }
-
- /**
- * An info dialog indicating the current script is busy.
- */
- class SieveScriptBusyDialog extends SieveDialog {
-
- /**
- * Create a new script busy dialog.
- * @param {string} name
- * the scripts name.
- */
- constructor(name) {
- super();
- this.name = name;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.script.busy.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector(".sieve-busy-dialog-scriptname").textContent = this.name;
- }
- }
-
- // TODO should be extracted an stored next to the editor
- /**
- * Asks is a changed script should be saved
- */
- class SieveScriptSaveDialog extends SieveDialog {
-
- /**
- * Create a rename script dialog instance.
- * @param {string} name
- * the scripts old name
- */
- constructor(name) {
- super();
- this.name = name;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.script.save.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector(".sieve-save-dialog-scriptname").textContent = this.name;
- }
-
- /**
- * @inheritdoc
- */
- onAccept(element) {
- if (element.classList.contains("sieve-save-dialog-discard"))
- return DIALOG_DISCARDED;
-
- if (element.classList.contains("sieve-save-dialog-save"))
- return DIALOG_ACCEPTED;
-
- throw new Error("Unknown button pressed");
- }
-
- /**
- * Checks if the dialogs return value was a discard
- * @param {int} value
- * the value to be checked.
- * @returns {boolean}
- * true in case the dialog was discarded.
- */
- static isDiscarded(value) {
- return value === DIALOG_DISCARDED;
- }
-
- /**
- * Checks if the dialogs return value was a cancel
- * @param {int} value
- * the value to be checked.
- * @returns {boolean}
- * true in case the dialog was canceled
- */
- static isCanceled(value) {
- return value === DIALOG_CANCELED;
- }
-
- /**
- * Checks if the dialogs return value was an accept
- * @param {int} value
- * the value to be checked.
- * @returns {boolean}
- * true in case the dialog was accepted.
- */
- static isAccepted(value) {
- return value === DIALOG_ACCEPTED;
- }
-
- /**
- * @inheritdoc
- */
- onCancel() {
- return DIALOG_CANCELED;
- }
- }
-
- /**
- * An info dialog indicating the current script is busy.
- */
- class SieveErrorDialog extends SieveDialog {
-
- /**
- * Create a new script busy dialog.
- * @param {string} description
- * the scripts name.
- */
- constructor(description) {
- super();
- this.description = description;
- }
-
- /**
- * @inheritdoc
- */
- getTemplate() {
- return "./dialogs/dialog.error.tpl";
- }
-
- /**
- * @inheritdoc
- */
- onInit() {
- this.getDialog()
- .querySelector(".sieve-error-dialog-description")
- .textContent = this.description;
- }
- }
-
-
- if (typeof (module) !== "undefined" && module && module.exports) {
- module.exports.SievePasswordDialog = SievePasswordDialog;
- module.exports.SieveRenameScriptDialog = SieveRenameScriptDialog;
- module.exports.SieveCreateScriptDialog = SieveCreateScriptDialog;
- module.exports.SieveDeleteScriptDialog = SieveDeleteScriptDialog;
- module.exports.SieveFingerprintDialog = SieveFingerprintDialog;
- module.exports.SieveDeleteAccountDialog = SieveDeleteAccountDialog;
- module.exports.SieveAuthorizationDialog = SieveAuthorizationDialog;
- module.exports.SieveScriptBusyDialog = SieveScriptBusyDialog;
- module.exports.SieveScriptSaveDialog = SieveScriptSaveDialog;
- module.exports.SieveErrorDialog = SieveErrorDialog;
- }
- else {
- exports.SievePasswordDialog = SievePasswordDialog;
- exports.SieveRenameScriptDialog = SieveRenameScriptDialog;
- exports.SieveCreateScriptDialog = SieveCreateScriptDialog;
- exports.SieveDeleteScriptDialog = SieveDeleteScriptDialog;
- exports.SieveFingerprintDialog = SieveFingerprintDialog;
- exports.SieveDeleteAccountDialog = SieveDeleteAccountDialog;
- exports.SieveAuthorizationDialog = SieveAuthorizationDialog;
- exports.SieveScriptBusyDialog = SieveScriptBusyDialog;
- exports.SieveScriptSaveDialog = SieveScriptSaveDialog;
- exports.SieveErrorDialog = SieveErrorDialog;
- }
-
-})(this);
diff --git a/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs b/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs
new file mode 100644
index 00000000..8a8757c4
--- /dev/null
+++ b/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs
@@ -0,0 +1,692 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const DIALOG_CANCELED = 0;
+const DIALOG_ACCEPTED = 1;
+const DIALOG_DISCARDED = 2;
+
+/* global bootstrap */
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+import { SieveUniqueId } from "./../utils/SieveUniqueId.mjs";
+
+/**
+ * Displays a simple dialog with an action button.
+ */
+class SieveDialog {
+
+ /**
+ * The path to the html template which should be used for this dialog
+ * @abstract
+ *
+ * @returns {string}
+ * the path to the html template.
+ */
+ getTemplate() {
+ throw new Error("Implement getTemplate");
+ }
+
+ /**
+ * Get the current dialogs root element
+ *
+ * @returns {HTMLElement}
+ * the dialogs root element.
+ */
+ getDialog() {
+ return document.querySelector(`#${this.id}`);
+ }
+
+ /**
+ * Called when the dialog is initialized.
+ * It is used to populate it with html elements.
+ *
+ * You can use getDialog to retrieve the dialogs root element.
+ * E.g. when adding new elements.
+ *
+ */
+ onInit() {
+ throw new Error("Implement on Init");
+ }
+
+ /**
+ * Called when the user clicks the accept button.
+ * Which is any button marked with the "sieve-dialog-resolve" class.
+ *
+ * The result should be typically true for simple dialogs or
+ * with more complex dialogs just return the desired value.
+ *
+ * @returns {object}
+ * returns true or the accept value
+ *
+ */
+ onAccept() {
+ return true;
+ }
+
+ /**
+ * Called when the user clicks the cancel button.
+ * Which is any button marked with the "sieve-dialog-reject" class.
+ *
+ * The result should be false for simple dialogs or a default value for
+ * more complex dialogs. In case there is no default value, the best strategy
+ * is to throw an exception.
+ *
+ * @returns {object}
+ * returns false or the reject result.
+ */
+ onCancel() {
+ return false;
+ }
+
+ /**
+ * Called when the dialog is shown.
+ * This can be used e.g. to move the focus to the desired text box.
+ */
+ onShown() {
+ }
+
+ /**
+ * Generates an if made of alphanumerical characters and dashes.
+ * @returns {string}
+ * a string with an html compatible unique id
+ */
+ generateId() {
+ return (new SieveUniqueId()).generate();
+ }
+
+ /**
+ * Removes the dialog window from the UI.
+ */
+ destroy() {
+ const elm = this.getDialog();
+ elm.remove();
+ }
+
+ /**
+ * Shows the dialog.
+ *
+ * @returns {object}
+ * the value returned from the dialog.
+ */
+ async show() {
+
+ this.id = this.generateId();
+
+ const dialog = await (new SieveTemplate()).load(this.getTemplate());
+ dialog.id = this.id;
+ document.querySelector("#ctx").append(dialog);
+
+ this.onInit();
+
+ return await new Promise((resolve, reject) => {
+
+ const modal = new bootstrap.Modal(this.getDialog());
+
+ const buttons = this.getDialog()
+ .querySelectorAll(".sieve-dialog-resolve");
+
+ for (const button of buttons) {
+ button.addEventListener("click", async () => {
+ try {
+ resolve(await this.onAccept(event.target));
+ } catch (ex) {
+ reject(ex);
+ }
+
+ modal.hide();
+ });
+ }
+
+
+ modal.show();
+
+ this.getDialog().addEventListener('hidden.bs.modal', async () => {
+
+ this.destroy();
+
+ try {
+ resolve(await this.onCancel());
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+
+ this.getDialog().addEventListener('shown.bs.modal', () => {
+ this.onShown();
+ });
+ });
+ }
+}
+
+/**
+ * Prompts if the given account shall be deleted.
+ */
+class SieveDeleteAccountDialog extends SieveDialog {
+
+ /**
+ * Creates a new dialog instance
+ *
+ * @param {string} displayName
+ * the accounts display name
+ */
+ constructor(displayName) {
+ super();
+ this.displayName = displayName;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.account.delete.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector("#sieve-dialog-account-remove-name")
+ .textContent = this.displayName;
+ }
+}
+
+/**
+ * Shows the dialog asking for the server's fingerprint.
+ */
+class SieveFingerprintDialog extends SieveDialog {
+
+ /**
+ * Creates a new instance
+ *
+ * @param {object} secInfo
+ * the security info object with details about the validation error.
+ */
+ constructor(secInfo) {
+ super();
+ this.fingerprint = secInfo.fingerprint;
+ this.fingerprint256 = secInfo.fingerprint256;
+ this.error = secInfo.message;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.account.cert.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector(".sieve-dialog-fingerprint")
+ .textContent = this.fingerprint;
+
+ if ((typeof (this.fingerprint256) !== "undefined") && (this.fingerprint256 !== null)) {
+ this.getDialog()
+ .querySelector(".sieve-dialog-fingerprint256")
+ .textContent = this.fingerprint256;
+ }
+
+ this.getDialog()
+ .querySelector(".sieve-dialog-certerror")
+ .textContent = this.error;
+ }
+}
+
+/**
+ * Aks if the given script should be deleted or not.
+ */
+class SieveDeleteScriptDialog extends SieveDialog {
+
+ /**
+ * Create a new delete script dialog instance.
+ *
+ * @param {string} name
+ * the script name as string
+ */
+ constructor(name) {
+ super();
+ this.name = name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.script.delete.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector('.sieve-delete-dialog-name')
+ .textContent = this.name;
+ }
+}
+
+/**
+ * Asks for a name, which should be used to create the new script.
+ */
+class SieveCreateScriptDialog extends SieveDialog {
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.script.create.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog().querySelector('.sieve-create-dialog-name').addEventListener("keypress", (e) => {
+ if (e.key === "Enter") {
+ const event = document.createEvent('HTMLEvents');
+ event.initEvent('click', true, false);
+ this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
+ }
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onShown() {
+ this.getDialog().querySelector('.sieve-create-dialog-name').focus();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onAccept() {
+ return this.getDialog().querySelector('.sieve-create-dialog-name').value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onCancel() {
+ return "";
+ }
+}
+
+/**
+ * Asks for a scripts new name
+ */
+class SieveRenameScriptDialog extends SieveDialog {
+
+ /**
+ * Create a rename script dialog instance.
+ * @param {string} name
+ * the scripts old name
+ */
+ constructor(name) {
+ super();
+ this.name = name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.script.rename.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog().querySelector('.sieve-rename-dialog-newname').addEventListener("keypress", (e) => {
+ if (e.key === "Enter") {
+ const event = document.createEvent('HTMLEvents');
+ event.initEvent('click', true, false);
+ this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
+ }
+ });
+
+ this.getDialog()
+ .querySelector(".sieve-rename-dialog-newname").value = this.name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onShown() {
+ this.getDialog().querySelector('.sieve-rename-dialog-newname').focus();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onAccept() {
+ return this.getDialog()
+ .querySelector(".sieve-rename-dialog-newname").value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onCancel() {
+ return this.name;
+ }
+}
+
+/**
+ * Asks for a password.
+ * The show method returns null or the authorization.
+ */
+class SievePasswordDialog extends SieveDialog {
+
+ /**
+ * Creates a password dialog.
+ *
+ * @param {string} username
+ * the username for which the password is requested
+ * @param {string} displayName
+ * the accounts display name.
+ * @param {{ remember : boolean }} [options]
+ * extended additional options.
+ * In case "remember" is set to true a switch will be rendered which allows
+ * the user to select if the password should be stored.
+ */
+ constructor(username, displayName, options) {
+ super();
+ this.username = username;
+ this.displayName = displayName;
+
+ if (typeof (options) === "undefined" || options === null)
+ options = {};
+
+ this.options = options;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.account.password.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ const dialog = this.getDialog();
+
+ if (!this.options.remember)
+ dialog.querySelector(".sieve-password-remember").style.display = "none";
+
+ dialog.querySelector(".sieve-username").textContent = this.username;
+ dialog.querySelector(".sieve-displayname").textContent = this.displayName;
+
+ this.getDialog().querySelector('.sieve-password').addEventListener("keypress", (e) => {
+ if (e.key === "Enter") {
+ const event = document.createEvent('HTMLEvents');
+ event.initEvent('click', true, false);
+ this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
+ }
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onShown() {
+ this.getDialog().querySelector('.sieve-password').focus();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onAccept() {
+ return {
+ "username": this.username,
+ "password": this.getDialog().querySelector(".sieve-password").value,
+ "remember": document.querySelector("#sieve-password-remember").checked
+ };
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onCancel() {
+ return {};
+ }
+}
+
+/**
+ * Asks for a authorization.
+ * The show method returns null or the authorization.
+ */
+class SieveAuthorizationDialog extends SieveDialog {
+
+ /**
+ * Creates a authorization request dialog.
+ *
+ * @param {string} displayName
+ * the account's display name.
+ */
+ constructor(displayName) {
+ super();
+ this.displayName = displayName;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.account.authorization.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ const dialog = this.getDialog();
+
+ dialog.querySelector(".sieve-displayname").textContent = this.displayName;
+
+ dialog.querySelector('.sieve-authorization').addEventListener("keypress", (e) => {
+ if (e.key === "Enter") {
+ const event = document.createEvent('HTMLEvents');
+ event.initEvent('click', true, false);
+ this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event);
+ }
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onShown() {
+ this.getDialog().querySelector('.sieve-authorization').focus();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onAccept() {
+ return this.getDialog().querySelector(".sieve-authorization").value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onCancel() {
+ return null;
+ }
+}
+
+/**
+ * An info dialog indicating the current script is busy.
+ */
+class SieveScriptBusyDialog extends SieveDialog {
+
+ /**
+ * Create a new script busy dialog.
+ * @param {string} name
+ * the scripts name.
+ */
+ constructor(name) {
+ super();
+ this.name = name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.script.busy.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector(".sieve-busy-dialog-scriptname").textContent = this.name;
+ }
+}
+
+// TODO should be extracted an stored next to the editor
+/**
+ * Asks is a changed script should be saved
+ */
+class SieveScriptSaveDialog extends SieveDialog {
+
+ /**
+ * Create a rename script dialog instance.
+ * @param {string} name
+ * the scripts old name
+ */
+ constructor(name) {
+ super();
+ this.name = name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.script.save.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector(".sieve-save-dialog-scriptname").textContent = this.name;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onAccept(element) {
+ if (element.classList.contains("sieve-save-dialog-discard"))
+ return DIALOG_DISCARDED;
+
+ if (element.classList.contains("sieve-save-dialog-save"))
+ return DIALOG_ACCEPTED;
+
+ throw new Error("Unknown button pressed");
+ }
+
+ /**
+ * Checks if the dialogs return value was a discard
+ * @param {int} value
+ * the value to be checked.
+ * @returns {boolean}
+ * true in case the dialog was discarded.
+ */
+ static isDiscarded(value) {
+ return value === DIALOG_DISCARDED;
+ }
+
+ /**
+ * Checks if the dialogs return value was a cancel
+ * @param {int} value
+ * the value to be checked.
+ * @returns {boolean}
+ * true in case the dialog was canceled
+ */
+ static isCanceled(value) {
+ return value === DIALOG_CANCELED;
+ }
+
+ /**
+ * Checks if the dialogs return value was an accept
+ * @param {int} value
+ * the value to be checked.
+ * @returns {boolean}
+ * true in case the dialog was accepted.
+ */
+ static isAccepted(value) {
+ return value === DIALOG_ACCEPTED;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onCancel() {
+ return DIALOG_CANCELED;
+ }
+}
+
+/**
+ * An info dialog indicating the current script is busy.
+ */
+class SieveErrorDialog extends SieveDialog {
+
+ /**
+ * Create a new script busy dialog.
+ * @param {string} description
+ * the scripts name.
+ */
+ constructor(description) {
+ super();
+ this.description = description;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getTemplate() {
+ return "./dialogs/dialog.error.html";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onInit() {
+ this.getDialog()
+ .querySelector(".sieve-error-dialog-description")
+ .textContent = this.description;
+ }
+}
+
+export {
+ SievePasswordDialog,
+ SieveRenameScriptDialog,
+ SieveCreateScriptDialog,
+ SieveDeleteScriptDialog,
+ SieveFingerprintDialog,
+ SieveDeleteAccountDialog,
+ SieveAuthorizationDialog,
+ SieveScriptBusyDialog,
+ SieveScriptSaveDialog,
+ SieveErrorDialog
+};
diff --git a/src/common/managesieve.ui/dialogs/dialog.account.authorization.tpl b/src/common/managesieve.ui/dialogs/dialog.account.authorization.html
index e0842545..278ec304 100644
--- a/src/common/managesieve.ui/dialogs/dialog.account.authorization.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.account.authorization.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 data-i18n="authorization.title" class="modal-title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3 row">
diff --git a/src/common/managesieve.ui/dialogs/dialog.account.cert.tpl b/src/common/managesieve.ui/dialogs/dialog.account.cert.html
index a4a8c464..bbe709cc 100644
--- a/src/common/managesieve.ui/dialogs/dialog.account.cert.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.account.cert.html
@@ -1,24 +1,23 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog" role="document">
+ <div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="cert.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
- <p data-i18n="cert.description1"></p>
- <div class="alert alert-danger" role="alert">
- <p data-i18n="cert.description2"></p>
+ <b><p data-i18n="cert.description1"></p></b>
- <p data-i18n="cert.error"></p>
- <p class="sieve-dialog-certerror"></p>
+ <div class="alert alert-danger" role="alert">
+ <b><p class="sieve-dialog-certerror"></p></b>
- <p data-i18n="cert.fingerprint"></p>
- <p class="sieve-dialog-fingerprint"></p>
+ <p class="sieve-dialog-fingerprint text-break"></p>
+ <p class="sieve-dialog-fingerprint256 text-break"></p>
</div>
<p data-i18n="cert.warning"></p>
+ <p data-i18n="cert.warning2"></p>
</div>
<div class="modal-footer">
<button data-i18n="cert.accept" type="button" class="btn btn-danger sieve-dialog-resolve"></button>
diff --git a/src/common/managesieve.ui/dialogs/dialog.account.delete.tpl b/src/common/managesieve.ui/dialogs/dialog.account.delete.html
index 1b2b6cb9..7a5aab29 100644
--- a/src/common/managesieve.ui/dialogs/dialog.account.delete.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.account.delete.html
@@ -3,7 +3,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="account.delete.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.account.password.tpl b/src/common/managesieve.ui/dialogs/dialog.account.password.html
index 6b70892a..fab5aeac 100644
--- a/src/common/managesieve.ui/dialogs/dialog.account.password.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.account.password.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="password.dialog.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3 row">
@@ -26,7 +26,7 @@
</div>
<div class="mb-3 row sieve-password-remember">
<label class="col-sm-3 col-form-label"></label>
- <div class="col-sm-8 ml-3 custom-control custom-switch">
+ <div class="col-sm-8 ms-3 custom-control custom-switch">
<input type="checkbox" class="form-check-input" id="sieve-password-remember">
<label class="form-check-label" for="sieve-password-remember" data-i18n="password.dialog.remember"></label>
</div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.error.tpl b/src/common/managesieve.ui/dialogs/dialog.error.html
index 9580d0aa..8855df68 100644
--- a/src/common/managesieve.ui/dialogs/dialog.error.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.error.html
@@ -3,7 +3,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="account.error.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div data-i18n="account.error.description"></div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.script.busy.tpl b/src/common/managesieve.ui/dialogs/dialog.script.busy.html
index 1e0049e8..28d74bc6 100644
--- a/src/common/managesieve.ui/dialogs/dialog.script.busy.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.script.busy.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="script.busy.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.script.create.tpl b/src/common/managesieve.ui/dialogs/dialog.script.create.html
index 8478d062..ec291dd0 100644
--- a/src/common/managesieve.ui/dialogs/dialog.script.create.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.script.create.html
@@ -3,7 +3,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="script.create.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div data-i18n="script.create.description"></div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.script.delete.tpl b/src/common/managesieve.ui/dialogs/dialog.script.delete.html
index db678a7e..f83b680f 100644
--- a/src/common/managesieve.ui/dialogs/dialog.script.delete.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.script.delete.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="script.delete.title" id="deleteScriptLabel"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.script.rename.tpl b/src/common/managesieve.ui/dialogs/dialog.script.rename.html
index e4a0f8ea..8a2ff06c 100644
--- a/src/common/managesieve.ui/dialogs/dialog.script.rename.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.script.rename.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 data-i18n="script.rename.title" class="modal-title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div data-i18n="script.rename.description"></div>
diff --git a/src/common/managesieve.ui/dialogs/dialog.script.save.tpl b/src/common/managesieve.ui/dialogs/dialog.script.save.html
index 1a7ede49..a1b47741 100644
--- a/src/common/managesieve.ui/dialogs/dialog.script.save.tpl
+++ b/src/common/managesieve.ui/dialogs/dialog.script.save.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="editor.save.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
diff --git a/src/common/managesieve.ui/editor.html b/src/common/managesieve.ui/editor.html
index 89e9d3d0..5e05ff87 100644
--- a/src/common/managesieve.ui/editor.html
+++ b/src/common/managesieve.ui/editor.html
@@ -72,25 +72,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="./../../libs/bootstrap/js/bootstrap.bundle.min.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveFakeRequire.js"></script>
-
- <script src="./../../libs/managesieve.ui/utils/SieveLogger.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveUniqueId.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveAbstractIpcClient.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveIpcClient.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveI18n.js"></script>
- <script src="./../../libs/managesieve.ui/utils/SieveTemplate.js"></script>
-
- <script src="./../../libs/managesieve.ui/editor/SieveAbstractEditor.js"></script>
- <script src="./../../libs/managesieve.ui/editor/SieveEditorController.js"></script>
- <script src="./../../libs/managesieve.ui/editor/graphical/SieveGraphicalEditor.js"></script>
- <script src="./../../libs/managesieve.ui/editor/text/SieveTextEditor.js"></script>
-
- <script src="./dialogs/SieveDialogUI.js"></script>
-
- <script src="./editor/SieveEditor.js"></script>
-
- <script src="editor.js"></script>
+ <script type="module" src="editor.mjs"></script>
</body>
diff --git a/src/common/managesieve.ui/editor.js b/src/common/managesieve.ui/editor.js
deleted file mode 100644
index c2a7d87d..00000000
--- a/src/common/managesieve.ui/editor.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(() => {
-
- "use strict";
-
- /* global SieveEditorUI */
- /* global SieveIpcClient */
- /* global SieveLogger */
- /* global SieveI18n */
- /* global SieveScriptSaveDialog */
-
- let editor = null;
-
- /**
- * Called when the editor is about to be closed.
- * Asks if the script should be saved or closing the window should be aborted.
- *
- * @param {string} name
- * the script name
- * @returns {boolean}
- * true in case the editor can be close, otherwise false.
- */
- async function onClose(name) {
-
- const result = await(new SieveScriptSaveDialog(name).show());
-
- if (SieveScriptSaveDialog.isCanceled(result))
- return false;
-
- if (SieveScriptSaveDialog.isAccepted(result))
- return await editor.save();
-
- return true;
- }
-
- /**
- * The main entry point.
- * Called as soon as the DOM is ready.
- */
- async function main() {
-
- SieveLogger.getInstance().level(
- await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
-
- await (SieveI18n.getInstance()).load();
-
- const url = new URL(window.location);
- const script = url.searchParams.get("script");
- const account = url.searchParams.get("account");
-
- // initialize the editor
- editor = new SieveEditorUI(script, account, "code");
-
- await editor.render();
- await editor.load();
-
- SieveIpcClient.setRequestHandler("editor", "editor-close",
- async (msg) => { return await onClose(msg.payload); });
- SieveIpcClient.setRequestHandler("editor", "editor-shown", () => { window.focus(); editor.focus(); });
- SieveIpcClient.setRequestHandler("editor", "editor-hasChanged", async () => { return await editor.hasChanged(); });
-
- // TODO Send a ready signal...
- }
-
- if (document.readyState !== 'loading')
- main();
- else
- document.addEventListener('DOMContentLoaded', () => { main(); }, {once: true});
-
- /*
- CodeMirror.on(window, "resize", function() {
- document.body.getElementsByClassName("CodeMirror-fullscreen")[0]
- .CodeMirror.getWrapperElement().style.height = winHeight() + "px";
- });
- */
-
- // hlLine = editor.addLineClass(0, "background", "activeline");
-
- // editor.on("change", function() { onChange(); });
-})();
diff --git a/src/common/managesieve.ui/editor.mjs b/src/common/managesieve.ui/editor.mjs
new file mode 100644
index 00000000..059e13c6
--- /dev/null
+++ b/src/common/managesieve.ui/editor.mjs
@@ -0,0 +1,88 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveIpcClient } from "./utils/SieveIpcClient.mjs";
+import { SieveLogger } from "./utils/SieveLogger.mjs";
+import { SieveI18n } from "./utils/SieveI18n.mjs";
+
+import { SieveEditorUI } from "./editor/SieveEditor.mjs";
+import { SieveScriptSaveDialog } from "./dialogs/SieveDialogUI.mjs";
+
+
+let editor = null;
+
+/**
+ * Called when the editor is about to be closed.
+ * Asks if the script should be saved or closing the window should be aborted.
+ *
+ * @param {string} name
+ * the script name
+ * @returns {boolean}
+ * true in case the editor can be close, otherwise false.
+ */
+async function onClose(name) {
+
+ const result = await (new SieveScriptSaveDialog(name).show());
+
+ if (SieveScriptSaveDialog.isCanceled(result))
+ return false;
+
+ if (SieveScriptSaveDialog.isAccepted(result))
+ return await editor.save();
+
+ return true;
+}
+
+/**
+ * The main entry point.
+ * Called as soon as the DOM is ready.
+ */
+async function main() {
+
+ SieveLogger.getInstance().level(
+ await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
+
+ await (SieveI18n.getInstance()).load();
+
+ const url = new URL(window.location);
+ const script = url.searchParams.get("script");
+ const account = url.searchParams.get("account");
+
+ // initialize the editor
+ editor = new SieveEditorUI(script, account, "code");
+
+ await editor.render();
+ await editor.load();
+
+ SieveIpcClient.setRequestHandler("editor", "editor-close",
+ async (msg) => { return await onClose(msg.payload); });
+ SieveIpcClient.setRequestHandler("editor", "editor-shown", () => { window.focus(); editor.focus(); });
+ SieveIpcClient.setRequestHandler("editor", "editor-hasChanged", async () => { return await editor.hasChanged(); });
+
+ // TODO Send a ready signal...
+}
+
+if (document.readyState !== 'loading')
+ main();
+else
+ document.addEventListener('DOMContentLoaded', () => { main(); }, { once: true });
+
+/*
+CodeMirror.on(window, "resize", function() {
+ document.body.getElementsByClassName("CodeMirror-fullscreen")[0]
+ .CodeMirror.getWrapperElement().style.height = winHeight() + "px";
+});
+*/
+
+// hlLine = editor.addLineClass(0, "background", "activeline");
+
+// editor.on("change", function() { onChange(); });
+
diff --git a/src/common/managesieve.ui/editor/SieveAbstractEditor.js b/src/common/managesieve.ui/editor/SieveAbstractEditor.js
deleted file mode 100644
index 1db052ec..00000000
--- a/src/common/managesieve.ui/editor/SieveAbstractEditor.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const HEX = 16;
- const HEX_LENGTH = 2;
-
- /**
- * An abstract and generic sieve editor interface .
- */
- class SieveAbstractEditorUI {
-
- /**
- * Creates a new text editor UI.
- *
- * @param {SieveEditorController} controller
- * the controller which is assigned to this editor.
- */
- constructor(controller) {
- this.controller = controller;
- }
-
- /**
- * The controller implements an editors external interfaces and actions.
- * It is typically shared between editors.
- *
- * @returns {SieveEditorController}
- * the controller which assigned to this editor.
- */
- getController() {
- return this.controller;
- }
-
- /**
- * Renders the current editor.
- * @abstract
- */
- async render() {
- }
-
- /**
- * Sets the editor's content.
- * @abstract
- *
- * @param {string} script
- * the script
- */
- // eslint-disable-next-line require-await
- async setScript(script) {
- throw new Error(`Implement setScript(${script})`);
- }
-
- /**
- * Gets the editor's content.
- * @abstract
- *
- * @returns {string}
- * the current script as string.
- */
- getScript() {
- throw new Error("Implement getScript()");
- }
-
- /**
- * Saves the script.
- */
- async save() {
- await this.controller.save();
- }
-
- /**
- * Focuses the editor's text area
- */
- focus() {
- }
-
- /**
- * Clears the editors
- */
- clearHistory() {
- }
-
- /**
- * Calculate the checksum for the current context script.
- *
- * @returns {string}
- * the content scripts sha256 checksum.
- */
- async getChecksum() {
-
- const digest = await crypto.subtle.digest('SHA-256',
- new TextEncoder().encode(await this.getScript()));
-
- return Array.from(new Uint8Array(digest)).map(
- (b) => { return b.toString(HEX).padStart(HEX_LENGTH, '0'); }).join('');
- }
-
- /**
- * Loads the editors settings.
- */
- async loadSettings() {
- }
-
- /**
- * Resets the editor to default settings
- */
- async loadDefaultSettings() {
- }
-
- /**
- * Save the current settings as default.
- */
- async saveDefaultSettings() {
- }
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractEditorUI = SieveAbstractEditorUI;
- else
- exports.SieveAbstractEditorUI = SieveAbstractEditorUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/editor/SieveAbstractEditor.mjs b/src/common/managesieve.ui/editor/SieveAbstractEditor.mjs
new file mode 100644
index 00000000..a8da509a
--- /dev/null
+++ b/src/common/managesieve.ui/editor/SieveAbstractEditor.mjs
@@ -0,0 +1,124 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const HEX = 16;
+const HEX_LENGTH = 2;
+
+/**
+ * An abstract and generic sieve editor interface .
+ */
+class SieveAbstractEditorUI {
+
+ /**
+ * Creates a new text editor UI.
+ *
+ * @param {SieveEditorController} controller
+ * the controller which is assigned to this editor.
+ */
+ constructor(controller) {
+ this.controller = controller;
+ }
+
+ /**
+ * The controller implements an editors external interfaces and actions.
+ * It is typically shared between editors.
+ *
+ * @returns {SieveEditorController}
+ * the controller which assigned to this editor.
+ */
+ getController() {
+ return this.controller;
+ }
+
+ /**
+ * Renders the current editor.
+ * @abstract
+ */
+ async render() {
+ }
+
+ /**
+ * Sets the editor's content.
+ * @abstract
+ *
+ * @param {string} script
+ * the script
+ */
+ async setScript(script) {
+ throw new Error(`Implement setScript(${script})`);
+ }
+
+ /**
+ * Gets the editor's content.
+ * @abstract
+ *
+ * @returns {string}
+ * the current script as string.
+ */
+ getScript() {
+ throw new Error("Implement getScript()");
+ }
+
+ /**
+ * Saves the script.
+ */
+ async save() {
+ await this.controller.save();
+ }
+
+ /**
+ * Focuses the editor's text area
+ */
+ focus() {
+ }
+
+ /**
+ * Clears the editors
+ */
+ clearHistory() {
+ }
+
+ /**
+ * Calculate the checksum for the current context script.
+ *
+ * @returns {string}
+ * the content scripts sha256 checksum.
+ */
+ async getChecksum() {
+
+ const digest = await crypto.subtle.digest('SHA-256',
+ (new TextEncoder()).encode(await this.getScript()));
+
+ return [...new Uint8Array(digest)].map(
+ (b) => { return b.toString(HEX).padStart(HEX_LENGTH, '0'); }).join('');
+ }
+
+ /**
+ * Loads the editors settings.
+ */
+ async loadSettings() {
+ }
+
+ /**
+ * Resets the editor to default settings
+ */
+ async loadDefaultSettings() {
+ }
+
+ /**
+ * Save the current settings as default.
+ */
+ async saveDefaultSettings() {
+ }
+
+}
+
+export { SieveAbstractEditorUI };
diff --git a/src/common/managesieve.ui/editor/SieveEditor.js b/src/common/managesieve.ui/editor/SieveEditor.js
deleted file mode 100644
index fbf4831e..00000000
--- a/src/common/managesieve.ui/editor/SieveEditor.js
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveEditorController */
- /* global SieveTextEditorUI */
- /* global SieveGraphicalEditorUI */
- /* global SieveTemplate */
-
- const EDITOR_OFFSET_PX = 40;
- /**
- * Implements a editor UI which contains a graphical as well as a text editor.
- */
- class SieveEditorUI extends SieveEditorController {
-
- /**
- * @inheritdoc
- */
- constructor(name, account) {
- super(name, account);
-
- this.textEditor = new SieveTextEditorUI(this);
- this.graphicalEditor = new SieveGraphicalEditorUI(this);
- this.checksum = "";
- }
-
- /**
- * Resizes the widget editors iframe to fill all of the available screen.
- */
- resize() {
- const topOffset = document
- .querySelector("#sieve-widget-editor")
- .getBoundingClientRect()
- .top + document.body.scrollTop;
-
- const screen = document.documentElement.clientHeight;
-
- // the visible screen size minus the top and bottom offset
- const size = screen - topOffset - EDITOR_OFFSET_PX;
-
- document
- .querySelector("#sieve-widget-editor")
- .style.height = `${size}px`;
- }
-
- /**
- * Moves the input focus to the currently active editor.
- */
- focus() {
- this.getCurrentEditor().focus();
- }
-
- /**
- * Renders the editor to screen.
- *
- * @returns {SieveEditorUI}
- * a self reference.
- */
- async render() {
-
- document.querySelector("#sieve-editor").appendChild(
- await (new SieveTemplate()).load("./editor/editor.tpl"));
-
- await this.getTextEditor().render();
- await this.getGraphicalEditor().render();
-
- await this.loadSettings();
-
- document
- .querySelector("#sieve-editor-settings .sieve-editor-settings-show")
- .addEventListener("click", () => {
- (new bootstrap.Tab(document.querySelector("#sieve-tab-settings"))).show();
- });
-
- document
- .querySelector("#sieve-editor-settings .sieve-editor-import")
- .addEventListener("click", () => {
- this.importScript();
- });
-
- document
- .querySelector("#sieve-editor-settings .sieve-editor-export")
- .addEventListener("click", () => {
- this.exportScript();
- });
-
- document
- .querySelector("#sieve-editor-save")
- .addEventListener("click", async () => {
- document
- .querySelector("#sieve-editor-saving").classList.remove("d-none");
-
- await this.save();
-
- document
- .querySelector("#sieve-editor-saving").classList.add("d-none");
- });
-
- document
- .querySelector('.nav-item > a[href="#sieve-widget-editor"]')
- .addEventListener('show.bs.tab', async (e) => {
-
- if (!this.isTextEditor())
- return;
-
- e.preventDefault();
-
- if (await this.switchToGraphicalEditor()) {
- (new bootstrap.Tab(document.querySelector('.nav-item > a[href="#sieve-widget-editor"]'))).show();
- }
- });
-
- document
- .querySelector('.nav-item > a[href="#sieve-widget-editor"]')
- .addEventListener('shown.bs.tab', () => {
- this.resize();
- });
-
- window.addEventListener("resize", () => {
- this.resize();
- });
-
- document
- .querySelector('.nav-item > a[href="#sieve-plaintext-editor"]')
- .addEventListener('shown.bs.tab', () => { this.switchToTextEditor(); });
-
- document
- .querySelector('.nav-item > a[href="#sieve-content-settings"]')
- .addEventListener('shown.bs.tab', () => { this.switchToSettings(); });
-
- return this;
- }
-
- /**
- * Hides/Dismisses any error messages.
- */
- hideErrorMessage() {
- const elm = document.querySelector("#sieve-editor-error");
- if (elm !== null)
- elm.parentNode.removeChild(elm);
- }
-
- /**
- * Shows an error message.
- *
- * @param {string} message
- * the error message to show.
- */
- async showErrorMessage(message) {
-
- const content = await (new SieveTemplate()).load("./editor/editor.error.save.html");
-
- content.querySelector(".sieve-editor-error-msg").textContent = message;
-
- this.hideErrorMessage();
-
- document.querySelector("#sieve-editor-errors").appendChild(content);
-
- // eslint-disable-next-line no-new
- new bootstrap.Alert(content);
-
- this.resize();
- }
-
- /**
- * Checks if the current editor has unsaved changes
- *
- * @returns {boolean}
- * true in case the editor contains unsaved changes.
- */
- async hasChanged() {
- return (await this.getCurrentEditor().getChecksum() !== this.checksum);
- }
-
- /**
- * Loads the sieve script into the editor.
- * All undo history will be flushed.
- *
- * @returns {boolean}
- * true in case the script could be loaded otherwise false.
- */
- async load() {
-
- const editor = this.getCurrentEditor();
-
- await editor.setScript(await this.loadScript());
-
- this.checksum = await editor.getChecksum();
-
- editor.clearHistory();
- editor.focus();
-
- return true;
- }
-
- /**
- * Saves the sieve script currently loaded into the editor.
- *
- * @returns {boolean}
- * true in case the script could be saved otherwise false.
- */
- async save() {
-
- if (!await this.hasChanged())
- return this;
-
- try {
-
- const editor = this.getCurrentEditor();
-
- await this.saveScript(
- await editor.getScript());
-
- this.checksum = await editor.getChecksum();
-
- this.hideErrorMessage();
- } catch (ex) {
- this.showErrorMessage(ex.toString());
- return false;
- }
-
- return true;
- }
-
- /**
- * Checks the script on the server for syntax errors.
- *
- * @param {string} [script]
- * the script to check if omitted the current editors
- * script will be used.
- *
- * @returns {undefined|string}
- * in case of an script error, the error message
- * otherwise null
- */
- async checkScript(script) {
- if (script === undefined || script === null)
- script = await this.getCurrentEditor().getScript();
-
- return await super.checkScript(script);
- }
-
- /**
- * Imports a sieve script from a file
- */
- async importScript() {
-
- const script = await super.importScript();
-
- if (typeof (script) === "undefined")
- return;
-
- await this.getCurrentEditor().setScript(script);
-
- // The dialog stole our focus...
- this.getCurrentEditor().focus();
- }
-
- /**
- * Exports the script to a file
- */
- async exportScript() {
- await super.exportScript(await this.getCurrentEditor().getScript());
-
- // The dialog stole our focus...
- this.getCurrentEditor().focus();
- }
-
- /**
- * Switches to the text editor.
- * It transfers the script from the graphical to the text editor.
- *
- * @returns {boolean}
- * true in case the editor was changed otherwise false.
- */
- async switchToTextEditor() {
-
- document.querySelector("#sieve-editor-save").classList.remove("d-none");
- document.querySelector("#sieve-editor-toolbar").classList.remove("d-none");
- document.querySelector("#sieve-plaintext-editor-toolbar").classList.remove("d-none");
-
- if (this.isTextEditor()) {
- this.getTextEditor().focus();
- return true;
- }
-
- // We keep the history intact so that undo works.
- // The set script would be just like an edit..
- await this.getTextEditor().setScript(
- await this.getGraphicalEditor().getScript());
-
- this.isTextEditor(true);
-
- this.getTextEditor().focus();
-
- return true;
- }
-
- /**
- * Switches the graphical editor.
- * It transfers the script from the text editor to the graphical.
- * It is only possible to switch to the graphical editor,
- * when the script is free of syntax errors.
- *
- * @returns {boolean}
- * true in case the editor was changed otherwise false.
- */
- async switchToGraphicalEditor() {
-
- document.querySelector("#sieve-editor-save").classList.remove("d-none");
- document.querySelector("#sieve-editor-toolbar").classList.add("d-none");
- document.querySelector("#sieve-plaintext-editor-toolbar").classList.add("d-none");
-
- if (!this.isTextEditor())
- return true;
-
- try {
- await this.getGraphicalEditor().setScript(
- await this.getTextEditor().getScript());
- } catch (ex) {
- await this.switchToTextEditor();
- this.showErrorMessage(`Switching to Graphical editor failed ${ex}`);
- return false;
- }
-
- this.isTextEditor(false);
- return true;
- }
-
- /**
- * Switches to the settings tab.
- */
- switchToSettings() {
- document.querySelector("#sieve-editor-toolbar").classList.add("d-none");
- document.querySelector("#sieve-editor-save").classList.add("d-none");
- }
-
- /**
- * Gets the currently active editor type and optionally
- * also sets the editor type.
- *
- * @param {boolean} [value]
- * optional. If set to true the text editor will be enabled.
- * Setting it to false will switch to the graphical editor.
- *
- * @returns {boolean}
- * true in case the text editor is the current editor.
- * Otherwise false.
- */
- isTextEditor(value) {
-
- const elm = document.querySelector('.nav-item > a[href="#sieve-widget-editor"]');
-
- if (value === true || value === false)
- elm.dataset.currentEditor = (!value).toString();
-
- return !(elm.dataset.currentEditor === "true");
- }
-
- /**
- * Returns a reference to the currently active editor.
- * Which is either the plain text editor or the rich text editor.
- *
- * @returns {SieveAbstractEditorUI} the currently active editor.
- */
- getCurrentEditor() {
- if (this.isTextEditor())
- return this.getTextEditor();
-
- return this.getGraphicalEditor();
- }
-
- /**
- * Returns the plain text editor.
- * Keep in mind the editor might be hidden.
- *
- * @returns {SieveTextEditorUI}
- * the plain text editor
- */
- getTextEditor() {
- return this.textEditor;
- }
-
- /**
- * Returns the graphical editor.
- * Keep in mind the editor might be hidden.
- *
- * @returns {SieveGraphicalEditorUI}
- * the graphical editor
- */
- getGraphicalEditor() {
- return this.graphicalEditor;
- }
-
- /**
- * Renders the current settings.
- */
- async renderSettings() {
-
- const parent = document.querySelector("#sieve-content-settings");
- while (parent.firstChild)
- parent.removeChild(parent.firstChild);
-
- await this.getTextEditor().renderSettings();
- // this.getGraphicalEditor().renderSettings();
-
- parent.appendChild(
- await (new SieveTemplate()).load("./editor/editor.settings.defaults.tpl"));
-
- document.querySelector("#editor-settings-save-defaults")
- .addEventListener("click", async () => {
- await this.saveDefaultSettings();
- });
-
- document.querySelector("#editor-settings-load-defaults")
- .addEventListener("click", async () => {
- await this.loadDefaultSettings();
- });
- }
-
- /**
- * @inheritdoc
- */
- async loadSettings() {
- await this.getTextEditor().loadSettings();
- await this.getGraphicalEditor().loadSettings();
-
- await this.renderSettings();
- }
-
- /**
- * @inheritdoc
- */
- async loadDefaultSettings() {
- await this.getTextEditor().loadDefaultSettings();
- await this.getGraphicalEditor().loadDefaultSettings();
-
- await this.renderSettings();
- }
-
- /**
- * @inheritdoc
- */
- async saveDefaultSettings() {
- await this.getTextEditor().saveDefaultSettings();
- await this.getGraphicalEditor().saveDefaultSettings();
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveEditorUI = SieveEditorUI;
- else
- exports.SieveEditorUI = SieveEditorUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/editor/SieveEditor.mjs b/src/common/managesieve.ui/editor/SieveEditor.mjs
new file mode 100644
index 00000000..36661df2
--- /dev/null
+++ b/src/common/managesieve.ui/editor/SieveEditor.mjs
@@ -0,0 +1,459 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+
+import { SieveEditorController } from "./SieveEditorController.mjs";
+import { SieveTextEditorUI } from "./text/SieveTextEditor.mjs";
+import { SieveGraphicalEditorUI } from "./graphical/SieveGraphicalEditor.mjs";
+import { SieveTemplate } from "./../utils/SieveTemplate.mjs";
+
+const EDITOR_OFFSET_PX = 40;
+/**
+ * Implements a editor UI which contains a graphical as well as a text editor.
+ */
+class SieveEditorUI extends SieveEditorController {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(name, account) {
+ super(name, account);
+
+ this.textEditor = new SieveTextEditorUI(this);
+ this.graphicalEditor = new SieveGraphicalEditorUI(this);
+ this.checksum = "";
+ }
+
+ /**
+ * Resizes the widget editors iframe to fill all of the available screen.
+ */
+ resize() {
+ const topOffset = document
+ .querySelector("#sieve-widget-editor")
+ .getBoundingClientRect()
+ .top + document.body.scrollTop;
+
+ const screen = document.documentElement.clientHeight;
+
+ // the visible screen size minus the top and bottom offset
+ const size = screen - topOffset - EDITOR_OFFSET_PX;
+
+ document
+ .querySelector("#sieve-widget-editor")
+ .style.height = `${size}px`;
+ }
+
+ /**
+ * Moves the input focus to the currently active editor.
+ */
+ focus() {
+ this.getCurrentEditor().focus();
+ }
+
+ /**
+ * Renders the editor to screen.
+ *
+ * @returns {SieveEditorUI}
+ * a self reference.
+ */
+ async render() {
+
+ document.querySelector("#sieve-editor").append(
+ await (new SieveTemplate()).load("./editor/editor.html"));
+
+ await this.getTextEditor().render();
+ await this.getGraphicalEditor().render();
+
+ await this.loadSettings();
+
+ document
+ .querySelector("#sieve-editor-settings .sieve-editor-settings-show")
+ .addEventListener("click", () => {
+ (new bootstrap.Tab("#sieve-tab-settings")).show();
+ });
+
+ document
+ .querySelector("#sieve-editor-settings .sieve-editor-import")
+ .addEventListener("click", () => {
+ this.importScript();
+ });
+
+ document
+ .querySelector("#sieve-editor-settings .sieve-editor-export")
+ .addEventListener("click", () => {
+ this.exportScript();
+ });
+
+ document
+ .querySelector("#sieve-editor-save")
+ .addEventListener("click", async () => {
+ document
+ .querySelector("#sieve-editor-saving").classList.remove("d-none");
+
+ await this.save();
+
+ document
+ .querySelector("#sieve-editor-saving").classList.add("d-none");
+ });
+
+ document
+ .querySelector('.nav-item > a[href="#sieve-widget-editor"]')
+ .addEventListener('show.bs.tab', async (e) => {
+
+ if (!this.isTextEditor())
+ return;
+
+ e.preventDefault();
+
+ if (await this.switchToGraphicalEditor()) {
+ (new bootstrap.Tab('.nav-item > a[href="#sieve-widget-editor"]')).show();
+ }
+ });
+
+ document
+ .querySelector('.nav-item > a[href="#sieve-widget-editor"]')
+ .addEventListener('shown.bs.tab', () => {
+ this.resize();
+ });
+
+ window.addEventListener("resize", () => {
+ this.resize();
+ });
+
+ document
+ .querySelector('.nav-item > a[href="#sieve-plaintext-editor"]')
+ .addEventListener('shown.bs.tab', () => { this.switchToTextEditor(); });
+
+ document
+ .querySelector('.nav-item > a[href="#sieve-content-settings"]')
+ .addEventListener('shown.bs.tab', () => { this.switchToSettings(); });
+
+ return this;
+ }
+
+ /**
+ * Hides/Dismisses any error messages.
+ */
+ hideErrorMessage() {
+ const elm = document.querySelector("#sieve-editor-error");
+ if (elm !== null)
+ elm.remove();
+ }
+
+ /**
+ * Shows an error message.
+ *
+ * @param {string} message
+ * the error message to show.
+ */
+ async showErrorMessage(message) {
+
+ const content = await (new SieveTemplate()).load("./editor/editor.error.save.html");
+
+ content.querySelector(".sieve-editor-error-msg").textContent = message;
+
+ this.hideErrorMessage();
+
+ document.querySelector("#sieve-editor-errors").append(content);
+
+ // eslint-disable-next-line no-new
+ new bootstrap.Alert(content);
+
+ this.resize();
+ }
+
+ /**
+ * Checks if the current editor has unsaved changes
+ *
+ * @returns {boolean}
+ * true in case the editor contains unsaved changes.
+ */
+ async hasChanged() {
+ return (await this.getCurrentEditor().getChecksum() !== this.checksum);
+ }
+
+ /**
+ * Loads the sieve script into the editor.
+ * All undo history will be flushed.
+ *
+ * @returns {boolean}
+ * true in case the script could be loaded otherwise false.
+ */
+ async load() {
+
+ const editor = this.getCurrentEditor();
+
+ await editor.setScript(await this.loadScript());
+
+ this.checksum = await editor.getChecksum();
+
+ editor.clearHistory();
+ editor.focus();
+
+ return true;
+ }
+
+ /**
+ * Saves the sieve script currently loaded into the editor.
+ *
+ * @returns {boolean}
+ * true in case the script could be saved otherwise false.
+ */
+ async save() {
+
+ if (!await this.hasChanged())
+ return this;
+
+ try {
+
+ const editor = this.getCurrentEditor();
+
+ await this.saveScript(
+ await editor.getScript());
+
+ this.checksum = await editor.getChecksum();
+
+ this.hideErrorMessage();
+ } catch (ex) {
+ this.showErrorMessage(ex.toString());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks the script on the server for syntax errors.
+ *
+ * @param {string} [script]
+ * the script to check if omitted the current editors
+ * script will be used.
+ *
+ * @returns {undefined|string}
+ * in case of an script error, the error message
+ * otherwise null
+ */
+ async checkScript(script) {
+ if (script === undefined || script === null)
+ script = await this.getCurrentEditor().getScript();
+
+ return await super.checkScript(script);
+ }
+
+ /**
+ * Imports a sieve script from a file
+ */
+ async importScript() {
+
+ const script = await super.importScript();
+
+ if (typeof (script) === "undefined")
+ return;
+
+ await this.getCurrentEditor().setScript(script);
+
+ // The dialog stole our focus...
+ this.getCurrentEditor().focus();
+ }
+
+ /**
+ * Exports the script to a file
+ */
+ async exportScript() {
+ await super.exportScript(await this.getCurrentEditor().getScript());
+
+ // The dialog stole our focus...
+ this.getCurrentEditor().focus();
+ }
+
+ /**
+ * Switches to the text editor.
+ * It transfers the script from the graphical to the text editor.
+ *
+ * @returns {boolean}
+ * true in case the editor was changed otherwise false.
+ */
+ async switchToTextEditor() {
+
+ document.querySelector("#sieve-editor-save").classList.remove("d-none");
+ document.querySelector("#sieve-editor-toolbar").classList.remove("d-none");
+ document.querySelector("#sieve-plaintext-editor-toolbar").classList.remove("d-none");
+
+ if (this.isTextEditor()) {
+ this.getTextEditor().focus();
+ return true;
+ }
+
+ // We keep the history intact so that undo works.
+ // The set script would be just like an edit..
+ await this.getTextEditor().setScript(
+ await this.getGraphicalEditor().getScript());
+
+ this.isTextEditor(true);
+
+ this.getTextEditor().focus();
+
+ return true;
+ }
+
+ /**
+ * Switches the graphical editor.
+ * It transfers the script from the text editor to the graphical.
+ * It is only possible to switch to the graphical editor,
+ * when the script is free of syntax errors.
+ *
+ * @returns {boolean}
+ * true in case the editor was changed otherwise false.
+ */
+ async switchToGraphicalEditor() {
+
+ document.querySelector("#sieve-editor-save").classList.remove("d-none");
+ document.querySelector("#sieve-editor-toolbar").classList.add("d-none");
+ document.querySelector("#sieve-plaintext-editor-toolbar").classList.add("d-none");
+
+ if (!this.isTextEditor())
+ return true;
+
+ try {
+ await this.getGraphicalEditor().setScript(
+ await this.getTextEditor().getScript());
+ } catch (ex) {
+ await this.switchToTextEditor();
+ this.showErrorMessage(`Switching to Graphical editor failed ${ex}`);
+ return false;
+ }
+
+ this.isTextEditor(false);
+ return true;
+ }
+
+ /**
+ * Switches to the settings tab.
+ */
+ switchToSettings() {
+ document.querySelector("#sieve-editor-toolbar").classList.add("d-none");
+ document.querySelector("#sieve-editor-save").classList.add("d-none");
+ }
+
+ /**
+ * Gets the currently active editor type and optionally
+ * also sets the editor type.
+ *
+ * @param {boolean} [value]
+ * optional. If set to true the text editor will be enabled.
+ * Setting it to false will switch to the graphical editor.
+ *
+ * @returns {boolean}
+ * true in case the text editor is the current editor.
+ * Otherwise false.
+ */
+ isTextEditor(value) {
+
+ const elm = document.querySelector('.nav-item > a[href="#sieve-widget-editor"]');
+
+ if (value === true || value === false)
+ elm.dataset.currentEditor = (!value).toString();
+
+ return !(elm.dataset.currentEditor === "true");
+ }
+
+ /**
+ * Returns a reference to the currently active editor.
+ * Which is either the plain text editor or the rich text editor.
+ *
+ * @returns {SieveAbstractEditorUI} the currently active editor.
+ */
+ getCurrentEditor() {
+ if (this.isTextEditor())
+ return this.getTextEditor();
+
+ return this.getGraphicalEditor();
+ }
+
+ /**
+ * Returns the plain text editor.
+ * Keep in mind the editor might be hidden.
+ *
+ * @returns {SieveTextEditorUI}
+ * the plain text editor
+ */
+ getTextEditor() {
+ return this.textEditor;
+ }
+
+ /**
+ * Returns the graphical editor.
+ * Keep in mind the editor might be hidden.
+ *
+ * @returns {SieveGraphicalEditorUI}
+ * the graphical editor
+ */
+ getGraphicalEditor() {
+ return this.graphicalEditor;
+ }
+
+ /**
+ * Renders the current settings.
+ */
+ async renderSettings() {
+
+ const parent = document.querySelector("#sieve-content-settings");
+ while (parent.firstChild)
+ parent.firstChild.remove();
+
+ await this.getTextEditor().renderSettings();
+ // this.getGraphicalEditor().renderSettings();
+
+ parent.append(
+ await (new SieveTemplate()).load("./editor/editor.settings.defaults.html"));
+
+ document.querySelector("#editor-settings-save-defaults")
+ .addEventListener("click", async () => {
+ await this.saveDefaultSettings();
+ });
+
+ document.querySelector("#editor-settings-load-defaults")
+ .addEventListener("click", async () => {
+ await this.loadDefaultSettings();
+ });
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async loadSettings() {
+ await this.getTextEditor().loadSettings();
+ await this.getGraphicalEditor().loadSettings();
+
+ await this.renderSettings();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async loadDefaultSettings() {
+ await this.getTextEditor().loadDefaultSettings();
+ await this.getGraphicalEditor().loadDefaultSettings();
+
+ await this.renderSettings();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async saveDefaultSettings() {
+ await this.getTextEditor().saveDefaultSettings();
+ await this.getGraphicalEditor().saveDefaultSettings();
+ }
+}
+
+export { SieveEditorUI };
diff --git a/src/common/managesieve.ui/editor/SieveEditorController.js b/src/common/managesieve.ui/editor/SieveEditorController.js
deleted file mode 100644
index 21e3243b..00000000
--- a/src/common/managesieve.ui/editor/SieveEditorController.js
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveIpcClient */
-
- /**
- * An editor controller is the logical endpoint for all editor
- * specific actions. It is triggered from the graphical editor
- * as well as the text editor and interacts with the outside world
- * like the sieve server or the operating system.
- */
- class SieveEditorController {
-
- /**
- * Creates a new instance
- *
- * @param {string} name
- * the sieve scripts name
- * @param {string} account
- * the account id
- */
- constructor(name, account) {
- this.name = name;
- this.account = account;
- }
-
- /**
- * Loads the script into the editor.
- *
- * @returns {string}
- * the script to be loaded.
- */
- async loadScript() {
- return await this.send("script-get", this.name);
- }
-
- /**
- * Saves the current script.
- *
- * @param {string} script
- * the script's content.
- */
- async saveScript(script) {
- await this.send("script-save", { "name": this.name, "script": script });
- }
-
- /**
- * Checks if the given script is free of syntax errors.
- * @param {string} script
- * the script to be checked
- * @returns {undefined|string}
- * either undefined in case the script is ok or an error string otherwise.
- */
- async checkScript(script) {
- return await this.send("script-check", script);
- }
-
- /**
- * Sets the current clipboard content.
- *
- * @param {string} data
- * the data to copy to the clipboard.
- */
- async setClipboard(data) {
- await this.send("copy", data);
- }
-
- /**
- * Gets the current clipboard content.
- *
- * @returns {string}
- * the current clipboard content
- */
- async getClipboard() {
- return await this.send("paste");
- }
-
- /**
- * Imports the given script from a file.
- *
- * @returns {string}
- * the imported sieve script.
- */
- async importScript() {
- return await this.send("script-import");
- }
-
- /**
- * Exports the given script into a file.
- * @param {string} script
- * the sieve content to be exported
- */
- async exportScript(script) {
- await this.send("script-export", { "name": this.name, "script": script });
- }
-
- /**
- * Reruns the server side capabilities for the given account
- * @returns {object}
- * the server's capabilities.
- */
- async getCapabilities() {
- return await this.send("account-capabilities");
- }
-
- /**
- * Sets a preference value.
- *
- * @param {string} name
- * the preferences unique name.
- * @param {object} value
- * the value which should be set.
- */
- async setPreference(name, value) {
- await this.send("set-preference", { "key": name, "value": value });
- }
-
- /**
- * Sets a default value for a preference.
- *
- * @param {string} name
- * the preferences unique name.
- * @param {object} value
- * the value which should be set as default.
- */
- async setDefaultPreference(name, value) {
- await this.send("set-default-preference", { "key": name, "value": value });
- }
-
- /**
- * Gets a preference value.
- *
- * @param {string} name
- * the preferences unique name
- * @returns {object}
- * the requested value.
- */
- async getPreference(name) {
- return await this.send("get-preference", name);
- }
-
- /**
- * Gets the default value for a preference.
- *
- * @param {string} name
- * the preferences unique name
- * @returns {object}
- * the preference's default value.
- */
- async getDefaultPreference(name) {
- return await this.send("get-default-preference", name);
- }
-
- /**
- * Executes an action on the communication process.
- *
- * @param {string} action
- * the actions unique name
- * @param {object} [payload]
- * th payload which should be send
- * @returns {Promise<object>}
- * the result received for this action.
- */
- async send(action, payload) {
-
- if (typeof (payload) === "undefined" || payload === null)
- payload = {};
-
- if (typeof (payload) !== "object")
- payload = { "data": payload };
-
- payload["account"] = this.account;
-
- return await SieveIpcClient.sendMessage("core", action, payload);
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveEditorController = SieveEditorController;
- else
- exports.SieveEditorController = SieveEditorController;
-
-})(this);
diff --git a/src/common/managesieve.ui/editor/SieveEditorController.mjs b/src/common/managesieve.ui/editor/SieveEditorController.mjs
new file mode 100644
index 00000000..a9667f31
--- /dev/null
+++ b/src/common/managesieve.ui/editor/SieveEditorController.mjs
@@ -0,0 +1,186 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveIpcClient } from "./../utils/SieveIpcClient.mjs";
+
+/**
+ * An editor controller is the logical endpoint for all editor
+ * specific actions. It is triggered from the graphical editor
+ * as well as the text editor and interacts with the outside world
+ * like the sieve server or the operating system.
+ */
+class SieveEditorController {
+
+ /**
+ * Creates a new instance
+ *
+ * @param {string} name
+ * the sieve scripts name
+ * @param {string} account
+ * the account id
+ */
+ constructor(name, account) {
+ this.name = name;
+ this.account = account;
+ }
+
+ /**
+ * Loads the script into the editor.
+ *
+ * @returns {string}
+ * the script to be loaded.
+ */
+ async loadScript() {
+ return await this.send("script-get", this.name);
+ }
+
+ /**
+ * Saves the current script.
+ *
+ * @param {string} script
+ * the script's content.
+ */
+ async saveScript(script) {
+ await this.send("script-save", { "name": this.name, "script": script });
+ }
+
+ /**
+ * Checks if the given script is free of syntax errors.
+ * @param {string} script
+ * the script to be checked
+ * @returns {undefined|string}
+ * either undefined in case the script is ok or an error string otherwise.
+ */
+ async checkScript(script) {
+ return await this.send("script-check", script);
+ }
+
+ /**
+ * Sets the current clipboard content.
+ *
+ * @param {string} data
+ * the data to copy to the clipboard.
+ */
+ async setClipboard(data) {
+ await this.send("copy", data);
+ }
+
+ /**
+ * Gets the current clipboard content.
+ *
+ * @returns {string}
+ * the current clipboard content
+ */
+ async getClipboard() {
+ return await this.send("paste");
+ }
+
+ /**
+ * Imports the given script from a file.
+ *
+ * @returns {string}
+ * the imported sieve script.
+ */
+ async importScript() {
+ return await this.send("script-import");
+ }
+
+ /**
+ * Exports the given script into a file.
+ * @param {string} script
+ * the sieve content to be exported
+ */
+ async exportScript(script) {
+ await this.send("script-export", { "name": this.name, "script": script });
+ }
+
+ /**
+ * Reruns the server side capabilities for the given account
+ * @returns {object}
+ * the server's capabilities.
+ */
+ async getCapabilities() {
+ return await this.send("account-capabilities");
+ }
+
+ /**
+ * Sets a preference value.
+ *
+ * @param {string} name
+ * the preferences unique name.
+ * @param {object} value
+ * the value which should be set.
+ */
+ async setPreference(name, value) {
+ await this.send("set-preference", { "key": name, "value": value });
+ }
+
+ /**
+ * Sets a default value for a preference.
+ *
+ * @param {string} name
+ * the preferences unique name.
+ * @param {object} value
+ * the value which should be set as default.
+ */
+ async setDefaultPreference(name, value) {
+ await this.send("set-default-preference", { "key": name, "value": value });
+ }
+
+ /**
+ * Gets a preference value.
+ *
+ * @param {string} name
+ * the preferences unique name
+ * @returns {object}
+ * the requested value.
+ */
+ async getPreference(name) {
+ return await this.send("get-preference", name);
+ }
+
+ /**
+ * Gets the default value for a preference.
+ *
+ * @param {string} name
+ * the preferences unique name
+ * @returns {object}
+ * the preference's default value.
+ */
+ async getDefaultPreference(name) {
+ return await this.send("get-default-preference", name);
+ }
+
+ /**
+ * Executes an action on the communication process.
+ *
+ * @param {string} action
+ * the actions unique name
+ * @param {object} [payload]
+ * th payload which should be send
+ * @returns {Promise<object>}
+ * the result received for this action.
+ */
+ async send(action, payload) {
+
+ if (typeof (payload) === "undefined" || payload === null)
+ payload = {};
+
+ if (typeof (payload) !== "object")
+ payload = { "data": payload };
+
+ payload["account"] = this.account;
+
+ return await SieveIpcClient.sendMessage("core", action, payload);
+ }
+}
+
+export { SieveEditorController };
diff --git a/src/common/managesieve.ui/editor/editor.error.save.html b/src/common/managesieve.ui/editor/editor.error.save.html
index ab3447a3..e62b5fa6 100644
--- a/src/common/managesieve.ui/editor/editor.error.save.html
+++ b/src/common/managesieve.ui/editor/editor.error.save.html
@@ -1,5 +1,5 @@
<div id="sieve-editor-error" class="alert alert-danger alert-dismissible fade show my-0" role="alert">
- <button type="button" class="btn-close" data-dismiss="alert" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<strong data-i18n="editor.error.save.title"></strong>
<span data-i18n="editor.error.save.description" class="sieve-editor-error-msg"></span>
</div> \ No newline at end of file
diff --git a/src/common/managesieve.ui/editor/editor.tpl b/src/common/managesieve.ui/editor/editor.html
index 8de2e532..5572f7ca 100644
--- a/src/common/managesieve.ui/editor/editor.tpl
+++ b/src/common/managesieve.ui/editor/editor.html
@@ -5,32 +5,32 @@
<div class="card-header d-flex justify-content-between py-0">
<ul class="nav nav-tabs card-header-tabs my-0 pt-3" role="tablist">
<li class="nav-item">
- <a class="nav-link" data-i18n="editor.script" data-toggle="tab" href="#sieve-widget-editor"
+ <a class="nav-link" data-i18n="editor.script" data-bs-toggle="tab" href="#sieve-widget-editor"
role="tab"></a>
</li>
<li class="nav-item">
- <a class="nav-link active" data-i18n="editor.source" data-toggle="tab" href="#sieve-plaintext-editor"
+ <a class="nav-link active" data-i18n="editor.source" data-bs-toggle="tab" href="#sieve-plaintext-editor"
role="tab"></a>
</li>
<li class="nav-item">
- <a id="sieve-tab-settings" data-i18n="editor.settings" class="nav-link" data-toggle="tab"
+ <a id="sieve-tab-settings" data-i18n="editor.settings" class="nav-link" data-bs-toggle="tab"
href="#sieve-content-settings" role="tab"></a>
</li>
</ul>
<div class="align-self-center">
- <button type="button" class="btn btn-sm btn-outline-secondary mr-1" id="sieve-editor-save">
+ <button type="button" class="btn btn-sm btn-outline-secondary me-1" id="sieve-editor-save">
<span id="sieve-editor-saving" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span data-i18n="editor.save"></span>
</button>
- <a class="btn btn-sm btn-outline-info mr-1 " href="https://www.paypal.com/paypalme2/thsmi" target="_blank"
+ <a class="btn btn-sm btn-outline-info me-1 " href="https://www.paypal.com/paypalme2/thsmi" target="_blank"
role="button" data-i18n="editor.donate"></a>
<div id="sieve-editor-settings" class="btn-group dropdown">
- <a class="btn btn-sm btn-outline-secondary dropdown-toggle" role="button" data-toggle="dropdown"
+ <a class="btn btn-sm btn-outline-secondary dropdown-toggle" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
</a>
- <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
+ <div class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownMenuLink">
<a data-i18n="editor.menu.settings" class="dropdown-item sieve-editor-settings-show"></a>
<a data-i18n="editor.menu.reload" class="dropdown-item"
href="javascript:window.location.reload(true)"></a>
diff --git a/src/common/managesieve.ui/editor/editor.settings.defaults.tpl b/src/common/managesieve.ui/editor/editor.settings.defaults.html
index 1b1b9fb3..83cf2cfa 100644
--- a/src/common/managesieve.ui/editor/editor.settings.defaults.tpl
+++ b/src/common/managesieve.ui/editor/editor.settings.defaults.html
@@ -4,6 +4,6 @@
<p data-i18n="editor.defaults.description"></p>
<div>
<a id="editor-settings-save-defaults" data-i18n="editor.defaults.save" class="btn btn-sm btn-outline-secondary" role="button"></a>
- <a id="editor-settings-load-defaults" data-i18n="editor.defaults.load" class="btn btn-sm btn-outline-secondary mr-1" role="button"></a>
+ <a id="editor-settings-load-defaults" data-i18n="editor.defaults.load" class="btn btn-sm btn-outline-secondary me-1" role="button"></a>
</div>
</div> \ No newline at end of file
diff --git a/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.js b/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.js
deleted file mode 100644
index 93c4ae79..00000000
--- a/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveAbstractEditorUI */
-
- // FIXME should use an IPC instead of talking directly to the iframe...
-
- /**
- *
- */
- class SieveGraphicalEditorUI extends SieveAbstractEditorUI {
-
- /**
- * Creates a new graphical editor UI.
- *
- * @param {SieveEditorController} controller
- * The controller which is assigned to this editor.
- */
- constructor(controller) {
- super(controller);
- this.id = "sieve-widget-editor";
- }
-
- /**
- * @inheritdoc
- */
- async render() {
- }
-
- /**
- * @inheritdoc
- */
- async setScript(script) {
-
- const capabilities = await this.getController().getCapabilities();
- // set script content...
- document.getElementById(this.id)
- .contentWindow
- .setSieveScript(script, JSON.stringify(capabilities.extensions));
- }
-
- /**
- * @inheritdoc
- */
- getScript() {
- return document.getElementById(this.id)
- .contentWindow
- .getSieveScript();
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveGraphicalEditorUI = SieveGraphicalEditorUI;
- else
- exports.SieveGraphicalEditorUI = SieveGraphicalEditorUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.mjs b/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.mjs
new file mode 100644
index 00000000..1e4db769
--- /dev/null
+++ b/src/common/managesieve.ui/editor/graphical/SieveGraphicalEditor.mjs
@@ -0,0 +1,60 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractEditorUI } from "./../SieveAbstractEditor.mjs";
+
+// FIXME should use an IPC instead of talking directly to the iframe...
+
+/**
+ *
+ */
+class SieveGraphicalEditorUI extends SieveAbstractEditorUI {
+
+ /**
+ * Creates a new graphical editor UI.
+ *
+ * @param {SieveEditorController} controller
+ * The controller which is assigned to this editor.
+ */
+ constructor(controller) {
+ super(controller);
+ this.id = "sieve-widget-editor";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async render() {
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async setScript(script) {
+
+ const capabilities = await this.getController().getCapabilities();
+ // set script content...
+ document.querySelector(`#${this.id}`)
+ .contentWindow
+ .setSieveScript(script, JSON.stringify(capabilities.extensions));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getScript() {
+ return document.querySelector(`#${this.id}`)
+ .contentWindow
+ .getSieveScript();
+ }
+}
+
+export { SieveGraphicalEditorUI };
diff --git a/src/common/managesieve.ui/editor/text/SieveTextEditor.js b/src/common/managesieve.ui/editor/text/SieveTextEditor.js
deleted file mode 100644
index c0309d0c..00000000
--- a/src/common/managesieve.ui/editor/text/SieveTextEditor.js
+++ /dev/null
@@ -1,760 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global CodeMirror */
- /* global SieveTemplate */
- /* global SieveAbstractEditorUI */
-
- const COMPILE_DELAY = 500;
- const EDITOR_SCROLL_INTO_VIEW_OFFSET = 200;
-
- /**
- * An text editor ui for sieve scripts.
- */
- class SieveTextEditorUI extends SieveAbstractEditorUI {
-
- /**
- * Creates a new text editor UI.
- *
- * @param {SieveEditorController} controller
- * The controller which is assigned to this editor.
- * @param {string} [id]
- * An optional id, which points to a the textbox, which will be converted
- * into a code mirror input. In case it is omitted the id "code" will be used.
- */
- constructor(controller, id) {
-
- super(controller);
-
- if (typeof (id) === "undefined" || id === null)
- this.id = "code";
-
- this.syntaxCheckEnabled = false;
- this.timeout = null;
-
- this.cm = null;
-
- this.activeLine = null;
-
- this.changed = false;
- }
-
- /**
- * Renders the text editors settings
- */
- async renderSettings() {
-
-
- const loader = new SieveTemplate();
-
- // Syntax Checks
- document
- .querySelector("#sieve-content-settings")
- .appendChild(await loader.load("./editor/text/editor.settings.syntax.tpl"));
-
- document
- .querySelector("#sieve-editor-settings-synatxcheck")
- .addEventListener("click", async () => {
-
- if (document.querySelector("#sieve-editor-settings-synatxcheck").checked === true)
- await this.enableSyntaxCheck();
- else
- await this.disableSyntaxCheck();
- });
-
- document.querySelector("#sieve-editor-settings-synatxcheck")
- .checked = this.isSyntaxCheckEnabled();
-
- // Indentation
- document
- .querySelector("#sieve-content-settings")
- .appendChild(await loader.load("./editor/text/editor.settings.indentation.tpl"));
-
- // Indentation width...
- document
- .querySelector("#editor-settings-indentation-width")
- .addEventListener("change", async () => {
- await this.setIndentWidth(
- document.querySelector("#editor-settings-indentation-width").value);
- });
-
- document.querySelector("#editor-settings-indentation-width")
- .value = this.getIndentWidth();
-
- // Indentation policy...
- document
- .querySelector("#editor-settings-indentation-policy-spaces")
- .addEventListener("change", async () => { await this.setIndentWithTabs(false); });
-
- document
- .querySelector("#editor-settings-indentation-policy-tabs")
- .addEventListener("change", async () => { await this.setIndentWithTabs(true); });
-
- if (this.getIndentWithTabs())
- document.querySelector("#editor-settings-indentation-policy-tabs").checked = true;
- else
- document.querySelector("#editor-settings-indentation-policy-spaces").checked = true;
-
- // Tabulator width...
- document
- .querySelector("#editor-settings-tabulator-width")
- .addEventListener("change", async () => {
- await this.setTabWidth(
- document.querySelector("#editor-settings-tabulator-width").value);
- });
-
- document.querySelector("#editor-settings-tabulator-width")
- .value = this.getTabWidth();
- }
-
- /**
- * @inheritdoc
- */
- async render() {
-
- const loader = new SieveTemplate();
-
- const editor = document.querySelector("#sieve-plaintext-editor");
- while (editor.firstChild)
- editor.removeChild(editor.firstChild);
-
- editor.appendChild(
- await loader.load("./editor/text/editor.plaintext.html"));
-
- this.cm = CodeMirror.fromTextArea(document.getElementById(this.id), {
- lineNumbers: true,
- lineWrapping: true,
-
- theme: "eclipse",
- matchBrackets: true,
-
- inputStyle: "contenteditable"
- });
-
- this.cm.on("renderLine", (cm, line, elt) => { this.onRenderLine(cm, line, elt); });
- this.cm.on("change", () => { this.onChange(); });
- this.cm.on("cursorActivity", () => { this.onActiveLineChange(); });
-
- this.cm.refresh();
-
- // Configure tab handling...
- this.cm.setOption("extraKeys", {
- "Tab": function (cm) {
-
- if (cm.somethingSelected()) {
- const sel = cm.getSelection("\n");
- // Indent only if there are multiple lines selected, or if the selection spans a full line
- if (sel.length > 0 && (sel.indexOf("\n") > -1 || sel.length === cm.getLine(cm.getCursor().line).length)) {
- cm.indentSelection("add");
- return;
- }
- }
-
- if (cm.options.indentWithTabs)
- cm.execCommand("insertTab");
- else
- cm.execCommand("insertSoftTab");
- },
- "Shift-Tab": function (cm) {
- cm.indentSelection("subtract");
- }
- });
-
- const toolbar = document.querySelector("#sieve-editor-toolbar");
- toolbar.appendChild(
- await loader.load("./editor/text/editor.plaintext.toolbar.html"));
-
- document
- .querySelector("#sieve-editor-undo")
- .addEventListener("click", () => { this.undo(); });
-
- document
- .querySelector("#sieve-editor-redo")
- .addEventListener("click", () => { this.redo(); });
-
- document
- .querySelector("#sieve-editor-cut")
- .addEventListener("click", () => { this.cut(); });
-
- document
- .querySelector("#sieve-editor-copy")
- .addEventListener("click", () => { this.copy(); });
-
- document
- .querySelector("#sieve-editor-paste")
- .addEventListener("click", () => { this.paste(); });
-
- document
- .querySelector("#sieve-editor-find")
- .addEventListener("click", () => {
- const token = document.querySelector("#sieve-editor-txt-find").value;
-
- const isReverse = document.querySelector("#sieve-editor-backward").checked;
- const isCaseSensitive = document.querySelector("#sieve-editor-casesensitive").checked;
-
- this.find(token, isCaseSensitive, isReverse);
- });
-
- document
- .querySelector("#sieve-editor-replace")
- .addEventListener("click", () => {
- const oldToken = document.querySelector("#sieve-editor-txt-find").value;
- const newToken = document.querySelector("#sieve-editor-txt-replace").value;
-
- const isReverse = document.querySelector("#sieve-editor-backward").checked;
- const isCaseSensitive = document.querySelector("#sieve-editor-casesensitive").checked;
-
- if (oldToken === "")
- return;
-
- this.replace(oldToken, newToken, isCaseSensitive, isReverse);
- });
-
-
- document
- .querySelector("#sieve-editor-replace-replace")
- .addEventListener("click", () => {
- document.querySelector("#sieve-editor-find-toolbar").classList.toggle("d-none");
- });
-
- await this.renderSettings();
- }
-
- /**
- * Returns the editor change status.
- *
- * @returns {boolean}
- * true in case the document was changed otherwise false.
- */
- hasChanged() {
- return this.changed;
- }
-
- /**
- * @inheritdoc
- */
- async setScript(script) {
- // Load a new script. It will discard the current script
- // the cursor position is reset to defaults.
-
- this.cm.setValue(script);
- this.cm.setCursor({ line: 0, ch: 0 });
-
- this.cm.refresh();
-
- // ensure the active line cursor changed...
- // onActiveLineChange();
- }
-
- /**
- * @inheritdoc
- */
- getScript() {
-
- this.focus();
-
- const script = this.cm.getValue();
-
- // ... and ensure the line endings are sanitized
- // eslint-disable-next-line no-control-regex
- return script.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
- }
-
- /**
- * @inheritdoc
- */
- focus() {
- if (this.cm)
- this.cm.focus();
- }
-
- /**
- * @inheritdoc
- */
- clearHistory() {
- this.cm.clearHistory();
- }
-
- /**
- * Checks the current script for syntax errors
- */
- async checkScript() {
-
- const errors = await this.getController().checkScript(await this.getScript());
-
- if (errors && errors !== "")
- this.showSyntaxErrors(errors);
- else
- this.hideSyntaxErrors();
- }
-
- /**
- * Undoes the last input
- */
- undo() {
- this.cm.undo();
- this.cm.focus();
- }
-
- /**
- * Redos the last input
- */
- redo() {
- this.cm.redo();
- this.cm.focus();
- }
-
- /**
- * Cuts the currently selected text.
- */
- async cut() {
- await this.copy();
- this.cm.replaceSelection("");
-
- this.cm.focus();
- }
-
- /**
- * Copies the currently selected text.
- */
- async copy() {
- const data = this.cm.getSelection();
-
- await this.getController().setClipboard(data);
-
- this.cm.focus();
- }
-
- /**
- * Pastes the clipboard content into the editor.
- */
- async paste() {
- const data = await this.getController().getClipboard();
- this.cm.replaceSelection(data);
-
- this.cm.focus();
- }
-
- /**
- * Gets the selection begin
- *
- * @param {boolean} isReverse
- * if true the selection is handled in reverse order.
- * which means the selection start gets the selections end and vice versa.
- * @returns {int}
- * the current start position.
- */
- getSelectionStart(isReverse) {
-
- const start = this.cm.getCursor(true);
- const end = this.cm.getCursor(false);
-
- if (isReverse) {
- if (start.line < end.line)
- return start;
-
- if (start.line > end.line)
- return end;
-
- // start.line == end.line
- if (start.ch > end.ch)
- return end;
-
- return start;
- }
-
-
- if (start.line > end.line)
- return start;
-
- if (start.line < end.line)
- return end;
-
- // start.line == end.line
- if (start.ch > end.ch)
- return start;
-
- return end;
-
- }
-
- /**
- * Finds the specified token within the editor.
- *
- * @param {string} token
- * the string to find.
- * @param {boolean} [isCaseSensitive]
- * if true the search is case sensitive.
- * @param {boolean} [isReverse]
- * if true the search will be in reverse direction.
- * @returns {boolean}
- * true in case the the string was found otherwise false.
- */
- find(token, isCaseSensitive, isReverse) {
-
- // Fix optional parameters...
- if (typeof (isCaseSensitive) === "undefined" || isCaseSensitive === null)
- isCaseSensitive = false;
-
- if (typeof (isReverse) === "undefined" || isReverse === null)
- isReverse = false;
-
- let cursor = this.cm.getSearchCursor(
- token,
- this.getSelectionStart(isReverse),
- !isCaseSensitive);
-
- if (!cursor.find(isReverse)) {
- // warp search at top or bottom
- cursor = this.cm.getSearchCursor(
- token,
- isReverse ? { line: this.cm.lineCount() - 1 } : { line: 0, ch: 0 },
- !isCaseSensitive);
-
- if (!cursor.find(isReverse))
- return false;
- }
-
- if (isReverse)
- this.cm.setSelection(cursor.from(), cursor.to());
- else
- this.cm.setSelection(cursor.to(), cursor.from());
-
- this.cm.scrollIntoView(cursor.to(), EDITOR_SCROLL_INTO_VIEW_OFFSET);
-
- return true;
- }
-
- /**
- * Checks if the specified token is selected.
- *
- * @param {string} token
- * the token
- * @param {boolean} isCaseSensitive
- * true in case the check should be case insensitive.
- * @returns {boolean}
- * true in case the token was found otherwise false.
- */
- isSelected(token, isCaseSensitive) {
- let selection = this.cm.getSelection();
-
- if (isCaseSensitive) {
- selection = selection.toLowerCase();
- token = token.toLocaleLowerCase();
- }
-
- if (selection !== token)
- return false;
-
- return true;
- }
-
- /**
- * Replaces the old token with the new token.
- *
- * @param {string} oldToken
- * the old token which should be replaced
- * @param {string} newToken
- * the new token
- * @param {boolean} [isCaseSensitive]
- * if true the search is case sensitive.
- * @param {boolean} [isReverse]
- * if true the search will be in reverse direction.
- * @returns {boolean}
- * true if the string was replaced, otherwise false.
- */
- replace(oldToken, newToken, isCaseSensitive, isReverse) {
-
- // Fix optional parameters...
- if (typeof (isCaseSensitive) === "undefined" || isCaseSensitive === null)
- isCaseSensitive = false;
-
- if (typeof (isReverse) === "undefined" || isReverse === null)
- isReverse = false;
-
- if (this.isSelected(oldToken, isCaseSensitive) === false) {
- if (this.find(oldToken, isCaseSensitive, isReverse) === false)
- return false;
- }
-
- this.cm.replaceSelection(newToken);
-
- return true;
- }
-
- /**
- * Callback handler for code mirror. Do not invoke unless you know what you are doing.
- *
- * @param {CodeMirror} cm
- * a reference to the code mirror instance
- * @param {LineHandle} line
- * the current line
- * @param {Element} element
- * the dom element which represents the line
- */
- onRenderLine(cm, line, element) {
- const charWidth = this.cm.defaultCharWidth();
- const basePadding = 4;
-
- const off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
- element.style.textIndent = "-" + off + "px";
- element.style.paddingLeft = (basePadding + off) + "px";
- }
-
- /**
- * On Change callback handler for codemirror
- * Do not invoke unless you know what you are doing.
- */
- onChange() {
-
- if (this.syntaxCheckEnabled === false)
- return;
-
- // reset the timer...
- if (this.timeout !== null) {
- clearTimeout(this.timeout);
- this.timeout = null;
- }
-
- this.timeout = setTimeout(() => { this.checkScript(); }, COMPILE_DELAY);
- }
-
- /**
- * On Active Line Change callback handler for codemirror.
- * Do not invoke unless you know what you are doing.
- */
- onActiveLineChange() {
- const currentLine = this.cm.getLineHandle(this.cm.getCursor().line);
-
- if (currentLine === this.activeLine)
- return;
-
- if (this.activeLine)
- this.cm.removeLineClass(this.activeLine, "background", "activeline");
-
- this.activeLine = this.cm.addLineClass(currentLine, "background", "activeline");
- }
-
- /**
- * Enables checking for syntax errors
- */
- async enableSyntaxCheck() {
- this.syntaxCheckEnabled = true;
- this.checkScript();
-
- this.focus();
-
- await this.getController().setPreference("syntax-check", this.syntaxCheckEnabled);
- }
-
- /**
- * Disables checking for syntax errors
- */
- async disableSyntaxCheck() {
- this.syntaxCheckEnabled = false;
- this.hideSyntaxErrors();
-
- this.focus();
-
- await this.getController().setPreference("syntax-check", this.syntaxCheckEnabled);
-
- // reset the timer...
- if (this.timeout === null)
- return;
-
- clearTimeout(this.timeout);
- this.timeout = null;
- }
-
- /**
- * Checks if syntax checking is enabled
- * @returns {boolean}
- * true in case syntax check is enabled otherwise false
- */
- isSyntaxCheckEnabled() {
- return this.syntaxCheckEnabled;
- }
-
- /**
- * Shows a message box with the given syntax errors
- * @param {string} errors
- * the errors which should be displayed
- */
- showSyntaxErrors(errors) {
- const msg = document.querySelector("#sieve-editor-msg");
- msg.style.display = '';
-
- const details = msg.querySelector(".sieve-editor-msg-details");
- while (details.firstChild)
- details.removeChild(details.firstChild);
-
- details.textContent = errors;
- }
-
- /**
- * Hides the syntax errors.
- */
- hideSyntaxErrors() {
- document.querySelector("#sieve-editor-msg").style.display = 'none';
- }
-
- /**
- * Sets the editors indentation width.
- *
- * @param {int} width
- * the indentation width in characters
- * @returns {SieveEditorUI}
- * a self reference
- */
- async setIndentWidth(width) {
- width = parseInt(width, 10);
-
- if (isNaN(width))
- throw new Error("Invalid Indent width");
-
- this.cm.setOption("indentUnit", width);
- await this.getController().setPreference("indentation-width", width);
-
- return this;
- }
-
- /**
- * Returns the indentation width.
- *
- * @returns {int}
- * the indentation width in characters.
- */
- getIndentWidth() {
- return this.cm.getOption("indentUnit");
- }
-
- /**
- * Sets the indent policy.
- *
- * @param {boolean} useTabs
- * if true tabs are used for indenting otherwise spaces are used.
- * @returns {SieveEditorUI}
- * a self reference
- */
- async setIndentWithTabs(useTabs) {
- this.cm.setOption("indentWithTabs", useTabs);
-
- await this.getController().setPreference("indentation-policy", useTabs);
-
- return this;
- }
-
- /**
- * Returns the indent policy.
- *
- * @returns {boolean}
- * true in case tabs are used to indent. False if spaces are used.
- */
- getIndentWithTabs() {
- return this.cm.getOption("indentWithTabs");
- }
-
- /**
- * Sets the editor's tabulator width and persists the changed value.
- *
- * @param {int} tabSize
- * the tabulator width in characters
- * @returns {SieveEditorUI}
- * a self reference
- */
- async setTabWidth(tabSize) {
- tabSize = parseInt(tabSize, 10);
-
- if (isNaN(tabSize))
- throw new Error(`Invalid Tab width ${tabSize}`);
-
- this.cm.setOption("tabSize", tabSize);
-
- await this.getController().setPreference("tabulator-width", tabSize);
-
- return this;
- }
-
- /**
- * Gets the editor's tabulator width.
- * @returns {int}
- * the tabulator width in characters.
- */
- getTabWidth() {
- return this.cm.getOption("tabSize");
- }
-
- /**
- * @inheritdoc
- */
- async loadSettings() {
- const tabWidth = await this.getController().getPreference("tabulator-width");
- await this.setTabWidth(tabWidth);
-
- const IndentWithTabs = await this.getController().getPreference("indentation-policy");
- await this.setIndentWithTabs(IndentWithTabs);
-
- const indentWidth = await this.getController().getPreference("indentation-width");
- await this.setIndentWidth(indentWidth);
-
- const syntaxCheck = await this.getController().getPreference("syntax-check");
- if (syntaxCheck === false || syntaxCheck === "false")
- await this.disableSyntaxCheck();
- else
- await this.enableSyntaxCheck();
- }
-
- /**
- * @inheritdoc
- */
- async loadDefaultSettings() {
- const tabWidth = await this.getController().getDefaultPreference("tabulator-width");
- await this.setTabWidth(tabWidth);
-
- const IndentWithTabs = await this.getController().getDefaultPreference("indentation-policy");
- await this.setIndentWithTabs(IndentWithTabs);
-
- const indentWidth = await this.getController().getDefaultPreference("indentation-width");
- await this.setIndentWidth(indentWidth);
-
- const syntaxCheck = await this.getController().getDefaultPreference("syntax-check");
- if (syntaxCheck === false)
- await this.disableSyntaxCheck();
- else
- await this.enableSyntaxCheck();
-
- await this.renderSettings();
- }
-
- /**
- * @inheritdoc
- */
- async saveDefaultSettings() {
- await this.getController().setDefaultPreference("tabulator-width", this.getTabWidth());
-
- await this.getController().setDefaultPreference("indentation-policy", this.getIndentWithTabs());
- await this.getController().setDefaultPreference("indentation-width", this.getIndentWidth());
-
- await this.getController().setDefaultPreference("syntax-check", this.isSyntaxCheckEnabled());
- }
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveTextEditorUI = SieveTextEditorUI;
- else
- exports.SieveTextEditorUI = SieveTextEditorUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/editor/text/SieveTextEditor.mjs b/src/common/managesieve.ui/editor/text/SieveTextEditor.mjs
new file mode 100644
index 00000000..49b87302
--- /dev/null
+++ b/src/common/managesieve.ui/editor/text/SieveTextEditor.mjs
@@ -0,0 +1,752 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global CodeMirror */
+
+import { SieveTemplate } from "./../../utils/SieveTemplate.mjs";
+import { SieveAbstractEditorUI } from "./../SieveAbstractEditor.mjs";
+
+const COMPILE_DELAY = 500;
+const EDITOR_SCROLL_INTO_VIEW_OFFSET = 200;
+
+/**
+ * An text editor ui for sieve scripts.
+ */
+class SieveTextEditorUI extends SieveAbstractEditorUI {
+
+ /**
+ * Creates a new text editor UI.
+ *
+ * @param {SieveEditorController} controller
+ * The controller which is assigned to this editor.
+ * @param {string} [id]
+ * An optional id, which points to a the textbox, which will be converted
+ * into a code mirror input. In case it is omitted the id "code" will be used.
+ */
+ constructor(controller, id) {
+
+ super(controller);
+
+ if (typeof (id) === "undefined" || id === null)
+ this.id = "code";
+
+ this.syntaxCheckEnabled = false;
+ this.timeout = null;
+
+ this.cm = null;
+
+ this.activeLine = null;
+
+ this.changed = false;
+ }
+
+ /**
+ * Renders the text editors settings
+ */
+ async renderSettings() {
+
+
+ const loader = new SieveTemplate();
+
+ // Syntax Checks
+ document
+ .querySelector("#sieve-content-settings")
+ .append(await loader.load("./editor/text/editor.settings.syntax.html"));
+
+ document
+ .querySelector("#sieve-editor-settings-synatxcheck")
+ .addEventListener("click", async () => {
+
+ if (document.querySelector("#sieve-editor-settings-synatxcheck").checked === true)
+ await this.enableSyntaxCheck();
+ else
+ await this.disableSyntaxCheck();
+ });
+
+ document.querySelector("#sieve-editor-settings-synatxcheck")
+ .checked = this.isSyntaxCheckEnabled();
+
+ // Indentation
+ document
+ .querySelector("#sieve-content-settings")
+ .append(await loader.load("./editor/text/editor.settings.indentation.html"));
+
+ // Indentation width...
+ document
+ .querySelector("#editor-settings-indentation-width")
+ .addEventListener("change", async () => {
+ await this.setIndentWidth(
+ document.querySelector("#editor-settings-indentation-width").value);
+ });
+
+ document.querySelector("#editor-settings-indentation-width")
+ .value = this.getIndentWidth();
+
+ // Indentation policy...
+ document
+ .querySelector("#editor-settings-indentation-policy-spaces")
+ .addEventListener("change", async () => { await this.setIndentWithTabs(false); });
+
+ document
+ .querySelector("#editor-settings-indentation-policy-tabs")
+ .addEventListener("change", async () => { await this.setIndentWithTabs(true); });
+
+ if (this.getIndentWithTabs())
+ document.querySelector("#editor-settings-indentation-policy-tabs").checked = true;
+ else
+ document.querySelector("#editor-settings-indentation-policy-spaces").checked = true;
+
+ // Tabulator width...
+ document
+ .querySelector("#editor-settings-tabulator-width")
+ .addEventListener("change", async () => {
+ await this.setTabWidth(
+ document.querySelector("#editor-settings-tabulator-width").value);
+ });
+
+ document.querySelector("#editor-settings-tabulator-width")
+ .value = this.getTabWidth();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async render() {
+
+ const loader = new SieveTemplate();
+
+ const editor = document.querySelector("#sieve-plaintext-editor");
+ while (editor.firstChild)
+ editor.firstChild.remove();
+
+ editor.append(
+ await loader.load("./editor/text/editor.plaintext.html"));
+
+ this.cm = CodeMirror.fromTextArea(document.querySelector(`#${this.id}`), {
+ lineNumbers: true,
+ lineWrapping: true,
+
+ theme: "eclipse",
+ matchBrackets: true,
+
+ inputStyle: "contenteditable"
+ });
+
+ this.cm.on("renderLine", (cm, line, elt) => { this.onRenderLine(cm, line, elt); });
+ this.cm.on("change", () => { this.onChange(); });
+ this.cm.on("cursorActivity", () => { this.onActiveLineChange(); });
+
+ this.cm.refresh();
+
+ // Configure tab handling...
+ this.cm.setOption("extraKeys", {
+ "Tab": function (cm) {
+
+ if (cm.somethingSelected()) {
+ const sel = cm.getSelection("\n");
+ // Indent only if there are multiple lines selected, or if the selection spans a full line
+ if (sel.length > 0 && (sel.includes("\n") || sel.length === cm.getLine(cm.getCursor().line).length)) {
+ cm.indentSelection("add");
+ return;
+ }
+ }
+
+ if (cm.options.indentWithTabs)
+ cm.execCommand("insertTab");
+ else
+ cm.execCommand("insertSoftTab");
+ },
+ "Shift-Tab": function (cm) {
+ cm.indentSelection("subtract");
+ }
+ });
+
+ const toolbar = document.querySelector("#sieve-editor-toolbar");
+ toolbar.append(
+ await loader.load("./editor/text/editor.plaintext.toolbar.html"));
+
+ document
+ .querySelector("#sieve-editor-undo")
+ .addEventListener("click", () => { this.undo(); });
+
+ document
+ .querySelector("#sieve-editor-redo")
+ .addEventListener("click", () => { this.redo(); });
+
+ document
+ .querySelector("#sieve-editor-cut")
+ .addEventListener("click", () => { this.cut(); });
+
+ document
+ .querySelector("#sieve-editor-copy")
+ .addEventListener("click", () => { this.copy(); });
+
+ document
+ .querySelector("#sieve-editor-paste")
+ .addEventListener("click", () => { this.paste(); });
+
+ document
+ .querySelector("#sieve-editor-find")
+ .addEventListener("click", () => {
+ const token = document.querySelector("#sieve-editor-txt-find").value;
+
+ const isReverse = document.querySelector("#sieve-editor-backward").checked;
+ const isCaseSensitive = document.querySelector("#sieve-editor-casesensitive").checked;
+
+ this.find(token, isCaseSensitive, isReverse);
+ });
+
+ document
+ .querySelector("#sieve-editor-replace")
+ .addEventListener("click", () => {
+ const oldToken = document.querySelector("#sieve-editor-txt-find").value;
+ const newToken = document.querySelector("#sieve-editor-txt-replace").value;
+
+ const isReverse = document.querySelector("#sieve-editor-backward").checked;
+ const isCaseSensitive = document.querySelector("#sieve-editor-casesensitive").checked;
+
+ if (oldToken === "")
+ return;
+
+ this.replace(oldToken, newToken, isCaseSensitive, isReverse);
+ });
+
+
+ document
+ .querySelector("#sieve-editor-replace-replace")
+ .addEventListener("click", () => {
+ document.querySelector("#sieve-editor-find-toolbar").classList.toggle("d-none");
+ });
+
+ await this.renderSettings();
+ }
+
+ /**
+ * Returns the editor change status.
+ *
+ * @returns {boolean}
+ * true in case the document was changed otherwise false.
+ */
+ hasChanged() {
+ return this.changed;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async setScript(script) {
+ // Load a new script. It will discard the current script
+ // the cursor position is reset to defaults.
+
+ this.cm.setValue(script);
+ this.cm.setCursor({ line: 0, ch: 0 });
+
+ this.cm.refresh();
+
+ // ensure the active line cursor changed...
+ // onActiveLineChange();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getScript() {
+
+ this.focus();
+
+ const script = this.cm.getValue();
+
+ // ... and ensure the line endings are sanitized
+ // eslint-disable-next-line no-control-regex
+ return script.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g, "\r\n");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ focus() {
+ if (this.cm)
+ this.cm.focus();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ clearHistory() {
+ this.cm.clearHistory();
+ }
+
+ /**
+ * Checks the current script for syntax errors
+ */
+ async checkScript() {
+
+ const errors = await this.getController().checkScript(await this.getScript());
+
+ if (errors && errors !== "")
+ this.showSyntaxErrors(errors);
+ else
+ this.hideSyntaxErrors();
+ }
+
+ /**
+ * Undoes the last input
+ */
+ undo() {
+ this.cm.undo();
+ this.cm.focus();
+ }
+
+ /**
+ * Redos the last input
+ */
+ redo() {
+ this.cm.redo();
+ this.cm.focus();
+ }
+
+ /**
+ * Cuts the currently selected text.
+ */
+ async cut() {
+ await this.copy();
+ this.cm.replaceSelection("");
+
+ this.cm.focus();
+ }
+
+ /**
+ * Copies the currently selected text.
+ */
+ async copy() {
+ const data = this.cm.getSelection();
+
+ await this.getController().setClipboard(data);
+
+ this.cm.focus();
+ }
+
+ /**
+ * Pastes the clipboard content into the editor.
+ */
+ async paste() {
+ const data = await this.getController().getClipboard();
+ this.cm.replaceSelection(data);
+
+ this.cm.focus();
+ }
+
+ /**
+ * Gets the selection begin
+ *
+ * @param {boolean} isReverse
+ * if true the selection is handled in reverse order.
+ * which means the selection start gets the selections end and vice versa.
+ * @returns {int}
+ * the current start position.
+ */
+ getSelectionStart(isReverse) {
+
+ const start = this.cm.getCursor(true);
+ const end = this.cm.getCursor(false);
+
+ if (isReverse) {
+ if (start.line < end.line)
+ return start;
+
+ if (start.line > end.line)
+ return end;
+
+ // start.line == end.line
+ if (start.ch > end.ch)
+ return end;
+
+ return start;
+ }
+
+
+ if (start.line > end.line)
+ return start;
+
+ if (start.line < end.line)
+ return end;
+
+ // start.line == end.line
+ if (start.ch > end.ch)
+ return start;
+
+ return end;
+
+ }
+
+ /**
+ * Finds the specified token within the editor.
+ *
+ * @param {string} token
+ * the string to find.
+ * @param {boolean} [isCaseSensitive]
+ * if true the search is case sensitive.
+ * @param {boolean} [isReverse]
+ * if true the search will be in reverse direction.
+ * @returns {boolean}
+ * true in case the the string was found otherwise false.
+ */
+ find(token, isCaseSensitive, isReverse) {
+
+ // Fix optional parameters...
+ if (typeof (isCaseSensitive) === "undefined" || isCaseSensitive === null)
+ isCaseSensitive = false;
+
+ if (typeof (isReverse) === "undefined" || isReverse === null)
+ isReverse = false;
+
+ let cursor = this.cm.getSearchCursor(
+ token,
+ this.getSelectionStart(isReverse),
+ !isCaseSensitive);
+
+ if (!cursor.find(isReverse)) {
+ // warp search at top or bottom
+ cursor = this.cm.getSearchCursor(
+ token,
+ isReverse ? { line: this.cm.lineCount() - 1 } : { line: 0, ch: 0 },
+ !isCaseSensitive);
+
+ if (!cursor.find(isReverse))
+ return false;
+ }
+
+ if (isReverse)
+ this.cm.setSelection(cursor.from(), cursor.to());
+ else
+ this.cm.setSelection(cursor.to(), cursor.from());
+
+ this.cm.scrollIntoView(cursor.to(), EDITOR_SCROLL_INTO_VIEW_OFFSET);
+
+ return true;
+ }
+
+ /**
+ * Checks if the specified token is selected.
+ *
+ * @param {string} token
+ * the token
+ * @param {boolean} isCaseSensitive
+ * true in case the check should be case insensitive.
+ * @returns {boolean}
+ * true in case the token was found otherwise false.
+ */
+ isSelected(token, isCaseSensitive) {
+ let selection = this.cm.getSelection();
+
+ if (isCaseSensitive) {
+ selection = selection.toLowerCase();
+ token = token.toLocaleLowerCase();
+ }
+
+ if (selection !== token)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Replaces the old token with the new token.
+ *
+ * @param {string} oldToken
+ * the old token which should be replaced
+ * @param {string} newToken
+ * the new token
+ * @param {boolean} [isCaseSensitive]
+ * if true the search is case sensitive.
+ * @param {boolean} [isReverse]
+ * if true the search will be in reverse direction.
+ * @returns {boolean}
+ * true if the string was replaced, otherwise false.
+ */
+ replace(oldToken, newToken, isCaseSensitive, isReverse) {
+
+ // Fix optional parameters...
+ if (typeof (isCaseSensitive) === "undefined" || isCaseSensitive === null)
+ isCaseSensitive = false;
+
+ if (typeof (isReverse) === "undefined" || isReverse === null)
+ isReverse = false;
+
+ if (this.isSelected(oldToken, isCaseSensitive) === false) {
+ if (this.find(oldToken, isCaseSensitive, isReverse) === false)
+ return false;
+ }
+
+ this.cm.replaceSelection(newToken);
+
+ return true;
+ }
+
+ /**
+ * Callback handler for code mirror. Do not invoke unless you know what you are doing.
+ *
+ * @param {CodeMirror} cm
+ * a reference to the code mirror instance
+ * @param {LineHandle} line
+ * the current line
+ * @param {Element} element
+ * the dom element which represents the line
+ */
+ onRenderLine(cm, line, element) {
+ const charWidth = this.cm.defaultCharWidth();
+ const basePadding = 4;
+
+ const off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
+ element.style.textIndent = "-" + off + "px";
+ element.style.paddingLeft = (basePadding + off) + "px";
+ }
+
+ /**
+ * On Change callback handler for codemirror
+ * Do not invoke unless you know what you are doing.
+ */
+ onChange() {
+
+ if (this.syntaxCheckEnabled === false)
+ return;
+
+ // reset the timer...
+ if (this.timeout !== null) {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
+
+ this.timeout = setTimeout(() => { this.checkScript(); }, COMPILE_DELAY);
+ }
+
+ /**
+ * On Active Line Change callback handler for codemirror.
+ * Do not invoke unless you know what you are doing.
+ */
+ onActiveLineChange() {
+ const currentLine = this.cm.getLineHandle(this.cm.getCursor().line);
+
+ if (currentLine === this.activeLine)
+ return;
+
+ if (this.activeLine)
+ this.cm.removeLineClass(this.activeLine, "background", "activeline");
+
+ this.activeLine = this.cm.addLineClass(currentLine, "background", "activeline");
+ }
+
+ /**
+ * Enables checking for syntax errors
+ */
+ async enableSyntaxCheck() {
+ this.syntaxCheckEnabled = true;
+ this.checkScript();
+
+ this.focus();
+
+ await this.getController().setPreference("syntax-check", this.syntaxCheckEnabled);
+ }
+
+ /**
+ * Disables checking for syntax errors
+ */
+ async disableSyntaxCheck() {
+ this.syntaxCheckEnabled = false;
+ this.hideSyntaxErrors();
+
+ this.focus();
+
+ await this.getController().setPreference("syntax-check", this.syntaxCheckEnabled);
+
+ // reset the timer...
+ if (this.timeout === null)
+ return;
+
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
+
+ /**
+ * Checks if syntax checking is enabled
+ * @returns {boolean}
+ * true in case syntax check is enabled otherwise false
+ */
+ isSyntaxCheckEnabled() {
+ return this.syntaxCheckEnabled;
+ }
+
+ /**
+ * Shows a message box with the given syntax errors
+ * @param {string} errors
+ * the errors which should be displayed
+ */
+ showSyntaxErrors(errors) {
+ const msg = document.querySelector("#sieve-editor-msg");
+ msg.style.display = '';
+
+ const details = msg.querySelector(".sieve-editor-msg-details");
+ while (details.firstChild)
+ details.firstChild.remove();
+
+ details.textContent = errors;
+ }
+
+ /**
+ * Hides the syntax errors.
+ */
+ hideSyntaxErrors() {
+ document.querySelector("#sieve-editor-msg").style.display = 'none';
+ }
+
+ /**
+ * Sets the editors indentation width.
+ *
+ * @param {int} width
+ * the indentation width in characters
+ * @returns {SieveEditorUI}
+ * a self reference
+ */
+ async setIndentWidth(width) {
+ width = Number.parseInt(width, 10);
+
+ if (Number.isNaN(width))
+ throw new Error("Invalid Indent width");
+
+ this.cm.setOption("indentUnit", width);
+ await this.getController().setPreference("indentation-width", width);
+
+ return this;
+ }
+
+ /**
+ * Returns the indentation width.
+ *
+ * @returns {int}
+ * the indentation width in characters.
+ */
+ getIndentWidth() {
+ return this.cm.getOption("indentUnit");
+ }
+
+ /**
+ * Sets the indent policy.
+ *
+ * @param {boolean} useTabs
+ * if true tabs are used for indenting otherwise spaces are used.
+ * @returns {SieveEditorUI}
+ * a self reference
+ */
+ async setIndentWithTabs(useTabs) {
+ this.cm.setOption("indentWithTabs", useTabs);
+
+ await this.getController().setPreference("indentation-policy", useTabs);
+
+ return this;
+ }
+
+ /**
+ * Returns the indent policy.
+ *
+ * @returns {boolean}
+ * true in case tabs are used to indent. False if spaces are used.
+ */
+ getIndentWithTabs() {
+ return this.cm.getOption("indentWithTabs");
+ }
+
+ /**
+ * Sets the editor's tabulator width and persists the changed value.
+ *
+ * @param {int} tabSize
+ * the tabulator width in characters
+ * @returns {SieveEditorUI}
+ * a self reference
+ */
+ async setTabWidth(tabSize) {
+ tabSize = Number.parseInt(tabSize, 10);
+
+ if (Number.isNaN(tabSize))
+ throw new Error(`Invalid Tab width ${tabSize}`);
+
+ this.cm.setOption("tabSize", tabSize);
+
+ await this.getController().setPreference("tabulator-width", tabSize);
+
+ return this;
+ }
+
+ /**
+ * Gets the editor's tabulator width.
+ * @returns {int}
+ * the tabulator width in characters.
+ */
+ getTabWidth() {
+ return this.cm.getOption("tabSize");
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async loadSettings() {
+ const tabWidth = await this.getController().getPreference("tabulator-width");
+ await this.setTabWidth(tabWidth);
+
+ const IndentWithTabs = await this.getController().getPreference("indentation-policy");
+ await this.setIndentWithTabs(IndentWithTabs);
+
+ const indentWidth = await this.getController().getPreference("indentation-width");
+ await this.setIndentWidth(indentWidth);
+
+ const syntaxCheck = await this.getController().getPreference("syntax-check");
+ if (syntaxCheck === false || syntaxCheck === "false")
+ await this.disableSyntaxCheck();
+ else
+ await this.enableSyntaxCheck();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async loadDefaultSettings() {
+ const tabWidth = await this.getController().getDefaultPreference("tabulator-width");
+ await this.setTabWidth(tabWidth);
+
+ const IndentWithTabs = await this.getController().getDefaultPreference("indentation-policy");
+ await this.setIndentWithTabs(IndentWithTabs);
+
+ const indentWidth = await this.getController().getDefaultPreference("indentation-width");
+ await this.setIndentWidth(indentWidth);
+
+ const syntaxCheck = await this.getController().getDefaultPreference("syntax-check");
+ if (syntaxCheck === false)
+ await this.disableSyntaxCheck();
+ else
+ await this.enableSyntaxCheck();
+
+ await this.renderSettings();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async saveDefaultSettings() {
+ await this.getController().setDefaultPreference("tabulator-width", this.getTabWidth());
+
+ await this.getController().setDefaultPreference("indentation-policy", this.getIndentWithTabs());
+ await this.getController().setDefaultPreference("indentation-width", this.getIndentWidth());
+
+ await this.getController().setDefaultPreference("syntax-check", this.isSyntaxCheckEnabled());
+ }
+
+}
+
+export { SieveTextEditorUI };
diff --git a/src/common/managesieve.ui/editor/text/editor.plaintext.toolbar.html b/src/common/managesieve.ui/editor/text/editor.plaintext.toolbar.html
index 6494f889..f8993369 100644
--- a/src/common/managesieve.ui/editor/text/editor.plaintext.toolbar.html
+++ b/src/common/managesieve.ui/editor/text/editor.plaintext.toolbar.html
@@ -1,7 +1,7 @@
<div>
<div id="sieve-plaintext-editor-toolbar">
<div>
- <div class="btn-group mr-2" role="group">
+ <div class="btn-group me-2" role="group">
<button id="sieve-editor-cut" type="button" class="btn btn-sm btn-outline-secondary"
data-i18n="texteditor.cut"></button>
<button id="sieve-editor-copy" type="button" class="btn btn-sm btn-outline-secondary"
@@ -10,19 +10,19 @@
data-i18n="texteditor.paste"></button>
</div>
- <div class="btn-group mr-2 " role="group">
+ <div class="btn-group me-2 " role="group">
<button id="sieve-editor-undo" type="button" class="btn btn-sm btn-outline-secondary"
data-i18n="texteditor.undo"></button>
<button id="sieve-editor-redo" type="button" class="btn btn-sm btn-outline-secondary"
data-i18n="texteditor.redo"></button>
</div>
- <div class="btn-group mr-2 " role="group">
+ <div class="btn-group me-2 " role="group">
<button id="sieve-editor-replace-replace" type="button" class="btn btn-sm btn-outline-secondary"
data-i18n="texteditor.findAndReplace"></button>
</div>
- <a class="btn-group mr-2 btn btn-sm btn-outline-secondary"
+ <a class="btn-group me-2 btn btn-sm btn-outline-secondary"
href="https://thsmi.github.io/sieve-reference/en/index.html" data-i18n="texteditor.reference" target="_blank"
role="button"></a>
diff --git a/src/common/managesieve.ui/editor/text/editor.settings.indentation.tpl b/src/common/managesieve.ui/editor/text/editor.settings.indentation.html
index d1dac04e..d1dac04e 100644
--- a/src/common/managesieve.ui/editor/text/editor.settings.indentation.tpl
+++ b/src/common/managesieve.ui/editor/text/editor.settings.indentation.html
diff --git a/src/common/managesieve.ui/editor/text/editor.settings.syntax.tpl b/src/common/managesieve.ui/editor/text/editor.settings.syntax.html
index becb8fc9..becb8fc9 100644
--- a/src/common/managesieve.ui/editor/text/editor.settings.syntax.tpl
+++ b/src/common/managesieve.ui/editor/text/editor.settings.syntax.html
diff --git a/src/common/managesieve.ui/i18n/en-US.json b/src/common/managesieve.ui/i18n/en-US.json
index 4b654731..dd3bfede 100644
--- a/src/common/managesieve.ui/i18n/en-US.json
+++ b/src/common/managesieve.ui/i18n/en-US.json
@@ -77,9 +77,9 @@
"credentials.sasl.default" : "Use suggested Mechanism",
"credentials.sasl.plain" : "Force plain",
"credentials.sasl.login" : "Force login (Deprecated)",
- "credentials.sasl.crammd5" : "Force CRAM-MD5",
"credentials.sasl.scramsha1" : "Force SCRAM-SHA-1",
"credentials.sasl.scramsha256" : "Force SCRAM-SHA-256",
+ "credentials.sasl.scramsha512" : "Force SCRAM-SHA-512",
"credentials.sasl.external" : "Force External",
"credentials.sasl.none" : "No authentication",
"credentials.username" : "Username",
@@ -140,11 +140,9 @@
"authorization.accept" : "Authorize",
"cert.title" : "Security alert",
- "cert.description1" : "Your mail server's authenticity cannot be verified!",
- "cert.description2" : "Someone might try to impersonate your mail server.",
- "cert.error" : "The validation failed with the following error message:",
- "cert.fingerprint" : "You need to verify manually, if the fingerprint matches your mailserver's fingerprint:",
- "cert.warning" : "Continue only if the fingerprints match and the error message is reasonable!",
+ "cert.description1" : "Your mail server's authenticity could not be verified!",
+ "cert.warning" : "Continue only if the fingerprints match your mail server's fingerprints and the error message is reasonable.",
+ "cert.warning2" : "Someone might have impersonated your mail server!",
"cert.accept" : "Continue",
"script.create.title" : "Create Script",
@@ -234,11 +232,15 @@
"debug.transport.states" : "Exceptions and State Machine Information",
"debug.transport.rawdump" : "Raw Dump/Dump Byte Stream",
"debug.transport.session" : "Session management",
+ "debug.transport.trace" : "Print full stack trace for log messages",
"debug.global.title" : "Global",
"debug.global.description" : "Used to log and debug the app's UI and rendering. The settings are global and apply to all accounts after restarting the app.",
"debug.global.actions" : "User Events and Actions",
"debug.global.ipc" : "IPC Messages",
"debug.global.widgets" : "Widgets",
- "debug.global.i18n" : "Internationalization (I18n)"
-} \ No newline at end of file
+ "debug.global.i18n" : "Internationalization (I18n)",
+
+ "debug.console.show" : "Show Console",
+ "debug.ui.reload" : "Reload UI"
+}
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.js b/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.js
deleted file mode 100644
index 6f102080..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SievePrefManager } = require('./SievePrefManager.js');
-
- const { SieveAuthorization } = require("./SieveAuthorizationSettings.js");
- const { SieveAuthentication } = require("./SieveAuthenticationSettings.js");
- const { SieveSecurity } = require("./SieveSecuritySettings.js");
- const { SieveHost } = require("./SieveHostSettings.js");
- const { SieveAccountSettings } = require("./SieveAccountSettings.js");
- const { SieveEditorSettings } = require("./SieveEditorSettings.js");
-
- const { SieveLogger } = require("./../../utils/SieveLogger.js");
-
- /**
- * Abstract implementation for managing an account's preferences.
- */
- class SieveAbstractAccount {
-
- /**
- * Creates a new instance.
- *
- * @param {string} id
- * the account's unique id.
- */
- constructor(id) {
- this.id = id;
-
- this.preferences = new SievePrefManager(`@${id}`);
-
- this.host = new SieveHost(this);
- this.authentication = new SieveAuthentication(this);
- this.authorization = new SieveAuthorization(this);
- this.security = new SieveSecurity(this);
- this.common = new SieveAccountSettings(this);
- }
-
- /**
- * Gets an instance to the logger.
- *
- * @returns {SieveLogger}
- * an reference to the logger instance.
- **/
- getLogger() {
- return SieveLogger.getInstance();
- }
-
- /**
- * Returns the account's unique id.
- * @returns {string}
- * the account unique id.
- */
- getId() {
- return this.id;
- }
-
- /**
- * Returns a reference to the preference manager for this account.
- *
- * @returns {SievePrefManager}
- * the reference to the preference manager
- */
- getConfig() {
- return this.preferences;
- }
-
- /**
- * Contains information about the server settings like
- * the hostname, port etc.
- *
- * @returns {SieveHost}
- * the current host settings
- **/
- async getHost() {
- return await this.host.get();
- }
-
- /**
- * Host alls security related setting like the SASL
- * mechanisms or the tls configuration.
- *
- * @returns {SieveSecurity}
- * the current security settings
- */
- getSecurity() {
- return this.security;
- }
-
- /**
- * Defines which authentication configuration is active.
- *
- * An account may support multiple concurrent configurations
- * from which only one can be active.
- *
- * E.g using Thunderbird's account settings
- * on user specified settings.
- *
- * @param {int} type
- * the authentication type which should be activated.
- *
- * @returns {SieveAbstractAccount}
- * a self reference
- */
- async setAuthentication(type) {
- await this.authentication.setMechanism(type);
- return this;
- }
-
- /**
- * Gets the authentication configuration
- *
- * @param {int} [type]
- * optional the configuration type. If omitted the default type is returned.
- * @returns {SieveAbstractAuthentication}
- * the object managing the authentication for the type.
- */
- async getAuthentication(type) {
- return await this.authentication.get(type);
- }
-
- /**
- * Defines which authorization configuration is active.
- *
- * @param {int} type
- * the authorization type which should be activated.
- * @returns {SieveAbstractAccount}
- * a self reference
- */
- async setAuthorization(type) {
- await this.authorization.setMechanism(type);
- return this;
- }
-
- /**
- * Gets the authorization configuration.
- *
- * @param {SieveAbstractAuthorization} [type]
- * optional authorization type. In case it is omitted
- * default settings are returned.
- * @returns {SieveAbstractAuthorization}
- * the object managing the authorization for the type
- */
- async getAuthorization(type) {
- return await this.authorization.get(type);
- }
-
- /**
- * Gets miscellaneous account specific settings like the log levels etc.
- *
- * @returns {SieveAccountSettings}
- * the object managing the miscellaneous account settings
- */
- getSettings() {
- return this.common;
- }
-
- /**
- * Gets the object managing the accounts editor's settings.
- *
- * @returns {SieveEditorSettings}
- * the settings object
- */
- getEditor() {
- return new SieveEditorSettings(
- new SievePrefManager(this.getConfig().getNamespace()));
- }
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractAccount = SieveAbstractAccount;
- else
- exports.SieveAbstractAccount = SieveAbstractAccount;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.mjs
new file mode 100644
index 00000000..4b7073c7
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractAccount.mjs
@@ -0,0 +1,154 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SievePrefManager } from "./SievePrefManager.mjs";
+
+import { SieveAuthorization } from "./SieveAuthorization.mjs";
+import { SieveAuthentication } from "./SieveAuthentication.mjs";
+import { SieveSecurity } from "./SieveSecurity.mjs";
+import { SieveHost } from "./SieveHost.mjs";
+import { SieveAccountSettings } from "./SieveAccountSettings.mjs";
+import { SieveEditorSettings } from "./SieveEditorSettings.mjs";
+
+import { SieveLogger } from "./../../utils/SieveLogger.mjs";
+/**
+ * Abstract implementation for managing an account's preferences.
+ */
+class SieveAbstractAccount {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {string} id
+ * the account's unique id.
+ */
+ constructor(id) {
+ this.id = id;
+
+ this.preferences = new SievePrefManager(`@${id}`);
+
+ this.host = new SieveHost(this);
+ this.authentication = new SieveAuthentication(this);
+ this.authorization = new SieveAuthorization(this);
+ this.security = new SieveSecurity(this);
+ this.common = new SieveAccountSettings(this);
+ }
+
+ /**
+ * Gets an instance to the logger.
+ *
+ * @returns {SieveLogger}
+ * an reference to the logger instance.
+ **/
+ getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+ /**
+ * Returns the account's unique id.
+ * @returns {string}
+ * the account unique id.
+ */
+ getId() {
+ return this.id;
+ }
+
+ /**
+ * Returns a reference to the preference manager for this account.
+ *
+ * @returns {SievePrefManager}
+ * the reference to the preference manager
+ */
+ getConfig() {
+ return this.preferences;
+ }
+
+ /**
+ * Contains information about the server settings like
+ * the hostname, port etc.
+ *
+ * @returns {SieveHost}
+ * the current host settings
+ **/
+ getHost() {
+ return this.host;
+ }
+
+ /**
+ * Host alls security related setting like the SASL
+ * mechanisms or the tls configuration.
+ *
+ * @returns {SieveSecurity}
+ * the current security settings
+ */
+ getSecurity() {
+ return this.security;
+ }
+
+ /**
+ * Gets the authentication configuration
+ *
+ * @returns {SieveAbstractAuthentication}
+ * the object managing the authentication for the type.
+ */
+ getAuthentication() {
+ return this.authentication;
+ }
+
+ /**
+ * Defines which authorization configuration is active.
+ *
+ * @param {int} type
+ * the authorization type which should be activated.
+ * @returns {SieveAbstractAccount}
+ * a self reference
+ */
+ async setAuthorization(type) {
+ await this.authorization.setMechanism(type);
+ return this;
+ }
+
+ /**
+ * Gets the authorization configuration.
+ *
+ * @param {SieveAbstractAuthorization} [type]
+ * optional authorization type. In case it is omitted
+ * default settings are returned.
+ * @returns {SieveAbstractAuthorization}
+ * the object managing the authorization for the type
+ */
+ async getAuthorization(type) {
+ return await this.authorization.get(type);
+ }
+
+ /**
+ * Gets miscellaneous account specific settings like the log levels etc.
+ *
+ * @returns {SieveAccountSettings}
+ * the object managing the miscellaneous account settings
+ */
+ getSettings() {
+ return this.common;
+ }
+
+ /**
+ * Gets the object managing the accounts editor's settings.
+ *
+ * @returns {SieveEditorSettings}
+ * the settings object
+ */
+ getEditor() {
+ return new SieveEditorSettings(
+ new SievePrefManager(this.getConfig().getNamespace()));
+ }
+}
+
+export { SieveAbstractAccount };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.js b/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.js
deleted file mode 100644
index 943709dd..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.js
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-
-(function (exports) {
-
- "use strict";
-
- const CONFIG_ID_GLOBAL = "global";
- const CONFIG_KEY_LOG_LEVEL = "loglevel";
-
- const DEFAULT_LOG_LEVEL = 0;
-
- const { SieveUniqueId } = require("./../../utils/SieveUniqueId.js");
- const { SievePrefManager } = require('./SievePrefManager.js');
- const { SieveEditorSettings } = require("./SieveEditorSettings.js");
-
- /**
- * Abstract class which manages sieve accounts.
- */
- class SieveAbstractAccounts {
-
- /**
- * Creates a new instance
- */
- constructor() {
- this.accounts = {};
- }
-
- /**
- * Loads the list of accounts configurations.
- * @abstract
- *
- * @returns {SieveAccounts}
- * a self reference.
- */
- // eslint-disable-next-line require-await
- async load() {
- throw new Error("Implement me");
- }
-
- /**
- * Generates a pseudo unique id.
- * The id is guaranteed to be made of alphanumerical characters and dashes.
- *
- * @returns {string}
- * the unique id in string representation.
- */
- generateId() {
- return (new SieveUniqueId()).generate();
- }
-
- /**
- * Returns a list with all accounts.
- * The accounts are returned as key value pairs (unique id and Account)
- *
- * @returns { object<string, SieveAccount>}
- * a list with sieve account.
- */
- getAccountIds() {
- return Object.keys(this.accounts);
- }
-
- /**
- * Returns a specific sieve account
- * @param {string} id
- * the accounts unique id.
- * @returns {SieveAccount}
- * the sieve account or undefined.
- */
- getAccountById(id) {
- return this.accounts[id];
- }
-
- /**
- * Sets the global log level.
- *
- * @param {int} level
- * the global log level as integer.
- * @returns {SieveAccounts}
- * a self reference.
- */
- async setLogLevel(level) {
- await (new SievePrefManager(CONFIG_ID_GLOBAL)).setInteger(CONFIG_KEY_LOG_LEVEL, level);
- return this;
- }
-
- /**
- * Gets the global log level.
- *
- * @returns {int}
- * the log level as integer.
- */
- async getLogLevel() {
- return await (new SievePrefManager(CONFIG_ID_GLOBAL))
- .getInteger(CONFIG_KEY_LOG_LEVEL, DEFAULT_LOG_LEVEL);
- }
-
- /**
- * Gets the object managing the editor's default settings.
- *
- * @returns {SieveEditorSettings}
- * the settings object
- */
- getEditor() {
- return new SieveEditorSettings(new SievePrefManager("defaults"));
- }
-
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractAccounts = SieveAbstractAccounts;
- else
- exports.SieveAbstractAccounts = SieveAbstractAccounts;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.mjs
new file mode 100644
index 00000000..a9a51b92
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractAccounts.mjs
@@ -0,0 +1,114 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+const CONFIG_ID_GLOBAL = "global";
+const CONFIG_KEY_LOG_LEVEL = "loglevel";
+
+const DEFAULT_LOG_LEVEL = 0;
+
+import { SieveUniqueId } from "./../../utils/SieveUniqueId.mjs";
+import { SievePrefManager } from "./SievePrefManager.mjs";
+import { SieveEditorSettings } from "./SieveEditorSettings.mjs";
+
+/**
+ * Abstract class which manages sieve accounts.
+ */
+class SieveAbstractAccounts {
+
+ /**
+ * Creates a new instance
+ */
+ constructor() {
+ this.accounts = {};
+ }
+
+ /**
+ * Loads the list of accounts configurations.
+ * @abstract
+ *
+ * @returns {SieveAccounts}
+ * a self reference.
+ */
+ async load() {
+ throw new Error("Implement me");
+ }
+
+ /**
+ * Generates a pseudo unique id.
+ * The id is guaranteed to be made of alphanumerical characters and dashes.
+ *
+ * @returns {string}
+ * the unique id in string representation.
+ */
+ generateId() {
+ return (new SieveUniqueId()).generate();
+ }
+
+ /**
+ * Returns a list with all accounts.
+ * The accounts are returned as key value pairs (unique id and Account)
+ *
+ * @returns { object<string, SieveAccount>}
+ * a list with sieve account.
+ */
+ getAccountIds() {
+ return Object.keys(this.accounts);
+ }
+
+ /**
+ * Returns a specific sieve account
+ * @param {string} id
+ * the accounts unique id.
+ * @returns {SieveAccount}
+ * the sieve account or undefined.
+ */
+ getAccountById(id) {
+ return this.accounts[id];
+ }
+
+ /**
+ * Sets the global log level.
+ *
+ * @param {int} level
+ * the global log level as integer.
+ * @returns {SieveAccounts}
+ * a self reference.
+ */
+ async setLogLevel(level) {
+ await (new SievePrefManager(CONFIG_ID_GLOBAL)).setInteger(CONFIG_KEY_LOG_LEVEL, level);
+ return this;
+ }
+
+ /**
+ * Gets the global log level.
+ *
+ * @returns {int}
+ * the log level as integer.
+ */
+ async getLogLevel() {
+ return await (new SievePrefManager(CONFIG_ID_GLOBAL))
+ .getInteger(CONFIG_KEY_LOG_LEVEL, DEFAULT_LOG_LEVEL);
+ }
+
+ /**
+ * Gets the object managing the editor's default settings.
+ *
+ * @returns {SieveEditorSettings}
+ * the settings object
+ */
+ getEditor() {
+ return new SieveEditorSettings(new SievePrefManager("defaults"));
+ }
+
+}
+
+export { SieveAbstractAccounts };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.js b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.js
deleted file mode 100644
index 63134b12..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /**
- * An base class common for all authentication mechanisms
- **/
- class SieveAbstractAuthentication {
-
- /**
- * Create a new instance.
- *
- * @param {int} type
- * the accounts unique identifier.
- * @param {SieveAccount} account
- * a reference to the parent sieve account.
- */
- constructor(type, account) {
- this.type = type;
- this.account = account;
- }
-
- /**
- * Returns the password for the chosen mechanism.
- *
- * Not all mechanisms require a password.
- * Others e.g. when prompting do not always return a password.
- *
- * @abstract
- *
- * @returns {string | null}
- * the password or null e.g. when the password prompt was dismissed.
- **/
- // eslint-disable-next-line require-await
- async getPassword() {
- throw new Error("Implement getPassword");
- }
-
- /**
- * Returns the username for the account.
- *
- * @abstract
- *
- * @returns {string}
- * the username as string.
- **/
- // eslint-disable-next-line require-await
- async getUsername() {
- throw new Error("Implement getUsername");
- }
-
- /**
- * Each authentication type has an unique identifier.
- *
- * @returns {int}
- * the identifier as int.
- */
- getType() {
- return this.type;
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractAuthentication = SieveAbstractAuthentication;
- else
- exports.SieveAbstractAuthentication = SieveAbstractAuthentication;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.mjs
new file mode 100644
index 00000000..1755cb1b
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthentication.mjs
@@ -0,0 +1,55 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/**
+ * An base class common for all authentication mechanisms
+ **/
+class SieveAbstractAuthentication {
+
+ /**
+ * Create a new instance.
+ *
+ * @param {SieveAccount} account
+ * a reference to the parent sieve account.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Returns the password for the chosen mechanism.
+ *
+ * Not all mechanisms require a password.
+ * Others e.g. when prompting do not always return a password.
+ *
+ * @abstract
+ *
+ * @returns {string | null}
+ * the password or null e.g. when the password prompt was dismissed.
+ **/
+ async getPassword() {
+ throw new Error("Implement getPassword()");
+ }
+
+ /**
+ * Returns the username for the account.
+ *
+ * @abstract
+ *
+ * @returns {string}
+ * the username as string.
+ **/
+ async getUsername() {
+ throw new Error("Implement getUsername()");
+ }
+}
+
+export { SieveAbstractAuthentication };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.js b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.js
deleted file mode 100644
index a8807524..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const CONFIG_AUTHORIZATION_USERNAME = "sasl.authorization.username";
- const DEFAULT_AUTHORIZATION = "";
-
- /**
- * An base class common for all authorization mechanism
- **/
- class SieveAbstractAuthorization {
-
- /**
- * Create a new instance.
- *
- * @param {int} type
- * the account's unique identifier.
- * @param {SieveAccount} account
- * a reference to the parent sieve account.
- */
- constructor(type, account) {
- this.account = account;
- this.type = type;
- }
-
- /**
- * Each authorization type has an unique identifier.
- *
- * @returns {int}
- * the identifier as int.
- */
- getType() {
- return this.type;
- }
-
- /**
- * Returns the authorization string.
- *
- * @abstract
- *
- * @returns {string}
- * the authorization string
- */
- getAuthorization() {
- throw new Error("Implement SieveAbstractAuthorizationMechanism::getAuthorization");
- }
- }
-
- /**
- * The easiest authorization mechanism.
- * When an empty string is passed the server should choose the most suitable authorization.
- */
- class SieveNoAuthorization extends SieveAbstractAuthorization {
-
- /**
- * Returns always an empty string. This means the server should choose the most suitable authorization.
- *
- * @returns {string}
- * an empty string.
- */
- getAuthorization() {
- return DEFAULT_AUTHORIZATION;
- }
- }
-
- /**
- * Uses for authorization the same username which was used for authentication.
- */
- class SieveDefaultAuthorization extends SieveAbstractAuthorization {
-
- /**
- * Returns the username which was used for authentication.
- *
- * @returns {string}
- * the username as string.
- */
- async getAuthorization() {
- return await (await this.account.getAuthentication()).getUsername();
- }
- }
-
- /**
- * Uses a custom authorization.
- */
- class SieveCustomAuthorization extends SieveAbstractAuthorization {
-
- /**
- * @inheritdoc
- **/
- async getAuthorization() {
- return await this.account.getConfig().getString(CONFIG_AUTHORIZATION_USERNAME, null);
- }
-
- /**
- * Sets a custom authorization.
- *
- * @param {string} authorization
- * the authorization as string.
- *
- */
- async setAuthorization(authorization) {
- if (typeof (authorization) === "undefined" || (authorization === null))
- throw new Error("Authorization can't be undefined");
-
- await this.account.getConfig().setString(CONFIG_AUTHORIZATION_USERNAME, authorization);
- }
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports) {
- module.exports.SieveAbstractAuthorization = SieveAbstractAuthorization;
-
- module.exports.SieveNoAuthorization = SieveNoAuthorization;
- module.exports.SieveCustomAuthorization = SieveCustomAuthorization;
- module.exports.SieveDefaultAuthorization = SieveDefaultAuthorization;
- } else {
- exports.SieveAbstractAuthorization = SieveAbstractAuthorization;
-
- exports.SieveNoAuthorization = SieveNoAuthorization;
- exports.SieveCustomAuthorization = SieveCustomAuthorization;
- exports.SieveDefaultAuthorization = SieveDefaultAuthorization;
- }
-
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.mjs
new file mode 100644
index 00000000..26d7aa62
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractAuthorization.mjs
@@ -0,0 +1,121 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const CONFIG_AUTHORIZATION_USERNAME = "sasl.authorization.username";
+const DEFAULT_AUTHORIZATION = "";
+
+/**
+ * An base class common for all authorization mechanism
+ **/
+class SieveAbstractAuthorization {
+
+ /**
+ * Create a new instance.
+ *
+ * @param {int} type
+ * the account's unique identifier.
+ * @param {SieveAccount} account
+ * a reference to the parent sieve account.
+ */
+ constructor(type, account) {
+ this.account = account;
+ this.type = type;
+ }
+
+ /**
+ * Each authorization type has an unique identifier.
+ *
+ * @returns {int}
+ * the identifier as int.
+ */
+ getType() {
+ return this.type;
+ }
+
+ /**
+ * Returns the authorization string.
+ *
+ * @abstract
+ *
+ * @returns {string}
+ * the authorization string
+ */
+ getAuthorization() {
+ throw new Error("Implement SieveAbstractAuthorizationMechanism::getAuthorization");
+ }
+}
+
+/**
+ * The easiest authorization mechanism.
+ * When an empty string is passed the server should choose the most suitable authorization.
+ */
+class SieveNoAuthorization extends SieveAbstractAuthorization {
+
+ /**
+ * Returns always an empty string. This means the server should choose the most suitable authorization.
+ *
+ * @returns {string}
+ * an empty string.
+ */
+ getAuthorization() {
+ return DEFAULT_AUTHORIZATION;
+ }
+}
+
+/**
+ * Uses for authorization the same username which was used for authentication.
+ */
+class SieveDefaultAuthorization extends SieveAbstractAuthorization {
+
+ /**
+ * Returns the username which was used for authentication.
+ *
+ * @returns {string}
+ * the username as string.
+ */
+ async getAuthorization() {
+ return await (await this.account.getAuthentication()).getUsername();
+ }
+}
+
+/**
+ * Uses a custom authorization.
+ */
+class SieveCustomAuthorization extends SieveAbstractAuthorization {
+
+ /**
+ * @inheritdoc
+ **/
+ async getAuthorization() {
+ return await this.account.getConfig().getString(CONFIG_AUTHORIZATION_USERNAME, null);
+ }
+
+ /**
+ * Sets a custom authorization.
+ *
+ * @param {string} authorization
+ * the authorization as string.
+ *
+ */
+ async setAuthorization(authorization) {
+ if (typeof (authorization) === "undefined" || (authorization === null))
+ throw new Error("Authorization can't be undefined");
+
+ await this.account.getConfig().setString(CONFIG_AUTHORIZATION_USERNAME, authorization);
+ }
+}
+
+export {
+ SieveAbstractAuthorization,
+ SieveNoAuthorization,
+ SieveCustomAuthorization,
+ SieveDefaultAuthorization
+};
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractHost.js b/src/common/managesieve.ui/settings/logic/SieveAbstractHost.js
deleted file mode 100644
index d3962300..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractHost.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const PORT_SIEVE_RFC = 4190;
- const PORT_SIEVE_OLD = 2000;
-
- const TYPE_RFC = 0;
- const TYPE_OLD = 1;
- const TYPE_CUSTOM = 2;
-
- const CONFIG_HOST_PORT_TYPE = "port.type";
- const CONFIG_HOST_PORT = "port";
-
- const CONFIG_KEEP_ALIVE_INTERVAL = "keepalive";
-
- // eslint-disable-next-line no-magic-numbers
- const ONE_MINUTE = 60 * 1000;
- // eslint-disable-next-line no-magic-numbers
- const FIVE_MINUTES = 5 * ONE_MINUTE;
-
-
- /**
- * An abstract implementation for the host settings.
- * They define the hostname as well as the port.
- **/
- class SieveAbstractHost {
-
- /**
- * Creates a new instance.
- *
- * @param {int} type
- * the accounts unique identifier.
- * @param {SieveAccount} account
- * a reference to the parent sieve account.
- */
- constructor(type, account) {
- this.account = account;
- this.type = type;
- }
-
- /**
- * Gets the hostname for this account
- * @abstract
- * @returns {string}
- * the hostname as string.
- */
- getHostname() {
- throw new Error("Implement getHostname");
- }
-
- /**
- * Returns the port for this configuration.
- *
- * @param {int} [type]
- * Use zero to get the standard port 4190.
- * One returns the old port 2000.
- * And two the currently configured port.
- * if omitted the port for the current type is returned
- *
- * @returns {int}
- * the port as integer for this account.
- */
- async getPort(type) {
-
- if (typeof (type) === "undefined" || type === null)
- type = await this.account.getConfig().getInteger(CONFIG_HOST_PORT_TYPE, TYPE_RFC);
-
- if (type === TYPE_CUSTOM)
- return await this.account.getConfig().getInteger(CONFIG_HOST_PORT, PORT_SIEVE_RFC);
-
- if (type === TYPE_OLD)
- return PORT_SIEVE_OLD;
-
- return PORT_SIEVE_RFC;
- }
-
- /**
- * Configures the TCP Port which sieve should use.
- *
- * @param {string} port
- * the port number as string.
- *
- * @returns {SieveAbstractHost}
- * a self reference
- */
- async setPort(port) {
- let type = TYPE_CUSTOM;
-
- if (port === PORT_SIEVE_RFC)
- type = TYPE_RFC;
- else if (port === PORT_SIEVE_OLD)
- type = TYPE_OLD;
-
- await this.account.getConfig().setInteger(CONFIG_HOST_PORT_TYPE, type);
-
- if (type !== TYPE_CUSTOM)
- return this;
-
- port = parseInt(port, 10);
-
- if (isNaN(port))
- port = PORT_SIEVE_RFC;
-
- await this.account.getConfig().setInteger(CONFIG_HOST_PORT, port);
- return this;
- }
-
- /**
- * Each host type has an unique identifier.
- *
- * @returns {int}
- * the identifier as int.
- */
- getType() {
- return this.type;
- }
-
- /**
- * Configures the maximum idle time after a message is send.
- * In case the time span elapsed an keep alive message will be
- * send to the server.
- *
- * @param {int} value
- * the maximal time in seconds. zero disables keep alive messages
- *
- * @returns {SieveAbstractHost}
- * a self reference
- */
- async setKeepAlive(value) {
- await this.account.getConfig().setInteger(CONFIG_KEEP_ALIVE_INTERVAL, value);
- return this;
- }
-
- /**
- * Gets the maximum idle time after a message is send
- * @returns {int}
- * the maximum idle time in seconds.
- * zero indicates keep alive messages are disabled
- **/
- async getKeepAlive() {
- return await this.account.getConfig().getInteger(CONFIG_KEEP_ALIVE_INTERVAL, FIVE_MINUTES);
- }
- }
-
- /**
- * This Class manages a custom host setting for a Sieve Account. Sieve Accounts
- * are identified by URIs.
- */
- class SieveCustomHost extends SieveAbstractHost {
-
- /**
- * @inheritdoc
- **/
- async getHostname() {
- return await this.account.getConfig().getString("hostname", "");
- }
-
- /**
- * Sets the custom hostname which shall be used.
- *
- * @param {string} hostname
- * the hostname or ip as string.
- *
- * @returns {SieveCustomHost}
- * a self reference
- */
- async setHostname(hostname) {
- await this.account.getConfig().setString("hostname", hostname);
- return this;
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports) {
- module.exports.SieveCustomHost = SieveCustomHost;
- module.exports.SieveAbstractHost = SieveAbstractHost;
- } else {
- exports.SieveCustomHost = SieveCustomHost;
- exports.SieveAbstractHost = SieveAbstractHost;
- }
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractHost.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractHost.mjs
new file mode 100644
index 00000000..4dacb44c
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractHost.mjs
@@ -0,0 +1,107 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const PORT_SIEVE_RFC = 4190;
+const CONFIG_HOST_PORT = "port";
+
+
+
+/**
+ * An abstract implementation for the host settings.
+ * They define the hostname as well as the port.
+ **/
+class SieveAbstractHost {
+
+ /**
+ * Gets the hostname for this account
+ * @abstract
+ *
+ * @returns {string}
+ * the hostname as string.
+ */
+ async getHostname() {
+ throw new Error("Implement getHostname");
+ }
+
+ /**
+ * Returns the port for this configuration.
+ * @abstract
+ *
+ * @returns {int}
+ * the port as integer for this account.
+ */
+ async getPort() {
+ throw new Error("Implement getPort()");
+ }
+
+ /**
+ * Gets the maximum idle time after a message is send
+ * @abstract
+ *
+ * @returns {int}
+ * the maximum idle time in seconds.
+ * zero indicates keep alive messages are disabled
+ **/
+ async getKeepAlive() {
+ throw new Error("Implement getKeepAlive()");
+ }
+}
+
+/**
+ * This Class manages a custom host setting for a Sieve Account. Sieve Accounts
+ * are identified by URIs.
+ */
+class SieveCustomHost extends SieveAbstractHost {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {SieveAccount} account
+ * a reference to the parent sieve account.
+ */
+ constructor(account) {
+ super();
+ this.account = account;
+ }
+
+
+ /**
+ * @inheritdoc
+ */
+ async getPort() {
+ return await (this.account.getConfig().getInteger(CONFIG_HOST_PORT, PORT_SIEVE_RFC));
+ }
+
+ /**
+ * Configures the TCP Port which sieve should use.
+ *
+ * @param {string} port
+ * the port number as string. In case it is no number or an invalid
+ * number it will silently fall back to the default port
+ *
+ * @returns {SieveAbstractHost}
+ * a self reference
+ */
+ async setPort(port) {
+ port = Number.parseInt(port, 10);
+
+ if (Number.isNaN(port))
+ port = PORT_SIEVE_RFC;
+
+ await this.account.getConfig().setInteger(CONFIG_HOST_PORT, port);
+ return this;
+ }
+}
+
+export {
+ SieveCustomHost,
+ SieveAbstractHost
+};
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.js b/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.js
deleted file mode 100644
index f4ed90ce..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /**
- * A union for a group of similar mechanisms.
- */
- class SieveAbstractMechanism {
-
- /**
- * Creates a new instance.
- *
- * @param {SieveAccount} account
- * a reference to the parent sieve account.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * The pref key which is used to store the information about the mechanism.
- * @abstract
- *
- * @returns {string}
- * the pref key
- **/
- getKey() {
- throw new Error("Implement getKey()");
- }
-
- /**
- * Returns the default mechanism which is used unless it is
- * overwritten by an other mechanism.
- * @abstract
- *
- * @returns {int}
- * the default mechanism
- */
- getDefault() {
- throw new Error("Implement getDefault()");
- }
-
- /**
- * Checks it the given mechanism is supported.
- * @abstract
- *
- * @param {int} mechanism
- * the authentication mechanisms unique id.
- * @returns {boolean}
- * true in case the given type is supported otherwise false.
- */
- hasMechanism(mechanism) {
- throw new Error(`Implement hasMechanism(${mechanism})`);
- }
-
- /**
- * Returns the authentication mechanism for the given type.
- * @abstract
- *
- * @param {int} mechanism
- * the authentication mechanism
- * @returns {SieveAbstractAuthentication}
- * the authentication mechanism instance.
- */
- getMechanismById(mechanism) {
- throw new Error(`Implement getMechanism(${mechanism})`);
- }
-
- /**
- * Sets the current mechanism.
- *
- * @param {int} type
- * the mechanism type's unique id.
- */
- async setMechanism(type) {
- if (typeof (type) === "undefined" || type === null)
- type = this.getDefault();
-
- if (typeof (type) === "string")
- type = Number.parseInt(type, 10);
-
- if (!this.hasMechanism(type))
- throw new Error(`Invalid mechanism ${type}`);
-
- await this.account.getConfig().setInteger(this.getKey(), type);
- }
-
- /**
- * Gets the current mechanism.
- *
- * @returns {int}
- * the mechanisms unique id.
- */
- async getMechanism() {
- return await this.account.getConfig().getInteger(this.getKey(), this.getDefault());
- }
-
- /**
- * Returns the settings for the given authentication mechanism
- *
- * @param {int} [mechanism]
- * the optional authentication mechanism's unique id.
- * If omitted the currently active authentication is returned.
- * @returns {SieveAbstractAuthentication}
- * the selected authentication mechanism.
- */
- async get(mechanism) {
- if (typeof (mechanism) === "undefined" || mechanism === null)
- mechanism = await this.getMechanism();
-
- if (!this.hasMechanism(mechanism))
- mechanism = this.getDefault();
-
- return await this.getMechanismById(mechanism);
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractMechanism = SieveAbstractMechanism;
- else
- exports.SieveAbstractMechanism = SieveAbstractMechanism;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.mjs
new file mode 100644
index 00000000..7d8192d9
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractMechanism.mjs
@@ -0,0 +1,125 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/**
+ * A union for a group of similar mechanisms.
+ */
+class SieveAbstractMechanism {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {SieveAccount} account
+ * a reference to the parent sieve account.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * The pref key which is used to store the information about the mechanism.
+ * @abstract
+ *
+ * @returns {string}
+ * the pref key
+ **/
+ getKey() {
+ throw new Error("Implement getKey()");
+ }
+
+ /**
+ * Returns the default mechanism which is used unless it is
+ * overwritten by an other mechanism.
+ * @abstract
+ *
+ * @returns {int}
+ * the default mechanism
+ */
+ getDefault() {
+ throw new Error("Implement getDefault()");
+ }
+
+ /**
+ * Checks it the given mechanism is supported.
+ * @abstract
+ *
+ * @param {int} mechanism
+ * the authentication mechanisms unique id.
+ * @returns {boolean}
+ * true in case the given type is supported otherwise false.
+ */
+ hasMechanism(mechanism) {
+ throw new Error(`Implement hasMechanism(${mechanism})`);
+ }
+
+ /**
+ * Returns the authentication mechanism for the given type.
+ * @abstract
+ *
+ * @param {int} mechanism
+ * the authentication mechanism
+ * @returns {SieveAbstractAuthentication}
+ * the authentication mechanism instance.
+ */
+ getMechanismById(mechanism) {
+ throw new Error(`Implement getMechanism(${mechanism})`);
+ }
+
+ /**
+ * Sets the current mechanism.
+ *
+ * @param {int} type
+ * the mechanism type's unique id.
+ */
+ async setMechanism(type) {
+ if (typeof (type) === "undefined" || type === null)
+ type = this.getDefault();
+
+ if (typeof (type) === "string")
+ type = Number.parseInt(type, 10);
+
+ if (!this.hasMechanism(type))
+ throw new Error(`Invalid mechanism ${type}`);
+
+ await this.account.getConfig().setInteger(this.getKey(), type);
+ }
+
+ /**
+ * Gets the current mechanism.
+ *
+ * @returns {int}
+ * the mechanisms unique id.
+ */
+ async getMechanism() {
+ return await this.account.getConfig().getInteger(this.getKey(), this.getDefault());
+ }
+
+ /**
+ * Returns the settings for the given authentication mechanism
+ *
+ * @param {int} [mechanism]
+ * the optional authentication mechanism's unique id.
+ * If omitted the currently active authentication is returned.
+ * @returns {SieveAbstractAuthentication}
+ * the selected authentication mechanism.
+ */
+ async get(mechanism) {
+ if (typeof (mechanism) === "undefined" || mechanism === null)
+ mechanism = await this.getMechanism();
+
+ if (!this.hasMechanism(mechanism))
+ mechanism = this.getDefault();
+
+ return await this.getMechanismById(mechanism);
+ }
+}
+
+export { SieveAbstractMechanism };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.js b/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.js
deleted file mode 100644
index 5fa7fc02..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.js
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /**
- * Manages preferences.
- */
- class SieveAbstractPrefManager {
-
- /**
- * Initializes the preference manager.
- *
- * @param {string} [namespace]
- * the optional preferences namespace.
- */
- constructor(namespace) {
- if ((typeof(namespace) === "undefined") || (namespace === null))
- namespace = "";
-
- this.namespace = namespace;
- }
-
- /**
- * Returns the namespace which is added to all preferences
- *
- * @returns {string}
- * the prefix as string.
- */
- getNamespace() {
- return this.namespace;
- }
-
- /**
- * Returns a specific value.
- * @abstract
- *
- * @param {string} key
- * the key which should be returned.
- * @returns {object}
- * the value or undefined in case it does not exist.
- */
- // eslint-disable-next-line require-await
- async getValue(key) {
- throw new Error(`Implement SieveAbstractPrefManager::getValue(${key})`);
- }
-
- /**
- * Sets and persists the given preference.
- * @abstract
- *
- * @param {string} key
- * the preference key which should be written.
- * @param {object} value
- * the key's value.
- * @returns {SievePrefManager}
- * a self reference.
- */
- // eslint-disable-next-line require-await
- async setValue(key, value) {
- throw new Error(`Implement SieveAbstractPrefManager::setValue(${key},${value})`);
- }
-
- /**
- * Returns the boolean value for the preference.
- *
- * @param {string} key
- * the preference's key
- * @param {boolean} [fallback]
- * the fallback value in case the key does not exist.
- * @returns {boolean}
- * the key's value as boolean
- */
- async getBoolean(key, fallback) {
- const value = await this.getValue(key);
-
- if (typeof (value) === "undefined" || value === null)
- return fallback;
-
- // Parse boolean
- if ((value === true) || (value === "true"))
- return true;
-
- if ((value === false) || (value === "false"))
- return false;
-
- return fallback;
- }
-
- /**
- * Sets a boolean value for the given key.
- *
- * @param {string} key
- * the preference's key
- * @param {boolean} value
- * the value which should be set
- * @returns {SievePrefManager}
- * a self reference
- */
- async setBoolean(key, value) {
- // ensure it is an boolean...
- value = !!value;
-
- await this.setValue(key, value);
- return this;
- }
-
- /**
- * Returns the string value for the preference.
- *
- * @param {string} key
- * the preference key
- * @param {string} [fallback]
- * the fallback value in case the key does not exist.
- * @returns {string}
- * the key's value as string
- */
- async getString(key, fallback) {
- const value = await this.getValue(key);
-
- if (typeof (value) === "undefined" || value === null)
- return fallback;
-
- return `${value}`;
- }
-
- /**
- * The string which should be set for th preference
- *
- * @param {string} key
- * the preference key
- * @param {string} value
- * the key's value as string
- * @returns {SievePrefManager}
- * a self reference
- */
- async setString(key, value) {
- await this.setValue(key, `${value}`);
- return this;
- }
-
- /**
- * Returns the integer value for the preference.
- *
- * @param {string} key
- * the preference's key
- * @param {int} [fallback]
- * the fallback value in case the key does not exist or is not a number.
- * @returns {string}
- * the key's value as integer
- */
- async getInteger(key, fallback) {
-
- let value = await this.getValue(key);
-
- if (typeof (value) === "undefined" || value === null || Number.isNaN(value))
- return fallback;
-
- if (Number.isInteger(value))
- return value;
-
- try {
- value = Number.parseInt(value, 10);
- } catch (ex) {
- return fallback;
- }
-
- if (Number.isNaN(value))
- return fallback;
-
- return value;
- }
-
- /**
- * Sets an integer value for the given key.
- *
- * @param {string} key
- * the preference's key
- * @param {int} value
- * the integer value which should be set.
- * @returns {SievePrefManager}
- * a self reference.
- */
- async setInteger(key, value) {
- await this.setValue(key, Number.parseInt(value, 10));
- return this;
- }
-
- /**
- * Saves a complex value like an object for the given key.
- * The object needs to be serializable to a json string.
- *
- * @param {string} key
- * the preference's key
- * @param {object} value
- * the complex value which should be saved.
- * @returns {SievePrefManager}
- * a self reference.
- */
- async setComplexValue(key, value) {
- await this.setValue(key, JSON.stringify(value));
- return this;
- }
-
- /**
- * Returns the complex value for the given key.
- *
- * @param {string} key
- * the preference's key.
- * @param {object} fallback
- * the fallback value in case the key does not exist.
- * @returns {object}
- * the key's complex value.
- */
- async getComplexValue(key, fallback) {
- const value = await this.getValue(key);
-
- if (typeof (value) === "undefined" || value === null)
- return fallback;
-
- try {
- return JSON.parse(value);
- } catch (ex) {
- return fallback;
- }
- }
-
- }
-
- if (typeof(module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractPrefManager = SieveAbstractPrefManager;
- else
- exports.SieveAbstractPrefManager = SieveAbstractPrefManager;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.mjs
new file mode 100644
index 00000000..ddbfcc3d
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractPrefManager.mjs
@@ -0,0 +1,234 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/**
+ * Manages preferences.
+ */
+class SieveAbstractPrefManager {
+
+ /**
+ * Initializes the preference manager.
+ *
+ * @param {string} [namespace]
+ * the optional preferences namespace.
+ */
+ constructor(namespace) {
+ if ((typeof (namespace) === "undefined") || (namespace === null))
+ namespace = "";
+
+ this.namespace = namespace;
+ }
+
+ /**
+ * Returns the namespace which is added to all preferences
+ *
+ * @returns {string}
+ * the prefix as string.
+ */
+ getNamespace() {
+ return this.namespace;
+ }
+
+ /**
+ * Returns a specific value.
+ * @abstract
+ *
+ * @param {string} key
+ * the key which should be returned.
+ * @returns {object}
+ * the value or undefined in case it does not exist.
+ */
+ async getValue(key) {
+ throw new Error(`Implement SieveAbstractPrefManager::getValue(${key})`);
+ }
+
+ /**
+ * Sets and persists the given preference.
+ * @abstract
+ *
+ * @param {string} key
+ * the preference key which should be written.
+ * @param {object} value
+ * the key's value.
+ * @returns {SievePrefManager}
+ * a self reference.
+ */
+ async setValue(key, value) {
+ throw new Error(`Implement SieveAbstractPrefManager::setValue(${key},${value})`);
+ }
+
+ /**
+ * Returns the boolean value for the preference.
+ *
+ * @param {string} key
+ * the preference's key
+ * @param {boolean} [fallback]
+ * the fallback value in case the key does not exist.
+ * @returns {boolean}
+ * the key's value as boolean
+ */
+ async getBoolean(key, fallback) {
+ const value = await this.getValue(key);
+
+ if (typeof (value) === "undefined" || value === null)
+ return fallback;
+
+ // Parse boolean
+ if ((value === true) || (value === "true"))
+ return true;
+
+ if ((value === false) || (value === "false"))
+ return false;
+
+ return fallback;
+ }
+
+ /**
+ * Sets a boolean value for the given key.
+ *
+ * @param {string} key
+ * the preference's key
+ * @param {boolean} value
+ * the value which should be set
+ * @returns {SievePrefManager}
+ * a self reference
+ */
+ async setBoolean(key, value) {
+ // ensure it is an boolean...
+ value = !!value;
+
+ await this.setValue(key, value);
+ return this;
+ }
+
+ /**
+ * Returns the string value for the preference.
+ *
+ * @param {string} key
+ * the preference key
+ * @param {string} [fallback]
+ * the fallback value in case the key does not exist.
+ * @returns {string}
+ * the key's value as string
+ */
+ async getString(key, fallback) {
+ const value = await this.getValue(key);
+
+ if (typeof (value) === "undefined" || value === null)
+ return fallback;
+
+ return `${value}`;
+ }
+
+ /**
+ * The string which should be set for th preference
+ *
+ * @param {string} key
+ * the preference key
+ * @param {string} value
+ * the key's value as string
+ * @returns {SievePrefManager}
+ * a self reference
+ */
+ async setString(key, value) {
+ await this.setValue(key, `${value}`);
+ return this;
+ }
+
+ /**
+ * Returns the integer value for the preference.
+ *
+ * @param {string} key
+ * the preference's key
+ * @param {int} [fallback]
+ * the fallback value in case the key does not exist or is not a number.
+ * @returns {string}
+ * the key's value as integer
+ */
+ async getInteger(key, fallback) {
+
+ let value = await this.getValue(key);
+
+ if (typeof (value) === "undefined" || value === null || Number.isNaN(value))
+ return fallback;
+
+ if (Number.isInteger(value))
+ return value;
+
+ try {
+ value = Number.parseInt(value, 10);
+ } catch {
+ return fallback;
+ }
+
+ if (Number.isNaN(value))
+ return fallback;
+
+ return value;
+ }
+
+ /**
+ * Sets an integer value for the given key.
+ *
+ * @param {string} key
+ * the preference's key
+ * @param {int} value
+ * the integer value which should be set.
+ * @returns {SievePrefManager}
+ * a self reference.
+ */
+ async setInteger(key, value) {
+ await this.setValue(key, Number.parseInt(value, 10));
+ return this;
+ }
+
+ /**
+ * Saves a complex value like an object for the given key.
+ * The object needs to be serializable to a json string.
+ *
+ * @param {string} key
+ * the preference's key
+ * @param {object} value
+ * the complex value which should be saved.
+ * @returns {SievePrefManager}
+ * a self reference.
+ */
+ async setComplexValue(key, value) {
+ await this.setValue(key, JSON.stringify(value));
+ return this;
+ }
+
+ /**
+ * Returns the complex value for the given key.
+ *
+ * @param {string} key
+ * the preference's key.
+ * @param {object} fallback
+ * the fallback value in case the key does not exist.
+ * @returns {object}
+ * the key's complex value.
+ */
+ async getComplexValue(key, fallback) {
+ const value = await this.getValue(key);
+
+ if (typeof (value) === "undefined" || value === null)
+ return fallback;
+
+ try {
+ return JSON.parse(value);
+ } catch {
+ return fallback;
+ }
+ }
+
+}
+
+export { SieveAbstractPrefManager };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs
new file mode 100644
index 00000000..4be291c7
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs
@@ -0,0 +1,52 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+/**
+ * Defines the security related settings for an account.
+ * It is a minimal, mozilla specific implementation.
+ */
+class SieveAbstractSecurity {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {SieveAccount} account
+ * the account with is associated with this account.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Gets the current security settings. In case it is set to true
+ * a secure connection shall be used.
+ *
+ * @returns {boolean}
+ * true in case a secure connection should be used.
+ **/
+ async isSecure() {
+ return await true;
+ }
+
+ /**
+ * Gets the currently configured sasl mechanism.
+ *
+ * @returns {string}
+ * the sasl mechanism
+ **/
+ async getMechanism() {
+ return await "default";
+ }
+
+}
+
+export { SieveAbstractSecurity };
diff --git a/src/common/managesieve.ui/settings/logic/SieveAccountSettings.js b/src/common/managesieve.ui/settings/logic/SieveAccountSettings.js
deleted file mode 100644
index 7e8f3254..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveAccountSettings.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const CONFIG_DEBUG_ACCOUNT = "debug";
- const DEFAULT_LOG_LEVEL = 0;
-
- /**
- * Manages the accounts common settings.
- */
- class SieveAccountSettings {
-
- /**
- * Creates a new instance.
- *
- * @param {SieveAccount} account
- * a reference to the parent sieve account.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * Gets the log levels for the given account.
- *
- * @returns {int}
- * the current log level
- */
- async getLogLevel() {
- return await this.account.getConfig()
- .getInteger(CONFIG_DEBUG_ACCOUNT, DEFAULT_LOG_LEVEL);
- }
-
- /**
- * Sets the log level for the given account.
- *
- * @param {int} level
- * the new log level
- *
- * @returns {SieveAccountSettings}
- * a self reference.
- */
- async setLogLevel(level) {
- await this.account.getConfig().setInteger(CONFIG_DEBUG_ACCOUNT, level);
- return this;
- }
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports) {
- module.exports.SieveAccountSettings = SieveAccountSettings;
- } else {
- exports.SieveAccountSettings = SieveAccountSettings;
- }
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveAccountSettings.mjs b/src/common/managesieve.ui/settings/logic/SieveAccountSettings.mjs
new file mode 100644
index 00000000..9af1e2c1
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveAccountSettings.mjs
@@ -0,0 +1,57 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const CONFIG_DEBUG_ACCOUNT = "debug";
+const DEFAULT_LOG_LEVEL = 0;
+
+/**
+ * Manages the accounts common settings.
+ */
+class SieveAccountSettings {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param {SieveAccount} account
+ * a reference to the parent sieve account.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Gets the log levels for the given account.
+ *
+ * @returns {int}
+ * the current log level
+ */
+ async getLogLevel() {
+ return await this.account.getConfig()
+ .getInteger(CONFIG_DEBUG_ACCOUNT, DEFAULT_LOG_LEVEL);
+ }
+
+ /**
+ * Sets the log level for the given account.
+ *
+ * @param {int} level
+ * the new log level
+ *
+ * @returns {SieveAccountSettings}
+ * a self reference.
+ */
+ async setLogLevel(level) {
+ await this.account.getConfig().setInteger(CONFIG_DEBUG_ACCOUNT, level);
+ return this;
+ }
+
+}
+
+export { SieveAccountSettings };
diff --git a/src/common/managesieve.ui/settings/logic/SieveEditorSettings.js b/src/common/managesieve.ui/settings/logic/SieveEditorSettings.js
deleted file mode 100644
index dc81330a..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveEditorSettings.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const DEFAULT_TAB_POLICY = true;
- const DEFAULT_TAB_WIDTH = 2;
- const DEFAULT_INDENTATION_POLICY = false;
- const DEFAULT_INDENTATION_WIDTH = 2;
-
- /**
- * Manages the sieve editor settings.
- */
- class SieveEditorSettings {
-
- /**
- * Create a new instance.
- *
- * @param {SievePrefManager} pref
- * the pref manager to be used for this editor settings.
- */
- constructor(pref) {
- this.pref = pref;
- }
-
- /**
- * Sets an editor setting.
- *
- * @param {string} name
- * the preference name
- * @param {object} value
- * the preference value
- */
- async setValue(name, value) {
- await this.pref.setValue(`editor.${name}`, value);
- }
-
- /**
- * Gets an editor settings.
- *
- * @param {string} name
- * the preference name
- * @returns {object}
- * the editor settings value.
- */
- async getValue(name) {
-
- if (name === "tabulator-policy")
- return await this.pref.getBoolean("editor.tabulator-policy", DEFAULT_TAB_POLICY);
-
- if (name === "tabulator-width")
- return await this.pref.getInteger("editor.tabulator-width", DEFAULT_TAB_WIDTH);
-
- if (name === "indentation-policy")
- return await this.pref.getBoolean("editor.indentation-policy", DEFAULT_INDENTATION_POLICY);
-
- if (name === "indentation-width")
- return await this.pref.getInteger("editor.indentation-width", DEFAULT_INDENTATION_WIDTH);
-
- if (name === "syntax-check")
- return await this.pref.getBoolean("editor.syntax-check", true);
-
- throw new Error(`Unknown settings ${name}`);
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveEditorSettings = SieveEditorSettings;
- else
- exports.SieveEditorSettings = SieveEditorSettings;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/logic/SieveEditorSettings.mjs b/src/common/managesieve.ui/settings/logic/SieveEditorSettings.mjs
new file mode 100644
index 00000000..4dee45ef
--- /dev/null
+++ b/src/common/managesieve.ui/settings/logic/SieveEditorSettings.mjs
@@ -0,0 +1,73 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const DEFAULT_TAB_POLICY = true;
+const DEFAULT_TAB_WIDTH = 2;
+const DEFAULT_INDENTATION_POLICY = false;
+const DEFAULT_INDENTATION_WIDTH = 2;
+
+/**
+ * Manages the sieve editor settings.
+ */
+class SieveEditorSettings {
+
+ /**
+ * Create a new instance.
+ *
+ * @param {SievePrefManager} pref
+ * the pref manager to be used for this editor settings.
+ */
+ constructor(pref) {
+ this.pref = pref;
+ }
+
+ /**
+ * Sets an editor setting.
+ *
+ * @param {string} name
+ * the preference name
+ * @param {object} value
+ * the preference value
+ */
+ async setValue(name, value) {
+ await this.pref.setValue(`editor.${name}`, value);
+ }
+
+ /**
+ * Gets an editor settings.
+ *
+ * @param {string} name
+ * the preference name
+ * @returns {object}
+ * the editor settings value.
+ */
+ async getValue(name) {
+
+ if (name === "tabulator-policy")
+ return await this.pref.getBoolean("editor.tabulator-policy", DEFAULT_TAB_POLICY);
+
+ if (name === "tabulator-width")
+ return await this.pref.getInteger("editor.tabulator-width", DEFAULT_TAB_WIDTH);
+
+ if (name === "indentation-policy")
+ return await this.pref.getBoolean("editor.indentation-policy", DEFAULT_INDENTATION_POLICY);
+
+ if (name === "indentation-width")
+ return await this.pref.getInteger("editor.indentation-width", DEFAULT_INDENTATION_WIDTH);
+
+ if (name === "syntax-check")
+ return await this.pref.getBoolean("editor.syntax-check", true);
+
+ throw new Error(`Unknown settings ${name}`);
+ }
+}
+
+export { SieveEditorSettings };
diff --git a/src/common/managesieve.ui/settings/logic/SieveSecuritySettings.js b/src/common/managesieve.ui/settings/logic/SieveSecuritySettings.js
deleted file mode 100644
index 11a69de3..00000000
--- a/src/common/managesieve.ui/settings/logic/SieveSecuritySettings.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const PREF_MECHANISM = "security.mechanism";
- const PREF_TLS = "security.tls";
-
- /**
- * Manages the account's security related settings
- */
- class SieveSecurity {
-
- /**
- * Creates a new instance.
- *
- * @param {SieveAccount} account
- * the account with is associated with this account.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * Gets the currently configured sasl mechanism.
- *
- * @returns {string}
- * the sasl mechanism
- **/
- async getMechanism() {
- return await this.account.getConfig().getString(PREF_MECHANISM, "default");
- }
-
- /**
- * Sets the sasl mechanism.
- *
- * @param {string} mechanism
- * the sasl mechanism which should be used.
- *
- * @returns {SieveSecurity}
- * a self reference
- */
- async setMechanism(mechanism) {
- await this.account.getConfig().setString(PREF_MECHANISM, mechanism);
- return this;
- }
-
- /**
- * Gets the current security settings. In case it is set to true
- * a secure connection shall be used.
- *
- * @returns {boolean}
- * true in case a secure connection should be used.
- **/
- async isSecure() {
- return await this.account.getConfig().getBoolean(PREF_TLS, true);
- }
-
- /**
- * Defines if a secure connections shall be used.
- *
- * @param {boolean} value
- * set to true for a secure connection.
- *
- * @returns {SieveSecurity}
- * a self reference
- */
- async setSecure(value) {
- await this.account.getConfig().setBoolean(PREF_TLS, value);
- return this;
- }
-
- }
-
- exports.SieveSecurity = SieveSecurity;
-
-})(module.exports);
diff --git a/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.js b/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.js
deleted file mode 100644
index e8bd9708..00000000
--- a/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.js
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global bootstrap */
- /* global SieveTemplate */
-
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACCOUNT_REQUEST = (1 << 0);
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACCOUNT_RESPONSE = (1 << 1);
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACCOUNT_STATE = (1 << 2);
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACCOUNT_STREAM = (1 << 3);
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACCOUNT_SESSION_INFO = (1 << 4);
-
- // eslint-disable-next-line no-magic-numbers
- const LOG_GLOBAL_IPC_MESSAGES = (1 << 0);
- // eslint-disable-next-line no-magic-numbers
- const LOG_GLOBAL_ACTION = (1 << 1);
- // eslint-disable-next-line no-magic-numbers
- const LOG_GLOBAL_WIDGET = (1 << 2);
- // eslint-disable-next-line no-magic-numbers
- const LOG_GLOBAL_I18N = (1 << 3);
-
- /**
- * A UI renderer for the sieve debug settings dialog
- */
- class SieveDebugSettingsUI {
-
- /**
- * Initializes the settings
- * @param {SieveAccount} account
- * the account for which the settings edited.
- */
- constructor(account) {
- this.account = account;
- }
-
- /**
- * Renders the UI element into the dom.
- */
- async render() {
- const dialog = this.getDialog();
-
- const levels = await this.account.send("account-settings-get-debug");
-
- this.setAccountLogLevel(levels.account);
- this.setGlobalLogLevel(levels.global);
-
- dialog.querySelector(".siv-settings-show-advanced")
- .addEventListener("click", () => { this.showAdvanced(); });
- dialog.querySelector(".siv-settings-hide-advanced")
- .addEventListener("click", () => { this.hideAdvanced(); });
-
- this.hideAdvanced();
-
- }
-
- /**
- * Shows the settings dialog
- */
- async show() {
-
- document.querySelector("#ctx").appendChild(
- await (new SieveTemplate()).load("./settings/ui/settings.debug.tpl"));
-
- await this.render();
-
- const dialog = this.getDialog();
- const modal = new bootstrap.Modal(dialog);
-
- modal.show();
-
- dialog.querySelector(".sieve-settings-apply")
- .addEventListener("click", () => {
- this.save();
- modal.hide();
- });
-
- await new Promise((resolve) => {
-
- dialog.addEventListener("hidden.bs.modal", () => {
- modal.dispose();
- dialog.parentNode.removeChild(dialog);
-
- resolve();
- });
- });
-
- }
-
- /**
- * Reads the currently set account log level from the dialog.
- *
- * @returns {int}
- * the account log level as integer.
- */
- getAccountLogLevel() {
- let level = 0x00;
-
- if (document.querySelector("#debugClientServer").checked === true)
- level |= LOG_ACCOUNT_REQUEST;
-
- if (document.querySelector("#debugServerClient").checked === true)
- level |= LOG_ACCOUNT_RESPONSE;
-
- if (document.querySelector("#debugSessionManagement").checked === true)
- level |= LOG_ACCOUNT_SESSION_INFO;
-
- if (document.querySelector("#debugStateMachine").checked === true)
- level |= LOG_ACCOUNT_STATE;
-
- if (document.querySelector("#debugRawDump").checked === true)
- level |= LOG_ACCOUNT_STREAM;
-
- return level;
- }
-
- /**
- * Sets the account log level in the dialog.
- *
- * @param {int} level
- * the account log level as integer
- */
- setAccountLogLevel(level) {
-
- document.querySelector("#debugClientServer").checked = (level & LOG_ACCOUNT_REQUEST);
- document.querySelector("#debugServerClient").checked = (level & LOG_ACCOUNT_RESPONSE);
- document.querySelector("#debugSessionManagement").checked = (level & LOG_ACCOUNT_SESSION_INFO);
- document.querySelector("#debugStateMachine").checked = (level & LOG_ACCOUNT_STATE);
- document.querySelector("#debugRawDump").checked = (level & LOG_ACCOUNT_STREAM);
- }
-
- /**
- * Reads the currently set global log level from the dialog.
- *
- * @returns {int}
- * the global log level as integer.
- */
- getGlobalLogLevel() {
- let level = 0x00;
-
- if (document.querySelector("#debugActions").checked)
- level |= LOG_GLOBAL_ACTION;
-
- if (document.querySelector("#debugIpcMessages").checked)
- level |= LOG_GLOBAL_IPC_MESSAGES;
-
- if (document.querySelector("#debugWidgets").checked)
- level |= LOG_GLOBAL_WIDGET;
-
- if (document.querySelector("#debugI18n").checked)
- level |= LOG_GLOBAL_I18N;
-
- return level;
- }
-
- /**
- * Sets the global log level in the dialog.
- *
- * @param {int} level
- * the global log level as integer
- */
- setGlobalLogLevel(level) {
- document.querySelector("#debugActions").checked = (level & LOG_GLOBAL_ACTION);
- document.querySelector("#debugIpcMessages").checked = (level & LOG_GLOBAL_IPC_MESSAGES);
- document.querySelector("#debugWidgets").checked = (level & LOG_GLOBAL_WIDGET);
- document.querySelector("#debugI18n").checked = (level & LOG_GLOBAL_I18N);
- }
-
- /**
- * Validates and saves the setting before closing the dialog.
- * In case the settings are invalid an error message is displayed.
- */
- async save() {
-
- const levels = {
- account: this.getAccountLogLevel(),
- global: this.getGlobalLogLevel()
- };
-
- await this.account.send("account-settings-set-debug", { "levels": levels });
- }
-
- /**
- * Returns the currents dialogs UI Element.
- *
- * @returns {HTMLElement}
- * the dialogs UI elements.
- */
- getDialog() {
- return document.querySelector("#dialog-settings-debug");
- }
-
- /**
- * Shows the advanced setting
- */
- showAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").style.display = "";
- parent.querySelector(".siv-settings-show-advanced").style.display = "none";
- parent.querySelector(".siv-settings-hide-advanced").style.display = "";
- }
-
- /**
- * Hides the advanced settings
- */
- hideAdvanced() {
- const parent = this.getDialog();
-
- parent.querySelector(".siv-settings-advanced").style.display = "none";
- parent.querySelector(".siv-settings-show-advanced").style.display = "";
- parent.querySelector(".siv-settings-hide-advanced").style.display = "none";
- }
-
- }
- if (typeof (module) !== "undefined" && module !== null && module.exports)
- module.exports = SieveDebugSettingsUI;
- else
- exports.SieveDebugSettingsUI = SieveDebugSettingsUI;
-
-})(this);
diff --git a/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.mjs b/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.mjs
new file mode 100644
index 00000000..835ab34d
--- /dev/null
+++ b/src/common/managesieve.ui/settings/ui/SieveDebugSettingsUI.mjs
@@ -0,0 +1,262 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global bootstrap */
+
+import { SieveTemplate } from "./../../utils/SieveTemplate.mjs";
+
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_REQUEST = (1 << 0);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_RESPONSE = (1 << 1);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_STATE = (1 << 2);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_STREAM = (1 << 3);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_SESSION_INFO = (1 << 4);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACCOUNT_TRACE = (1 << 5);
+
+// eslint-disable-next-line no-magic-numbers
+const LOG_GLOBAL_IPC_MESSAGES = (1 << 0);
+// eslint-disable-next-line no-magic-numbers
+const LOG_GLOBAL_ACTION = (1 << 1);
+// eslint-disable-next-line no-magic-numbers
+const LOG_GLOBAL_WIDGET = (1 << 2);
+// eslint-disable-next-line no-magic-numbers
+const LOG_GLOBAL_I18N = (1 << 3);
+// eslint-disable-next-line no-magic-numbers
+const LOG_GLOBAL_TRACE = (1 << 5);
+
+/**
+ * A UI renderer for the sieve debug settings dialog
+ */
+class SieveDebugSettingsUI {
+
+ /**
+ * Initializes the settings
+ * @param {SieveAccount} account
+ * the account for which the settings edited.
+ */
+ constructor(account) {
+ this.account = account;
+ }
+
+ /**
+ * Renders the UI element into the dom.
+ */
+ async render() {
+ const dialog = this.getDialog();
+
+ const levels = await this.account.send("account-settings-get-debug");
+
+ this.setAccountLogLevel(levels.account);
+ this.setGlobalLogLevel(levels.global);
+
+ dialog.querySelector(".siv-settings-show-advanced")
+ .addEventListener("click", () => { this.showAdvanced(); });
+ dialog.querySelector(".siv-settings-hide-advanced")
+ .addEventListener("click", () => { this.hideAdvanced(); });
+
+ dialog.querySelector(".siv-settings-open-developer-tools")
+ .addEventListener("click", () => { this.openDeveloperTools(); });
+
+ dialog.querySelector(".siv-settings-reload-ui")
+ .addEventListener("click", () => { this.reloadApp(); });
+
+
+ this.hideAdvanced();
+ }
+
+ /**
+ * Reloads the application.
+ */
+ reloadApp() {
+ this.account.send("reload-ui");
+ }
+
+ /**
+ * Opens the developer tools.
+ */
+ openDeveloperTools() {
+ this.account.send("open-developer-tools");
+ }
+
+ /**
+ * Shows the settings dialog
+ */
+ async show() {
+
+ document.querySelector("#ctx").append(
+ await (new SieveTemplate()).load("./settings/ui/settings.debug.html"));
+
+ await this.render();
+
+ const dialog = this.getDialog();
+ const modal = new bootstrap.Modal(dialog);
+
+ modal.show();
+
+ dialog.querySelector(".sieve-settings-apply")
+ .addEventListener("click", () => {
+ this.save();
+ modal.hide();
+ });
+
+ await new Promise((resolve) => {
+
+ dialog.addEventListener("hidden.bs.modal", () => {
+ modal.dispose();
+ dialog.remove();
+
+ resolve();
+ });
+ });
+
+ }
+
+ /**
+ * Reads the currently set account log level from the dialog.
+ *
+ * @returns {int}
+ * the account log level as integer.
+ */
+ getAccountLogLevel() {
+ let level = 0x00;
+
+ if (document.querySelector("#debugClientServer").checked === true)
+ level |= LOG_ACCOUNT_REQUEST;
+
+ if (document.querySelector("#debugServerClient").checked === true)
+ level |= LOG_ACCOUNT_RESPONSE;
+
+ if (document.querySelector("#debugSessionManagement").checked === true)
+ level |= LOG_ACCOUNT_SESSION_INFO;
+
+ if (document.querySelector("#debugStateMachine").checked === true)
+ level |= LOG_ACCOUNT_STATE;
+
+ if (document.querySelector("#debugRawDump").checked === true)
+ level |= LOG_ACCOUNT_STREAM;
+
+ if (document.querySelector("#debugBackEndTrace").checked)
+ level |= LOG_ACCOUNT_TRACE;
+
+ return level;
+ }
+
+ /**
+ * Sets the account log level in the dialog.
+ *
+ * @param {int} level
+ * the account log level as integer
+ */
+ setAccountLogLevel(level) {
+
+ document.querySelector("#debugClientServer").checked = (level & LOG_ACCOUNT_REQUEST);
+ document.querySelector("#debugServerClient").checked = (level & LOG_ACCOUNT_RESPONSE);
+ document.querySelector("#debugSessionManagement").checked = (level & LOG_ACCOUNT_SESSION_INFO);
+ document.querySelector("#debugStateMachine").checked = (level & LOG_ACCOUNT_STATE);
+ document.querySelector("#debugRawDump").checked = (level & LOG_ACCOUNT_STREAM);
+ document.querySelector("#debugBackEndTrace").checked = (level & LOG_ACCOUNT_TRACE);
+ }
+
+ /**
+ * Reads the currently set global log level from the dialog.
+ *
+ * @returns {int}
+ * the global log level as integer.
+ */
+ getGlobalLogLevel() {
+ let level = 0x00;
+
+ if (document.querySelector("#debugActions").checked)
+ level |= LOG_GLOBAL_ACTION;
+
+ if (document.querySelector("#debugIpcMessages").checked)
+ level |= LOG_GLOBAL_IPC_MESSAGES;
+
+ if (document.querySelector("#debugWidgets").checked)
+ level |= LOG_GLOBAL_WIDGET;
+
+ if (document.querySelector("#debugI18n").checked)
+ level |= LOG_GLOBAL_I18N;
+
+ if (document.querySelector("#debugFrontEndTrace").checked)
+ level |= LOG_GLOBAL_TRACE;
+
+ return level;
+ }
+
+ /**
+ * Sets the global log level in the dialog.
+ *
+ * @param {int} level
+ * the global log level as integer
+ */
+ setGlobalLogLevel(level) {
+ document.querySelector("#debugActions").checked = (level & LOG_GLOBAL_ACTION);
+ document.querySelector("#debugIpcMessages").checked = (level & LOG_GLOBAL_IPC_MESSAGES);
+ document.querySelector("#debugWidgets").checked = (level & LOG_GLOBAL_WIDGET);
+ document.querySelector("#debugI18n").checked = (level & LOG_GLOBAL_I18N);
+ document.querySelector("#debugFrontEndTrace").checked = (level & LOG_GLOBAL_TRACE);
+ }
+
+ /**
+ * Validates and saves the setting before closing the dialog.
+ * In case the settings are invalid an error message is displayed.
+ */
+ async save() {
+
+ const levels = {
+ account: this.getAccountLogLevel(),
+ global: this.getGlobalLogLevel()
+ };
+
+ await this.account.send("account-settings-set-debug", { "levels": levels });
+ }
+
+ /**
+ * Returns the currents dialogs UI Element.
+ *
+ * @returns {HTMLElement}
+ * the dialogs UI elements.
+ */
+ getDialog() {
+ return document.querySelector("#dialog-settings-debug");
+ }
+
+ /**
+ * Shows the advanced setting
+ */
+ showAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").style.display = "";
+ parent.querySelector(".siv-settings-show-advanced").style.display = "none";
+ parent.querySelector(".siv-settings-hide-advanced").style.display = "";
+ }
+
+ /**
+ * Hides the advanced settings
+ */
+ hideAdvanced() {
+ const parent = this.getDialog();
+
+ parent.querySelector(".siv-settings-advanced").style.display = "none";
+ parent.querySelector(".siv-settings-show-advanced").style.display = "";
+ parent.querySelector(".siv-settings-hide-advanced").style.display = "none";
+ }
+
+}
+
+export { SieveDebugSettingsUI };
diff --git a/src/common/managesieve.ui/settings/ui/settings.debug.tpl b/src/common/managesieve.ui/settings/ui/settings.debug.html
index 7328c157..71dedf81 100644
--- a/src/common/managesieve.ui/settings/ui/settings.debug.tpl
+++ b/src/common/managesieve.ui/settings/ui/settings.debug.html
@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-i18n="settings.title"></h5>
- <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -36,6 +36,12 @@
<label class="form-check-label" for="debugSessionManagement"
data-i18n="debug.transport.session"></label>
</div>
+ <div class="custom-control custom-switch">
+ <input type="checkbox" class="form-check-input" id="debugBackEndTrace" />
+ <label class="form-check-label" for="debugBackEndTrace"
+ data-i18n="debug.transport.trace"></label>
+ </div>
+
<div class="siv-settings-advanced">
<hr />
<h5 data-i18n="debug.global.title"></h5>
@@ -63,12 +69,21 @@
<input type="checkbox" class="form-check-input" id="debugI18n" />
<label data-i18n="debug.global.i18n" class="form-check-label" for="debugI18n"></label>
</div>
+
+ <div class="custom-control custom-switch">
+ <input type="checkbox" class="form-check-input" id="debugFrontEndTrace" />
+ <label class="form-check-label" for="debugFrontEndTrace"
+ data-i18n="debug.global.trace"></label>
+ </div>
</div>
</div>
</div>
<div class="modal-footer">
+ <button data-i18n="debug.console.show" class="siv-settings-open-developer-tools btn btn-outline-secondary"></button>
+ <button data-i18n="debug.ui.reload" class="siv-settings-reload-ui btn btn-outline-secondary"></button>
+ <span class="flex-grow-1"></span>
<button data-i18n="settings.more" class="siv-settings-show-advanced btn btn-outline-secondary"></button>
<button data-i18n="settings.less" class="siv-settings-hide-advanced btn btn-outline-secondary"></button>
diff --git a/src/common/managesieve.ui/utils/SieveAbstractIpcClient.js b/src/common/managesieve.ui/utils/SieveAbstractIpcClient.js
deleted file mode 100644
index 2c517de0..00000000
--- a/src/common/managesieve.ui/utils/SieveAbstractIpcClient.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveUniqueId } = require("./SieveUniqueId.js");
-
- const _requestHandlers = new Map();
- const _responseHandlers = new Map();
-
- /**
- * An abstract implementation for a inter process/frame communication.
- */
- class SieveAbstractIpcClient {
-
- /**
- * Generates a unique id
- * @returns {string}
- * a unique id
- */
- static generateId() {
- return (new SieveUniqueId()).generate();
- }
-
- /**
- * Gets a logger instance.
- * @abstract
- *
- * @returns {SieveLogger}
- * a sieve logger instance
- */
- static getLogger() {
- throw new Error(`Implement getLogger`);
- }
-
- /**
- * Called every time a new ipc message was received.
- * @param {Event} e
- * the ipc message containing the data.
- */
- static onMessage(e) {
- const msg = this.parseMessageFromEvent(e);
-
- if (msg.isResponse === true) {
- this.onResponse(msg);
- return;
- }
-
- if (msg.isRequest === true) {
- this.onRequest(msg, e.source);
- return;
- }
-
- this.onError(e);
- }
-
- /**
- * Called upon an external request which requires a response.
- *
- * @param {object} request
- * the response message containing the data.
- *
- * @param {object} source
- * the object which emitted/created this message.
- */
- static async onRequest(request, source) {
-
- this.getLogger().logIpcMessage(`OnRequest: ${JSON.stringify(request)}`);
-
- if (!_requestHandlers.has(request.subject)) {
- this.getLogger().logIpcMessage(`Unknown subject ${request.subject} in ${window.location}`);
- return;
- }
-
- const handler = _requestHandlers.get(request.subject);
-
- const response = request;
- response.isResponse = true;
-
- try {
- if (!handler.has(request.action)) {
- this.getLogger().logIpcMessage(`Unknown action ${request.action} in ${window.location}`);
- throw new Error(`Unknown action ${request.action}`);
- }
-
- response.payload = await (handler.get(request.action)(request));
- } catch (ex) {
- response.error = ex.message;
- this.getLogger().logIpcMessage(ex);
- }
-
- this.dispatch(response, source);
- }
-
- /**
- * Called when a response to a request it received.
- *
- * @param {object} message
- * the response message containing the data.
- */
- static onResponse(message) {
-
- this.getLogger().logIpcMessage(`On Response: ${JSON.stringify(message)}`);
-
- const id = message.id;
-
- // Check if the id is known to us.
- if (id === undefined || id === null)
- return;
-
- if (!_responseHandlers.has(message.id))
- return;
-
- this.getLogger().logIpcMessage(`Callback for ${id}`);
-
- // Check the response handlers
- const handler = _responseHandlers.get(id);
- _responseHandlers.delete(id);
-
- handler(message);
- }
-
- /**
- * Sends a message via the ipc communication
- * @abstract
- * @param {string} message
- * the message to be send.
- * @param {Window} target
- * the target which should receive the message
- * @param {object} [origin]
- * optional information about the origin.
- */
- static dispatch(message, target, origin) {
- throw new Error(`Implement me ${message} ${target} ${origin}`);
- }
-
- /**
- * Extracts the message from the message event.
- * @abstract
- *
- * @param {Event} e
- * the event which contains the message.
- */
- static parseMessageFromEvent(e) {
- throw new Error(`Implement me ${e} `);
- }
-
- /**
- * Extracts the message source from the event object.
- * @abstract
- *
- * @param {Event} e
- * the event which should be analyzed
- * @returns {object}
- * the message source
- */
- static getSource(e) {
- throw new Error(`Implement me ${e} `);
- }
-
- /**
- * Registers a request handler for the given action.
- * The can be at most one handler per action. In case it already
- * exists it will be replaced.
- *
- * @param {string} subject
- * the subject name to listen to. All other subject will be ignored.
- * @param {string} action
- * the action's unique name.
- * @param {Function} callback
- * the callback which should be invoked upon a matching request.
- */
- static setRequestHandler(subject, action, callback) {
- if (!_requestHandlers.has(subject))
- _requestHandlers.set(subject, new Map());
-
- _requestHandlers.get(subject).set(action, callback);
- }
-
- /**
- * Sends a message to the given target.
- *
- * @param {string} subject
- * the messages subject name specifies who will receive the message.
- * @param {string} action
- * the action to be performed.
- * @param {object} payload
- * the payload to be send
- * @param {Window} [target]
- * the target which host the receiver. In case it is omitted "parent" is used.
- * @returns {*}
- * the messages response or an exception in case of an error.
- */
- static async sendMessage(subject, action, payload, target) {
-
- const id = this.generateId();
-
- const msg = JSON.stringify({
- id: id,
- subject: subject,
- action: action,
- payload: payload,
- isRequest: true
- });
-
- return await new Promise((resolve, reject) => {
-
- const onResponse = (message) => {
- if (message.error) {
- reject(message.error);
- return;
- }
-
- resolve(message.payload);
- };
-
- _responseHandlers.set(id, onResponse);
- this.dispatch(msg, target);
- });
- }
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAbstractIpcClient = SieveAbstractIpcClient;
- else
- exports.SieveAbstractIpcClient = SieveAbstractIpcClient;
-
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveAbstractIpcClient.mjs b/src/common/managesieve.ui/utils/SieveAbstractIpcClient.mjs
new file mode 100644
index 00000000..a0940f7c
--- /dev/null
+++ b/src/common/managesieve.ui/utils/SieveAbstractIpcClient.mjs
@@ -0,0 +1,229 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveUniqueId } from "./SieveUniqueId.mjs";
+
+const _requestHandlers = new Map();
+const _responseHandlers = new Map();
+
+/**
+ * An abstract implementation for a inter process/frame communication.
+ */
+class SieveAbstractIpcClient {
+
+ /**
+ * Generates a unique id
+ * @returns {string}
+ * a unique id
+ */
+ static generateId() {
+ return (new SieveUniqueId()).generate();
+ }
+
+ /**
+ * Gets a logger instance.
+ * @abstract
+ *
+ * @returns {SieveLogger}
+ * a sieve logger instance
+ */
+ static getLogger() {
+ throw new Error(`Implement getLogger`);
+ }
+
+ /**
+ * Called every time a new ipc message was received.
+ * @param {Event} e
+ * the ipc message containing the data.
+ */
+ static onMessage(e) {
+ const msg = this.parseMessageFromEvent(e);
+
+ if (msg.isResponse === true) {
+ this.onResponse(msg);
+ return;
+ }
+
+ if (msg.isRequest === true) {
+ this.onRequest(msg, e.source);
+ return;
+ }
+
+ this.onError(e);
+ }
+
+ /**
+ * Called upon an external request which requires a response.
+ *
+ * @param {object} request
+ * the response message containing the data.
+ *
+ * @param {object} source
+ * the object which emitted/created this message.
+ */
+ static async onRequest(request, source) {
+
+ this.getLogger().logIpcMessage(`OnRequest: ${JSON.stringify(request)}`);
+
+ if (!_requestHandlers.has(request.subject)) {
+ this.getLogger().logIpcMessage(`Unknown subject ${request.subject} in ${window.location}`);
+ return;
+ }
+
+ const handler = _requestHandlers.get(request.subject);
+
+ const response = request;
+ response.isResponse = true;
+
+ try {
+ if (!handler.has(request.action)) {
+ this.getLogger().logIpcMessage(`Unknown action ${request.action} in ${window.location}`);
+ throw new Error(`Unknown action ${request.action}`);
+ }
+
+ response.payload = await (handler.get(request.action)(request));
+ } catch (ex) {
+ response.error = ex.message;
+ this.getLogger().logIpcMessage(ex);
+ }
+
+ this.dispatch(response, source);
+ }
+
+ /**
+ * Called when a response to a request it received.
+ *
+ * @param {object} message
+ * the response message containing the data.
+ */
+ static onResponse(message) {
+
+ this.getLogger().logIpcMessage(`On Response: ${JSON.stringify(message)}`);
+
+ const id = message.id;
+
+ // Check if the id is known to us.
+ if (id === undefined || id === null)
+ return;
+
+ if (!_responseHandlers.has(message.id))
+ return;
+
+ this.getLogger().logIpcMessage(`Callback for ${id}`);
+
+ // Check the response handlers
+ const handler = _responseHandlers.get(id);
+ _responseHandlers.delete(id);
+
+ handler(message);
+ }
+
+ /**
+ * Sends a message via the ipc communication
+ * @abstract
+ * @param {string} message
+ * the message to be send.
+ * @param {Window} target
+ * the target which should receive the message
+ * @param {object} [origin]
+ * optional information about the origin.
+ */
+ static dispatch(message, target, origin) {
+ throw new Error(`Implement me ${message} ${target} ${origin}`);
+ }
+
+ /**
+ * Extracts the message from the message event.
+ * @abstract
+ *
+ * @param {Event} e
+ * the event which contains the message.
+ */
+ static parseMessageFromEvent(e) {
+ throw new Error(`Implement me ${e} `);
+ }
+
+ /**
+ * Extracts the message source from the event object.
+ * @abstract
+ *
+ * @param {Event} e
+ * the event which should be analyzed
+ * @returns {object}
+ * the message source
+ */
+ static getSource(e) {
+ throw new Error(`Implement me ${e} `);
+ }
+
+ /**
+ * Registers a request handler for the given action.
+ * The can be at most one handler per action. In case it already
+ * exists it will be replaced.
+ *
+ * @param {string} subject
+ * the subject name to listen to. All other subject will be ignored.
+ * @param {string} action
+ * the action's unique name.
+ * @param {Function} callback
+ * the callback which should be invoked upon a matching request.
+ */
+ static setRequestHandler(subject, action, callback) {
+ if (!_requestHandlers.has(subject))
+ _requestHandlers.set(subject, new Map());
+
+ _requestHandlers.get(subject).set(action, callback);
+ }
+
+ /**
+ * Sends a message to the given target.
+ *
+ * @param {string} subject
+ * the messages subject name specifies who will receive the message.
+ * @param {string} action
+ * the action to be performed.
+ * @param {object} payload
+ * the payload to be send
+ * @param {Window} [target]
+ * the target which host the receiver. In case it is omitted "parent" is used.
+ * @returns {*}
+ * the messages response or an exception in case of an error.
+ */
+ static async sendMessage(subject, action, payload, target) {
+
+ const id = this.generateId();
+
+ const msg = JSON.stringify({
+ id: id,
+ subject: subject,
+ action: action,
+ payload: payload,
+ isRequest: true
+ });
+
+ return await new Promise((resolve, reject) => {
+
+ const onResponse = (message) => {
+ if (message.error) {
+ reject(message.error);
+ return;
+ }
+
+ resolve(message.payload);
+ };
+
+ _responseHandlers.set(id, onResponse);
+ this.dispatch(msg, target);
+ });
+ }
+}
+
+export { SieveAbstractIpcClient };
diff --git a/src/common/managesieve.ui/utils/SieveFakeRequire.js b/src/common/managesieve.ui/utils/SieveFakeRequire.js
deleted file mode 100644
index cb364a66..00000000
--- a/src/common/managesieve.ui/utils/SieveFakeRequire.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-
-(function (exports) {
-
- "use strict";
-
- /**
- * Gets the module for the given name.
- * In case it is not loaded into the current scope an exception will be thrown.
- *
- * @param {string} name
- * the unique module name.
- * @returns {object}
- * the module.
- */
- function getModule(name) {
-
- if (exports.module && exports.module.exports && exports.module.exports[name])
- return exports.module.exports[name];
-
- if (exports[name])
- return exports[name];
-
- throw new Error(`Module ${name} not loaded into scope`);
- }
-
- /**
- * Gets a list of modules.
- * It will throw an exception in case one or more of the module
- * names could not be loaded from the current scope
- *
- * @param {...string} names
- * a list of module names
- * @returns {object}
- * the modules.
- */
- function getModules(...names) {
-
- const m = {};
-
- for (const name of names)
- m[name] = getModule(name);
-
- return m;
- }
-
- const globals = new Map();
- globals.set("./SieveUniqueId.js", "SieveUniqueId");
- globals.set("./../../utils/SieveUniqueId.js", "SieveUniqueId");
- globals.set("./../utils/SieveUniqueId.js", "SieveUniqueId");
-
- globals.set("./SieveAbstractIpcClient.js", "SieveAbstractIpcClient");
-
- globals.set("./../utils/SieveTemplate.js", "SieveTemplate");
- globals.set("./SieveI18n.js", "SieveI18n");
-
- globals.set("./SieveAbstractAccounts.js", "SieveAbstractAccounts");
- globals.set("./SieveAbstractAccount.js", "SieveAbstractAccount");
- globals.set("./SievePrefManager.js", "SievePrefManager");
- globals.set("./SieveHostSettings.js", "SieveHost");
- globals.set("./SieveAuthorizationSettings.js", "SieveAuthorization");
- globals.set("./SieveAuthenticationSettings.js", "SieveAuthentication");
- globals.set("./SieveSecuritySettings.js", "SieveSecurity");
- globals.set("./SieveAccountSettings.js", "SieveAccountSettings");
- globals.set("./SieveEditorSettings.js", "SieveEditorSettings");
-
- globals.set("./../../utils/SieveLogger.js", "SieveLogger");
- globals.set("./SieveLogger.js", "SieveLogger");
-
- globals.set("libs/managesieve.ui/settings/SieveAbstractMechanism.js", "SieveAbstractMechanism");
- globals.set(
- "libs/managesieve.ui/settings/SieveAbstractAuthorization.js",
- ["SieveAbstractAuthorization", "SieveDefaultAuthorization"]);
- globals.set("libs/managesieve.ui/settings/SieveAbstractAuthentication.js", "SieveAbstractAuthentication");
-
-
- globals.set("libs/managesieve.ui/settings/SieveAbstractHost.js", "SieveAbstractHost");
-
- globals.set("libs/managesieve.ui/settings/SievePrefManager.js", "SievePrefManager");
- globals.set("libs/managesieve.ui/settings/SieveAbstractPrefManager.js", "SieveAbstractPrefManager");
-
-
-
- /**
- * A fake CommonJs Module implementation.
- * Temporarily needed as node does not yet support ES6 modules.
- * And mozilla does not support CommonJS
- *
- * @param {string} module
- * the module to be loaded
- *
- * @returns {object}
- * the module or an exception.
- */
- function fakeRequire(module) {
-
- if (!globals.has(module))
- throw new Error(`Module ${module} unknown to fake module loader`);
-
- let modules = globals.get(module);
-
- if (!Array.isArray(modules))
- modules = [modules];
-
- return getModules(...modules);
- }
-
-
- // In case there is no require in our scope we add our fake.
- if (typeof (exports.require) !== "undefined" && exports.require !== null) {
- exports.require = fakeRequire();
- }
-
-
- exports.require = fakeRequire;
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveI18n.js b/src/common/managesieve.ui/utils/SieveI18n.js
deleted file mode 100644
index 0b4179ce..00000000
--- a/src/common/managesieve.ui/utils/SieveI18n.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const FIRST_ELEMENT = 0;
-
- const DEFAULT_LOCALE = "en-US";
- const DEFAULT_PATH = "./i18n/";
-
- // A list with all supported languages.
- const LANGUAGES = new Set();
- LANGUAGES.add("en-US");
- LANGUAGES.add("de-DE");
-
- // Maps a language to a supported language.
- const LANGUAGE_MAPPING = new Map();
- LANGUAGE_MAPPING.set("en", "en-US");
- LANGUAGE_MAPPING.set("de", "de-DE");
-
- const { SieveLogger } = require("./SieveLogger.js");
-
- let instance = null;
-
- /**
- * A poor mans i18n helper class which provides help to translate strings.
- */
- class SieveI18n {
-
- /**
- * Initializes a new instance.
- */
- constructor() {
- this.entities = {};
- }
-
- /**
- * Creates or returns an initialized i18n instance.
- * It is guaranteed to be a singleton.
- *
- * @returns {SieveI18n}
- * the logger instance.
- */
- static getInstance() {
-
- if (instance === null)
- instance = new SieveI18n();
-
- return instance;
- }
-
- /**
- * Gets an instance of the default logger.
- *
- * @returns {SieveLogger}
- * a reference to a logger instance.
- */
- getLogger() {
- return SieveLogger.getInstance();
- }
-
-
- /**
- * Tries to find a compatible and supported language.
- * In case the language can not be mapped it will
- * fallback to american english.
- *
- * @param {string} language
- * a language string in BCP 47 format
- *
- * @returns {string}
- * the best compatible locale.
- */
- getLanguage(language) {
-
- // Check if it's a perfect match with a well known language region.
- if (LANGUAGES.has(language))
- return language;
-
- // If not we split the language from the region...
- language = language.split('-')[FIRST_ELEMENT].toLowerCase();
-
- // ... and try to find the matching the language.
- // in case it fails we fall back to the default.
- if (!LANGUAGE_MAPPING.has(language))
- return DEFAULT_LOCALE;
-
- language = LANGUAGE_MAPPING.get(language);
-
- // Double check that our mapping really points to a supported language.
- // if not we fall back to the default.
- if (!LANGUAGES.has(language))
- return DEFAULT_LOCALE;
-
- return language;
- }
-
- /**
- * Loads translations for the given locale.
- *
- * @param {string} [locale]
- * optional the locale to be loaded, if omitted or set to "default"
- * the browser's default language (navigator.language) is used.
- * @param {string} [path]
- * the optional path to the directory where the locale files are stored.
- * If omitted ./i18n is used
- * @returns {SieveI18n}
- * a self reference.
- */
- async load(locale, path) {
-
- if (typeof (locale) === "undefined" || locale === null || locale === "default")
- locale = navigator.language;
-
- if (typeof (path) === "undefined" || path === null)
- path = DEFAULT_PATH;
-
- if (!path.endsWith("/"))
- path = `${path}/`;
-
- this.getLogger().logI18n(`Language set to ${locale}`);
-
- locale = this.getLanguage(locale);
-
- this.getLogger().logI18n(`Language normalized to ${locale}`);
-
- try {
- await this.loadDictionary(`${path}${locale}.json`);
- } catch (ex) {
- // In case loading the dictionary failed e.g. due to a parsing error
- // we try falling back to our default one which is used during development.
- await this.loadDictionary(`${path}${DEFAULT_LOCALE}.json`);
- }
-
- return this;
- }
-
- /**
- * Loads a dictionary which is used to translate the strings.
- * It will throw an exception in case the dictionary can not be loaded.
- *
- * @param {string} dictionary
- * the path to the dictionary file.
- *
- * @returns {SieveI18n}
- * a self reference
- */
- async loadDictionary(dictionary) {
-
- let data = null;
-
- try {
- data = await (await fetch(dictionary, { cache: "no-store" })).text();
- } catch (ex) {
- this.getLogger().logI18n(`Failed to load dictionary ${dictionary}`);
- throw new Error(`Failed to load dictionary ${dictionary}`);
- }
-
- try {
- this.entities = JSON.parse(data.replace(/^\s+\/\/.*$/gm, ""));
- } catch (ex) {
- this.getLogger().logI18n(`Parsing dictionary ${dictionary} failed with error ${ex}`);
- }
-
- this.getLogger().logI18n(`Dictionary ${dictionary} loaded`);
-
- return this;
- }
-
- /**
- * Returns the translated string for the entity.
- * In case no translation was found an exception is thrown.
- *
- * @param {string} entity
- * the string which should be translated
- * @returns {string}
- * the translated string
- */
- getString(entity) {
- const value = this.entities[entity];
-
- if (typeof (value) === "undefined" || value === null)
- throw new Error(`No translation for ${entity}`);
-
- return value;
- }
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveI18n = SieveI18n;
- else
- exports.SieveI18n = SieveI18n;
-
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveI18n.mjs b/src/common/managesieve.ui/utils/SieveI18n.mjs
new file mode 100644
index 00000000..fbe17c47
--- /dev/null
+++ b/src/common/managesieve.ui/utils/SieveI18n.mjs
@@ -0,0 +1,196 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const FIRST_ELEMENT = 0;
+
+const DEFAULT_LOCALE = "en-US";
+const DEFAULT_PATH = "./i18n/";
+
+// A list with all supported languages.
+const LANGUAGES = new Set();
+LANGUAGES.add("en-US");
+LANGUAGES.add("de-DE");
+
+// Maps a language to a supported language.
+const LANGUAGE_MAPPING = new Map();
+LANGUAGE_MAPPING.set("en", "en-US");
+LANGUAGE_MAPPING.set("de", "de-DE");
+
+import { SieveLogger } from "./SieveLogger.mjs";
+
+let instance = null;
+
+/**
+ * A poor mans i18n helper class which provides help to translate strings.
+ */
+class SieveI18n {
+
+ /**
+ * Initializes a new instance.
+ */
+ constructor() {
+ this.entities = {};
+ }
+
+ /**
+ * Creates or returns an initialized i18n instance.
+ * It is guaranteed to be a singleton.
+ *
+ * @returns {SieveI18n}
+ * the logger instance.
+ */
+ static getInstance() {
+
+ if (instance === null)
+ instance = new SieveI18n();
+
+ return instance;
+ }
+
+ /**
+ * Gets an instance of the default logger.
+ *
+ * @returns {SieveLogger}
+ * a reference to a logger instance.
+ */
+ getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+
+ /**
+ * Tries to find a compatible and supported language.
+ * In case the language can not be mapped it will
+ * fallback to american english.
+ *
+ * @param {string} language
+ * a language string in BCP 47 format
+ *
+ * @returns {string}
+ * the best compatible locale.
+ */
+ getLanguage(language) {
+
+ // Check if it's a perfect match with a well known language region.
+ if (LANGUAGES.has(language))
+ return language;
+
+ // If not we split the language from the region...
+ language = language.split('-')[FIRST_ELEMENT].toLowerCase();
+
+ // ... and try to find the matching the language.
+ // in case it fails we fall back to the default.
+ if (!LANGUAGE_MAPPING.has(language))
+ return DEFAULT_LOCALE;
+
+ language = LANGUAGE_MAPPING.get(language);
+
+ // Double check that our mapping really points to a supported language.
+ // if not we fall back to the default.
+ if (!LANGUAGES.has(language))
+ return DEFAULT_LOCALE;
+
+ return language;
+ }
+
+ /**
+ * Loads translations for the given locale.
+ *
+ * @param {string} [locale]
+ * optional the locale to be loaded, if omitted or set to "default"
+ * the browser's default language (navigator.language) is used.
+ * @param {string} [path]
+ * the optional path to the directory where the locale files are stored.
+ * If omitted ./i18n is used
+ * @returns {SieveI18n}
+ * a self reference.
+ */
+ async load(locale, path) {
+
+ if (typeof (locale) === "undefined" || locale === null || locale === "default")
+ locale = navigator.language;
+
+ if (typeof (path) === "undefined" || path === null)
+ path = DEFAULT_PATH;
+
+ if (!path.endsWith("/"))
+ path = `${path}/`;
+
+ this.getLogger().logI18n(`Language set to ${locale}`);
+
+ locale = this.getLanguage(locale);
+
+ this.getLogger().logI18n(`Language normalized to ${locale}`);
+
+ try {
+ await this.loadDictionary(`${path}${locale}.json`);
+ } catch {
+ // In case loading the dictionary failed e.g. due to a parsing error
+ // we try falling back to our default one which is used during development.
+ await this.loadDictionary(`${path}${DEFAULT_LOCALE}.json`);
+ }
+
+ return this;
+ }
+
+ /**
+ * Loads a dictionary which is used to translate the strings.
+ * It will throw an exception in case the dictionary can not be loaded.
+ *
+ * @param {string} dictionary
+ * the path to the dictionary file.
+ *
+ * @returns {SieveI18n}
+ * a self reference
+ */
+ async loadDictionary(dictionary) {
+
+ let data = null;
+
+ try {
+ data = await (await fetch(dictionary, { cache: "no-store" })).text();
+ } catch (ex) {
+ this.getLogger().logI18n(`Loading dictionary ${dictionary} failed with error ${ex}`);
+ throw new Error(`Failed to load dictionary ${dictionary}`);
+ }
+
+ try {
+ this.entities = JSON.parse(data.replace(/^\s+\/\/.*$/gm, ""));
+ } catch (ex) {
+ this.getLogger().logI18n(`Parsing dictionary ${dictionary} failed with error ${ex}`);
+ }
+
+ this.getLogger().logI18n(`Dictionary ${dictionary} loaded`);
+
+ return this;
+ }
+
+ /**
+ * Returns the translated string for the entity.
+ * In case no translation was found an exception is thrown.
+ *
+ * @param {string} entity
+ * the string which should be translated
+ * @returns {string}
+ * the translated string
+ */
+ getString(entity) {
+ const value = this.entities[entity];
+
+ if (typeof (value) === "undefined" || value === null)
+ throw new Error(`No translation for ${entity}`);
+
+ return value;
+ }
+
+}
+
+export { SieveI18n };
diff --git a/src/common/managesieve.ui/utils/SieveLogger.js b/src/common/managesieve.ui/utils/SieveLogger.js
deleted file mode 100644
index bee5916b..00000000
--- a/src/common/managesieve.ui/utils/SieveLogger.js
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const TWO_CHARS = 2;
- const THREE_CHARS = 3;
- const BASE_10 = 10;
-
- // eslint-disable-next-line no-magic-numbers
- const LOG_IPC_MESSAGES = (1 << 0);
- // eslint-disable-next-line no-magic-numbers
- const LOG_ACTION = (1 << 1);
- // eslint-disable-next-line no-magic-numbers
- const LOG_UI = (1 << 2);
- // eslint-disable-next-line no-magic-numbers
- const LOG_I18N = (1 << 3);
-
- const DEFAULT_LEVEL = 0x00;
-
- let instance = null;
-
- /**
- * Implements a common and platform independent logging interface.
- * The log level is interpreted as a bit filed with turns logging
- * for the specified scope on and of.
- *
- * The level is concerning scopes and does not differentiate between
- * warning, error and info.
- */
- class SieveLogger {
-
- /**
- * Creates a new instance
- * @param {int} [level]
- * the logger level
- *
- */
- constructor(level) {
-
- if (typeof (level) === "undefined")
- level = DEFAULT_LEVEL;
-
- this._level = level;
- }
-
- /**
- * Creates or returns a logger instance.
- * It is guaranteed to be a singleton.
- *
- * @returns {SieveLogger}
- * the logger instance.
- */
- static getInstance() {
- if (instance === null)
- instance = new SieveLogger();
-
- return instance;
- }
-
- /**
- * Logs an action related message which as triggered by the
- * user on the backend
- *
- * @param {string} message
- * the log message
- * @returns {SieveLogger}
- * a self reference
- */
- logAction(message) {
- return this.log(message, "Action", LOG_ACTION);
- }
-
- /**
- * Logs an ipc related log message.
- *
- * @param {string} message
- * the log message
- * @returns {SieveLogger}
- * a self reference
- */
- logIpcMessage(message) {
- return this.log(message, "Ipc", LOG_IPC_MESSAGES);
- }
-
- /**
- * Logs an ui related message.
- *
- * @param {string} message
- * the log message
- * @returns {SieveLogger}
- * a self reference
- */
- logWidget(message) {
- return this.log(message, "Ui", LOG_UI);
- }
-
- /**
- * Logs a i18n related message.
- *
- * @param {string} message
- * the log message.
- * @returns {SieveLogger}
- * a self reference.
- */
- logI18n(message) {
- return this.log(message, "I18n", LOG_I18N);
- }
-
- /**
- * Logs the given message to the browser console.
- *
- * @param {string} message
- * the message which should be logged
- * @param {string} prefix
- * the log messages prefix.
- * @param {int} [level]
- * the log level. If omitted the message will be always logged.
- * @returns {SieveLogger}
- * a self reference
- */
- log(message, prefix, level) {
-
- if (!this.isLoggable(level))
- return this;
-
- if ((typeof (prefix) === "undefined") || prefix === null)
- prefix = "";
-
- // eslint-disable-next-line no-console
- console.log(`[${this.getTimestamp()} ${prefix}] ${message}`);
- return this;
- }
-
-
- /**
- * Tests if the log level should log.
- *
- * @param {int} level
- * the level which should be checked.
- * @returns {boolean}
- * true in case the log level is activated otherwise false
- */
- isLoggable(level) {
- if (typeof (level) === "undefined")
- return true;
-
- return !!(this.level() & level);
- }
-
- /**
- * Gets and sets the log level to the given bit mask.
- * Note that the log level is a bit mask, every bit in the
- * bit mask corresponds to a special logger.
- *
- * In order to activate or deactivate a logger you need to
- * get the level toggle the desired bits and set the new level.
- *
- * @param {int} [level]
- * the desired log level as bit mask.
- * @returns {int}
- * the current log level
- */
- level(level) {
- if (typeof (level) !== "undefined")
- this._level = level;
-
- return this._level;
- }
-
-
- /**
- * Pads the given string with leading zeros
- * @private
- *
- * @param {string} n
- * the string which should be padded
- * @param {int} m
- * the maximum padding.
- *
- * @returns {string}
- * the padded string
- */
- _pad(n, m) {
-
- let str = n;
-
- for (let i = 0; i < m; i++)
- if (n < Math.pow(BASE_10, i))
- str = '0' + str;
-
- return str;
- }
-
- /**
- * Gets the current time in iso format (hh:mm:ss.SSS)
- *
- * @returns {string}
- * the current timestamp as string.
- */
- getTimestamp() {
-
- const date = new Date();
- return this._pad(date.getHours(), TWO_CHARS)
- + ":" + this._pad(date.getMinutes(), TWO_CHARS)
- + ":" + this._pad(date.getSeconds(), TWO_CHARS)
- + "." + this._pad(date.getMilliseconds(), THREE_CHARS);
- }
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveLogger = SieveLogger;
- else
- exports.SieveLogger = SieveLogger;
-
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveLogger.mjs b/src/common/managesieve.ui/utils/SieveLogger.mjs
new file mode 100644
index 00000000..1839a068
--- /dev/null
+++ b/src/common/managesieve.ui/utils/SieveLogger.mjs
@@ -0,0 +1,226 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const TWO_CHARS = 2;
+const THREE_CHARS = 3;
+const BASE_10 = 10;
+
+// eslint-disable-next-line no-magic-numbers
+const LOG_IPC_MESSAGES = (1 << 0);
+// eslint-disable-next-line no-magic-numbers
+const LOG_ACTION = (1 << 1);
+// eslint-disable-next-line no-magic-numbers
+const LOG_UI = (1 << 2);
+// eslint-disable-next-line no-magic-numbers
+const LOG_I18N = (1 << 3);
+// eslint-disable-next-line no-magic-numbers
+const LOG_TRACE = (1 << 5);
+
+const DEFAULT_LEVEL = 0x00;
+
+let instance = null;
+
+/**
+ * Implements a common and platform independent logging interface.
+ * The log level is interpreted as a bit filed with turns logging
+ * for the specified scope on and of.
+ *
+ * The level is concerning scopes and does not differentiate between
+ * warning, error and info.
+ */
+class SieveLogger {
+
+ /**
+ * Creates a new instance
+ * @param {int} [level]
+ * the logger level
+ *
+ */
+ constructor(level) {
+
+ if (typeof (level) === "undefined")
+ level = DEFAULT_LEVEL;
+
+ this._level = level;
+ }
+
+ /**
+ * Creates or returns a logger instance.
+ * It is guaranteed to be a singleton.
+ *
+ * @returns {SieveLogger}
+ * the logger instance.
+ */
+ static getInstance() {
+ if (instance === null)
+ instance = new SieveLogger();
+
+ return instance;
+ }
+
+ /**
+ * Logs an action related message which as triggered by the
+ * user on the backend
+ *
+ * @param {string} message
+ * the log message
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logAction(message) {
+ return this.log(message, "Action", LOG_ACTION);
+ }
+
+ /**
+ * Logs an ipc related log message.
+ *
+ * @param {string} message
+ * the log message
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logIpcMessage(message) {
+ return this.log(message, "Ipc", LOG_IPC_MESSAGES);
+ }
+
+ /**
+ * Logs an ui related message.
+ *
+ * @param {string} message
+ * the log message
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ logWidget(message) {
+ return this.log(message, "Ui", LOG_UI);
+ }
+
+ /**
+ * Logs a i18n related message.
+ *
+ * @param {string} message
+ * the log message.
+ * @returns {SieveLogger}
+ * a self reference.
+ */
+ logI18n(message) {
+ return this.log(message, "I18n", LOG_I18N);
+ }
+
+ /**
+ * Logs the given message to the browser console.
+ *
+ * @param {string} message
+ * the message which should be logged
+ * @param {string} prefix
+ * the log messages prefix.
+ * @param {int} [level]
+ * the log level. If omitted the message will be always logged.
+ * @returns {SieveLogger}
+ * a self reference
+ */
+ log(message, prefix, level) {
+
+ if (!this.isLoggable(level))
+ return this;
+
+ if ((typeof (prefix) === "undefined") || prefix === null)
+ prefix = "";
+
+ if (this.isLoggable(LOG_TRACE)) {
+ // eslint-disable-next-line no-console
+ console.trace(`[${this.getTimestamp()} ${prefix}] ${message}`);
+ return this;
+ }
+
+ // eslint-disable-next-line no-console
+ console.log(`[${this.getTimestamp()} ${prefix}] ${message}`);
+ return this;
+ }
+
+
+ /**
+ * Tests if the log level should log.
+ *
+ * @param {int} level
+ * the level which should be checked.
+ * @returns {boolean}
+ * true in case the log level is activated otherwise false
+ */
+ isLoggable(level) {
+ if (typeof (level) === "undefined")
+ return true;
+
+ return !!(this.level() & level);
+ }
+
+ /**
+ * Gets and sets the log level to the given bit mask.
+ * Note that the log level is a bit mask, every bit in the
+ * bit mask corresponds to a special logger.
+ *
+ * In order to activate or deactivate a logger you need to
+ * get the level toggle the desired bits and set the new level.
+ *
+ * @param {int} [level]
+ * the desired log level as bit mask.
+ * @returns {int}
+ * the current log level
+ */
+ level(level) {
+ if (typeof (level) !== "undefined")
+ this._level = level;
+
+ return this._level;
+ }
+
+
+ /**
+ * Pads the given string with leading zeros
+ * @private
+ *
+ * @param {string} n
+ * the string which should be padded
+ * @param {int} m
+ * the maximum padding.
+ *
+ * @returns {string}
+ * the padded string
+ */
+ _pad(n, m) {
+
+ let str = n;
+
+ for (let i = 0; i < m; i++)
+ if (n < Math.pow(BASE_10, i))
+ str = '0' + str;
+
+ return str;
+ }
+
+ /**
+ * Gets the current time in iso format (hh:mm:ss.SSS)
+ *
+ * @returns {string}
+ * the current timestamp as string.
+ */
+ getTimestamp() {
+
+ const date = new Date();
+ return this._pad(date.getHours(), TWO_CHARS)
+ + ":" + this._pad(date.getMinutes(), TWO_CHARS)
+ + ":" + this._pad(date.getSeconds(), TWO_CHARS)
+ + "." + this._pad(date.getMilliseconds(), THREE_CHARS);
+ }
+
+}
+
+export { SieveLogger };
diff --git a/src/common/managesieve.ui/utils/SieveTemplate.js b/src/common/managesieve.ui/utils/SieveTemplate.js
deleted file mode 100644
index 40907519..00000000
--- a/src/common/managesieve.ui/utils/SieveTemplate.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveLogger } = require("./SieveLogger.js");
- const { SieveI18n } = require("./SieveI18n.js");
-
- /**
- * Loads an html fragment from a file or url.
- */
- class SieveTemplate {
-
- /**
- * Gets an instance of the default i18n
- *
- * @returns {SieveI18n}
- * a reference to an i18n instance.
- */
- getI18n() {
- return SieveI18n.getInstance();
- }
-
- /**
- * Gets an instance of the default logger.
- *
- * @returns {SieveLogger}
- * a reference to a logger instance.
- */
- getLogger() {
- return SieveLogger.getInstance();
- }
-
- /**
- * Translates a loaded template.
- * It queries all data-i18n and translates all elements found.
- *
- * @param {DocumentFragment} fragment
- * the template which should be translated.
- * @returns {DocumentFragment}
- * the translated template.
- */
- translate(fragment) {
-
- // Check if a translator is attached to this loader...
- // if ((typeof(this.i18n) === "undefined") || (this.i18n === null))
- // return fragment;
-
- // Get all elements with a data-i18n tag from the fragment.
- for (const elm of fragment.querySelectorAll('[data-i18n]')) {
-
- const entity = elm.dataset.i18n;
-
- // We translate the placeholder on HTML Elements
- if ((elm instanceof HTMLInputElement) && (elm.type === "text")) {
- try {
- elm.placeholder = this.getI18n().getString(entity);
- } catch (ex) {
- this.getLogger().logI18n(ex);
- }
- continue;
- }
-
- // Warn if text content is not empty.
- if (elm.textContent.trim() !== "") {
- this.getLogger().logI18n(`Text node for ${entity} not empty, replacing existing text`);
- }
-
- // Get the translation and update the text...
- try {
- elm.textContent = this.getI18n().getString(entity);
- } catch (ex) {
- this.getLogger().logI18n(ex);
- elm.classList.add("alert-danger");
- elm.textContent = entity;
- }
- }
-
- return fragment;
- }
-
- /**
- * Loads an html fragment from file or url
- *
- * @param {string} tpl
- * the path tho the template file
- * @returns {Promise<HTMLElement>}
- * the template which should be loaded.
- */
- async load(tpl) {
-
- this.getLogger().logWidget(`Load template ${tpl}`);
-
- const html = await (await fetch(tpl, { cache: "no-store" })).text();
-
- const doc = (new DOMParser()).parseFromString(html, "text/html");
-
- return this.translate(doc.body.firstElementChild);
- }
- }
-
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveTemplate = SieveTemplate;
- else
- exports.SieveTemplate = SieveTemplate;
-
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveTemplate.mjs b/src/common/managesieve.ui/utils/SieveTemplate.mjs
new file mode 100644
index 00000000..514e5007
--- /dev/null
+++ b/src/common/managesieve.ui/utils/SieveTemplate.mjs
@@ -0,0 +1,108 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveLogger } from "./SieveLogger.mjs";
+import { SieveI18n } from "./SieveI18n.mjs";
+
+/**
+ * Loads an html fragment from a file or url.
+ */
+class SieveTemplate {
+
+ /**
+ * Gets an instance of the default i18n
+ *
+ * @returns {SieveI18n}
+ * a reference to an i18n instance.
+ */
+ getI18n() {
+ return SieveI18n.getInstance();
+ }
+
+ /**
+ * Gets an instance of the default logger.
+ *
+ * @returns {SieveLogger}
+ * a reference to a logger instance.
+ */
+ getLogger() {
+ return SieveLogger.getInstance();
+ }
+
+ /**
+ * Translates a loaded template.
+ * It queries all data-i18n and translates all elements found.
+ *
+ * @param {DocumentFragment} fragment
+ * the template which should be translated.
+ * @returns {DocumentFragment}
+ * the translated template.
+ */
+ translate(fragment) {
+
+ // Check if a translator is attached to this loader...
+ // if ((typeof(this.i18n) === "undefined") || (this.i18n === null))
+ // return fragment;
+
+ // Get all elements with a data-i18n tag from the fragment.
+ for (const elm of fragment.querySelectorAll('[data-i18n]')) {
+
+ const entity = elm.dataset.i18n;
+
+ // We translate the placeholder on HTML Elements
+ if ((elm instanceof HTMLInputElement) && (elm.type === "text")) {
+ try {
+ elm.placeholder = this.getI18n().getString(entity);
+ } catch (ex) {
+ this.getLogger().logI18n(ex);
+ }
+ continue;
+ }
+
+ // Warn if text content is not empty.
+ if (elm.textContent.trim() !== "") {
+ this.getLogger().logI18n(`Text node for ${entity} not empty, replacing existing text`);
+ }
+
+ // Get the translation and update the text...
+ try {
+ elm.textContent = this.getI18n().getString(entity);
+ } catch (ex) {
+ this.getLogger().logI18n(ex);
+ elm.classList.add("alert-danger");
+ elm.textContent = entity;
+ }
+ }
+
+ return fragment;
+ }
+
+ /**
+ * Loads an html fragment from file or url
+ *
+ * @param {string} tpl
+ * the path tho the template file
+ * @returns {Promise<HTMLElement>}
+ * the template which should be loaded.
+ */
+ async load(tpl) {
+
+ this.getLogger().logWidget(`Load template ${tpl}`);
+
+ const html = await (await fetch(tpl, { cache: "no-store" })).text();
+
+ const doc = (new DOMParser()).parseFromString(html, "text/html");
+
+ return this.translate(doc.body.firstElementChild);
+ }
+}
+
+export { SieveTemplate };
diff --git a/src/common/managesieve.ui/utils/SieveUniqueId.js b/src/common/managesieve.ui/utils/SieveUniqueId.js
deleted file mode 100644
index 1786b8da..00000000
--- a/src/common/managesieve.ui/utils/SieveUniqueId.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * The contents of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via email
- * from the author. Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- *
- */
-
-(function (exports) {
-
- // Enable Strict Mode
- "use strict";
-
- const ASCII = 36;
- const SEED_OFFSET = 2;
- const SEED_LENGTH = 16;
- /**
- * Generates a poor mans unique id.
- * It simply combines the current time with a random number.
- * Which should be more than sufficient for us.
- */
- class SieveUniqueId {
-
- /**
- * Creates a pseudo random alpha numerical id.
- * @returns {string}
- * the generated id.
- */
- generate() {
- // "" + Math.floor(Math.random() * 10000000).toString(16) + Date.now().toString(16)
- return (new Date()).getTime().toString(ASCII)
- + "-" + Math.random().toString(ASCII).substr(SEED_OFFSET, SEED_LENGTH);
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveUniqueId = SieveUniqueId;
- else
- exports.SieveUniqueId = SieveUniqueId;
-
-})(this);
diff --git a/src/common/managesieve.ui/utils/SieveUniqueId.mjs b/src/common/managesieve.ui/utils/SieveUniqueId.mjs
new file mode 100644
index 00000000..3e1379b2
--- /dev/null
+++ b/src/common/managesieve.ui/utils/SieveUniqueId.mjs
@@ -0,0 +1,33 @@
+/*
+ * The contents of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via email
+ * from the author. Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ *
+ */
+
+const ASCII = 36;
+const SEED_OFFSET = 2;
+const SEED_LENGTH = 16;
+/**
+ * Generates a poor mans unique id.
+ * It simply combines the current time with a random number.
+ * Which should be more than sufficient for us.
+ */
+class SieveUniqueId {
+
+ /**
+ * Creates a pseudo random alpha numerical id.
+ * @returns {string}
+ * the generated id.
+ */
+ generate() {
+ // "" + Math.floor(Math.random() * 10000000).toString(16) + Date.now().toString(16)
+ return Date.now().toString(ASCII)
+ + "-" + Math.random().toString(ASCII).substr(SEED_OFFSET, SEED_LENGTH);
+ }
+}
+
+export { SieveUniqueId };
diff --git a/src/wx/api/sieve/SieveAccountsApi.js b/src/wx/api/sieve/SieveAccountsApi.js
index 201cfb23..be264dc2 100644
--- a/src/wx/api/sieve/SieveAccountsApi.js
+++ b/src/wx/api/sieve/SieveAccountsApi.js
@@ -11,8 +11,6 @@
(function (exports) {
- "use strict";
-
/* global ExtensionCommon */
/* global Components */
diff --git a/src/wx/api/sieve/SieveAccountsApi.json b/src/wx/api/sieve/SieveAccountsApi.json
index 328b1a76..6c6967a3 100644
--- a/src/wx/api/sieve/SieveAccountsApi.json
+++ b/src/wx/api/sieve/SieveAccountsApi.json
@@ -8,22 +8,26 @@
"type": "function",
"description": "Gets a password from the password store",
"async": true,
- "parameters": [{
+ "parameters": [
+ {
"name": "id",
"type": "string",
"description": "The server's id"
- }]
+ }
+ ]
},
{
"name": "getPrettyName",
"type": "function",
"description": "Gets a password from the password store",
"async": true,
- "parameters": [{
+ "parameters": [
+ {
"name": "id",
"type": "string",
"description": "The account key"
- }]
+ }
+ ]
},
{
"name": "getUsername",
@@ -43,11 +47,13 @@
"type": "function",
"description": "Gets a password from the password store",
"async": true,
- "parameters": [{
+ "parameters": [
+ {
"name": "id",
"type": "string",
"description": "The account key"
- }]
+ }
+ ]
}
]
}
diff --git a/src/wx/api/sieve/SieveMenuApi.js b/src/wx/api/sieve/SieveMenuApi.js
index f9dcab27..92fef2a2 100644
--- a/src/wx/api/sieve/SieveMenuApi.js
+++ b/src/wx/api/sieve/SieveMenuApi.js
@@ -11,8 +11,6 @@
(function (exports) {
- "use strict";
-
/* global ExtensionCommon */
/* global Components */
@@ -42,7 +40,7 @@
* true in case the element exists otherwise false.
*/
hasNode(id) {
- const node = this.document.getElementById(id);
+ const node = this.document.querySelector(`#${id}`);
if (!node)
return false;
@@ -60,7 +58,7 @@
* the dom element or throws an exception.
*/
getNode(id) {
- const node = this.document.getElementById(id);
+ const node = this.document.querySelector(`#${id}`);
if (!node)
throw new Error(`Unknown element ${id}`);
@@ -74,9 +72,9 @@
* the node to be removed.
*/
removeNode(id) {
- const elm = this.document.getElementById(id);
+ const elm = this.document.querySelector(`#${id}`);
if (elm)
- elm.parentNode.removeChild(elm);
+ elm.remove();
}
/**
@@ -121,7 +119,7 @@
const ref = this.getNode(refId);
this.removeNode(item.getId());
- ref.appendChild(item.createNode(this.document));
+ ref.append(item.createNode(this.document));
}
}
@@ -384,7 +382,7 @@
onCommand: new ExtensionCommon.EventManager({
context,
- name: "sieve.session.onCommand",
+ name: "sieve.menu.onCommand",
register: (fire) => {
const callback = async (windowsId, id) => {
diff --git a/src/wx/api/sieve/SieveMenuApi.json b/src/wx/api/sieve/SieveMenuApi.json
index 89814178..c3d7111b 100644
--- a/src/wx/api/sieve/SieveMenuApi.json
+++ b/src/wx/api/sieve/SieveMenuApi.json
@@ -8,31 +8,36 @@
"type": "function",
"description": "Checks if the menu item exists.",
"async": true,
- "parameters": [{
- "name": "windowId",
- "type": "string",
- "description" : "The unique window id"
- },{
- "name": "widgetId",
- "type": "string",
- "description" : "the widget unique id",
- "additionalProperties" :true
- }]
+ "parameters": [
+ {
+ "name": "windowId",
+ "type": "string",
+ "description": "The unique window id"
+ },
+ {
+ "name": "widgetId",
+ "type": "string",
+ "description": "the widget unique id",
+ "additionalProperties": true
+ }
+ ]
},
{
"name": "add",
"type": "function",
"description": "Adds the menu item, in case it exists it will be overwritten",
"async": true,
- "parameters": [{
+ "parameters": [
+ {
"name": "windowId",
"type": "string",
- "description" : "The unique window id"
- },{
+ "description": "The unique window id"
+ },
+ {
"name": "widget",
"type": "object",
- "description" : "the widget description",
- "additionalProperties" :true
+ "description": "the widget description",
+ "additionalProperties": true
}
]
},
@@ -41,16 +46,19 @@
"type": "function",
"description": "Removes the menu item.",
"async": true,
- "parameters": [{
- "name": "windowId",
- "type": "string",
- "description" : "The unique window id"
- },{
- "name": "widgetId",
- "type": "string",
- "description" : "the widget unique id",
- "additionalProperties" :true
- }]
+ "parameters": [
+ {
+ "name": "windowId",
+ "type": "string",
+ "description": "The unique window id"
+ },
+ {
+ "name": "widgetId",
+ "type": "string",
+ "description": "the widget unique id",
+ "additionalProperties": true
+ }
+ ]
}
],
"events": [
diff --git a/src/wx/api/sieve/SieveSessionApi.js b/src/wx/api/sieve/SieveSessionApi.js
deleted file mode 100644
index e46dcadd..00000000
--- a/src/wx/api/sieve/SieveSessionApi.js
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global ExtensionCommon */
- /* global Components */
- /* global ChromeUtils */
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
-
- const logger = undefined;
- // // eslint-disable-next-line no-console
- // const logger = function(msg) { console.log(msg); };
-
- /**
- * Implements a webextension api for sieve session and connection management.
- */
- class SieveSessionApi extends ExtensionCommon.ExtensionAPI {
- /**
- * @inheritdoc
- */
- getAPI(context) {
-
- const rootUri = context.extension.rootURI;
- const url = context.extension.getURL();
-
- // The default case it loading from a jar file.
- let requireUrl = rootUri.resolve("/modules/SieveRequire.jsm");
-
- // But for debugging we are loading from a file url. Which is prohibited
- // so we need to convert the file url into an extension url.
- if (rootUri.schemeIs("file"))
- requireUrl = `${url}/modules/SieveRequire.jsm`;
-
- const { Modules } = ChromeUtils.import(requireUrl);
- const modules = new Modules(
- `${url}/libs/libManageSieve/`, logger);
-
- const sessions = new Map();
-
- context.callOnClose({ close: () => {
-
- // First ensure we exit all active sessions.
- for (const item of sessions.values())
- item.disconnect(true);
- sessions.clear();
-
- // Then invalidate all the modules.
- modules.invalidate();
-
- // And finally unload the module loader.
- Components.utils.unload(rootUri.resolve("/modules/SieveRequire.jsm"));
- }});
-
- const { SieveSession } = modules.require("./SieveSession.js");
-
- return {
- sieve: {
- session: {
-
- onAuthenticate: new ExtensionCommon.EventManager({
- context,
- name: "sieve.session.onAuthenticate",
- register: (fire, id) => {
-
- const callback = async (hasPassword) => {
- return await fire.async(hasPassword);
- };
-
- sessions.get(id).on("authenticate", callback);
-
- return () => {
- if (sessions.has(id))
- sessions.get(id).on("authenticate");
- };
- }
- }).api(),
-
- onAuthorize: new ExtensionCommon.EventManager({
- context,
- name: "sieve.session.onAuthorize",
- register: (fire, id) => {
-
- const callback = async () => {
- return await fire.async();
- };
-
- sessions.get(id).on("authorize", callback);
-
- return () => {
- if (sessions.has(id))
- sessions.get(id).on("authorize");
- };
- }
- }).api(),
-
- onCertError: new ExtensionCommon.EventManager({
- context,
- name: "sieve.session.onCertError",
- register: (fire, id) => {
-
- // It is just a notification so no need to wait.
- const callback = (secInfo) => {
- fire.async(secInfo);
- };
-
- sessions.get(id).on("certerror", callback);
-
- return () => {
- if (sessions.has(id))
- sessions.get(id).on("certerror");
- };
- }
- }).api(),
-
- onProxyLookup: new ExtensionCommon.EventManager({
- context,
- name: "sieve.session.onProxyLookup",
- register: (fire, id) => {
-
- const callback = async (host, port) => {
- return await fire.async(host, port);
- };
-
- sessions.get(id).on("proxy", callback);
-
- return () => {
- if (sessions.has(id))
- sessions.get(id).on("proxy");
- };
- }
- }).api(),
-
- // eslint-disable-next-line require-await
- async create(id, options) {
-
- if (sessions.has(id))
- throw new Error("Id already in use");
-
- sessions.set(id,
- new SieveSession(id, options));
- },
-
- async destroy(id) {
- if (!sessions.has(id))
- return;
-
- await this.disconnect(id, true);
- sessions.delete(id);
- },
-
- async has(id) {
- return await sessions.has(id);
- },
-
- // eslint-disable-next-line require-await
- async addCertErrorOverride(hostname, port, rawDER, flags) {
-
- const overrideService = Cc["@mozilla.org/security/certoverride;1"]
- .getService(Ci.nsICertOverrideService);
-
- const certdb = Cc["@mozilla.org/security/x509certdb;1"]
- .getService(Ci.nsIX509CertDB);
-
- // The constructX509 has an incompatible change.
- // While newer version consume an array, older version use strings.
- // This magic is only needed for thunderbird 68
- let cert = null;
- try {
- // Try first with the new api..
- cert = certdb.constructX509(rawDER);
- } catch (ex) {
-
- if (ex.name !== "NS_ERROR_FAILURE")
- throw ex;
-
- // ... other wise the use the old api.
- cert = "";
- for (const ch of rawDER)
- cert += (String.fromCharCode(ch));
-
- cert = certdb.constructX509(cert);
- }
-
- overrideService.rememberValidityOverride(
- hostname, port,
- cert,
- flags,
- false);
- },
-
- async connect(id, host, port) {
- await sessions.get(id).connect(host, port);
- },
-
- async disconnect(id, force) {
- await sessions.get(id).disconnect(force);
- },
-
- async isConnected(id) {
- if (!sessions.has(id))
- return false;
-
- return await sessions.get(id).isConnected();
- },
-
- async capabilities(id) {
- return await sessions.get(id).capabilities();
- },
-
- async listScripts(id) {
- return await sessions.get(id).listScripts();
- },
-
- async putScript(id, name, body) {
- return await sessions.get(id).putScript(name, body);
- },
-
- async getScript(id, name) {
- return await sessions.get(id).getScript(name);
- },
-
- async renameScript(id, oldname, newname) {
- return await sessions.get(id).renameScript(oldname, newname);
- },
-
- async deleteScript(id, name) {
- return await sessions.get(id).deleteScript(name);
- },
-
- async activateScript(id, name) {
- return await sessions.get(id).activateScript(name);
- },
-
- async checkScript(id, body) {
- // TODO move the try catch into checkscript...
- try {
- await sessions.get(id).checkScript(body);
- } catch (ex) {
- // FIXME We need to rethrow incase checkscript returns a SieveServerException.
- return ex.getResponse().getMessage();
- }
-
- return "";
- },
-
- async probe(host, port) {
-
- const { Sieve } = modules.require("./SieveSession.js");
- const { SieveLogger } = modules.require("./SieveLogger.js");
- const { SieveInitRequest } = modules.require("./SieveRequest.js");
-
- const sieve = new Sieve(new SieveLogger());
-
- return await new Promise((resolve) => {
-
- const listener = {
-
- onInitResponse: function () {
- resolve(true);
- sieve.disconnect();
- },
-
- onError: function () {
- resolve(false);
- sieve.disconnect();
- },
-
- onTimeout: function () {
- resolve(false);
- sieve.disconnect();
- },
-
- onDisconnect: function () {
- // we are already disconnected....
- resolve(false);
- }
- };
-
- const request = new SieveInitRequest();
- request.addErrorListener(listener.onError);
- request.addResponseListener(listener.onInitResponse);
- sieve.addRequest(request);
-
- sieve.addListener(listener);
-
- sieve.connect(host, port, false);
- });
- }
- }
- }
- };
- }
- }
-
- exports.SieveSessionApi = SieveSessionApi;
-
-})(this);
diff --git a/src/wx/api/sieve/SieveSessionApi.json b/src/wx/api/sieve/SieveSessionApi.json
deleted file mode 100644
index 0e7accee..00000000
--- a/src/wx/api/sieve/SieveSessionApi.json
+++ /dev/null
@@ -1,390 +0,0 @@
-[
- {
- "namespace": "sieve.session",
- "description": "Sieve Session Management",
- "functions": [
- {
- "name": "create",
- "type": "function",
- "description": "Creates a new session",
- "async":true,
- "parameters":[{
- "name": "id",
- "type": "string",
- "description" : "The sessions unique id"
- },{
- "name": "options",
- "type": "object",
- "description" : "Connection options as key value pairs",
- "additionalProperties" :true
- }]
- },
- {
- "name": "destroy",
- "type": "function",
- "description": "Destroys the given session",
- "async":true,
- "parameters":[{
- "name": "id",
- "type": "string",
- "description" : "The sessions unique id"
- }]
- },
- {
- "name": "connect",
- "type": "function",
- "description": "Connects to a remote server.",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the server host name"
- },{
- "name": "host",
- "type": "string",
- "description" : "the server host name"
- },{
- "name": "port",
- "type": "string",
- "description" : "the server port"
- }]
- },
- {
- "name": "has",
- "type": "function",
- "description": "Checks if the connection is known.",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the connection id"
- }]
- },
- {
- "name": "disconnect",
- "type": "function",
- "description": "Disconnects from the remote server.",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the connection id"
- },{
- "name": "force",
- "type": "boolean",
- "optional": true,
- "description" : "If true the disconnect will be forced."
- }]
- },
- {
- "name": "isConnected",
- "type": "function",
- "description": "Checks if the given id is connected",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- }]
- },
-
- {
- "name": "capabilities",
- "type": "function",
- "description": "Gets the server's capabilities",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- }]
- },
- {
- "name": "listScripts",
- "type": "function",
- "description": "Lists the server's scripts",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- }]
- },
- {
- "name": "putScript",
- "type": "function",
- "description": "Uploads a new or changed script",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "name",
- "type": "string",
- "description" : "The script name"
- },
- {
- "name": "body",
- "type": "string",
- "description" : "The scripts body"
- }]
- },
- {
- "name": "getScript",
- "type": "function",
- "description": "Gets a script from the server",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "name",
- "type": "string",
- "description" : "The script name"
- }]
- },
- {
- "name": "renameScript",
- "type": "function",
- "description": "Renames an existing script",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "oldName",
- "type": "string",
- "description" : "The old script name"
- },{
- "name": "newName",
- "type": "string",
- "description" : "The new script name"
- }]
- },
- {
- "name": "deleteScript",
- "type": "function",
- "description": "Deletes an existing script",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "name",
- "type": "string",
- "description" : "The script name"
- }]
- },
- {
- "name": "activateScript",
- "type": "function",
- "description": "Activates or deactivates a script",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "name",
- "type": "string",
- "optional": true,
- "description" : "The active script"
- }]
- },
- {
- "name": "checkScript",
- "type": "function",
- "description": "Check a script for syntax errors",
- "async": true,
- "parameters": [{
- "name": "id",
- "type": "string",
- "description" : "the unique connection id"
- },{
- "name": "body",
- "type": "string",
- "description" : "The scripts body"
- }]
- },
- {
- "name": "probe",
- "type": "function",
- "description": "Checks the connection to the server",
- "async": true,
- "parameters": [{
- "name": "host",
- "type": "string",
- "description" : "the server's hostname"
- },{
- "name": "port",
- "type": "string",
- "description" : "the server's port"
- }]
- },{
- "name": "addCertErrorOverride",
- "type": "function",
- "description": "Adds a certificate error override for the given cert.",
- "async": true,
- "parameters": [
- {
- "name" : "host",
- "type" : "string",
- "description" : "The hostname to override"
- },
- {
- "name" : "port",
- "type" : "string",
- "description" : "The port to override"
- },
- {
- "name": "rawDER",
- "type": "array",
- "description" : "The server's certificate",
- "items": {
- "type": "integer"
- }
- },
- {
- "name": "flags",
- "type": "integer",
- "description": "The override flags."
- }
- ]
- }
- ],
- "events": [
- {
- "name": "onAuthenticate",
- "type": "function",
- "description" : "Called when authentication information is needed",
- "parameters": [
- {
- "name": "hasPassword",
- "description": "True in case a password is needed otherwise false.",
- "type" : "boolean"
- }
- ],
- "returns": {
- "type": "object",
- "description" : "The authentication information.",
- "properties": {
- "username": { "type": "string" },
- "password": { "type": "string" }
- }
- },
- "extraParameters": [
- {
- "name": "id",
- "description": "The unique account id.",
- "type": "string"
- }
- ]
- },
- {
- "name": "onAuthorize",
- "type": "function",
- "description" : "Called when authorization information is needed",
- "parameters": [
- ],
- "returns": {
- "type" : "string",
- "description" : "The authentication information."
- },
- "extraParameters": [
- {
- "name": "id",
- "description": "The unique account id.",
- "type": "string"
- }
- ]
- },
- {
- "name": "onCertError",
- "type": "function",
- "description" : "Called when a tls handshake failed",
- "parameters": [
- {
- "name": "securityInfo",
- "type": "object",
- "description": "The authentication information.",
- "properties": {
- "host": {
- "type" : "string",
- "description" : "The hostname for which the validation failed"
- },
- "port": {
- "type" : "string",
- "description" : "The port for which te validation failed"
- },
- "rawDER": {
- "type": "array",
- "description" : "The server's certificate",
- "items": {
- "type": "integer"
- }
- },
- "isDomainMismatch": {
- "description" : "The domain does not match",
- "type": "boolean"
- },
- "isExtendedValidation": {
- "description" : "The extended validation failed",
- "type": "boolean"
- },
- "isNotValidAtThisTime": {
- "description" : "The certificate's validity expired",
- "type": "boolean"
- },
- "isUntrusted": {
- "description" : "The certificate's is untrusted or self signed",
- "type": "boolean"
- }
- }
- }
- ],
- "returns": {
-
- },
- "extraParameters": [
- {
- "name": "id",
- "description": "The unique account id.",
- "type": "string"
- }
- ]
- },
- {
- "name": "onProxyLookup",
- "type": "function",
- "description" : "Called when a proxy lookup is needed",
- "parameters": [
- {
- "name": "host",
- "description": "the servers hostname",
- "type" : "string"
- },
- {
- "name": "port",
- "description": "the server port",
- "type" : "string"
- }
- ],
- "returns": {
- "type" : "string",
- "description" : "The proxy information."
- },
- "extraParameters": [
- {
- "name": "id",
- "description": "The unique account id.",
- "type": "string"
- }
- ]
- }
- ]
- }
-] \ No newline at end of file
diff --git a/src/wx/api/sieve/SieveSocketApi.js b/src/wx/api/sieve/SieveSocketApi.js
new file mode 100644
index 00000000..89ad82a1
--- /dev/null
+++ b/src/wx/api/sieve/SieveSocketApi.js
@@ -0,0 +1,1030 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+(function (exports) {
+
+ /* global ExtensionCommon */
+ /* global Components */
+ /* global ChromeUtils */
+ /* global Services */
+
+ // Input & output stream constants.
+ const STREAM_BUFFERED = 0;
+
+ const DEFAULT_SEGMENT_SIZE = 0;
+ const DEFAULT_SEGMENT_COUNT = 0;
+
+ const TRANSPORT_SECURE = 1;
+
+ const OLD_TRANSPORT_API = 4;
+ const REALLY_OLD_TRANSPORT_API = 5;
+
+ const STRING_AS_HEX = 16;
+
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ // const Cu = Components.utils;
+ const Cr = Components.results;
+
+
+ const HEX_PREFIX_LEN = 2;
+ const HEX_UINT16_LEN = 4;
+ const HEX_UINT32_LEN = 8;
+ const HEX_UINT48_LEN = 12;
+
+ const STATE_CLOSED = 0;
+ const STATE_CONNECTING = 1;
+ const STATE_OPEN = 2;
+ const STATE_CLOSING = 3;
+
+ const SOCKET_STATUS = {
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B0003 : "resolving",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B000B : "resolved",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B0007 : "connecting",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B0004 : "connected",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B0005 : "sending",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B000A : "waiting",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B0006 : "receiving",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B000C : "tls handshake stated",
+ // eslint-disable-next-line no-magic-numbers
+ 0x804B000D : "tls handshake stopped"
+ };
+
+ const NS_BASE_STREAM_CLOSED = 0x80470002;
+ const NS_ERROR_FAILURE = 0x80004005;
+ const NS_FAILURE_FLAG = 0x80000000;
+
+ // eslint-disable-next-line no-magic-numbers
+ const LOG_STATE = (1 << 2);
+ // eslint-disable-next-line no-magic-numbers
+ const LOG_TRACE = (1 << 5);
+
+ /**
+ * A simple TCP socket implementation.
+ */
+ class SieveSocket {
+
+ /**
+ * Constructs a new Sieve Socket object.
+ *
+ * The constructor neither creates nor connects the socket.
+ * You need to do this by calling the corresponding functions.
+ *
+ * @param {string} host
+ * the host name to which to connect
+ * @param {int} port
+ * the host's port
+ * @param {int} level
+ * enable logging debug messages
+ */
+ constructor(host, port, level) {
+ this.socket = null;
+
+ this.host = host;
+ this.port = Number.parseInt(port, 10);
+ this.level = Number.parseInt(level, 10);
+
+ this.outstream = null;
+ this.instream = null;
+
+ this.binaryOutStream = null;
+ this.buffer = [];
+
+ this.state = STATE_CLOSED;
+
+ this.handler = {};
+
+ this.thread = Cc["@mozilla.org/thread-manager;1"]
+ .getService(Ci.nsIThreadManager)
+ .mainThread;
+ }
+
+ /**
+ * Logs a message via the attached log handler.
+ * In case no log handler is attached the message will be silently
+ * discarded
+ *
+ * @param {string} message
+ * the message to be logger.
+ */
+ log(message) {
+
+ if (!(this.level & LOG_STATE))
+ return;
+
+ if (this.level & LOG_TRACE) {
+ // eslint-disable-next-line no-console
+ console.trace(`[${(new Date()).toISOString()}] [${this.port}] ${message}`);
+ return;
+ }
+
+ // eslint-disable-next-line no-console
+ console.log(`[${(new Date()).toISOString()}] [${this.port}] ${message}`);
+ }
+
+
+ /**
+ * We need this wrapper for compatibility. Stating Thunderbird 69
+ * Bug 1558726 (https://bugzilla.mozilla.org/show_bug.cgi?id=1558726)
+ * introduced a breaking change for this interface.
+ *
+ * Before the change the first parameter was an array and the second one
+ * the array length. After the change the length parameter was removed
+ * which causes all the other arguments to shift by one.
+ *
+ * @private
+ *
+ * @param {*} [proxyInfo]
+ * optional proxy information.
+ *
+ * @returns {nsISocketTransportService}
+ * the transport service or throws an exception in case creation failed.
+ */
+ createTransport(proxyInfo) {
+
+ const transportService =
+ Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+ // The createTransport API changed several times so we need some magic.
+ //
+ // We currently have to support three versions. Thunderbird before 69 take
+ // five arguments. Between 69 and 89 take four arguments and after 89 they
+ // are back to five.
+
+ // We know if it is before 78 we need to test if it is the really old API
+ if (Services.vc.compare(Services.appinfo.platformVersion, "78.0") < 0) {
+ if (transportService.createTransport.length === REALLY_OLD_TRANSPORT_API)
+ return transportService.createTransport(["starttls"], TRANSPORT_SECURE, this.host, this.port, proxyInfo);
+ }
+
+ // After 78 we know it is either the old or the new api.
+ if (transportService.createTransport.length === OLD_TRANSPORT_API)
+ return transportService.createTransport(["starttls"], this.host, this.port, proxyInfo);
+
+ return transportService.createTransport(["starttls"], this.host, this.port, proxyInfo, null);
+ }
+
+ /**
+ * Connects to the remote server via the given proxy.
+ *
+ * @param {*} [proxy]
+ * the optional proxy information. If omitted a proxy lookup is performed.
+ */
+ connect(proxy) {
+ this.log(`[SieveSocketApi:connect()] Preparing connection to ${this.host}:${this.port}, ...`);
+
+ // If we know the proxy setting, we can do a shortcut...
+ if (proxy) {
+ this.log("[SieveSocketApi:connect()] ... proxy info ready.");
+ this.onProxyAvailable(null, null, proxy[0], null);
+ return;
+ }
+
+ this.log(`Lookup Proxy Configuration for x-sieve://${this.host}:${this.port} ...`);
+
+ const ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ // const uri = ios.newURI("x-sieve://" + this.host + ":" + this.port, null, null);
+ const uri = ios.newURI(`http://${this.host}:${this.port}`, null, null);
+
+ const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(Ci.nsIProtocolProxyService);
+ pps.asyncResolve(uri, 0, this);
+
+ this.log("[SieveSocketApi:connect()] ... waiting for proxy info.");
+ }
+
+ /**
+ * Checks if the socket is still alive.
+ *
+ * @returns {boolean}
+ * true in case the socket is alive otherwise false.
+ */
+ isAlive() {
+ return this.socket.isAlive();
+ }
+
+ /**
+ * Starts upgrading to a secure connection.
+ *
+ * This calls is async an non blocking. In case the upgrade fails the
+ * connection will be automatically closed by thunderbird. Which will
+ * trigger onStopRequest to be called. The request object can then be used
+ * to analyze what caused the error.
+ */
+ startTLS() {
+
+ this.log("[SieveSocketApi:startTLS()] Requesting upgrade to secure...");
+
+ if (this.state !== STATE_OPEN)
+ throw new Error("Socket not in open state");
+
+ const control = this.socket.securityInfo
+ .QueryInterface(Ci.nsISSLSocketControl);
+
+ if (!control)
+ throw new Error("Socket can not be upgraded.");
+
+ control.StartTLS();
+
+ this.log("[SieveSocketApi:startTLS()] ... done");
+ }
+
+ /**
+ * Signals a change in the transport status.
+ * We use it only to detect the point when we are connected and
+ * ready to send and receive data.
+ *
+ * @param {nsITransport} transport
+ * the transport which triggered this status update
+ * @param {int} status
+ * the status as error code
+ * @param {int} progress
+ * the amount of data read or written depending on the status code.
+ * @param {int} progressMax
+ * the maximum amount of data which will be read or written.
+ */
+ // eslint-disable-next-line no-unused-vars
+ onTransportStatus(transport, status, progress, progressMax) {
+
+ this.log(`[SieveSocketApi:onTransportStatus()] Status change to `
+ + `${SOCKET_STATUS[status]} (${status.toString(STRING_AS_HEX)}) ...` );
+
+ if (status !== Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ this.log("[SieveSocketApi:onTransportStatus()] ... ignored" );
+ return;
+ }
+
+ this.state = STATE_OPEN;
+ this.registerAsyncWait(this.instream.QueryInterface(Ci.nsIAsyncInputStream));
+
+ this.log("[SieveSocketApi:onTransportStatus()] ... socket now in open state");
+ }
+
+ /**
+ * Wait async for the input stream to have new data.
+ *
+ * @param {nsIAsyncInputStream} stream
+ * the input stream for which should be waited.
+ */
+ registerAsyncWait(stream) {
+ this.log(`[SieveSocketApi:registerAsyncWait()] Registering async callback ...` );
+
+ if (!stream)
+ throw new Error("Invalid input stream");
+
+ if (!stream.asyncWait)
+ throw new Error("Not an async stream");
+
+ const threads = Cc["@mozilla.org/thread-manager;1"]
+ .getService(Ci.nsIThreadManager);
+
+ // this.log(threads.mainThread);
+ // this.log(threads.currentThread);
+ // this.log(threads.mainThreadEventTarget);
+
+ stream
+ .asyncWait(this, 0, 0, threads.mainThread);
+
+ this.log(`[SieveSocketApi:registerAsyncWait()] ... done` );
+ }
+
+
+ /**
+ * Analyzes if the exception is an error and if so it calls the error
+ * handler if available.
+ *
+ * @param {Error} ex
+ * the error to be analyzed
+ */
+ onInputStreamError(ex) {
+
+ this.log(`[SieveSocketApi:onInputStreamError()] Parsing error information...`);
+ let status = NS_ERROR_FAILURE;
+
+ if (ex.result)
+ status = ex.result;
+
+ // In case we are closing the socket we expect a stream closed error...
+ if (((this.state === STATE_CLOSING) || (this.state === STATE_CLOSED)) && (status === NS_BASE_STREAM_CLOSED)) {
+ this.log(`[SieveSocketApi:onInputStreamError()] ... skipping, closing stream on a closing socket`);
+ return;
+ }
+
+ // In case is it is no error code we can skip.
+ if (!(status & NS_FAILURE_FLAG)) {
+ this.log(`[SieveSocketApi:onInputStreamError()] ... skipping, `
+ + `${status.toString(STRING_AS_HEX)} is not an error code`);
+ return;
+ }
+
+ const error = this.getError(status);
+
+ if (this.handler && this.handler.onError) {
+ this.log(`[SieveSocketApi:onInputStreamError()] ... calling error handler ...`);
+ (async () => { this.handler.onError(error); })();
+ }
+
+ this.log(`[SieveSocketApi:onInputStreamError()] ... done`);
+ return;
+ }
+
+ /**
+ * Calls by the async wait method whenever new data is available
+ * or the stream was closed.
+ *
+ * @param {nsIAsyncInputStream} stream
+ * the stream with the input data.
+ */
+ onInputStreamReady(stream) {
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] Reading data...`);
+
+ try {
+ // Check if we have data.
+ // This will throw in case the connection was refused or closed.
+ const count = stream.available();
+
+ // Ok we got data, so let's read it.
+ const data = this.binaryInputStream.readByteArray(count);
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] Reading ${count} bytes...`);
+
+ // And then make sure we get notified as soon as new data arrives.
+ this.registerAsyncWait(stream.QueryInterface(Ci.nsIAsyncInputStream));
+
+ // Now it is time to process the data.
+ if ((data.length) && (this.handler && this.handler.onData))
+ (async() => { this.handler.onData(data); })();
+
+ } catch (ex) {
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... failed with an error...`);
+
+ // We ignore any errors after being closed
+ if (this.state === STATE_CLOSED) {
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... ignoring error socket is closed`);
+ return;
+ }
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... processing error...`);
+ this.onInputStreamError(ex);
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... disconnecting socket...`);
+ this.disconnect();
+ this.state = STATE_CLOSED;
+
+ if (this.handler && this.handler.onError) {
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... calling close handler...`);
+ (async () => { this.handler.onClose(); })();
+ }
+ }
+
+ this.log(`[SieveSocketApi:onInputStreamReady()] ... done`);
+ }
+
+ /**
+ * Sends the given data via the socket to the remote sever.
+ *
+ * @param {byte[]} bytes
+ * the data to be send as byte array.
+ */
+ send(bytes) {
+ this.log(`[SieveSocketApi:send()] Sending ${bytes.length} bytes...`);
+
+ if (this.state !== STATE_OPEN) {
+ this.log(`[SieveSocketApi:send()] ... error socket is not open`);
+ throw new Error("Socket not in open state");
+ }
+
+ this.buffer.push(bytes);
+
+ if (this.buffer.length) {
+ this.registerAsyncWait(
+ this.outstream.QueryInterface(Ci.nsIAsyncOutputStream));
+ }
+
+ // this.binaryOutStream.writeByteArray(bytes, bytes.length);
+
+ this.log(`[SieveSocketApi:send()] ... done`);
+ }
+
+ /**
+ * Called by the async wait method whenever a write was requested and
+ * successfully scheduled.
+ *
+ * @param {nsIAsyncOutputStream} stream
+ * the output which cause this callback.
+ */
+ // eslint-disable-next-line no-unused-vars
+ onOutputStreamReady(stream) {
+ this.log(`[SieveSocketApi:onOutputStreamReady()] Writing data...`);
+
+ if (!this.buffer || !this.buffer.length) {
+ this.log(`[SieveSocketApi:onOutputStreamReady()] ... buffer is empty.`);
+ return;
+ }
+
+ while (this.buffer.length) {
+ const bytes = this.buffer.shift();
+
+ this.log(`[SieveSocketApi:onOutputStreamReady()] ... sending from ${bytes.length} buffer...`);
+
+ this.binaryOutStream.writeByteArray(bytes, bytes.length);
+ this.binaryOutStream.flush();
+ }
+
+ this.log(`[SieveSocketApi:onOutputStreamReady()] ... done`);
+
+ return;
+ }
+
+ /**
+ * Disconnects from the remote socket.
+ * In case the socket is already disconnected it will fail silently.
+ * So calling disconnect multiple times is perfectly fine.
+ *
+ * After disconnecting the onClose handler will be fired.
+ */
+ disconnect() {
+
+ this.log("[SieveSocketApi:Disconnect()] Disconnecting socket..." );
+
+ if (this.state === STATE_CLOSED || this.state === STATE_CLOSING) {
+ this.log("[SieveSocketApi:Disconnect()] ... already done" );
+ return;
+ }
+
+ this.state = STATE_CLOSING;
+
+ if (this.outstream) {
+ this.outstream.close();
+ this.outstream = null;
+ }
+
+ if (this.instream) {
+ this.instream.close();
+ this.instream = null;
+ }
+
+ this.socket.close(0);
+ this.socket = 0;
+
+ this.log("[SieveSocketApi:Disconnect()] ... done" );
+ }
+
+ /**
+ * Attaches or detaches an event listener to this socket
+ *
+ * Existing event listeners with the same name will be replace.
+ * In case the callback is omitted the existing event listener will be removed.
+ *
+ * Currently the following listeners are supported:
+ *
+ * data - called upon data received.
+ * error - called in case the connection was terminated because of an error.
+ * Will be directly followed by a close message.
+ * close - called whenever the connection was closed.
+ * log - called whenever something should forwarded to the logger.
+ *
+ * Unknown names will fail with an exception.
+ *
+ * @param {string} name
+ * the event listener's name
+ * @param {Function} [callback]
+ * the optional callback to be invoked upon the event. It will replace
+ * the previous event listener with the same name.
+ */
+ on(name, callback) {
+ if (name === "data") {
+ this.handler.onData = callback;
+ return;
+ }
+
+ if (name === "error") {
+ this.handler.onError = callback;
+ return;
+ }
+
+ if (name === "close") {
+ this.handler.onClose = callback;
+ return;
+ }
+
+ throw new Error(`Invalid event handler ${name}`);
+ }
+
+
+ /**
+ * Checks if the error code is from the certificate error class.
+ * @param {int} code
+ * the code to be checked.
+ *
+ * @returns {boolean}
+ * true in case it is a certificate error otherwise false.
+ */
+ isCertError(code) {
+ try {
+ const nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+
+ const errorClass = nssErrorsService.getErrorClass(code);
+ if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT)
+ return true;
+
+ } catch {
+ this.log(`Failed to extract error class`);
+ }
+
+ return false;
+ }
+
+ /**
+ * Tries to get the reason why the certificate handshake failed.
+ *
+ * @returns {Exception}
+ * an exception which describes the certificate error or null
+ */
+ getCertError() {
+ this.log(`[SieveSocketApi:getCertError()] Converting cert error...`);
+
+ if (!this.socket || !this.socket.securityInfo) {
+ this.log(`[SieveSocketApi:getCertError()] ... not a secure socket`);
+ return null;
+ }
+
+ const secInfo = this.socket.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+
+ const rv = {
+ "type" : "CertValidationError",
+ "host": this.host,
+ "port": this.port,
+
+ "rawDER": secInfo.serverCert.getRawDER({}),
+ "fingerprint": secInfo.serverCert.sha256Fingerprint,
+
+ "message": secInfo.errorCodeString,
+ "isDomainMismatch": secInfo.isDomainMismatch,
+ "isExtendedValidation": secInfo.isExtendedValidation,
+ "isNotValidAtThisTime": secInfo.isNotValidAtThisTime,
+ "isUntrusted": secInfo.isUntrusted
+ };
+
+ this.log(`[SieveSocketApi:getCertError()] ... done`);
+ return rv;
+ }
+
+ /**
+ * Converts the status/error code into an socket error object.
+ *
+ * It load the official description if available.
+ *
+ * @param {int} status
+ * the error code to be analyzed
+ *
+ * @returns {object}
+ * a serializable message containing the error details.
+ */
+ getSocketError(status) {
+
+ this.log(`[SieveSocketApi:getSocketError()] Converting socket error ${status.toString(STRING_AS_HEX)}...`);
+
+ const error = {
+ "type": "SocketError",
+ "status": status,
+ "message" : `Socket failed with error ${status.toString(STRING_AS_HEX)}`
+ };
+
+ try {
+ const nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+
+ error.message = nssErrorsService.getErrorMessage(status);
+ this.log(`[SieveSocketApi:getSocketError()] ... ${error.message} ...`);
+ } catch {
+ // do nothing here we fallback to our generic default.
+ }
+
+ this.log(`[SieveSocketApi:getSocketError()] ... done`);
+ return error;
+ }
+
+ /**
+ * Converts the status code into an error object
+ * which can be transferred via ipc.
+ *
+ * @param {int} status
+ * the status code which should be analyzed.
+ *
+ * @returns {object}
+ * the error object.
+ */
+ getError(status) {
+
+ if (this.isCertError(status))
+ return this.getCertError();
+
+ return this.getSocketError(status);
+ }
+
+ /**
+ * An async callback for the proxy lookup.
+ *
+ * @private
+ * @param {nsIRequest} request
+ * the request for which the proxy information as requested.
+ * @param {nsIChannel} channel
+ * the channel for which the proxy information was requested.
+ * @param {nsIProxyInfo} aProxyInfo
+ * the proxy information from the lookup, if null a direct
+ * connection will be used.
+ * @param {int} status
+ * the failure code in case the proxy could not be resolved.
+ **/
+ // eslint-disable-next-line no-unused-vars
+ onProxyAvailable(request, channel, aProxyInfo, status) {
+
+ if (aProxyInfo)
+ this.log(`Using Proxy: [${aProxyInfo.type}] ${aProxyInfo.host}:${aProxyInfo.port}`);
+ else
+ this.log("Using Proxy: Direct");
+
+ this.socket = this.createTransport(aProxyInfo);
+
+ this.state = STATE_CONNECTING;
+
+ this.socket.setEventSink(this, this.thread);
+
+ this.instream = this.socket.openInputStream(
+ STREAM_BUFFERED, DEFAULT_SEGMENT_SIZE, DEFAULT_SEGMENT_COUNT);
+ this.outstream = this.socket.openOutputStream(
+ STREAM_BUFFERED, DEFAULT_SEGMENT_SIZE, DEFAULT_SEGMENT_COUNT);
+
+ // this.registerAsyncWait(this.instream.QueryInterface(Ci.nsIAsyncInputStream));
+
+ this.binaryInputStream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ this.binaryInputStream.setInputStream(this.instream);
+
+
+ this.binaryOutStream =
+ Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+
+ this.binaryOutStream.setOutputStream(this.outstream);
+ }
+
+
+ /**
+ * Implements the Gecko Component Manager's interfaces.
+ * So that the JavaScript code can be passed as interface
+ * to C++.
+ *
+ * In case the interface is not supported an NS_ERROR_NO_INTERFACE
+ * will be thrown.
+ *
+ * @param {nsIIDRef} uuid
+ * the interface's unique id to check.
+ *
+ * @returns {SieveMozClient}
+ * a self reference.
+ */
+ QueryInterface(uuid) {
+ if (uuid.equals(Ci.nsISupports))
+ return this;
+ // onProxyAvailable...
+ if (uuid.equals(Ci.nsIProtocolProxyCallback))
+ return this;
+ // onInputStreamReady
+ if (uuid.equals(Ci.nsIInputStreamCallback))
+ return this;
+ // onTransportStatus
+ if (uuid.equals(Ci.nsITransportEventSink))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+
+ }
+
+
+ // We use this to track all open sockets.
+ // So that we can do a clean shutdown.
+ const sockets = new Map();
+
+ /**
+ * Checks if the given socket exists.
+ *
+ * @param {string} socket
+ * the sockets unique id.
+ * @returns {boolean}
+ * true in case the socket exists otherwise false.
+ */
+ function hasSocket(socket) {
+ return sockets.has(socket);
+ }
+
+ /**
+ * Gets the socket for the given id.
+ *
+ * @param {string} socket
+ * the sockets unique id.
+ * @returns {SieveSocket}
+ * a reference to the socket object or an exception.
+ */
+ function getSocket(socket) {
+ if (!sockets.has(socket))
+ throw new Error(`Unknown socket id ${socket}`);
+
+ return sockets.get(socket);
+ }
+
+
+ /**
+ * Implements a webextension api for sieve session and connection management.
+ */
+ class SieveSocketApi extends ExtensionCommon.ExtensionAPI {
+
+ /**
+ * @inheritdoc
+ */
+ onStartup() {
+ // we're not actually interested in startup, we need the event only
+ // to ensure this experiment gets loaded.
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onShutdown(isAppShutdown) {
+ if (isAppShutdown) {
+ return;
+ }
+
+ // Clear caches that could prevent upgrades from working properly
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm");
+ Services.obs.notifyObservers(null, "startupcache-invalidate", null);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getAPI(context) {
+
+ context.callOnClose({
+ close: () => {
+ for (const key of sockets.keys()) {
+ sockets[key].destroy();
+ sockets.delete(key);
+ }
+ }
+ });
+
+ return {
+ sieve: {
+ socket: {
+
+ /**
+ * Creates a socket which can be connected to the remote server.
+ *
+ * @param {string} host
+ * the remote server's hostname.
+ * @param {string} port
+ * the remote server's port.
+ * @param {int} level
+ * true in case logging should be enabled.
+ *
+ * @returns {string}
+ * return the unique id.
+ */
+ async create(host, port, level) {
+
+ const socket = new SieveSocket(host, port, level);
+
+ const id = Math.random().toString(STRING_AS_HEX).substr(HEX_PREFIX_LEN, HEX_UINT32_LEN)
+ + "-" + Math.random().toString(STRING_AS_HEX).substr(HEX_PREFIX_LEN, HEX_UINT16_LEN)
+ + "-" + Math.random().toString(STRING_AS_HEX).substr(HEX_PREFIX_LEN, HEX_UINT16_LEN)
+ + "-" + Math.random().toString(STRING_AS_HEX).substr(HEX_PREFIX_LEN, HEX_UINT16_LEN)
+ + "-" + Math.random().toString(STRING_AS_HEX).substr(HEX_PREFIX_LEN, HEX_UINT48_LEN);
+
+ sockets.set(id, socket);
+
+ return id;
+ },
+
+ /**
+ * Connects the socket to the remote server.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ */
+ async connect(socket) {
+ await (getSocket(socket).connect());
+ },
+
+ /**
+ * Upgrades the socket to be secure.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ */
+ async startTLS(socket) {
+ await (getSocket(socket).startTLS());
+ },
+
+ /**
+ * Sends data to the remote socket.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ * @param {int[]} bytes
+ * the data to send as byte array.
+ */
+ async send(socket, bytes) {
+ await (getSocket(socket).send(bytes));
+ },
+
+ /**
+ * Checks if the socket isAlive.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ * @returns {boolean}
+ * true in case the socket is active otherwise false.
+ */
+ async isAlive(socket) {
+ if (!hasSocket(socket))
+ return false;
+
+ return await (getSocket(socket).isAlive());
+ },
+
+ /**
+ * Disconnects from the remote server.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ */
+ async disconnect(socket) {
+
+ // We can skip in case the socket is already cleaned up.
+ if (!hasSocket(socket))
+ return;
+
+ await (getSocket(socket).disconnect());
+ },
+
+ /**
+ * Destroys and disconnects the remote server connection.
+ *
+ * @param {string} socket
+ * the socket's unique id.
+ */
+ async destroy(socket) {
+
+ if (!hasSocket(socket))
+ return;
+
+ await this.disconnect(socket);
+
+ sockets.delete(socket);
+ },
+
+ /**
+ * Add a override for an certificate error.
+ *
+ * @param {string} host
+ * the server's host name.
+ * @param {string} port
+ * the server's port.
+ * @param {int[]} rawDER
+ * the certificate which should be overridden as byte array.
+ * @param {int} flags
+ * the override flags.
+ */
+ async addCertErrorOverride(host, port, rawDER, flags) {
+
+ const overrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+
+ const certDB = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+
+ // The constructX509 has an incompatible change.
+ // While newer version consume an array, older version use strings.
+ // This magic is only needed for thunderbird 68
+ let cert = null;
+ try {
+ // Try first with the new api..
+ cert = certDB.constructX509(rawDER);
+ } catch (ex) {
+
+ if (ex.name !== "NS_ERROR_FAILURE")
+ throw ex;
+
+ // ... other wise the use the old api.
+ cert = "";
+ for (const ch of rawDER)
+ cert += (String.fromCharCode(ch));
+
+ cert = certDB.constructX509(cert);
+ }
+
+ overrideService.rememberValidityOverride(
+ host, port, cert, flags, false);
+ },
+
+ // Event handlers...
+ /**
+ * The OnData handler is called whenever data is received.
+ */
+ onData: new ExtensionCommon.EventManager({
+ context,
+ name: "sieve.socket.onData",
+ register: (fire, socket) => {
+
+ const callback = async (bytes) => {
+ return await fire.async(bytes);
+ };
+
+ getSocket(socket).on("data", callback);
+
+ return () => {
+ if (hasSocket(socket))
+ getSocket(socket).on("data");
+ };
+ }
+ }).api(),
+
+ /**
+ * The OnError handler is called whenever an error occurred.
+ * The close event will be called directly after.
+ */
+ onError: new ExtensionCommon.EventManager({
+ context,
+ name: "sieve.socket.onError",
+ register: (fire, socket) => {
+
+ const callback = async (error) => {
+ return await fire.async(error);
+ };
+
+ getSocket(socket).on("error", callback);
+
+ return () => {
+ if (hasSocket(socket))
+ getSocket(socket).on("error");
+ };
+ }
+ }).api(),
+
+ /**
+ * The OnClose handler is called whenever the connection to the
+ * remote server was closed.
+ */
+ onClose: new ExtensionCommon.EventManager({
+ context,
+ name: "sieve.socket.onClose",
+ register: (fire, socket) => {
+
+ const callback = async () => {
+ return await fire.async();
+ };
+
+ getSocket(socket).on("close", callback);
+
+ return () => {
+ if (hasSocket(socket))
+ getSocket(socket).on("close");
+ };
+ }
+ }).api()
+
+ }
+ }
+ };
+ }
+ }
+
+ exports.SieveSocketApi = SieveSocketApi;
+
+})(this);
diff --git a/src/wx/api/sieve/SieveSocketApi.json b/src/wx/api/sieve/SieveSocketApi.json
new file mode 100644
index 00000000..8101d48d
--- /dev/null
+++ b/src/wx/api/sieve/SieveSocketApi.json
@@ -0,0 +1,213 @@
+[
+ {
+ "namespace": "sieve.socket",
+ "description": "Sieve Socket Connection",
+ "functions": [
+ {
+ "name": "create",
+ "type": "function",
+ "description": "Creates (but does not connect) a server connection",
+ "async": true,
+ "parameters": [
+ {
+ "name": "host",
+ "type": "string",
+ "description": "The remote's host name"
+ },
+ {
+ "name": "port",
+ "type": "string",
+ "description": "The remote's port"
+ },
+ {
+ "name": "level",
+ "type": "integer",
+ "description": "The log level to be used"
+ }
+ ]
+ },
+ {
+ "name": "connect",
+ "type": "function",
+ "description": "Connects to a remote server.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "The unique socket handle"
+ }
+ ]
+ },
+ {
+ "name": "startTLS",
+ "type": "function",
+ "description": "Upgrades the socket to be secure. In case the upgrade fails the socket will be closed",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "description": "The unique socket handle",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "send",
+ "type": "function",
+ "description": "Upgrades the socket to be secure. In case the upgrade fails the socket will be closed",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "description": "The unique socket handle",
+ "type": "string"
+ },
+ {
+ "name": "bytes",
+ "description": "The unique socket handle",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ ]
+ },
+ {
+ "name": "isAlive",
+ "type": "function",
+ "description": "Checks if the socket is still alive and connected",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "description": "The unique socket handle",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "disconnect",
+ "type": "function",
+ "description": "Disconnects from the remote server.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "the connection id"
+ }
+ ]
+ },
+ {
+ "name": "destroy",
+ "type": "function",
+ "description": "Destroys the given session",
+ "async": true,
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "The sessions unique id"
+ }
+ ]
+ },
+ {
+ "name": "addCertErrorOverride",
+ "type": "function",
+ "description": "Adds a certificate error override for the given cert.",
+ "async": true,
+ "parameters": [
+ {
+ "name": "host",
+ "type": "string",
+ "description": "The hostname to override"
+ },
+ {
+ "name": "port",
+ "type": "string",
+ "description": "The port to override"
+ },
+ {
+ "name": "rawDER",
+ "type": "array",
+ "description": "The server's certificate",
+ "items": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "flags",
+ "type": "integer",
+ "description": "The override flags."
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onData",
+ "type": "function",
+ "description": "Called when data is received",
+ "parameters": [
+ {
+ "name": "bytes",
+ "description": "The data received as byte array.",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ ],
+ "returns": {},
+ "extraParameters": [
+ {
+ "name": "socket",
+ "description": "The socket's unique id",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "onError",
+ "type": "function",
+ "description": "Called whenever the socket was closed unexpectedly",
+ "parameters": [
+ {
+ "name": "error",
+ "type": "object",
+ "description": "An object containing details about the error."
+ }
+ ],
+ "returns": {},
+ "extraParameters": [
+ {
+ "name": "id",
+ "description": "The socket's unique id",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "onClose",
+ "type": "function",
+ "description": "Called after the socket was closed.",
+ "parameters": [
+ {
+ "name": "hadError",
+ "description": "True in case the socket was closed due to an error",
+ "type": "boolean"
+ }
+ ],
+ "returns": {},
+ "extraParameters": [
+ {
+ "name": "id",
+ "description": "The socket's unique id",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/wx/background.html b/src/wx/background.html
index 1370dc5c..3c2d76e6 100644
--- a/src/wx/background.html
+++ b/src/wx/background.html
@@ -2,32 +2,6 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <script src="./libs/managesieve.ui/utils/SieveFakeRequire.js"></script>
-
- <script src="./libs/managesieve.ui/utils/SieveLogger.js"></script>
-
- <script src="./libs/managesieve.ui/utils/SieveUniqueId.js"></script>
- <script src="./libs/managesieve.ui/utils/SieveAbstractIpcClient.js"></script>
- <script src="./libs/managesieve.ui/utils/SieveIpcClient.js"></script>
-
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractPrefManager.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SievePrefManager.js"></script>
-
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractMechanism.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractAuthorization.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAuthorization.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractAuthentication.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAuthentication.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveSecurity.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractHost.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveHost.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveEditorSettings.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAccountSettings.js"></script>
-
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractAccount.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAbstractAccounts.js"></script>
- <script src="./libs/managesieve.ui/settings/logic/SieveAccounts.js"></script>
-
- <script src="./background.js"></script>
+ <script type="module" src="./background.mjs"></script>
</head>
</html> \ No newline at end of file
diff --git a/src/wx/background.js b/src/wx/background.mjs
index 0d0e642d..a8495910 100644
--- a/src/wx/background.js
+++ b/src/wx/background.mjs
@@ -9,14 +9,17 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/
-(async function () {
+/* global browser */
+
+import { SieveSession } from "./libs/libManageSieve/SieveSession.mjs";
+import { SieveCertValidationException } from "./libs/libManageSieve/SieveExceptions.mjs";
- "use strict";
+import { SieveLogger } from "./libs/managesieve.ui/utils/SieveLogger.mjs";
+import { SieveIpcClient } from "./libs/managesieve.ui/utils/SieveIpcClient.mjs";
+import { SieveAccounts } from "./libs/managesieve.ui/settings/logic/SieveAccounts.mjs";
- /* global browser */
- /* global SieveIpcClient */
- /* global SieveAccounts */
- /* global SieveLogger */
+
+(async function () {
const ERROR_UNTRUSTED = 1;
const ERROR_MISMATCH = 2;
@@ -28,6 +31,8 @@
const accounts = await (new SieveAccounts().load());
+ const sessions = new Map();
+
// TODO Extract into separate class..
/**
* Gets a tab by its script and account name.
@@ -74,8 +79,13 @@
if (tabs.length)
return;
- for (const id of accounts.getAccountIds())
- browser.sieve.session.destroy(id);
+ for (const id of accounts.getAccountIds()) {
+ if (!sessions.has(id))
+ continue;
+
+ await (sessions.get(id).disconnect(true));
+ sessions.delete(id);
+ }
});
// ------------------------------------------------------------------------ //
@@ -184,7 +194,11 @@
logger.logAction(`Is connected ${msg.payload.account}`);
const id = msg.payload.account;
- return browser.sieve.session.isConnected(id);
+
+ if (!sessions.has(id))
+ return false;
+
+ return sessions.get(id).isConnected();
},
"account-connect": async function (msg) {
@@ -228,57 +242,81 @@
return await authorization.getAuthorization();
};
- const onCertError = async (secInfo) => {
- logger.logAction(`Certificate Error for ${secInfo.host}:${secInfo.port}`);
+ if (sessions.has(id))
+ throw new Error("Id already in use");
- const rv = await SieveIpcClient.sendMessage(
- "accounts", "script-show-certerror", secInfo);
+ sessions.set(id,
+ new SieveSession(id, options));
- if (!rv)
- return;
+ sessions.get(id).on("authenticate",
+ async (hasPassword) => { return await onAuthenticate(hasPassword); });
+ sessions.get(id).on("authorize",
+ async () => { return await onAuthorize(); });
- let overrideBits = 0;
- if (secInfo.isNotValidAtThisTime)
- overrideBits |= ERROR_TIME;
+ const hostname = await host.getHostname();
+ const port = await host.getPort();
- if (secInfo.isUntrusted)
- overrideBits |= ERROR_UNTRUSTED;
+ try {
+ await sessions.get(id).connect(hostname, `${port}`);
+ } catch (ex) {
- if (secInfo.isDomainMismatch)
- overrideBits |= ERROR_MISMATCH;
+ await (actions["account-disconnect"](msg));
- await browser.sieve.session.addCertErrorOverride(
- secInfo.host, secInfo.port, secInfo.rawDER, overrideBits);
- };
+ if (ex instanceof SieveCertValidationException) {
+ const secInfo = ex.getSecurityInfo();
- await browser.sieve.session.create(id, options);
- await browser.sieve.session.onAuthenticate.addListener(
- async (hasPassword) => { return await onAuthenticate(hasPassword); }, id);
- await browser.sieve.session.onAuthorize.addListener(
- async () => { return await onAuthorize(); }, id);
+ const rv = (await SieveIpcClient.sendMessage(
+ "accounts", "account-show-certerror", secInfo));
- await browser.sieve.session.onCertError.addListener(
- async (secInfo) => { return await onCertError(secInfo); }, id);
+ // Dialog was canceled we bail out.
+ if (!rv)
+ return;
- const hostname = await host.getHostname();
- const port = await host.getPort();
+ let overrideBits = 0;
+ if (secInfo.isNotValidAtThisTime)
+ overrideBits |= ERROR_TIME;
+
+ if (secInfo.isUntrusted)
+ overrideBits |= ERROR_UNTRUSTED;
+
+ if (secInfo.isDomainMismatch)
+ overrideBits |= ERROR_MISMATCH;
+
+ await (browser.sieve.socket.addCertErrorOverride(
+ secInfo.host, `${secInfo.port}`, secInfo.rawDER, overrideBits));
+
+ await (actions["account-connect"](msg));
+ return;
+ }
+
+ // connecting failed for some reason, which means we
+ // need to handle the error.
+ logger.logAction("Connecting failed due to an error " + ex);
- await browser.sieve.session.connect(id, hostname, `${port}`);
+ await SieveIpcClient.sendMessage(
+ "accounts", "account-show-error", ex.message);
+ }
},
"account-disconnect": async function (msg) {
logger.logAction(`Disconnect ${msg.payload.account}`);
- await browser.sieve.session.destroy(msg.payload.account);
+
+ const id = msg.payload.account;
+ if (!sessions.has(id))
+ return;
+
+ await (sessions.get(id).disconnect(msg.payload.account));
+ sessions.delete(id);
},
"account-list": async function (msg) {
logger.logAction(`List scripts for ${msg.payload.account}`);
- return await browser.sieve.session.listScripts(msg.payload.account);
+ return await sessions.get(msg.payload.account).listScripts();
},
"account-capabilities": async function (msg) {
logger.logAction(`Get capabilities for ${msg.payload.account}`);
- return await browser.sieve.session.capabilities(msg.payload.account);
+ return await sessions.get(msg.payload.account).capabilities();
},
// Script endpoint...
@@ -290,7 +328,7 @@
const name = await SieveIpcClient.sendMessage("accounts", "script-show-create", account);
if (name.trim() !== "")
- await browser.sieve.session.putScript(account, name, "#test\r\n");
+ await sessions.get(account).putScript(name, "#test\r\n");
return name;
},
@@ -311,7 +349,7 @@
if (newName === oldName)
return false;
- await browser.sieve.session.renameScript(account, oldName, newName);
+ await sessions.get(account).renameScript(oldName, newName);
return true;
},
@@ -329,7 +367,7 @@
const rv = await SieveIpcClient.sendMessage("accounts", "script-show-delete", name);
if (rv === true)
- await browser.sieve.session.deleteScript(account, name);
+ await sessions.get(account).deleteScript(name);
return rv;
},
@@ -340,7 +378,7 @@
logger.logAction(`Activate ${name} for ${account}`);
- await browser.sieve.session.activateScript(account, name);
+ await sessions.get(account).activateScript(name);
},
"script-deactivate": async function (msg) {
@@ -348,7 +386,7 @@
logger.logAction(`Deactivate script for ${account}`);
- await browser.sieve.session.activateScript(account);
+ await sessions.get(account).activateScript();
},
"script-edit": async function (msg) {
@@ -382,7 +420,7 @@
logger.logAction(`Get ${name} for ${account}`);
- return await browser.sieve.session.getScript(account, name);
+ return await sessions.get(account).getScript(name);
},
"script-check": async function (msg) {
@@ -391,7 +429,14 @@
logger.logAction(`Check Script for ${account}`);
- return await browser.sieve.session.checkScript(account, script);
+ try {
+ await sessions.get(account).checkScript(script);
+ } catch (ex) {
+ // FIXME We need to rethrow incase checkscript returns a SieveServerException.
+ return ex.getResponse().getMessage();
+ }
+
+ return "";
},
"script-save": async function (msg) {
@@ -401,7 +446,7 @@
logger.logAction(`Save ${name} for ${account}`);
- await browser.sieve.session.putScript(account, name, script);
+ await sessions.get(account).putScript(name, script);
},
"account-get-settings": async function (msg) {
@@ -467,7 +512,7 @@
return value;
},
- "get-default-preference": async(msg) => {
+ "get-default-preference": async (msg) => {
const name = msg.payload.data;
logger.logAction(`Get default value for ${name}`);
@@ -485,7 +530,7 @@
await accounts.getAccountById(account).getEditor().setValue(name, value);
},
- "set-default-preference": async(msg) => {
+ "set-default-preference": async (msg) => {
const name = msg.payload.key;
const value = msg.payload.value;
diff --git a/src/wx/libs/libManageSieve/SieveBase64.mjs b/src/wx/libs/libManageSieve/SieveBase64.mjs
new file mode 100644
index 00000000..015c1171
--- /dev/null
+++ b/src/wx/libs/libManageSieve/SieveBase64.mjs
@@ -0,0 +1,356 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import {
+ SieveAbstractBase64Decoder,
+ SieveAbstractBase64Encoder
+} from "./SieveAbstractBase64.mjs";
+
+const DECODED_QUANTUM = 3;
+const ENCODED_QUANTUM = 4;
+
+const CHAR_PADDING = 61;
+
+// First Quantum
+const Q1_HIGH_OFFSET = 0;
+const Q1_HIGH_MASK = 0x3F;
+const Q1_HIGH_SHIFT = 2;
+
+const Q1_LOW_OFFSET = 1;
+const Q1_LOW_MASK = 0x30;
+const Q1_LOW_SHIFT = 4;
+
+// Second Quantum
+const Q2_HIGH_OFFSET = 1;
+const Q2_HIGH_MASK = 0x0F;
+const Q2_HIGH_SHIFT = 4;
+
+const Q2_LOW_OFFSET = 2;
+const Q2_LOW_MASK = 0x3C;
+const Q2_LOW_SHIFT = 2;
+
+// Third Quantum
+const Q3_HIGH_OFFSET = 2;
+const Q3_HIGH_MASK = 0x03;
+const Q3_HIGH_SHIFT = 6;
+
+const Q3_LOW_OFFSET = 3;
+const Q3_LOW_MASK = 0x3F;
+const Q3_LOW_SHIFT = 0;
+
+const TWO_QUANTUM = 2;
+const ONE_QUANTUM = 1;
+
+// Byte Offsets
+const FIRST_BYTE = 0;
+const SECOND_BYTE = 1;
+const THIRD_BYTE = 2;
+const FOURTH_BYTE = 3;
+
+const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ *
+ */
+class SieveWebBase64Encoder extends SieveAbstractBase64Encoder {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(decoded) {
+
+ if (Array.isArray(decoded))
+ decoded = new Uint8Array(decoded);
+
+ if (!(decoded instanceof Uint8Array))
+ decoded = (new TextEncoder()).encode(decoded);
+
+ super(decoded);
+ }
+
+ /**
+ * Converts the 6bit integer into the corresponding base64 bytes
+ * @param {byte} data
+ * the 6 bit integer to be converted
+ * @returns {string}
+ * the corresponding base64 character
+ *
+ */
+ lookup(data) {
+ return (BASE64_CHARS[data].charCodeAt());
+ }
+
+ /**
+ * Encodes the first byte. It is exclusively used by the first
+ * quantum.
+ *
+ * @param {byte} q1
+ * the first quantum to be encoded.
+ * @returns {byte}
+ * the encoded quantum
+ */
+ encodeFirstByte(q1) {
+ return this.lookup((q1 >> Q1_HIGH_SHIFT) & Q1_HIGH_MASK);
+ }
+
+ /**
+ * Encodes the second byte. This byte is shared by the first
+ * and second quantum.
+ *
+ * The second quantum is optional and should be omitted or
+ * set to zero in case the end of the decoded data is reached.
+ *
+ * @param {byte} q1
+ * the first quantum.
+ * @param {byte} [q2]
+ * the second quantum.
+ * @returns {byte}
+ * the encoded quantum.
+ */
+ encodeSecondByte(q1, q2) {
+ if ((typeof(q2) === "undefined") || (q2 === null))
+ return this.lookup((q1 << Q1_LOW_SHIFT) & Q1_LOW_MASK);
+
+ return this.lookup(
+ ((q1 << Q1_LOW_SHIFT) & Q1_LOW_MASK)
+ + ((q2 >> Q2_HIGH_SHIFT) & Q2_HIGH_MASK));
+ }
+
+ /**
+ * Encodes the third byte. This byte is shared by the second
+ * and third quantum.
+ *
+ * The third quantum is optional and should be omitted or
+ * set to zero in case the end of the decoded date is reached.
+ *
+ * @param {byte} q2
+ * the second quantum.
+ * @param {byte} [q3]
+ * the third quantum.
+ * @returns {byte}
+ * the encoded quantum.
+ */
+ encodeThirdByte(q2, q3) {
+ if ((typeof(q3) === "undefined") || (q3 === null))
+ return this.lookup(((q2 << Q2_LOW_SHIFT) & Q2_LOW_MASK));
+
+ return this.lookup(
+ ((q2 << Q2_LOW_SHIFT) & Q2_LOW_MASK)
+ + ((q3 >> Q3_HIGH_SHIFT) & Q3_HIGH_MASK));
+ }
+
+ /**
+ * Encodes the fourth byte. This byte is exclusively used by
+ * the fourth quantum.
+ *
+ * @param {byte} q4
+ * the fourth quantum.
+ * @returns {byte}
+ * the encoded quantum.
+ */
+ encodeFourthByte(q4) {
+ return this.lookup((q4 >> Q3_LOW_SHIFT) & Q3_LOW_MASK);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ toArray() {
+
+ const decoded = this.decoded;
+
+ const data = new Uint8Array(
+ Math.ceil(decoded.byteLength / DECODED_QUANTUM) * ENCODED_QUANTUM);
+
+ const padding = (decoded.length % DECODED_QUANTUM);
+
+ for (let i = 0; i < decoded.length - padding; i += DECODED_QUANTUM) {
+
+ const offset = (i / DECODED_QUANTUM) * ENCODED_QUANTUM;
+
+ data[offset + FIRST_BYTE] = this.encodeFirstByte(decoded[i + FIRST_BYTE]);
+ data[offset + SECOND_BYTE]
+ = this.encodeSecondByte(decoded[i + FIRST_BYTE], decoded[i + SECOND_BYTE]);
+ data[offset + THIRD_BYTE]
+ = this.encodeThirdByte(decoded[i + SECOND_BYTE], decoded[i + THIRD_BYTE]);
+ data[offset + FOURTH_BYTE] = this.encodeFourthByte(decoded[i + THIRD_BYTE]);
+ }
+
+ if (padding === ONE_QUANTUM) {
+ const offset = data.length - ENCODED_QUANTUM;
+
+ data[offset + FIRST_BYTE] = this.encodeFirstByte(decoded[decoded.length - 1]);
+ data[offset + SECOND_BYTE] = this.encodeSecondByte(decoded[decoded.length - 1]);
+ data[offset + THIRD_BYTE] = CHAR_PADDING;
+ data[offset + FOURTH_BYTE] = CHAR_PADDING;
+ }
+
+ if (padding === TWO_QUANTUM) {
+ const offset = data.length - ENCODED_QUANTUM;
+
+ data[offset + FIRST_BYTE] = this.encodeFirstByte(
+ decoded[decoded.length - 2]);
+ data[offset + SECOND_BYTE] = this.encodeSecondByte(
+ decoded[decoded.length - 2], decoded[decoded.length - 1]);
+ data[offset + THIRD_BYTE] = this.encodeThirdByte(
+ decoded[decoded.length - 1]);
+ data[offset + FOURTH_BYTE] = CHAR_PADDING;
+ }
+
+ return data;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ toUtf8() {
+ return (new TextDecoder("UTF-8")).decode(this.toArray());
+ }
+}
+
+/**
+ * Implements a base64 decoded as atob is not capable of
+ * decoding an encoded UTF8 string.
+ */
+class SieveWebBase64Decoder extends SieveAbstractBase64Decoder {
+
+ /**
+ * @inheritdoc
+ */
+ constructor(encoded) {
+
+ if (!(encoded instanceof Uint8Array))
+ encoded = new Uint8Array((new TextEncoder()).encode(encoded));
+
+ super(encoded);
+ }
+
+ /**
+ * Calculates the decoded length.
+ * @param {Uint8Array} encoded
+ * the encoded data which should be analyzed
+ * @returns {int}
+ * the decoded length in bytes
+ */
+ calculateLength(encoded) {
+ const length = Math.floor((encoded.length / ENCODED_QUANTUM) * DECODED_QUANTUM);
+
+ // If the last character is not equals to "=" then we know
+ // the last quantum is fully or empty.
+ if (encoded[encoded.length - 1] !== CHAR_PADDING)
+ return length;
+
+ // In case the last two bytes are not equal to "=" we know
+ // we know the last quantum is 16bit.
+ if (encoded[encoded.length - 2] !== CHAR_PADDING)
+ return length - ONE_QUANTUM;
+
+ // Otherwise it is a single "=" at the end which means the
+ // las quantum is 8 bits.
+ return length - TWO_QUANTUM;
+ }
+
+ /**
+ * Decodes the first byte or quantum
+ *
+ * @param {int} offset
+ * the offset which points to the first group of the encoded data.
+ * @param {Uint8Array} encoded
+ * the encoded data.
+ * @returns {byte}
+ * The first byte or quantum.
+ */
+ decodeFirstQuantum(offset, encoded) {
+ return ((this.lookup(encoded[offset + Q1_HIGH_OFFSET]) & Q1_HIGH_MASK) << Q1_HIGH_SHIFT)
+ + ((this.lookup(encoded[offset + Q1_LOW_OFFSET]) & Q1_LOW_MASK) >> Q1_LOW_SHIFT);
+ }
+
+ /**
+ * Decodes the second byte or quantum
+ *
+ * @param {int} offset
+ * the offset which points to the first group of the encoded data.
+ * @param {Uint8Array} encoded
+ * the encoded data.
+ * @returns {byte}
+ * The second byte or quantum.
+ */
+ decodeSecondQuantum(offset, encoded) {
+ return ((this.lookup(encoded[offset + Q2_HIGH_OFFSET]) & Q2_HIGH_MASK) << Q2_HIGH_SHIFT)
+ + ((this.lookup(encoded[offset + Q2_LOW_OFFSET]) & Q2_LOW_MASK) >> Q2_LOW_SHIFT);
+ }
+
+ /**
+ * Decodes the third byte or quantum
+ *
+ * @param {int} offset
+ * the offset which points to the first group of the encoded data.
+ * @param {Uint8Array} encoded
+ * the encoded data.
+ * @returns {byte}
+ * The third byte or quantum.
+ */
+ decodeThirdQuantum(offset, encoded) {
+
+ return ((this.lookup(encoded[offset + Q3_HIGH_OFFSET]) & Q3_HIGH_MASK) << Q3_HIGH_SHIFT)
+ + ((this.lookup(encoded[offset + Q3_LOW_OFFSET]) & Q3_LOW_MASK) << Q3_LOW_SHIFT);
+ }
+
+ /**
+ * Converts a the base64 code point value to the decoded value.
+ * @param {byte} data
+ * the base64 code point
+ * @returns {byte}
+ * the decoded value.
+ */
+ lookup(data) {
+ return BASE64_CHARS.indexOf(String.fromCharCode(data));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ toArray() {
+
+ const encoded = this.encoded;
+ // adjust the array view to the padding
+
+ const data = new Uint8Array(this.calculateLength(encoded));
+
+ for (let i = 0; i < data.length - 1; i += DECODED_QUANTUM) {
+ const offset = (i / DECODED_QUANTUM) * ENCODED_QUANTUM;
+
+ data[i + FIRST_BYTE] = this.decodeFirstQuantum(offset, encoded);
+ data[i + SECOND_BYTE] = this.decodeSecondQuantum(offset, encoded);
+ data[i + THIRD_BYTE] = this.decodeThirdQuantum(offset, encoded);
+ }
+
+ const padding = (data.length % DECODED_QUANTUM);
+
+ if (padding === TWO_QUANTUM) {
+ const offset = ((data.length - padding) / DECODED_QUANTUM) * ENCODED_QUANTUM;
+ data[data.length - 2] = this.decodeFirstQuantum(offset, encoded);
+ data[data.length - 1] = this.decodeSecondQuantum(offset, encoded);
+ }
+
+ if (padding === ONE_QUANTUM) {
+ const offset = ((data.length - padding) / DECODED_QUANTUM) * ENCODED_QUANTUM;
+ data[data.length - 1] = this.decodeFirstQuantum(offset, encoded);
+ }
+
+ return data;
+ }
+}
+
+export {
+ SieveWebBase64Decoder as SieveBase64Decoder,
+ SieveWebBase64Encoder as SieveBase64Encoder
+};
diff --git a/src/wx/libs/libManageSieve/SieveClient.js b/src/wx/libs/libManageSieve/SieveClient.js
deleted file mode 100644
index 22b563b5..00000000
--- a/src/wx/libs/libManageSieve/SieveClient.js
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global Components */
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- // const Cu = Components.utils;
- const Cr = Components.results;
-
- // Handle all imports..
- const { SieveAbstractClient } = require("./SieveAbstractClient.js");
- const { SieveResponseParser } = require("./SieveResponseParser.js");
- const { SieveRequestBuilder } = require("./SieveRequestBuilder.js");
-
- const { SieveCertValidationException } = require("./SieveExceptions.js");
-
- const NEW_TRANSPORT_API = 4;
- const OLD_TRANSPORT_API = 5;
-
- // Input & output stream constants.
- const DEFAULT_FLAGS = 0;
- const DEFAULT_SEGMENT_SIZE = 0;
- const DEFAULT_SEGMENT_COUNT = 0;
-
- const SEGMENT_SIZE = 5000;
- const SEGMENT_COUNT = 2;
-
- const TRANSPORT_INSECURE = 0;
- const TRANSPORT_SECURE = 1;
-
- /**
- * This realizes the abstract sieve implementation by using
- * the mozilla specific network implementation.
- */
- class SieveMozClient extends SieveAbstractClient {
-
-
- /**
- * Creates a new instance
- * @param {SieveAbstractLogger} logger
- * the logger which should be used.
- */
- constructor(logger) {
-
- super();
-
- this.timeoutTimer = null;
- this.idleTimer = null;
-
- this.outstream = null;
- this.binaryOutStream = null;
-
- this._logger = logger;
- this.secure = true;
- }
-
-
- /**
- * Implements the Gecko Component Manager's interfaces.
- * So that the JavaScript code can be passed as interface
- * to C++.
- *
- * In case the interface is not supported an NS_ERROR_NO_INTERFACE
- * will be thrown.
- *
- * @param {nsIIDRef} uuid
- * the interface's unique id to check.
- *
- * @returns {SieveMozClient}
- * a self reference.
- */
- QueryInterface(uuid) {
- if (uuid.equals(Ci.nsISupports))
- return this;
- // onProxyAvailable...
- if (uuid.equals(Ci.nsIProtocolProxyCallback))
- return this;
- // onDataAvailable...
- if (uuid.equals(Ci.nsIStreamListener))
- return this;
- // onStartRequest and onStopRequest...
- if (uuid.equals(Ci.nsIRequestObserver))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
-
- /**
- * @inheritdoc
- */
- isAlive() {
- if (!super.isAlive(this))
- return false;
-
- return this.socket.isAlive();
- }
-
- /**
- * This method secures the connection to the sieve server. By activating
- * Transport Layer Security all Data exchanged is encrypted.
- *
- * Before calling this method you need to request a encrypted connection by
- * sending a startTLSRequest. Invoke this method immediately after the server
- * confirms switching to TLS.
- *
- **/
- async startTLS() {
-
- await super.startTLS();
-
- const securityInfo = this.socket.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
- securityInfo.StartTLS();
- }
-
- /**
- * @inheritdoc
- */
- onStartTimeout() {
-
- // clear any existing timeouts
- if (this.timeoutTimer)
- this.timeoutTimer.cancel();
-
- // ensure the idle timer is stopped
- this.onStopIdle();
-
- // then restart the timeout timer.
- if (this.timeoutTimer)
- this.timeoutTimer.initWithCallback(
- this, this.getTimeoutWait(),
- Components.interfaces.nsITimer.TYPE_ONE_SHOT);
-
- }
-
- /**
- * @inheritdoc
- */
- onStopTimeout() {
-
- // clear any existing timeouts.
- if (this.timeoutTimer)
- this.timeoutTimer.cancel();
-
- // and start the idle timer
- this.onStartIdle();
- }
-
- /**
- * Called when the idle timer should be started or restarted
- */
- onStartIdle() {
-
- // first ensure the timer is stopped..
- this.onStopIdle();
-
- // ... then configure the timer.
- const delay = this.getIdleWait();
-
- if (!delay)
- return;
-
- if (!this.idleTimer)
- return;
-
- this.idleTimer.initWithCallback(this, delay,
- Components.interfaces.nsITimer.TYPE_ONE_SHOT);
- }
-
- /**
- * Called when the idle timer should be stopped.
- */
- onStopIdle() {
-
- if (!this.idleTimer)
- return;
-
- this.idleTimer.cancel();
- }
-
- /**
- * When a mozilla timer triggers, it calls this
- * well known method.
- *
- * @param {nsITimer} timer
- * the timer which caused this callback.
- *
- */
- notify(timer) {
-
- if (this.idleTimer === timer) {
- (async () => { await this.onIdle(); })();
- return;
- }
-
- if (this.timeoutTimer === timer) {
- (async () => { await this.onTimeout(); })();
- return;
- }
- }
-
- /**
- * @inheritdoc
- */
- createParser(data) {
- return new SieveResponseParser(data);
- }
-
- /**
- * @inheritdoc
- */
- createRequestBuilder() {
- return new SieveRequestBuilder();
- }
-
- /**
- * @inheritdoc
- */
- getLogger() {
- return this._logger;
- }
-
- /**
- * @inheritdoc
- */
- isSecure() {
- return this.secure;
- }
-
- /**
- * @inheritdoc
- */
- connect(host, port, secure, proxy) {
- if (this.socket)
- return this;
-
- /* if ( (this.socket != null) && (this.socket.isAlive()) )
- return;*/
-
- this.host = host;
- this.port = port;
- this.secure = secure;
-
- this.idleTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- this.timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
- this.getLogger().logState(`Connecting to ${this.host}:${this.port} ...`);
-
- // If we know the proxy setting, we can do a shortcut...
- if (proxy) {
- this.onProxyAvailable(null, null, proxy[0], null);
- return this;
- }
-
- this.getLogger().logState(`Lookup Proxy Configuration for x-sieve://${this.host}:${this.port} ...`);
-
- const ios = Cc["@mozilla.org/network/io-service;1"]
- .getService(Ci.nsIIOService);
-
- // const uri = ios.newURI("x-sieve://" + this.host + ":" + this.port, null, null);
- const uri = ios.newURI(`http://${this.host}:${this.port}`, null, null);
-
- this.getLogger().logState(`Connecting to ${uri.hostPort}:${this.host} ...`);
-
- const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
- .getService(Ci.nsIProtocolProxyService);
- pps.asyncResolve(uri, 0, this);
-
- return this;
- }
-
- /**
- * We need this wrapper for compatibility. Stating Thunderbird 69
- * Bug 1558726 (https://bugzilla.mozilla.org/show_bug.cgi?id=1558726)
- * introduced a breaking change for this interface.
- *
- * Before the change the first parameter was an array and the second one
- * the array length. After the change the length parameter was removed
- * which causes all the other arguments to shift by one.
- *
- * @private
- * @param {string} host
- * the host name to which to connect
- * @param {int} port
- * the host's port
- * @param {boolean} secure
- * if true a secure socket is created.
- * @param {*} [proxyInfo]
- * optional proxy information.
- *
- * @returns {nsISocketTransportService}
- * the transport service or throws an exception in case creation failed.
- */
- createTransport(host, port, secure, proxyInfo) {
-
- const transportService =
- Cc["@mozilla.org/network/socket-transport-service;1"]
- .getService(Ci.nsISocketTransportService);
-
- if (transportService.createTransport.length === NEW_TRANSPORT_API)
- return transportService.createTransport(((secure) ? ["starttls"] : []), host, port, proxyInfo);
-
- if (transportService.createTransport.length === OLD_TRANSPORT_API) {
- if (secure)
- return transportService.createTransport(["starttls"], TRANSPORT_SECURE, host, port, proxyInfo);
-
- return transportService.createTransport(null, TRANSPORT_INSECURE, host, port, proxyInfo);
- }
-
- throw new Error("Unknown Create Transport signature");
- }
-
- /**
- * An async callback for the proxy lookup.
- *
- * @private
- * @param {nsIRequest} request
- * thr request for which the proxy information as requested.
- * @param {*} aURI
- * @param {*} aProxyInfo
- * the proxy information from the lookup, if null a direct
- * connection will be used.
- * @param {*} status
- *
- **/
- // eslint-disable-next-line no-unused-vars
- onProxyAvailable( request, aURI, aProxyInfo, status) {
-
- if (aProxyInfo)
- this.getLogger().logState(`Using Proxy: [${aProxyInfo.type}] ${aProxyInfo.host}:${aProxyInfo.port}`);
- else
- this.getLogger().logState("Using Proxy: Direct");
-
-
- this.socket = this.createTransport(this.host, this.port, this.isSecure(), aProxyInfo);
-
- this.outstream = this.socket.openOutputStream(
- DEFAULT_FLAGS, DEFAULT_SEGMENT_SIZE, DEFAULT_SEGMENT_COUNT);
-
- this.binaryOutStream =
- Cc["@mozilla.org/binaryoutputstream;1"]
- .createInstance(Ci.nsIBinaryOutputStream);
-
- this.binaryOutStream.setOutputStream(this.outstream);
-
- const stream = this.socket.openInputStream(
- DEFAULT_FLAGS, DEFAULT_SEGMENT_SIZE, DEFAULT_SEGMENT_COUNT);
-
- const pump = Cc["@mozilla.org/network/input-stream-pump;1"]
- .createInstance(Ci.nsIInputStreamPump);
-
- pump.init(stream, SEGMENT_SIZE, SEGMENT_COUNT, true);
-
- pump.asyncRead(this, null);
- }
-
- /**
- * @inheritdoc
- */
- disconnect(reason) {
- super.disconnect(reason);
-
- if (!this.socket)
- return;
-
- this.binaryOutStream.close();
- this.outstream.close();
- this.socket.close(0);
-
- this.binaryOutStream = null;
- this.outstream = null;
- this.socket = null;
-
- this.idleTimer = null;
- this.timeoutTimer = null;
-
- this.getLogger().logState("Disconnected ...");
- }
-
- /**
- * Checks if the status indicates a certificate error.
- *
- * @param {int} status
- * the status which should be checked.
- *
- * @returns {boolean}
- * true in case of a certificate error otherwise false.
- */
- isBadCert(status) {
- if (status === Cr.NS_OK)
- return false;
-
- this.getLogger().logState("Checking for certificate errors... ");
-
- const nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
- .getService(Ci.nsINSSErrorsService);
-
- try {
- const errorType = nssErrorsService.getErrorClass(status);
-
- if (errorType === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
- this.getLogger().logState(`... certificated considered bad (${status},${errorType})`);
- return true;
- }
-
- } catch (e) {
- console.warn(e);
- // nsINSSErrorsService.getErrorClass throws if given a non-TLS, non-cert error, so ignore this
- }
-
- return false;
- }
-
- /**
- * Called whenever the connection is terminated. And no more communication
- * via this socket is possible.
- *
- * If the disconnect is planned/"graceful", the error code is zero.
- *
- * Otherwise the status indicates what caused this disconnect.
- * Common issues are link loss (e.g. by switching to offline mode, when
- * the network cable is disconnected or when the server closed the connection.)^
- *
- * But the socket may be also closed because of non trivial errors. E.g
- * in case a tls upgrade failed due to an certification validation error.
- *
- * @param {nsIRequest} request
- * the request which was stopped.
- * @param {int} status
- * the reason why the stop was called.
- */
- // eslint-disable-next-line no-unused-vars
- onStopRequest(request, status) {
-
- this.getLogger().logState(`Disconnected from ${this.host}:${this.port} with status ${status}`);
-
- // we can ignore this if we are already disconnected.
- if (!this.socket)
- return;
-
- let reason;
-
- if (this.isBadCert(status)) {
- const secInfo = this.socket.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
-
- reason = new SieveCertValidationException({
- "host": this.host,
- "port": this.port,
-
- "rawDER": secInfo.serverCert.getRawDER({}),
- "fingerprint" : secInfo.serverCert.sha256Fingerprint,
-
- "message" : secInfo.errorCodeString,
- "isDomainMismatch": secInfo.isDomainMismatch,
- "isExtendedValidation": secInfo.isExtendedValidation,
- "isNotValidAtThisTime": secInfo.isNotValidAtThisTime,
- "isUntrusted": secInfo.isUntrusted
- });
- }
-
- // Stop timeout timer, the connection is gone, so...
- // ... it won't help us anymore...
- this.disconnect(reason);
-
- // if the request queue is not empty,
- // we should call directly on timeout..
- if ((this.listener) && (this.listener.onDisconnect))
- (async () => { await this.listener.onDisconnect(); })();
- }
-
- /**
- * Called when the connection to the remote as is ready.
- *
- * @param {nsIRequest} request
- * the request which is ready.
- */
- // eslint-disable-next-line no-unused-vars
- onStartRequest(request) {
- this.getLogger().logState(`Connected to ${this.host}:${this.port} ...`);
- }
-
- /**
- * Call as soon as data arrives and needs to be processed.
- *
- * @param {nsIRequest} request
- * the request object.
- * @param {nsIInputStream} inputStream
- * the input stream containing the data chunks
- * @param {offset} offset
- * the offset from the beginning of the stream.
- * @param {int} count
- * the maximum number of bytes which can be read in this call.
- */
- // eslint-disable-next-line no-unused-vars
- onDataAvailable(request, inputStream, offset, count) {
-
- const binaryInStream = Cc["@mozilla.org/binaryinputstream;1"]
- .createInstance(Ci.nsIBinaryInputStream);
-
- binaryInStream.setInputStream(inputStream);
-
- const data = binaryInStream.readByteArray(count);
-
- super.onReceive(data);
- }
-
- /**
- * @inheritdoc
- */
- onSend(data) {
-
- // Convert string into an UTF-8 array...
- const output = Array.prototype.slice.call(
- new Uint8Array(new TextEncoder("UTF-8").encode(data)));
-
- if (this.getLogger().isLevelStream())
- this.getLogger().logStream(`Client -> Server [Byte Array]:\n${output}`);
-
- this.binaryOutStream.writeByteArray(output, output.length);
-
- return;
- }
- }
-
- exports.Sieve = SieveMozClient;
-
-})(module.exports);
diff --git a/src/wx/libs/libManageSieve/SieveClient.mjs b/src/wx/libs/libManageSieve/SieveClient.mjs
new file mode 100644
index 00000000..af204f90
--- /dev/null
+++ b/src/wx/libs/libManageSieve/SieveClient.mjs
@@ -0,0 +1,171 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global browser */
+
+// Handle all imports..
+import { SieveAbstractClient } from "./SieveAbstractClient.mjs";
+
+
+import {
+ SieveCertValidationException,
+ SieveClientException,
+ SieveException
+} from "./SieveExceptions.mjs";
+
+/**
+ * This realizes the abstract sieve implementation by using
+ * the mozilla specific network implementation.
+ */
+class SieveMozClient extends SieveAbstractClient {
+
+
+ /**
+ * Creates a new instance
+ * @param {SieveLogger} logger
+ * the logger which should be used.
+ */
+ constructor(logger) {
+
+ super();
+
+ this._logger = logger;
+ this.secure = true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ isAlive() {
+ if (!super.isAlive(this))
+ return false;
+
+ return browser.sieve.socket.isAlive(this.socket);
+ }
+
+
+ /**
+ * This method secures the connection to the sieve server. By activating
+ * Transport Layer Security all Data exchanged is encrypted.
+ *
+ * Before calling this method you need to request a encrypted connection by
+ * sending a startTLSRequest. Invoke this method immediately after the server
+ * confirms switching to TLS.
+ *
+ **/
+ async startTLS() {
+ await super.startTLS();
+
+ this.getLogger().logState("[SieveClient:startTLS()] Upgrading to secure socket");
+
+ await browser.sieve.socket.startTLS(this.socket);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getLogger() {
+ return this._logger;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ isSecure() {
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async connect(host, port) {
+ if (this.socket)
+ return this;
+
+ this.host = host;
+ this.port = port;
+
+ this.getLogger().logState(`Connecting to ${this.host}:${this.port} ...`);
+
+ this.socket = await (browser.sieve.socket.create(host, port, this.getLogger().level()));
+
+ await (browser.sieve.socket.onData.addListener(async (bytes) => {
+ await super.onReceive(bytes);
+ }, this.socket));
+
+ await (browser.sieve.socket.onError.addListener((error) => {
+
+ // Exceptions can't be transferred between experiments and background pages
+ // This means we need to convert the error object into an exception.
+ if (error && error.type === "CertValidationError")
+ error = new SieveCertValidationException(error);
+ else if (error && error.type === "SocketError")
+ error = new SieveClientException(error.message);
+ else
+ error = new SieveException(`Socket failed without providing an error code.`);
+
+ if ((this.listener) && (this.listener.onError))
+ (async () => { await this.listener.onError(error); })();
+ }, this.socket));
+
+ await (browser.sieve.socket.onClose.addListener(() => {
+ this.disconnect();
+ }, this.socket));
+
+ await (browser.sieve.socket.connect(this.socket));
+
+ return this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async disconnect(reason) {
+
+ this.getLogger().logState(`[SieveClient:disconnect] Disconnecting ${this.host}:${this.port}...`);
+
+ await super.disconnect(reason);
+
+ if (!this.socket) {
+ this.getLogger().logState(`[SieveClient:disconnect()] ... no valid socket`);
+ return;
+ }
+
+ this.getLogger().logState(`[SieveClient:disconnect()] ... destroying socket...`);
+ await browser.sieve.socket.destroy(this.socket);
+ this.socket = null;
+
+ if ((this.listener) && (this.listener.onDisconnected))
+ await this.listener.onDisconnected();
+
+ this.getLogger().logState("[SieveClient:disconnect()] ... disconnected.");
+ }
+
+
+ /**
+ * @inheritdoc
+ */
+ onSend(data) {
+
+ // Convert string into an UTF-8 array...
+ const output = Array.prototype.slice.call(
+ (new TextEncoder()).encode(data));
+
+ if (this.getLogger().isLevelStream())
+ this.getLogger().logStream(`Client -> Server [Byte Array]:\n${output}`);
+
+ browser.sieve.socket.send(this.socket, output);
+
+ return;
+ }
+}
+
+export { SieveMozClient as Sieve };
diff --git a/src/wx/libs/libManageSieve/SieveCrypto.js b/src/wx/libs/libManageSieve/SieveCrypto.js
deleted file mode 100644
index 9d4cd95d..00000000
--- a/src/wx/libs/libManageSieve/SieveCrypto.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global Components */
-
- const { SieveAbstractCrypto } = require("./SieveAbstractCrypto.js");
-
- /**
- * A Mozilla specific crypto implementation.
- */
- class SieveMozCrypto extends SieveAbstractCrypto {
-
- /**
- * Returns the HMAC implementation for the given name.
- * In case the algorithm is unknown an exception is thrown.
- *
- * @returns {nsICryptoHMAC}
- * the HMAC type.
- */
- getCryptoHMAC() {
-
- if (this.name === "SHA1")
- return Components.interfaces.nsICryptoHMAC.SHA1;
-
- if (this.name === "SHA256")
- return Components.interfaces.nsICryptoHMAC.SHA256;
-
- if (this.name === "MD5")
- return Components.interfaces.nsICryptoHMAC.MD5;
-
- throw Error(`Unknown HMAC algorithm ${this.name}`);
- }
-
- /**
- * Returns the hashing implementation for the given name.
- * In case the algorithm is unknown an exception is thrown.
- *
- * @returns {nsICryptoHash}
- * the Hash type .
- */
- getCryptoHash() {
- if (this.name === "SHA1")
- return Components.interfaces.nsICryptoHash.SHA1;
-
- if (this.name === "SHA256")
- return Components.interfaces.nsICryptoHash.SHA256;
-
- if (this.name === "MD5")
- return Components.interfaces.nsICryptoHash.MD5;
-
- throw Error(`Unknown HASH algorithm ${this.name}`);
- }
-
- /**
- * @inheritdoc
- */
- HMAC(key, bytes, output) {
-
- if (typeof(key) === "undefined" || key === null)
- throw new Error("Invalid key");
-
- // Mozilla's api is odd. This means we need some magic here
- // The salt has to be a string while the data needs to be an byte array
-
- if (Array.isArray(bytes) === false)
- bytes = this.strToByteArray(bytes);
-
- if (Array.isArray(key) === true)
- key = this.byteArrayToStr(key);
-
- const crypto = Components.classes["@mozilla.org/security/hmac;1"]
- .createInstance(Components.interfaces.nsICryptoHMAC);
- const keyObject = Components.classes["@mozilla.org/security/keyobjectfactory;1"]
- .getService(Components.interfaces.nsIKeyObjectFactory)
- .keyFromString(Components.interfaces.nsIKeyObject.HMAC, key);
-
- crypto.init(this.getCryptoHMAC(), keyObject);
- crypto.update(bytes, bytes.length);
-
- const rv = this.strToByteArray(crypto.finish(false));
-
- if (typeof (output) !== "undefined" && output === "hex")
- return this.byteArrayToHexString(rv);
-
- return rv;
- }
-
- /**
- * @inheritdoc
- */
- H(bytes, output) {
-
- if (Array.isArray(bytes) === false)
- bytes = this.strToByteArray(bytes);
-
- const crypto = Components.classes["@mozilla.org/security/hash;1"]
- .createInstance(Components.interfaces.nsICryptoHash);
-
- crypto.init(this.getCryptoHash());
- crypto.update(bytes, bytes.length);
-
- const rv = this.strToByteArray(crypto.finish(false));
-
- if (typeof (output) !== "undefined" && output === "hex")
- return this.byteArrayToHexString(rv);
-
- return rv;
- }
-
- }
-
- exports.SieveCrypto = SieveMozCrypto;
-
-})(module.exports);
diff --git a/src/wx/libs/libManageSieve/SieveCrypto.mjs b/src/wx/libs/libManageSieve/SieveCrypto.mjs
new file mode 100644
index 00000000..39a8aeb6
--- /dev/null
+++ b/src/wx/libs/libManageSieve/SieveCrypto.mjs
@@ -0,0 +1,141 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import {SieveAbstractCrypto } from "./SieveAbstractCrypto.mjs";
+
+// eslint-disable-next-line no-magic-numbers
+const HASH_SHA1_LENGTH = 20 * 8;
+// eslint-disable-next-line no-magic-numbers
+const HASH_SHA256_LENGTH = 32 * 8;
+// eslint-disable-next-line no-magic-numbers
+const HASH_SHA512_LENGTH = 64 * 8;
+
+const HASH_SHA1 = "SHA-1";
+const HASH_SHA256 = "SHA-256";
+const HASH_SHA512 = "SHA-512";
+
+
+/**
+ * Implements a crypto provider which is backed by the web crypto api.
+ */
+class SieveWebCrypto extends SieveAbstractCrypto {
+
+ /**
+ * Returns the hash length in bits.
+ *
+ * @returns {int}
+ * the hash length in bits
+ */
+ getCryptoHashLength() {
+ if (this.getCryptoHash() === HASH_SHA1)
+ return HASH_SHA1_LENGTH;
+
+ if (this.getCryptoHash() === HASH_SHA256)
+ return HASH_SHA256_LENGTH;
+
+ if (this.getCryptoHash() === HASH_SHA512)
+ return HASH_SHA512_LENGTH;
+
+ throw new Error(`Unknown Hash algorithm ${this.name}`);
+ }
+
+
+ /**
+ * Hi() is a PBKDF2 [RFC2898] implementation with HMAC() as the pseudo random
+ * function (PRF) and with dkLen == output length of HMAC() == output
+ * length of H().
+ *
+ * @param {Uint8Array} key
+ * the key as byte array
+ * @param {Uint8Array} salt
+ * the salt as byte array
+ * @param {int} iterations
+ * iteration count a positive number (>= 1), suggested to be at least 4096
+ *
+ * @returns {Uint8Array}
+ * the pseudorandom value as byte string
+ */
+ async Hi(key, salt, iterations) {
+
+ if (!(key instanceof Uint8Array))
+ throw new Error("Key is not a Byte Array");
+
+ if (!(salt instanceof Uint8Array))
+ throw new Error("Salt is not a byte array");
+
+ // Create the base key to derive from.
+ key = await crypto.subtle.importKey(
+ "raw", key, "PBKDF2", false, ["deriveBits"]);
+
+ const algorithm = {
+ "name": "PBKDF2",
+ "hash": this.getCryptoHash(),
+ "salt": salt,
+ "iterations": iterations
+ };
+
+ return new Uint8Array(
+ await window.crypto.subtle.deriveBits(
+ algorithm, key, this.getCryptoHashLength()));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async HMAC(key, bytes, output) {
+
+ if (!Array.isArray(key) && !(key instanceof Uint8Array))
+ key = (new TextEncoder()).encode(key);
+
+ if (!Array.isArray(bytes))
+ bytes = (new TextEncoder()).encode(bytes);
+
+ key = await crypto.subtle.importKey(
+ "raw", new Uint8Array(key),
+ { name: "HMAC", hash: { name: this.getCryptoHash() } },
+ false, ["sign", "verify"]);
+
+ const signature = new Uint8Array(await crypto.subtle.sign(
+ "HMAC", key, new Uint8Array(bytes)));
+
+ if (typeof (output) !== "undefined" && output === "hex") {
+ const rv = this.byteArrayToHexString(signature);
+ return rv;
+ }
+
+ return [...signature];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async H(bytes, output) {
+
+ if (!Array.isArray(bytes)) {
+ // bytes = this.strToByteArray(bytes);
+ bytes = (new TextEncoder()).encode(bytes);
+ }
+
+ // this.name is the algorithm.
+ const hash = new Uint8Array(await crypto.subtle
+ .digest(this.getCryptoHash(), new Uint8Array(bytes)));
+
+ if (typeof (output) !== "undefined" && output === "hex") {
+ const rv = this.byteArrayToHexString(hash);
+ return rv;
+ }
+
+ return [...hash];
+ }
+}
+
+export { SieveWebCrypto as SieveCrypto };
diff --git a/src/wx/libs/libManageSieve/SieveLogger.js b/src/wx/libs/libManageSieve/SieveLogger.js
deleted file mode 100644
index bbdb30da..00000000
--- a/src/wx/libs/libManageSieve/SieveLogger.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global Components */
-
- const { SieveAbstractLogger } = require("./SieveAbstractLogger.js");
-
- /**
- * A mozilla specific logger
- */
- class SieveMozLogger extends SieveAbstractLogger {
-
- /**
- * @inheritdoc
- **/
- log(message, level) {
-
- if (!this.isLoggable(level))
- return this;
-
- Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService)
- .logStringMessage(`[${this.getTimestamp()} ${this.prefix()}] ${message}`);
-
- return this;
- }
- }
-
- exports.SieveLogger = SieveMozLogger;
-
-})(module.exports);
-
diff --git a/src/wx/libs/libManageSieve/SieveRequestBuilder.js b/src/wx/libs/libManageSieve/SieveRequestBuilder.js
deleted file mode 100644
index 1b35cbf8..00000000
--- a/src/wx/libs/libManageSieve/SieveRequestBuilder.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractRequestBuilder } = require("./SieveAbstractRequestBuilder.js");
-
- /**
- * @inheritdoc
- */
- class SieveMozRequestBuilder extends SieveAbstractRequestBuilder {
-
- /**
- * Manage Sieve uses for literals UTF-8 as encoding, network sockets are usually
- * binary, and javascript is something in between. This means we have to convert
- * UTF-8 into a binary by our own...
- *
- * @param {string} str The binary string which should be converted
- * @returns {string} The converted string in UTF8
- *
- * @author Thomas Schmid <schmid-thomas@gmx.net>
- */
- jsStringToByteArray(str) {
- // with chrome we have to use the TextEncoder.
- const data = new Uint8Array(new TextEncoder("UTF-8").encode(str));
- return Array.prototype.slice.call(data);
- }
-
- /**
- * @inheritdoc
- */
- calculateByteLength(data) {
- return this.jsStringToByteArray(data).length;
- }
-
- /**
- * @inheritdoc
- */
- convertToBase64(decoded) {
-
- // btoa is a bit strange it requires a javascript (unicode) string
- // which contains only latin1 code point.
-
- if (Array.isArray(decoded))
- decoded = String.fromCharCode(...new Uint8Array(decoded));
-
- return btoa(decoded);
- }
-
- /**
- * @inheritdoc
- **/
- convertFromBase64(encoded) {
- return atob(encoded);
- }
- }
-
- exports.SieveRequestBuilder = SieveMozRequestBuilder;
-
-})(module.exports);
diff --git a/src/wx/libs/libManageSieve/SieveResponseParser.js b/src/wx/libs/libManageSieve/SieveResponseParser.js
deleted file mode 100644
index 26a49c4e..00000000
--- a/src/wx/libs/libManageSieve/SieveResponseParser.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const { SieveAbstractResponseParser } = require("./SieveAbstractResponseParser.js");
-
- /**
- * Implements a mozilla specific response parser
- **/
- class SieveMozResponseParser extends SieveAbstractResponseParser {
-
- /**
- * @inheritdoc
- **/
- convertToString(byteArray) {
- // The new code should run with Google and Mozilla
- byteArray = new Uint8Array(byteArray);
- return (new TextDecoder("UTF-8")).decode(byteArray);
- }
-
- /**
- * @inheritdoc
- **/
- convertToBase64(decoded) {
- return btoa(decoded);
- }
-
- /**
- * @inheritdoc
- **/
- convertFromBase64(encoded) {
- return atob(encoded);
- }
- }
-
- exports.SieveResponseParser = SieveMozResponseParser;
-
-})(module.exports);
diff --git a/src/wx/libs/libManageSieve/SieveSession.mjs b/src/wx/libs/libManageSieve/SieveSession.mjs
new file mode 100644
index 00000000..57ddbd11
--- /dev/null
+++ b/src/wx/libs/libManageSieve/SieveSession.mjs
@@ -0,0 +1,80 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractSession } from "./SieveAbstractSession.mjs";
+
+/**
+ * A mozilla specific session implementation.
+ */
+class SieveMozSession extends SieveAbstractSession {
+
+ /**
+ * @inheritdoc
+ */
+ async onError(error) {
+
+ if (this.listeners && this.listeners.onError) {
+ await this.listeners.onError(error);
+ return;
+ }
+
+ super.onError(error);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async onDisconnected() {
+
+ if (this.listeners && this.listeners.onDisconnected) {
+ await this.listeners.onDisconnected();
+ return;
+ }
+
+ super.onDisconnected();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async connect(host, port) {
+
+ // eslint-disable-next-line no-async-promise-executor
+ await new Promise(async (resolve, reject) => {
+
+ try {
+ this.on("error", (error) => {
+ this.getLogger().logState("SieveSession:connect:onError()");
+ reject(error);
+ });
+
+ this.on("disconnected", () => {
+ this.getLogger().logState("SieveSession:connect:onDisconnected()");
+ // reject(new Error(`Server disconnected`));
+ });
+
+ await super.connect(host, port);
+ resolve();
+ }
+ catch (ex) {
+ reject(ex);
+ } finally {
+ // Restore the original listener.
+ this.on("error");
+ this.on("disconnected");
+ }
+ });
+
+ return this;
+ }
+}
+
+export { SieveMozSession as SieveSession };
diff --git a/src/wx/libs/libManageSieve/SieveTimer.mjs b/src/wx/libs/libManageSieve/SieveTimer.mjs
new file mode 100644
index 00000000..de9e9e02
--- /dev/null
+++ b/src/wx/libs/libManageSieve/SieveTimer.mjs
@@ -0,0 +1,45 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import { SieveAbstractTimer } from "./SieveAbstractTimer.mjs";
+
+/**
+ * Uses JavaScript's setTimeout() method to implement a timer.
+ */
+class SieveWebTimer extends SieveAbstractTimer {
+
+
+ /**
+ * @inheritdoc
+ */
+ cancel() {
+ if (!this.timer)
+ return;
+
+ window.clearTimeout(this.timer);
+ this.timer = null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ start(callback, ms) {
+ this.cancel();
+
+ if (ms === 0)
+ return;
+
+ this.timer = window.setTimeout(callback, ms);
+ }
+}
+
+export { SieveWebTimer as SieveTimer };
diff --git a/src/wx/libs/managesieve.ui/accounts.html b/src/wx/libs/managesieve.ui/accounts.html
index 6bf8486a..1932b990 100644
--- a/src/wx/libs/managesieve.ui/accounts.html
+++ b/src/wx/libs/managesieve.ui/accounts.html
@@ -23,27 +23,7 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="./../bootstrap/js/bootstrap.bundle.min.js"></script>
- <script src="./utils/SieveFakeRequire.js"></script>
-
- <script src="./utils/SieveLogger.js"></script>
- <script src="./utils/SieveUniqueId.js"></script>
- <script src="./utils/SieveAbstractIpcClient.js"></script>
- <script src="./utils/SieveIpcClient.js"></script>
-
- <script src="./utils/SieveI18n.js"></script>
- <script src="./utils/SieveTemplate.js"></script>
-
- <script src="./settings/ui/SieveDebugSettingsUI.js"></script>
-
- <script src="./accounts/SieveCapabilities.js"></script>
- <script src="./accounts/SieveScriptUI.js"></script>
- <script src="./accounts/SieveAccountUI.js"></script>
- <script src="./accounts/SieveAbstractAccounts.js"></script>
- <script src="./accounts/SieveAccounts.js"></script>
-
- <script src="./dialogs/SieveDialogUI.js"></script>
-
- <script src="accounts.js"></script>
+ <script type="module" src="accounts.mjs"></script>
</body>
diff --git a/src/wx/libs/managesieve.ui/accounts.js b/src/wx/libs/managesieve.ui/accounts.js
deleted file mode 100644
index eeec5ef9..00000000
--- a/src/wx/libs/managesieve.ui/accounts.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function () {
-
- "use strict";
-
- /* global SieveAccounts */
- /* global SieveIpcClient */
- /* global SieveRenameScriptDialog */
- /* global SieveCreateScriptDialog */
- /* global SieveDeleteScriptDialog */
- /* global SieveScriptBusyDialog */
- /* global SieveFingerprintDialog */
- /* global SieveLogger */
- /* global SieveI18n */
-
- /**
- * Shows a prompt which asks the user for the new script name.
- *
- * @returns {string}
- * the script name or an empty string in case the dialog was canceled.
- */
- async function onCreateScript() {
- return await (new SieveCreateScriptDialog()).show();
- }
-
- /**
- * Shows a prompt which asks the user if the script should be deleted.
- *
- * @param {string} name
- * the script name which should be deleted
- *
- * @returns {boolean}
- * true in case the script shall be deleted otherwise false.
- */
- async function onDeleteScript(name) {
- return await (new SieveDeleteScriptDialog(name)).show();
- }
-
- /**
- * Shows a prompt which asks the user if the script should be renamed.
- *
- * @param {string} name
- * the name which should be renamed
- *
- * @returns {string}
- * the script name in case the dialog. In case the dialog was
- * canceled the original name otherwise the new name.
- */
- async function onRenameScript(name) {
- return await (new SieveRenameScriptDialog(name)).show();
- }
-
- /**
- * Informs the user that the action can't be performed because
- * the script is currently in use.
- *
- * @param {string} name
- * the name of the script which was busy
- */
- async function onBusy(name) {
- await (new SieveScriptBusyDialog(name)).show();
- }
-
- /**
- * Informs the user about a failed certificate validation.
- *
- * @param {object} secInfo
- * the security information with more details about the validation error.
- *
- * @returns {boolean}
- * true in case the certificate should be overwritten otherwise false.
- */
- async function onCertError(secInfo) {
- return await (new SieveFingerprintDialog(secInfo)).show();
- }
-
- /**
- * The main entry point for the account view
- */
- async function main() {
-
- // TODO move to editor
- /* window.onbeforeunload = (e) => {
- // if changed...
- e.preventDefault();
- };*/
-
- SieveLogger.getInstance().level(
- await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
-
- await (SieveI18n.getInstance()).load();
-
- const accounts = new SieveAccounts();
- accounts.render();
-
- SieveIpcClient.setRequestHandler("accounts", "script-show-create",
- async () => { return await onCreateScript(); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-delete",
- async (msg) => { return await onDeleteScript(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-rename",
- async (msg) => { return await onRenameScript(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "script-show-busy",
- async (msg) => { await onBusy(msg.payload); });
- SieveIpcClient.setRequestHandler("accounts", "account-show-certerror",
- async (msg) => { return await onCertError(msg.payload); });
- }
-
- if (document.readyState !== 'loading')
- main();
- else
- document.addEventListener('DOMContentLoaded', () => { main(); }, {once: true});
-
-})();
diff --git a/src/wx/libs/managesieve.ui/accounts.mjs b/src/wx/libs/managesieve.ui/accounts.mjs
new file mode 100644
index 00000000..9eea29c3
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/accounts.mjs
@@ -0,0 +1,137 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveLogger } from "./utils/SieveLogger.mjs";
+import { SieveIpcClient } from "./utils/SieveIpcClient.mjs";
+import { SieveI18n } from "./utils/SieveI18n.mjs";
+
+import { SieveAbstractAccounts as SieveAccounts } from "./accounts/SieveAbstractAccounts.mjs";
+import {
+ SieveCreateScriptDialog,
+ SieveDeleteScriptDialog,
+ SieveRenameScriptDialog,
+ SieveFingerprintDialog,
+ SieveScriptBusyDialog,
+ SieveErrorDialog
+} from "./dialogs/SieveDialogUI.mjs";
+
+/**
+ * Shows a prompt which asks the user for the new script name.
+ *
+ * @returns {string}
+ * the script name or an empty string in case the dialog was canceled.
+ */
+async function onCreateScript() {
+ return await (new SieveCreateScriptDialog()).show();
+}
+
+/**
+ * Shows a prompt which asks the user if the script should be deleted.
+ *
+ * @param {string} name
+ * the script name which should be deleted
+ *
+ * @returns {boolean}
+ * true in case the script shall be deleted otherwise false.
+ */
+async function onDeleteScript(name) {
+ return await (new SieveDeleteScriptDialog(name)).show();
+}
+
+/**
+ * Shows a prompt which asks the user if the script should be renamed.
+ *
+ * @param {string} name
+ * the name which should be renamed
+ *
+ * @returns {string}
+ * the script name in case the dialog. In case the dialog was
+ * canceled the original name otherwise the new name.
+ */
+async function onRenameScript(name) {
+ return await (new SieveRenameScriptDialog(name)).show();
+}
+
+/**
+ * Informs the user that the action can't be performed because
+ * the script is currently in use.
+ *
+ * @param {string} name
+ * the name of the script which was busy
+ */
+async function onBusy(name) {
+ await (new SieveScriptBusyDialog(name)).show();
+}
+
+/**
+ * Informs the user about a failed certificate validation.
+ *
+ * @param {object} secInfo
+ * the security information with more details about the validation error.
+ *
+ * @returns {boolean}
+ * true in case the certificate should be overwritten otherwise false.
+ */
+async function onCertError(secInfo) {
+ return await (new SieveFingerprintDialog(secInfo)).show();
+}
+
+/**
+ * Informs the user about a connection error.
+ *
+ * @param {string} message
+ * the detailed connection error.
+ */
+async function onError(message) {
+ await (new SieveErrorDialog(message)).show();
+}
+
+/**
+ * The main entry point for the account view
+ */
+async function main() {
+
+ // TODO move to editor
+ /* window.onbeforeunload = (e) => {
+ // if changed...
+ e.preventDefault();
+ };*/
+ try {
+
+ SieveLogger.getInstance().level(
+ await SieveIpcClient.sendMessage("core", "settings-get-loglevel"));
+
+ await (SieveI18n.getInstance()).load();
+
+ const accounts = new SieveAccounts();
+ accounts.render();
+
+ SieveIpcClient.setRequestHandler("accounts", "script-show-create",
+ async () => { return await onCreateScript(); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-delete",
+ async (msg) => { return await onDeleteScript(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-rename",
+ async (msg) => { return await onRenameScript(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "script-show-busy",
+ async (msg) => { await onBusy(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "account-show-certerror",
+ async (msg) => { return await onCertError(msg.payload); });
+ SieveIpcClient.setRequestHandler("accounts", "account-show-error",
+ async (msg) => { return await onError(msg.payload); });
+ } catch (ex) {
+ console.error(ex);
+ }
+}
+
+if (document.readyState !== 'loading')
+ main();
+else
+ document.addEventListener('DOMContentLoaded', () => { main(); }, { once: true });
diff --git a/src/wx/libs/managesieve.ui/accounts/SieveAccountUI.mjs b/src/wx/libs/managesieve.ui/accounts/SieveAccountUI.mjs
new file mode 100644
index 00000000..e9fc4c28
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/accounts/SieveAccountUI.mjs
@@ -0,0 +1,21 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractAccountUI } from "./SieveAbstractAccountUI.mjs";
+
+
+/**
+ * A UI renderer for a sieve account
+ */
+class SieveMozAccountUI extends SieveAbstractAccountUI {
+}
+
+export { SieveMozAccountUI as SieveAccountUI };
diff --git a/src/wx/libs/managesieve.ui/accounts/SieveAccounts.js b/src/wx/libs/managesieve.ui/accounts/SieveAccounts.js
deleted file mode 100644
index 69726d66..00000000
--- a/src/wx/libs/managesieve.ui/accounts/SieveAccounts.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global SieveAbstractAccounts */
-
- /**
- * @inheritdoc
- */
- class SieveWxAccounts extends SieveAbstractAccounts {
-
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAccounts = SieveWxAccounts;
- else
- exports.SieveAccounts = SieveWxAccounts;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/accounts/SieveAccounts.mjs b/src/wx/libs/managesieve.ui/accounts/SieveAccounts.mjs
new file mode 100644
index 00000000..d0050e4b
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/accounts/SieveAccounts.mjs
@@ -0,0 +1,22 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+
+import { SieveAbstractAccounts } from "./SieveAbstractAccounts.mjs";
+
+/**
+ * @inheritdoc
+ */
+class SieveWxAccounts extends SieveAbstractAccounts {
+
+}
+
+export { SieveWxAccounts as SieveAccounts };
diff --git a/src/wx/libs/managesieve.ui/accounts/account.settings.tpl b/src/wx/libs/managesieve.ui/accounts/account.settings.html
index db522a96..9f83be38 100644
--- a/src/wx/libs/managesieve.ui/accounts/account.settings.tpl
+++ b/src/wx/libs/managesieve.ui/accounts/account.settings.html
@@ -4,7 +4,7 @@
<div style="min-width:8em" data-i18n="account.details.server"></div>
<span class="sieve-settings-hostname"></span>:
<span class="sieve-settings-port"></span>
- <span class="sieve-settings-secure ml-1" data-i18n="account.details.secure"></span>
+ <span class="sieve-settings-secure ms-1" data-i18n="account.details.secure"></span>
</div>
<!--<div class="row sieve-settings-fingerprint-item">
<span class="col-3">Fingerprint:</span>
@@ -19,8 +19,8 @@
<span class="sieve-settings-mechanism">SCRAM-MD5</span>
</div>-->
<div class="mt-3">
- <!-- <button type="button" class="sieve-account-edit-server btn btn-sm btn-outline-secondary mr-1">Edit Server</button>
- <button type="button" class="sieve-account-edit-credentials btn btn-sm btn-outline-secondary mr-1">Edit Credentials</button>-->
+ <!-- <button type="button" class="sieve-account-edit-server btn btn-sm btn-outline-secondary me-1">Edit Server</button>
+ <button type="button" class="sieve-account-edit-credentials btn btn-sm btn-outline-secondary me-1">Edit Credentials</button>-->
<button type="button"
data-i18n="account.details.debugging.edit"
class="sieve-account-edit-debug btn btn-sm btn-outline-secondary"></button>
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.js b/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.js
deleted file mode 100644
index 1fdc49ab..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global browser */
- const { SieveAbstractAccount } = require("./SieveAbstractAccount.js");
- const { SieveAbstractAccounts } = require("./SieveAbstractAccounts.js");
-
-
- /**
- * Manages the configuration for sieve accounts.
- * It queries thunderbird's account and extracts all needed information.
- *
- * Global settings are stored in the addons persistence.
- */
- class SieveAccounts extends SieveAbstractAccounts{
-
- /**
- * @inheritdoc
- */
- async load() {
-
- const items = await (browser.accounts.list());
-
- const accounts = {};
-
- if (!items)
- return this;
-
- for (const item of items) {
-
- if (item.type !== "imap" && item.type !== "pop3")
- continue;
-
- accounts[item.id] = new SieveAbstractAccount(item.id);
- }
-
- this.accounts = accounts;
- return this;
- }
-
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAccounts = SieveAccounts;
- else
- exports.SieveAccounts = SieveAccounts;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.mjs b/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.mjs
new file mode 100644
index 00000000..78e5562b
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/settings/logic/SieveAccounts.mjs
@@ -0,0 +1,50 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global browser */
+import { SieveAbstractAccount } from "./SieveAbstractAccount.mjs";
+import { SieveAbstractAccounts } from "./SieveAbstractAccounts.mjs";
+
+/**
+ * Manages the configuration for sieve accounts.
+ * It queries thunderbird's account and extracts all needed information.
+ *
+ * Global settings are stored in the addons persistence.
+ */
+class SieveAccounts extends SieveAbstractAccounts {
+
+ /**
+ * @inheritdoc
+ */
+ async load() {
+
+ const items = await (browser.accounts.list());
+
+ const accounts = {};
+
+ if (!items)
+ return this;
+
+ for (const item of items) {
+
+ if (item.type !== "imap" && item.type !== "pop3")
+ continue;
+
+ accounts[item.id] = new SieveAbstractAccount(item.id);
+ }
+
+ this.accounts = accounts;
+ return this;
+ }
+
+}
+
+export { SieveAccounts };
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.js b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.js
deleted file mode 100644
index 0f717941..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const AUTH_TYPE_IMAP = 1;
- const DEFAULT_AUTH_TYPE = AUTH_TYPE_IMAP;
-
- const CONFIG_AUTHENTICATION_TYPE = "activeLogin";
-
- const { SieveAbstractAuthentication } = require("libs/managesieve.ui/settings/SieveAbstractAuthentication.js");
- const { SieveAbstractMechanism } = require("libs/managesieve.ui/settings/SieveAbstractMechanism.js");
-
- /* global browser */
-
- /**
- * Uses the IMAP accounts credentials.
- */
- class SieveImapAuthentication extends SieveAbstractAuthentication {
-
- /**
- * @inheritdoc
- */
- async getPassword() {
- return await browser.sieve.accounts.getPassword(this.account.getId());
- }
-
- /**
- * @inheritdoc
- */
- async getUsername() {
- return await browser.sieve.accounts.getUsername(this.account.getId());
- }
- }
-
- /**
- * Manages the authorization settings.
- */
- class SieveAuthentication extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return DEFAULT_AUTH_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_AUTHENTICATION_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- hasMechanism(type) {
- switch (type) {
- case AUTH_TYPE_IMAP:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- **/
- getMechanismById(type) {
- switch (type) {
- case AUTH_TYPE_IMAP:
- // fall through we just implement prompt authentication
- default:
- return new SieveImapAuthentication(AUTH_TYPE_IMAP, this.account);
- }
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAuthentication = SieveAuthentication;
- else
- exports.SieveAuthentication = SieveAuthentication;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs
new file mode 100644
index 00000000..df3067c8
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthentication.mjs
@@ -0,0 +1,37 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+import { SieveAbstractAuthentication } from "./SieveAbstractAuthentication.mjs";
+
+/* global browser */
+
+/**
+ * Uses the IMAP accounts credentials.
+ */
+class SieveMozAuthentication extends SieveAbstractAuthentication {
+
+ /**
+ * @inheritdoc
+ */
+ async getPassword() {
+ return await browser.sieve.accounts.getPassword(this.account.getId());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getUsername() {
+ return await browser.sieve.accounts.getUsername(this.account.getId());
+ }
+}
+
+export { SieveMozAuthentication as SieveAuthentication };
+
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.js b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.js
deleted file mode 100644
index 889b62bf..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- const AUTHORIZATION_TYPE_USERNAME = 1;
- const CONFIG_AUTHORIZATION_TYPE = "authorization.type";
-
- const { SieveAbstractMechanism } = require("libs/managesieve.ui/settings/SieveAbstractMechanism.js");
- const { SieveDefaultAuthorization } = require("libs/managesieve.ui/settings/SieveAbstractAuthorization.js");
-
- /**
- * Manages the authorization settings.
- */
- class SieveAuthorization extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return AUTHORIZATION_TYPE_USERNAME;
- }
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_AUTHORIZATION_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- hasMechanism(type) {
- switch (type) {
- case AUTHORIZATION_TYPE_USERNAME:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- **/
- getMechanismById(type) {
- switch (type) {
- case AUTHORIZATION_TYPE_USERNAME:
- return new SieveDefaultAuthorization(AUTHORIZATION_TYPE_USERNAME, this.account);
-
- default:
- throw new Error("Unknown authorization mechanism");
- }
- }
- }
-
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveAuthorization = SieveAuthorization;
- else
- exports.SieveAuthorization = SieveAuthorization;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs
new file mode 100644
index 00000000..7d74f0a1
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/settings/logic/SieveAuthorization.mjs
@@ -0,0 +1,64 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+const AUTHORIZATION_TYPE_USERNAME = 1;
+const CONFIG_AUTHORIZATION_TYPE = "authorization.type";
+
+import { SieveAbstractMechanism } from "./SieveAbstractMechanism.mjs";
+import { SieveDefaultAuthorization } from "./SieveAbstractAuthorization.mjs";
+
+/**
+ * Manages the authorization settings.
+ */
+class SieveAuthorization extends SieveAbstractMechanism {
+
+ /**
+ * @inheritdoc
+ **/
+ getDefault() {
+ return AUTHORIZATION_TYPE_USERNAME;
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ getKey() {
+ return CONFIG_AUTHORIZATION_TYPE;
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ hasMechanism(type) {
+ switch (type) {
+ case AUTHORIZATION_TYPE_USERNAME:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ **/
+ getMechanismById(type) {
+ switch (type) {
+ case AUTHORIZATION_TYPE_USERNAME:
+ return new SieveDefaultAuthorization(AUTHORIZATION_TYPE_USERNAME, this.account);
+
+ default:
+ throw new Error("Unknown authorization mechanism");
+ }
+ }
+}
+
+export { SieveAuthorization };
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveHost.js b/src/wx/libs/managesieve.ui/settings/logic/SieveHost.js
deleted file mode 100644
index 8eae489e..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SieveHost.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global browser */
-
- const HOST_TYPE_IMAP = 0;
- const CONFIG_HOST_TYPE = "activeHost";
-
- const { SieveAbstractMechanism } = require("libs/managesieve.ui/settings/SieveAbstractMechanism.js");
- const { SieveAbstractHost } = require("libs/managesieve.ui/settings/SieveAbstractHost.js");
-
- /**
- * This class loads the hostname from an IMAP account. The hostname is not
- * cached it. This ensures that always the most recent settings are used.
- */
- class SieveImapHost extends SieveAbstractHost {
-
- /**
- * @inheritdoc
- */
- async getDisplayName() {
- return await browser.sieve.accounts.getPrettyName(this.account.getId());
- }
-
- /**
- * @inheritdoc
- */
- async getHostname() {
- return await browser.sieve.accounts.getHostname(this.account.getId());
- }
- }
-
- /**
- * A transparent wrapper needed to deal with the different
- * host mechanism which are provided by electron and thunderbird.
- **/
- class SieveHost extends SieveAbstractMechanism {
-
- /**
- * @inheritdoc
- **/
- getKey() {
- return CONFIG_HOST_TYPE;
- }
-
- /**
- * @inheritdoc
- **/
- getDefault() {
- return HOST_TYPE_IMAP;
- }
-
- /**
- * @inheritdoc
- */
- hasMechanism(type) {
- switch (type) {
- case HOST_TYPE_IMAP:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * @inheritdoc
- */
- getMechanismById(type) {
-
- switch (type) {
-
- case HOST_TYPE_IMAP:
- // fall through
- default:
- return new SieveImapHost(HOST_TYPE_IMAP, this.account);
- }
- }
-
- }
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveHost = SieveHost;
- else
- exports.SieveHost = SieveHost;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveHost.mjs b/src/wx/libs/managesieve.ui/settings/logic/SieveHost.mjs
new file mode 100644
index 00000000..102c2ced
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/settings/logic/SieveHost.mjs
@@ -0,0 +1,50 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global browser */
+
+import { SieveCustomHost } from "./SieveAbstractHost.mjs";
+
+const CONFIG_KEEP_ALIVE_INTERVAL = "keepalive";
+// eslint-disable-next-line no-magic-numbers
+const ONE_MINUTE = 60 * 1000;
+// eslint-disable-next-line no-magic-numbers
+const FIVE_MINUTES = 5 * ONE_MINUTE;
+
+/**
+ * This class loads the hostname from an IMAP account. The hostname is not
+ * cached it. This ensures that always the most recent settings are used.
+ */
+class SieveMozHost extends SieveCustomHost {
+
+ /**
+ * @inheritdoc
+ */
+ async getDisplayName() {
+ return await browser.sieve.accounts.getPrettyName(this.account.getId());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getHostname() {
+ return await browser.sieve.accounts.getHostname(this.account.getId());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async getKeepAlive() {
+ return await this.account.getConfig().getInteger(CONFIG_KEEP_ALIVE_INTERVAL, FIVE_MINUTES);
+ }
+}
+
+export { SieveMozHost as SieveHost };
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.js b/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.js
deleted file mode 100644
index 784c6777..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /* global browser */
-
- const { SieveAbstractPrefManager } = require("libs/managesieve.ui/settings/SieveAbstractPrefManager.js");
-
- /**
- * Manages preferences.
- * It uses the WebExtension's local storage interface
- */
- class SieveMozPrefManager extends SieveAbstractPrefManager {
-
- /**
- * @inheritdoc
- */
- async getValue(key) {
- key = `${this.getNamespace()}.${key}`;
-
- const pair = await browser.storage.local.get(key);
-
- if (pair[key] === undefined)
- return undefined;
-
- return pair[key];
- }
-
- /**
- * @inheritdoc
- */
- async setValue(key, value) {
-
- const item = {};
- item[`${this.getNamespace()}.${key}`] = value;
-
- await browser.storage.local.set(item);
- return this;
- }
- }
-
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SievePrefManager = SieveMozPrefManager;
- else
- exports.SievePrefManager = SieveMozPrefManager;
-
-})(this);
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.mjs b/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.mjs
new file mode 100644
index 00000000..60d78340
--- /dev/null
+++ b/src/wx/libs/managesieve.ui/settings/logic/SievePrefManager.mjs
@@ -0,0 +1,49 @@
+/*
+ * The content of this file is licensed. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ *
+ * The initial author of the code is:
+ * Thomas Schmid <schmid-thomas@gmx.net>
+ */
+
+/* global browser */
+import { SieveAbstractPrefManager } from "./SieveAbstractPrefManager.mjs";
+
+/**
+ * Manages preferences.
+ * It uses the WebExtension's local storage interface
+ */
+class SieveMozPrefManager extends SieveAbstractPrefManager {
+
+ /**
+ * @inheritdoc
+ */
+ async getValue(key) {
+ key = `${this.getNamespace()}.${key}`;
+
+ const pair = await browser.storage.local.get(key);
+
+ if (pair[key] === undefined)
+ return undefined;
+
+ return pair[key];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async setValue(key, value) {
+
+ const item = {};
+ item[`${this.getNamespace()}.${key}`] = value;
+
+ await browser.storage.local.set(item);
+ return this;
+ }
+}
+
+
+export { SieveMozPrefManager as SievePrefManager };
diff --git a/src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.js b/src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.js
deleted file mode 100644
index 79b5e3b8..00000000
--- a/src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-(function (exports) {
-
- "use strict";
-
- /**
- * Defines the security related settings for an account.
- * It is a minimal, mozilla specific implementation.
- */
- class SieveSecurity {
-
- /**
- * @inheritdoc
- */
- async isSecure() {
- return await true;
- }
-
- /**
- * @inheritdoc
- */
- async getMechanism() {
- return await "default";
- }
- }
-
-
- // Require modules need to use export.module
- if (typeof (module) !== "undefined" && module && module.exports)
- module.exports.SieveSecurity = SieveSecurity;
- else
- exports.SieveSecurity = SieveSecurity;
-
-})(this);
diff --git a/src/wx/libs/libManageSieve/SieveSession.js b/src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.mjs
index 9a164838..95eb8ed1 100644
--- a/src/wx/libs/libManageSieve/SieveSession.js
+++ b/src/wx/libs/managesieve.ui/settings/logic/SieveSecurity.mjs
@@ -9,12 +9,8 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/
-(function (exports) {
- "use strict";
+import { SieveAbstractSecurity } from "./SieveAbstractSecurity.mjs";
- const { SieveAbstractSession } = require("./SieveAbstractSession.js");
+export { SieveAbstractSecurity as SieveSecurity };
- exports.SieveSession = SieveAbstractSession;
-
-})(module.exports);
diff --git a/src/wx/libs/managesieve.ui/utils/SieveIpcClient.js b/src/wx/libs/managesieve.ui/utils/SieveIpcClient.js
index dfef1461..cff8ca78 100644
--- a/src/wx/libs/managesieve.ui/utils/SieveIpcClient.js
+++ b/src/wx/libs/managesieve.ui/utils/SieveIpcClient.js
@@ -9,55 +9,45 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/
-(function (exports) {
+/* global browser */
+import { SieveLogger } from "./SieveLogger.mjs";
+import { SieveAbstractIpcClient } from "./SieveAbstractIpcClient.mjs";
- "use strict";
+/**
+ * An abstract implementation for a inter process/frame communication.
+ */
+class SieveWxIpcClient extends SieveAbstractIpcClient {
- /* global browser */
- const { SieveLogger } = require("./SieveLogger.js");
- const { SieveAbstractIpcClient } = require("./SieveAbstractIpcClient.js");
+ /**
+ * @inheritdoc
+ */
+ static getLogger() {
+ return SieveLogger.getInstance();
+ }
/**
- * An abstract implementation for a inter process/frame communication.
+ * @inheritdoc
*/
- class SieveWxIpcClient extends SieveAbstractIpcClient {
+ static parseMessageFromEvent(e) {
+ return JSON.parse(e.data);
+ }
- /**
- * @inheritdoc
- */
- static getLogger() {
- return SieveLogger.getInstance();
- }
+ /**
+ * @inheritdoc
+ */
+ // eslint-disable-next-line no-unused-vars
+ static dispatch(message, target) {
- /**
- * @inheritdoc
- */
- static parseMessageFromEvent(e) {
- return JSON.parse(e.data);
+ if (typeof (message) !== 'string') {
+ message = JSON.stringify(message);
}
- /**
- * @inheritdoc
- */
- // eslint-disable-next-line no-unused-vars
- static dispatch(message, target) {
-
- if (typeof(message) !== 'string') {
- message = JSON.stringify(message);
- }
-
- browser.runtime.sendMessage(message);
- }
+ browser.runtime.sendMessage(message);
}
+}
- browser.runtime.onMessage.addListener((request, sender) => {
- SieveWxIpcClient.onMessage({data : request, source: sender});
- });
-
- // Require modules need to use export.module
- if (typeof(module) !== "undefined" && module && module.exports)
- module.exports.SieveIpcClient = SieveWxIpcClient;
- else
- exports.SieveIpcClient = SieveWxIpcClient;
+browser.runtime.onMessage.addListener((request, sender) => {
+ SieveWxIpcClient.onMessage({ data: request, source: sender });
+});
-})(this);
+export { SieveWxIpcClient as SieveIpcClient };
diff --git a/src/wx/manifest.json b/src/wx/manifest.json
index 026cb3f9..b5d6491b 100644
--- a/src/wx/manifest.json
+++ b/src/wx/manifest.json
@@ -37,17 +37,17 @@
]
}
},
- "SieveSessionApi": {
- "schema": "api/sieve/SieveSessionApi.json",
+ "SieveSocketApi": {
+ "schema": "api/sieve/SieveSocketApi.json",
"parent": {
"scopes": [
"addon_parent"
],
- "script": "api/sieve/SieveSessionApi.js",
+ "script": "api/sieve/SieveSocketApi.js",
"paths": [
[
"sieve",
- "session"
+ "socket"
]
]
}
diff --git a/src/wx/modules/SieveRequire.jsm b/src/wx/modules/SieveRequire.jsm
deleted file mode 100644
index 17a96487..00000000
--- a/src/wx/modules/SieveRequire.jsm
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * The content of this file is licensed. You may obtain a copy of
- * the license at https://github.com/thsmi/sieve/ or request it via
- * email from the author.
- *
- * Do not remove or change this comment.
- *
- * The initial author of the code is:
- * Thomas Schmid <schmid-thomas@gmx.net>
- */
-
-/**
- * Implements a minimal CommonJS compatible implementation.
- *
- * It emulates the node.js CommonJS implementation as described here
- * http://fredkschott.com/post/2014/06/require-and-the-module-system/
- *
- * The sandbox loading code is based on the base-loader from mozilla's devtools.
- * See mozilla-central/source/devtools/shared/base-loader.js and
- * mozilla-central/source/devtools/shared/loader-plugin-raw.jsm for more details.
- */
-(function (exports) {
-
- "use strict";
-
- const JS_EXTENSION = ".js";
-
- /* global Components */
- /* global ChromeUtils */
-
- const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
-
- /**
- * Manages a simple CommonJS Module.
- * It implements a sandbox in which the modules code lives.
- */
- class Module {
-
- /**
- * Creates a new CommonJS Module.
- *
- * @param {Modules} modules
- * a reference to the parent module.
- * @param {string} uri
- * the uri to the modules source code.
- */
- constructor(modules, uri) {
-
- this.modules = modules;
- this.uri = uri;
-
- const systemPrincipal = Components
- .classes["@mozilla.org/systemprincipal;1"]
- .createInstance(Components.interfaces.nsIPrincipal);
-
- // create a new scope.
- this.sandbox = new Components.utils.Sandbox(systemPrincipal, {
- wantGlobalProperties: ["XMLHttpRequest", "TextEncoder", "TextDecoder", "atob", "btoa"],
- wantXrays: false,
- freshCompartment: false
- // invisibleToDebugger: true
- });
-
- // then push a reference to our require function to it
- this.sandbox.require = (url) => { return this.modules.require(url); };
- this.sandbox.Error = Error;
- // and create a dummy modules.exports
- this.sandbox.module = { exports: {} };
- // and a exports.
- this.sandbox.export = {};
- }
-
- /**
- * Invalidates the module and releases the sandbox.
- * This should immediately remove all references to any sandboxed element
- * including listeners.
- * So be care full when calling this.
- */
- invalidate() {
- if (this.sandbox === null)
- return;
-
- this.modules.log(`Nuking sandbox for ${this.uri} ...`);
-
- delete this.sandbox.require;
- delete this.sandbox.Error;
- delete this.sandbox.module;
- delete this.sandbox.export;
-
- try {
- Components.utils.nukeSandbox(this.sandbox);
- } catch (ex) {
- // If nuke failed we just ignore it as we can't do anything about it.
- }
-
- this.sandbox = null;
- this.modules = null;
- }
-
- /**
- * Returns the module's exports
- * In case the module is loaded and empty object is returned.
- *
- * @returns {object}
- * the modules exports
- */
- getExports() {
- return this.sandbox.module.exports;
- }
-
-
- /**
- * Reads from the given resource and returns the result.
- *
- * @param {string} uri
- * the uri to be read
- * @returns {string}
- * the uri's content as string.
- */
- readURI(uri) {
- this.modules.log(`Reading url ${uri}`);
-
- const stream = NetUtil.newChannel({
- uri: NetUtil.newURI(uri, "UTF-8"),
- loadUsingSystemPrincipal: true
- }).open();
-
- const count = stream.available();
- const data = NetUtil.readInputStreamToString(stream, count, {
- charset: "UTF-8"
- });
- stream.close();
-
- return data;
- }
-
- /**
- * Loads a commonjs module into the sandbox.
- */
- load() {
-
- if (this.sandbox === null)
- throw new Error("Module was invalidated and cannot be reused.");
-
- this.modules.log(`Loading CommonJS Module from ${this.uri}`);
-
- const script = this.readURI(this.uri);
-
- Components.utils.evalInSandbox(script, this.sandbox);
-
- if (!this.sandbox.module || !this.sandbox.module.exports)
- throw new Error(`Failed to load CommonJS Module ${this.uri}`);
-
- if (!Object.getOwnPropertyNames(this.sandbox.module.exports).length)
- throw new Error(`Module does not export anything ${this.uri}`);
- }
- }
-
- /**
- * Manages the CommonJS modules as well as their cache.
- */
- class Modules {
-
- /**
- * Creates a new instance.
- *
- * @param {string} base
- * an optional prefix.
- *
- * @param {Function} [logger]
- * a function which wraps a logger call.
- * in case omitted the logger will be disabled
- *
- */
- constructor(base, logger) {
-
- if (base.endsWith("/"))
- base = base.slice(0, -1);
-
- this.base = base;
-
- this.cache = new Map();
-
- this.logger = logger;
- }
-
- /**
- * Logs a message in case a logger was set in the constructor.
- *
- * @param {string} msg
- * the log message
- */
- log(msg) {
- if (typeof(this.logger) === "undefined" || this.logger === null)
- return;
-
- this.logger(msg);
- }
-
- /**
- * Resolves and normalizes the given uri. This means it converts
- * a relative uri into an absolute one.
- *
- * @param {string} uri
- * the uri to be normalized.
- *
- * @returns {string}
- * the normalize uri
- */
- resolve(uri) {
- this.log(`Resolving ${uri}`);
-
- if (!uri.endsWith(JS_EXTENSION))
- throw new Error(`Not a JavaScript file ${uri}`);
-
- if (uri.startsWith("./"))
- uri = this.base + uri.substring(".".length);
-
- return uri;
- }
-
- /**
- * Loads a CommonJS module into this cache.
- *
- * @param {string} uri
- * the uri of the module to be loaded.
- *
- * @returns {object}
- * the modules exports
- */
- require(uri) {
-
- uri = this.resolve(uri);
-
- this.log(`Loading CommonJS Module from ${uri}`);
-
- if (this.cache.has(uri)) {
- this.log(`Cache hit for ${uri}`);
- return this.cache.get(uri).getExports();
- }
-
- const module = new Module(this, uri);
- this.cache.set(uri, module);
-
- try {
- module.load();
- } catch (ex) {
- this.log(ex);
-
- this.cache.delete(uri);
- throw ex;
- }
-
- return module.getExports();
- }
-
- /**
- * Clears and invalidates all cached values.
- */
- invalidate() {
- for (const item of this.cache.values())
- item.invalidate();
-
- this.cache.clear();
- }
- }
-
- exports.Modules = Modules;
-
- // Expose as mozilla module...
- if (!exports.EXPORTED_SYMBOLS)
- exports.EXPORTED_SYMBOLS = [];
-
- exports.EXPORTED_SYMBOLS.push("Modules");
-
-})(this);