Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
checkpoint; still writing unit tests, but largely there
Browse files Browse the repository at this point in the history
  • Loading branch information
asutherland committed Dec 14, 2012
1 parent a3d81b7 commit bbdf316
Show file tree
Hide file tree
Showing 16 changed files with 920 additions and 155 deletions.
10 changes: 6 additions & 4 deletions data/lib/imap.js
Expand Up @@ -749,17 +749,19 @@ ImapConnection.prototype.connect = function(loginCb) {
};
this._state.conn.onerror = function(evt) {
try {
var err = evt.data;
var err = evt.data, errType = 'unknown';
// (only do error probing on things we can safely use 'in' on)
if (err && typeof(err) === 'object') {
// detect an nsISSLStatus instance by an unusual property.
if ('isNotValidAtThisTime' in err)
err = 'bad-security';
if ('isNotValidAtThisTime' in err) {
err = new Error('SSL error');
errType = err.type = 'bad-security';
}
}
clearTimeoutFunc(self._state.tmrConn);
if (self._state.status === STATES.NOCONNECT) {
var connErr = new Error('Unable to connect. Reason: ' + err);
connErr.type = 'unknown';
connErr.type = errType;
connErr.serverResponse = '';
loginCb(connErr);
}
Expand Down
174 changes: 123 additions & 51 deletions data/lib/mailapi/accountcommon.js
Expand Up @@ -221,7 +221,7 @@ CompositeAccount.prototype = {
this.universe.appendMessages(sentFolder.id,
[message]);
}
callback(err, errDetails);
callback(err, errDetails, null);
}.bind(this));

},
Expand Down Expand Up @@ -362,18 +362,18 @@ Configurators['imap+smtp'] = {
['imap', 'smtp'],
function probesDone(results) {
// -- both good?
if (!results.imap[0] && results.smtp) {
if (results.imap[0] === null && results.smtp === null) {
var account = self._defineImapAccount(
universe,
userDetails, credentials,
imapConnInfo, smtpConnInfo, results.imap[1],
results.imap[2]);
callback(null, account);
callback(null, account, null);
}
// -- either/both bad
else {
// clean up the imap connection if it was okay but smtp failed
if (!results.imap[0]) {
if (results.imap[0] === null) {
results.imap[1].die();
// Failure was caused by SMTP, but who knows why
callback('smtp-unknown', null);
Expand Down Expand Up @@ -434,7 +434,7 @@ Configurators['imap+smtp'] = {

var account = this._loadAccount(universe, accountDef,
oldAccountInfo.folderInfo);
callback(null, account);
callback(null, account, null);
},

/**
Expand Down Expand Up @@ -538,7 +538,7 @@ Configurators['fake'] = {
};

var account = this._loadAccount(universe, accountDef);
callback(null, account);
callback(null, account, null);
},

recreateAccount: function cfg_fake_ra(universe, oldVersion, oldAccountInfo,
Expand Down Expand Up @@ -568,7 +568,7 @@ Configurators['fake'] = {
};

var account = this._loadAccount(universe, accountDef);
callback(null, account);
callback(null, account, null);
},

/**
Expand Down Expand Up @@ -603,18 +603,34 @@ Configurators['activesync'] = {
conn.timeout = $asacct.DEFAULT_TIMEOUT_MS;

conn.connect(function(error, options) {
// XXX: Think about what to do with this error handling, since it's
// replicated in the autoconfig code.
if (error) {
var failureType = 'unknown';
// This error is basically an indication of whether we were able to
// call getOptions or not. If the XHR request completed, we get an
// HttpError. If we timed out or an XHR error occurred, we get a
// general Error.
var failureType,
failureDetails = { server: domainInfo.incoming.server };

if (error instanceof $asproto.HttpError) {
if (error.status === 401)
if (error.status === 401) {
failureType = 'bad-user-or-pass';
else if (error.status === 403)
}
else if (error.status === 403) {
failureType = 'not-authorized';
}
// Treat any other errors where we talked to the server as a problem
// with the server.
else {
failureType = 'server-problem';
failureDetails.status = error.status;
}
}
else {
// We didn't talk to the server, so let's call it an unresponsive
// server.
failureType = 'unresponsive-server';
}
callback(failureType, null);
callback(failureType, null, failureDetails);
return;
}

Expand Down Expand Up @@ -644,7 +660,7 @@ Configurators['activesync'] = {
};

var account = self._loadAccount(universe, accountDef, conn);
callback(null, account);
callback(null, account, null);
});
},

Expand Down Expand Up @@ -673,7 +689,7 @@ Configurators['activesync'] = {
};

var account = this._loadAccount(universe, accountDef, null);
callback(null, account);
callback(null, account, null);
},

/**
Expand Down Expand Up @@ -760,6 +776,20 @@ function Autoconfigurator(_LOG) {
}
exports.Autoconfigurator = Autoconfigurator;
Autoconfigurator.prototype = {
/**
* The list of fatal error codes.
*
* What's fatal and why:
* - bad-user-or-pass: We found a server, it told us the credentials were
* bogus. There is no point going on.
* - not-authorized: We found a server, it told us the credentials are fine
* but the access rights are insufficient. There is no point going on.
*
* Non-fatal and why:
* - unknown: If something failed we should keep checking other info sources.
* - no-config-info: The specific source had no details; we should keep
* checking other sources.
*/
_fatalErrors: ['bad-user-or-pass', 'not-authorized'],

/**
Expand Down Expand Up @@ -791,7 +821,9 @@ Autoconfigurator.prototype = {

xhr.onload = function() {
if (xhr.status < 200 || xhr.status >= 300) {
callback('unknown');
// Non-fatal failure to get the config info. While a 404 is the
// expected case, this is the appropriate error for weirder cases too.
callback('no-config-info', null, { status: xhr.status });
return;
}
// XXX: For reasons which are currently unclear (possibly a platform
Expand Down Expand Up @@ -823,19 +855,36 @@ Autoconfigurator.prototype = {
config.type = 'imap+smtp';
for (let [,child] in Iterator(outgoing.children))
config.outgoing[child.tagName] = child.textContent;

// We do not support unencrypted connections outside of unit tests.
if (config.incoming.socketType !== 'SSL' ||
config.outgoing.socketType !== 'SSL') {
callback('no-config-info', null, { status: 'unsafe' });
return;
}
}
else {
callback('unknown');
callback('no-config-info', null, { status: 'no-outgoing' });
return;
}

callback(null, config);
callback(null, config, null);
}
else {
callback('unknown');
callback('no-config-info', null, { status: 'no-incoming' });
}
};

xhr.ontimeout = xhr.onerror = function() { callback('unknown'); };
xhr.ontimeout = xhr.onerror = function() {
// The effective result is a failure to get configuration info, but make
// sure the status conveys that a timeout occurred.
callback('no-config-info', null, { status: 'timeout' });
};
xhr.onerror = function() {
// The effective result is a failure to get configuration info, but make
// sure the status conveys that a timeout occurred.
callback('no-config-info', null, { status: 'error' });
};

xhr.send();
},
Expand Down Expand Up @@ -864,15 +913,18 @@ Autoconfigurator.prototype = {
$asproto.autodiscover(userDetails.emailAddress, userDetails.password,
this.timeout, function(error, config) {
if (error) {
var failureType = 'unknown';
var failureType = 'no-config-info',
failureDetails = {};

if (error instanceof $asproto.HttpError) {
if (error.status === 401)
failureType = 'bad-user-or-pass';
else if (error.status === 403)
failureType = 'not-authorized';
else
failureDetails.status = error.status;
}
callback(failureType);
callback(failureType, null, failureDetails);
return;
}

Expand All @@ -884,7 +936,7 @@ Autoconfigurator.prototype = {
username: config.user.email
},
};
callback(null, autoconfig);
callback(null, autoconfig, null);
});
},

Expand All @@ -905,15 +957,19 @@ Autoconfigurator.prototype = {
let url = 'http://autoconfig.' + domain + suffix;
let self = this;

this._getXmlConfig(url, function(error, config) {
if (self._isSuccessOrFatal(error))
return callback(error, config);
this._getXmlConfig(url, function(error, config, errorDetails) {
if (self._isSuccessOrFatal(error)) {
callback(error, config, errorDetails);
return;
}

// See <http://tools.ietf.org/html/draft-nottingham-site-meta-04>.
let url = 'http://' + domain + '/.well-known/autoconfig' + suffix;
self._getXmlConfig(url, function(error, config) {
if (self._isSuccessOrFatal(error))
return callback(error, config);
self._getXmlConfig(url, function(error, config, errorDetails) {
if (self._isSuccessOrFatal(error)) {
callback(error, config, errorDetails);
return;
}

console.log(' Trying domain autodiscover');
self._getConfigFromAutodiscover(userDetails, callback);
Expand Down Expand Up @@ -949,12 +1005,17 @@ Autoconfigurator.prototype = {

xhr.onload = function() {
if (xhr.status === 200)
callback(null, xhr.responseText.split('\n')[0]);
callback(null, xhr.responseText.split('\n')[0], null);
else
callback('unknown');
callback('no-config-info', null, { status: 'mx' + xhr.status });
};

xhr.ontimeout = xhr.onerror = function() { callback('unknown'); };
xhr.ontimeout = function() {
callback('no-config-info', null, { status: 'mxtimeout' });
};
xhr.onerror = function() {
callback('no-config-info', null, { status: 'mxerror' });
};

xhr.send();
},
Expand All @@ -969,9 +1030,9 @@ Autoconfigurator.prototype = {
*/
_getConfigFromMX: function getConfigFromMX(domain, callback) {
let self = this;
this._getMX(domain, function(error, mxDomain) {
this._getMX(domain, function(error, mxDomain, errorDetails) {
if (error)
return callback(error);
return callback(error, null, errorDetails);

// XXX: We need to normalize the domain here to get the base domain, but
// that's complicated because people like putting dots in TLDs. For now,
Expand All @@ -980,15 +1041,19 @@ Autoconfigurator.prototype = {
console.log(' Found MX for', mxDomain);

if (domain === mxDomain)
return callback('unknown');
return callback('no-config-info', null, { status: 'mxsame' });

// If we found a different domain after MX lookup, we should look in our
// local file store (mostly to support Google Apps domains) and, if that
// doesn't work, the Mozilla ISPDB.
console.log(' Looking in local file store');
self._getConfigFromLocalFile(mxDomain, function(error, config) {
if (!error)
return callback(error, config);
self._getConfigFromLocalFile(mxDomain, function(error, config,
errorDetails) {
// (Local XML lookup should not have any fatal errors)
if (!error) {
callback(error, config, errorDetails);
return;
}

console.log(' Looking in the Mozilla ISPDB');
self._getConfigFromDB(mxDomain, callback);
Expand Down Expand Up @@ -1022,7 +1087,7 @@ Autoconfigurator.prototype = {
.replace('%REALNAME%', userDetails.displayName);
}

function onComplete(error, config) {
function onComplete(error, config, errorDetails) {
console.log(error ? 'FAILURE' : 'SUCCESS');

// Fill any placeholder strings in the configuration object we retrieved.
Expand All @@ -1039,7 +1104,7 @@ Autoconfigurator.prototype = {
}
}

callback(error, config);
callback(error, config, errorDetails);
}

console.log(' Looking in GELAM');
Expand All @@ -1050,19 +1115,26 @@ Autoconfigurator.prototype = {

let self = this;
console.log(' Looking in local file store');
this._getConfigFromLocalFile(domain, function(error, config) {
if (self._isSuccessOrFatal(error))
return onComplete(error, config);
this._getConfigFromLocalFile(domain, function(error, config, errorDetails) {
if (self._isSuccessOrFatal(error)) {
onComplete(error, config, errorDetails);
return;
}

console.log(' Looking at domain');
self._getConfigFromDomain(userDetails, domain, function(error, config) {
if (self._isSuccessOrFatal(error))
return onComplete(error, config);
self._getConfigFromDomain(userDetails, domain, function(error, config,
errorDetails) {
if (self._isSuccessOrFatal(error)) {
onComplete(error, config, errorDetails);
return;
}

console.log(' Looking in the Mozilla ISPDB');
self._getConfigFromDB(domain, function(error, config) {
if (self._isSuccessOrFatal(error))
return onComplete(error, config);
self._getConfigFromDB(domain, function(error, config, errorDetails) {
if (self._isSuccessOrFatal(error)) {
onComplete(error, config, errorDetails);
return;
}

console.log(' Looking up MX');
self._getConfigFromMX(domain, onComplete);
Expand All @@ -1084,9 +1156,9 @@ Autoconfigurator.prototype = {
*/
tryToCreateAccount: function(universe, userDetails, callback) {
let self = this;
this.getConfig(userDetails, function(error, config) {
this.getConfig(userDetails, function(error, config, errorDetails) {
if (error)
return callback(error);
return callback(error, null, errorDetails);

var configurator = Configurators[config.type];
configurator.tryToCreateAccount(universe, userDetails, config,
Expand Down

0 comments on commit bbdf316

Please sign in to comment.