Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1

This commit is contained in:
Matt A. Tobin 2019-11-03 00:17:46 -04:00 • committed by Roy Tam
commit e400f4130a
1564 changed files with 510348 additions and 0 deletions

View file

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This class implements nsIBadCertListener. It's job is to prevent "bad cert"
* security dialogs from being shown to the user. We call back to the
* 'callback' object's method "processCertError" so that it can deal with it as
* needed (in the case of autoconfig, setting up temporary overrides).
*/
function BadCertHandler(callback)
{
this._init(callback);
}
BadCertHandler.prototype =
{
_init: function(callback) {
this._callback = callback;
},
// Suppress any certificate errors
notifyCertProblem: function(socketInfo, status, targetSite) {
return this._callback.processCertError(socketInfo, status, targetSite);
},
// nsIInterfaceRequestor
getInterface: function(iid) {
return this.QueryInterface(iid);
},
// nsISupports
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
!iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};

View file

@ -0,0 +1,259 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This file creates the class AccountConfig, which is a JS object that holds
* a configuration for a certain account. It is *not* created in the backend
* yet (use aw-createAccount.js for that), and it may be incomplete.
*
* Several AccountConfig objects may co-exist, e.g. for autoconfig.
* One AccountConfig object is used to prefill and read the widgets
* in the Wizard UI.
* When we autoconfigure, we autoconfig writes the values into a
* new object and returns that, and the caller can copy these
* values into the object used by the UI.
*
* See also
* <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
* for values stored.
*/
function AccountConfig()
{
this.incoming = this.createNewIncoming();
this.incomingAlternatives = [];
this.outgoing = this.createNewOutgoing();
this.outgoingAlternatives = [];
this.identity =
{
// displayed real name of user
realname : "%REALNAME%",
// email address of user, as shown in From of outgoing mails
emailAddress : "%EMAILADDRESS%",
};
this.inputFields = [];
this.domains = [];
};
AccountConfig.prototype =
{
// @see createNewIncoming()
incoming : null,
// @see createNewOutgoing()
outgoing : null,
/**
* Other servers which can be used instead of |incoming|,
* in order of decreasing preference.
* (|incoming| itself should not be included here.)
* { Array of incoming/createNewIncoming() }
*/
incomingAlternatives : null,
outgoingAlternatives : null,
// OAuth2 configuration, if needed.
oauthSettings : null,
// just an internal string to refer to this. Do not show to user.
id : null,
// who created the config.
// { one of kSource* }
source : 0,
displayName : null,
// { Array of { varname (value without %), displayName, exampleValue } }
inputFields : null,
// email address domains for which this config is applicable
// { Array of Strings }
domains : null,
/**
* Factory function for incoming and incomingAlternatives
*/
createNewIncoming : function()
{
return {
// { String-enum: "pop3", "imap", "nntp" }
type : null,
hostname : null,
// { Integer }
port : null,
// May be a placeholder (starts and ends with %). { String }
username : null,
password : null,
// { enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited }
// ('TLS when available' is insecure and not supported here)
socketType : 0,
/**
* true when the cert is invalid (and thus SSL useless), because it's
* 1) not from an accepted CA (including self-signed certs)
* 2) for a different hostname or
* 3) expired.
* May go back to false when user explicitly accepted the cert.
*/
badCert : false,
/**
* How to log in to the server: plaintext or encrypted pw, GSSAPI etc.
* Defined by Ci.nsMsgAuthMethod
* Same as server pref "authMethod".
*/
auth : 0,
/**
* Other auth methods that we think the server supports.
* They are ordered by descreasing preference.
* (|auth| itself is not included in |authAlternatives|)
* {Array of Ci.nsMsgAuthMethod} (same as .auth)
*/
authAlternatives : null,
// in minutes { Integer }
checkInterval : 10,
loginAtStartup : true,
// POP3 only:
// Not yet implemented. { Boolean }
useGlobalInbox : false,
leaveMessagesOnServer : true,
daysToLeaveMessagesOnServer : 14,
deleteByAgeFromServer : true,
// When user hits delete, delete from local store and from server
deleteOnServerWhenLocalDelete : true,
downloadOnBiff : true,
};
},
/**
* Factory function for outgoing and outgoingAlternatives
*/
createNewOutgoing : function()
{
return {
type : "smtp",
hostname : null,
port : null, // see incoming
username : null, // see incoming. may be null, if auth is 0.
password : null, // see incoming. may be null, if auth is 0.
socketType : 0, // see incoming
badCert : false, // see incoming
auth : 0, // see incoming
authAlternatives : null, // see incoming
addThisServer : true, // if we already have an SMTP server, add this
// if we already have an SMTP server, use it.
useGlobalPreferredServer : false,
// we should reuse an already configured SMTP server.
// nsISmtpServer.key
existingServerKey : null,
// user display value for existingServerKey
existingServerLabel : null,
};
},
/**
* Returns a deep copy of this object,
* i.e. modifying the copy will not affect the original object.
*/
copy : function()
{
// Workaround: deepCopy() fails to preserve base obj (instanceof)
var result = new AccountConfig();
for (var prop in this)
result[prop] = deepCopy(this[prop]);
return result;
},
isComplete : function()
{
return (!!this.incoming.hostname && !!this.incoming.port &&
!!this.incoming.socketType && !!this.incoming.auth &&
!!this.incoming.username &&
(!!this.outgoing.existingServerKey ||
(!!this.outgoing.hostname && !!this.outgoing.port &&
!!this.outgoing.socketType && !!this.outgoing.auth &&
!!this.outgoing.username)));
},
};
// enum consts
// .source
AccountConfig.kSourceUser = 1; // user manually entered the config
AccountConfig.kSourceXML = 2; // config from XML from ISP or Mozilla DB
AccountConfig.kSourceGuess = 3; // guessConfig()
/**
* Some fields on the account config accept placeholders (when coming from XML).
*
* These are the predefined ones
* * %EMAILADDRESS% (full email address of the user, usually entered by user)
* * %EMAILLOCALPART% (email address, part before @)
* * %EMAILDOMAIN% (email address, part after @)
* * %REALNAME%
* as well as those defined in account.inputFields.*.varname, with % added
* before and after.
*
* These must replaced with real values, supplied by the user or app,
* before the account is created. This is done here. You call this function once
* you have all the data - gathered the standard vars mentioned above as well as
* all listed in account.inputFields, and pass them in here. This function will
* insert them in the fields, returning a fully filled-out account ready to be
* created.
*
* @param account {AccountConfig}
* The account data to be modified. It may or may not contain placeholders.
* After this function, it should not contain placeholders anymore.
* This object will be modified in-place.
*
* @param emailfull {String}
* Full email address of this account, e.g. "joe@example.com".
* Empty of incomplete email addresses will/may be rejected.
*
* @param realname {String}
* Real name of user, as will appear in From of outgoing messages
*
* @param password {String}
* The password for the incoming server and (if necessary) the outgoing server
*/
function replaceVariables(account, realname, emailfull, password)
{
sanitize.nonemptystring(emailfull);
let emailsplit = emailfull.split("@");
assert(emailsplit.length == 2,
"email address not in expected format: must contain exactly one @");
let emaillocal = sanitize.nonemptystring(emailsplit[0]);
let emaildomain = sanitize.hostname(emailsplit[1]);
sanitize.label(realname);
sanitize.nonemptystring(realname);
let otherVariables = {};
otherVariables.EMAILADDRESS = emailfull;
otherVariables.EMAILLOCALPART = emaillocal;
otherVariables.EMAILDOMAIN = emaildomain;
otherVariables.REALNAME = realname;
if (password) {
account.incoming.password = password;
account.outgoing.password = password; // set member only if auth required?
}
account.incoming.username = _replaceVariable(account.incoming.username,
otherVariables);
account.outgoing.username = _replaceVariable(account.outgoing.username,
otherVariables);
account.incoming.hostname =
_replaceVariable(account.incoming.hostname, otherVariables);
if (account.outgoing.hostname) // will be null if user picked existing server.
account.outgoing.hostname =
_replaceVariable(account.outgoing.hostname, otherVariables);
account.identity.realname =
_replaceVariable(account.identity.realname, otherVariables);
account.identity.emailAddress =
_replaceVariable(account.identity.emailAddress, otherVariables);
account.displayName = _replaceVariable(account.displayName, otherVariables);
}
function _replaceVariable(variable, values)
{
let str = variable;
if (typeof(str) != "string")
return str;
for (let varname in values)
str = str.replace("%" + varname + "%", values[varname]);
return str;
}

View file

