From 3c23f4aa7eeabff0382f7789cfb7ad0c3090615a Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 3 Oct 2013 14:24:48 -0300 Subject: Initial import. --- .gitignore | 2 + Makefile | 62 ++ README.rst | 61 ++ changes/feature_3542-create-thunderbird-extension | 2 + chrome.manifest | 7 + chrome/content/.gitignore | 1 + chrome/content/accountWizard/accountWizard.js | 720 +++++++++++++++++++++ chrome/content/accountWizard/accountWizard.xul | 223 +++++++ .../accountWizard/bitmaskAccountManagerOverlay.xul | 14 + .../accountWizard/bitmaskMessengerOverlay.xul | 14 + chrome/content/accountWizard/createInBackend.js | 330 ++++++++++ .../content/accountWizard/launchAccountWizard.js | 64 ++ .../preventCaching/bitmaskAmOfflineOverlay.js | 67 ++ .../preventCaching/bitmaskAmOfflineOverlay.xul | 8 + chrome/content/serverConfig.js | 28 + chrome/content/statusBar/statusBarOverlay.js | 94 +++ chrome/content/statusBar/statusBarOverlay.xul | 17 + chrome/content/util.js | 164 +++++ chrome/locale/en-US/accountWizard.dtd | 1 + chrome/locale/en-US/accountWizard.properties | 2 + .../locale/en-US/bitmaskAccountManagerOverlay.dtd | 2 + chrome/locale/en-US/bitmaskMessengerOverlay.dtd | 2 + chrome/locale/en-US/statusBar.properties | 3 + chrome/skin/accountWizard.css | 10 + install.rdf | 21 + 25 files changed, 1919 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.rst create mode 100644 changes/feature_3542-create-thunderbird-extension create mode 100644 chrome.manifest create mode 100644 chrome/content/.gitignore create mode 100644 chrome/content/accountWizard/accountWizard.js create mode 100644 chrome/content/accountWizard/accountWizard.xul create mode 100644 chrome/content/accountWizard/bitmaskAccountManagerOverlay.xul create mode 100644 chrome/content/accountWizard/bitmaskMessengerOverlay.xul create mode 100644 chrome/content/accountWizard/createInBackend.js create mode 100644 chrome/content/accountWizard/launchAccountWizard.js create mode 100644 chrome/content/preventCaching/bitmaskAmOfflineOverlay.js create mode 100644 chrome/content/preventCaching/bitmaskAmOfflineOverlay.xul create mode 100644 chrome/content/serverConfig.js create mode 100644 chrome/content/statusBar/statusBarOverlay.js create mode 100644 chrome/content/statusBar/statusBarOverlay.xul create mode 100644 chrome/content/util.js create mode 100644 chrome/locale/en-US/accountWizard.dtd create mode 100644 chrome/locale/en-US/accountWizard.properties create mode 100644 chrome/locale/en-US/bitmaskAccountManagerOverlay.dtd create mode 100644 chrome/locale/en-US/bitmaskMessengerOverlay.dtd create mode 100644 chrome/locale/en-US/statusBar.properties create mode 100644 chrome/skin/accountWizard.css create mode 100644 install.rdf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee34263 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +dot-thunderbird/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7d23b14 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +EXTNAME := bitmask-thunderbird +PREFIX := . +FILES_TO_PACKAGE := chrome,chrome.manifest,install.rdf +RSA_FILE := META-INF/zigbert.rsa + +# the following variables are updated automatically +COMMIT := $(shell git --no-pager log -1 --format=format:%h) +VERSION := $(shell grep \ $(PREFIX)/install.rdf | sed -e s/[^\>]\\\+\>// -e s/\<[^\>]\\\+\>//) +PKGNAME := $(EXTNAME)-$(VERSION)-$(COMMIT).xpi +TARGET := $(CURDIR)/build/$(PKGNAME) +TEMPDIR := $(shell mktemp -d -u) + +# make sure CERTDIR and CERTNAME are defined for signing +USAGE := "Usage: make CERTDIR= CERTNAME= DEFAULTKEY=" +ifeq ($(MAKECMDGOALS),signed) +ifndef CERTDIR + $(error $(USAGE)) +endif +ifndef CERTNAME + $(error $(USAGE)) +endif +ifndef DEFAULTKEY + $(error $(USAGE)) +endif +endif + +# make sure DEFAULTKEY was given to sign the calculated hashes +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),upload) +ifndef DEFAULTKEY + $(error "Usage: make DEFAULTKEY=") +endif +endif +endif + +# main rule +all: clean $(TARGET) + +# main target: .xpi file + +$(TARGET): clean + mkdir -p $(TEMPDIR) + cp -r $(PREFIX)/{$(FILES_TO_PACKAGE)} $(TEMPDIR)/ + (cd $(TEMPDIR) && zip -r $(TARGET) ./) + rm -rf $(TEMPDIR) + (cd build/ && sha512sum $(PKGNAME) > SHA512SUMS && gpg --default-key $(DEFAULTKEY) --sign SHA512SUMS) + +signed: clean + mkdir -p $(TEMPDIR) + cp -r $(PREFIX)/{$(FILES_TO_PACKAGE)} $(TEMPDIR)/ + signtool -d $(CERTDIR) -k $(CERTNAME) $(TEMPDIR)/ + (cd $(TEMPDIR) && zip $(TARGET) ./$(RSA_FILE) && zip -r -D $(TARGET) ./ -x ./$(RSA_FILE)) + rm -rf $(TEMPDIR) + (cd build/ && sha512sum $(PKGNAME) > SHA512SUMS && gpg --default-key $(DEFAULTKEY) --sign SHA512SUMS) + +clean: + rm -f $(TARGET) build/* + +upload: + scp build/* downloads.leap.se:~/public/thunderbird_extension/ + +.PHONY: all clean signed diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..be00062 --- /dev/null +++ b/README.rst @@ -0,0 +1,61 @@ +Bitmask Thunderbird Extension +============================= + +The Bitmask Thunderbird Extension provides: + +* A wizard for creating email accounts with IMAP/SMTP configuration targeted + to localhost and the default Bitmask client ports. There are different ways to + launch the wizard for configuring a Bitmask Account: + - Clicking on the statusbar notification. + - File -> New -> Bitmask account. + - Edit -> Account Settings... -> Account Actions -> Add Bitmask Accont. +* Caching prevention: accounts are created with caching turned off and the + UI is modified to prevent users from turning on caching for these + accounts. + +Development/testing +------------------- + +For development/testing purposes you can create a text file in Thunderbird's +extension directory whose contents point to the repository dir: + +* The file must be created in the `~/.thunderbird//extensions/` + directory. +* The file name must be `bitmask-thunderbird@leap.se`. +* The file contents must be the path for the `src/` directory inside this + repository. + +XPI Package +----------- + +To generate an unsigned XPI package, type the following inside the root of the +repository: + + make DEFAULTKEY= + +This command will: + +* Generate a `.xpi` file inside the `build/` directory. +* Create a `build/SHA512SUMS` file containing the sha512 hash of the `.xpi` file. +* Sign that file with the given `DEFAULTKEY` and create a `build/SHA512SUMS.gpg` file. + +You can now use the generated `.xpi` file install the package as a normal +Thunderbird extension. + +Signed XPI package +------------------ + +To generate a signed XPI package you must first have a certificate and then do +the following: + + make sign CERTDIR= CERTNAME= DEFAULTKEY= + +This command will: + +* Generate a signed `.xpi` file inside the `build/` directory using the + `CERTNAME` certificate contained in `CERTDIR`. +* Create a `build/SHA512SUMS` file containing the sha512 hash of the `.xpi` file. +* Sign that file with the given `DEFAULTKEY` and create a `build/SHA512SUMS.gpg` file. + +For more information about signed `.xpi` files, see: +https://developer.mozilla.org/en-US/docs/Signing_a_XPI diff --git a/changes/feature_3542-create-thunderbird-extension b/changes/feature_3542-create-thunderbird-extension new file mode 100644 index 0000000..1055474 --- /dev/null +++ b/changes/feature_3542-create-thunderbird-extension @@ -0,0 +1,2 @@ + o Create a Thunderbird extension with a wizard for creating LEAP's Bitmask + client-compliant accounts and with caching prevention. Closes #3542. diff --git a/chrome.manifest b/chrome.manifest new file mode 100644 index 0000000..bb14689 --- /dev/null +++ b/chrome.manifest @@ -0,0 +1,7 @@ +content bitmask chrome/content/ +skin bitmask classic/1.0 chrome/skin/ +locale bitmask en-US chrome/locale/en-US/ +overlay chrome://messenger/content/messenger.xul chrome://bitmask/content/statusBar/statusBarOverlay.xul +overlay chrome://messenger/content/messenger.xul chrome://bitmask/content/accountWizard/bitmaskMessengerOverlay.xul +overlay chrome://messenger/content/AccountManager.xul chrome://bitmask/content/accountWizard/bitmaskAccountManagerOverlay.xul +overlay chrome://messenger/content/am-offline.xul chrome://bitmask/content/preventCaching/bitmaskAmOfflineOverlay.xul diff --git a/chrome/content/.gitignore b/chrome/content/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/chrome/content/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/chrome/content/accountWizard/accountWizard.js b/chrome/content/accountWizard/accountWizard.js new file mode 100644 index 0000000..b2b533f --- /dev/null +++ b/chrome/content/accountWizard/accountWizard.js @@ -0,0 +1,720 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/hostnameUtils.jsm"); + +/** + * This is the dialog opened by menu File | New account | Mail... . + * + * It gets the user's realname, email address and password, + * and automatically configures the LEAP account from that. + * + * Steps: + * - User enters realname, email address and password + * - verify the setup, by trying to login to the configured servers + * - let user verify and maybe edit the server names and ports + * - If user clicks OK, create the account + */ + + +// from http://xyfer.blogspot.com/2005/01/javascript-regexp-email-validator.html +var emailRE = /^[-_a-z0-9\'+*$^&%=~!?{}]+(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*@(?:[-a-z0-9.]+\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?$/i; + +Cu.import("resource:///modules/gloda/log4moz.js"); +let gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard"); + +var gStringsBundle; +var gMessengerBundle; +var gBrandShortName; + +function e(elementID) +{ + return document.getElementById(elementID); +}; + +function _hide(id) +{ + e(id).hidden = true; +} + +function _show(id) +{ + e(id).hidden = false; +} + +function _enable(id) +{ + e(id).disabled = false; +} + +function _disable(id) +{ + e(id).disabled = true; +} + +function setText(id, value) +{ + var element = e(id); + assert(element, "setText() on non-existant element ID"); + + if (element.localName == "textbox" || element.localName == "label") { + element.value = value; + } else if (element.localName == "description") { + element.textContent = value; + } else { + throw new NotReached("XUL element type not supported"); + } +} + +function BitmaskAccountWizard() +{ + this._init(); +} +BitmaskAccountWizard.prototype = +{ + _init : function BitmaskAccountWizard__init() + { + gEmailWizardLogger.info("Initializing setup wizard"); + this._abortable = null; + }, + + onLoad : function() + { + /** + * this._currentConfig is the config we got either from the XML file or + * from guessing or from the user. Unless it's from the user, it contains + * placeholders like %EMAILLOCALPART% in username and other fields. + * + * The config here must retain these placeholders, to be able to + * adapt when the user enters a different realname, or password or + * email local part. (A change of the domain name will trigger a new + * detection anyways.) + * That means, before you actually use the config (e.g. to create an + * account or to show it to the user), you need to run replaceVariables(). + */ + this._currentConfig = null; + + let userFullname; + try { + let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo); + userFullname = userInfo.fullname; + } catch(e) { + // nsIUserInfo may not be implemented on all platforms, and name might + // not be avaialble even if it is. + } + + this._domain = ""; + this._email = ""; + this._realname = (userFullname) ? userFullname : ""; + e("realname").value = this._realname; + this._password = ""; + this._okCallback = null; + + if (window.arguments && window.arguments[0]) { + if (window.arguments[0].msgWindow) { + this._parentMsgWindow = window.arguments[0].msgWindow; + } + if (window.arguments[0].okCallback) { + this._okCallback = window.arguments[0].okCallback; + } + } + + gEmailWizardLogger.info("Email account setup dialog loaded."); + + gStringsBundle = e("strings"); + gMessengerBundle = e("bundle_messenger"); + gBrandShortName = e("bundle_brand").getString("brandShortName"); + + // admin-locked prefs hurray + if (!Services.prefs.getBoolPref("signon.rememberSignons")) { + let rememberPasswordE = e("remember_password"); + rememberPasswordE.checked = false; + rememberPasswordE.disabled = true; + } + + // First, unhide the main window areas, and store the width, + // so that we don't resize wildly when we unhide areas. + // switchToMode() will then hide the unneeded parts again. + // We will add some leeway of 10px, in case some of the s wrap, + // e.g. outgoing username != incoming username. + _show("status_area"); + _show("result_area"); + window.sizeToContent(); + e("mastervbox").setAttribute("style", + "min-width: " + document.width + "px; " + + "min-height: " + (document.height + 10) + "px;"); + + this.switchToMode("start"); + e("realname").focus(); + }, + + /** + * Changes the window configuration to the different modes we have. + * Shows/hides various window parts and buttons. + * @param modename {String-enum} + * "start" : Just the realname, email address, password fields + * "result" : We found a config and display it to the user. + * The user may create the account. + */ + switchToMode : function(modename) + { + if (modename == this._currentModename) { + return; + } + this._currentModename = modename; + gEmailWizardLogger.info("switching to UI mode " + modename) + + //_show("initialSettings"); always visible + //_show("cancel_button"); always visible + if (modename == "start") { + _hide("status_area"); + _hide("result_area"); + + _show("next_button"); + _disable("next_button"); // will be enabled by code + _hide("create_button"); + } else if (modename == "result") { + _show("status_area"); + _show("result_area"); + + _hide("next_button"); + _show("create_button"); + _enable("create_button"); + } else { + throw new NotReached("unknown mode"); + } + window.sizeToContent(); + }, + + /** + * Start from beginning with possibly new email address. + */ + onStartOver : function() + { + if (this._abortable) { + this.onStop(); + } + this.switchToMode("start"); + }, + + getConcreteConfig : function() + { + var result = this._currentConfig.copy(); + replaceVariables(result, this._realname, this._email, this._password); + result.rememberPassword = e("remember_password").checked && + !!this._password; + return result; + }, + + /* + * This checks if the email address is at least possibly valid, meaning it + * has an '@' before the last char. + */ + validateEmailMinimally : function(emailAddr) + { + let atPos = emailAddr.lastIndexOf("@"); + return atPos > 0 && atPos + 1 < emailAddr.length; + }, + + /* + * This checks if the email address is syntactically valid, + * as far as we can determine. We try hard to make full checks. + * + * OTOH, we have a very small chance of false negatives, + * because the RFC822 address spec is insanely complicated, + * but rarely needed, so when this here fails, we show an error message, + * but don't stop the user from continuing. + * In contrast, if validateEmailMinimally() fails, we stop the user. + */ + validateEmail : function(emailAddr) + { + return emailRE.test(emailAddr); + }, + + /** + * onInputEmail and onInputRealname are called on input = keypresses, and + * enable/disable the next button based on whether there's a semi-proper + * e-mail address and non-blank realname to start with. + * + * A change to the email address also automatically restarts the + * whole process. + */ + onInputEmail : function() + { + this._email = e("email").value; + this.onStartOver(); + this.checkStartDone(); + }, + onInputRealname : function() + { + this._realname = e("realname").value; + this.checkStartDone(); + }, + + onInputPassword : function() + { + this._password = e("password").value; + }, + + /** + * This does very little other than to check that a name was entered at all + * Since this is such an insignificant test we should be using a very light + * or even jovial warning. + */ + onBlurRealname : function() + { + let realnameEl = e("realname"); + if (this._realname) { + this.clearError("nameerror"); + _show("nametext"); + realnameEl.removeAttribute("error"); + // bug 638790: don't show realname error until user enter an email address + } else if (this.validateEmailMinimally(this._email)) { + _hide("nametext"); + this.setError("nameerror", "please_enter_name"); + realnameEl.setAttribute("error", "true"); + } + }, + + /** + * This check is only done as an informative warning. + * We don't want to block the person, if they've entered an email address + * that doesn't conform to our regex. + */ + onBlurEmail : function() + { + if (!this._email) { + return; + } + var emailEl = e("email"); + if (this.validateEmail(this._email)) { + this.clearError("emailerror"); + emailEl.removeAttribute("error"); + this.onBlurRealname(); + } else { + this.setError("emailerror", "double_check_email"); + emailEl.setAttribute("error", "true"); + } + }, + + /** + * If the user just tabbed through the password input without entering + * anything, set the type back to text so we don't wind up showing the + * emptytext as bullet characters. + */ + onBlurPassword : function() + { + if (!this._password) { + e("password").type = "text"; + } + }, + + /** + * @see onBlurPassword() + */ + onFocusPassword : function() + { + e("password").type = "password"; + }, + + /** + * Check whether the user entered the minimum of information + * needed to leave the "start" mode (entering of name, email, pw) + * and is allowed to proceed to detection step. + */ + checkStartDone : function() + { + if (this.validateEmailMinimally(this._email) && + this._realname) { + this._domain = this._email.split("@")[1].toLowerCase(); + _enable("next_button"); + } else { + _disable("next_button"); + } + }, + + /** + * When the [Continue] button is clicked, we move from the initial account + * information stage to using that information to configure account details. + */ + onNext : function() + { + this.fillConfig(this._domain, this._email); + }, + + fillConfig : function(domain, email) + { + var config = new AccountConfig(); + this._prefillConfig(config); + config.source = AccountConfig.kSourceXML; // TODO: change kSource type? + config.incoming.hostname = IMAP_HOST; + config.incoming.username = config.identity.emailAddress; + config.outgoing.username = config.identity.emailAddress; + config.incoming.type = "imap"; + config.incoming.port = IMAP_PORT; + // Values for socketType are: + // 1 - plain + // 2 - SSL / TLS + // 3 - STARTTLS + config.incoming.socketType = 1; + config.incoming.auth = Ci.nsMsgAuthMethod.passwordCleartext; + config.outgoing.hostname = SMTP_HOST; + config.outgoing.socketType = 1; + config.outgoing.port = SMTP_PORT; + config.outgoing.auth = Ci.nsMsgAuthMethod.passwordCleartext; + this.foundConfig(config); + }, + + /** + * When findConfig() was successful, it calls this. + * This displays the config to the user. + */ + foundConfig : function(config) + { + gEmailWizardLogger.info("foundConfig()"); + assert(config instanceof AccountConfig, + "BUG: Arg 'config' needs to be an AccountConfig object"); + + this._haveValidConfigForDomain = this._email.split("@")[1];; + + if (!this._realname || !this._email) { + return; + } + this._foundConfig2(config); + }, + + // Continuation of foundConfig2() after custom fields. + _foundConfig2 : function(config) + { + this.displayConfigResult(config); + }, + + + + + /////////////////////////////////////////////////////////////////// + // status area + + startSpinner : function(actionStrName) + { + e("status_area").setAttribute("status", "loading"); + gEmailWizardLogger.warn("spinner start " + actionStrName); + this._showStatusTitle(actionStrName); + }, + + stopSpinner : function(actionStrName) + { + e("status_area").setAttribute("status", "result"); + _hide("stop_button"); + this._showStatusTitle(actionStrName); + gEmailWizardLogger.warn("all spinner stop " + actionStrName); + }, + + showErrorStatus : function(actionStrName) + { + e("status_area").setAttribute("status", "error"); + gEmailWizardLogger.warn("status error " + actionStrName); + this._showStatusTitle(actionStrName); + }, + + _showStatusTitle : function(msgName) + { + let msg = " "; // assure height. Do via min-height in CSS, for 2 lines? + try { + if (msgName) { + msg = gStringsBundle.getFormattedString(msgName, [gBrandShortName]); + } + } catch(ex) { + gEmailWizardLogger.error("missing string for " + msgName); + msg = msgName + " (missing string in translation!)"; + } + + e("status_msg").textContent = msg; + gEmailWizardLogger.info("status msg: " + msg); + }, + + + + ///////////////////////////////////////////////////////////////// + // Result area + + /** + * Displays a (probed) config to the user, + * in the result config details area. + * + * @param config {AccountConfig} The config to present to user + */ + displayConfigResult : function(config) + { + assert(config instanceof AccountConfig); + this._currentConfig = config; + var configFilledIn = this.getConcreteConfig(); + + var unknownString = gStringsBundle.getString("resultUnknown"); + + function _makeHostDisplayString(server, stringName) + { + let type = gStringsBundle.getString(sanitize.translate(server.type, + { imap : "resultIMAP", pop3 : "resultPOP3", smtp : "resultSMTP" }), + unknownString); + let host = server.hostname + + (isStandardPort(server.port) ? "" : ":" + server.port); + let ssl = gStringsBundle.getString(sanitize.translate(server.socketType, + { 1 : "resultNoEncryption", 2 : "resultSSL", 3 : "resultSTARTTLS" }), + unknownString); + let certStatus = gStringsBundle.getString(server.badCert ? + "resultSSLCertWeak" : "resultSSLCertOK"); + return gStringsBundle.getFormattedString(stringName, + [ type, host, ssl, certStatus ]); + }; + + var incomingResult = unknownString; + if (configFilledIn.incoming.hostname) { + incomingResult = _makeHostDisplayString(configFilledIn.incoming, + "resultIncoming"); + } + + var outgoingResult = unknownString; + if (!config.outgoing.existingServerKey) { + if (configFilledIn.outgoing.hostname) { + outgoingResult = _makeHostDisplayString(configFilledIn.outgoing, + "resultOutgoing"); + } + } else { + outgoingResult = gStringsBundle.getString("resultOutgoingExisting"); + } + + var usernameResult; + if (configFilledIn.incoming.username == configFilledIn.outgoing.username) { + usernameResult = gStringsBundle.getFormattedString("resultUsernameBoth", + [ configFilledIn.incoming.username || unknownString ]); + } else { + usernameResult = gStringsBundle.getFormattedString( + "resultUsernameDifferent", + [ configFilledIn.incoming.username || unknownString, + configFilledIn.outgoing.username || unknownString ]); + } + + setText("result-incoming", incomingResult); + setText("result-outgoing", outgoingResult); + setText("result-username", usernameResult); + + gEmailWizardLogger.info(debugObject(config, "config")); + // IMAP / POP dropdown + var lookForAltType = + config.incoming.type == "imap" ? "pop3" : "imap"; + var alternative = null; + for (let i = 0; i < config.incomingAlternatives.length; i++) { + let alt = config.incomingAlternatives[i]; + if (alt.type == lookForAltType) { + alternative = alt; + break; + } + } + if (alternative) { + _show("result_imappop"); + e("result_select_" + alternative.type).configIncoming = alternative; + e("result_select_" + config.incoming.type).configIncoming = + config.incoming; + e("result_imappop").value = + config.incoming.type == "imap" ? 1 : 2; + } else { + _hide("result_imappop"); + } + + this.switchToMode("result"); + }, + + onInputUsername : function() + { + this.onChangedManualEdit(); + }, + onInputHostname : function() + { + this.onChangedManualEdit(); + }, + + + + ///////////////////////////////////////////////////////////////// + // UI helper functions + + _prefillConfig : function(initialConfig) + { + var emailsplit = this._email.split("@"); + assert(emailsplit.length > 1); + var emaillocal = sanitize.nonemptystring(emailsplit[0]); + initialConfig.incoming.username = emaillocal; + initialConfig.outgoing.username = SMTP_USER; + return initialConfig; + }, + + clearError : function(which) + { + _hide(which); + _hide(which + "icon"); + e(which).textContent = ""; + }, + + setError : function(which, msg_name) + { + try { + _show(which); + _show(which + "icon"); + e(which).textContent = gStringsBundle.getString(msg_name); + window.sizeToContent(); + } catch (ex) { alertPrompt("missing error string", msg_name); } + }, + + + + ///////////////////////////////////////////////////////////////// + // Finish & dialog close functions + + onKeyDown : function(event) + { + let key = event.keyCode; + if (key == 27) { // Escape key + this.onCancel(); + return true; + } + if (key == 13) { // OK key + let buttons = [ + { id: "next_button", action: makeCallback(this, this.onNext) }, + { id: "create_button", action: makeCallback(this, this.onCreate) }, + ]; + for each (let button in buttons) { + button.e = e(button.id); + if (button.e.hidden || button.e.disabled) { + continue; + } + button.action(); + return true; + } + } + return false; + }, + + onCancel : function() + { + window.close(); + // The window onclose handler will call onWizardShutdown for us. + }, + + onWizardShutdown : function() + { + if (this._abortable) { + this._abortable.cancel(new UserCancelledException()); + } + + if (this._okCallback) { + this._okCallback(); + } + gEmailWizardLogger.info("Shutting down email config dialog"); + }, + + + onCreate : function() + { + try { + gEmailWizardLogger.info("Create button clicked"); + + this.validateAndFinish(); + } catch (ex) { + gEmailWizardLogger.error("Error creating account. ex=" + ex + + ", stack=" + ex.stack); + alertPrompt(gStringsBundle.getString("error_creating_account"), ex); + } + }, + + // called by onCreate() + validateAndFinish : function() + { + var configFilledIn = this.getConcreteConfig(); + + if (checkIncomingServerAlreadyExists(configFilledIn)) { + alertPrompt(gStringsBundle.getString("error_creating_account"), + gStringsBundle.getString("incoming_server_exists")); + return; + } + + if (configFilledIn.outgoing.addThisServer) { + let existingServer = checkOutgoingServerAlreadyExists(configFilledIn); + if (existingServer) { + configFilledIn.outgoing.addThisServer = false; + configFilledIn.outgoing.existingServerKey = existingServer.key; + } + } + + // TODO use a UI mode (switchToMode()) for verfication, too. + // But we need to go back to the previous mode, because we might be in + // "result" or "manual-edit-complete" mode. + _disable("create_button"); + // no stop button: backend has no ability to stop :-( + var self = this; + this.startSpinner("checking_password"); + // logic function defined in verifyConfig.js + verifyConfig( + configFilledIn, + // guess login config? + configFilledIn.source != AccountConfig.kSourceXML, + // TODO Instead, the following line would be correct, but I cannot use it, + // because some other code doesn't adhere to the expectations/specs. + // Find out what it was and fix it. + //concreteConfig.source == AccountConfig.kSourceGuess, + this._parentMsgWindow, + function(successfulConfig) // success + { + self.stopSpinner(successfulConfig.incoming.password ? + "password_ok" : null); + + // the auth might have changed, so we + // should back-port it to the current config. + self._currentConfig.incoming.auth = successfulConfig.incoming.auth; + self._currentConfig.outgoing.auth = successfulConfig.outgoing.auth; + self._currentConfig.incoming.username = successfulConfig.incoming.username; + self._currentConfig.outgoing.username = successfulConfig.outgoing.username; + self.finish(); + }, + function(e) // failed + { + self.showErrorStatus("config_unverifiable"); + // TODO bug 555448: wrong error msg, there may be a 1000 other + // reasons why this failed, and this is misleading users. + self.setError("passworderror", "user_pass_invalid"); + // TODO use switchToMode(), see above + // give user something to proceed after fixing + _enable("create_button"); + }); + }, + + finish : function() + { + gEmailWizardLogger.info("creating account in backend"); + createAccountInBackend(this.getConcreteConfig()); + window.close(); + }, +}; + +var gBitmaskAccountWizard = new BitmaskAccountWizard(); + +var _gStandardPorts = {}; +_gStandardPorts["imap"] = [ 143, 993 ]; +_gStandardPorts["pop3"] = [ 110, 995 ]; +_gStandardPorts["smtp"] = [ 587, 25, 465 ]; // order matters +var _gAllStandardPorts = _gStandardPorts["smtp"] + .concat(_gStandardPorts["imap"]).concat(_gStandardPorts["pop3"]); + +function isStandardPort(port) +{ + return _gAllStandardPorts.indexOf(port) != -1; +} + +function getStandardPorts(protocolType) +{ + return _gStandardPorts[protocolType]; +} diff --git a/chrome/content/accountWizard/accountWizard.xul b/chrome/content/accountWizard/accountWizard.xul new file mode 100644 index 0000000..0d42c0d --- /dev/null +++ b/chrome/content/accountWizard/accountWizard.xul @@ -0,0 +1,223 @@ + + + + + + + + + %brandDTD; + + %acDTD; + + %awDTD; +]> + + + + + + + + + +