summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2013-10-03 14:24:48 -0300
committerdrebs <drebs@leap.se>2013-10-04 14:25:08 -0300
commit3c23f4aa7eeabff0382f7789cfb7ad0c3090615a (patch)
treeb7970f3d7fef0ceabb4293f135c97f257fe6baef
Initial import.
-rw-r--r--.gitignore2
-rw-r--r--Makefile62
-rw-r--r--README.rst61
-rw-r--r--changes/feature_3542-create-thunderbird-extension2
-rw-r--r--chrome.manifest7
-rw-r--r--chrome/content/.gitignore1
-rw-r--r--chrome/content/accountWizard/accountWizard.js720
-rw-r--r--chrome/content/accountWizard/accountWizard.xul223
-rw-r--r--chrome/content/accountWizard/bitmaskAccountManagerOverlay.xul14
-rw-r--r--chrome/content/accountWizard/bitmaskMessengerOverlay.xul14
-rw-r--r--chrome/content/accountWizard/createInBackend.js330
-rw-r--r--chrome/content/accountWizard/launchAccountWizard.js64
-rw-r--r--chrome/content/preventCaching/bitmaskAmOfflineOverlay.js67
-rw-r--r--chrome/content/preventCaching/bitmaskAmOfflineOverlay.xul8
-rw-r--r--chrome/content/serverConfig.js28
-rw-r--r--chrome/content/statusBar/statusBarOverlay.js94
-rw-r--r--chrome/content/statusBar/statusBarOverlay.xul17
-rw-r--r--chrome/content/util.js164
-rw-r--r--chrome/locale/en-US/accountWizard.dtd1
-rw-r--r--chrome/locale/en-US/accountWizard.properties2
-rw-r--r--chrome/locale/en-US/bitmaskAccountManagerOverlay.dtd2
-rw-r--r--chrome/locale/en-US/bitmaskMessengerOverlay.dtd2
-rw-r--r--chrome/locale/en-US/statusBar.properties3
-rw-r--r--chrome/skin/accountWizard.css10
-rw-r--r--install.rdf21
25 files changed, 1919 insertions, 0 deletions
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 \<em:version\> $(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=<certificate directory> CERTNAME=<certificate name> DEFAULTKEY=<key id>"
+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=<key id>")
+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/<profile>/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=<key id>
+
+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=<path to cert dir> CERTNAME=<cert name> DEFAULTKEY=<key id>
+
+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 <description>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 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://bitmask/skin/accountWizard.css"
+ type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % acDTD SYSTEM "chrome://messenger/locale/accountCreation.dtd">
+ %acDTD;
+ <!ENTITY % awDTD SYSTEM "chrome://bitmask/locale/accountWizard.dtd">
+ %awDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="autoconfigWizard"
+ windowtype="mail:autoconfig"
+ title="&autoconfigWizard.title;"
+ onload="gBitmaskAccountWizard.onLoad();"
+ onkeypress="gBitmaskAccountWizard.onKeyDown(event)"
+ onclose="gBitmaskAccountWizard.onWizardShutdown();"
+ onunload="gBitmaskAccountWizard.onWizardShutdown();"
+ >
+
+ <stringbundleset>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="strings"
+ src="chrome://messenger/locale/accountCreation.properties"/>
+ <stringbundle id="utilstrings"
+ src="chrome://messenger/locale/accountCreationUtil.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/serverConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/util.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/accountConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/accountWizard/accountWizard.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/sanitizeDatatypes.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/verifyConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/accountWizard/createInBackend.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountUtils.js" />
+
+ <keyset id="mailKeys">
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+ <tooltip id="optional-password">
+ <description>&password.text;</description>
+ </tooltip>
+
+ <spacer id="fullwidth"/>
+
+ <vbox id="mastervbox" class="mastervbox" flex="1">
+ <description
+ id="BitmaskAccountWizardTitle"
+ class="bitmaskAccountWizardTitle">&bitmask.accountWizard.title;</description>
+ <spacer flex="1" />
+ <grid id="initialSettings">
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label accesskey="&name.accesskey;"
+ class="autoconfigLabel"
+ value="&name.label;"
+ control="realname"/>
+ <textbox id="realname"
+ class="padded"
+ placeholder="&name.placeholder;"
+ oninput="gBitmaskAccountWizard.onInputRealname();"
+ onblur="gBitmaskAccountWizard.onBlurRealname();"/>
+ <hbox>
+ <description id="nametext" class="initialDesc">&name.text;</description>
+ <image id="nameerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="nameerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label accesskey="&email.accesskey;"
+ class="autoconfigLabel"
+ value="&email.label;"
+ control="email"/>
+ <textbox id="email"
+ class="padded uri-element"
+ placeholder="&email.placeholder;"
+ oninput="gBitmaskAccountWizard.onInputEmail();"
+ onblur="gBitmaskAccountWizard.onBlurEmail();"/>
+ <hbox>
+ <image id="emailerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="emailerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <!-- this starts out as text so the emptytext shows, but then
+ changes to type=password once it's not empty -->
+ <label accesskey="&password.accesskey;"
+ class="autoconfigLabel"
+ value="&password.label;"
+ control="password"
+ tooltip="optional-password"/>
+ <textbox id="password"
+ class="padded"
+ placeholder="&password.placeholder;"
+ type="text"
+ oninput="gBitmaskAccountWizard.onInputPassword();"
+ onfocus="gBitmaskAccountWizard.onFocusPassword();"
+ onblur="gBitmaskAccountWizard.onBlurPassword();"/>
+ <hbox>
+ <image id="passworderroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="passworderror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center" pack="start">
+ <label class="autoconfigLabel"/>
+ <checkbox id="remember_password"
+ label="&rememberPassword.label;"
+ accesskey="&rememberPassword.accesskey;"
+ checked="true"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1" />
+
+ <hbox id="status_area" flex="1">
+ <vbox id="status_img_before" pack="start"/>
+ <description id="status_msg">&#160;</description>
+ <!-- Include 160 = nbsp, to make the element occupy the
+ full height, for at least one line. With a normal space,
+ it does not have sufficient height. -->
+ <vbox id="status_img_after" pack="start"/>
+ </hbox>
+
+ <groupbox id="result_area" hidden="true">
+ <radiogroup id="result_imappop" orient="horizontal">
+ <radio id="result_select_imap" label="&imapLong.label;" value="1"
+ oncommand="gBitmaskAccountWizard.onResultIMAPOrPOP3();"/>
+ <radio id="result_select_pop3" label="&pop3Long.label;" value="2"
+ oncommand="gBitmaskAccountWizard.onResultIMAPOrPOP3();"/>
+ </radiogroup>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label class="textbox-label" value="&incoming.label;"
+ control="result-incoming"/>
+ <textbox id="result-incoming" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&outgoing.label;"
+ control="result-outgoing"/>
+ <textbox id="result-outgoing" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&username.label;"
+ control="result-username"/>
+ <textbox id="result-username" disabled="true" flex="1"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <spacer flex="1" />
+ <hbox id="buttons_area">
+ <spacer flex="1"/>
+ <hbox id="right_buttons_area" align="center" pack="end">
+ <button id="stop_button"
+ label="&stop.label;"
+ accesskey="&stop.accesskey;"
+ hidden="true"
+ oncommand="gBitmaskAccountWizard.onStop();"/>
+ <button id="cancel_button"
+ label="&cancel.label;"
+ accesskey="&cancel.accesskey;"
+ oncommand="gBitmaskAccountWizard.onCancel();"/>
+ <button id="half-manual-test_button"
+ label="&half-manual-test.label;"
+ accesskey="&half-manual-test.accesskey;"
+ hidden="true"
+ oncommand="gBitmaskAccountWizard.onHalfManualTest();"/>
+ <button id="next_button"
+ label="&continue.label;"
+ accesskey="&continue.accesskey;"
+ hidden="false"
+ oncommand="gBitmaskAccountWizard.onNext();"/>
+ <button id="create_button"
+ label="&doneAccount.label;"
+ accesskey="&doneAccount.accesskey;"
+ class="important-button"
+ hidden="true"
+ oncommand="gBitmaskAccountWizard.onCreate();"/>
+ </hbox>
+ </hbox>
+ </vbox>
+
+
+</window>
diff --git a/chrome/content/accountWizard/bitmaskAccountManagerOverlay.xul b/chrome/content/accountWizard/bitmaskAccountManagerOverlay.xul
new file mode 100644
index 0000000..ba68b5f
--- /dev/null
+++ b/chrome/content/accountWizard/bitmaskAccountManagerOverlay.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://bitmask/locale/bitmaskAccountManagerOverlay.dtd">
+<overlay id="bitmaskAccountManagerOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://bitmask/content/accountWizard/launchAccountWizard.js"/>
+ <menupopup id="accountActionsDropdown">
+ <menuitem id="accountActionsAddBitmaskAccount"
+ label="&addBitmaskAccountButton.label;"
+ accesskey="&addBitmaskAccountButton.accesskey;"
+ insertbefore="accountActionsDropdownSep1"
+ oncommand="launchAccountWizard();" />
+ </menupopup>
+</overlay>
diff --git a/chrome/content/accountWizard/bitmaskMessengerOverlay.xul b/chrome/content/accountWizard/bitmaskMessengerOverlay.xul
new file mode 100644
index 0000000..a767022
--- /dev/null
+++ b/chrome/content/accountWizard/bitmaskMessengerOverlay.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://bitmask/locale/bitmaskMessengerOverlay.dtd">
+<overlay id="bitmaskMessengerOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://bitmask/content/accountWizard/launchAccountWizard.js"/>
+ <menupopup id="menu_NewPopup">
+ <menuitem id="newBitmaskAccountMenuItem"
+ label="&newBitmaskAccountCmd.label;"
+ accesskey="&newBitmaskAccountCmd.accesskey;"
+ oncommand="launchAccountWizard();"
+ insertbefore="newPopupMenuSeparator" />
+ </menupopup>
+</overlay>
diff --git a/chrome/content/accountWizard/createInBackend.js b/chrome/content/accountWizard/createInBackend.js
new file mode 100644
index 0000000..f15b882
--- /dev/null
+++ b/chrome/content/accountWizard/createInBackend.js
@@ -0,0 +1,330 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Takes an |AccountConfig| JS object and creates that account in the
+ * Thunderbird backend (which also writes it to prefs).
+ *
+ * @param config {AccountConfig} The account to create
+ *
+ * @return - the account created.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+function createAccountInBackend(config)
+{
+ // incoming server
+ let inServer = MailServices.accounts.createIncomingServer(
+ config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type, ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.authMethod = config.incoming.auth;
+ inServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(inServer, config.incoming.password);
+
+ // SSL
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL / TLS
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+ //inServer.prettyName = config.displayName;
+ inServer.prettyName = config.identity.emailAddress;
+
+ inServer.doBiff = true;
+ inServer.biffMinutes = config.incoming.checkInterval;
+ const loginAtStartupPrefTemplate =
+ "mail.server.%serverkey%.login_at_startup";
+ var loginAtStartupPref =
+ loginAtStartupPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(loginAtStartupPref,
+ config.incoming.loginAtStartup);
+ if (config.incoming.type == "pop3")
+ {
+ const leaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.leave_on_server";
+ const daysToLeaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.num_days_to_leave_on_server";
+ const deleteFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_mail_left_on_server";
+ const deleteByAgeFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_by_age_from_server";
+ const downloadOnBiffPrefTemplate =
+ "mail.server.%serverkey%.download_on_biff";
+ var leaveOnServerPref =
+ leaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var ageFromServerPref =
+ deleteByAgeFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var daysToLeaveOnServerPref =
+ daysToLeaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var deleteFromServerPref =
+ deleteFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ let downloadOnBiffPref =
+ downloadOnBiffPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(leaveOnServerPref,
+ config.incoming.leaveMessagesOnServer);
+ Services.prefs.setIntPref(daysToLeaveOnServerPref,
+ config.incoming.daysToLeaveMessagesOnServer);
+ Services.prefs.setBoolPref(deleteFromServerPref,
+ config.incoming.deleteOnServerWhenLocalDelete);
+ Services.prefs.setBoolPref(ageFromServerPref,
+ config.incoming.deleteByAgeFromServer);
+ Services.prefs.setBoolPref(downloadOnBiffPref,
+ config.incoming.downloadOnBiff);
+ }
+ inServer.valid = true;
+
+ let username = config.outgoing.auth > 1 ? config.outgoing.username : null;
+ let outServer = MailServices.smtp.findServer(username, config.outgoing.hostname);
+ assert(config.outgoing.addThisServer ||
+ config.outgoing.useGlobalPreferredServer ||
+ config.outgoing.existingServerKey,
+ "No SMTP server: inconsistent flags");
+
+ if (config.outgoing.addThisServer && !outServer)
+ {
+ outServer = MailServices.smtp.createServer();
+ outServer.hostname = config.outgoing.hostname;
+ outServer.port = config.outgoing.port;
+ outServer.authMethod = config.outgoing.auth;
+ if (config.outgoing.auth > 1)
+ {
+ outServer.username = username;
+ outServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(outServer, config.incoming.password);
+ }
+
+ if (config.outgoing.socketType == 1) // no SSL
+ outServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.outgoing.socketType == 2) // SSL / TLS
+ outServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.outgoing.socketType == 3) // STARTTLS
+ outServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ // API problem: <http://mxr.mozilla.org/seamonkey/source/mailnews/compose/public/nsISmtpServer.idl#93>
+ outServer.description = config.displayName;
+ if (config.password)
+ outServer.password = config.outgoing.password;
+
+ // If this is the first SMTP server, set it as default
+ if (!MailServices.smtp.defaultServer ||
+ !MailServices.smtp.defaultServer.hostname)
+ MailServices.smtp.defaultServer = outServer;
+ }
+
+ // identity
+ // TODO accounts without identity?
+ let identity = MailServices.accounts.createIdentity();
+ identity.fullName = config.identity.realname;
+ identity.email = config.identity.emailAddress;
+
+ // for new accounts, default to replies being positioned above the quote
+ // if a default account is defined already, take its settings instead
+ if (config.incoming.type == "imap" || config.incoming.type == "pop3")
+ {
+ identity.replyOnTop = 1;
+ // identity.sigBottom = false; // don't set this until Bug 218346 is fixed
+
+ if (MailServices.accounts.accounts.length &&
+ MailServices.accounts.defaultAccount)
+ {
+ let defAccount = MailServices.accounts.defaultAccount;
+ let defIdentity = defAccount.defaultIdentity;
+ if (defAccount.incomingServer.canBeDefaultServer &&
+ defIdentity && defIdentity.valid)
+ {
+ identity.replyOnTop = defIdentity.replyOnTop;
+ identity.sigBottom = defIdentity.sigBottom;
+ }
+ }
+ }
+
+ // due to accepted conventions, news accounts should default to plain text
+ if (config.incoming.type == "nntp")
+ identity.composeHtml = false;
+
+ identity.valid = true;
+
+ if (config.outgoing.existingServerKey)
+ identity.smtpServerKey = config.outgoing.existingServerKey;
+ else if (!config.outgoing.useGlobalPreferredServer)
+ identity.smtpServerKey = outServer.key;
+
+ // account and hook up
+ // Note: Setting incomingServer will cause the AccountManager to refresh
+ // itself, which could be a problem if we came from it and we haven't set
+ // the identity (see bug 521955), so make sure everything else on the
+ // account is set up before you set the incomingServer.
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+ account.incomingServer = inServer;
+ if (inServer.canBeDefaultServer && (!MailServices.accounts.defaultAccount ||
+ !MailServices.accounts.defaultAccount
+ .incomingServer.canBeDefaultServer))
+ MailServices.accounts.defaultAccount = account;
+
+ verifyLocalFoldersAccount(MailServices.accounts);
+ setFolders(identity, inServer);
+ doNotCache(inServer);
+
+ // save
+ MailServices.accounts.saveAccountInfo();
+ try {
+ Services.prefs.savePrefFile(null);
+ } catch (ex) {
+ ddump("Could not write out prefs: " + ex);
+ }
+ return account;
+}
+
+function setFolders(identity, server)
+{
+ // TODO: support for local folders for global inbox (or use smart search
+ // folder instead)
+
+ var baseURI = server.serverURI + "/";
+
+ // Names will be localized in UI, not in folder names on server/disk
+ // TODO allow to override these names in the XML config file,
+ // in case e.g. Google or AOL use different names?
+ // Workaround: Let user fix it :)
+ var fccName = "Sent";
+ var draftName = "Drafts";
+ var templatesName = "Templates";
+
+ identity.draftFolder = baseURI + draftName;
+ identity.stationeryFolder = baseURI + templatesName;
+ identity.fccFolder = baseURI + fccName;
+
+ identity.fccFolderPickerMode = 0;
+ identity.draftsFolderPickerMode = 0;
+ identity.tmplFolderPickerMode = 0;
+}
+
+function doNotCache(inServer)
+{
+ // make sure account is marked to not download
+ inServerQI = inServer.QueryInterface(
+ Components.interfaces.nsIImapIncomingServer);
+ inServerQI.offlineDownload = false;
+ // and remove offline flag from all folders
+ var allFolders = inServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+}
+
+function rememberPassword(server, password)
+{
+ if (server instanceof Components.interfaces.nsIMsgIncomingServer)
+ var passwordURI = server.localStoreType + "://" + server.hostName;
+ else if (server instanceof Components.interfaces.nsISmtpServer)
+ var passwordURI = "smtp://" + server.hostname;
+ else
+ throw new NotReached("Server type not supported");
+
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ login.init(passwordURI, null, passwordURI, server.username, password, "", "");
+ try {
+ Services.logins.addLogin(login);
+ } catch (e if e.message.contains("This login already exists")) {
+ // TODO modify
+ }
+}
+
+/**
+ * Check whether the user's setup already has an incoming server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ * (We also check the email address as username.)
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsIMsgIncomingServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkIncomingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let incoming = config.incoming;
+ let existing = MailServices.accounts.findRealServer(incoming.username,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+
+ // if username does not have an '@', also check the e-mail
+ // address form of the name.
+ if (!existing && !incoming.username.contains("@"))
+ existing = MailServices.accounts.findRealServer(config.identity.emailAddress,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+ return existing;
+};
+
+/**
+ * Check whether the user's setup already has an outgoing server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsISmtpServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkOutgoingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let smtpServers = MailServices.smtp.servers;
+ while (smtpServers.hasMoreElements())
+ {
+ let existingServer = smtpServers.getNext()
+ .QueryInterface(Ci.nsISmtpServer);
+ // TODO check username with full email address, too, like for incoming
+ if (existingServer.hostname == config.outgoing.hostname &&
+ existingServer.port == config.outgoing.port &&
+ existingServer.username == config.outgoing.username)
+ return existingServer;
+ }
+ return null;
+};
+
+/**
+ * Check if there already is a "Local Folders". If not, create it.
+ * Copied from AccountWizard.js with minor updates.
+ */
+function verifyLocalFoldersAccount(am)
+{
+ let localMailServer;
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ localMailServer = null;
+ }
+
+ try {
+ if (!localMailServer)
+ {
+ // creates a copy of the identity you pass in
+ am.createLocalMailAccount();
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ ddump("Error! we should have found the local mail server " +
+ "after we created it.");
+ }
+ }
+ }
+ catch (ex) { ddump("Error in verifyLocalFoldersAccount " + ex); }
+}
diff --git a/chrome/content/accountWizard/launchAccountWizard.js b/chrome/content/accountWizard/launchAccountWizard.js
new file mode 100644
index 0000000..bda743f
--- /dev/null
+++ b/chrome/content/accountWizard/launchAccountWizard.js
@@ -0,0 +1,64 @@
+/**
+ * statusBar.js
+ * Copyright (C) 2013 LEAP
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along
+ */
+
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+
+/*****************************************************************************
+ * Wizard functions.
+ ****************************************************************************/
+
+/**
+ * Launch the wizard to configure a new LEAP account.
+ */
+function launchAccountWizard()
+{
+ msgNewBitmaskMailAccount(MailServices.mailSession.topmostMsgWindow, null, null);
+}
+
+/**
+ * Open the New Mail Account Wizard, or focus it if it's already open.
+ *
+ * @param msgWindow a msgWindow for us to use to verify the accounts.
+ * @param okCallback an optional callback for us to call back to if
+ * everything's okay.
+ * @param extraData an optional param that allows us to pass data in and
+ * out. Used in the upcoming AccountProvisioner add-on.
+ * @see msgOpenAccountWizard below for the previous implementation.
+ */
+function msgNewBitmaskMailAccount(msgWindow, okCallback, extraData)
+{
+ if (!msgWindow)
+ throw new Error("msgNewBitmaskMailAccount must be given a msgWindow.");
+ let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService()
+ .QueryInterface(Components.interfaces.nsIWindowMediator);
+ let existingWindow = wm.getMostRecentWindow("mail:bitmaskautoconfig");
+ if (existingWindow)
+ existingWindow.focus();
+ else
+ // disabling modal for the time being, see 688273 REMOVEME
+ window.openDialog("chrome://bitmask/content/accountWizard/accountWizard.xul",
+ "AccountSetup", "chrome,titlebar,centerscreen",
+ {msgWindow:msgWindow,
+ okCallback:function () { updatePanel(); },
+ extraData:extraData});
+
+}
+
diff --git a/chrome/content/preventCaching/bitmaskAmOfflineOverlay.js b/chrome/content/preventCaching/bitmaskAmOfflineOverlay.js
new file mode 100644
index 0000000..c88b2ab
--- /dev/null
+++ b/chrome/content/preventCaching/bitmaskAmOfflineOverlay.js
@@ -0,0 +1,67 @@
+/**
+ * preventCachingOverlay.js
+ * Copyright (C) 2013 LEAP
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along
+ */
+
+
+// The following variable will hold the current account being edited after
+// onPreInit() is called.
+var currentAccount = null;
+
+
+// The following function disables UI items that would allow a user to turn
+// on offline caching for a LEAP account. It acts on am-offline.xul items that
+// can be accessed in Thunderbird by choosing:
+//
+// Edit -> Account Settings... -> Synchronization and Storage.
+function disableOfflineCaching()
+{
+ var disabled;
+ // for now, we consider a LEAP account every account whose incoming server
+ // has a hostname equal to IMAP_HOST and port equal to IMAP_PORT.
+ if (currentAccount.incomingServer.port == IMAP_PORT &&
+ currentAccount.incomingServer.hostName == IMAP_HOST)
+ disabled = true;
+ else
+ disabled = false;
+ // The "Keep messsages for this account on this computer" checkbox.
+ document.getElementById("offline.folders").disabled = disabled;
+ // The "Advanced..." button.
+ document.getElementById("selectImapFoldersButton").disabled = disabled;
+}
+
+
+// Clone 'am-offline.js' onPreInit() so we can store the current account.
+var oldOnPreInit = onPreInit.bind({});
+
+// Store current account and call old onPreInit().
+var onPreInit = function(account, accountValues)
+{
+ currentAccount = account;
+ oldOnPreInit(account, accountValues);
+}
+
+
+// Clone 'am-offline.js' onInit() so we can disable offline caching.
+var oldOnInit = onInit.bind({});
+
+// Call old onInit() and disable offline caching.
+var onInit = function(aPageId, aServerId)
+{
+ oldOnInit(aPageId, aServerId);
+ disableOfflineCaching();
+}
+
diff --git a/chrome/content/preventCaching/bitmaskAmOfflineOverlay.xul b/chrome/content/preventCaching/bitmaskAmOfflineOverlay.xul
new file mode 100644
index 0000000..a5b5039
--- /dev/null
+++ b/chrome/content/preventCaching/bitmaskAmOfflineOverlay.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<overlay id="bitmaskPreventCachingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://bitmask/content/serverConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/preventCaching/bitmaskAmOfflineOverlay.js"/>
+</overlay>
diff --git a/chrome/content/serverConfig.js b/chrome/content/serverConfig.js
new file mode 100644
index 0000000..470ceb3
--- /dev/null
+++ b/chrome/content/serverConfig.js
@@ -0,0 +1,28 @@
+/**
+ * serverConfig.js
+ * Copyright (C) 2013 LEAP
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along
+ */
+
+
+// IMAP configuration
+var IMAP_HOST = "localhost";
+var IMAP_PORT = 1984;
+
+
+// SMTP configuration
+var SMTP_HOST = "localhost";
+var SMTP_PORT = 2013;
+var SMTP_USER = "";
diff --git a/chrome/content/statusBar/statusBarOverlay.js b/chrome/content/statusBar/statusBarOverlay.js
new file mode 100644
index 0000000..31ec16a
--- /dev/null
+++ b/chrome/content/statusBar/statusBarOverlay.js
@@ -0,0 +1,94 @@
+/**
+ * statusBar.js
+ * Copyright (C) 2013 LEAP
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along
+ */
+
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var accountNotConfigured = getStringBundle(
+ "chrome://bitmask/locale/statusBar.properties")
+ .GetStringFromName("account_not_configured");
+var accountConfigured = getStringBundle(
+ "chrome://bitmask/locale/statusBar.properties")
+ .GetStringFromName("account_configured");
+
+
+/*****************************************************************************
+ * Schedule initialization and update functions.
+ ****************************************************************************/
+
+// run startUp() once when window loads
+window.addEventListener("load", function(e) {
+ starUp();
+}, false);
+
+// run updatePanel() periodically
+window.setInterval(
+ function() {
+ updatePanel();
+ }, 10000); // update every ten seconds
+
+
+/*****************************************************************************
+ * GUI maintenance functions.
+ ****************************************************************************/
+
+function starUp() {
+ updatePanel();
+ if (!isBitmaskAccountConfigured()) {
+ launchAccountWizard();
+ }
+}
+
+/**
+ * Update the status bar panel with information about bitmask accounts.
+ */
+function updatePanel() {
+ var statusBarPanel = document.getElementById("bitmask-status-bar");
+ if (isBitmaskAccountConfigured())
+ statusBarPanel.label = accountConfigured;
+ else
+ statusBarPanel.label = accountNotConfigured;
+}
+
+/**
+ * Handle a click on the status bar panel. For now, just launch the new
+ * account wizard if there's no account configured.
+ */
+function handleStatusBarClick() {
+ if (!isBitmaskAccountConfigured())
+ launchAccountWizard();
+}
+
+
+/*****************************************************************************
+ * Account management functions
+ ****************************************************************************/
+
+/**
+ * Return true if there exists an account with incoming server hostname equal
+ * to IMAP_HOST and port equal to IMAP_PORT.
+ *
+ * TODO: also verify for SMTP configuration?
+ */
+function isBitmaskAccountConfigured() {
+ var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+ .getService(Ci.nsIMsgAccountManager);
+ var existing = accountManager.findRealServer(
+ "", IMAP_HOST, "imap", IMAP_PORT);
+ return !!existing;
+}
diff --git a/chrome/content/statusBar/statusBarOverlay.xul b/chrome/content/statusBar/statusBarOverlay.xul
new file mode 100644
index 0000000..006ccb9
--- /dev/null
+++ b/chrome/content/statusBar/statusBarOverlay.xul
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<overlay id="bitmaskStatusBarOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://bitmask/content/util.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/serverConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/accountWizard/bitmaskAccountManagerOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://bitmask/content/statusBar/statusBarOverlay.js"/>
+ <statusbar id="status-bar">
+ <statusbarpanel id="bitmask-status-bar"
+ label="Bitmask"
+ onclick="handleStatusBarClick();" />
+ </statusbar>
+</overlay>
diff --git a/chrome/content/util.js b/chrome/content/util.js
new file mode 100644
index 0000000..1fab8d3
--- /dev/null
+++ b/chrome/content/util.js
@@ -0,0 +1,164 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/**
+ * Some common, generic functions
+ */
+
+try {
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+} catch (e) { ddump(e); } // if already declared, as in xpcshell-tests
+try {
+ var Cu = Components.utils;
+} catch (e) { ddump(e); }
+
+Cu.import("resource:///modules/errUtils.js");
+
+/**
+ * Create a subtype
+ */
+function extend(child, supertype)
+{
+ child.prototype.__proto__ = supertype.prototype;
+}
+
+function assert(test, errorMsg)
+{
+ if (!test)
+ throw new NotReached(errorMsg ? errorMsg :
+ "Programming bug. Assertion failed, see log.");
+}
+
+function makeCallback(obj, func)
+{
+ return function()
+ {
+ return func.apply(obj, arguments);
+ }
+}
+
+/**
+ * @param bundleURI {String} chrome URL to properties file
+ * @return nsIStringBundle
+ */
+function getStringBundle(bundleURI)
+{
+ try {
+ return Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle(bundleURI);
+ } catch (e) {
+ throw new Exception("Failed to get stringbundle URI <" + bundleURI +
+ ">. Error: " + e);
+ }
+}
+
+function Exception(msg)
+{
+ this._message = msg;
+
+ // get stack
+ try {
+ not.found.here += 1; // force a native exception ...
+ } catch (e) {
+ this.stack = e.stack; // ... to get the current stack
+ }
+}
+Exception.prototype =
+{
+ get message()
+ {
+ return this._message;
+ },
+ toString : function()
+ {
+ return this._message;
+ }
+}
+
+function NotReached(msg)
+{
+ Exception.call(this, msg);
+ logException(this);
+}
+extend(NotReached, Exception);
+
+
+function deepCopy(org)
+{
+ if (typeof(org) == "undefined")
+ return undefined;
+ if (org == null)
+ return null;
+ if (typeof(org) == "string")
+ return org;
+ if (typeof(org) == "number")
+ return org;
+ if (typeof(org) == "boolean")
+ return org == true;
+ if (typeof(org) == "function")
+ return org;
+ if (typeof(org) != "object")
+ throw "can't copy objects of type " + typeof(org) + " yet";
+
+ //TODO still instanceof org != instanceof copy
+ //var result = new org.constructor();
+ var result = new Object();
+ if (typeof(org.length) != "undefined")
+ var result = new Array();
+ for (var prop in org)
+ result[prop] = deepCopy(org[prop]);
+ return result;
+}
+
+let kDebug = false;
+function ddump(text)
+{
+ if (kDebug)
+ dump(text + "\n");
+}
+
+function debugObject(obj, name, maxDepth, curDepth)
+{
+ if (curDepth == undefined)
+ curDepth = 0;
+ if (maxDepth != undefined && curDepth > maxDepth)
+ return "";
+
+ var result = "";
+ var i = 0;
+ for (let prop in obj)
+ {
+ i++;
+ try {
+ if (typeof(obj[prop]) == "object")
+ {
+ if (obj[prop] && obj[prop].length != undefined)
+ result += name + "." + prop + "=[probably array, length " +
+ obj[prop].length + "]\n";
+ else
+ result += name + "." + prop + "=[" + typeof(obj[prop]) + "]\n";
+ result += debugObject(obj[prop], name + "." + prop,
+ maxDepth, curDepth + 1);
+ }
+ else if (typeof(obj[prop]) == "function")
+ result += name + "." + prop + "=[function]\n";
+ else
+ result += name + "." + prop + "=" + obj[prop] + "\n";
+ } catch (e) {
+ result += name + "." + prop + "-> Exception(" + e + ")\n";
+ }
+ }
+ if (!i)
+ result += name + " is empty\n";
+ return result;
+}
+
+function alertPrompt(alertTitle, alertMsg)
+{
+ Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .alert(window, alertTitle, alertMsg);
+}
diff --git a/chrome/locale/en-US/accountWizard.dtd b/chrome/locale/en-US/accountWizard.dtd
new file mode 100644
index 0000000..a0221ee
--- /dev/null
+++ b/chrome/locale/en-US/accountWizard.dtd
@@ -0,0 +1 @@
+<!ENTITY bitmask.accountWizard.title "Configure Bitmask Email Account">
diff --git a/chrome/locale/en-US/accountWizard.properties b/chrome/locale/en-US/accountWizard.properties
new file mode 100644
index 0000000..51532d0
--- /dev/null
+++ b/chrome/locale/en-US/accountWizard.properties
@@ -0,0 +1,2 @@
+# verifyConfig.js
+cannot_login.error=Unable to log in at server. Probably wrong configuration, username or password.
diff --git a/chrome/locale/en-US/bitmaskAccountManagerOverlay.dtd b/chrome/locale/en-US/bitmaskAccountManagerOverlay.dtd
new file mode 100644
index 0000000..b87fbc3
--- /dev/null
+++ b/chrome/locale/en-US/bitmaskAccountManagerOverlay.dtd
@@ -0,0 +1,2 @@
+<!ENTITY addBitmaskAccountButton.label "Add Bitmask Account...">
+<!ENTITY addBitmaskAccountButton.accesskey "B">
diff --git a/chrome/locale/en-US/bitmaskMessengerOverlay.dtd b/chrome/locale/en-US/bitmaskMessengerOverlay.dtd
new file mode 100644
index 0000000..1e815cd
--- /dev/null
+++ b/chrome/locale/en-US/bitmaskMessengerOverlay.dtd
@@ -0,0 +1,2 @@
+<!ENTITY newBitmaskAccountCmd.label "Bitmask Account...">
+<!ENTITY newBitmaskAccountCmd.accesskey "B">
diff --git a/chrome/locale/en-US/statusBar.properties b/chrome/locale/en-US/statusBar.properties
new file mode 100644
index 0000000..7715c71
--- /dev/null
+++ b/chrome/locale/en-US/statusBar.properties
@@ -0,0 +1,3 @@
+# statusBar.js
+account_not_configured=Click to config Bitmask account
+account_configured=Bitmask account is configured!
diff --git a/chrome/skin/accountWizard.css b/chrome/skin/accountWizard.css
new file mode 100644
index 0000000..3bf6846
--- /dev/null
+++ b/chrome/skin/accountWizard.css
@@ -0,0 +1,10 @@
+@import url("chrome://messenger/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+
+.bitmaskAccountWizardTitle {
+ font-size: 150%;
+ font-weight: bold;
+}
+
diff --git a/install.rdf b/install.rdf
new file mode 100644
index 0000000..f0a5398
--- /dev/null
+++ b/install.rdf
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>bitmask-thunderbird@leap.se</em:id>
+ <em:name>Bitmask Thunderbird Extension</em:name>
+ <em:version>0.0.1</em:version>
+ <em:creator>LEAP developers</em:creator>
+
+ <em:targetApplication>
+ <Description>
+ <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
+ <em:minVersion>24.0</em:minVersion>
+ <em:maxVersion>24.0.*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ </Description>
+</RDF>