diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mail/components/accountcreation/CreateInBackend.jsm | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/CreateInBackend.jsm b/comm/mail/components/accountcreation/CreateInBackend.jsm new file mode 100644 index 0000000000..c254bbb44b --- /dev/null +++ b/comm/mail/components/accountcreation/CreateInBackend.jsm @@ -0,0 +1,459 @@ +/* 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/. */ + +const EXPORTED_SYMBOLS = ["CreateInBackend"]; + +const lazy = {}; + +ChromeUtils.defineModuleGetter( + lazy, + "AccountConfig", + "resource:///modules/accountcreation/AccountConfig.jsm" +); +ChromeUtils.defineModuleGetter( + lazy, + "AccountCreationUtils", + "resource:///modules/accountcreation/AccountCreationUtils.jsm" +); + +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/* eslint-disable complexity */ +/** + * Takes an |AccountConfig| JS object and creates that account in the + * Thunderbird backend (which also writes it to prefs). + * + * @param {AccountConfig} config - The account to create + * @returns {nsIMsgAccount} - the newly created account + */ +function createAccountInBackend(config) { + // incoming server + let inServer = MailServices.accounts.createIncomingServer( + config.incoming.username, + config.incoming.hostname, + config.incoming.type + ); + inServer.port = config.incoming.port; + inServer.authMethod = config.incoming.auth; + inServer.password = config.incoming.password; + // This new CLIENTID is for the outgoing server, and will be applied to the + // incoming only if the incoming username and hostname match the outgoing. + // We must generate this unconditionally because we cannot determine whether + // the outgoing server has clientid enabled yet or not, and we need to do it + // here in order to populate the incoming server if the outgoing matches. + let newOutgoingClientid = Services.uuid + .generateUUID() + .toString() + .replace(/[{}]/g, ""); + // Grab the base domain of both incoming and outgoing hostname in order to + // compare the two to detect if the base domain is the same. + let incomingBaseDomain; + let outgoingBaseDomain; + try { + incomingBaseDomain = Services.eTLD.getBaseDomainFromHost( + config.incoming.hostname + ); + } catch (e) { + incomingBaseDomain = config.incoming.hostname; + } + try { + outgoingBaseDomain = Services.eTLD.getBaseDomainFromHost( + config.outgoing.hostname + ); + } catch (e) { + outgoingBaseDomain = config.outgoing.hostname; + } + if ( + config.incoming.username == config.outgoing.username && + incomingBaseDomain == outgoingBaseDomain + ) { + inServer.clientid = newOutgoingClientid; + } else { + // If the username/hostname are different then generate a new CLIENTID. + inServer.clientid = Services.uuid + .generateUUID() + .toString() + .replace(/[{}]/g, ""); + } + + if (config.rememberPassword && config.incoming.password) { + rememberPassword(inServer, config.incoming.password); + } + + if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) { + inServer.setUnicharValue( + "oauth2.scope", + config.incoming.oauthSettings.scope + ); + inServer.setUnicharValue( + "oauth2.issuer", + config.incoming.oauthSettings.issuer + ); + } + + // SSL + inServer.socketType = config.incoming.socketType; + + // If we already have an account with an identical name, generate a unique + // name for the new account to avoid duplicates. + inServer.prettyName = checkAccountNameAlreadyExists( + config.identity.emailAddress + ) + ? generateUniqueAccountName(config) + : config.identity.emailAddress; + + inServer.doBiff = true; + inServer.biffMinutes = config.incoming.checkInterval; + inServer.setBoolValue("login_at_startup", config.incoming.loginAtStartup); + if (config.incoming.type == "pop3") { + inServer.setBoolValue( + "leave_on_server", + config.incoming.leaveMessagesOnServer + ); + inServer.setIntValue( + "num_days_to_leave_on_server", + config.incoming.daysToLeaveMessagesOnServer + ); + inServer.setBoolValue( + "delete_mail_left_on_server", + config.incoming.deleteOnServerWhenLocalDelete + ); + inServer.setBoolValue( + "delete_by_age_from_server", + config.incoming.deleteByAgeFromServer + ); + inServer.setBoolValue("download_on_biff", config.incoming.downloadOnBiff); + } + if (config.incoming.owaURL) { + inServer.setUnicharValue("owa_url", config.incoming.owaURL); + } + if (config.incoming.ewsURL) { + inServer.setUnicharValue("ews_url", config.incoming.ewsURL); + } + if (config.incoming.easURL) { + inServer.setUnicharValue("eas_url", config.incoming.easURL); + } + inServer.valid = true; + + let username = + config.outgoing.auth != Ci.nsMsgAuthMethod.none + ? config.outgoing.username + : null; + let outServer = MailServices.smtp.findServer( + username, + config.outgoing.hostname + ); + lazy.AccountCreationUtils.assert( + config.outgoing.addThisServer || + config.outgoing.useGlobalPreferredServer || + config.outgoing.existingServerKey, + "No SMTP server: inconsistent flags" + ); + + if ( + config.outgoing.addThisServer && + !outServer && + !config.incoming.useGlobalPreferredServer + ) { + outServer = MailServices.smtp.createServer(); + outServer.hostname = config.outgoing.hostname; + outServer.port = config.outgoing.port; + outServer.authMethod = config.outgoing.auth; + // Populate the clientid if it is enabled for this outgoing server. + if (outServer.clientidEnabled) { + outServer.clientid = newOutgoingClientid; + } + if (config.outgoing.auth != Ci.nsMsgAuthMethod.none) { + outServer.username = username; + outServer.password = config.outgoing.password; + if (config.rememberPassword && config.outgoing.password) { + rememberPassword(outServer, config.outgoing.password); + } + } + + if (outServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) { + let prefBranch = "mail.smtpserver." + outServer.key + "."; + Services.prefs.setCharPref( + prefBranch + "oauth2.scope", + config.outgoing.oauthSettings.scope + ); + Services.prefs.setCharPref( + prefBranch + "oauth2.issuer", + config.outgoing.oauthSettings.issuer + ); + } + + outServer.socketType = config.outgoing.socketType; + outServer.description = config.displayName; + + // 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.useGlobalPreferredServer && + !config.incoming.useGlobalPreferredServer + ) { + if (config.outgoing.existingServerKey) { + identity.smtpServerKey = config.outgoing.existingServerKey; + } else { + 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) { + lazy.AccountCreationUtils.ddump("Could not write out prefs: " + ex); + } + return account; +} +/* eslint-enable complexity */ + +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) { + let passwordURI; + if (server instanceof Ci.nsIMsgIncomingServer) { + passwordURI = server.localStoreType + "://" + server.hostName; + } else if (server instanceof Ci.nsISmtpServer) { + passwordURI = "smtp://" + server.hostname; + } else { + throw new lazy.AccountCreationUtils.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) + * @returns {nsIMsgIncomingServer} If it already exists, the server + * object is returned. + * If it's a new server, |null| is returned. + */ +function checkIncomingServerAlreadyExists(config) { + lazy.AccountCreationUtils.assert(config instanceof lazy.AccountConfig); + let incoming = config.incoming; + let existing = MailServices.accounts.findServer( + incoming.username, + incoming.hostname, + incoming.type, + 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.findServer( + config.identity.emailAddress, + incoming.hostname, + incoming.type, + 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) + * @returns {nsISmtpServer} If it already exists, the server + * object is returned. + * If it's a new server, |null| is returned. + */ +function checkOutgoingServerAlreadyExists(config) { + lazy.AccountCreationUtils.assert(config instanceof lazy.AccountConfig); + for (let existingServer of MailServices.smtp.servers) { + // 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 whether the user's setup already has an account with the same email + * address. This might happen if the user uses the same email for different + * protocols (eg. IMAP and POP3). + * + * @param {string} name - The name or email address of the new account. + * @returns {boolean} True if an account with the same name is found. + */ +function checkAccountNameAlreadyExists(name) { + return MailServices.accounts.accounts.some( + a => a.incomingServer.prettyName == name + ); +} + +/** + * Generate a unique account name by appending the incoming protocol type, and + * a counter if necessary. + * + * @param {AccountConfig} config - The config data of the account being created. + * @returns {string} - The unique account name. + */ +function generateUniqueAccountName(config) { + // Generate a potential unique name. e.g. "foo@bar.com (POP3)". + let name = `${ + config.identity.emailAddress + } (${config.incoming.type.toUpperCase()})`; + + // If this name already exists, append a counter until we find a unique name. + if (checkAccountNameAlreadyExists(name)) { + let counter = 2; + while (checkAccountNameAlreadyExists(`${name}_${counter}`)) { + counter++; + } + // e.g. "foo@bar.com (POP3)_1". + name = `${name}_${counter}`; + } + + return name; +} + +/** + * 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) { + lazy.AccountCreationUtils.ddump( + "Error! we should have found the local mail server " + + "after we created it." + ); + } + } + } catch (ex) { + lazy.AccountCreationUtils.ddump("Error in verifyLocalFoldersAccount " + ex); + } +} + +var CreateInBackend = { + checkIncomingServerAlreadyExists, + checkOutgoingServerAlreadyExists, + createAccountInBackend, +}; |