@ -0,0 +1,333 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Takes an |AccountConfig| JS object and creates that account in the
* Thunderbird backend (which also writes it to prefs).
*
* @param config {AccountConfig} The account to create
*
* @return - the account created.
*/
Components.utils.import("resource:///modules/mailServices.js");
Components.utils.import("resource://gre/modules/Services.jsm");
function createAccountInBackend(config)
{
// incoming server
let inServer = MailServices.accounts.createIncomingServer(
config.incoming.username,
config.incoming.hostname,
sanitize.enum(config.incoming.type, ["pop3", "imap", "nntp"]));
inServer.port = config.incoming.port;
inServer.authMethod = config.incoming.auth;
inServer.password = config.incoming.password;
if (config.rememberPassword && config.incoming.password.length)
rememberPassword(inServer, config.incoming.password);
if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
}
// SSL
if (config.incoming.socketType == 1) // plain
inServer.socketType = Ci.nsMsgSocketType.plain;
else if (config.incoming.socketType == 2) // SSL / TLS
inServer.socketType = Ci.nsMsgSocketType.SSL;
else if (config.incoming.socketType == 3) // STARTTLS
inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
//inServer.prettyName = config.displayName;
inServer.prettyName = config.identity.emailAddress;
inServer.doBiff = true;
inServer.biffMinutes = config.incoming.checkInterval;
const loginAtStartupPrefTemplate =
"mail.server.%serverkey%.login_at_startup";
var loginAtStartupPref =
loginAtStartupPrefTemplate.replace("%serverkey%", inServer.key);
Services.prefs.setBoolPref(loginAtStartupPref,
config.incoming.loginAtStartup);
if (config.incoming.type == "pop3")
{
const leaveOnServerPrefTemplate =
"mail.server.%serverkey%.leave_on_server";
const daysToLeaveOnServerPrefTemplate =
"mail.server.%serverkey%.num_days_to_leave_on_server";
const deleteFromServerPrefTemplate =
"mail.server.%serverkey%.delete_mail_left_on_server";
const deleteByAgeFromServerPrefTemplate =
"mail.server.%serverkey%.delete_by_age_from_server";
const downloadOnBiffPrefTemplate =
"mail.server.%serverkey%.download_on_biff";
var leaveOnServerPref =
leaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
var ageFromServerPref =
deleteByAgeFromServerPrefTemplate.replace("%serverkey%", inServer.key);
var daysToLeaveOnServerPref =
daysToLeaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
var deleteFromServerPref =
deleteFromServerPrefTemplate.replace("%serverkey%", inServer.key);
let downloadOnBiffPref =
downloadOnBiffPrefTemplate.replace("%serverkey%", inServer.key);
Services.prefs.setBoolPref(leaveOnServerPref,
config.incoming.leaveMessagesOnServer);
Services.prefs.setIntPref(daysToLeaveOnServerPref,
config.incoming.daysToLeaveMessagesOnServer);
Services.prefs.setBoolPref(deleteFromServerPref,
config.incoming.deleteOnServerWhenLocalDelete);
Services.prefs.setBoolPref(ageFromServerPref,
config.incoming.deleteByAgeFromServer);
Services.prefs.setBoolPref(downloadOnBiffPref,
config.incoming.downloadOnBiff);
}
inServer.valid = true;
let username = config.outgoing.auth > 1 ? config.outgoing.username : null;
let outServer = MailServices.smtp.findServer(username, config.outgoing.hostname);
assert(config.outgoing.addThisServer ||
config.outgoing.useGlobalPreferredServer ||
config.outgoing.existingServerKey,
"No SMTP server: inconsistent flags");
if (config.outgoing.addThisServer && !outServer)
{
outServer = MailServices.smtp.createServer();
outServer.hostname = config.outgoing.hostname;
outServer.port = config.outgoing.port;
outServer.authMethod = config.outgoing.auth;
if (config.outgoing.auth > 1)
{
outServer.username = username;
outServer.password = config.incoming.password;
if (config.rememberPassword && config.incoming.password.length)
rememberPassword(outServer, config.incoming.password);
}
if (outServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
let pref = "mail.smtpserver." + outServer.key + ".";
Services.prefs.setCharPref(pref + "oauth2.scope",
config.oauthSettings.scope);
Services.prefs.setCharPref(pref + "oauth2.issuer",
config.oauthSettings.issuer);
}
if (config.outgoing.socketType == 1) // no SSL
outServer.socketType = Ci.nsMsgSocketType.plain;
else if (config.outgoing.socketType == 2) // SSL / TLS
outServer.socketType = Ci.nsMsgSocketType.SSL;
else if (config.outgoing.socketType == 3) // STARTTLS
outServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
// API problem: <http://mxr.mozilla.org/seamonkey/source/mailnews/compose/public/nsISmtpServer.idl#93>
outServer.description = config.displayName;
if (config.password)
outServer.password = config.outgoing.password;
// If this is the first SMTP server, set it as default
if (!MailServices.smtp.defaultServer ||
!MailServices.smtp.defaultServer.hostname)
MailServices.smtp.defaultServer = outServer;
}
// identity
// TODO accounts without identity?
let identity = MailServices.accounts.createIdentity();
identity.fullName = config.identity.realname;
identity.email = config.identity.emailAddress;
// for new accounts, default to replies being positioned above the quote
// if a default account is defined already, take its settings instead
if (config.incoming.type == "imap" || config.incoming.type == "pop3")
{
identity.replyOnTop = 1;
// identity.sigBottom = false; // don't set this until Bug 218346 is fixed
if (MailServices.accounts.accounts.length &&
MailServices.accounts.defaultAccount)
{
let defAccount = MailServices.accounts.defaultAccount;
let defIdentity = defAccount.defaultIdentity;
if (defAccount.incomingServer.canBeDefaultServer &&
defIdentity && defIdentity.valid)
{
identity.replyOnTop = defIdentity.replyOnTop;
identity.sigBottom = defIdentity.sigBottom;
}
}
}
// due to accepted conventions, news accounts should default to plain text
if (config.incoming.type == "nntp")
identity.composeHtml = false;
identity.valid = true;
if (config.outgoing.existingServerKey)
identity.smtpServerKey = config.outgoing.existingServerKey;
else if (!config.outgoing.useGlobalPreferredServer)
identity.smtpServerKey = outServer.key;
// account and hook up
// Note: Setting incomingServer will cause the AccountManager to refresh
// itself, which could be a problem if we came from it and we haven't set
// the identity (see bug 521955), so make sure everything else on the
// account is set up before you set the incomingServer.
let account = MailServices.accounts.createAccount();
account.addIdentity(identity);
account.incomingServer = inServer;
if (inServer.canBeDefaultServer && (!MailServices.accounts.defaultAccount ||
!MailServices.accounts.defaultAccount
.incomingServer.canBeDefaultServer))
MailServices.accounts.defaultAccount = account;
verifyLocalFoldersAccount(MailServices.accounts);
setFolders(identity, inServer);
// save
MailServices.accounts.saveAccountInfo();
try {
Services.prefs.savePrefFile(null);
} catch (ex) {
ddump("Could not write out prefs: " + ex);
}
return account;
}
function setFolders(identity, server)
{
// TODO: support for local folders for global inbox (or use smart search
// folder instead)
var baseURI = server.serverURI + "/";
// Names will be localized in UI, not in folder names on server/disk
// TODO allow to override these names in the XML config file,
// in case e.g. Google or AOL use different names?
// Workaround: Let user fix it :)
var fccName = "Sent";
var draftName = "Drafts";
var templatesName = "Templates";
identity.draftFolder = baseURI + draftName;
identity.stationeryFolder = baseURI + templatesName;
identity.fccFolder = baseURI + fccName;
identity.fccFolderPickerMode = 0;
identity.draftsFolderPickerMode = 0;
identity.tmplFolderPickerMode = 0;
}
function rememberPassword(server, password)
{
if (server instanceof Components.interfaces.nsIMsgIncomingServer)
var passwordURI = server.localStoreType + "://" + server.hostName;
else if (server instanceof Components.interfaces.nsISmtpServer)
var passwordURI = "smtp://" + server.hostname;
else
throw new NotReached("Server type not supported");
let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
login.init(passwordURI, null, passwordURI, server.username, password, "", "");
try {
Services.logins.addLogin(login);
} catch (e) {
if (e.message.includes("This login already exists")) {
// TODO modify
} else {
throw e;
}
}
}
/**
* Check whether the user's setup already has an incoming server
* which matches (hostname, port, username) the primary one
* in the config.
* (We also check the email address as username.)
*
* @param config {AccountConfig} filled in (no placeholders)
* @return {nsIMsgIncomingServer} If it already exists, the server
* object is returned.
* If it's a new server, |null| is returned.
*/
function checkIncomingServerAlreadyExists(config)
{
assert(config instanceof AccountConfig);
let incoming = config.incoming;
let existing = MailServices.accounts.findRealServer(incoming.username,
incoming.hostname,
sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
incoming.port);
// if username does not have an '@', also check the e-mail
// address form of the name.
if (!existing && !incoming.username.includes("@"))
existing = MailServices.accounts.findRealServer(config.identity.emailAddress,
incoming.hostname,
sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
incoming.port);
return existing;
};
/**
* Check whether the user's setup already has an outgoing server
* which matches (hostname, port, username) the primary one
* in the config.
*
* @param config {AccountConfig} filled in (no placeholders)
* @return {nsISmtpServer} If it already exists, the server
* object is returned.
* If it's a new server, |null| is returned.
*/
function checkOutgoingServerAlreadyExists(config)
{
assert(config instanceof AccountConfig);
let smtpServers = MailServices.smtp.servers;
while (smtpServers.hasMoreElements())
{
let existingServer = smtpServers.getNext()
.QueryInterface(Ci.nsISmtpServer);
// TODO check username with full email address, too, like for incoming
if (existingServer.hostname == config.outgoing.hostname &&
existingServer.port == config.outgoing.port &&
existingServer.username == config.outgoing.username)
return existingServer;
}
return null;
};
/**
* Check if there already is a "Local Folders". If not, create it.
* Copied from AccountWizard.js with minor updates.
*/
function verifyLocalFoldersAccount(am)
{
let localMailServer;
try {
localMailServer = am.localFoldersServer;
}
catch (ex) {
localMailServer = null;
}
try {
if (!localMailServer)
{
// creates a copy of the identity you pass in
am.createLocalMailAccount();
try {
localMailServer = am.localFoldersServer;
}
catch (ex) {
ddump("Error! we should have found the local mail server " +
"after we created it.");
}
}
}
catch (ex) { ddump("Error in verifyLocalFoldersAccount " + ex); }
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,493 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css"
type="text/css"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % acDTD SYSTEM "chrome://messenger/locale/accountCreation.dtd">
%acDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="autoconfigWizard"
windowtype="mail:autoconfig"
title="&autoconfigWizard.title;"
onload="gEmailConfigWizard.onLoad();"
onkeypress="gEmailConfigWizard.onKeyDown(event);"
onclose="gEmailConfigWizard.onWizardShutdown();"
onunload="gEmailConfigWizard.onWizardShutdown();"
>
<stringbundleset>
<stringbundle id="bundle_brand"
src="chrome://branding/locale/brand.properties"/>
<stringbundle id="strings"
src="chrome://messenger/locale/accountCreation.properties"/>
<stringbundle id="utilstrings"
src="chrome://messenger/locale/accountCreationUtil.properties"/>
<stringbundle id="bundle_messenger"
src="chrome://messenger/locale/messenger.properties"/>
</stringbundleset>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/util.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/accountConfig.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/emailWizard.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/sanitizeDatatypes.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/fetchhttp.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/readFromXML.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/guessConfig.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/verifyConfig.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/fetchConfig.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/createInBackend.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountcreation/MyBadCertHandler.js"/>
<script type="application/javascript"
src="chrome://messenger/content/accountUtils.js" />
<keyset id="mailKeys">
<key keycode="VK_ESCAPE" oncommand="window.close();"/>
</keyset>
<panel id="insecureserver-cleartext-panel" class="popup-panel">
<hbox>
<image class="insecureLarry"/>
<vbox flex="1">
<description class="title">&insecureServer.tooltip.title;</description>
<description class="details">
&insecureUnencrypted.description;</description>
</vbox>
</hbox>
</panel>
<panel id="insecureserver-selfsigned-panel" class="popup-panel">
<hbox>
<image class="insecureLarry"/>
<vbox flex="1">
<description class="title">&insecureServer.tooltip.title;</description>
<description class="details">
&insecureSelfSigned.description;</description>
</vbox>
</hbox>
</panel>
<panel id="secureserver-panel" class="popup-panel">
<hbox>
<image class="secureLarry"/>
<vbox flex="1">
<description class="title">&secureServer.description;</description>
</vbox>
</hbox>
</panel>
<tooltip id="insecureserver-cleartext">
<hbox>
<image class="insecureLarry"/>
<vbox>
<description class="title">&insecureServer.tooltip.title;</description>
<description class="details">
&insecureServer.tooltip.details;</description>
</vbox>
</hbox>
</tooltip>
<tooltip id="insecureserver-selfsigned">
<hbox>
<image class="insecureLarry"/>
<vbox>
<description class="title">&insecureServer.tooltip.title;</description>
<description class="details">
&insecureServer.tooltip.details;</description>
</vbox>
</hbox>
</tooltip>
<tooltip id="secureservertooltip">
<hbox>
<image class="secureLarry"/>
<description class="title">&secureServer.description;</description>
</hbox>
</tooltip>
<tooltip id="optional-password">
<description>&password.text;</description>
</tooltip>
<spacer id="fullwidth"/>
<vbox id="mastervbox" class="mastervbox" flex="1">
<grid id="initialSettings">
<columns>
<column/>
<column/>
<column/>
</columns>
<rows>
<row align="center">
<label accesskey="&name.accesskey;"
class="autoconfigLabel"
value="&name.label;"
control="realname"/>
<textbox id="realname"
class="padded"
placeholder="&name.placeholder;"
oninput="gEmailConfigWizard.onInputRealname();"
onblur="gEmailConfigWizard.onBlurRealname();"/>
<hbox>
<description id="nametext" class="initialDesc">&name.text;</description>
<image id="nameerroricon"
hidden="true"
class="warningicon"/>
<description id="nameerror" class="errordescription" hidden="true"/>
</hbox>
</row>
<row align="center">
<label accesskey="&email.accesskey;"
class="autoconfigLabel"
value="&email.label;"
control="email"/>
<textbox id="email"
class="padded uri-element"
placeholder="&email.placeholder;"
oninput="gEmailConfigWizard.onInputEmail();"
onblur="gEmailConfigWizard.onBlurEmail();"/>
<hbox>
<image id="emailerroricon"
hidden="true"
class="warningicon"/>
<description id="emailerror" class="errordescription" hidden="true"/>
</hbox>
</row>
<row align="center">
<!-- this starts out as text so the emptytext shows, but then
changes to type=password once it's not empty -->
<label accesskey="&password.accesskey;"
class="autoconfigLabel"
value="&password.label;"
control="password"
tooltip="optional-password"/>
<textbox id="password"
class="padded"
placeholder="&password.placeholder;"
type="text"
oninput="gEmailConfigWizard.onInputPassword();"
onfocus="gEmailConfigWizard.onFocusPassword();"
onblur="gEmailConfigWizard.onBlurPassword();"/>
<hbox>
<image id="passworderroricon"
hidden="true"
class="warningicon"/>
<description id="passworderror" class="errordescription" hidden="true"/>
</hbox>
</row>
<row align="center" pack="start">
<label class="autoconfigLabel"/>
<checkbox id="remember_password"
label="&rememberPassword.label;"
accesskey="&rememberPassword.accesskey;"
checked="true"/>
</row>
</rows>
</grid>
<spacer flex="1" />
<hbox id="status_area" flex="1">
<vbox id="status_img_before" pack="start"/>
<description id="status_msg">&#160;</description>
<!-- Include 160 = nbsp, to make the element occupy the
full height, for at least one line. With a normal space,
it does not have sufficient height. -->
<vbox id="status_img_after" pack="start"/>
</hbox>
<groupbox id="result_area" hidden="true">
<radiogroup id="result_imappop" orient="horizontal">
<radio id="result_select_imap" label="&imapLong.label;" value="1"
oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
<radio id="result_select_pop3" label="&pop3Long.label;" value="2"
oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
</radiogroup>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row align="center">
<label class="textbox-label" value="&incoming.label;"
control="result-incoming"/>
<textbox id="result-incoming" disabled="true" flex="1"/>
</row>
<row align="center">
<label class="textbox-label" value="&outgoing.label;"
control="result-outgoing"/>
<textbox id="result-outgoing" disabled="true" flex="1"/>
</row>
<row align="center">
<label class="textbox-label" value="&username.label;"
control="result-username"/>
<textbox id="result-username" disabled="true" flex="1"/>
</row>
</rows>
</grid>
</groupbox>
<groupbox id="manual-edit_area" hidden="true">
<grid>
<columns>
<column/><!-- row label, e.g. "incoming" -->
<column/><!-- protocol, e.g. "IMAP" -->
<column flex="1"/><!-- hostname / username -->
<column/><!-- port -->
<column/><!-- SSL -->
<column/><!-- auth method -->
</columns>
<rows>
<row id="labels_row" align="center">
<spacer/>
<spacer/>
<label value="&hostname.label;" class="columnHeader"/>
<label value="&port.label;" class="columnHeader"/>
<label value="&ssl.label;" class="columnHeader"/>
<label value="&auth.label;" class="columnHeader"/>
</row>
<row id="incoming_server_area">
<hbox align="center" pack="end">
<label class="textbox-label"
value="&incoming.label;"
control="incoming_hostname"/>
</hbox>
<menulist id="incoming_protocol"
oncommand="gEmailConfigWizard.onChangedProtocolIncoming();"
sizetopopup="always">
<menupopup>
<menuitem label="&imap.label;" value="1"/>
<menuitem label="&pop3.label;" value="2"/>
</menupopup>
</menulist>
<textbox id="incoming_hostname"
oninput="gEmailConfigWizard.onInputHostname();"
class="host uri-element"/>
<menulist id="incoming_port"
editable="true"
oninput="gEmailConfigWizard.onChangedPortIncoming();"
oncommand="gEmailConfigWizard.onChangedPortIncoming();"
class="port">
<menupopup/>
</menulist>
<menulist id="incoming_ssl"
class="security"
oncommand="gEmailConfigWizard.onChangedSSLIncoming();"
sizetopopup="always">
<menupopup>
<!-- values defined in nsMsgSocketType -->
<menuitem label="&autodetect.label;" value="0"/>
<menuitem label="&noEncryption.label;" value="1"/>
<menuitem label="&starttls.label;" value="3"/>
<menuitem label="&sslTls.label;" value="2"/>
</menupopup>
</menulist>
<menulist id="incoming_authMethod"
class="auth"
oncommand="gEmailConfigWizard.onChangedInAuth();"
sizetopopup="always">
<menupopup>
<menuitem label="&autodetect.label;" value="0"/>
<!-- values defined in nsMsgAuthMethod -->
<!-- labels set from messenger.properties
to avoid duplication -->
<menuitem id="in-authMethod-password-cleartext" value="3"/>
<menuitem id="in-authMethod-password-encrypted" value="4"/>
<menuitem id="in-authMethod-kerberos" value="5"/>
<menuitem id="in-authMethod-ntlm" value="6"/>
<menuitem id="in-authMethod-oauth2" value="10" hidden="true"/>
</menupopup>
</menulist>
</row>
<row id="outgoing_server_area" align="center">
<label class="textbox-label"
value="&outgoing.label;"
control="outgoing_hostname"/>
<label id="outgoing_protocol"
value="&smtp.label;"/>
<menulist id="outgoing_hostname"
editable="true"
sizetopopup="none"
oninput="gEmailConfigWizard.onInputHostname();"
oncommand="gEmailConfigWizard.onChangedOutgoingDropdown();"
onpopupshowing="gEmailConfigWizard.onOpenOutgoingDropdown();"
class="host uri-element">
<menupopup id="outgoing_hostname_popup"/>
</menulist>
<menulist id="outgoing_port"
editable="true"
oninput="gEmailConfigWizard.onChangedPortOutgoing();"
oncommand="gEmailConfigWizard.onChangedPortOutgoing();"
class="port">
<menupopup/>
</menulist>
<menulist id="outgoing_ssl"
class="security"
oncommand="gEmailConfigWizard.onChangedSSLOutgoing();"
sizetopopup="always">
<menupopup>
<!-- @see incoming -->
<menuitem label="&autodetect.label;" value="0"/>
<menuitem label="&noEncryption.label;" value="1"/>
<menuitem label="&starttls.label;" value="3"/>
<menuitem label="&sslTls.label;" value="2"/>
</menupopup>
</menulist>
<menulist id="outgoing_authMethod"
class="auth"
oncommand="gEmailConfigWizard.onChangedOutAuth(this.selectedItem);"
sizetopopup="always">
<menupopup>
<menuitem label="&autodetect.label;" value="0"/>
<!-- @see incoming -->
<menuitem id="out-authMethod-no" value="1"/>
<menuitem id="out-authMethod-password-cleartext" value="3"/>
<menuitem id="out-authMethod-password-encrypted" value="4"/>
<menuitem id="out-authMethod-kerberos" value="5"/>
<menuitem id="out-authMethod-ntlm" value="6"/>
<menuitem id="out-authMethod-oauth2" value="10" hidden="true"/>
</menupopup>
</menulist>
</row>
<row id="username_area" align="center">
<label class="textbox-label"
value="&username.label;"/>
<label class="columnHeader"
value="&incoming.label;"
control="incoming_username"/>
<textbox id="incoming_username"
oninput="gEmailConfigWizard.onInputInUsername();"
class="username"/>
<spacer/>
<label class="columnHeader"
id="outgoing_label"
value="&outgoing.label;"
control="outgoing_username"/>
<textbox id="outgoing_username"
oninput="gEmailConfigWizard.onInputOutUsername();"
class="username"/>
</row>
</rows>
</grid>
</groupbox>
<spacer flex="1" />
<hbox id="buttons_area">
<hbox id="left_buttons_area" align="center" pack="start">
<button id="provisioner_button"
label="&switch-to-provisioner.label;"
accesskey="&switch-to-provisioner.accesskey;"
class="larger-button"
oncommand="gEmailConfigWizard.onSwitchToProvisioner();"/>
<button id="manual-edit_button"
label="&manual-edit.label;"
accesskey="&manual-edit.accesskey;"
hidden="true"
oncommand="gEmailConfigWizard.onManualEdit();"/>
<button id="advanced-setup_button"
label="&advancedSetup.label;"
accesskey="&advancedSetup.accesskey;"
disabled="true"
hidden="true"
oncommand="gEmailConfigWizard.onAdvancedSetup();"/>
</hbox>
<spacer flex="1"/>
<hbox id="right_buttons_area" align="center" pack="end">
<button id="stop_button"
label="&stop.label;"
accesskey="&stop.accesskey;"
hidden="true"
oncommand="gEmailConfigWizard.onStop();"/>
<button id="cancel_button"
label="&cancel.label;"
accesskey="&cancel.accesskey;"
oncommand="gEmailConfigWizard.onCancel();"/>
<button id="half-manual-test_button"
label="&half-manual-test.label;"
accesskey="&half-manual-test.accesskey;"
hidden="true"
oncommand="gEmailConfigWizard.onHalfManualTest();"/>
<button id="next_button"
label="&continue.label;"
accesskey="&continue.accesskey;"
hidden="false"
oncommand="gEmailConfigWizard.onNext();"/>
<button id="create_button"
label="&doneAccount.label;"
accesskey="&doneAccount.accesskey;"
class="important-button"
hidden="true"
oncommand="gEmailConfigWizard.onCreate();"/>
</hbox>
</hbox>
</vbox>
<vbox id="warningbox" hidden="true" flex="1">
<hbox class="warning" flex="1">
<vbox class="larrybox">
<image id="insecure_larry" class="insecureLarry"/>
</vbox>
<vbox flex="1" class="warning_text">
<label class="warning-heading">&warning.label;</label>
<vbox id="incoming_box">
<hbox>
<label class="warning_settings" value="&incomingSettings.label;"/>
<description id="warning_incoming"/>
</hbox>
<label id="incoming_technical"
class="technical_details"
value="&technicaldetails.label;"
onclick="gSecurityWarningDialog.toggleDetails('incoming');"/>
<description id="incoming_details" collapsed="true"/>
</vbox>
<vbox id="outgoing_box">
<hbox>
<label class="warning_settings" value="&outgoingSettings.label;"/>
<description id="warning_outgoing"/>
</hbox>
<label id="outgoing_technical"
class="technical_details"
value="&technicaldetails.label;"
onclick="gSecurityWarningDialog.toggleDetails('outgoing');"/>
<description id="outgoing_details" collapsed="true"/>
</vbox>
<spacer flex="10"/>
<description id="findoutmore">
&contactYourProvider.description;</description>
<spacer flex="100"/>
<checkbox id="acknowledge_warning"
label="&confirmWarning.label;"
accesskey="&confirmWarning.accesskey;"
class="acknowledge_checkbox"
oncommand="gSecurityWarningDialog.toggleAcknowledge()"/>
<hbox>
<button id="getmeoutofhere"
label="&changeSettings.label;"
accesskey="&changeSettings.accesskey;"
oncommand="gSecurityWarningDialog.onCancel()"/>
<spacer flex="1"/>
<button id="iknow"
label="&doneAccount.label;"
accesskey="&doneAccount.accesskey;"
disabled="true"
oncommand="gSecurityWarningDialog.onOK()"/>
</hbox>
</vbox>
</hbox>
</vbox>
</window>

View file

@ -0,0 +1,240 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Tries to find a configuration for this ISP on the local harddisk, in the
* application install directory's "isp" subdirectory.
* Params @see fetchConfigFromISP()
*/
Components.utils.import("resource:///modules/mailServices.js");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/JXON.js");
function fetchConfigFromDisk(domain, successCallback, errorCallback)
{
return new TimeoutAbortable(runAsync(function()
{
try {
// <TB installdir>/isp/example.com.xml
var configLocation = Services.dirsvc.get("CurProcD", Ci.nsIFile);
configLocation.append("isp");
configLocation.append(sanitize.hostname(domain) + ".xml");
var contents =
readURLasUTF8(Services.io.newFileURI(configLocation));
let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
successCallback(readFromXML(JXON.build(
domParser.parseFromString(contents, "text/xml"))));
} catch (e) { errorCallback(e); }
}));
}
/**
* Tries to get a configuration from the ISP / mail provider directly.
*
* Disclaimers:
* - To support domain hosters, we cannot use SSL. That means we
* rely on insecure DNS and http, which means the results may be
* forged when under attack. The same is true for guessConfig(), though.
*
* @param domain {String} The domain part of the user's email address
* @param emailAddress {String} The user's email address
* @param successCallback {Function(config {AccountConfig}})} A callback that
* will be called when we could retrieve a configuration.
* The AccountConfig object will be passed in as first parameter.
* @param errorCallback {Function(ex)} A callback that
* will be called when we could not retrieve a configuration,
* for whatever reason. This is expected (e.g. when there's no config
* for this domain at this location),
* so do not unconditionally show this to the user.
* The first paramter will be an exception object or error string.
*/
function fetchConfigFromISP(domain, emailAddress, successCallback,
errorCallback)
{
if (!Services.prefs.getBoolPref(
"mailnews.auto_config.fetchFromISP.enabled")) {
errorCallback("ISP fetch disabled per user preference");
return;
}
let url1 = "http://autoconfig." + sanitize.hostname(domain) +
"/mail/config-v1.1.xml";
// .well-known/ <http://tools.ietf.org/html/draft-nottingham-site-meta-04>
let url2 = "http://" + sanitize.hostname(domain) +
"/.well-known/autoconfig/mail/config-v1.1.xml";
let sucAbortable = new SuccessiveAbortable();
var time = Date.now();
var urlArgs = { emailaddress: emailAddress };
if (!Services.prefs.getBoolPref(
"mailnews.auto_config.fetchFromISP.sendEmailAddress")) {
delete urlArgs.emailaddress;
}
let fetch1 = new FetchHTTP(url1, urlArgs, false,
function(result)
{
successCallback(readFromXML(result));
},
function(e1) // fetch1 failed
{
ddump("fetchisp 1 <" + url1 + "> took " + (Date.now() - time) +
"ms and failed with " + e1);
time = Date.now();
if (e1 instanceof CancelledException)
{
errorCallback(e1);
return;
}
let fetch2 = new FetchHTTP(url2, urlArgs, false,
function(result)
{
successCallback(readFromXML(result));
},
function(e2)
{
ddump("fetchisp 2 <" + url2 + "> took " + (Date.now() - time) +
"ms and failed with " + e2);
// return the error for the primary call,
// unless the fetch was cancelled
errorCallback(e2 instanceof CancelledException ? e2 : e1);
});
sucAbortable.current = fetch2;
fetch2.start();
});
sucAbortable.current = fetch1;
fetch1.start();
return sucAbortable;
}
/**
* Tries to get a configuration for this ISP from a central database at
* Mozilla servers.
* Params @see fetchConfigFromISP()
*/
function fetchConfigFromDB(domain, successCallback, errorCallback)
{
let url = Services.prefs.getCharPref("mailnews.auto_config_url");
domain = sanitize.hostname(domain);
// If we don't specify a place to put the domain, put it at the end.
if (!url.includes("{{domain}}"))
url = url + domain;
else
url = url.replace("{{domain}}", domain);
url = url.replace("{{accounts}}", MailServices.accounts.accounts.length);
if (!url.length)
return errorCallback("no fetch url set");
let fetch = new FetchHTTP(url, null, false,
function(result)
{
successCallback(readFromXML(result));
},
errorCallback);
fetch.start();
return fetch;
}
/**
* Does a lookup of DNS MX, to get the server who is responsible for
* recieving mail for this domain. Then it takes the domain of that
* server, and does another lookup (in ISPDB and possible at ISP autoconfig
* server) and if such a config is found, returns that.
*
* Disclaimers:
* - DNS is unprotected, meaning the results could be forged.
* The same is true for fetchConfigFromISP() and guessConfig(), though.
* - DNS MX tells us the incoming server, not the mailbox (IMAP) server.
* They are different. This mechnism is only an approximation
* for hosted domains (yourname.com is served by mx.hoster.com and
* therefore imap.hoster.com - that "therefore" is exactly the
* conclusional jump we make here.) and alternative domains
* (e.g. yahoo.de -> yahoo.com).
* - We make a look up for the base domain. E.g. if MX is
* mx1.incoming.servers.hoster.com, we look up hoster.com.
* Thanks to Services.eTLD, we also get bbc.co.uk right.
*
* Params @see fetchConfigFromISP()
*/
function fetchConfigForMX(domain, successCallback, errorCallback)
{
domain = sanitize.hostname(domain);
var sucAbortable = new SuccessiveAbortable();
var time = Date.now();
sucAbortable.current = getMX(domain,
function(mxHostname) // success
{
ddump("getmx took " + (Date.now() - time) + "ms");
let sld = Services.eTLD.getBaseDomainFromHost(mxHostname);
ddump("base domain " + sld + " for " + mxHostname);
if (sld == domain)
{
errorCallback("MX lookup would be no different from domain");
return;
}
sucAbortable.current = fetchConfigFromDB(sld, successCallback,
errorCallback);
},
errorCallback);
return sucAbortable;
}
/**
* Queries the DNS MX for the domain
*
* The current implementation goes to a web service to do the
* DNS resolve for us, because Mozilla unfortunately has no implementation
* to do it. That's just a workaround. Once bug 545866 is fixed, we make
* the DNS query directly on the client. The API of this function should not
* change then.
*
* Returns (in successCallback) the hostname of the MX server.
* If there are several entires with different preference values,
* only the most preferred (i.e. those with the lowest value)
* is returned. If there are several most preferred servers (i.e.
* round robin), only one of them is returned.
*
* @param domain @see fetchConfigFromISP()
* @param successCallback {function(hostname {String})
* Called when we found an MX for the domain.
* For |hostname|, see description above.
* @param errorCallback @see fetchConfigFromISP()
* @returns @see fetchConfigFromISP()
*/
function getMX(domain, successCallback, errorCallback)
{
domain = sanitize.hostname(domain);
let url = Services.prefs.getCharPref("mailnews.mx_service_url");
if (!url)
errorCallback("no URL for MX service configured");
url += domain;
let fetch = new FetchHTTP(url, null, false,
function(result)
{
// result is plain text, with one line per server.
// So just take the first line
ddump("MX query result: \n" + result + "(end)");
assert(typeof(result) == "string");
let first = result.split("\n")[0];
first.toLowerCase().replace(/[^a-z0-9\-_\.]*/g, "");
if (first.length == 0)
{
errorCallback("no MX found");
return;
}
successCallback(first);
},
errorCallback);
fetch.start();
return fetch;
}

View file

@ -0,0 +1,267 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a small wrapper around XMLHttpRequest, which solves various
* inadequacies of the API, e.g. error handling. It is entirely generic and
* can be used for purposes outside of even mail.
*
* It does not provide download progress, but assumes that the
* fetched resource is so small (<1 10 KB) that the roundtrip and
* response generation is far more significant than the
* download time of the response. In other words, it's fine for RPC,
* but not for bigger file downloads.
*/
Components.utils.import("resource://gre/modules/JXON.js");
/**
* Set up a fetch.
*
* @param url {String} URL of the server function.
* ATTENTION: The caller needs to make sure that the URL is secure to call.
* @param urlArgs {Object, associative array} Parameters to add
* to the end of the URL as query string. E.g.
* { foo: "bla", bar: "blub blub" } will add "?foo=bla&bar=blub%20blub"
* to the URL
* (unless the URL already has a "?", then it adds "&foo...").
* The values will be urlComponentEncoded, so pass them unencoded.
* @param post {Boolean} HTTP GET or POST
* Only influences the HTTP request method,
* i.e. first line of the HTTP request, not the body or parameters.
* Use POST when you modify server state,
* GET when you only request information.
*
* @param successCallback {Function(result {String})}
* Called when the server call worked (no errors).
* |result| will contain the body of the HTTP reponse, as string.
* @param errorCallback {Function(ex)}
* Called in case of error. ex contains the error
* with a user-displayable but not localized |.message| and maybe a
* |.code|, which can be either
* - an nsresult error code,
* - an HTTP result error code (0...1000) or
* - negative: 0...-100 :
* -2 = can't resolve server in DNS etc.
* -4 = response body (e.g. XML) malformed
*/
/* not yet supported:
* @param headers {Object, associative array} Like urlArgs,
* just that the params will be added as HTTP headers.
* { foo: "blub blub" } will add "Foo: Blub blub"
* The values will be urlComponentEncoded, apart from space,
* so pass them unencoded.
* @param headerArgs {Object, associative array} Like urlArgs,
* just that the params will be added as HTTP headers.
* { foo: "blub blub" } will add "X-Moz-Arg-Foo: Blub blub"
* The values will be urlComponentEncoded, apart from space,
* so pass them unencoded.
* @param bodyArgs {Object, associative array} Like urlArgs,
* just that the params will be sent x-url-encoded in the body,
* like a HTML form post.
* The values will be urlComponentEncoded, so pass them unencoded.
* This cannot be used together with |uploadBody|.
* @param uploadbody {Object} Arbitrary object, which to use as
* body of the HTTP request. Will also set the mimetype accordingly.
* Only supported object types, currently only E4X is supported
* (sending XML).
* Usually, you have nothing to upload, so just pass |null|.
*/
function FetchHTTP(url, urlArgs, post, successCallback, errorCallback)
{
assert(typeof(successCallback) == "function", "BUG: successCallback");
assert(typeof(errorCallback) == "function", "BUG: errorCallback");
this._url = sanitize.string(url);
if (!urlArgs)
urlArgs = {};
this._urlArgs = urlArgs;
this._post = sanitize.boolean(post);
this._successCallback = successCallback;
this._errorCallback = errorCallback;
}
FetchHTTP.prototype =
{
__proto__: Abortable.prototype,
_url : null, // URL as passed to ctor, without arguments
_urlArgs : null,
_post : null,
_successCallback : null,
_errorCallback : null,
_request : null, // the XMLHttpRequest object
result : null,
start : function()
{
var url = this._url;
for (var name in this._urlArgs)
{
url += (!url.includes("?") ? "?" : "&") +
name + "=" + encodeURIComponent(this._urlArgs[name]);
}
this._request = new XMLHttpRequest();
let request = this._request;
request.open(this._post ? "POST" : "GET", url);
request.channel.loadGroup = null;
// needs bug 407190 patch v4 (or higher) - uncomment if that lands.
// try {
// var channel = request.channel.QueryInterface(Ci.nsIHttpChannel2);
// channel.connectTimeout = 5;
// channel.requestTimeout = 5;
// } catch (e) { dump(e + "\n"); }
var me = this;
request.onload = function() { me._response(true); }
request.onerror = function() { me._response(false); }
request.send(null);
},
_response : function(success, exStored)
{
try
{
var errorCode = null;
var errorStr = null;
if (success && this._request.status >= 200 &&
this._request.status < 300) // HTTP level success
{
try
{
// response
var mimetype = this._request.getResponseHeader("Content-Type");
if (!mimetype)
mimetype = "";
mimetype = mimetype.split(";")[0];
if (mimetype == "text/xml" ||
mimetype == "application/xml" ||
mimetype == "text/rdf")
{
this.result = JXON.build(this._request.responseXML);
}
else
{
//ddump("mimetype: " + mimetype + " only supported as text");
this.result = this._request.responseText;
}
//ddump("result:\n" + this.result);
}
catch (e)
{
success = false;
errorStr = getStringBundle(
"chrome://messenger/locale/accountCreationUtil.properties")
.GetStringFromName("bad_response_content.error");
errorCode = -4;
}
}
else
{
success = false;
try
{
errorCode = this._request.status;
errorStr = this._request.statusText;
} catch (e) {
// If we can't resolve the hostname in DNS etc., .statusText throws
errorCode = -2;
errorStr = getStringBundle(
"chrome://messenger/locale/accountCreationUtil.properties")
.GetStringFromName("cannot_contact_server.error");
ddump(errorStr);
}
}
// Callbacks
if (success)
{
try {
this._successCallback(this.result);
} catch (e) {
logException(e);
this._error(e);
}
}
else if (exStored)
this._error(exStored);
else
this._error(new ServerException(errorStr, errorCode, this._url));
if (this._finishedCallback)
{
try {
this._finishedCallback(this);
} catch (e) {
logException(e);
this._error(e);
}
}
} catch (e) {
// error in our fetchhttp._response() code
logException(e);
this._error(e);
}
},
_error : function(e)
{
try {
this._errorCallback(e);
} catch (e) {
// error in errorCallback, too!
logException(e);
alertPrompt("Error in errorCallback for fetchhttp", e);
}
},
/**
* Call this between start() and finishedCallback fired.
*/
cancel : function(ex)
{
assert(!this.result, "Call already returned");
this._request.abort();
// Need to manually call error handler
// <https://bugzilla.mozilla.org/show_bug.cgi?id=218236#c11>
this._response(false, ex ? ex : new UserCancelledException());
},
/**
* Allows caller or lib to be notified when the call is done.
* This is useful to enable and disable a Cancel button in the UI,
* which allows to cancel the network request.
*/
setFinishedCallback : function(finishedCallback)
{
this._finishedCallback = finishedCallback;
}
}
function CancelledException(msg)
{
Exception.call(this, msg);
}
CancelledException.prototype = Object.create(Exception.prototype);
CancelledException.prototype.constructor = CancelledException;
function UserCancelledException(msg)
{
// The user knows they cancelled so I don't see a need
// for a message to that effect.
if (!msg)
msg = "User cancelled";
CancelledException.call(this, msg);
}
UserCancelledException.prototype = Object.create(CancelledException.prototype);
UserCancelledException.prototype.constructor = UserCancelledException;
function ServerException(msg, code, uri)
{
Exception.call(this, msg);
this.code = code;
this.uri = uri;
}
ServerException.prototype = Object.create(Exception.prototype);
ServerException.prototype.constructor = ServerException;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,238 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Takes an XML snipplet (as JXON) and reads the values into
* a new AccountConfig object.
* It does so securely (or tries to), by trying to avoid remote execution
* and similar holes which can appear when reading too naively.
* Of course it cannot tell whether the actual values are correct,
* e.g. it can't tell whether the host name is a good server.
*
* The XML format is documented at
* <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
*
* @param clientConfigXML {JXON} The <clientConfig> node.
* @return AccountConfig object filled with the data from XML
*/
Components.utils.import("resource:///modules/hostnameUtils.jsm");
function readFromXML(clientConfigXML)
{
function array_or_undef(value) {
return value === undefined ? [] : value;
}
var exception;
if (typeof(clientConfigXML) != "object" ||
!("clientConfig" in clientConfigXML) ||
!("emailProvider" in clientConfigXML.clientConfig))
{
dump("client config xml = " + JSON.stringify(clientConfigXML) + "\n");
var stringBundle = getStringBundle(
"chrome://messenger/locale/accountCreationModel.properties");
throw stringBundle.GetStringFromName("no_emailProvider.error");
}
var xml = clientConfigXML.clientConfig.emailProvider;
var d = new AccountConfig();
d.source = AccountConfig.kSourceXML;
d.id = sanitize.hostname(xml["@id"]);
d.displayName = d.id;
try {
d.displayName = sanitize.label(xml.displayName);
} catch (e) { logException(e); }
for (var domain of xml.$domain)
{
try {
d.domains.push(sanitize.hostname(domain));
} catch (e) { logException(e); exception = e; }
}
if (d.domains.length == 0)
throw exception ? exception : "need proper <domain> in XML";
exception = null;
// incoming server
for (let iX of array_or_undef(xml.$incomingServer)) // input (XML)
{
let iO = d.createNewIncoming(); // output (object)
try {
// throws if not supported
iO.type = sanitize.enum(iX["@type"], ["pop3", "imap", "nntp"]);
iO.hostname = sanitize.hostname(iX.hostname);
iO.port = sanitize.integerRange(iX.port, kMinPort, kMaxPort);
// We need a username even for Kerberos, need it even internally.
iO.username = sanitize.string(iX.username); // may be a %VARIABLE%
if ("password" in iX) {
d.rememberPassword = true;
iO.password = sanitize.string(iX.password);
}
for (let iXsocketType of array_or_undef(iX.$socketType))
{
try {
iO.socketType = sanitize.translate(iXsocketType,
{ plain : 1, SSL: 2, STARTTLS: 3 });
break; // take first that we support
} catch (e) { exception = e; }
}
if (!iO.socketType)
throw exception ? exception : "need proper <socketType> in XML";
exception = null;
for (let iXauth of array_or_undef(iX.$authentication))
{
try {
iO.auth = sanitize.translate(iXauth,
{ "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
// @deprecated TODO remove
"plain" : Ci.nsMsgAuthMethod.passwordCleartext,
"password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
// @deprecated TODO remove
"secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
"GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
"NTLM" : Ci.nsMsgAuthMethod.NTLM,
"OAuth2" : Ci.nsMsgAuthMethod.OAuth2 });
break; // take first that we support
} catch (e) { exception = e; }
}
if (!iO.auth)
throw exception ? exception : "need proper <authentication> in XML";
exception = null;
// defaults are in accountConfig.js
if (iO.type == "pop3" && "pop3" in iX)
{
try {
if ("leaveMessagesOnServer" in iX.pop3)
iO.leaveMessagesOnServer =
sanitize.boolean(iX.pop3.leaveMessagesOnServer);
if ("daysToLeaveMessagesOnServer" in iX.pop3)
iO.daysToLeaveMessagesOnServer =
sanitize.integer(iX.pop3.daysToLeaveMessagesOnServer);
} catch (e) { logException(e); }
try {
if ("downloadOnBiff" in iX.pop3)
iO.downloadOnBiff = sanitize.boolean(iX.pop3.downloadOnBiff);
} catch (e) { logException(e); }
}
// processed successfully, now add to result object
if (!d.incoming.hostname) // first valid
d.incoming = iO;
else
d.incomingAlternatives.push(iO);
} catch (e) { exception = e; }
}
if (!d.incoming.hostname)
// throw exception for last server
throw exception ? exception : "Need proper <incomingServer> in XML file";
exception = null;
// outgoing server
for (let oX of array_or_undef(xml.$outgoingServer)) // input (XML)
{
let oO = d.createNewOutgoing(); // output (object)
try {
if (oX["@type"] != "smtp")
{
var stringBundle = getStringBundle(
"chrome://messenger/locale/accountCreationModel.properties");
throw stringBundle.GetStringFromName("outgoing_not_smtp.error");
}
oO.hostname = sanitize.hostname(oX.hostname);
oO.port = sanitize.integerRange(oX.port, kMinPort, kMaxPort);
for (let oXsocketType of array_or_undef(oX.$socketType))
{
try {
oO.socketType = sanitize.translate(oXsocketType,
{ plain : 1, SSL: 2, STARTTLS: 3 });
break; // take first that we support
} catch (e) { exception = e; }
}
if (!oO.socketType)
throw exception ? exception : "need proper <socketType> in XML";
exception = null;
for (let oXauth of array_or_undef(oX.$authentication))
{
try {
oO.auth = sanitize.translate(oXauth,
{ // open relay
"none" : Ci.nsMsgAuthMethod.none,
// inside ISP or corp network
"client-IP-address" : Ci.nsMsgAuthMethod.none,
// hope for the best
"smtp-after-pop" : Ci.nsMsgAuthMethod.none,
"password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
// @deprecated TODO remove
"plain" : Ci.nsMsgAuthMethod.passwordCleartext,
"password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
// @deprecated TODO remove
"secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
"GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
"NTLM" : Ci.nsMsgAuthMethod.NTLM,
"OAuth2" : Ci.nsMsgAuthMethod.OAuth2,
});
break; // take first that we support
} catch (e) { exception = e; }
}
if (!oO.auth)
throw exception ? exception : "need proper <authentication> in XML";
exception = null;
if ("username" in oX ||
// if password-based auth, we need a username,
// so go there anyways and throw.
oO.auth == Ci.nsMsgAuthMethod.passwordCleartext ||
oO.auth == Ci.nsMsgAuthMethod.passwordEncrypted)
oO.username = sanitize.string(oX.username);
if ("password" in oX) {
d.rememberPassword = true;
oO.password = sanitize.string(oX.password);
}
try {
// defaults are in accountConfig.js
if ("addThisServer" in oX)
oO.addThisServer = sanitize.boolean(oX.addThisServer);
if ("useGlobalPreferredServer" in oX)
oO.useGlobalPreferredServer =
sanitize.boolean(oX.useGlobalPreferredServer);
} catch (e) { logException(e); }
// processed successfully, now add to result object
if (!d.outgoing.hostname) // first valid
d.outgoing = oO;
else
d.outgoingAlternatives.push(oO);
} catch (e) { logException(e); exception = e; }
}
if (!d.outgoing.hostname)
// throw exception for last server
throw exception ? exception : "Need proper <outgoingServer> in XML file";
exception = null;
d.inputFields = new Array();
for (let inputField of array_or_undef(xml.$inputField))
{
try {
var fieldset =
{
varname : sanitize.alphanumdash(inputField["@key"]).toUpperCase(),
displayName : sanitize.label(inputField["@label"]),
exampleValue : sanitize.label(inputField.value)
};
d.inputFields.push(fieldset);
} catch (e) { logException(e); } // for now, don't throw,
// because we don't support custom fields yet anyways.
}
return d;
}

View file

@ -0,0 +1,207 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a generic input validation lib. Use it when you process
* data from the network.
*
* Just a few functions which verify, for security purposes, that the
* input variables (strings, if nothing else is noted) are of the expected
* type and syntax.
*
* The functions take a string (unless noted otherwise) and return
* the expected datatype in JS types. If the value is not as expected,
* they throw exceptions.
*/
Components.utils.import("resource:///modules/hostnameUtils.jsm");
var sanitize =
{
integer : function(unchecked)
{
if (typeof(unchecked) == "number" && !isNaN(unchecked))
return unchecked;
var r = parseInt(unchecked);
if (isNaN(r))
throw new MalformedException("no_number.error", unchecked);
return r;
},
integerRange : function(unchecked, min, max)
{
var int = this.integer(unchecked);
if (int < min)
throw new MalformedException("number_too_small.error", unchecked);
if (int > max)
throw new MalformedException("number_too_large.error", unchecked);
return int;
},
boolean : function(unchecked)
{
if (typeof(unchecked) == "boolean")
return unchecked;
if (unchecked == "true")
return true;
if (unchecked == "false")
return false;
throw new MalformedException("boolean.error", unchecked);
},
string : function(unchecked)
{
return String(unchecked);
},
nonemptystring : function(unchecked)
{
if (!unchecked)
throw new MalformedException("string_empty.error", unchecked);
return this.string(unchecked);
},
/**
* Allow only letters, numbers, "-" and "_".
*
* Empty strings not allowed (good idea?).
*/
alphanumdash : function(unchecked)
{
var str = this.nonemptystring(unchecked);
if (!/^[a-zA-Z0-9\-\_]*$/.test(str))
throw new MalformedException("alphanumdash.error", unchecked);
return str;
},
/**
* DNS hostnames like foo.bar.example.com
* Allow only letters, numbers, "-" and "."
* Empty strings not allowed.
* Currently does not support IDN (international domain names).
*/
hostname : function(unchecked)
{
let str = cleanUpHostName(this.nonemptystring(unchecked));
// Allow placeholders. TODO move to a new hostnameOrPlaceholder()
// The regex is "anything, followed by one or more (placeholders than
// anything)". This doesn't catch the non-placeholder case, but that's
// handled down below.
if (/^[a-zA-Z0-9\-\.]*(%[A-Z0-9]+%[a-zA-Z0-9\-\.]*)+$/.test(str))
return str;
if (!isLegalHostNameOrIP(str))
throw new MalformedException("hostname_syntax.error", unchecked);
return str.toLowerCase();
},
/**
* A non-chrome URL that's safe to request.
*/
url : function (unchecked)
{
var str = this.string(unchecked);
if (!str.startsWith("http") && !str.startsWith("https"))
throw new MalformedException("url_scheme.error", unchecked);
var uri;
try {
uri = Services.io.newURI(str, null, null);
uri = uri.QueryInterface(Ci.nsIURL);
} catch (e) {
throw new MalformedException("url_parsing.error", unchecked);
}
if (uri.scheme != "http" && uri.scheme != "https")
throw new MalformedException("url_scheme.error", unchecked);
return uri.spec;
},
/**
* A value which should be shown to the user in the UI as label
*/
label : function(unchecked)
{
return this.string(unchecked);
},
/**
* Allows only certain values as input, otherwise throw.
*
* @param unchecked {Any} The value to check
* @param allowedValues {Array} List of values that |unchecked| may have.
* @param defaultValue {Any} (Optional) If |unchecked| does not match
* anything in |mapping|, a |defaultValue| can be returned instead of
* throwing an exception. The latter is the default and happens when
* no |defaultValue| is passed.
* @throws MalformedException
*/
enum : function(unchecked, allowedValues, defaultValue)
{
for (let allowedValue of allowedValues)
{
if (allowedValue == unchecked)
return allowedValue;
}
// value is bad
if (typeof(defaultValue) == "undefined")
throw new MalformedException("allowed_value.error", unchecked);
return defaultValue;
},
/**
* Like enum, allows only certain (string) values as input, but allows the
* caller to specify another value to return instead of the input value. E.g.,
* if unchecked == "foo", return 1, if unchecked == "bar", return 2,
* otherwise throw. This allows to translate string enums into integer enums.
*
* @param unchecked {Any} The value to check
* @param mapping {Object} Associative array. property name is the input
* value, property value is the output value. E.g. the example above
* would be: { foo: 1, bar : 2 }.
* Use quotes when you need freaky characters: "baz-" : 3.
* @param defaultValue {Any} (Optional) If |unchecked| does not match
* anything in |mapping|, a |defaultValue| can be returned instead of
* throwing an exception. The latter is the default and happens when
* no |defaultValue| is passed.
* @throws MalformedException
*/
translate : function(unchecked, mapping, defaultValue)
{
for (var inputValue in mapping)
{
if (inputValue == unchecked)
return mapping[inputValue];
}
// value is bad
if (typeof(defaultValue) == "undefined")
throw new MalformedException("allowed_value.error", unchecked);
return defaultValue;
}
};
function MalformedException(msgID, uncheckedBadValue)
{
var stringBundle = getStringBundle(
"chrome://messenger/locale/accountCreationUtil.properties");
var msg = stringBundle.GetStringFromName(msgID);
if (kDebug)
msg += " (bad value: " + new String(uncheckedBadValue) + ")";
Exception.call(this, msg);
}
MalformedException.prototype = Object.create(Exception.prototype);
MalformedException.prototype.constructor = MalformedException;

View file

@ -0,0 +1,304 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Some common, generic functions
*/
try {
var Cc = Components.classes;
var Ci = Components.interfaces;
} catch (e) { ddump(e); } // if already declared, as in xpcshell-tests
try {
var Cu = Components.utils;
} catch (e) { ddump(e); }
Cu.import("resource:///modules/errUtils.js");
Cu.import("resource://gre/modules/Services.jsm");
function assert(test, errorMsg)
{
if (!test)
throw new NotReached(errorMsg ? errorMsg :
"Programming bug. Assertion failed, see log.");
}
function makeCallback(obj, func)
{
return function()
{
return func.apply(obj, arguments);
}
}
/**
* Runs the given function sometime later
*
* Currently implemented using setTimeout(), but
* can later be replaced with an nsITimer impl,
* when code wants to use it in a module.
*/
function runAsync(func)
{
setTimeout(func, 0);
}
/**
* @param uriStr {String}
* @result {nsIURI}
*/
function makeNSIURI(uriStr)
{
return Services.io.newURI(uriStr, null, null);
}
/**
* Reads UTF8 data from a URL.
*
* @param uri {nsIURI} what you want to read
* @return {Array of String} the contents of the file, one string per line
*/
function readURLasUTF8(uri)
{
assert(uri instanceof Ci.nsIURI, "uri must be an nsIURI");
try {
let chan = Services.io.newChannelFromURI2(uri,
null,
Services.scriptSecurityManager.getSystemPrincipal(),
null,
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
let is = Cc["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Ci.nsIConverterInputStream);
is.init(chan.open(), "UTF-8", 1024,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
let content = "";
let strOut = new Object();
try {
while (is.readString(1024, strOut) != 0)
content += strOut.value;
// catch in outer try/catch
} finally {
is.close();
}
return content;
} catch (e) {
// TODO this has a numeric error message. We need to ship translations
// into human language.
throw e;
}
}
/**
* Takes a string (which is typically the content of a file,
* e.g. the result returned from readURLUTF8() ), and splits
* it into lines, and returns an array with one string per line
*
* Linebreaks are not contained in the result,,
* and all of \r\n, (Windows) \r (Mac) and \n (Unix) counts as linebreak.
*
* @param content {String} one long string with the whole file
* @return {Array of String} one string per line (no linebreaks)
*/
function splitLines(content)
{
content = content.replace("\r\n", "\n");
content = content.replace("\r", "\n");
return content.split("\n");
}
/**
* @param bundleURI {String} chrome URL to properties file
* @return nsIStringBundle
*/
function getStringBundle(bundleURI)
{
try {
return Services.strings.createBundle(bundleURI);
} catch (e) {
throw new Exception("Failed to get stringbundle URI <" + bundleURI +
">. Error: " + e);
}
}
function Exception(msg)
{
this._message = msg;
// get stack
try {
not.found.here += 1; // force a native exception ...
} catch (e) {
this.stack = e.stack; // ... to get the current stack
}
}
Exception.prototype =
{
get message()
{
return this._message;
},
toString : function()
{
return this._message;
}
}
function NotReached(msg)
{
Exception.call(this, msg); // call super constructor
logException(this);
}
// Make NotReached extend Exception.
NotReached.prototype = Object.create(Exception.prototype);
NotReached.prototype.constructor = NotReached;
/**
* A handle for an async function which you can cancel.
* The async function will return an object of this type (a subtype)
* and you can call cancel() when you feel like killing the function.
*/
function Abortable()
{
}
Abortable.prototype =
{
cancel : function()
{
}
}
/**
* Utility implementation, for allowing to abort a setTimeout.
* Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
* @param setTimeoutID {Integer} Return value of setTimeout()
*/
function TimeoutAbortable(setTimeoutID)
{
Abortable.call(this, setTimeoutID); // call super constructor
this._id = setTimeoutID;
}
TimeoutAbortable.prototype = Object.create(Abortable.prototype);
TimeoutAbortable.prototype.constructor = TimeoutAbortable;
TimeoutAbortable.prototype.cancel = function() { clearTimeout(this._id); }
/**
* Utility implementation, for allowing to abort a setTimeout.
* Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
* @param setIntervalID {Integer} Return value of setInterval()
*/
function IntervalAbortable(setIntervalID)
{
Abortable.call(this, setIntervalID); // call super constructor
this._id = setIntervalID;
}
IntervalAbortable.prototype = Object.create(Abortable.prototype);
IntervalAbortable.prototype.constructor = IntervalAbortable;
IntervalAbortable.prototype.cancel = function() { clearInterval(this._id); }
// Allows you to make several network calls, but return
// only one Abortable object.
function SuccessiveAbortable()
{
Abortable.call(this); // call super constructor
this._current = null;
}
SuccessiveAbortable.prototype = {
__proto__: Abortable.prototype,
get current() { return this._current; },
set current(abortable)
{
assert(abortable instanceof Abortable || abortable == null,
"need an Abortable object (or null)");
this._current = abortable;
},
cancel: function()
{
if (this._current)
this._current.cancel();
}
}
function deepCopy(org)
{
if (typeof(org) == "undefined")
return undefined;
if (org == null)
return null;
if (typeof(org) == "string")
return org;
if (typeof(org) == "number")
return org;
if (typeof(org) == "boolean")
return org == true;
if (typeof(org) == "function")
return org;
if (typeof(org) != "object")
throw "can't copy objects of type " + typeof(org) + " yet";
//TODO still instanceof org != instanceof copy
//var result = new org.constructor();
var result = new Object();
if (typeof(org.length) != "undefined")
var result = new Array();
for (var prop in org)
result[prop] = deepCopy(org[prop]);
return result;
}
if (typeof gEmailWizardLogger == "undefined") {
Cu.import("resource:///modules/gloda/log4moz.js");
var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
}
function ddump(text)
{
gEmailWizardLogger.info(text);
}
function debugObject(obj, name, maxDepth, curDepth)
{
if (curDepth == undefined)
curDepth = 0;
if (maxDepth != undefined && curDepth > maxDepth)
return "";
var result = "";
var i = 0;
for (let prop in obj)
{
i++;
try {
if (typeof(obj[prop]) == "object")
{
if (obj[prop] && obj[prop].length != undefined)
result += name + "." + prop + "=[probably array, length " +
obj[prop].length + "]\n";
else
result += name + "." + prop + "=[" + typeof(obj[prop]) + "]\n";
result += debugObject(obj[prop], name + "." + prop,
maxDepth, curDepth + 1);
}
else if (typeof(obj[prop]) == "function")
result += name + "." + prop + "=[function]\n";
else
result += name + "." + prop + "=" + obj[prop] + "\n";
} catch (e) {
result += name + "." + prop + "-> Exception(" + e + ")\n";
}
}
if (!i)
result += name + " is empty\n";
return result;
}
function alertPrompt(alertTitle, alertMsg)
{
Services.prompt.alert(window, alertTitle, alertMsg);
}

View file

@ -0,0 +1,347 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This checks a given config, by trying a real connection and login,
* with username and password.
*
* TODO
* - give specific errors, bug 555448
* - return a working |Abortable| to allow cancel
*
* @param accountConfig {AccountConfig} The guessed account config.
* username, password, realname, emailaddress etc. are not filled out,
* but placeholders to be filled out via replaceVariables().
* @param alter {boolean}
* Try other usernames and login schemes, until login works.
* Warning: Modifies |accountConfig|.
*
* This function is async.
* @param successCallback function(accountConfig)
* Called when we could guess the config.
* For accountConfig, see below.
* @param errorCallback function(ex)
* Called when we could guess not the config, either
* because we have not found anything or
* because there was an error (e.g. no network connection).
* The ex.message will contain a user-presentable message.
*/
Components.utils.import("resource:///modules/mailServices.js");
Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
if (typeof gEmailWizardLogger == "undefined") {
Cu.import("resource:///modules/gloda/log4moz.js");
var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
}
function verifyConfig(config, alter, msgWindow, successCallback, errorCallback)
{
ddump(debugObject(config, "config", 3));
assert(config instanceof AccountConfig,
"BUG: Arg 'config' needs to be an AccountConfig object");
assert(typeof(alter) == "boolean");
assert(typeof(successCallback) == "function");
assert(typeof(errorCallback) == "function");
if (MailServices.accounts.findRealServer(config.incoming.username,
config.incoming.hostname,
sanitize.enum(config.incoming.type,
["pop3", "imap", "nntp"]),
config.incoming.port)) {
errorCallback("Incoming server exists");
return;
}
// incoming server
let inServer =
MailServices.accounts.createIncomingServer(config.incoming.username,
config.incoming.hostname,
sanitize.enum(config.incoming.type,
["pop3", "imap", "nntp"]));
inServer.port = config.incoming.port;
inServer.password = config.incoming.password;
if (config.incoming.socketType == 1) // plain
inServer.socketType = Ci.nsMsgSocketType.plain;
else if (config.incoming.socketType == 2) // SSL
inServer.socketType = Ci.nsMsgSocketType.SSL;
else if (config.incoming.socketType == 3) // STARTTLS
inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
gEmailWizardLogger.info("Setting incoming server authMethod to " +
config.incoming.auth);
inServer.authMethod = config.incoming.auth;
try {
// Lookup issuer if needed.
if (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 ||
config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2) {
if (!config.oauthSettings)
config.oauthSettings = {};
if (!config.oauthSettings.issuer || !config.oauthSettings.scope) {
// lookup issuer or scope from hostname
let hostname = (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) ?
config.incoming.hostname : config.outgoing.hostname;
let hostDetails = OAuth2Providers.getHostnameDetails(hostname);
if (hostDetails)
[config.oauthSettings.issuer, config.oauthSettings.scope] = hostDetails;
if (!config.oauthSettings.issuer || !config.oauthSettings.scope)
throw "Could not get issuer for oauth2 authentication";
}
gEmailWizardLogger.info("Saving oauth parameters for issuer " +
config.oauthSettings.issuer);
inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
gEmailWizardLogger.info("OAuth2 issuer, scope is " +
config.oauthSettings.issuer + ", " + config.oauthSettings.scope);
}
if (inServer.password ||
inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2)
verifyLogon(config, inServer, alter, msgWindow,
successCallback, errorCallback);
else {
// Avoid pref pollution, clear out server prefs.
MailServices.accounts.removeIncomingServer(inServer, true);
successCallback(config);
}
return;
}
catch (e) {
gEmailWizardLogger.error("ERROR: verify logon shouldn't have failed");
}
// Avoid pref pollution, clear out server prefs.
MailServices.accounts.removeIncomingServer(inServer, true);
errorCallback(e);
}
function verifyLogon(config, inServer, alter, msgWindow, successCallback,
errorCallback)
{
gEmailWizardLogger.info("verifyLogon for server at " + inServer.hostName);
// hack - save away the old callbacks.
let saveCallbacks = msgWindow.notificationCallbacks;
// set our own callbacks - this works because verifyLogon will
// synchronously create the transport and use the notification callbacks.
let listener = new urlListener(config, inServer, alter, msgWindow,
successCallback, errorCallback);
// our listener listens both for the url and cert errors.
msgWindow.notificationCallbacks = listener;
// try to work around bug where backend is clearing password.
try {
inServer.password = config.incoming.password;
let uri = inServer.verifyLogon(listener, msgWindow);
// clear msgWindow so url won't prompt for passwords.
uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
}
catch (e) { gEmailWizardLogger.error("verifyLogon failed: " + e); throw e;}
finally {
// restore them
msgWindow.notificationCallbacks = saveCallbacks;
}
}
/**
* The url listener also implements nsIBadCertListener2. Its job is to prevent
* "bad cert" security dialogs from being shown to the user. Currently it puts
* up the cert override dialog, though we'd like to give the user more detailed
* information in the future.
*/
function urlListener(config, server, alter, msgWindow, successCallback,
errorCallback)
{
this.mConfig = config;
this.mServer = server;
this.mAlter = alter;
this.mSuccessCallback = successCallback;
this.mErrorCallback = errorCallback;
this.mMsgWindow = msgWindow;
this.mCertError = false;
this._log = Log4Moz.getConfiguredLogger("mail.wizard");
}
urlListener.prototype =
{
OnStartRunningUrl: function(aUrl)
{
this._log.info("Starting to test username");
this._log.info(" username=" + (this.mConfig.incoming.username !=
this.mConfig.identity.emailAddress) +
", have savedUsername=" +
(this.mConfig.usernameSaved ? "true" : "false"));
this._log.info(" authMethod=" + this.mServer.authMethod);
},
OnStopRunningUrl: function(aUrl, aExitCode)
{
this._log.info("Finished verifyConfig resulted in " + aExitCode);
if (Components.isSuccessCode(aExitCode))
{
this._cleanup();
this.mSuccessCallback(this.mConfig);
}
// Logon failed, and we aren't supposed to try other variations.
else if (!this.mAlter)
{
this._cleanup();
var errorMsg = getStringBundle(
"chrome://messenger/locale/accountCreationModel.properties")
.GetStringFromName("cannot_login.error");
this.mErrorCallback(new Exception(errorMsg));
}
// Try other variations, unless there's a cert error, in which
// case we'll see what the user chooses.
else if (!this.mCertError)
{
this.tryNextLogon()
}
},
tryNextLogon: function()
{
this._log.info("tryNextLogon()");
this._log.info(" username=" + (this.mConfig.incoming.username !=
this.mConfig.identity.emailAddress) +
", have savedUsername=" +
(this.mConfig.usernameSaved ? "true" : "false"));
this._log.info(" authMethod=" + this.mServer.authMethod);
// check if we tried full email address as username
if (this.mConfig.incoming.username != this.mConfig.identity.emailAddress)
{
this._log.info(" Changing username to email address.");
this.mConfig.usernameSaved = this.mConfig.incoming.username;
this.mConfig.incoming.username = this.mConfig.identity.emailAddress;
this.mConfig.outgoing.username = this.mConfig.identity.emailAddress;
this.mServer.username = this.mConfig.incoming.username;
this.mServer.password = this.mConfig.incoming.password;
verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
this.mSuccessCallback, this.mErrorCallback);
return;
}
if (this.mConfig.usernameSaved)
{
this._log.info(" Re-setting username.");
// If we tried the full email address as the username, then let's go
// back to trying just the username before trying the other cases.
this.mConfig.incoming.username = this.mConfig.usernameSaved;
this.mConfig.outgoing.username = this.mConfig.usernameSaved;
this.mConfig.usernameSaved = null;
this.mServer.username = this.mConfig.incoming.username;
this.mServer.password = this.mConfig.incoming.password;
}
// sec auth seems to have failed, and we've tried both
// varieties of user name, sadly.
// So fall back to non-secure auth, and
// again try the user name and email address as username
assert(this.mConfig.incoming.auth == this.mServer.authMethod);
this._log.info(" Using SSL: " +
(this.mServer.socketType == Ci.nsMsgSocketType.SSL ||
this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS));
if (this.mConfig.incoming.authAlternatives &&
this.mConfig.incoming.authAlternatives.length)
// We may be dropping back to insecure auth methods here,
// which is not good. But then again, we already warned the user,
// if it is a config without SSL.
{
this._log.info(" auth alternatives = " +
this.mConfig.incoming.authAlternatives.join(","));
this._log.info(" Decreasing auth.");
this._log.info(" Have password: " +
(this.mServer.password ? "true" : "false"));
let brokenAuth = this.mConfig.incoming.auth;
// take the next best method (compare chooseBestAuthMethod() in guess)
this.mConfig.incoming.auth =
this.mConfig.incoming.authAlternatives.shift();
this.mServer.authMethod = this.mConfig.incoming.auth;
// Assume that SMTP server has same methods working as incoming.
// Broken assumption, but we currently have no SMTP verification.
// TODO implement real SMTP verification
if (this.mConfig.outgoing.auth == brokenAuth &&
this.mConfig.outgoing.authAlternatives.indexOf(
this.mConfig.incoming.auth) != -1)
this.mConfig.outgoing.auth = this.mConfig.incoming.auth;
this._log.info(" outgoing auth: " + this.mConfig.outgoing.auth);
verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
this.mSuccessCallback, this.mErrorCallback);
return;
}
// Tried all variations we can. Give up.
this._log.info("Giving up.");
this._cleanup();
let errorMsg = getStringBundle(
"chrome://messenger/locale/accountCreationModel.properties")
.GetStringFromName("cannot_login.error");
this.mErrorCallback(new Exception(errorMsg));
return;
},
_cleanup : function()
{
try {
// Avoid pref pollution, clear out server prefs.
if (this.mServer) {
MailServices.accounts.removeIncomingServer(this.mServer, true);
this.mServer = null;
}
} catch (e) { this._log.error(e); }
},
// Suppress any certificate errors
notifyCertProblem: function(socketInfo, status, targetSite) {
this.mCertError = true;
this._log.error("cert error");
let self = this;
setTimeout(function () {
try {
self.informUserOfCertError(socketInfo, status, targetSite);
} catch (e) { logException(e); }
}, 0);
return true;
},
informUserOfCertError : function(socketInfo, status, targetSite) {
var params = {
exceptionAdded : false,
sslStatus : status,
prefetchCert : true,
location : targetSite,
};
window.openDialog("chrome://pippki/content/exceptionDialog.xul",
"","chrome,centerscreen,modal", params);
this._log.info("cert exception dialog closed");
this._log.info("cert exceptionAdded = " + params.exceptionAdded);
if (!params.exceptionAdded) {
this._cleanup();
let errorMsg = getStringBundle(
"chrome://messenger/locale/accountCreationModel.properties")
.GetStringFromName("cannot_login.error");
this.mErrorCallback(new Exception(errorMsg));
}
else {
// Retry the logon now that we've added the cert exception.
verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
this.mSuccessCallback, this.mErrorCallback);
}
},
// nsIInterfaceRequestor
getInterface: function(iid) {
return this.QueryInterface(iid);
},
// nsISupports
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
!iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
!iid.equals(Components.interfaces.nsIUrlListener) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
}