var requirejs, require, define; (function (global, undef) { var prim, topReq, dataMain, src, subPath, bootstrapConfig = requirejs || require, hasOwn = Object.prototype.hasOwnProperty, contexts = {}, queue = [], currDirRegExp = /^\.\//, urlRegExp = /^\/|\:|\?|\.js$/, commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/; if (typeof requirejs === 'function') { return; } function hasProp(obj, prop) { return hasOwn.call(obj, prop); } function getOwn(obj, prop) { return obj && hasProp(obj, prop) && obj[prop]; } function eachProp(obj, func) { var prop; for (prop in obj) { if (hasProp(obj, prop)) { if (func(obj[prop], prop)) { break; } } } } function mixin(target, source, force, deepStringMixin) { if (source) { eachProp(source, function (value, prop) { if (force || !hasProp(target, prop)) { if (deepStringMixin && typeof value === 'object' && value && !Array.isArray(value) && typeof value !== 'function' && !(value instanceof RegExp)) { if (!target[prop]) { target[prop] = {}; } mixin(target[prop], value, force, deepStringMixin); } else { target[prop] = value; } } }); } return target; } function getGlobal(value) { if (!value) { return value; } var g = global; value.split('.').forEach(function (part) { g = g[part]; }); return g; } (function () { 'use strict'; var waitingId, nextTick, waiting = []; function callWaiting() { waitingId = 0; var w = waiting; waiting = []; while (w.length) { w.shift()(); } } function asyncTick(fn) { waiting.push(fn); if (!waitingId) { waitingId = setTimeout(callWaiting, 0); } } function syncTick(fn) { fn(); } function isFunObj(x) { var type = typeof x; return type === 'object' || type === 'function'; } nextTick = typeof setImmediate === 'function' ? setImmediate.bind() : typeof process !== 'undefined' && process.nextTick ? process.nextTick : typeof setTimeout !== 'undefined' ? asyncTick : syncTick; function notify(ary, value) { prim.nextTick(function () { ary.forEach(function (item) { item(value); }); }); } function callback(p, ok, yes) { if (p.hasOwnProperty('v')) { prim.nextTick(function () { yes(p.v); }); } else { ok.push(yes); } } function errback(p, fail, no) { if (p.hasOwnProperty('e')) { prim.nextTick(function () { no(p.e); }); } else { fail.push(no); } } prim = function prim(fn) { var promise, f, p = {}, ok = [], fail = []; function makeFulfill() { var f, f2, called = false; function fulfill(v, prop, listeners) { if (called) { return; } called = true; if (promise === v) { called = false; f.reject(new TypeError('value is same promise')); return; } try { var then = v && v.then; if (isFunObj(v) && typeof then === 'function') { f2 = makeFulfill(); then.call(v, f2.resolve, f2.reject); } else { p[prop] = v; notify(listeners, v); } } catch (e) { called = false; f.reject(e); } } f = { resolve: function (v) { fulfill(v, 'v', ok); }, reject: function (e) { fulfill(e, 'e', fail); } }; return f; } f = makeFulfill(); promise = { then: function (yes, no) { var next = prim(function (nextResolve, nextReject) { function finish(fn, nextFn, v) { try { if (fn && typeof fn === 'function') { v = fn(v); nextResolve(v); } else { nextFn(v); } } catch (e) { nextReject(e); } } callback(p, ok, finish.bind(undefined, yes, nextResolve)); errback(p, fail, finish.bind(undefined, no, nextReject)); }); return next; }, catch: function (no) { return promise.then(null, no); } }; try { fn(f.resolve, f.reject); } catch (e) { f.reject(e); } return promise; }; prim.resolve = function (value) { return prim(function (yes) { yes(value); }); }; prim.reject = function (err) { return prim(function (yes, no) { no(err); }); }; prim.cast = function (x) { if (isFunObj(x) && 'then' in x) { return x; } else { return prim(function (yes, no) { if (x instanceof Error) { no(x); } else { yes(x); } }); } }; prim.all = function (ary) { return prim(function (yes, no) { var count = 0, length = ary.length, result = []; function resolved(i, v) { result[i] = v; count += 1; if (count === length) { yes(result); } } ary.forEach(function (item, i) { prim.cast(item).then(function (v) { resolved(i, v); }, function (err) { no(err); }); }); }); }; prim.nextTick = nextTick; }()); function newContext(contextName) { var req, main, makeMap, callDep, handlers, checkingLater, load, context, defined = {}, waiting = {}, config = { waitSeconds: 7, baseUrl: './', paths: {}, bundles: {}, pkgs: {}, shim: {}, config: {} }, mapCache = {}, requireDeferreds = [], deferreds = {}, calledDefine = {}, calledPlugin = {}, loadCount = 0, startTime = new Date().getTime(), errCount = 0, trackedErrors = {}, urlFetched = {}, bundlesMap = {}; function trimDots(ary) { var i, part, length = ary.length; for (i = 0; i < length; i++) { part = ary[i]; if (part === '.') { ary.splice(i, 1); i -= 1; } else if (part === '..') { if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { break; } else if (i > 0) { ary.splice(i - 1, 2); i -= 2; } } } } function normalize(name, baseName, applyMap) { var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, foundMap, foundI, foundStarMap, starI, baseParts = baseName && baseName.split('/'), normalizedBaseParts = baseParts, map = config.map, starMap = map && map['*']; if (name && name.charAt(0) === '.') { if (baseName) { normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); name = name.split('/'); lastIndex = name.length - 1; if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); } name = normalizedBaseParts.concat(name); trimDots(name); name = name.join('/'); } else if (name.indexOf('./') === 0) { name = name.substring(2); } } if (applyMap && map && (baseParts || starMap)) { nameParts = name.split('/'); outerLoop: for (i = nameParts.length; i > 0; i -= 1) { nameSegment = nameParts.slice(0, i).join('/'); if (baseParts) { for (j = baseParts.length; j > 0; j -= 1) { mapValue = getOwn(map, baseParts.slice(0, j).join('/')); if (mapValue) { mapValue = getOwn(mapValue, nameSegment); if (mapValue) { foundMap = mapValue; foundI = i; break outerLoop; } } } } if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { foundStarMap = getOwn(starMap, nameSegment); starI = i; } } if (!foundMap && foundStarMap) { foundMap = foundStarMap; foundI = starI; } if (foundMap) { nameParts.splice(0, foundI, foundMap); name = nameParts.join('/'); } } pkgMain = getOwn(config.pkgs, name); return pkgMain ? pkgMain : name; } function makeShimExports(value) { function fn() { var ret; if (value.init) { ret = value.init.apply(global, arguments); } return ret || value.exports && getGlobal(value.exports); } return fn; } function takeQueue(anonId) { var i, id, args, shim; for (i = 0; i < queue.length; i += 1) { if (typeof queue[i][0] !== 'string') { if (anonId) { queue[i].unshift(anonId); anonId = undef; } else { break; } } args = queue.shift(); id = args[0]; i -= 1; if (!hasProp(defined, id) && !hasProp(waiting, id)) { if (hasProp(deferreds, id)) { main.apply(undef, args); } else { waiting[id] = args; } } } if (anonId) { shim = getOwn(config.shim, anonId) || {}; main(anonId, shim.deps || [], shim.exportsFn); } } function makeRequire(relName, topLevel) { var req = function (deps, callback, errback, alt) { var name, cfg; if (topLevel) { takeQueue(); } if (typeof deps === 'string') { if (handlers[deps]) { return handlers[deps](relName); } name = makeMap(deps, relName, true).id; if (!hasProp(defined, name)) { throw new Error('Not loaded: ' + name); } return defined[name]; } else if (deps && !Array.isArray(deps)) { cfg = deps; deps = undef; if (Array.isArray(callback)) { deps = callback; callback = errback; errback = alt; } if (topLevel) { return req.config(cfg)(deps, callback, errback); } } callback = callback || function () { }; prim.nextTick(function () { takeQueue(); main(undef, deps || [], callback, errback, relName); }); return req; }; req.isBrowser = typeof document !== 'undefined' && typeof navigator !== 'undefined'; req.nameToUrl = function (moduleName, ext, skipExt) { var paths, syms, i, parentModule, url, parentPath, bundleId, pkgMain = getOwn(config.pkgs, moduleName); if (pkgMain) { moduleName = pkgMain; } bundleId = getOwn(bundlesMap, moduleName); if (bundleId) { return req.nameToUrl(bundleId, ext, skipExt); } if (urlRegExp.test(moduleName)) { url = moduleName + (ext || ''); } else { paths = config.paths; syms = moduleName.split('/'); for (i = syms.length; i > 0; i -= 1) { parentModule = syms.slice(0, i).join('/'); parentPath = getOwn(paths, parentModule); if (parentPath) { if (Array.isArray(parentPath)) { parentPath = parentPath[0]; } syms.splice(0, i, parentPath); break; } } url = syms.join('/'); url += ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js'); url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; } return config.urlArgs ? url + ((url.indexOf('?') === -1 ? '?' : '&') + config.urlArgs) : url; }; req.toUrl = function (moduleNamePlusExt) { var ext, index = moduleNamePlusExt.lastIndexOf('.'), segment = moduleNamePlusExt.split('/')[0], isRelative = segment === '.' || segment === '..'; if (index !== -1 && (!isRelative || index > 1)) { ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); moduleNamePlusExt = moduleNamePlusExt.substring(0, index); } return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true); }; req.defined = function (id) { return hasProp(defined, makeMap(id, relName, true).id); }; req.specified = function (id) { id = makeMap(id, relName, true).id; return hasProp(defined, id) || hasProp(deferreds, id); }; return req; } function resolve(name, d, value) { if (name) { defined[name] = value; if (requirejs.onResourceLoad) { requirejs.onResourceLoad(context, d.map, d.deps); } } d.finished = true; d.resolve(value); } function reject(d, err) { d.finished = true; d.rejected = true; d.reject(err); } function makeNormalize(relName) { return function (name) { return normalize(name, relName, true); }; } function defineModule(d) { var name = d.map.id, ret = d.factory.apply(defined[name], d.values); if (name) { if (ret === undef) { if (d.cjsModule) { ret = d.cjsModule.exports; } else if (d.usingExports) { ret = defined[name]; } } } else { requireDeferreds.splice(requireDeferreds.indexOf(d), 1); } resolve(name, d, ret); } function depFinished(val, i) { if (!this.rejected && !this.depDefined[i]) { this.depDefined[i] = true; this.depCount += 1; this.values[i] = val; if (!this.depending && this.depCount === this.depMax) { defineModule(this); } } } function makeDefer(name) { var d = {}; d.promise = prim(function (resolve, reject) { d.resolve = resolve; d.reject = reject; }); d.map = name ? makeMap(name, null, true) : {}; d.depCount = 0; d.depMax = 0; d.values = []; d.depDefined = []; d.depFinished = depFinished; if (d.map.pr) { d.deps = [makeMap(d.map.pr)]; } return d; } function getDefer(name) { var d; if (name) { d = hasProp(deferreds, name) && deferreds[name]; if (!d) { d = deferreds[name] = makeDefer(name); } } else { d = makeDefer(); requireDeferreds.push(d); } return d; } function makeErrback(d, name) { return function (err) { if (!d.rejected) { if (!err.dynaId) { err.dynaId = 'id' + (errCount += 1); err.requireModules = [name]; } reject(d, err); } }; } function waitForDep(depMap, relName, d, i) { d.depMax += 1; callDep(depMap, relName).then(function (val) { d.depFinished(val, i); }, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id)); } function makeLoad(id) { var fromTextCalled; function load(value) { if (!fromTextCalled) { resolve(id, getDefer(id), value); } } load.error = function (err) { getDefer(id).reject(err); }; load.fromText = function (text, textAlt) { var d = getDefer(id), map = makeMap(makeMap(id).n), plainId = map.id; fromTextCalled = true; d.factory = function (p, val) { return val; }; if (textAlt) { text = textAlt; } if (hasProp(config.config, id)) { config.config[plainId] = config.config[id]; } try { req.exec(text); } catch (e) { reject(d, new Error('fromText eval for ' + plainId + ' failed: ' + e)); } takeQueue(plainId); d.deps = [map]; waitForDep(map, null, d, d.deps.length); }; return load; } load = typeof importScripts === 'function' ? function (map) { var url = map.url; if (urlFetched[url]) { return; } urlFetched[url] = true; getDefer(map.id); importScripts(url); takeQueue(map.id); } : function (map) { var script, id = map.id, url = map.url; if (urlFetched[url]) { return; } urlFetched[url] = true; script = document.createElement('script'); script.setAttribute('data-requiremodule', id); script.type = config.scriptType || 'text/javascript'; script.charset = 'utf-8'; script.async = true; loadCount += 1; script.addEventListener('load', function () { loadCount -= 1; takeQueue(id); }, false); script.addEventListener('error', function () { loadCount -= 1; var err, pathConfig = getOwn(config.paths, id), d = getOwn(deferreds, id); if (pathConfig && Array.isArray(pathConfig) && pathConfig.length > 1) { script.parentNode.removeChild(script); pathConfig.shift(); d.map = makeMap(id); load(d.map); } else { err = new Error('Load failed: ' + id + ': ' + script.src); err.requireModules = [id]; getDefer(id).reject(err); } }, false); script.src = url; document.head.appendChild(script); }; function callPlugin(plugin, map, relName) { plugin.load(map.n, makeRequire(relName), makeLoad(map.id), {}); } callDep = function (map, relName) { var args, bundleId, name = map.id, shim = config.shim[name]; if (hasProp(waiting, name)) { args = waiting[name]; delete waiting[name]; main.apply(undef, args); } else if (!hasProp(deferreds, name)) { if (map.pr) { if (bundleId = getOwn(bundlesMap, name)) { map.url = req.nameToUrl(bundleId); load(map); } else { return callDep(makeMap(map.pr)).then(function (plugin) { var newMap = makeMap(name, relName, true), newId = newMap.id, shim = getOwn(config.shim, newId); if (!hasProp(calledPlugin, newId)) { calledPlugin[newId] = true; if (shim && shim.deps) { req(shim.deps, function () { callPlugin(plugin, newMap, relName); }); } else { callPlugin(plugin, newMap, relName); } } return getDefer(newId).promise; }); } } else if (shim && shim.deps) { req(shim.deps, function () { load(map); }); } else { load(map); } } return getDefer(name).promise; }; function splitPrefix(name) { var prefix, index = name ? name.indexOf('!') : -1; if (index > -1) { prefix = name.substring(0, index); name = name.substring(index + 1, name.length); } return [ prefix, name ]; } makeMap = function (name, relName, applyMap) { if (typeof name !== 'string') { return name; } var plugin, url, parts, prefix, result, cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap; parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; if (!prefix && hasProp(mapCache, cacheKey)) { return mapCache[cacheKey]; } if (prefix) { prefix = normalize(prefix, relName, applyMap); plugin = hasProp(defined, prefix) && defined[prefix]; } if (prefix) { if (plugin && plugin.normalize) { name = plugin.normalize(name, makeNormalize(relName)); } else { name = normalize(name, relName, applyMap); } } else { name = normalize(name, relName, applyMap); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; url = req.nameToUrl(name); } result = { id: prefix ? prefix + '!' + name : name, n: name, pr: prefix, url: url }; if (!prefix) { mapCache[cacheKey] = result; } return result; }; handlers = { require: function (name) { return makeRequire(name); }, exports: function (name) { var e = defined[name]; if (typeof e !== 'undefined') { return e; } else { return defined[name] = {}; } }, module: function (name) { return { id: name, uri: '', exports: handlers.exports(name), config: function () { return getOwn(config.config, name) || {}; } }; } }; function breakCycle(d, traced, processed) { var id = d.map.id; traced[id] = true; if (!d.finished && d.deps) { d.deps.forEach(function (depMap) { var depId = depMap.id, dep = !hasProp(handlers, depId) && getDefer(depId); if (dep && !dep.finished && !processed[depId]) { if (hasProp(traced, depId)) { d.deps.forEach(function (depMap, i) { if (depMap.id === depId) { d.depFinished(defined[depId], i); } }); } else { breakCycle(dep, traced, processed); } } }); } processed[id] = true; } function check(d) { var err, notFinished = [], waitInterval = config.waitSeconds * 1000, expired = waitInterval && startTime + waitInterval < new Date().getTime(); if (loadCount === 0) { if (d) { if (!d.finished) { breakCycle(d, {}, {}); } } else if (requireDeferreds.length) { requireDeferreds.forEach(function (d) { breakCycle(d, {}, {}); }); } } if (expired) { eachProp(deferreds, function (d) { if (!d.finished) { notFinished.push(d.map.id); } }); err = new Error('Timeout for modules: ' + notFinished); err.requireModules = notFinished; req.onError(err); } else if (loadCount || requireDeferreds.length) { if (!checkingLater) { checkingLater = true; prim.nextTick(function () { checkingLater = false; check(); }); } } } function delayedError(e) { prim.nextTick(function () { if (!e.dynaId || !trackedErrors[e.dynaId]) { trackedErrors[e.dynaId] = true; req.onError(e); } }); } main = function (name, deps, factory, errback, relName) { if (name && hasProp(calledDefine, name)) { return; } calledDefine[name] = true; var d = getDefer(name); if (deps && !Array.isArray(deps)) { factory = deps; deps = []; } d.promise.catch(errback || delayedError); relName = relName || name; if (typeof factory === 'function') { if (!deps.length && factory.length) { factory.toString().replace(commentRegExp, '').replace(cjsRequireRegExp, function (match, dep) { deps.push(dep); }); deps = (factory.length === 1 ? ['require'] : [ 'require', 'exports', 'module' ]).concat(deps); } d.factory = factory; d.deps = deps; d.depending = true; deps.forEach(function (depName, i) { var depMap; deps[i] = depMap = makeMap(depName, relName, true); depName = depMap.id; if (depName === 'require') { d.values[i] = handlers.require(name); } else if (depName === 'exports') { d.values[i] = handlers.exports(name); d.usingExports = true; } else if (depName === 'module') { d.values[i] = d.cjsModule = handlers.module(name); } else if (depName === undefined) { d.values[i] = undefined; } else { waitForDep(depMap, relName, d, i); } }); d.depending = false; if (d.depCount === d.depMax) { defineModule(d); } } else if (name) { resolve(name, d, factory); } startTime = new Date().getTime(); if (!name) { check(d); } }; req = makeRequire(null, true); req.config = function (cfg) { if (cfg.context && cfg.context !== contextName) { return newContext(cfg.context).config(cfg); } mapCache = {}; if (cfg.baseUrl) { if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { cfg.baseUrl += '/'; } } var primId, shim = config.shim, objs = { paths: true, bundles: true, config: true, map: true }; eachProp(cfg, function (value, prop) { if (objs[prop]) { if (!config[prop]) { config[prop] = {}; } mixin(config[prop], value, true, true); } else { config[prop] = value; } }); if (cfg.bundles) { eachProp(cfg.bundles, function (value, prop) { value.forEach(function (v) { if (v !== prop) { bundlesMap[v] = prop; } }); }); } if (cfg.shim) { eachProp(cfg.shim, function (value, id) { if (Array.isArray(value)) { value = { deps: value }; } if ((value.exports || value.init) && !value.exportsFn) { value.exportsFn = makeShimExports(value); } shim[id] = value; }); config.shim = shim; } if (cfg.packages) { cfg.packages.forEach(function (pkgObj) { var location, name; pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; name = pkgObj.name; location = pkgObj.location; if (location) { config.paths[name] = pkgObj.location; } config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main').replace(currDirRegExp, '').replace(jsSuffixRegExp, ''); }); } primId = config.definePrim; if (primId) { waiting[primId] = [ primId, [], function () { return prim; } ]; } if (cfg.deps || cfg.callback) { req(cfg.deps, cfg.callback); } return req; }; req.onError = function (err) { throw err; }; context = { id: contextName, defined: defined, waiting: waiting, config: config, deferreds: deferreds }; contexts[contextName] = context; return req; } requirejs = topReq = newContext('_'); if (typeof require !== 'function') { require = topReq; } topReq.exec = function (text) { return eval(text); }; topReq.contexts = contexts; define = function () { queue.push([].slice.call(arguments, 0)); }; define.amd = { jQuery: true }; if (bootstrapConfig) { topReq.config(bootstrapConfig); } if (topReq.isBrowser && !contexts._.config.skipDataMain) { dataMain = document.querySelectorAll('script[data-main]')[0]; dataMain = dataMain && dataMain.getAttribute('data-main'); if (dataMain) { dataMain = dataMain.replace(jsSuffixRegExp, ''); if (!bootstrapConfig || !bootstrapConfig.baseUrl) { src = dataMain.split('/'); dataMain = src.pop(); subPath = src.length ? src.join('/') + '/' : './'; topReq.config({ baseUrl: subPath }); } topReq([dataMain]); } } }(this)); define('alameda', function () { }); var window = self; function consoleHelper() { var msg = arguments[0] + ':'; for (var i = 1; i < arguments.length; i++) { msg += ' ' + arguments[i]; } msg += '\x1B[0m\n'; dump(msg); } window.console = { log: consoleHelper.bind(null, '\x1B[32mWLOG'), error: consoleHelper.bind(null, '\x1B[31mWERR'), info: consoleHelper.bind(null, '\x1B[36mWINF'), warn: consoleHelper.bind(null, '\x1B[33mWWAR') }; require(['worker-setup']); define('worker-bootstrap', function () { }); (function (root) { 'use strict'; requirejs.config({ baseUrl: '.', packages: [{ name: 'wo-imap-handler', location: 'ext/imap-handler/src', main: 'imap-handler' }], map: { 'browserbox': { 'axe': 'axeshim-browserbox' }, 'browserbox-imap': { 'axe': 'axeshim-browserbox' }, 'ext/smtpclient': { 'axe': 'axeshim-smtpclient' } }, paths: { 'bleach': 'ext/bleach.js/lib/bleach', 'imap-formal-syntax': 'ext/imap-handler/src/imap-formal-syntax', 'smtpclient-response-parser': 'ext/smtpclient/src/smtpclient-response-parser', 'tests': '../test/unit', 'wbxml': 'ext/activesync-lib/wbxml/wbxml', 'activesync/codepages': 'ext/activesync-lib/codepages', 'activesync/protocol': 'ext/activesync-lib/protocol', 'activesync-lib': 'ext/activesync-lib', 'addressparser': 'ext/addressparser', 'alameda': 'ext/alameda', 'axe': 'ext/axe', 'axe-logger': 'ext/axe-logger', 'axeshim-browserbox': 'ext/axeshim-browserbox', 'axeshim-smtpclient': 'ext/axeshim-smtpclient', 'bleach.js': 'ext/bleach.js', 'browserbox': 'ext/browserbox', 'browserbox-imap': 'ext/browserbox-imap', 'co': 'ext/co', 'equal': 'ext/equal', 'evt': 'ext/evt', 'imap-handler': 'ext/imap-handler', 'mailbuild': 'ext/mailbuild', 'md5': 'ext/md5', 'mimefuncs': 'ext/mimefuncs', 'mimeparser': 'ext/mimeparser', 'mimeparser-tzabbr': 'ext/mimeparser-tzabbr', 'mimetypes': 'ext/mimetypes', 'mix': 'ext/mix', 'punycode': 'ext/punycode', 'safe-base64': 'ext/safe-base64', 'smtpclient': 'ext/smtpclient', 'stringencoding': 'ext/stringencoding', 'tcp-socket': 'ext/tcp-socket', 'utf7': 'ext/utf7', 'wo-utf7': 'ext/wo-utf7' }, waitSeconds: 0 }); if (typeof gelamWorkerBaseUrl === 'string') { requirejs.config({ baseUrl: gelamWorkerBaseUrl }); } root.setZeroTimeout = function (fn) { setTimeout(function () { fn(); }, 0); }; }(this)); define('worker-config', function () { }); define('worker-router', [], function () { var listeners = {}; function receiveMessage(evt) { var data = evt.data; var listener = listeners[data.type]; if (listener) listener(data); } window.addEventListener('message', receiveMessage); function unregister(type) { delete listeners[type]; } function registerSimple(type, callback) { listeners[type] = callback; return function sendSimpleMessage(cmd, args) { window.postMessage({ type: type, uid: null, cmd: cmd, args: args }); }; } var callbackSenders = {}; function registerCallbackType(type) { if (callbackSenders.hasOwnProperty(type)) return callbackSenders[type]; listeners[type] = function receiveCallbackMessage(data) { var callback = callbacks[data.uid]; if (!callback) return; delete callbacks[data.uid]; callback.apply(callback, data.args); }; var callbacks = {}; var uid = 0; var sender = function sendCallbackMessage(cmd, args, callback) { if (callback) { callbacks[uid] = callback; } window.postMessage({ type: type, uid: uid++, cmd: cmd, args: args }); }; callbackSenders[type] = sender; return sender; } function registerInstanceType(type) { var uid = 0; var instanceMap = {}; listeners[type] = function receiveInstanceMessage(data) { var instanceListener = instanceMap[data.uid]; if (!instanceListener) return; instanceListener(data); }; return { register: function (instanceListener) { var thisUid = uid++; instanceMap[thisUid] = instanceListener; return { sendMessage: function sendInstanceMessage(cmd, args, transferArgs) { window.postMessage({ type: type, uid: thisUid, cmd: cmd, args: args }, transferArgs); }, unregister: function unregisterInstance() { delete instanceMap[thisUid]; } }; } }; } function shutdown() { window.removeEventListener('message', receiveMessage); listeners = {}; callbackSenders = {}; } return { registerSimple: registerSimple, registerCallbackType: registerCallbackType, registerInstanceType: registerInstanceType, unregister: unregister, shutdown: shutdown }; }); ; define('evt', [], function () { var evt, slice = Array.prototype.slice, props = [ '_events', '_pendingEvents', 'on', 'once', 'latest', 'latestOnce', 'removeListener', 'emitWhenListener', 'emit' ]; function Emitter() { this._events = {}; this._pendingEvents = {}; } Emitter.prototype = { on: function (id, fn) { var listeners = this._events[id], pending = this._pendingEvents[id]; if (!listeners) { listeners = this._events[id] = []; } listeners.push(fn); if (pending) { pending.forEach(function (args) { fn.apply(null, args); }); delete this._pendingEvents[id]; } return this; }, once: function (id, fn) { var self = this, fired = false; function one() { if (fired) return; fired = true; fn.apply(null, arguments); setTimeout(function () { self.removeListener(id, one); }); } return this.on(id, one); }, latest: function (id, fn) { if (this[id] && !this._pendingEvents[id]) { fn(this[id]); } this.on(id, fn); }, latestOnce: function (id, fn) { if (this[id] && !this._pendingEvents[id]) fn(this[id]); else this.once(id, fn); }, removeListener: function (id, fn) { var i, listeners = this._events[id]; if (listeners) { i = listeners.indexOf(fn); if (i !== -1) { listeners.splice(i, 1); } if (listeners.length === 0) delete this._events[id]; } }, emitWhenListener: function (id) { var listeners = this._events[id]; if (listeners) { this.emit.apply(this, arguments); } else { if (!this._pendingEvents[id]) this._pendingEvents[id] = []; this._pendingEvents[id].push(slice.call(arguments, 1)); } }, emit: function (id) { var args = slice.call(arguments, 1), listeners = this._events[id]; if (listeners) { listeners.forEach(function (fn) { try { fn.apply(null, args); } catch (e) { setTimeout(function () { throw e; }); } }); } } }; evt = new Emitter(); evt.Emitter = Emitter; evt.mix = function (obj) { var e = new Emitter(); props.forEach(function (prop) { if (obj.hasOwnProperty(prop)) { throw new Error('Object already has a property "' + prop + '"'); } obj[prop] = e[prop]; }); return obj; }; return evt; }); define('ext/equal', ['require'], function (require) { var COMPARE_DEPTH = 6; function boundedCmpObjs(a, b, depthLeft) { var aAttrCount = 0, bAttrCount = 0, key, nextDepth = depthLeft - 1; if ('toJSON' in a) a = a.toJSON(); if ('toJSON' in b) b = b.toJSON(); for (key in a) { aAttrCount++; if (!(key in b)) return false; if (depthLeft) { if (!equal(a[key], b[key], nextDepth)) return false; } else { if (a[key] !== b[key]) return false; } } for (key in b) { bAttrCount++; } if (aAttrCount !== bAttrCount) return false; return true; } function equal(a, b, depthLeft) { if (depthLeft === undefined) { depthLeft = COMPARE_DEPTH; } var ta = typeof a, tb = typeof b; if (ta !== 'object' || tb !== ta || a == null || b == null) return a === b; if (a === b) return true; if (Array.isArray(a)) { if (!Array.isArray(b)) return false; if (a.length !== b.length) return false; for (var iArr = 0; iArr < a.length; iArr++) { if (!equal(a[iArr], b[iArr], depthLeft - 1)) return false; } return true; } return boundedCmpObjs(a, b, depthLeft); } return equal; }); ; define('logic', [ 'require', 'evt', './ext/equal' ], function (require) { var evt = require('evt'); var equal = require('./ext/equal'); function logic() { return logic.event.apply(logic, arguments); } evt.mix(logic); logic.scope = function (namespace, defaultDetails) { return new Scope(namespace, defaultDetails); }; var objectToScope = new WeakMap(); function toScope(scope) { if (!(scope instanceof Scope)) { scope = objectToScope.get(scope); if (!scope) { throw new Error('Invalid scope ' + scope + ' passed to logic.event(); ' + 'did you remember to call logic.defineScope()? ' + new Error().stack); } } return scope; } logic.defineScope = function (obj, namespace, defaultDetails) { if (!namespace && obj && obj.constructor && obj.constructor.name) { namespace = obj.constructor.name; } var scope = new Scope(namespace, defaultDetails); objectToScope.set(obj, scope); return scope; }; logic.subscope = function (scope, defaultDetails) { scope = toScope(scope); return new Scope(scope.namespace, into(shallowClone(scope.defaultDetails), shallowClone(defaultDetails))); }; logic.event = function (scope, type, details) { scope = toScope(scope); var isDefaultPrevented = false; var preprocessEvent = { scope: scope, namespace: scope.namespace, type: type, details: details, preventDefault: function () { isDefaultPrevented = true; } }; logic.emit('preprocessEvent', preprocessEvent); if (isDefaultPrevented) { return { id: 0 }; } type = preprocessEvent.type; details = preprocessEvent.details; if (typeof type !== 'string') { throw new Error('Invalid "type" passed to logic.event(); ' + 'expected a string, got "' + type + '"'); } if (scope.defaultDetails) { if (isPlainObject(details)) { details = into(shallowClone(scope.defaultDetails), shallowClone(details)); } else { details = shallowClone(scope.defaultDetails); } } else { details = shallowClone(details); } var event = new LogicEvent(scope, type, details); logic.emit('censorEvent', event); logic.emit('event', event); if (logic.realtimeLogEverything) { dump('logic: ' + event.toString() + '\n'); } return event; }; logic.underTest = false; logic._currentTestRejectFunction = null; logic.fail = function (ex) { if (logic.underTest) { if (logic._currentTestRejectFunction) { logic._currentTestRejectFunction(ex); } else { throw ex; } } else { console.error('Logic fail:', ex); } }; var nextId = 1; logic.uniqueId = function () { return nextId++; }; logic.isCensored = false; logic.realtimeLogEverything = false; var interceptions = {}; logic.interceptable = function (type, fn) { if (interceptions[type]) { return interceptions[type](); } else { return fn(); } }; logic.interceptOnce = function (type, replacementFn) { var prevFn = interceptions[type]; interceptions[type] = function () { interceptions[type] = prevFn; return replacementFn(); }; }; logic.match = function (ns, type, detailPredicate) { return new LogicMatcher(LogicMatcher.normalizeMatchArgs(ns, type, detailPredicate)); }; function MismatchError(matcher, event) { this.matcher = matcher; this.event = event; } MismatchError.prototype = Object.create(Error.prototype, { constructor: { value: MismatchError }, toString: { value: function () { if (this.matcher.not) { return 'MismatchError: expected ' + this.event + ' to not occur (failIfMatched ' + this.matcher + ').'; } else { return 'MismatchError: expected ' + this.event + ' to match ' + JSON.stringify(this.matcher.detailPredicate) + '.'; } } } }); function LogicMatcher(opts) { this.matchedLogs = opts.prevMatcher ? opts.prevMatcher.matchedLogs : []; this.capturedLogs = []; this.ns = opts.ns; this.type = opts.type; this.detailPredicate = opts.detailPredicate; this.failOnMismatchedDetails = true; this.not = opts.not; this.timeoutMS = 2000; this.resolved = false; this.anotherMatcherNeedsMyLogs = false; if (opts.prevMatcher) { opts.prevMatcher.anotherMatcherNeedsMyLogs = true; } logic.defineScope(this, 'LogicMatcher'); var hasPrevPromise = !!opts.prevPromise; var normalizedPrevPromise = opts.prevPromise || Promise.resolve(); if (this.not) { this.promise = normalizedPrevPromise.then(() => { this.capturedLogs.some(event => { if ((!this.ns || event.namespace === this.ns) && event.matches(this.type, this.detailPredicate)) { throw new MismatchError(this, event); } }); }); } else if (this.type) { this.promise = new Promise((resolve, reject) => { var subscribeToNextMatch = () => { var timeoutId = setTimeout(() => { logic(this, 'failedMatch', { ns: this.ns, type: this.type, detailPredicate: this.detailPredicate, capturedLogs: this.capturedLogs }); reject(new Error('LogicMatcherTimeout: ' + this)); }, this.timeoutMS); var resolveThisMatcher = event => { this.resolved = true; this.capturedLogs = []; if (!this.anotherMatcherNeedsMyLogs) { this.removeMatchListener(); } }; var matchFn = event => { this.capturedLogs.push(event); if (this.resolved) { return; } if (this.ns && event.namespace !== this.ns || event.type !== this.type) { return false; } if (event.matches(this.type, this.detailPredicate)) { resolveThisMatcher(event); this.matchedLogs.push(event); clearTimeout(timeoutId); logic(this, 'match', { ns: this.ns, type: this.type, event: event }); resolve(event); return true; } else { if (this.failOnMismatchedDetails) { resolveThisMatcher(event); reject(new MismatchError(this, event)); return true; } else { } } return false; }; this.removeMatchListener = () => { logic.removeListener('event', matchFn); }; logic.on('event', matchFn); if (opts.prevMatcher) { var prevLogs = opts.prevMatcher.capturedLogs; var matchIndex = prevLogs.findIndex(matchFn); if (matchIndex !== -1) { this.capturedLogs = prevLogs.slice(matchIndex + 1); } opts.prevMatcher.removeMatchListener(); } }; if (hasPrevPromise) { normalizedPrevPromise.then(subscribeToNextMatch, e => reject(e)); } else { try { subscribeToNextMatch(); } catch (e) { reject(e); } } }); } else { this.promise = normalizedPrevPromise; } } LogicMatcher.normalizeMatchArgs = function (ns, type, details) { if (typeof type === 'object') { details = type; type = ns; ns = null; } return { ns: ns, type: type, detailPredicate: details }; }; LogicMatcher.prototype = { match(ns, type, details) { var args = LogicMatcher.normalizeMatchArgs(ns, type, details); args.prevMatcher = this; args.prevPromise = this.promise; return new LogicMatcher(args); }, failIfMatched(ns, type, details) { var args = LogicMatcher.normalizeMatchArgs(ns, type, details); args.not = true; args.prevMatcher = this; args.prevPromise = this.promise; return new LogicMatcher(args); }, then(fn, catchFn) { return new LogicMatcher({ prevPromise: this.promise.then(() => { var ret = fn(this.matchedLogs.slice()); if (ret instanceof Promise) { ret = new LogicMatcher({ prevPromise: ret }); } return ret; }, catchFn) }); }, toString() { return ''; } }; function Scope(namespace, defaultDetails) { this.namespace = namespace; if (defaultDetails && !isPlainObject(defaultDetails)) { throw new Error('Invalid defaultDetails; expected a plain-old object: ' + defaultDetails); } this.defaultDetails = defaultDetails; } function ObjectSimplifier(opts) { opts = opts || {}; this.maxDepth = opts.maxDepth || 10; this.maxStringLength = opts.maxStringLength || 1000; this.maxArrayLength = opts.maxArrayLength || 1000; this.maxObjectLength = opts.maxObjectLength || 10; } ObjectSimplifier.prototype = { simplify: function (x) { return this._simplify(x, 0, new WeakSet()); }, _simplify: function (x, depth, cacheSet) { if (cacheSet.has(x)) { return '(cycle)'; } if (typeof x === 'number') { return x; } else if (typeof x === 'string') { return x.slice(0, this.maxStringLength); } else if (x && x.BYTES_PER_ELEMENT) { return x.slice(0, this.maxArrayLength); } else if (Array.isArray(x)) { if (depth < this.maxDepth) { return x.slice(0, this.maxArrayLength).map(element => this._simplify(element, depth + 1, cacheSet)); } else { return '[Array length=' + x.length + ']'; } } else if (x && typeof x === 'object') { cacheSet.add(x); if (!isPlainObject(x)) { if (x.toJSON) { return this._simplify(x.toJSON(), depth, cacheSet); } else if (x.toString) { return this._simplify(x.toString(), depth, cacheSet); } else { return '(?)'; } } else { if (depth < this.maxDepth) { var retObj = {}; var idx = 0; for (var key in x) { if (idx > this.maxObjectLength) { break; } retObj[key] = this._simplify(x[key], depth + 1, cacheSet); idx++; } return retObj; } else if (x.toString) { return this._simplify(x.toString(), depth, cacheSet); } else { return '(object?)'; } } } else if (typeof x === 'function') { return '(function)'; } else { return x; } } }; function LogicEvent(scope, type, details) { if (!(scope instanceof Scope)) { throw new Error('Invalid "scope" passed to LogicEvent(); ' + 'did you remember to call logic.defineScope()?'); } this.scope = scope; this.type = type; this.details = details; this.time = Date.now(); this.id = logic.uniqueId(); this.jsonRepresentation = { namespace: this.scope.namespace, type: this.type, details: new ObjectSimplifier().simplify(this.details), time: this.time, id: this.id }; } LogicEvent.fromJSON = function (data) { var event = new LogicEvent(new Scope(data.namespace), data.type, data.details); event.time = data.time; event.id = data.id; return event; }; LogicEvent.prototype = { get namespace() { return this.scope.namespace; }, toJSON: function () { return this.jsonRepresentation; }, toString: function () { return ''; }, matches: function (type, detailPredicate) { if (this.type !== type) { return false; } if (typeof detailPredicate === 'function') { return !!detailPredicate(this.details); } else if (isPlainObject(detailPredicate)) { for (var key in detailPredicate) { var expected = detailPredicate && detailPredicate[key]; var actual = this.details && this.details[key]; if (actual === undefined) { actual = null; } if (expected === undefined) { continue; } else if (!this.details || !equal(expected, actual)) { return false; } } return true; } else if (detailPredicate != null) { return equal(this.details, detailPredicate); } else { return true; } } }; function isPlainObject(obj) { if (!obj || typeof obj !== 'object') { return false; } if (obj.toString && obj.toString() !== '[object Object]') { return false; } for (var k in obj) { if (typeof k === 'function') { return false; } } return true; } logic.isPlainObject = isPlainObject; var promiseToStartEventMap = new WeakMap(); var promiseToResultEventMap = new WeakMap(); logic.startAsync = function (scope, type, details) { var resolve, reject; var promise = logic.async(scope, type, details, (_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return { resolve: resolve, reject: reject }; }; logic.async = function (scope, type, details, fn) { if (!fn && typeof details === 'function') { fn = details; details = null; } scope = logic.subscope(scope, details); var startEvent; var promise = new Promise((resolve, reject) => { startEvent = logic(scope, 'begin ' + type, { asyncStatus: 0, asyncName: type }); fn(result => { promiseToResultEventMap.set(promise, logic(scope, type, { asyncStatus: 1, sourceEventIds: [startEvent.id], result: result })); resolve(result); }, error => { promiseToResultEventMap.set(promise, logic(scope, type, { asyncStatus: 2, sourceEventIds: [startEvent.id], error: error })); reject(error); }); }); promiseToStartEventMap.set(promise, startEvent); return promise; }; logic.await = function (scope, type, details, promise) { if (!promise && details.then) { promise = details; details = null; } scope = logic.subscope(scope, details).subscope(scope); var startEvent = promiseToStartEventMap.get(promise); var awaitEvent = logic.event(scope, 'await ' + type, { awaitStatus: 0, sourceEventIds: startEvent ? [startEvent.id] : null, awaitName: type }); return promise.then(result => { var resultEvent = promiseToResultEventMap.get(promise); logic(scope, type, { awaitStatus: 1, result: result, sourceEventIds: resultEvent ? [ resultEvent.id, awaitEvent.id ] : [awaitEvent.id] }); return result; }, error => { var resultEvent = promiseToResultEventMap.get(promise); logic(scope, type, { awaitStatus: 2, error: error, sourceEventIds: resultEvent ? [ resultEvent.id, awaitEvent.id ] : [awaitEvent.id] }); throw error; }); }; function shallowClone(x) { if (isPlainObject(x)) { var ret = {}; for (var key in x) { ret[key] = x[key]; } return ret; } else { return x; } } function into(target, source) { if (!target) { target = {}; } for (var key in source) { target[key] = source[key]; } return target; } return logic; }); define('util', ['exports'], function (exports) { var cmpHeaderYoungToOld = exports.cmpHeaderYoungToOld = function cmpHeaderYoungToOld(a, b) { var delta = b.date - a.date; if (delta) return delta; return b.id - a.id; }; var bsearchForInsert = exports.bsearchForInsert = function bsearchForInsert(list, seekVal, cmpfunc) { if (!list.length) return 0; var low = 0, high = list.length - 1, mid, cmpval; while (low <= high) { mid = low + Math.floor((high - low) / 2); cmpval = cmpfunc(seekVal, list[mid]); if (cmpval < 0) high = mid - 1; else if (cmpval > 0) low = mid + 1; else break; } if (cmpval < 0) return mid; else if (cmpval > 0) return mid + 1; else return mid; }; var bsearchMaybeExists = exports.bsearchMaybeExists = function bsearchMaybeExists(list, seekVal, cmpfunc, aLow, aHigh) { var low = aLow === undefined ? 0 : aLow, high = aHigh === undefined ? list.length - 1 : aHigh, mid, cmpval; while (low <= high) { mid = low + Math.floor((high - low) / 2); cmpval = cmpfunc(seekVal, list[mid]); if (cmpval < 0) high = mid - 1; else if (cmpval > 0) low = mid + 1; else return mid; } return null; }; exports.partitionMessagesByFolderId = function partitionMessagesByFolderId(messageNamers) { var results = [], foldersToMsgs = {}; for (var i = 0; i < messageNamers.length; i++) { var messageNamer = messageNamers[i], messageSuid = messageNamer.suid, idxLastSlash = messageSuid.lastIndexOf('/'), folderId = messageSuid.substring(0, idxLastSlash); if (!foldersToMsgs.hasOwnProperty(folderId)) { var messages = [messageNamer]; results.push({ folderId: folderId, messages: messages }); foldersToMsgs[folderId] = messages; } else { foldersToMsgs[folderId].push(messageNamer); } } return results; }; exports.formatAddresses = function (nameAddrPairs) { var addrstrings = []; for (var i = 0; i < nameAddrPairs.length; i++) { var pair = nameAddrPairs[i]; if (typeof pair === 'string') { addrstrings.push(pair); } else if (!pair.name) { addrstrings.push(pair.address); } else { addrstrings.push('"' + pair.name.replace(/["']/g, '') + '" <' + pair.address + '>'); } } return addrstrings.join(', '); }; }); ; define('mailchew-strings', [ 'exports', 'evt' ], function (exports, evt) { exports.events = new evt.Emitter(); exports.set = function set(strings) { exports.strings = strings; exports.events.emit('strings', strings); }; }); define('date', [ 'module', 'exports' ], function ($module, exports) { var BEFORE = exports.BEFORE = function BEFORE(testDate, comparisonDate) { return testDate < comparisonDate; }; var ON_OR_BEFORE = exports.ON_OR_BEFORE = function ON_OR_BEFORE(testDate, comparisonDate) { return testDate <= comparisonDate; }; var SINCE = exports.SINCE = function SINCE(testDate, comparisonDate) { return testDate >= comparisonDate; }; var STRICTLY_AFTER = exports.STRICTLY_AFTER = function STRICTLY_AFTER(testDate, comparisonDate) { return testDate > comparisonDate; }; var IN_BS_DATE_RANGE = exports.IN_BS_DATE_RANGE = function IN_BS_DATE_RANGE(testDate, startTS, endTS) { return testDate >= startTS && testDate < endTS; }; var PASTWARDS = 1, FUTUREWARDS = -1; var TIME_DIR_AT_OR_BEYOND = exports.TIME_DIR_AT_OR_BEYOND = function TIME_DIR_AT_OR_BEYOND(dir, testDate, comparisonDate) { if (dir === PASTWARDS) return testDate <= comparisonDate; else if (comparisonDate === null) return testDate >= NOW(); else return testDate >= comparisonDate; }; var TIME_DIR_DELTA = exports.TIME_DIR_DELTA = function TIME_DIR_DELTA(dir, testDate, comparisonDate) { if (dir === PASTWARDS) return testDate - comparisonDate; else return comparisonDate - testDate; }; var TIME_DIR_ADD = exports.TIME_DIR_ADD = function TIME_DIR_ADD(dir, baseDate, time) { if (dir === PASTWARDS) return baseDate + time; else return baseDate - time; }; var HOUR_MILLIS = exports.HOUR_MILLIS = 60 * 60 * 1000; var DAY_MILLIS = exports.DAY_MILLIS = 24 * 60 * 60 * 1000; var TIME_WARPED_NOW = null; exports.TEST_LetsDoTheTimewarpAgain = function (fakeNow) { if (fakeNow === null) { TIME_WARPED_NOW = null; return; } if (typeof fakeNow !== 'number') fakeNow = fakeNow.valueOf(); TIME_WARPED_NOW = fakeNow; }; var NOW = exports.NOW = function NOW() { return TIME_WARPED_NOW || Date.now(); }; var perfObj = typeof performance !== 'undefined' ? performance : Date; exports.PERFNOW = function PERFNOW() { return TIME_WARPED_NOW || perfObj.now(); }; var makeDaysAgo = exports.makeDaysAgo = function makeDaysAgo(numDays) { var past = quantizeDate(TIME_WARPED_NOW || Date.now()) - numDays * DAY_MILLIS; return past; }; var makeDaysBefore = exports.makeDaysBefore = function makeDaysBefore(date, numDaysBefore) { if (date === null) return makeDaysAgo(numDaysBefore - 1); return quantizeDate(date) - numDaysBefore * DAY_MILLIS; }; var quantizeDate = exports.quantizeDate = function quantizeDate(date) { if (date === null) return null; if (typeof date === 'number') date = new Date(date); return date.setUTCHours(0, 0, 0, 0).valueOf(); }; var quantizeDateUp = exports.quantizeDateUp = function quantizeDateUp(date) { if (typeof date === 'number') date = new Date(date); var truncated = date.setUTCHours(0, 0, 0, 0).valueOf(); if (date.valueOf() === truncated) return truncated; return truncated + DAY_MILLIS; }; }); ; define('slice_bridge_proxy', ['exports'], function (exports) { function SliceBridgeProxy(bridge, ns, handle) { this._bridge = bridge; this._ns = ns; this._handle = handle; this.__listener = null; this.status = 'synced'; this.progress = 0; this.atTop = false; this.atBottom = false; this.headerCount = 0; this.userCanGrowUpwards = false; this.userCanGrowDownwards = false; this.pendingUpdates = []; this.scheduledUpdate = false; } exports.SliceBridgeProxy = SliceBridgeProxy; SliceBridgeProxy.prototype = { sendSplice: function sbp_sendSplice(index, howMany, addItems, requested, moreExpected, newEmailCount) { var updateSplice = { index: index, howMany: howMany, addItems: addItems, requested: requested, moreExpected: moreExpected, newEmailCount: newEmailCount, headerCount: this.headerCount, type: 'slice' }; this.addUpdate(updateSplice); }, sendUpdate: function sbp_sendUpdate(indexUpdatesRun) { var update = { updates: indexUpdatesRun, type: 'update' }; this.addUpdate(update); }, sendStatus: function sbp_sendStatus(status, requested, moreExpected, progress, newEmailCount) { this.status = status; if (progress != null) { this.progress = progress; } this.sendSplice(0, 0, [], requested, moreExpected, newEmailCount); }, sendSyncProgress: function (progress) { this.progress = progress; this.sendSplice(0, 0, [], true, true); }, addUpdate: function sbp_addUpdate(update) { this.pendingUpdates.push(update); if (this.pendingUpdates.length > 5) { this.flushUpdates(); } else if (!this.scheduledUpdate) { window.setZeroTimeout(this.flushUpdates.bind(this)); this.scheduledUpdate = true; } }, flushUpdates: function sbp_flushUpdates() { this._bridge.__sendMessage({ type: 'batchSlice', handle: this._handle, status: this.status, progress: this.progress, atTop: this.atTop, atBottom: this.atBottom, userCanGrowUpwards: this.userCanGrowUpwards, userCanGrowDownwards: this.userCanGrowDownwards, sliceUpdates: this.pendingUpdates }); this.pendingUpdates = []; this.scheduledUpdate = false; }, die: function sbp_die() { if (this.__listener) this.__listener.die(); } }; }); define('mailbridge', [ 'logic', './util', './mailchew-strings', './date', './slice_bridge_proxy', 'require', 'module', 'exports' ], function (logic, $imaputil, $mailchewStrings, $date, $sliceBridgeProxy, require, $module, exports) { var bsearchForInsert = $imaputil.bsearchForInsert, bsearchMaybeExists = $imaputil.bsearchMaybeExists, SliceBridgeProxy = $sliceBridgeProxy.SliceBridgeProxy; function toBridgeWireOn(x) { return x.toBridgeWire(); } var FOLDER_TYPE_TO_SORT_PRIORITY = { account: 'a', inbox: 'c', starred: 'e', important: 'f', drafts: 'g', localdrafts: 'h', outbox: 'i', queue: 'j', sent: 'k', junk: 'l', trash: 'n', archive: 'p', normal: 'z', nomail: 'z' }; function makeFolderSortString(account, folder) { if (!folder) return account.id; var parentFolder = account.getFolderMetaForFolderId(folder.parentId); return makeFolderSortString(account, parentFolder) + '!' + FOLDER_TYPE_TO_SORT_PRIORITY[folder.type] + '!' + folder.name.toLocaleLowerCase(); } function strcmp(a, b) { if (a < b) return -1; else if (a > b) return 1; return 0; } function checkIfAddressListContainsAddress(list, addrPair) { if (!list) return false; var checkAddress = addrPair.address; for (var i = 0; i < list.length; i++) { if (list[i].address === checkAddress) return true; } return false; } function MailBridge(universe, name) { this.universe = universe; this.universe.registerBridge(this); logic.defineScope(this, 'MailBridge', { name: name }); this._slices = {}; this._slicesByType = { accounts: [], identities: [], folders: [], headers: [], matchedHeaders: [] }; this._observedBodies = {}; this._pendingRequests = {}; this._lastUndoableOpPair = null; } exports.MailBridge = MailBridge; MailBridge.prototype = { __sendMessage: function (msg) { throw new Error('This is supposed to get hidden by an instance var.'); }, __receiveMessage: function mb___receiveMessage(msg) { var implCmdName = '_cmd_' + msg.type; if (!(implCmdName in this)) { logic(this, 'badMessageType', { type: msg.type }); return; } logic(this, 'cmd', { type: msg.type, msg: msg }); try { this[implCmdName](msg); } catch (ex) { logic.fail(ex); return; } }, _cmd_ping: function mb__cmd_ping(msg) { this.__sendMessage({ type: 'pong', handle: msg.handle }); }, _cmd_modifyConfig: function mb__cmd_modifyConfig(msg) { this.universe.modifyConfig(msg.mods); }, bodyHasObservers: function (suid) { return !!this._observedBodies[suid]; }, notifyConfig: function (config) { this.__sendMessage({ type: 'config', config: config }); }, _cmd_debugSupport: function mb__cmd_debugSupport(msg) { switch (msg.cmd) { case 'setLogging': this.universe.modifyConfig({ debugLogging: msg.arg }); break; case 'dumpLog': switch (msg.arg) { case 'storage': this.universe.dumpLogToDeviceStorage(); break; } break; } }, _cmd_setInteractive: function mb__cmd_setInteractive(msg) { this.universe.setInteractive(); }, _cmd_localizedStrings: function mb__cmd_localizedStrings(msg) { $mailchewStrings.set(msg.strings); }, _cmd_learnAboutAccount: function (msg) { this.universe.learnAboutAccount(msg.details).then(function success(info) { this.__sendMessage({ type: 'learnAboutAccountResults', handle: msg.handle, data: info }); }.bind(this), function errback(err) { this.__sendMessage({ type: 'learnAboutAccountResults', handle: msg.handle, data: { result: 'no-config-info', configInfo: null } }); }.bind(this)); }, _cmd_tryToCreateAccount: function mb__cmd_tryToCreateAccount(msg) { var self = this; this.universe.tryToCreateAccount(msg.details, msg.domainInfo, function (error, account, errorDetails) { self.__sendMessage({ type: 'tryToCreateAccountResults', handle: msg.handle, account: account ? account.toBridgeWire() : null, error: error, errorDetails: errorDetails }); }); }, _cmd_clearAccountProblems: function mb__cmd_clearAccountProblems(msg) { var account = this.universe.getAccountForAccountId(msg.accountId), self = this; account.checkAccount(function (incomingErr, outgoingErr) { function canIgnoreError(err) { return !err || err !== 'bad-user-or-pass' && err !== 'bad-address' && err !== 'needs-oauth-reauth' && err !== 'imap-disabled'; } if (canIgnoreError(incomingErr) && canIgnoreError(outgoingErr)) { self.universe.clearAccountProblems(account); } self.__sendMessage({ type: 'clearAccountProblems', handle: msg.handle }); }); }, _cmd_modifyAccount: function mb__cmd_modifyAccount(msg) { var account = this.universe.getAccountForAccountId(msg.accountId), accountDef = account.accountDef; for (var key in msg.mods) { var val = msg.mods[key]; switch (key) { case 'name': accountDef.name = val; break; case 'username': if (accountDef.credentials.outgoingUsername === accountDef.credentials.username) { accountDef.credentials.outgoingUsername = val; } accountDef.credentials.username = val; break; case 'incomingUsername': accountDef.credentials.username = val; break; case 'outgoingUsername': accountDef.credentials.outgoingUsername = val; break; case 'password': if (accountDef.credentials.outgoingPassword === accountDef.credentials.password) { accountDef.credentials.outgoingPassword = val; } accountDef.credentials.password = val; break; case 'incomingPassword': accountDef.credentials.password = val; break; case 'outgoingPassword': accountDef.credentials.outgoingPassword = val; break; case 'oauthTokens': var oauth2 = accountDef.credentials.oauth2; oauth2.accessToken = val.accessToken; oauth2.refreshToken = val.refreshToken; oauth2.expireTimeMS = val.expireTimeMS; break; case 'identities': break; case 'servers': break; case 'syncRange': accountDef.syncRange = val; break; case 'syncInterval': accountDef.syncInterval = val; break; case 'notifyOnNew': accountDef.notifyOnNew = val; break; case 'playSoundOnSend': accountDef.playSoundOnSend = val; break; case 'setAsDefault': if (val) accountDef.defaultPriority = $date.NOW(); break; default: throw new Error('Invalid key for modifyAccount: "' + key + '"'); } } this.universe.saveAccountDef(accountDef, null); this.__sendMessage({ type: 'modifyAccount', handle: msg.handle }); }, _cmd_deleteAccount: function mb__cmd_deleteAccount(msg) { this.universe.deleteAccount(msg.accountId); }, _cmd_modifyIdentity: function mb__cmd_modifyIdentity(msg) { var account = this.universe.getAccountForSenderIdentityId(msg.identityId), accountDef = account.accountDef, identity = this.universe.getIdentityForSenderIdentityId(msg.identityId); for (var key in msg.mods) { var val = msg.mods[key]; switch (key) { case 'name': identity.name = val; break; case 'address': identity.address = val; break; case 'replyTo': identity.replyTo = val; break; case 'signature': identity.signature = val; break; case 'signatureEnabled': identity.signatureEnabled = val; break; default: throw new Error('Invalid key for modifyIdentity: "' + key + '"'); } } this.universe.saveAccountDef(accountDef, null, function () { this.__sendMessage({ type: 'modifyIdentity', handle: msg.handle }); }.bind(this)); }, notifyBadLogin: function mb_notifyBadLogin(account, problem, whichSide) { this.__sendMessage({ type: 'badLogin', account: account.toBridgeWire(), problem: problem, whichSide: whichSide }); }, _cmd_viewAccounts: function mb__cmd_viewAccounts(msg) { var proxy = this._slices[msg.handle] = new SliceBridgeProxy(this, 'accounts', msg.handle); proxy.markers = this.universe.accounts.map(function (x) { return x.id; }); this._slicesByType['accounts'].push(proxy); var wireReps = this.universe.accounts.map(toBridgeWireOn); proxy.sendSplice(0, 0, wireReps, true, false); }, notifyAccountAdded: function mb_notifyAccountAdded(account) { var accountWireRep = account.toBridgeWire(); var i, proxy, slices, wireSplice = null, markersSplice = null; slices = this._slicesByType['accounts']; for (i = 0; i < slices.length; i++) { proxy = slices[i]; proxy.sendSplice(proxy.markers.length, 0, [accountWireRep], false, false); proxy.markers.push(account.id); } accountWireRep = account.toBridgeFolder(); slices = this._slicesByType['folders']; var startMarker = makeFolderSortString(account, accountWireRep), idxStart; for (i = 0; i < slices.length; i++) { proxy = slices[i]; if (proxy.mode === 'account') continue; idxStart = bsearchForInsert(proxy.markers, startMarker, strcmp); wireSplice = [accountWireRep]; markersSplice = [startMarker]; for (var iFolder = 0; iFolder < account.folders.length; iFolder++) { var folder = account.folders[iFolder], folderMarker = makeFolderSortString(account, folder), idxFolder = bsearchForInsert(markersSplice, folderMarker, strcmp); wireSplice.splice(idxFolder, 0, folder); markersSplice.splice(idxFolder, 0, folderMarker); } proxy.sendSplice(idxStart, 0, wireSplice, false, false); proxy.markers.splice.apply(proxy.markers, [ idxStart, 0 ].concat(markersSplice)); } }, notifyAccountModified: function (account) { var slices = this._slicesByType['accounts'], accountWireRep = account.toBridgeWire(); for (var i = 0; i < slices.length; i++) { var proxy = slices[i]; var idx = proxy.markers.indexOf(account.id); if (idx !== -1) { proxy.sendUpdate([ idx, accountWireRep ]); } } }, notifyAccountRemoved: function (accountId) { var i, proxy, slices; slices = this._slicesByType['accounts']; for (i = 0; i < slices.length; i++) { proxy = slices[i]; var idx = proxy.markers.indexOf(accountId); if (idx !== -1) { proxy.sendSplice(idx, 1, [], false, false); proxy.markers.splice(idx, 1); } } slices = this._slicesByType['folders']; var startMarker = accountId + '!!', endMarker = accountId + '!|'; for (i = 0; i < slices.length; i++) { proxy = slices[i]; var idxStart = bsearchForInsert(proxy.markers, startMarker, strcmp), idxEnd = bsearchForInsert(proxy.markers, endMarker, strcmp); if (idxEnd !== idxStart) { proxy.sendSplice(idxStart, idxEnd - idxStart, [], false, false); proxy.markers.splice(idxStart, idxEnd - idxStart); } } }, _cmd_viewSenderIdentities: function mb__cmd_viewSenderIdentities(msg) { var proxy = this._slices[msg.handle] = new SliceBridgeProxy(this, 'identities', msg.handle); this._slicesByType['identities'].push(proxy); var wireReps = this.universe.identities; proxy.sendSplice(0, 0, wireReps, true, false); }, _cmd_requestBodies: function (msg) { var self = this; this.universe.downloadBodies(msg.messages, msg.options, function () { self.__sendMessage({ type: 'requestBodiesComplete', handle: msg.handle, requestId: msg.requestId }); }); }, notifyFolderAdded: function (account, folderMeta) { var newMarker = makeFolderSortString(account, folderMeta); var slices = this._slicesByType['folders']; for (var i = 0; i < slices.length; i++) { var proxy = slices[i]; var idx = bsearchForInsert(proxy.markers, newMarker, strcmp); proxy.sendSplice(idx, 0, [folderMeta], false, false); proxy.markers.splice(idx, 0, newMarker); } }, notifyFolderModified: function (account, folderMeta) { var marker = makeFolderSortString(account, folderMeta); var slices = this._slicesByType['folders']; for (var i = 0; i < slices.length; i++) { var proxy = slices[i]; var idx = bsearchMaybeExists(proxy.markers, marker, strcmp); if (idx === null) continue; proxy.sendUpdate([ idx, folderMeta ]); } }, notifyFolderRemoved: function (account, folderMeta) { var marker = makeFolderSortString(account, folderMeta); var slices = this._slicesByType['folders']; for (var i = 0; i < slices.length; i++) { var proxy = slices[i]; var idx = bsearchMaybeExists(proxy.markers, marker, strcmp); if (idx === null) continue; proxy.sendSplice(idx, 1, [], false, false); proxy.markers.splice(idx, 1); } }, notifyBodyModified: function (suid, detail, body) { var handles = this._observedBodies[suid]; var defaultHandler = this.__sendMessage; if (handles) { for (var handle in handles) { var emit = handles[handle] || defaultHandler; emit.call(this, { type: 'bodyModified', handle: handle, bodyInfo: body, detail: detail }); } } }, _cmd_viewFolders: function mb__cmd_viewFolders(msg) { var proxy = this._slices[msg.handle] = new SliceBridgeProxy(this, 'folders', msg.handle); this._slicesByType['folders'].push(proxy); proxy.mode = msg.mode; proxy.argument = msg.argument; var markers = proxy.markers = []; var wireReps = []; function pushAccountFolders(acct) { for (var iFolder = 0; iFolder < acct.folders.length; iFolder++) { var folder = acct.folders[iFolder]; var newMarker = makeFolderSortString(acct, folder); var idx = bsearchForInsert(markers, newMarker, strcmp); wireReps.splice(idx, 0, folder); markers.splice(idx, 0, newMarker); } } if (msg.mode === 'account') { pushAccountFolders(this.universe.getAccountForAccountId(msg.argument)); } else { var accounts = this.universe.accounts.concat(); accounts.sort(function (a, b) { return a.id.localeCompare(b.id); }); for (var iAcct = 0; iAcct < accounts.length; iAcct++) { var acct = accounts[iAcct], acctBridgeRep = acct.toBridgeFolder(), acctMarker = makeFolderSortString(acct, acctBridgeRep), idxAcct = bsearchForInsert(markers, acctMarker, strcmp); wireReps.splice(idxAcct, 0, acctBridgeRep); markers.splice(idxAcct, 0, acctMarker); pushAccountFolders(acct); } } proxy.sendSplice(0, 0, wireReps, true, false); }, _cmd_viewFolderMessages: function mb__cmd_viewFolderMessages(msg) { var proxy = this._slices[msg.handle] = new SliceBridgeProxy(this, 'headers', msg.handle); this._slicesByType['headers'].push(proxy); var account = this.universe.getAccountForFolderId(msg.folderId); account.sliceFolderMessages(msg.folderId, proxy); }, _cmd_searchFolderMessages: function mb__cmd_searchFolderMessages(msg) { var proxy = this._slices[msg.handle] = new SliceBridgeProxy(this, 'matchedHeaders', msg.handle); this._slicesByType['matchedHeaders'].push(proxy); var account = this.universe.getAccountForFolderId(msg.folderId); account.searchFolderMessages(msg.folderId, proxy, msg.phrase, msg.whatToSearch); }, _cmd_refreshHeaders: function mb__cmd_refreshHeaders(msg) { var proxy = this._slices[msg.handle]; if (!proxy) { logic(this, 'badSliceHandle', { handle: msg.handle }); return; } if (proxy.__listener) proxy.__listener.refresh(); }, _cmd_growSlice: function mb__cmd_growSlice(msg) { var proxy = this._slices[msg.handle]; if (!proxy) { logic(this, 'badSliceHandle', { handle: msg.handle }); return; } if (proxy.__listener) proxy.__listener.reqGrow(msg.dirMagnitude, msg.userRequestsGrowth); }, _cmd_shrinkSlice: function mb__cmd_shrinkSlice(msg) { var proxy = this._slices[msg.handle]; if (!proxy) { logic(this, 'badSliceHandle', { handle: msg.handle }); return; } if (proxy.__listener) proxy.__listener.reqNoteRanges(msg.firstIndex, msg.firstSuid, msg.lastIndex, msg.lastSuid); }, _cmd_killSlice: function mb__cmd_killSlice(msg) { var proxy = this._slices[msg.handle]; if (!proxy) { logic(this, 'badSliceHandle', { handle: msg.handle }); return; } delete this._slices[msg.handle]; var proxies = this._slicesByType[proxy._ns], idx = proxies.indexOf(proxy); proxies.splice(idx, 1); proxy.die(); this.__sendMessage({ type: 'sliceDead', handle: msg.handle }); }, _cmd_getBody: function mb__cmd_getBody(msg) { var self = this; var folderStorage = this.universe.getFolderStorageForMessageSuid(msg.suid); var pendingUpdates = []; var catchPending = function catchPending(msg) { pendingUpdates.push(msg); }; if (!this._observedBodies[msg.suid]) this._observedBodies[msg.suid] = {}; this._observedBodies[msg.suid][msg.handle] = catchPending; var handler = function (bodyInfo) { self.__sendMessage({ type: 'gotBody', handle: msg.handle, bodyInfo: bodyInfo }); if (msg.downloadBodyReps && !folderStorage.messageBodyRepsDownloaded(bodyInfo)) { self.universe.downloadMessageBodyReps(msg.suid, msg.date, function () { }); } pendingUpdates.forEach(self.__sendMessage, self); pendingUpdates = null; self._observedBodies[msg.suid][msg.handle] = null; }; if (msg.withBodyReps) folderStorage.getMessageBodyWithReps(msg.suid, msg.date, handler); else folderStorage.getMessageBody(msg.suid, msg.date, handler); }, _cmd_killBody: function (msg) { var handles = this._observedBodies[msg.id]; if (handles) { delete handles[msg.handle]; var purgeHandles = true; for (var key in handles) { purgeHandles = false; break; } if (purgeHandles) { delete this._observedBodies[msg.id]; } } this.__sendMessage({ type: 'bodyDead', handle: msg.handle }); }, _cmd_downloadAttachments: function mb__cmd__downloadAttachments(msg) { var self = this; this.universe.downloadMessageAttachments(msg.suid, msg.date, msg.relPartIndices, msg.attachmentIndices, msg.registerAttachments, function (err) { self.__sendMessage({ type: 'downloadedAttachments', handle: msg.handle }); }); }, _cmd_modifyMessageTags: function mb__cmd_modifyMessageTags(msg) { var longtermIds = this.universe.modifyMessageTags(msg.opcode, msg.messages, msg.addTags, msg.removeTags); this.__sendMessage({ type: 'mutationConfirmed', handle: msg.handle, longtermIds: longtermIds }); }, _cmd_deleteMessages: function mb__cmd_deleteMessages(msg) { var longtermIds = this.universe.deleteMessages(msg.messages); this.__sendMessage({ type: 'mutationConfirmed', handle: msg.handle, longtermIds: longtermIds }); }, _cmd_moveMessages: function mb__cmd_moveMessages(msg) { var longtermIds = this.universe.moveMessages(msg.messages, msg.targetFolder, function (err, moveMap) { this.__sendMessage({ type: 'mutationConfirmed', handle: msg.handle, longtermIds: longtermIds, result: moveMap }); }.bind(this)); }, _cmd_sendOutboxMessages: function (msg) { var account = this.universe.getAccountForAccountId(msg.accountId); this.universe.sendOutboxMessages(account, { reason: 'api request' }, function (err) { this.__sendMessage({ type: 'sendOutboxMessages', handle: msg.handle }); }.bind(this)); }, _cmd_setOutboxSyncEnabled: function (msg) { var account = this.universe.getAccountForAccountId(msg.accountId); this.universe.setOutboxSyncEnabled(account, msg.outboxSyncEnabled, function () { this.__sendMessage({ type: 'setOutboxSyncEnabled', handle: msg.handle }); }.bind(this)); }, _cmd_undo: function mb__cmd_undo(msg) { this.universe.undoMutation(msg.longtermIds); }, _cmd_beginCompose: function mb__cmd_beginCompose(msg) { require([ './drafts/composer', 'mailchew' ], function ($composer, $mailchew) { var req = this._pendingRequests[msg.handle] = { type: 'compose', active: 'begin', account: null, persistedNamer: null, die: false }; var account, identity, folderId; if (msg.mode === 'new' && msg.submode === 'folder') account = this.universe.getAccountForFolderId(msg.refSuid); else account = this.universe.getAccountForMessageSuid(msg.refSuid); req.account = account; identity = account.identities[0]; var bodyText = $mailchew.generateBaseComposeBody(identity); if (msg.mode !== 'reply' && msg.mode !== 'forward') { return this.__sendMessage({ type: 'composeBegun', handle: msg.handle, error: null, identity: identity, subject: '', body: { text: bodyText, html: null }, to: [], cc: [], bcc: [], references: null, attachments: [] }); } var folderStorage = this.universe.getFolderStorageForMessageSuid(msg.refSuid); var self = this; folderStorage.getMessage(msg.refSuid, msg.refDate, { withBodyReps: true }, function (res) { if (!res) { return console.warn('Cannot compose message missing header/body: ', msg.refSuid); } var header = res.header; var bodyInfo = res.body; if (msg.mode === 'reply') { var rTo, rCc, rBcc; var replyToAddress = header.replyTo && header.replyTo[0]; var effectiveAuthor = replyToAddress || msg.refAuthor; switch (msg.submode) { case 'list': case null: case 'sender': rTo = [effectiveAuthor]; rCc = rBcc = []; break; case 'all': if (checkIfAddressListContainsAddress(header.to, effectiveAuthor) || checkIfAddressListContainsAddress(header.cc, effectiveAuthor)) { rTo = header.to; } else { if (header.to && header.to.length) rTo = [effectiveAuthor].concat(header.to); else rTo = [effectiveAuthor]; } var notYourIdentity = function (person) { return person.address !== identity.address; }; rTo = rTo.filter(notYourIdentity); rCc = (header.cc || []).filter(notYourIdentity); rBcc = header.bcc; break; } var referencesStr; if (bodyInfo.references) { referencesStr = bodyInfo.references.concat([msg.refGuid]).map(function (x) { return '<' + x + '>'; }).join(' '); } else if (msg.refGuid) { referencesStr = '<' + msg.refGuid + '>'; } else { referencesStr = ''; } req.active = null; self.__sendMessage({ type: 'composeBegun', handle: msg.handle, error: null, identity: identity, subject: $mailchew.generateReplySubject(msg.refSubject), body: $mailchew.generateReplyBody(bodyInfo.bodyReps, effectiveAuthor, msg.refDate, identity, msg.refGuid), to: rTo, cc: rCc, bcc: rBcc, referencesStr: referencesStr, attachments: [] }); } else { req.active = null; self.__sendMessage({ type: 'composeBegun', handle: msg.handle, error: null, identity: identity, subject: $mailchew.generateForwardSubject(msg.refSubject), body: $mailchew.generateForwardMessage(msg.refAuthor, msg.refDate, msg.refSubject, header, bodyInfo, identity), to: [], cc: [], bcc: [], references: null, attachments: [] }); } }); }.bind(this)); }, _cmd_attachBlobToDraft: function (msg) { require(['./drafts/composer'], function ($composer) { var draftReq = this._pendingRequests[msg.draftHandle]; if (!draftReq) return; this.universe.attachBlobToDraft(draftReq.account, draftReq.persistedNamer, msg.attachmentDef, function (err) { this.__sendMessage({ type: 'attachedBlobToDraft', handle: msg.handle, draftHandle: msg.draftHandle, err: err }); }.bind(this)); }.bind(this)); }, _cmd_detachAttachmentFromDraft: function (msg) { require(['./drafts/composer'], function ($composer) { var req = this._pendingRequests[msg.draftHandle]; if (!req) return; this.universe.detachAttachmentFromDraft(req.account, req.persistedNamer, msg.attachmentIndex, function (err) { this.__sendMessage({ type: 'detachedAttachmentFromDraft', handle: msg.handle, draftHandle: msg.draftHandle, err: err }); }.bind(this)); }.bind(this)); }, _cmd_resumeCompose: function mb__cmd_resumeCompose(msg) { var req = this._pendingRequests[msg.handle] = { type: 'compose', active: 'resume', account: null, persistedNamer: msg.messageNamer, die: false }; var account = req.account = this.universe.getAccountForMessageSuid(msg.messageNamer.suid); var folderStorage = this.universe.getFolderStorageForMessageSuid(msg.messageNamer.suid); var self = this; folderStorage.runMutexed('resumeCompose', function (callWhenDone) { function fail() { self.__sendMessage({ type: 'composeBegun', handle: msg.handle, error: 'no-message' }); callWhenDone(); } folderStorage.getMessage(msg.messageNamer.suid, msg.messageNamer.date, function (res) { try { if (!res.header || !res.body) { fail(); return; } var header = res.header, body = res.body; var composeBody = { text: '', html: null }; if (body.bodyReps.length >= 1 && body.bodyReps[0].type === 'plain' && body.bodyReps[0].content.length === 2 && body.bodyReps[0].content[0] === 1) { composeBody.text = body.bodyReps[0].content[1]; } if (body.bodyReps.length == 2 && body.bodyReps[1].type === 'html') { composeBody.html = body.bodyReps[1].content; } var attachments = []; body.attachments.forEach(function (att) { attachments.push({ name: att.name, blob: { size: att.sizeEstimate, type: att.type } }); }); req.active = null; self.__sendMessage({ type: 'composeBegun', handle: msg.handle, error: null, identity: account.identities[0], subject: header.subject, body: composeBody, to: header.to, cc: header.cc, bcc: header.bcc, referencesStr: body.references, attachments: attachments, sendStatus: header.sendStatus }); callWhenDone(); } catch (ex) { fail(); } }); }); }, _cmd_doneCompose: function mb__cmd_doneCompose(msg) { require(['./drafts/composer'], function ($composer) { var req = this._pendingRequests[msg.handle], self = this; if (!req) { return; } if (msg.command === 'die') { if (req.active) { req.die = true; } else { delete this._pendingRequests[msg.handle]; } return; } var account; if (msg.command === 'delete') { function sendDeleted() { self.__sendMessage({ type: 'doneCompose', handle: msg.handle }); } if (req.persistedNamer) { account = this.universe.getAccountForMessageSuid(req.persistedNamer.suid); this.universe.deleteDraft(account, req.persistedNamer, sendDeleted); } else { sendDeleted(); } delete this._pendingRequests[msg.handle]; return; } var wireRep = msg.state; account = this.universe.getAccountForSenderIdentityId(wireRep.senderId); var identity = this.universe.getIdentityForSenderIdentityId(wireRep.senderId); if (msg.command === 'send') { req.persistedNamer = this.universe.saveDraft(account, req.persistedNamer, wireRep, function (err, newRecords) { req.active = null; if (req.die) { delete this._pendingRequests[msg.handle]; } var outboxFolder = account.getFirstFolderWithType('outbox'); this.universe.moveMessages([req.persistedNamer], outboxFolder.id); this.universe.sendOutboxMessages(account, { reason: 'moved to outbox', emitNotifications: this.universe.online }); }.bind(this)); var initialSendStatus = { accountId: account.id, suid: req.persistedNamer.suid, state: this.universe.online ? 'sending' : 'pending', emitNotifications: true }; this.__sendMessage({ type: 'doneCompose', handle: msg.handle, sendStatus: initialSendStatus }); this.universe.__notifyBackgroundSendStatus(initialSendStatus); } else if (msg.command === 'save') { req.persistedNamer = this.universe.saveDraft(account, req.persistedNamer, wireRep, function (err) { req.active = null; if (req.die) delete self._pendingRequests[msg.handle]; self.__sendMessage({ type: 'doneCompose', handle: msg.handle }); }); } }.bind(this)); }, notifyCronSyncStart: function mb_notifyCronSyncStart(accountIds) { this.__sendMessage({ type: 'cronSyncStart', accountIds: accountIds }); }, notifyCronSyncStop: function mb_notifyCronSyncStop(accountsResults) { this.__sendMessage({ type: 'cronSyncStop', accountsResults: accountsResults }); }, notifyBackgroundSendStatus: function (data) { this.__sendMessage({ type: 'backgroundSendStatus', data: data }); } }; }); ; define('a64', ['exports'], function (exports) { var ORDERED_ARBITRARY_BASE64_CHARS = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '}' ]; var ZERO_PADDING = '0000000000000000'; function encodeInt(v, padTo) { var sbits = []; do { sbits.push(ORDERED_ARBITRARY_BASE64_CHARS[v & 63]); v = Math.floor(v / 64); } while (v > 0); sbits.reverse(); var estr = sbits.join(''); if (padTo && estr.length < padTo) return ZERO_PADDING.substring(0, padTo - estr.length) + estr; return estr; } exports.encodeInt = encodeInt; var E10_14_RSH_14 = Math.pow(10, 14) / Math.pow(2, 14), P2_14 = Math.pow(2, 14), P2_22 = Math.pow(2, 22), P2_32 = Math.pow(2, 32), P2_36 = Math.pow(2, 36), MASK32 = 4294967295; exports.parseUI64 = function p(s, padTo) { if (s.length < 16) { return encodeInt(parseInt(s, 10)); } var lowParse = parseInt(s.substring(s.length - 14), 10), highParse = parseInt(s.substring(0, s.length - 14), 10), rawHighBits = highParse * E10_14_RSH_14; var lowBitsAdded = rawHighBits % P2_36 * P2_14 % P2_36 + lowParse % P2_36, lowBits = lowBitsAdded % P2_36, overflow = Math.floor(lowBitsAdded / P2_36) % 2; var highBits = Math.floor(rawHighBits / P2_22) + Math.floor(lowParse / P2_36) + overflow; var outStr = encodeInt(highBits) + encodeInt(lowBits, 6); if (padTo && outStr.length < padTo) return ZERO_PADDING.substring(0, padTo - outStr.length) + outStr; return outStr; }; exports.cmpUI64 = function (a, b) { var c = a.length - b.length; if (c !== 0) return c; if (a < b) return -1; else if (a > b) return 1; return 0; }; exports.decodeUI64 = function d(es) { var iNonZero = 0; for (; es.charCodeAt(iNonZero) === 48; iNonZero++) { } if (iNonZero) es = es.substring(iNonZero); var v, i; if (es.length <= 8) { v = 0; for (i = 0; i < es.length; i++) { v = v * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(es[i]); } return v.toString(10); } var ues = es.substring(0, es.length - 6), uv = 0, les = es.substring(es.length - 6), lv = 0; for (i = 0; i < ues.length; i++) { uv = uv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(ues[i]); } for (i = 0; i < les.length; i++) { lv = lv * 64 + ORDERED_ARBITRARY_BASE64_CHARS.indexOf(les[i]); } var rsh14val = uv * P2_22 + Math.floor(lv / P2_14), uraw = rsh14val / E10_14_RSH_14, udv = Math.floor(uraw), uds = udv.toString(); var rsh14Leftover = rsh14val - udv * E10_14_RSH_14, lowBitsRemoved = rsh14Leftover * P2_14 + lv % P2_14; var lds = lowBitsRemoved.toString(); if (lds.length < 14) lds = ZERO_PADDING.substring(0, 14 - lds.length) + lds; return uds + lds; }; }); ; define('syncbase', [ './date', 'exports' ], function ($date, exports) { exports.OPEN_REFRESH_THRESH_MS = 10 * 60 * 1000; exports.GROW_REFRESH_THRESH_MS = 60 * 60 * 1000; exports.EXPECTED_BLOCK_SIZE = 8; exports.MAX_BLOCK_SIZE = exports.EXPECTED_BLOCK_SIZE * 1024, exports.BLOCK_SPLIT_SMALL_PART = exports.EXPECTED_BLOCK_SIZE / 3 * 1024, exports.BLOCK_SPLIT_EQUAL_PART = exports.EXPECTED_BLOCK_SIZE / 2 * 1024, exports.BLOCK_SPLIT_LARGE_PART = exports.EXPECTED_BLOCK_SIZE / 1.5 * 1024; exports.BLOCK_PURGE_EVERY_N_NEW_BODY_BLOCKS = 32; exports.BLOCK_PURGE_ONLY_AFTER_UNSYNCED_MS = 14 * $date.DAY_MILLIS; exports.BLOCK_PURGE_HARD_MAX_BLOCK_LIMIT = 1024; exports.POP3_SAVE_STATE_EVERY_N_MESSAGES = 50; exports.POP3_MAX_MESSAGES_PER_SYNC = 100; exports.POP3_INFER_ATTACHMENTS_SIZE = 512 * 1024; exports.POP3_SNIPPET_SIZE_GOAL = 4 * 1024; exports.SYNC_FOLDER_LIST_EVERY_MS = $date.DAY_MILLIS; exports.INITIAL_FILL_SIZE = 15; exports.INITIAL_SYNC_DAYS = 3; exports.INITIAL_SYNC_GROWTH_DAYS = 3; exports.TIME_SCALE_FACTOR_ON_NO_MESSAGES = 2; exports.OLDEST_SYNC_DATE = Date.UTC(1990, 0, 1); exports.SYNC_WHOLE_FOLDER_AT_N_MESSAGES = 40; exports.BISECT_DATE_AT_N_MESSAGES = 60; exports.TOO_MANY_MESSAGES = 2000; exports.IMAP_SEARCH_AMBIGUITY_MS = $date.DAY_MILLIS; exports.HEADER_EST_SIZE_IN_BYTES = 430; exports.MAX_OP_TRY_COUNT = 10; exports.OP_UNKNOWN_ERROR_TRY_COUNT_INCREMENT = 5; exports.DEFERRED_OP_DELAY_MS = 30 * 1000; exports.CHECK_INTERVALS_ENUMS_TO_MS = { 'manual': 0, '3min': 3 * 60 * 1000, '5min': 5 * 60 * 1000, '10min': 10 * 60 * 1000, '15min': 15 * 60 * 1000, '30min': 30 * 60 * 1000, '60min': 60 * 60 * 1000 }; exports.DEFAULT_CHECK_INTERVAL_ENUM = 'manual'; exports.CONNECT_TIMEOUT_MS = 30000; exports.STALE_CONNECTION_TIMEOUT_MS = 30000; exports.KILL_CONNECTIONS_WHEN_JOBLESS = true; var DAY_MILLIS = 24 * 60 * 60 * 1000; exports.SYNC_RANGE_ENUMS_TO_MS = { 'auto': 30 * DAY_MILLIS, '1d': 1 * DAY_MILLIS, '3d': 3 * DAY_MILLIS, '1w': 7 * DAY_MILLIS, '2w': 14 * DAY_MILLIS, '1m': 30 * DAY_MILLIS, 'all': 30 * 365 * DAY_MILLIS }; exports.CRONSYNC_MAX_MESSAGES_TO_REPORT_PER_ACCOUNT = 5; exports.CRONSYNC_MAX_SNIPPETS_TO_FETCH_PER_ACCOUNT = 5; exports.MAX_SNIPPET_BYTES = 4 * 1024; exports.TEST_adjustSyncValues = function TEST_adjustSyncValues(syncValues) { var legacyKeys = { fillSize: 'INITIAL_FILL_SIZE', days: 'INITIAL_SYNC_DAYS', growDays: 'INITIAL_SYNC_GROWTH_DAYS', wholeFolderSync: 'SYNC_WHOLE_FOLDER_AT_N_MESSAGES', bisectThresh: 'BISECT_DATE_AT_N_MESSAGES', tooMany: 'TOO_MANY_MESSAGES', scaleFactor: 'TIME_SCALE_FACTOR_ON_NO_MESSAGES', openRefreshThresh: 'OPEN_REFRESH_THRESH_MS', growRefreshThresh: 'GROW_REFRESH_THRESH_MS' }; for (var key in syncValues) if (syncValues.hasOwnProperty(key)) { var outKey = legacyKeys[key] || key; if (exports.hasOwnProperty(outKey)) { exports[outKey] = syncValues[key]; } else { console.warn('Invalid key for TEST_adjustSyncValues: ' + key); } } }; }); ; define('maildb', [ './worker-router', 'exports' ], function ($router, exports) { 'use strict'; var sendMessage = $router.registerCallbackType('maildb'); function MailDB(testOptions) { this._callbacksQueue = []; function processQueue() { console.log('main thread reports DB ready'); this._ready = true; this._callbacksQueue.forEach(function executeCallback(cb) { cb(); }); this._callbacksQueue = null; } sendMessage('open', [testOptions], processQueue.bind(this)); } exports.MailDB = MailDB; MailDB.prototype = { close: function () { sendMessage('close'); }, getConfig: function (callback) { if (!this._ready) { console.log('deferring getConfig call until ready'); this._callbacksQueue.push(this.getConfig.bind(this, callback)); return; } console.log('issuing getConfig call to main thread'); sendMessage('getConfig', null, callback); }, saveConfig: function (config) { sendMessage('saveConfig', [config]); }, saveAccountDef: function (config, accountDef, folderInfo, callback) { sendMessage('saveAccountDef', [ config, accountDef, folderInfo ], callback); }, loadHeaderBlock: function (folderId, blockId, callback) { sendMessage('loadHeaderBlock', [ folderId, blockId ], callback); }, loadBodyBlock: function (folderId, blockId, callback) { sendMessage('loadBodyBlock', [ folderId, blockId ], callback); }, saveAccountFolderStates: function (accountId, folderInfo, perFolderStuff, deletedFolderIds, callback, reuseTrans) { var args = [ accountId, folderInfo, perFolderStuff, deletedFolderIds ]; sendMessage('saveAccountFolderStates', args, callback); return null; }, deleteAccount: function (accountId) { sendMessage('deleteAccount', [accountId]); } }; }); ; define('allback', ['exports'], function (exports) { exports.allbackMaker = function allbackMaker(names, allDoneCallback) { var aggrData = Object.create(null), callbacks = {}, waitingFor = names.concat(); names.forEach(function (name) { aggrData[name] = undefined; callbacks[name] = function anAllback(callbackResult) { var i = waitingFor.indexOf(name); if (i === -1) { console.error('Callback \'' + name + '\' fired multiple times!'); throw new Error('Callback \'' + name + '\' fired multiple times!'); } waitingFor.splice(i, 1); if (arguments.length > 1) aggrData[name] = arguments; else aggrData[name] = callbackResult; if (waitingFor.length === 0 && allDoneCallback) allDoneCallback(aggrData); }; }); return callbacks; }; exports.latch = function () { var ready = false; var deferred = {}; var results = Object.create(null); var count = 0; deferred.promise = new Promise(function (resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; }); function defer(name) { count++; var resolved = false; return function resolve() { if (resolved) { var err = new Error('You have already resolved this deferred!'); console.error(err + '\n' + err.stack); throw err; } resolved = true; if (name != null) { results[name] = Array.slice(arguments); } if (--count === 0) { setZeroTimeout(function () { deferred.resolve(results); }); } }; } var unlatch = defer(); return { defer: defer, then: function () { var ret = deferred.promise.then.apply(deferred.promise, arguments); if (!ready) { ready = true; unlatch(); } return ret; } }; }; exports.extractErrFromCallbackArgs = function (results) { var anyErr = null; for (var key in results) { var args = results[key]; var errIfAny = args[0]; if (errIfAny) { anyErr = errIfAny; break; } } return anyErr; }; exports.latchedWithRejections = function (namedPromises) { return new Promise(function (resolve, reject) { var results = Object.create(null); var pending = 0; Object.keys(namedPromises).forEach(function (name) { pending++; var promise = namedPromises[name]; promise.then(function (result) { results[name] = { resolved: true, value: result }; if (--pending === 0) { resolve(results); } }, function (err) { results[name] = { resolved: false, value: err }; if (--pending === 0) { resolve(results); } }); }); if (!pending) { resolve(results); } }); }; }); ; define('mailslice', [ 'require', 'exports', 'module', 'logic', './util', './a64', './allback', './date', './syncbase' ], function (require, exports, module) { var logic = require('logic'); var $util = require('./util'); var $a64 = require('./a64'); var $allback = require('./allback'); var $date = require('./date'); var $sync = require('./syncbase'); var bsearchForInsert = $util.bsearchForInsert, bsearchMaybeExists = $util.bsearchMaybeExists, cmpHeaderYoungToOld = $util.cmpHeaderYoungToOld, allbackMaker = $allback.allbackMaker, BEFORE = $date.BEFORE, ON_OR_BEFORE = $date.ON_OR_BEFORE, SINCE = $date.SINCE, STRICTLY_AFTER = $date.STRICTLY_AFTER, IN_BS_DATE_RANGE = $date.IN_BS_DATE_RANGE, HOUR_MILLIS = $date.HOUR_MILLIS, DAY_MILLIS = $date.DAY_MILLIS, NOW = $date.NOW, quantizeDate = $date.quantizeDate, quantizeDateUp = $date.quantizeDateUp; var PASTWARDS = 1, FUTUREWARDS = -1; var OBJ_OVERHEAD_EST = 2, STR_ATTR_OVERHEAD_EST = 5, NUM_ATTR_OVERHEAD_EST = 10, LIST_ATTR_OVERHEAD_EST = 4, NULL_ATTR_OVERHEAD_EST = 2, LIST_OVERHEAD_EST = 4, NUM_OVERHEAD_EST = 8, STR_OVERHEAD_EST = 4; var tupleRangeIntersectsTupleRange = exports.tupleRangeIntersectsTupleRange = function tupleRangeIntersectsTupleRange(a, b) { if (BEFORE(a.endTS, b.startTS) || STRICTLY_AFTER(a.startTS, b.endTS)) return false; if (a.endTS === b.startTS && a.endUID < b.startUID || a.startTS === b.endTS && a.startTS > b.endUID) return false; return true; }; var SYNC_START_MINIMUM_PROGRESS = 0.02; function MailSlice(bridgeHandle, storage) { this._bridgeHandle = bridgeHandle; bridgeHandle.__listener = this; this._storage = storage; logic.defineScope(this, 'MailSlice', { bridgeHandle: bridgeHandle._handle }); this.startTS = null; this.startUID = null; this.endTS = null; this.endUID = null; this.waitingOnData = false; this.ignoreHeaders = false; this.headers = []; this.desiredHeaders = $sync.INITIAL_FILL_SIZE; this.headerCount = storage.headerCount; } exports.MailSlice = MailSlice; MailSlice.prototype = { type: 'folder', set atTop(val) { if (this._bridgeHandle) this._bridgeHandle.atTop = val; return val; }, set atBottom(val) { if (this._bridgeHandle) this._bridgeHandle.atBottom = val; return val; }, set userCanGrowUpwards(val) { if (this._bridgeHandle) this._bridgeHandle.userCanGrowUpwards = val; return val; }, set userCanGrowDownwards(val) { if (this._bridgeHandle) this._bridgeHandle.userCanGrowDownwards = val; return val; }, get headerCount() { if (this._bridgeHandle) { return this._bridgeHandle.headerCount; } return null; }, set headerCount(val) { if (this._bridgeHandle) this._bridgeHandle.headerCount = val; return val; }, _updateSliceFlags: function () { var flagHolder = this._bridgeHandle; flagHolder.atTop = this._storage.headerIsYoungestKnown(this.endTS, this.endUID); flagHolder.atBottom = this._storage.headerIsOldestKnown(this.startTS, this.startUID); if (flagHolder.atTop) flagHolder.userCanGrowUpwards = !this._storage.syncedToToday(); else flagHolder.userCanGrowUpwards = false; if (flagHolder.atBottom) flagHolder.userCanGrowDownwards = !this._storage.syncedToDawnOfTime(); else flagHolder.userCanGrowDownwards = false; }, reset: function () { if (!this._bridgeHandle) return; this.headerCount = this._storage.headerCount; if (this.headers.length) { this._bridgeHandle.sendSplice(0, this.headers.length, [], false, true); this.headers.splice(0, this.headers.length); this.startTS = null; this.startUID = null; this.endTS = null; this.endUID = null; } }, refresh: function () { this._storage.refreshSlice(this); }, reqNoteRanges: function (firstIndex, firstSuid, lastIndex, lastSuid) { if (!this._bridgeHandle) return; var i; if (firstIndex >= this.headers.length || this.headers[firstIndex].suid !== firstSuid) { firstIndex = 0; for (i = 0; i < this.headers.length; i++) { if (this.headers[i].suid === firstSuid) { firstIndex = i; break; } } } if (lastIndex >= this.headers.length || this.headers[lastIndex].suid !== lastSuid) { for (i = this.headers.length - 1; i >= 0; i--) { if (this.headers[i].suid === lastSuid) { lastIndex = i; break; } } } if (lastIndex + 1 < this.headers.length) { this.atBottom = false; this.userCanGrowDownwards = false; var delCount = this.headers.length - lastIndex - 1; this.desiredHeaders -= delCount; this._bridgeHandle.sendSplice(lastIndex + 1, delCount, [], true, firstIndex > 0); this.headers.splice(lastIndex + 1, this.headers.length - lastIndex - 1); var lastHeader = this.headers[lastIndex]; this.startTS = lastHeader.date; this.startUID = lastHeader.id; } if (firstIndex > 0) { this.atTop = false; this.userCanGrowUpwards = false; this.desiredHeaders -= firstIndex; this._bridgeHandle.sendSplice(0, firstIndex, [], true, false); this.headers.splice(0, firstIndex); var firstHeader = this.headers[0]; this.endTS = firstHeader.date; this.endUID = firstHeader.id; } this._storage.sliceShrunk(this); }, reqGrow: function (dirMagnitude, userRequestsGrowth) { if (dirMagnitude === -1) dirMagnitude = -$sync.INITIAL_FILL_SIZE; else if (dirMagnitude === 1) dirMagnitude = $sync.INITIAL_FILL_SIZE; this._storage.growSlice(this, dirMagnitude, userRequestsGrowth); }, sendEmptyCompletion: function () { this.setStatus('synced', true, false); }, setStatus: function (status, requested, moreExpected, flushAccumulated, progress, newEmailCount) { if (!this._bridgeHandle) return; switch (status) { case 'synced': case 'syncfailed': this._updateSliceFlags(); break; } this._bridgeHandle.sendStatus(status, requested, moreExpected, progress, newEmailCount); }, setSyncProgress: function (value) { if (!this._bridgeHandle) return; this._bridgeHandle.sendSyncProgress(value); }, batchAppendHeaders: function (headers, insertAt, moreComing) { if (!this._bridgeHandle) return; logic(this, 'headersAppended', { headers: headers }); if (insertAt === -1) insertAt = this.headers.length; this.headers.splice.apply(this.headers, [ insertAt, 0 ].concat(headers)); for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (this.startTS === null || BEFORE(header.date, this.startTS)) { this.startTS = header.date; this.startUID = header.id; } else if (header.date === this.startTS && header.id < this.startUID) { this.startUID = header.id; } if (this.endTS === null || STRICTLY_AFTER(header.date, this.endTS)) { this.endTS = header.date; this.endUID = header.id; } else if (header.date === this.endTS && header.id > this.endUID) { this.endUID = header.id; } } this._updateSliceFlags(); this._bridgeHandle.sendSplice(insertAt, 0, headers, true, moreComing); }, onHeaderAdded: function (header, body, syncDriven, messageIsNew) { if (!this._bridgeHandle) return; var idx = bsearchForInsert(this.headers, header, cmpHeaderYoungToOld); var hlen = this.headers.length; if (hlen >= this.desiredHeaders && idx === hlen) return; if (hlen >= this.desiredHeaders) this.desiredHeaders++; if (this.startTS === null || BEFORE(header.date, this.startTS)) { this.startTS = header.date; this.startUID = header.id; } else if (header.date === this.startTS && header.id < this.startUID) { this.startUID = header.id; } if (this.endTS === null || STRICTLY_AFTER(header.date, this.endTS)) { this.endTS = header.date; this.endUID = header.id; } else if (header.date === this.endTS && header.id > this.endUID) { this.endUID = header.id; } logic(this, 'headerAdded', { index: idx, header: header }); this._bridgeHandle.sendSplice(idx, 0, [header], Boolean(this.waitingOnData), Boolean(this.waitingOnData)); this.headers.splice(idx, 0, header); }, onHeaderModified: function (header, body) { if (!this._bridgeHandle) return; var idx = bsearchMaybeExists(this.headers, header, cmpHeaderYoungToOld); if (idx !== null) { this.headers[idx] = header; logic(this, 'headerModified', { index: idx, header: header }); this._bridgeHandle.sendUpdate([ idx, header ]); } }, onHeaderRemoved: function (header) { if (!this._bridgeHandle) return; var idx = bsearchMaybeExists(this.headers, header, cmpHeaderYoungToOld); if (idx !== null) { logic(this, 'headerRemoved', { index: idx, header: header }); this._bridgeHandle.sendSplice(idx, 1, [], Boolean(this.waitingOnData), Boolean(this.waitingOnData)); this.headers.splice(idx, 1); if (header.date === this.endTS && header.id === this.endUID) { if (!this.headers.length) { this.endTS = null; this.endUID = null; } else { this.endTS = this.headers[0].date; this.endUID = this.headers[0].id; } } if (header.date === this.startTS && header.id === this.startUID) { if (!this.headers.length) { this.startTS = null; this.startUID = null; } else { var lastHeader = this.headers[this.headers.length - 1]; this.startTS = lastHeader.date; this.startUID = lastHeader.id; } } } }, die: function () { this._bridgeHandle = null; this.desiredHeaders = 0; this._storage.dyingSlice(this); }, get isDead() { return this._bridgeHandle === null; } }; var FOLDER_DB_VERSION = exports.FOLDER_DB_VERSION = 3; function FolderStorage(account, folderId, persistedFolderInfo, dbConn, FolderSyncer) { this._account = account; this._imapDb = dbConn; this.folderId = folderId; this.folderMeta = persistedFolderInfo.$meta; this._folderImpl = persistedFolderInfo.$impl; logic.defineScope(this, 'FolderStorage', { accountId: account.id, folderId: folderId }); this._accuracyRanges = persistedFolderInfo.accuracy; this._headerBlockInfos = persistedFolderInfo.headerBlocks; this.headerCount = 0; if (this._headerBlockInfos) { this._headerBlockInfos.forEach(function (headerBlockInfo) { this.headerCount += headerBlockInfo.count; }.bind(this)); } this._bodyBlockInfos = persistedFolderInfo.bodyBlocks; this._serverIdHeaderBlockMapping = persistedFolderInfo.serverIdHeaderBlockMapping; this._headerBlocks = {}; this._loadedHeaderBlockInfos = []; this._bodyBlocks = {}; this._loadedBodyBlockInfos = []; this._flushExcessTimeoutId = 0; this._bound_flushExcessOnTimeout = this._flushExcessOnTimeout.bind(this); this._bound_makeHeaderBlock = this._makeHeaderBlock.bind(this); this._bound_insertHeaderInBlock = this._insertHeaderInBlock.bind(this); this._bound_splitHeaderBlock = this._splitHeaderBlock.bind(this); this._bound_deleteHeaderFromBlock = this._deleteHeaderFromBlock.bind(this); this._bound_makeBodyBlock = this._makeBodyBlock.bind(this); this._bound_insertBodyInBlock = this._insertBodyInBlock.bind(this); this._bound_splitBodyBlock = this._splitBodyBlock.bind(this); this._bound_deleteBodyFromBlock = this._deleteBodyFromBlock.bind(this); this._dirty = false; this._dirtyHeaderBlocks = {}; this._dirtyBodyBlocks = {}; this._pendingLoads = []; this._pendingLoadListeners = {}; this._deferredCalls = []; this._mutexQueue = []; this._slices = []; this._curSyncSlice = null; this._messagePurgeScheduled = false; this.folderSyncer = FolderSyncer && new FolderSyncer(account, this); } exports.FolderStorage = FolderStorage; FolderStorage.isTypeLocalOnly = function (type) { if (typeof type !== 'string') { throw new Error('isTypeLocalOnly() expects a string, not ' + type); } return type === 'outbox' || type === 'localdrafts'; }; FolderStorage.prototype = { get hasActiveSlices() { return this._slices.length > 0; }, get isLocalOnly() { return FolderStorage.isTypeLocalOnly(this.folderMeta.type); }, resetAndRefreshActiveSlices: function () { if (!this._slices.length) return; for (var i = this._slices.length - 1; i >= 0; i--) { var slice = this._slices[i]; slice.desiredHeaders = $sync.INITIAL_FILL_SIZE; slice.reset(); if (slice.type === 'folder') { this._resetAndResyncSlice(slice, true, null); } } }, generatePersistenceInfo: function () { if (!this._dirty) return null; var pinfo = { id: this.folderId, headerBlocks: this._dirtyHeaderBlocks, bodyBlocks: this._dirtyBodyBlocks }; logic(this, 'generatePersistenceInfo', { info: pinfo }); this._dirtyHeaderBlocks = {}; this._dirtyBodyBlocks = {}; this._dirty = false; this.flushExcessCachedBlocks('persist'); this._account.universe.__notifyModifiedFolder(this._account, this.folderMeta); return pinfo; }, _invokeNextMutexedCall: function () { var callInfo = this._mutexQueue[0], self = this, done = false; this._mutexedCallInProgress = true; logic(this, 'mutexedCall_begin', { name: callInfo.name }); try { var mutexedOpDone = function (err) { if (done) { logic(self, 'tooManyCallbacks', { name: callInfo.name }); return; } logic(self, 'mutexedCall_end', { name: callInfo.name }); logic(self, 'mailslice:mutex-released', { folderId: self.folderId, err: err }); done = true; if (self._mutexQueue[0] !== callInfo) { logic(self, 'mutexInvariantFail', { callName: callInfo.name, mutexName: self._mutexQueue[0].name }); return; } self._mutexQueue.shift(); self.flushExcessCachedBlocks('mutex'); if (self._mutexQueue.length) window.setZeroTimeout(self._invokeNextMutexedCall.bind(self)); else if (self._slices.length === 0) self.folderSyncer.allConsumersDead(); }; callInfo.func(mutexedOpDone); } catch (ex) { logic(this, 'mutexedOpErr', { ex: ex }); } }, runMutexed: function (name, func) { var doRun = this._mutexQueue.length === 0; this._mutexQueue.push({ name: name, func: func }); if (doRun) this._invokeNextMutexedCall(); }, upgradeIfNeeded: function () { if (!this.folderMeta.version || FOLDER_DB_VERSION > this.folderMeta.version) { this._account.universe.performFolderUpgrade(this.folderMeta.id); } }, _issueNewHeaderId: function () { return this._folderImpl.nextId++; }, _makeHeaderBlock: function ifs__makeHeaderBlock(startTS, startUID, endTS, endUID, estSize, ids, headers) { var blockId = $a64.encodeInt(this._folderImpl.nextHeaderBlock++), blockInfo = { blockId: blockId, startTS: startTS, startUID: startUID, endTS: endTS, endUID: endUID, count: ids ? ids.length : 0, estSize: estSize || 0 }, block = { ids: ids || [], headers: headers || [] }; this._dirty = true; this._headerBlocks[blockId] = block; this._dirtyHeaderBlocks[blockId] = block; if (this._serverIdHeaderBlockMapping && headers) { var srvMapping = this._serverIdHeaderBlockMapping; for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (header.srvid) srvMapping[header.srvid] = blockId; } } return blockInfo; }, _insertHeaderInBlock: function ifs__insertHeaderInBlock(header, uid, info, block) { var idx = bsearchForInsert(block.headers, header, cmpHeaderYoungToOld); block.ids.splice(idx, 0, header.id); block.headers.splice(idx, 0, header); this._dirty = true; this._dirtyHeaderBlocks[info.blockId] = block; }, _deleteHeaderFromBlock: function ifs__deleteHeaderFromBlock(uid, info, block) { var idx = block.ids.indexOf(uid), header; if (idx === -1) { logic(this, 'badDeletionRequest', { header: header, uid: uid }); return; } header = block.headers[idx]; if (header.flags && header.flags.indexOf('\\Seen') === -1) { this.folderMeta.unreadCount--; } block.ids.splice(idx, 1); block.headers.splice(idx, 1); info.estSize -= $sync.HEADER_EST_SIZE_IN_BYTES; info.count--; this._dirty = true; this._dirtyHeaderBlocks[info.blockId] = block; if (idx === 0 && info.count) { header = block.headers[0]; info.endTS = header.date; info.endUID = header.id; } if (idx === info.count && idx > 0) { header = block.headers[idx - 1]; info.startTS = header.date; info.startUID = header.id; } }, _splitHeaderBlock: function ifs__splitHeaderBlock(splinfo, splock, newerTargetBytes) { var numHeaders = Math.ceil(newerTargetBytes / $sync.HEADER_EST_SIZE_IN_BYTES); if (numHeaders > splock.headers.length) throw new Error('No need to split!'); var olderNumHeaders = splock.headers.length - numHeaders, olderEndHeader = splock.headers[numHeaders], olderInfo = this._makeHeaderBlock(splinfo.startTS, splinfo.startUID, olderEndHeader.date, olderEndHeader.id, olderNumHeaders * $sync.HEADER_EST_SIZE_IN_BYTES, splock.ids.splice(numHeaders, olderNumHeaders), splock.headers.splice(numHeaders, olderNumHeaders)); var newerStartHeader = splock.headers[numHeaders - 1]; splinfo.count = numHeaders; splinfo.estSize = numHeaders * $sync.HEADER_EST_SIZE_IN_BYTES; splinfo.startTS = newerStartHeader.date; splinfo.startUID = newerStartHeader.id; this._dirtyHeaderBlocks[splinfo.blockId] = splock; return olderInfo; }, _makeBodyBlock: function ifs__makeBodyBlock(startTS, startUID, endTS, endUID, size, ids, bodies) { var blockId = $a64.encodeInt(this._folderImpl.nextBodyBlock++), blockInfo = { blockId: blockId, startTS: startTS, startUID: startUID, endTS: endTS, endUID: endUID, count: ids ? ids.length : 0, estSize: size || 0 }, block = { ids: ids || [], bodies: bodies || {} }; this._dirty = true; this._bodyBlocks[blockId] = block; this._dirtyBodyBlocks[blockId] = block; if (this._folderImpl.nextBodyBlock % $sync.BLOCK_PURGE_EVERY_N_NEW_BODY_BLOCKS === 0 && !this._messagePurgeScheduled) { this._messagePurgeScheduled = true; this._account.scheduleMessagePurge(this.folderId); } return blockInfo; }, _insertBodyInBlock: function ifs__insertBodyInBlock(body, id, info, block) { function cmpBodyByID(aID, bID) { var aDate = aID === id ? body.date : block.bodies[aID].date, bDate = bID === id ? body.date : block.bodies[bID].date, d = bDate - aDate; if (d) return d; d = bID - aID; return d; } var idx = bsearchForInsert(block.ids, id, cmpBodyByID); block.ids.splice(idx, 0, id); block.bodies[id] = body; this._dirty = true; this._dirtyBodyBlocks[info.blockId] = block; }, _deleteBodyFromBlock: function ifs__deleteBodyFromBlock(id, info, block) { var idx = block.ids.indexOf(id); var body = block.bodies[id]; if (idx === -1 || !body) { logic(this, 'bodyBlockMissing', { id: id, index: idx, hasBody: !!body }); return; } block.ids.splice(idx, 1); delete block.bodies[id]; info.estSize -= body.size; info.count--; this._dirty = true; this._dirtyBodyBlocks[info.blockId] = block; if (idx === 0 && info.count) { info.endUID = id = block.ids[0]; info.endTS = block.bodies[id].date; } if (idx === info.count && idx > 0) { info.startUID = id = block.ids[idx - 1]; info.startTS = block.bodies[id].date; } }, _splitBodyBlock: function ifs__splitBodyBlock(splinfo, splock, newerTargetBytes) { var savedStartTS = splinfo.startTS, savedStartUID = splinfo.startUID; var newerBytes = 0, ids = splock.ids, newDict = {}, oldDict = {}, inNew = true, numHeaders = null, i, id, body, idxLast = ids.length - 1; for (i = 0; i < idxLast; i++) { id = ids[i], body = splock.bodies[id]; newerBytes += body.size; newDict[id] = body; if (newerBytes >= newerTargetBytes) { i++; break; } } splinfo.count = numHeaders = i; splinfo.startTS = body.date; splinfo.startUID = id; for (; i < ids.length; i++) { id = ids[i]; oldDict[id] = splock.bodies[id]; } var oldEndUID = ids[numHeaders]; var olderInfo = this._makeBodyBlock(savedStartTS, savedStartUID, oldDict[oldEndUID].date, oldEndUID, splinfo.estSize - newerBytes, ids.splice(numHeaders, ids.length - numHeaders), oldDict); splinfo.estSize = newerBytes; splock.bodies = newDict; this._dirtyBodyBlocks[splinfo.blockId] = splock; return olderInfo; }, flushExcessCachedBlocks: function (debugLabel) { var slices = this._slices.filter(function (slice) { return slice.type === 'folder'; }); function blockIntersectsAnySlice(blockInfo) { for (var i = 0; i < slices.length; i++) { var slice = slices[i]; if (tupleRangeIntersectsTupleRange(slice, blockInfo)) { return true; } } return false; } function maybeDiscard(blockType, blockInfoList, loadedBlockInfos, blockMap, dirtyMap, shouldDiscardFunc) { for (var i = loadedBlockInfos.length - 1; i > -1; i--) { var blockInfo = loadedBlockInfos[i]; if (dirtyMap.hasOwnProperty(blockInfo.blockId)) { continue; } if (shouldDiscardFunc(blockInfo)) { delete blockMap[blockInfo.blockId]; loadedBlockInfos.splice(i, 1); } } } maybeDiscard('header', this._headerBlockInfos, this._loadedHeaderBlockInfos, this._headerBlocks, this._dirtyHeaderBlocks, function (blockInfo) { return !blockIntersectsAnySlice(blockInfo); }); var keepCount = slices.length ? 1 : 0, foundCount = 0; maybeDiscard('body', this._bodyBlockInfos, this._loadedBodyBlockInfos, this._bodyBlocks, this._dirtyBodyBlocks, function (blockInfo) { foundCount += 1; return foundCount > keepCount; }); }, _flushExcessOnTimeout: function () { this._flushExcessTimeoutId = 0; if (!this.isDead && this._mutexQueue.length === 0) { this.flushExcessCachedBlocks('flushExcessOnTimeout'); } }, _discardCachedBlockUsingDateAndID: function (type, date, id) { var scope = logic.subscope(this, { type: type, date: date, id: id }); var blockInfoList, loadedBlockInfoList, blockMap, dirtyMap; logic(scope, 'discardFromBlock'); if (type === 'header') { blockInfoList = this._headerBlockInfos; loadedBlockInfoList = this._loadedHeaderBlockInfos; blockMap = this._headerBlocks; dirtyMap = this._dirtyHeaderBlocks; } else { blockInfoList = this._bodyBlockInfos; loadedBlockInfoList = this._loadedBodyBlockInfos; blockMap = this._bodyBlocks; dirtyMap = this._dirtyBodyBlocks; } var infoTuple = this._findRangeObjIndexForDateAndID(blockInfoList, date, id), iInfo = infoTuple[0], info = infoTuple[1]; if (!info) { logic(scope, 'badDiscardRequest'); return; } var blockId = info.blockId; if (!blockMap.hasOwnProperty(blockId)) return; if (dirtyMap.hasOwnProperty(blockId)) { logic(scope, 'badDiscardRequest'); return; } delete blockMap[blockId]; var idxLoaded = loadedBlockInfoList.indexOf(info); if (idxLoaded !== -1) loadedBlockInfoList.splice(idxLoaded, 1); }, purgeExcessMessages: function (callback) { this._messagePurgeScheduled = false; var cutTS = Math.max(this._purge_findLastAccessCutPoint(), this._purge_findHardBlockCutPoint(this._headerBlockInfos), this._purge_findHardBlockCutPoint(this._bodyBlockInfos)); if (cutTS === 0) { callback(0, cutTS); return; } cutTS = quantizeDate(cutTS + DAY_MILLIS); var aranges = this._accuracyRanges; var splitInfo = this._findFirstObjIndexForDateRange(aranges, cutTS, cutTS); if (splitInfo[1]) { splitInfo[1].startTS = cutTS; aranges.splice(splitInfo[0] + 1, aranges.length - splitInfo[0]); } else { aranges.splice(splitInfo[0], aranges.length - splitInfo[0]); } var headerBlockInfos = this._headerBlockInfos, headerBlocks = this._headerBlocks, deletionCount = 0, callActive = false, deleteTriggered = false; var deleteNextHeader = function () { if (callActive) { deleteTriggered = true; return; } while (true) { if (!headerBlockInfos.length) { callback(deletionCount, cutTS); return; } var blockInfo = headerBlockInfos[headerBlockInfos.length - 1]; if (!this._headerBlocks.hasOwnProperty(blockInfo.blockId)) { this._loadBlock('header', blockInfo, deleteNextHeader); return; } var headerBlock = this._headerBlocks[blockInfo.blockId], lastHeader = headerBlock.headers[headerBlock.headers.length - 1]; if (SINCE(lastHeader.date, cutTS)) { callback(deletionCount, cutTS); return; } deleteTriggered = false; callActive = true; deletionCount++; this.deleteMessageHeaderAndBodyUsingHeader(lastHeader, deleteNextHeader); callActive = false; if (!deleteTriggered) return; } }.bind(this); deleteNextHeader(); }, _purge_findLastAccessCutPoint: function () { var aranges = this._accuracyRanges, cutoffDate = $date.NOW() - $sync.BLOCK_PURGE_ONLY_AFTER_UNSYNCED_MS; var iCutRange; for (iCutRange = aranges.length; iCutRange >= 1; iCutRange--) { var arange = aranges[iCutRange - 1]; if (!arange.fullSync) continue; if (arange.fullSync.updated > cutoffDate) break; } if (iCutRange === aranges.length) return 0; var cutTS = aranges[iCutRange].endTS, syncRangeMS = $sync.SYNC_RANGE_ENUMS_TO_MS[this._account.accountDef.syncRange] || $sync.SYNC_RANGE_ENUMS_TO_MS['auto'], syncHorizonTS = $date.NOW() - syncRangeMS - DAY_MILLIS; if (STRICTLY_AFTER(cutTS, syncHorizonTS)) return syncHorizonTS; return cutTS; }, _purge_findHardBlockCutPoint: function (blockInfoList) { if (blockInfoList.length <= $sync.BLOCK_PURGE_HARD_MAX_BLOCK_LIMIT) return 0; return blockInfoList[$sync.BLOCK_PURGE_HARD_MAX_BLOCK_LIMIT].startTS; }, _findRangeObjIndexForDate: function ifs__findRangeObjIndexForDate(list, date) { var i; for (i = 0; i < list.length; i++) { var info = list[i]; if (SINCE(date, info.endTS)) return [ i, null ]; if (SINCE(date, info.startTS)) return [ i, info ]; } return [ i, null ]; }, _findRangeObjIndexForDateAndID: function ifs__findRangeObjIndexForDateAndID(list, date, uid) { var i; for (i = 0; i < list.length; i++) { var info = list[i]; if (STRICTLY_AFTER(date, info.endTS) || date === info.endTS && uid > info.endUID) return [ i, null ]; if (STRICTLY_AFTER(date, info.startTS) || date === info.startTS && uid >= info.startUID) return [ i, info ]; } return [ i, null ]; }, _findFirstObjIndexForDateRange: function ifs__findFirstObjIndexForDateRange(list, startTS, endTS) { var i; for (i = 0; i < list.length; i++) { var info = list[i]; if (STRICTLY_AFTER(startTS, info.endTS)) return [ i, null ]; if (endTS === null || STRICTLY_AFTER(endTS, info.startTS)) return [ i, info ]; } return [ i, null ]; }, _findLastObjIndexForDateRange: function ifs__findLastObjIndexForDateRange(list, startTS, endTS) { var i; for (i = list.length - 1; i >= 0; i--) { var info = list[i]; if (ON_OR_BEFORE(endTS, info.startTS)) return [ i + 1, null ]; if (BEFORE(startTS, info.endTS)) return [ i, info ]; } return [ 0, null ]; }, _findFirstObjForDateRange: function ifs__findFirstObjForDateRange(list, startTS, endTS) { var i; var dateComparator = endTS === null ? SINCE : IN_BS_DATE_RANGE; for (i = 0; i < list.length; i++) { var date = list[i].date; if (dateComparator(date, startTS, endTS)) return [ i, list[i] ]; } return [ i, null ]; }, _insertIntoBlockUsingDateAndUID: function ifs__pickInsertionBlocks(type, date, uid, srvid, estSizeCost, thing, blockPickedCallback) { var blockInfoList, loadedBlockInfoList, blockMap, makeBlock, insertInBlock, splitBlock, serverIdBlockMapping; if (type === 'header') { blockInfoList = this._headerBlockInfos; loadedBlockInfoList = this._loadedHeaderBlockInfos; blockMap = this._headerBlocks; serverIdBlockMapping = this._serverIdHeaderBlockMapping; makeBlock = this._bound_makeHeaderBlock; insertInBlock = this._bound_insertHeaderInBlock; splitBlock = this._bound_splitHeaderBlock; } else { blockInfoList = this._bodyBlockInfos; loadedBlockInfoList = this._loadedBodyBlockInfos; blockMap = this._bodyBlocks; serverIdBlockMapping = null; makeBlock = this._bound_makeBodyBlock; insertInBlock = this._bound_insertBodyInBlock; splitBlock = this._bound_splitBodyBlock; } var infoTuple = this._findRangeObjIndexForDateAndID(blockInfoList, date, uid), iInfo = infoTuple[0], info = infoTuple[1]; var updateInfo = { startTS: null, startUID: null, endTS: null, endUID: null }; if (!info) { if (blockInfoList.length === 0) { info = makeBlock(date, uid, date, uid); blockInfoList.splice(iInfo, 0, info); loadedBlockInfoList.push(info); } else if (iInfo < blockInfoList.length && blockInfoList[iInfo].estSize + estSizeCost < $sync.MAX_BLOCK_SIZE) { info = blockInfoList[iInfo]; if (STRICTLY_AFTER(date, info.endTS)) { updateInfo.endTS = date; updateInfo.endUID = uid; } else if (date === info.endTS && uid > info.endUID) { updateInfo.endUID = uid; } } else if (iInfo > 0 && blockInfoList[iInfo - 1].estSize + estSizeCost < $sync.MAX_BLOCK_SIZE) { info = blockInfoList[--iInfo]; if (BEFORE(date, info.startTS)) { updateInfo.startTS = date; updateInfo.startUID = uid; } else if (date === info.startTS && uid < info.startUID) { updateInfo.startUID = uid; } } else if (iInfo > 0 && iInfo < blockInfoList.length / 2 || iInfo === blockInfoList.length) { info = blockInfoList[--iInfo]; if (BEFORE(date, info.startTS)) { updateInfo.startTS = date; updateInfo.startUID = uid; } else if (date === info.startTS && uid < info.startUID) { updateInfo.startUID = uid; } } else { info = blockInfoList[iInfo]; if (STRICTLY_AFTER(date, info.endTS)) { updateInfo.endTS = date; updateInfo.endUID = uid; } else if (date === info.endTS && uid > info.endUID) { updateInfo.endUID = uid; } } } function processBlock(block) { if (updateInfo.startTS !== null) { info.startTS = updateInfo.startTS; } if (updateInfo.startUID !== null) { info.startUID = updateInfo.startUID; } if (updateInfo.endTS !== null) { info.endTS = updateInfo.endTS; } if (updateInfo.endUID !== null) { info.endUID = updateInfo.endUID; } info.estSize += estSizeCost; info.count++; insertInBlock(thing, uid, info, block); if (info.count > 1 && info.estSize >= $sync.MAX_BLOCK_SIZE) { var firstBlockTarget; if (iInfo === 0) firstBlockTarget = $sync.BLOCK_SPLIT_SMALL_PART; else if (iInfo === blockInfoList.length - 1) firstBlockTarget = $sync.BLOCK_SPLIT_LARGE_PART; else firstBlockTarget = $sync.BLOCK_SPLIT_EQUAL_PART; var olderInfo; olderInfo = splitBlock(info, block, firstBlockTarget); blockInfoList.splice(iInfo + 1, 0, olderInfo); loadedBlockInfoList.push(olderInfo); if (BEFORE(date, olderInfo.endTS) || date === olderInfo.endTS && uid <= olderInfo.endUID) { iInfo++; info = olderInfo; block = blockMap[info.blockId]; } } if (serverIdBlockMapping && srvid) serverIdBlockMapping[srvid] = info.blockId; if (blockPickedCallback) { blockPickedCallback(info, block); } } if (blockMap.hasOwnProperty(info.blockId)) processBlock.call(this, blockMap[info.blockId]); else this._loadBlock(type, info, processBlock.bind(this)); }, _runDeferredCalls: function ifs__runDeferredCalls() { while (this._deferredCalls.length && this._pendingLoads.length === 0) { var toCall = this._deferredCalls.shift(); try { toCall(); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } } }, _findBlockInfoFromBlockId: function (type, blockId) { var blockInfoList; if (type === 'header') blockInfoList = this._headerBlockInfos; else blockInfoList = this._bodyBlockInfos; for (var i = 0; i < blockInfoList.length; i++) { var blockInfo = blockInfoList[i]; if (blockInfo.blockId === blockId) return blockInfo; } return null; }, _loadBlock: function ifs__loadBlock(type, blockInfo, callback) { var blockId = blockInfo.blockId; var aggrId = type + blockId; if (this._pendingLoads.indexOf(aggrId) !== -1) { this._pendingLoadListeners[aggrId].push(callback); return; } var index = this._pendingLoads.length; this._pendingLoads.push(aggrId); this._pendingLoadListeners[aggrId] = [callback]; var self = this; function onLoaded(block) { if (!block) logic(self, 'badBlockLoad', { type: type, blockId: blockId }); logic(self, 'loadBlock_end', { type: type, blockId: blockId, block: block }); if (type === 'header') { self._headerBlocks[blockId] = block; self._loadedHeaderBlockInfos.push(blockInfo); } else { self._bodyBlocks[blockId] = block; self._loadedBodyBlockInfos.push(blockInfo); } self._pendingLoads.splice(self._pendingLoads.indexOf(aggrId), 1); var listeners = self._pendingLoadListeners[aggrId]; delete self._pendingLoadListeners[aggrId]; for (var i = 0; i < listeners.length; i++) { try { listeners[i](block); } catch (ex) { logic(self, 'callbackErr', { ex: ex }); } } if (self._pendingLoads.length === 0) self._runDeferredCalls(); if (self._mutexQueue.length === 0 && !self._flushExcessTimeoutId) { self._flushExcessTimeoutId = setTimeout(self._bound_flushExcessOnTimeout, 5000); } } logic(this, 'loadBlock_begin', { type: type, blockId: blockId }); if (type === 'header') this._imapDb.loadHeaderBlock(this.folderId, blockId, onLoaded); else this._imapDb.loadBodyBlock(this.folderId, blockId, onLoaded); }, _deleteFromBlock: function ifs__deleteFromBlock(type, date, id, callback) { var blockInfoList, loadedBlockInfoList, blockMap, deleteFromBlock; var scope = logic.subscope(this, { type: type, date: date, id: id }); logic(scope, 'deleteFromBlock'); if (type === 'header') { blockInfoList = this._headerBlockInfos; loadedBlockInfoList = this._loadedHeaderBlockInfos; blockMap = this._headerBlocks; deleteFromBlock = this._bound_deleteHeaderFromBlock; } else { blockInfoList = this._bodyBlockInfos; loadedBlockInfoList = this._loadedBodyBlockInfos; blockMap = this._bodyBlocks; deleteFromBlock = this._bound_deleteBodyFromBlock; } var infoTuple = this._findRangeObjIndexForDateAndID(blockInfoList, date, id), iInfo = infoTuple[0], info = infoTuple[1]; if (!info) { log('badDeletionRequest'); return; } function processBlock(block) { deleteFromBlock(id, info, block); if (info.count === 0) { blockInfoList.splice(iInfo, 1); delete blockMap[info.blockId]; loadedBlockInfoList.splice(loadedBlockInfoList.indexOf(info), 1); this._dirty = true; if (type === 'header') this._dirtyHeaderBlocks[info.blockId] = null; else this._dirtyBodyBlocks[info.blockId] = null; } if (callback) callback(); } if (blockMap.hasOwnProperty(info.blockId)) processBlock.call(this, blockMap[info.blockId]); else this._loadBlock(type, info, processBlock.bind(this)); }, sliceOpenSearch: function fs_sliceOpenSearch(slice) { this._slices.push(slice); }, sliceOpenMostRecent: function fs_sliceOpenMostRecent(slice, forceRefresh) { slice.setStatus('synchronizing', false, true, false, SYNC_START_MINIMUM_PROGRESS); var sliceFn = this._sliceOpenMostRecent.bind(this, slice, forceRefresh); if (this.isLocalOnly) { sliceFn(function fakeReleaseMutex() { }); } else { this.runMutexed('sync', sliceFn); } }, _sliceOpenMostRecent: function fs__sliceOpenMostRecent(slice, forceRefresh, releaseMutex) { this._slices.push(slice); slice.headerCount = this.headerCount; var doneCallback = function doneSyncCallback(err, reportSyncStatusAs, moreExpected) { if (!reportSyncStatusAs) { if (err) reportSyncStatusAs = 'syncfailed'; else reportSyncStatusAs = 'synced'; } if (moreExpected === undefined) moreExpected = false; slice.waitingOnData = false; slice.setStatus(reportSyncStatusAs, true, moreExpected, true); this._curSyncSlice = null; releaseMutex(err); }.bind(this); if (this._accuracyRanges.length || this.isLocalOnly) { var triggerRefresh; if (this._account.universe.online && this.folderSyncer.syncable && !this.isLocalOnly) { if (forceRefresh) triggerRefresh = 'force'; else triggerRefresh = true; } else { triggerRefresh = false; } slice.waitingOnData = 'db'; this.getMessagesInImapDateRange(0, null, $sync.INITIAL_FILL_SIZE, $sync.INITIAL_FILL_SIZE, this.onFetchDBHeaders.bind(this, slice, triggerRefresh, doneCallback, releaseMutex)); return; } if (!this._account.universe.online || this.isLocalOnly) { doneCallback(); return; } if (!this.folderSyncer.syncable) { console.log('Synchronization is currently blocked; waiting...'); doneCallback(null, 'syncblocked', true); return; } var progressCallback = slice.setSyncProgress.bind(slice); var syncCallback = function syncCallback(syncMode, ignoreHeaders) { slice.waitingOnData = syncMode; if (ignoreHeaders) { slice.ignoreHeaders = true; } this._curSyncSlice = slice; slice.headerCount = this.headerCount; }.bind(this); slice._updateSliceFlags(); this.folderSyncer.initialSync(slice, $sync.INITIAL_SYNC_DAYS, syncCallback, doneCallback, progressCallback); }, growSlice: function ifs_growSlice(slice, dirMagnitude, userRequestsGrowth) { if (userRequestsGrowth) slice.setStatus('synchronizing', false, true, false, SYNC_START_MINIMUM_PROGRESS); this.runMutexed('grow', this._growSlice.bind(this, slice, dirMagnitude, userRequestsGrowth)); }, _growSlice: function ifs__growSlice(slice, dirMagnitude, userRequestsGrowth, releaseMutex) { var dir, desiredCount; var batchHeaders = []; var gotMessages = function gotMessages(headers, moreExpected) { if (headers.length === 0) { } if (dir === PASTWARDS) { batchHeaders = batchHeaders.concat(headers); } else { batchHeaders = headers.concat(batchHeaders); } if (moreExpected) return; var doneCallback = function doneGrowCallback(err) { slice.desiredHeaders = slice.headers.length; slice.waitingOnData = false; slice.setStatus(err ? 'syncfailed' : 'synced', true, false, true); this._curSyncSlice = null; releaseMutex(err); }.bind(this); var progressCallback = slice.setSyncProgress.bind(slice); if (batchHeaders.length) { var refreshInterval; if (!this._account.universe.online || !this._account.enabled || !this.folderSyncer.canGrowSync) { refreshInterval = null; } else { var highestLegalEndTS; var openEndTS = false; var startTS, endTS; if (dir === PASTWARDS) { var oldestHeader = batchHeaders[batchHeaders.length - 1]; highestLegalEndTS = NOW() - $sync.OPEN_REFRESH_THRESH_MS; endTS = slice.startTS + $date.DAY_MILLIS; if (this.headerIsOldestKnown(oldestHeader.date, oldestHeader.id)) { startTS = this.getOldestFullSyncDate(); } else { startTS = oldestHeader.date - $sync.IMAP_SEARCH_AMBIGUITY_MS; } } else { highestLegalEndTS = NOW() - $sync.GROW_REFRESH_THRESH_MS; var youngestHeader = batchHeaders[0]; startTS = slice.endTS; endTS = youngestHeader.date + $date.DAY_MILLIS; } if (STRICTLY_AFTER(endTS, highestLegalEndTS)) { endTS = highestLegalEndTS; openEndTS = true; } else { endTS = quantizeDate(endTS); } if (SINCE(startTS, endTS)) refreshInterval = null; else refreshInterval = this.checkAccuracyCoverageNeedingRefresh(quantizeDate(startTS), endTS, $sync.GROW_REFRESH_THRESH_MS); } slice.batchAppendHeaders(batchHeaders, dir === PASTWARDS ? -1 : 0, true); slice.desiredHeaders = Math.max(slice.headers.length, desiredCount); if (refreshInterval && refreshInterval.startTS !== refreshInterval.endTS) { if (!userRequestsGrowth) slice.setStatus('synchronizing', false, true, false, SYNC_START_MINIMUM_PROGRESS); this.folderSyncer.refreshSync(slice, dir, quantizeDate(refreshInterval.startTS), openEndTS && refreshInterval.endTS === highestLegalEndTS ? null : quantizeDateUp(refreshInterval.endTS), null, doneCallback, progressCallback); } else { doneCallback(); } return; } if (!this._account.universe.online || !this.folderSyncer.canGrowSync || !userRequestsGrowth) { if (this.folderSyncer.syncable) slice.sendEmptyCompletion(); releaseMutex(null); return; } if (!userRequestsGrowth) slice.setStatus('synchronizing', false, true, false, SYNC_START_MINIMUM_PROGRESS); this._curSyncSlice = slice; slice.headerCount = this.headerCount; slice.waitingOnData = 'grow'; slice.desiredHeaders += desiredCount; this.folderSyncer.growSync(slice, dir, dir === PASTWARDS ? quantizeDate(slice.startTS) : quantizeDate(slice.endTS + $date.DAY_MILLIS), $sync.INITIAL_SYNC_GROWTH_DAYS, doneCallback, progressCallback); }.bind(this); if (this._mutexQueue.length === 0) { this.flushExcessCachedBlocks('grow'); } if (dirMagnitude < 0) { dir = FUTUREWARDS; desiredCount = -dirMagnitude; this.getMessagesAfterMessage(slice.endTS, slice.endUID, desiredCount, gotMessages); } else { dir = PASTWARDS; desiredCount = dirMagnitude; this.getMessagesBeforeMessage(slice.startTS, slice.startUID, desiredCount, gotMessages); } }, sliceShrunk: function fs_sliceShrunk(slice) { if (this._mutexQueue.length === 0) this.flushExcessCachedBlocks('shrunk'); }, refreshSlice: function fs_refreshSlice(slice) { slice.setStatus('synchronizing', false, true, false, 0); var refreshFn = this._refreshSlice.bind(this, slice, false); if (this.isLocalOnly) { refreshFn(function fakeReleaseMutex() { }); } else { this.runMutexed('refresh', refreshFn); } }, _refreshSlice: function fs__refreshSlice(slice, checkOpenRecency, releaseMutex) { var doneCallback = function refreshDoneCallback(err, bisectInfo, numMessages) { slice._onAddingHeader = null; var reportSyncStatusAs = 'synced'; switch (err) { case 'aborted': case 'unknown': reportSyncStatusAs = 'syncfailed'; break; } releaseMutex(err); slice.waitingOnData = false; slice.setStatus(reportSyncStatusAs, true, false, false, null, newEmailCount); return undefined; }.bind(this); if (slice.isDead) { console.log('MailSlice: Attempted to refresh a dead slice.'); doneCallback('unknown'); return; } slice.waitingOnData = 'refresh'; var startTS = slice.startTS, endTS = slice.endTS, origStartTS = null, newEmailCount = null; if (this.headerIsYoungestKnown(endTS, slice.endUID)) { var prevTS = endTS; newEmailCount = 0; slice._onAddingHeader = function (header, currentSlice) { if (SINCE(header.date, prevTS) && (!header.flags || header.flags.indexOf('\\Seen') === -1)) { newEmailCount += 1; if (slice.onNewHeader) slice.onNewHeader(header); } }.bind(this); endTS = null; } else { endTS = quantizeDate(endTS + DAY_MILLIS); } if (this.headerIsOldestKnown(startTS, slice.startUID)) { origStartTS = quantizeDate(startTS); startTS = this.getOldestFullSyncDate(); } else { startTS -= $sync.IMAP_SEARCH_AMBIGUITY_MS; } if (startTS) startTS = quantizeDate(startTS); if (checkOpenRecency) { if (this.checkAccuracyCoverageNeedingRefresh(startTS, endTS || NOW() - $sync.OPEN_REFRESH_THRESH_MS, $sync.OPEN_REFRESH_THRESH_MS) === null) { doneCallback(); return; } } this.folderSyncer.refreshSync(slice, FUTUREWARDS, startTS, endTS, origStartTS, doneCallback, slice.setSyncProgress.bind(slice)); }, _resetAndResyncSlice: function (slice, forceRefresh, releaseMutex) { this._slices.splice(this._slices.indexOf(slice), 1); if (releaseMutex) this._sliceOpenMostRecent(slice, forceRefresh, releaseMutex); else this.sliceOpenMostRecent(slice, forceRefresh); }, dyingSlice: function ifs_dyingSlice(slice) { var idx = this._slices.indexOf(slice); this._slices.splice(idx, 1); if (slice.type === 'folder') { this.flushExcessCachedBlocks('deadslice'); } if (this._slices.length === 0 && this._mutexQueue.length === 0) { this.folderSyncer.allConsumersDead(); } }, onFetchDBHeaders: function (slice, triggerRefresh, doneCallback, releaseMutex, headers, moreMessagesComing) { var triggerNow = false; if (!moreMessagesComing && triggerRefresh) { moreMessagesComing = true; triggerNow = true; } slice.batchAppendHeaders(headers, -1, true); if (!moreMessagesComing) { slice.desiredHeaders = slice.headers.length; doneCallback(); } else if (triggerNow) { slice.desiredHeaders = slice.headers.length; this._curSyncSlice = null; var checkOpenRecency = triggerRefresh !== 'force'; this._refreshSlice(slice, checkOpenRecency, releaseMutex); } }, sliceQuicksearch: function ifs_sliceQuicksearch(slice, searchParams) { }, getYoungestMessageTimestamp: function () { if (!this._headerBlockInfos.length) return 0; return this._headerBlockInfos[0].endTS; }, headerIsYoungestKnown: function (date, uid) { if (!this._headerBlockInfos.length) return date === null && uid === null; var blockInfo = this._headerBlockInfos[0]; return date === blockInfo.endTS && uid === blockInfo.endUID; }, getOldestMessageTimestamp: function () { if (!this._headerBlockInfos.length) return 0; return this._headerBlockInfos[this._headerBlockInfos.length - 1].startTS; }, headerIsOldestKnown: function (date, uid) { if (!this._headerBlockInfos.length) return date === null && uid === null; var blockInfo = this._headerBlockInfos[this._headerBlockInfos.length - 1]; return date === blockInfo.startTS && uid === blockInfo.startUID; }, getNewestFullSyncDate: function () { if (this._accuracyRanges.length) return this._accuracyRanges[0].endTS; return 0; }, getOldestFullSyncDate: function () { var idxAR = this._accuracyRanges.length - 1; while (idxAR >= 0 && !this._accuracyRanges[idxAR].fullSync) { idxAR--; } var syncTS; if (idxAR >= 0) syncTS = this._accuracyRanges[idxAR].startTS; else syncTS = NOW(); return syncTS; }, syncedToToday: function () { if (!this.folderSyncer.canGrowSync) return true; var newestSyncTS = this.getNewestFullSyncDate(); return SINCE(newestSyncTS, quantizeDate(NOW())); }, syncedToDawnOfTime: function () { if (!this.folderSyncer.canGrowSync) return true; var oldestSyncTS = this.getOldestFullSyncDate(); return ON_OR_BEFORE(oldestSyncTS, $sync.OLDEST_SYNC_DATE + $date.DAY_MILLIS); }, getKnownMessageCount: function () { var count = 0; for (var i = 0; i < this._headerBlockInfos.length; i++) { var blockInfo = this._headerBlockInfos[i]; count += blockInfo.count; } return count; }, getMessagesInImapDateRange: function ifs_getMessagesInDateRange(startTS, endTS, minDesired, maxDesired, messageCallback) { var toFill = minDesired != null ? minDesired : $sync.TOO_MANY_MESSAGES, maxFill = maxDesired != null ? maxDesired : $sync.TOO_MANY_MESSAGES, self = this, iHeadBlockInfo = null, headBlockInfo; var headerPair = this._findFirstObjIndexForDateRange(this._headerBlockInfos, startTS, endTS); iHeadBlockInfo = headerPair[0]; headBlockInfo = headerPair[1]; if (!headBlockInfo) { messageCallback([], false); return; } function fetchMore() { while (true) { if (!self._headerBlocks.hasOwnProperty(headBlockInfo.blockId)) { self._loadBlock('header', headBlockInfo, fetchMore); return; } var headerBlock = self._headerBlocks[headBlockInfo.blockId]; var headerTuple = self._findFirstObjForDateRange(headerBlock.headers, startTS, endTS), iFirstHeader = headerTuple[0], header = headerTuple[1]; if (!header) { messageCallback([], false); return; } var iHeader = iFirstHeader; for (; iHeader < headerBlock.headers.length && maxFill; iHeader++, maxFill--) { header = headerBlock.headers[iHeader]; if (BEFORE(header.date, startTS)) break; } if (maxFill && iHeader < headerBlock.headers.length) toFill = 0; else toFill -= iHeader - iFirstHeader; if (!toFill) { } else if (++iHeadBlockInfo >= self._headerBlockInfos.length) { toFill = 0; } else { headBlockInfo = self._headerBlockInfos[iHeadBlockInfo]; if (STRICTLY_AFTER(startTS, headBlockInfo.endTS)) toFill = 0; } messageCallback(headerBlock.headers.slice(iFirstHeader, iHeader), Boolean(toFill)); if (!toFill) return; } } fetchMore(); }, getAllMessagesInImapDateRange: function ifs_getAllMessagesInDateRange(startTS, endTS, allCallback) { var allHeaders = null; function someMessages(headers, moreHeadersExpected) { if (allHeaders) allHeaders = allHeaders.concat(headers); else allHeaders = headers; if (!moreHeadersExpected) allCallback(allHeaders); } this.getMessagesInImapDateRange(startTS, endTS, null, null, someMessages); }, getMessagesBeforeMessage: function (date, id, limit, messageCallback) { var toFill = limit != null ? limit : $sync.TOO_MANY_MESSAGES, self = this; var headerPair, iHeadBlockInfo, headBlockInfo; if (date) { headerPair = this._findRangeObjIndexForDateAndID(this._headerBlockInfos, date, id); iHeadBlockInfo = headerPair[0]; headBlockInfo = headerPair[1]; } else { iHeadBlockInfo = 0; headBlockInfo = this._headerBlockInfos[0]; } if (!headBlockInfo) { if (iHeadBlockInfo < this._headerBlockInfos.length) { headBlockInfo = this._headerBlockInfos[iHeadBlockInfo]; } else { messageCallback([], false); return; } } var iHeader = null; function fetchMore() { while (true) { if (!self._headerBlocks.hasOwnProperty(headBlockInfo.blockId)) { self._loadBlock('header', headBlockInfo, fetchMore); return; } var headerBlock = self._headerBlocks[headBlockInfo.blockId]; if (iHeader === null) { if (id != null) { iHeader = bsearchForInsert(headerBlock.headers, { date: date, id: id }, cmpHeaderYoungToOld); if (headerBlock.ids[iHeader] === id) { iHeader++; } else { } } else { iHeader = 1; } } else { iHeader = 0; } var useHeaders = Math.min(headerBlock.headers.length - iHeader, toFill); if (iHeader >= headerBlock.headers.length) useHeaders = 0; toFill -= useHeaders; if (!toFill) { } else if (++iHeadBlockInfo >= self._headerBlockInfos.length) { toFill = 0; } else { headBlockInfo = self._headerBlockInfos[iHeadBlockInfo]; } messageCallback(headerBlock.headers.slice(iHeader, iHeader + useHeaders), Boolean(toFill)); if (!toFill) return; } } fetchMore(); }, getMessagesAfterMessage: function (date, id, limit, messageCallback) { var toFill = limit != null ? limit : $sync.TOO_MANY_MESSAGES, self = this; var headerPair = this._findRangeObjIndexForDateAndID(this._headerBlockInfos, date, id); var iHeadBlockInfo = headerPair[0]; var headBlockInfo = headerPair[1]; var scope = logic.subscope(this, { date: date, id: id }); if (!headBlockInfo) { logic(scope, 'badIterationStart'); messageCallback([], false); return; } var iHeader = null; function fetchMore() { while (true) { if (!self._headerBlocks.hasOwnProperty(headBlockInfo.blockId)) { self._loadBlock('header', headBlockInfo, fetchMore); return; } var headerBlock = self._headerBlocks[headBlockInfo.blockId]; if (iHeader === null) { iHeader = headerBlock.ids.indexOf(id); if (iHeader === -1) { logic(scope, 'badIterationStart'); toFill = 0; } iHeader--; } else { iHeader = headerBlock.headers.length - 1; } var useHeaders = Math.min(iHeader + 1, toFill); if (iHeader < 0) useHeaders = 0; toFill -= useHeaders; if (!toFill) { } else if (--iHeadBlockInfo < 0) { toFill = 0; } else { headBlockInfo = self._headerBlockInfos[iHeadBlockInfo]; } var messages = headerBlock.headers.slice(iHeader - useHeaders + 1, iHeader + 1); messageCallback(messages, Boolean(toFill)); if (!toFill) return; } } fetchMore(); }, markSyncRange: function (startTS, endTS, modseq, updated) { if (!endTS) endTS = NOW(); if (startTS > endTS) throw new Error('Your timestamps are switched!'); var aranges = this._accuracyRanges; function makeRange(start, end, modseq, updated) { return { startTS: start, endTS: end, fullSync: typeof modseq === 'string' ? { highestModseq: modseq, updated: updated } : { highestModseq: modseq.fullSync.highestModseq, updated: modseq.fullSync.updated } }; } var newInfo = this._findFirstObjIndexForDateRange(aranges, startTS, endTS), oldInfo = this._findLastObjIndexForDateRange(aranges, startTS, endTS), newSplits, oldSplits; newSplits = newInfo[1] && STRICTLY_AFTER(newInfo[1].endTS, endTS); oldSplits = oldInfo[1] && BEFORE(oldInfo[1].startTS, startTS); var insertions = [], delCount = oldInfo[0] - newInfo[0]; if (oldInfo[1]) delCount++; if (newSplits) { if (newInfo[1].fullSync && newInfo[1].fullSync.highestModseq === modseq && newInfo[1].fullSync.updated === updated) endTS = newInfo[1].endTS; else insertions.push(makeRange(endTS, newInfo[1].endTS, newInfo[1])); } insertions.push(makeRange(startTS, endTS, modseq, updated)); if (oldSplits) { if (oldInfo[1].fullSync && oldInfo[1].fullSync.highestModseq === modseq && oldInfo[1].fullSync.updated === updated) insertions[insertions.length - 1].startTS = oldInfo[1].startTS; else insertions.push(makeRange(oldInfo[1].startTS, startTS, oldInfo[1])); } var newNeighbor = newInfo[0] > 0 ? aranges[newInfo[0] - 1] : null, oldAdjust = oldInfo[1] ? 1 : 0, oldNeighbor = oldInfo[0] < aranges.length - oldAdjust ? aranges[oldInfo[0] + oldAdjust] : null; if (newNeighbor && insertions[0].endTS === newNeighbor.startTS && newNeighbor.fullSync && newNeighbor.fullSync.highestModseq === modseq && newNeighbor.fullSync.updated === updated) { insertions[0].endTS = newNeighbor.endTS; newInfo[0]--; delCount++; } if (oldNeighbor && insertions[insertions.length - 1].startTS === oldNeighbor.endTS && oldNeighbor.fullSync && oldNeighbor.fullSync.highestModseq === modseq && oldNeighbor.fullSync.updated === updated) { insertions[insertions.length - 1].startTS = oldNeighbor.startTS; delCount++; } aranges.splice.apply(aranges, [ newInfo[0], delCount ].concat(insertions)); this.folderMeta.lastSyncedAt = NOW(); this._dirty = true; }, markSyncedToDawnOfTime: function () { logic(this, 'syncedToDawnOfTime'); var aranges = this._accuracyRanges; aranges[aranges.length - 1].startTS = $sync.OLDEST_SYNC_DATE; this.folderMeta.lastSyncedAt = NOW(); this._dirty = true; }, clearSyncedToDawnOfTime: function (newOldestTS) { var aranges = this._accuracyRanges; if (!aranges.length) return; var lastRange = aranges[aranges.length - 1]; if (STRICTLY_AFTER(lastRange.endTS, newOldestTS)) { lastRange.startTS = newOldestTS; } else { logic(this, 'accuracyRangeSuspect', { lastRange: lastRange }); aranges.pop(); } }, checkAccuracyCoverageNeedingRefresh: function (startTS, endTS, threshMS) { var aranges = this._accuracyRanges, arange, newInfo = this._findFirstObjIndexForDateRange(aranges, startTS, endTS), oldInfo = this._findLastObjIndexForDateRange(aranges, startTS, endTS), recencyCutoff = NOW() - threshMS; var result = { startTS: startTS, endTS: endTS }; if (newInfo[1]) { var i; for (i = newInfo[0]; i <= oldInfo[0]; i++) { arange = aranges[i]; if (BEFORE(arange.endTS, result.endTS)) break; if (!arange.fullSync || BEFORE(arange.fullSync.updated, recencyCutoff)) break; if (ON_OR_BEFORE(arange.startTS, result.startTS)) return null; result.endTS = arange.startTS; } for (i = oldInfo[0]; i >= 0; i--) { arange = aranges[i]; if (STRICTLY_AFTER(arange.startTS, result.startTS)) break; if (!arange.fullSync || BEFORE(arange.fullSync.updated, recencyCutoff)) break; result.startTS = arange.endTS; } } return result; }, getMessage: function (suid, date, options, callback) { if (typeof options === 'function') { callback = options; options = undefined; } var header; var body; var pending = 2; function next() { if (!--pending) { if (!body || !header) { return callback(null); } callback({ header: header, body: body }); } } this.getMessageHeader(suid, date, function (_header) { header = _header; next(); }); var gotBody = function gotBody(_body) { body = _body; next(); }; if (options && options.withBodyReps) { this.getMessageBodyWithReps(suid, date, gotBody); } else { this.getMessageBody(suid, date, gotBody); } }, getMessageHeader: function ifs_getMessageHeader(suid, date, callback) { var id = parseInt(suid.substring(suid.lastIndexOf('/') + 1)), posInfo = this._findRangeObjIndexForDateAndID(this._headerBlockInfos, date, id); if (posInfo[1] === null) { logic(this, 'headerNotFound'); try { callback(null); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } return; } var headerBlockInfo = posInfo[1], self = this; if (!this._headerBlocks.hasOwnProperty(headerBlockInfo.blockId)) { this._loadBlock('header', headerBlockInfo, function (headerBlock) { var idx = headerBlock.ids.indexOf(id); var headerInfo = headerBlock.headers[idx] || null; if (!headerInfo) logic(self, 'headerNotFound'); try { callback(headerInfo); } catch (ex) { logic(self, 'callbackErr', { ex: ex }); } }); return; } var block = this._headerBlocks[headerBlockInfo.blockId], idx = block.ids.indexOf(id), headerInfo = block.headers[idx] || null; if (!headerInfo) logic(this, 'headerNotFound'); try { callback(headerInfo); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } }, getMessageHeaders: function ifs_getMessageHeaders(namers, callback) { var pending = namers.length; var headers = []; var gotHeader = function gotHeader(header) { if (header) { headers.push(header); } if (!--pending) { callback(headers); } }; for (var i = 0; i < namers.length; i++) { var namer = namers[i]; this.getMessageHeader(namer.suid, namer.date, gotHeader); } }, addMessageHeader: function ifs_addMessageHeader(header, body, callback) { if (header.id == null || header.suid == null) { throw new Error('No valid id: ' + header.id + ' or suid: ' + header.suid); } if (this._pendingLoads.length) { this._deferredCalls.push(this.addMessageHeader.bind(this, header, body, callback)); return; } if (header.flags && header.flags.indexOf('\\Seen') === -1) { this.folderMeta.unreadCount++; } logic(this, 'addMessageHeader', { date: header.date, id: header.id, srvid: header.srvid }); this.headerCount += 1; if (this._curSyncSlice) { this._curSyncSlice.headerCount = this.headerCount; if (!this._curSyncSlice.ignoreHeaders) { this._curSyncSlice.onHeaderAdded(header, body, true, true); } } if (this._slices.length > (this._curSyncSlice ? 1 : 0)) { var date = header.date, uid = header.id; for (var iSlice = 0; iSlice < this._slices.length; iSlice++) { var slice = this._slices[iSlice]; if (slice === this._curSyncSlice) { continue; } if (slice.type === 'folder') { slice.headerCount = this.headerCount; } if (slice.startTS !== null) { if (BEFORE(date, slice.startTS)) { if (slice.headers.length >= slice.desiredHeaders) { continue; } } else if (SINCE(date, slice.endTS)) { if (!(this._headerBlockInfos.length && slice.endTS === this._headerBlockInfos[0].endTS && slice.endUID === this._headerBlockInfos[0].endUID)) continue; } else if (date === slice.startTS && uid < slice.startUID || date === slice.endTS && uid > slice.endUID) { continue; } } else { slice.desiredHeaders++; } if (slice._onAddingHeader) { try { slice._onAddingHeader(header); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } } try { slice.onHeaderAdded(header, body, false, true); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } } } this._insertIntoBlockUsingDateAndUID('header', header.date, header.id, header.srvid, $sync.HEADER_EST_SIZE_IN_BYTES, header, callback); }, updateMessageHeader: function ifs_updateMessageHeader(date, id, partOfSync, headerOrMutationFunc, body, callback, opts) { if (this._pendingLoads.length) { this._deferredCalls.push(this.updateMessageHeader.bind(this, date, id, partOfSync, headerOrMutationFunc, body, callback)); return; } var infoTuple = this._findRangeObjIndexForDateAndID(this._headerBlockInfos, date, id), iInfo = infoTuple[0], info = infoTuple[1], self = this; function doUpdateHeader(block) { var idx = block.ids.indexOf(id), header; if (idx === -1) { if (headerOrMutationFunc instanceof Function) headerOrMutationFunc(null); else throw new Error('Failed to find ID ' + id + '!'); } else if (headerOrMutationFunc instanceof Function) { if (!headerOrMutationFunc(header = block.headers[idx])) header = null; } else { header = block.headers[idx] = headerOrMutationFunc; } if (header) { self._dirty = true; self._dirtyHeaderBlocks[info.blockId] = block; logic(self, 'updateMessageHeader', { date: header.date, id: header.id, srvid: header.srvid }); if (self._slices.length > (self._curSyncSlice ? 1 : 0)) { for (var iSlice = 0; iSlice < self._slices.length; iSlice++) { var slice = self._slices[iSlice]; if (partOfSync && slice === self._curSyncSlice) continue; if (opts && opts.silent) { continue; } if (BEFORE(date, slice.startTS) || STRICTLY_AFTER(date, slice.endTS)) continue; if (date === slice.startTS && id < slice.startUID || date === slice.endTS && id > slice.endUID) continue; try { slice.onHeaderModified(header, body); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } } } } if (callback) callback(); } if (!info) { if (headerOrMutationFunc instanceof Function) headerOrMutationFunc(null); else throw new Error('Failed to find block containing header with date: ' + date + ' id: ' + id); } else if (!this._headerBlocks.hasOwnProperty(info.blockId)) this._loadBlock('header', info, doUpdateHeader); else doUpdateHeader(this._headerBlocks[info.blockId]); }, updateMessageHeaderByServerId: function (srvid, partOfSync, headerOrMutationFunc, body, callback) { if (this._pendingLoads.length) { this._deferredCalls.push(this.updateMessageHeaderByServerId.bind(this, srvid, partOfSync, headerOrMutationFunc, body, callback)); return; } var blockId = this._serverIdHeaderBlockMapping[srvid]; if (srvid === undefined) { logic(this, 'serverIdMappingMissing', { srvid: srvid }); return; } var findInBlock = function findInBlock(headerBlock) { var headers = headerBlock.headers; for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (header.srvid === srvid) { this.updateMessageHeader(header.date, header.id, partOfSync, headerOrMutationFunc, body, callback); return; } } }.bind(this); if (this._headerBlocks.hasOwnProperty(blockId)) { findInBlock(this._headerBlocks[blockId]); } else { var blockInfo = this._findBlockInfoFromBlockId('header', blockId); this._loadBlock('header', blockInfo, findInBlock); } }, unchangedMessageHeader: function ifs_unchangedMessageHeader(header) { if (this._pendingLoads.length) { this._deferredCalls.push(this.unchangedMessageHeader.bind(this, header)); return; } if (this._curSyncSlice && !this._curSyncSlice.ignoreHeaders) this._curSyncSlice.onHeaderAdded(header, true, false); }, hasMessageWithServerId: function (srvid) { if (!this._serverIdHeaderBlockMapping) throw new Error('Server ID mapping not supported for this storage!'); var blockId = this._serverIdHeaderBlockMapping[srvid]; if (srvid === undefined) { logic(this, 'serverIdMappingMissing', { srvid: srvid }); return false; } return !!blockId; }, deleteMessageHeaderAndBody: function (suid, date, callback) { this.getMessageHeader(suid, date, function (header) { if (header) this.deleteMessageHeaderAndBodyUsingHeader(header, callback); else callback(); }.bind(this)); }, deleteMessageHeaderUsingHeader: function (header, callback) { if (this._pendingLoads.length) { this._deferredCalls.push(this.deleteMessageHeaderUsingHeader.bind(this, header, callback)); return; } this.headerCount -= 1; if (this._curSyncSlice) { this._curSyncSlice.headerCount = this.headerCount; if (!this._curSyncSlice.ignoreHeaders) { this._curSyncSlice.onHeaderRemoved(header); } } if (this._slices.length > (this._curSyncSlice ? 1 : 0)) { for (var iSlice = 0; iSlice < this._slices.length; iSlice++) { var slice = this._slices[iSlice]; if (slice.type === 'folder') { slice.headerCount = this.headerCount; } if (slice === this._curSyncSlice) continue; if (BEFORE(header.date, slice.startTS) || STRICTLY_AFTER(header.date, slice.endTS)) continue; if (header.date === slice.startTS && header.id < slice.startUID || header.date === slice.endTS && header.id > slice.endUID) continue; slice.onHeaderRemoved(header); } } if (this._serverIdHeaderBlockMapping && header.srvid) delete this._serverIdHeaderBlockMapping[header.srvid]; this._deleteFromBlock('header', header.date, header.id, callback); }, deleteMessageHeaderAndBodyUsingHeader: function (header, callback) { if (this._pendingLoads.length) { this._deferredCalls.push(this.deleteMessageHeaderAndBodyUsingHeader.bind(this, header, callback)); return; } this.deleteMessageHeaderUsingHeader(header, function () { this._deleteFromBlock('body', header.date, header.id, callback); }.bind(this)); }, deleteMessageByServerId: function (srvid, callback) { if (!this._serverIdHeaderBlockMapping) throw new Error('Server ID mapping not supported for this storage!'); if (this._pendingLoads.length) { this._deferredCalls.push(this.deleteMessageByServerId.bind(this, srvid, callback)); return; } var blockId = this._serverIdHeaderBlockMapping[srvid]; if (srvid === undefined) { logic(this, 'serverIdMappingMissing', { srvid: srvid }); return; } var findInBlock = function findInBlock(headerBlock) { var headers = headerBlock.headers; for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (header.srvid === srvid) { this.deleteMessageHeaderAndBodyUsingHeader(header, callback); return; } } }.bind(this); if (this._headerBlocks.hasOwnProperty(blockId)) { findInBlock(this._headerBlocks[blockId]); } else { var blockInfo = this._findBlockInfoFromBlockId('header', blockId); this._loadBlock('header', blockInfo, findInBlock); } }, addMessageBody: function ifs_addMessageBody(header, bodyInfo, callback) { if (this._pendingLoads.length) { this._deferredCalls.push(this.addMessageBody.bind(this, header, bodyInfo, callback)); return; } logic(this, 'addMessageBody', { date: header.date, id: header.id, srvid: header.srvid, bodyInfo: bodyInfo }); var sizeEst = OBJ_OVERHEAD_EST + NUM_ATTR_OVERHEAD_EST + 4 * NULL_ATTR_OVERHEAD_EST; function sizifyAddrs(addrs) { sizeEst += LIST_ATTR_OVERHEAD_EST; if (!addrs) return; for (var i = 0; i < addrs.length; i++) { var addrPair = addrs[i]; sizeEst += OBJ_OVERHEAD_EST + 2 * STR_ATTR_OVERHEAD_EST + (addrPair.name ? addrPair.name.length : 0) + (addrPair.address ? addrPair.address.length : 0); } } function sizifyAttachments(atts) { sizeEst += LIST_ATTR_OVERHEAD_EST; if (!atts) return; for (var i = 0; i < atts.length; i++) { var att = atts[i]; sizeEst += OBJ_OVERHEAD_EST + 2 * STR_ATTR_OVERHEAD_EST + att.name.length + att.type.length + NUM_ATTR_OVERHEAD_EST; } } function sizifyStr(str) { sizeEst += STR_ATTR_OVERHEAD_EST + str.length; } function sizifyStringList(strings) { sizeEst += LIST_OVERHEAD_EST; if (!strings) return; for (var i = 0; i < strings.length; i++) { sizeEst += STR_ATTR_OVERHEAD_EST + strings[i].length; } } function sizifyBodyRep(rep) { sizeEst += LIST_OVERHEAD_EST + NUM_OVERHEAD_EST * (rep.length / 2) + STR_OVERHEAD_EST * (rep.length / 2); for (var i = 1; i < rep.length; i += 2) { if (rep[i]) sizeEst += rep[i].length; } } ; function sizifyBodyReps(reps) { if (!reps) return; sizeEst += STR_OVERHEAD_EST * (reps.length / 2); for (var i = 0; i < reps.length; i++) { var rep = reps[i]; if (rep.type === 'html') { sizeEst += STR_OVERHEAD_EST + rep.amountDownloaded; } else { rep.content && sizifyBodyRep(rep.content); } } } ; if (bodyInfo.to) sizifyAddrs(bodyInfo.to); if (bodyInfo.cc) sizifyAddrs(bodyInfo.cc); if (bodyInfo.bcc) sizifyAddrs(bodyInfo.bcc); if (bodyInfo.replyTo) sizifyStr(bodyInfo.replyTo); sizifyAttachments(bodyInfo.attachments); sizifyAttachments(bodyInfo.relatedParts); sizifyStringList(bodyInfo.references); sizifyBodyReps(bodyInfo.bodyReps); bodyInfo.size = sizeEst; this._insertIntoBlockUsingDateAndUID('body', header.date, header.id, header.srvid, bodyInfo.size, bodyInfo, callback); }, messageBodyRepsDownloaded: function (bodyInfo) { if (!bodyInfo.bodyReps || !bodyInfo.bodyReps.length) return true; var bodyRepsDownloaded = bodyInfo.bodyReps.every(function (rep) { return rep.isDownloaded; }); if (this._account.type !== 'pop3' || this.folderMeta.type !== 'inbox') { return bodyRepsDownloaded; } var attachmentsDownloaded = bodyInfo.attachments.every(function (att) { return !!att.file; }); return bodyRepsDownloaded && attachmentsDownloaded; }, getMessageBodyWithReps: function (suid, date, callback) { var self = this; this.getMessageBody(suid, date, function (bodyInfo) { if (!bodyInfo) { return callback(bodyInfo); } if (self.messageBodyRepsDownloaded(bodyInfo)) { return callback(bodyInfo); } self._account.universe.downloadMessageBodyReps(suid, date, function (err, bodyInfo) { callback(bodyInfo); }); }); }, getMessageBody: function ifs_getMessageBody(suid, date, callback) { if (this._pendingLoads.length) { this._deferredCalls.push(this.getMessageBody.bind(this, suid, date, callback)); return; } var id = parseInt(suid.substring(suid.lastIndexOf('/') + 1)), posInfo = this._findRangeObjIndexForDateAndID(this._bodyBlockInfos, date, id); if (posInfo[1] === null) { logic(this, 'bodyNotFound'); try { callback(null); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } return; } var bodyBlockInfo = posInfo[1], self = this; if (!this._bodyBlocks.hasOwnProperty(bodyBlockInfo.blockId)) { this._loadBlock('body', bodyBlockInfo, function (bodyBlock) { var bodyInfo = bodyBlock.bodies[id] || null; if (!bodyInfo) logic(self, 'bodyNotFound'); try { callback(bodyInfo); } catch (ex) { logic(self, 'callbackErr', { ex: ex }); } }); return; } var block = this._bodyBlocks[bodyBlockInfo.blockId], bodyInfo = block.bodies[id] || null; if (!bodyInfo) logic(this, 'bodyNotFound'); try { callback(bodyInfo); } catch (ex) { logic(this, 'callbackErr', { ex: ex }); } }, updateMessageBody: function (header, bodyInfo, options, eventDetails, callback) { if (typeof eventDetails === 'function') { callback = eventDetails; eventDetails = null; } if (this._pendingLoads.length) { this._deferredCalls.push(this.updateMessageBody.bind(this, header, bodyInfo, options, eventDetails, callback)); return; } var suid = header.suid; var id = parseInt(suid.substring(suid.lastIndexOf('/') + 1)); var self = this; function bodyUpdated() { if (options.flushBecause) { bodyInfo = null; self._account.saveAccountState(null, function forgetAndReGetMessageBody() { self.getMessageBody(suid, header.date, performNotifications); }, 'flushBody'); } else { performNotifications(); } } function performNotifications(refreshedBody) { if (refreshedBody) { bodyInfo = refreshedBody; } if (eventDetails && self._account.universe) { self._account.universe.__notifyModifiedBody(suid, eventDetails, bodyInfo); } if (callback) { callback(bodyInfo); } } this._deleteFromBlock('body', header.date, id, function () { self.addMessageBody(header, bodyInfo, bodyUpdated); }); }, shutdown: function () { for (var i = this._slices.length - 1; i >= 0; i--) { this._slices[i].die(); } this.folderSyncer.shutdown(); }, youAreDeadCleanupAfterYourself: function () { } }; }); ; define('cronsync', [ 'require', 'exports', 'module', 'logic', './worker-router', './mailslice', './syncbase', './allback', './slice_bridge_proxy' ], function (require, exports) { 'use strict'; var logic = require('logic'), router = require('./worker-router'), mailslice = require('./mailslice'), syncbase = require('./syncbase'), allback = require('./allback'); function debug(str) { console.log('cronsync: ' + str + '\n'); } var SliceBridgeProxy = require('./slice_bridge_proxy').SliceBridgeProxy; function makeHackedUpSlice(storage, callback) { var fakeBridgeThatEatsStuff = { __sendMessage: function () { } }, proxy = new SliceBridgeProxy(fakeBridgeThatEatsStuff, 'cron'), slice = new mailslice.MailSlice(proxy, storage), oldStatusMethod = proxy.sendStatus, newHeaders = []; slice.onNewHeader = function (header) { console.log('onNewHeader: ' + header); newHeaders.push(header); }; proxy.sendStatus = function (status, requested, moreExpected, progress, newEmailCount) { oldStatusMethod.apply(this, arguments); if (callback) { switch (status) { case 'synced': case 'syncfailed': case 'syncblocked': try { callback(newHeaders); } catch (ex) { console.error('cronsync callback error:', ex, '\n', ex.stack); callback = null; throw ex; } callback = null; break; } } }; return slice; } function CronSync(universe) { this._universe = universe; logic.defineScope(this, 'CronSync'); this._ensureSyncResolve = null; this.sendCronSync = router.registerSimple('cronsync', data => { var args = data.args; switch (data.cmd) { case 'alarm': debug('received an alarm via a message handler'); this.onAlarm.apply(this, args); break; case 'syncEnsured': debug('received an syncEnsured via a message handler'); this.onSyncEnsured.apply(this, args); break; } }); this.sendCronSync('hello'); this.ensureSync(); } exports.CronSync = CronSync; CronSync.prototype = { ensureSync: function () { if (this._ensureSyncResolve) { return; } logic(this, 'ensureSync_begin'); this._ensureSyncPromise = new Promise(resolve => { this._ensureSyncResolve = resolve; }); debug('ensureSync called'); var accounts = this._universe.accounts, syncData = {}; accounts.forEach(function (account) { var interval = account.accountDef.syncInterval, intervalKey = 'interval' + interval; if (!syncData.hasOwnProperty(intervalKey)) { syncData[intervalKey] = []; } syncData[intervalKey].push(account.id); }); this.sendCronSync('ensureSync', [syncData]); }, onSyncEnsured: function () { this._ensureSyncResolve(); this._ensureSyncResolve = null; logic(this, 'ensureSync_end'); }, syncAccount: function (account) { return new Promise(resolve => { var scope = logic.subscope(this, { accountId: account.id }); if (!this._universe.online || !account.enabled) { debug('syncAccount early exit: online: ' + this._universe.online + ', enabled: ' + account.enabled); logic(scope, 'syncSkipped'); resolve(); return; } var latch = allback.latch(); var inboxDone = latch.defer('inbox'); var inboxFolder = account.getFirstFolderWithType('inbox'); var storage = account.getFolderStorageForFolderId(inboxFolder.id); logic(scope, 'syncAccount_begin'); logic(scope, 'syncAccountHeaders_begin'); var slice = makeHackedUpSlice(storage, newHeaders => { logic(scope, 'syncAccountHeaders_end', { headers: newHeaders }); var notifyHeaders = []; newHeaders.some(function (header, i) { notifyHeaders.push({ date: header.date, from: header.author.name || header.author.address, subject: header.subject, accountId: account.id, messageSuid: header.suid }); if (i === syncbase.CRONSYNC_MAX_MESSAGES_TO_REPORT_PER_ACCOUNT - 1) { return true; } }); if (newHeaders.length) { debug('Asking for snippets for ' + notifyHeaders.length + ' headers'); if (account.accountDef.type === 'pop3+smtp') { logic(scope, 'syncAccount_end'); inboxDone([ newHeaders.length, notifyHeaders ]); } else if (this._universe.online) { logic(scope, 'syncAccountSnippets_begin'); this._universe.downloadBodies(newHeaders.slice(0, syncbase.CRONSYNC_MAX_SNIPPETS_TO_FETCH_PER_ACCOUNT), { maximumBytesToFetch: syncbase.MAX_SNIPPET_BYTES }, () => { debug('Notifying for ' + newHeaders.length + ' headers'); logic(scope, 'syncAccountSnippets_end'); logic(scope, 'syncAccount_end'); inboxDone([ newHeaders.length, notifyHeaders ]); }); } else { logic(scope, 'syncAccount_end'); debug('UNIVERSE OFFLINE. Notifying for ' + newHeaders.length + ' headers'); inboxDone([ newHeaders.length, notifyHeaders ]); } } else { logic(scope, 'syncAccount_end'); inboxDone(); } slice.die(); }); storage.sliceOpenMostRecent(slice, true); var outboxFolder = account.getFirstFolderWithType('outbox'); if (outboxFolder) { var outboxStorage = account.getFolderStorageForFolderId(outboxFolder.id); if (outboxStorage.getKnownMessageCount() > 0) { var outboxDone = latch.defer('outbox'); logic(scope, 'sendOutbox_begin'); this._universe.sendOutboxMessages(account, { reason: 'syncAccount' }, () => { logic(scope, 'sendOutbox_end'); outboxDone(); }); } } latch.then(latchResults => { var inboxResult = latchResults.inbox[0]; this._universe.waitForAccountOps(account, function () { account.runAfterSaves(function () { resolve(inboxResult); }); }); }); }); }, onAlarm: function (accountIds) { logic(this, 'alarmFired', { accountIds: accountIds }); if (!accountIds) { return; } var accounts = this._universe.accounts, targetAccounts = [], ids = []; logic(this, 'cronSync_begin'); this._universe.__notifyStartedCronSync(accountIds); accountIds.forEach(function (id) { accounts.some(function (account) { if (account.id === id) { targetAccounts.push(account); ids.push(id); return true; } }); }); this.ensureSync(); var syncResults = []; var accountsResults = { accountIds: accountIds }; var done = () => { this._ensureSyncPromise.then(() => { if (syncResults.length) { accountsResults.updates = syncResults; } this._universe.__notifyStoppedCronSync(accountsResults); logic(this, 'syncAccounts_end', { accountsResults: accountsResults }); logic(this, 'cronSync_end'); }); }; if (!ids.length) { done(); return; } logic(this, 'syncAccounts_begin'); Promise.all(targetAccounts.map(account => { return this.syncAccount(account).then(result => { if (result) { syncResults.push({ id: account.id, address: account.identities[0].address, count: result[0], latestMessageInfos: result[1] }); } }); })).then(done); }, shutdown: function () { router.unregister('cronsync'); } }; }); ; define('accountcommon', [ './a64', 'logic', './allback', 'require', 'module', 'exports' ], function ($a64, logic, allback, require, $module, exports) { var latchedWithRejections = allback.latchedWithRejections; var AUTOCONFIG_TIMEOUT_MS = 30 * 1000; var ISPDB_AUTOCONFIG_ROOT = 'https://live.mozillamessaging.com/autoconfig/v1.1/'; function requireConfigurator(type, fn) { if (type === 'activesync') { require(['activesync/configurator'], fn); } else if (type === 'pop3+smtp' || type === 'imap+smtp') { require(['composite/configurator'], fn); } } function accountTypeToClass(type, callback) { requireConfigurator(type, function (mod) { callback(mod.account.Account); }); } exports.accountTypeToClass = accountTypeToClass; var autoconfigByDomain = exports._autoconfigByDomain = { 'localhost': { type: 'imap+smtp', incoming: { hostname: 'localhost', port: 143, socketType: 'plain', username: '%EMAILLOCALPART%' }, outgoing: { hostname: 'localhost', port: 25, socketType: 'plain', username: '%EMAILLOCALPART%' } }, 'fakeimaphost': { type: 'imap+smtp', incoming: { hostname: 'localhost', port: 0, socketType: 'plain', username: '%EMAILLOCALPART%' }, outgoing: { hostname: 'localhost', port: 0, socketType: 'plain', username: '%EMAILLOCALPART%' } }, 'fakepop3host': { type: 'pop3+smtp', incoming: { hostname: 'localhost', port: 0, socketType: 'plain', username: '%EMAILLOCALPART%' }, outgoing: { hostname: 'localhost', port: 0, socketType: 'plain', username: '%EMAILLOCALPART%' } }, 'slocalhost': { type: 'imap+smtp', incoming: { hostname: 'localhost', port: 993, socketType: 'SSL', username: '%EMAILLOCALPART%' }, outgoing: { hostname: 'localhost', port: 465, socketType: 'SSL', username: '%EMAILLOCALPART%' } }, 'fakeashost': { type: 'activesync', displayName: 'Test', incoming: { server: 'http://localhost:8880', username: '%EMAILADDRESS%' } }, 'saslocalhost': { type: 'activesync', displayName: 'Test', incoming: { server: 'https://localhost:443', username: '%EMAILADDRESS%' } }, 'nonesuch.nonesuch': { type: 'imap+smtp', imapHost: 'nonesuch.nonesuch', imapPort: 993, imapCrypto: true, smtpHost: 'nonesuch.nonesuch', smtpPort: 465, smtpCrypto: true, usernameIsFullEmail: false } }; function recreateIdentities(universe, accountId, oldIdentities) { var identities = []; for (var iter in Iterator(oldIdentities)) { var oldIdentity = iter[1]; identities.push({ id: accountId + '/' + $a64.encodeInt(universe.config.nextIdentityNum++), name: oldIdentity.name, address: oldIdentity.address, replyTo: oldIdentity.replyTo, signature: oldIdentity.signature, signatureEnabled: oldIdentity.signatureEnabled }); } return identities; } exports.recreateIdentities = recreateIdentities; function fillConfigPlaceholders(userDetails, sourceConfigInfo) { var configInfo = JSON.parse(JSON.stringify(sourceConfigInfo)); var details = userDetails.emailAddress.split('@'); var emailLocalPart = details[0], emailDomainPart = details[1]; var domain = emailDomainPart.toLowerCase(); var placeholderFields = { incoming: [ 'username', 'hostname', 'server' ], outgoing: [ 'username', 'hostname' ] }; function fillPlaceholder(value) { return value.replace('%EMAILADDRESS%', userDetails.emailAddress).replace('%EMAILLOCALPART%', emailLocalPart).replace('%EMAILDOMAIN%', emailDomainPart).replace('%REALNAME%', userDetails.displayName); } for (var serverType in placeholderFields) { var fields = placeholderFields[serverType]; var server = configInfo[serverType]; if (!server) { continue; } for (var iField = 0; iField < fields.length; iField++) { var field = fields[iField]; if (server.hasOwnProperty(field)) { server[field] = fillPlaceholder(server[field]); } } } return configInfo; } exports.fillConfigPlaceholders = fillConfigPlaceholders; function Autoconfigurator() { this.timeout = AUTOCONFIG_TIMEOUT_MS; logic.defineScope(this, 'Autoconfigurator'); } exports.Autoconfigurator = Autoconfigurator; Autoconfigurator.prototype = { _fatalErrors: [ 'bad-user-or-pass', 'not-authorized' ], _isSuccessOrFatal: function (error) { return !error || this._fatalErrors.indexOf(error) !== -1; }, _getXmlConfig: function getXmlConfig(url) { return new Promise(function (resolve, reject) { var scope = logic.subscope(this, { method: 'GET', url: url }); logic(scope, 'xhr:start'); var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('GET', url, true); xhr.timeout = this.timeout; xhr.onload = function () { logic(scope, 'xhr:end', { status: xhr.status }); if (xhr.status < 200 || xhr.status >= 300) { reject('status' + xhr.status); return; } self.postMessage({ uid: 0, type: 'configparser', cmd: 'accountcommon', args: [xhr.responseText] }); self.addEventListener('message', function onworkerresponse(evt) { var data = evt.data; if (data.type != 'configparser' || data.cmd != 'accountcommon') { return; } self.removeEventListener(evt.type, onworkerresponse); var args = data.args; var config = args[0], status = args[1]; resolve(config); }); }; xhr.ontimeout = function () { logic(scope, 'xhr:end', { status: 'timeout' }); reject('timeout'); }; xhr.onerror = function () { logic(scope, 'xhr:end', { status: 'error' }); reject('error'); }; try { xhr.send(); } catch (e) { logic(scope, 'xhr:end', { status: 'sync-error' }); reject('status404'); } }.bind(this)); }, _getConfigFromLocalFile: function getConfigFromLocalFile(domain) { return this._getXmlConfig('/autoconfig/' + encodeURIComponent(domain)); }, _checkAutodiscoverUrl: function (url) { return new Promise(function (resolve, reject) { var scope = logic.subscope(this, { method: 'POST', url: url }); logic(scope, 'autodiscoverProbe:start'); var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('POST', url, true); xhr.timeout = this.timeout; var victory = function () { resolve({ type: 'activesync', incoming: { autodiscoverEndpoint: url } }); }.bind(this); xhr.onload = function () { logic(scope, 'autodiscoverProbe:end', { status: xhr.status }); if (xhr.status === 401) { victory(); return; } reject('status' + xhr.status); }; xhr.ontimeout = function () { logic(scope, 'autodiscoverProbe:end', { status: 'timeout' }); reject('timeout'); }; xhr.onerror = function () { logic(scope, 'autodiscoverProbe:end', { status: 'error' }); reject('error'); }; try { xhr.send(null); } catch (e) { logic(scope, 'autodiscoverProbe:end', { status: 'sync-error' }); reject('status404'); } }.bind(this)); }, _probeForAutodiscover: function (domain) { var subdirUrl = 'https://' + domain + '/autodiscover/autodiscover.xml'; var domainUrl = 'https://autodiscover.' + domain + '/autodiscover/autodiscover.xml'; return latchedWithRejections({ subdir: this._checkAutodiscoverUrl(subdirUrl), domain: this._checkAutodiscoverUrl(domainUrl) }).then(function (results) { if (results.subdir.resolved && results.subdir.value) { return results.subdir.value; } if (results.domain.resolved && results.domain.value) { return results.domain.value; } return null; }.bind(this)); }, _getConfigFromISPDB: function (domain) { return this._getXmlConfig(ISPDB_AUTOCONFIG_ROOT + encodeURIComponent(domain)); }, _getMX: function getMX(domain) { return new Promise(function (resolve, reject) { var scope = logic.subscope(this, { domain: domain }); logic(scope, 'mxLookup:begin'); var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('GET', 'https://live.mozillamessaging.com/dns/mx/' + encodeURIComponent(domain), true); xhr.timeout = this.timeout; xhr.onload = function () { var reportDomain = null; if (xhr.status === 200) { var normStr = xhr.responseText.split('\n')[0]; if (normStr) { normStr = normStr.toLowerCase(); var mxDomain = normStr.split('.').slice(-2).join('.'); if (mxDomain !== domain) { reportDomain = mxDomain; } } } logic(scope, 'mxLookup:end', { 'raw': normStr, normalized: mxDomain, reporting: reportDomain }); resolve(reportDomain); }; xhr.ontimeout = function () { logic(scope, 'mxLookup:end', { status: 'timeout' }); reject('timeout'); }; xhr.onerror = function () { logic(scope, 'mxLookup:end', { status: 'error' }); reject('error'); }; xhr.send(); }.bind(this)); }, _getHostedAndISPDBConfigs: function (domain, emailAddress) { var commonAutoconfigSuffix = '/mail/config-v1.1.xml?emailaddress=' + encodeURIComponent(emailAddress); var subdomainAutoconfigUrl = 'https://autoconfig.' + domain + commonAutoconfigSuffix; var wellKnownAutoconfigUrl = 'https://' + domain + '/.well-known/autoconfig' + commonAutoconfigSuffix; return latchedWithRejections({ autoconfigSubdomain: this._getXmlConfig(subdomainAutoconfigUrl), autoconfigWellKnown: this._getXmlConfig(wellKnownAutoconfigUrl), ispdb: this._getConfigFromISPDB(domain), mxDomain: this._getMX(domain) }).then(function (results) { if (results.autoconfigSubdomain.resolved && results.autoconfigSubdomain.value) { return { type: 'config', source: 'autoconfig-subdomain', config: results.autoconfigSubdomain.value }; } if (results.autoconfigWellKnown.resolved && results.autoconfigWellKnown.value) { return { type: 'config', source: 'autoconfig-wellknown', config: results.autoconfigWellKnown.value }; } if (results.ispdb.resolved && results.ispdb.value) { return { type: 'config', source: 'ispdb', config: results.ispdb.value }; } if (results.mxDomain.resolved && results.mxDomain.value && results.mxDomain.value !== domain) { return { type: 'mx', domain: results.mxDomain.value }; } return { type: null }; }.bind(this)); }, _getConfigFromMX: function getConfigFromMX(domain, callback) { var self = this; this._getMX(domain, function (error, mxDomain, errorDetails) { if (error) return callback(error, null, errorDetails); console.log(' Found MX for', mxDomain); if (domain === mxDomain) return callback('no-config-info', null, { status: 'mxsame' }); console.log(' Looking in local file store'); self._getConfigFromLocalFile(mxDomain, function (error, config, errorDetails) { if (!error) { callback(error, config, errorDetails); return; } console.log(' Looking in the Mozilla ISPDB'); self._getConfigFromDB(mxDomain, callback); }); }); }, _checkGelamConfig: function (domain) { if (autoconfigByDomain.hasOwnProperty(domain)) { return autoconfigByDomain[domain]; } return null; }, learnAboutAccount: function (details) { return new Promise(function (resolve, reject) { var emailAddress = details.emailAddress; var emailParts = emailAddress.split('@'); var emailLocalPart = emailParts[0], emailDomainPart = emailParts[1]; var domain = emailDomainPart.toLowerCase(); var scope = logic.subscope(this, { domain: domain }); logic(scope, 'autoconfig:begin'); var victory = function (sourceConfigInfo, source) { var configInfo = null, result; if (sourceConfigInfo) { configInfo = fillConfigPlaceholders(details, sourceConfigInfo); if (configInfo.incoming && configInfo.incoming.authentication === 'xoauth2') { result = 'need-oauth2'; } else { result = 'need-password'; } } else { result = 'no-config-info'; } logic(scope, 'autoconfig:end', { result: result, source: source, configInfo: configInfo }); resolve({ result: result, source: source, configInfo: configInfo }); }.bind(this); var failsafeFailure = function (err) { logic(this, 'autoconfig:end', { err: { message: err && err.message, stack: err && err.stack } }); resolve({ result: 'no-config-info', configInfo: null }); }.bind(this); var coerceRejectionToNull = function (err) { logic(scope, 'autoconfig:coerceRejection', { err: err }); return null; }.bind(this); var hardcodedConfig = this._checkGelamConfig(domain); if (hardcodedConfig) { victory(hardcodedConfig, 'hardcoded'); return; } var localConfigHandler = function (info) { if (info) { victory(info, 'local'); return null; } return this._getHostedAndISPDBConfigs(domain, emailAddress).then(selfHostedAndISPDBHandler); }.bind(this); var mxDomain; var selfHostedAndISPDBHandler = function (typedResult) { if (typedResult.type === 'config') { victory(typedResult.config, typedResult.source); return null; } if (typedResult.type === 'mx') { mxDomain = typedResult.domain; return this._getConfigFromLocalFile(mxDomain).catch(coerceRejectionToNull).then(mxLocalHandler); } return this._probeForAutodiscover(domain).then(autodiscoverHandler); }.bind(this); var mxLocalHandler = function (info) { if (info) { victory(info, 'mx local'); return null; } return this._getConfigFromISPDB(mxDomain).catch(coerceRejectionToNull).then(mxISPDBHandler); }.bind(this); var mxISPDBHandler = function (info) { if (info) { victory(info, 'mx ispdb'); return null; } return this._probeForAutodiscover(domain).then(autodiscoverHandler); }.bind(this); var autodiscoverHandler = function (info) { victory(info, info ? 'autodiscover' : null); return null; }.bind(this); this._getConfigFromLocalFile(domain).catch(coerceRejectionToNull).then(localConfigHandler).catch(failsafeFailure); }.bind(this)); }, tryToCreateAccount: function (universe, userDetails, callback) { this.learnAboutAccount(userDetails).then(function success(results) { if (results.result === 'need-password') { var config = results.configInfo; requireConfigurator(config.type, function (mod) { mod.configurator.tryToCreateAccount(universe, userDetails, config, callback); }); return; } logic(this, 'legacyCreateFail', { result: results.result }); callback('no-config-info'); }.bind(this), function failure(err) { callback(err, null, null); }.bind(this)); } }; function recreateAccount(universe, oldVersion, accountInfo, callback) { requireConfigurator(accountInfo.def.type, function (mod) { mod.configurator.recreateAccount(universe, oldVersion, accountInfo, callback); }); } exports.recreateAccount = recreateAccount; function tryToManuallyCreateAccount(universe, userDetails, domainInfo, callback) { requireConfigurator(domainInfo.type, function (mod) { mod.configurator.tryToCreateAccount(universe, userDetails, domainInfo, callback); }); } exports.tryToManuallyCreateAccount = tryToManuallyCreateAccount; }); ; define('mailuniverse', [ 'logic', './a64', './date', './syncbase', './worker-router', './maildb', './cronsync', './accountcommon', './allback', 'module', 'exports' ], function (logic, $a64, $date, $syncbase, $router, $maildb, $cronsync, $acctcommon, $allback, $module, exports) { var MAX_MUTATIONS_FOR_UNDO = 10; var MAX_LOG_BACKLOG_MS = 30000; function makeBridgeFn(bridgeMethod) { return function (a1, a2, a3) { for (var iBridge = 0; iBridge < this._bridges.length; iBridge++) { var bridge = this._bridges[iBridge]; bridge[bridgeMethod](a1, a2, a3); } }; } function MailUniverse(callAfterBigBang, online, testOptions) { this.accounts = []; this._accountsById = {}; this.identities = []; this._identitiesById = {}; this._opsByAccount = {}; this._opCompletionListenersByAccount = {}; this._opCallbacks = {}; this._bridges = []; this._testModeDisablingLocalOps = false; this._testModeFakeNavigator = testOptions && testOptions.fakeNavigator || null; this.online = true; this._onConnectionChange(online); this._mode = 'cron'; this._deferredOpTimeout = null; this._boundQueueDeferredOps = this._queueDeferredOps.bind(this); this.config = null; this._logBacklog = []; this._db = new $maildb.MailDB(testOptions); this._cronSync = null; var self = this; this._db.getConfig(function (configObj, accountInfos, lazyCarryover) { function setupLogging(config) { function censorLogs() { logic.isCensored = true; function censorValue(value) { if (value && (value.suid || value.srvid)) { return { date: value.date, suid: value.suid, srvid: value.srvid }; } else if (value && typeof value === 'object') { return value.toString(); } else { return value; } } logic.on('censorEvent', function (e) { if (logic.isPlainObject(e.details)) { for (var key in e.details) { var value = e.details[key]; if (key[0] === '_') { delete e.details[key]; } else if (Array.isArray(value)) { e.details[key] = value.map(censorValue); } else { e.details[key] = censorValue(value); } } } }); } if (self.config.debugLogging) { if (self.config.debugLogging === 'realtime-dangerous') { console.warn('!!!'); console.warn('!!! REALTIME USER-DATA ENTRAINING LOGGING ENABLED !!!'); console.warn('!!!'); console.warn('You are about to see a lot of logs, as they happen!'); console.warn('They will also be circularly buffered for saving.'); console.warn(''); console.warn('These logs will contain SENSITIVE DATA. The CONTENTS'); console.warn('OF EMAILS, maybe some PASSWORDS. This was turned on'); console.warn('via the secret debug mode UI. Use it to turn us off:'); console.warn('https://wiki.mozilla.org/Gaia/Email/SecretDebugMode'); logic.realtimeLogEverything = true; } else if (self.config.debugLogging !== 'dangerous') { console.warn('GENERAL LOGGING ENABLED!'); console.warn('(CIRCULAR EVENT LOGGING WITH NON-SENSITIVE DATA)'); censorLogs(); } else { console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); console.warn('DANGEROUS USER-DATA ENTRAINING LOGGING ENABLED !!!'); console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); console.warn('This means contents of e-mails and passwords if you'); console.warn('set up a new account. (The IMAP protocol sanitizes'); console.warn('passwords, but the bridge logger may not.)'); console.warn(''); console.warn('If you forget how to turn us off, see:'); console.warn('https://wiki.mozilla.org/Gaia/Email/SecretDebugMode'); console.warn('...................................................'); } } else if (!logic.underTest) { censorLogs(); var NAMESPACES_TO_ALWAYS_LOG = [ 'BrowserBox', 'SmtpClient', 'ActivesyncConfigurator', 'ImapFolderSync', 'Pop3Prober', 'Autoconfigurator', 'DisasterRecovery', 'ImapClient', 'ImapJobDriver', 'Oauth', 'Pop3FolderSyncer', 'SmtpProber' ]; logic.on('preprocessEvent', function (e) { var eventShouldBeLogged = NAMESPACES_TO_ALWAYS_LOG.indexOf(e.namespace) !== -1 || e.namespace === 'Account' && e.details && e.details.accountType === 'smtp' || e.type === 'allOpsCompleted' || e.type === 'mailslice:mutex-released'; if (!eventShouldBeLogged) { e.preventDefault(); } }); logic.on('event', function (e) { var obj = e.toJSON(); dump('[' + obj.namespace + '] ' + obj.type + ' ' + JSON.stringify(obj.details) + '\n'); }); } } var accountInfo, i; var doneCount = 0; var accountCount = accountInfos.length; if (configObj) { self.config = configObj; setupLogging(); logic.defineScope(self, 'MailUniverse'); if (self.config.debugLogging) self._enableCircularLogging(); logic(self, 'configLoaded', { config: self.config, accountInfos: accountInfos }); function done() { doneCount += 1; if (doneCount === accountCount) { self._initFromConfig(); callAfterBigBang(); } } if (accountCount) { for (i = 0; i < accountCount; i++) { accountInfo = accountInfos[i]; self._loadAccount(accountInfo.def, accountInfo.folderInfo, null, done); } return; } } else { self.config = { id: 'config', nextAccountNum: 0, nextIdentityNum: 0, debugLogging: lazyCarryover ? lazyCarryover.config.debugLogging : false }; setupLogging(); logic.defineScope(self, 'MailUniverse'); if (self.config.debugLogging) self._enableCircularLogging(); self._db.saveConfig(self.config); if (lazyCarryover) { logic(self, 'configMigrating_begin', { lazyCarryover: lazyCarryover }); var waitingCount = lazyCarryover.accountInfos.length; var oldVersion = lazyCarryover.oldVersion; var accountRecreated = function (accountInfo, err) { logic(self, 'recreateAccount_end', { type: accountInfo.type, id: accountInfo.id, error: err }); if (--waitingCount === 0) { logic(self, 'configMigrating_end'); this._initFromConfig(); callAfterBigBang(); } }; for (i = 0; i < lazyCarryover.accountInfos.length; i++) { var accountInfo = lazyCarryover.accountInfos[i]; logic(this, 'recreateAccount_begin', { type: accountInfo.type, id: accountInfo.id, error: null }); $acctcommon.recreateAccount(self, oldVersion, accountInfo, accountRecreated.bind(this, accountInfo)); } return; } else { logic(self, 'configCreated', { config: self.config }); } } self._initFromConfig(); callAfterBigBang(); }.bind(this)); } exports.MailUniverse = MailUniverse; MailUniverse.prototype = { _enableCircularLogging: function () { this._logBacklog = []; logic.on('event', event => { this._logBacklog.push(event.toJSON()); var oldestTimeAllowed = Date.now() - MAX_LOG_BACKLOG_MS; while (this._logBacklog.length && this._logBacklog[0].time < oldestTimeAllowed) { this._logBacklog.shift(); } }); }, createLogBacklogRep: function () { return { type: 'logic', events: this._logBacklog }; }, dumpLogToDeviceStorage: function () { var sendMessage = $router.registerCallbackType('devicestorage'); try { var blob = new Blob([JSON.stringify(this.createLogBacklogRep())], { type: 'application/json', endings: 'transparent' }); var filename = 'gem-log-' + Date.now() + '.json'; sendMessage('save', [ 'sdcard', blob, filename ], function (success, err, savedFile) { if (success) console.log('saved log to "sdcard" devicestorage:', savedFile); else console.error('failed to save log to', filename); }); } catch (ex) { console.error('Problem dumping log to device storage:', ex, '\n', ex.stack); } }, _initFromConfig: function () { this._cronSync = new $cronsync.CronSync(this); }, exposeConfigForClient: function () { return { debugLogging: this.config.debugLogging }; }, modifyConfig: function (changes) { for (var key in changes) { var val = changes[key]; switch (key) { case 'debugLogging': break; default: continue; } this.config[key] = val; } this._db.saveConfig(this.config); this.__notifyConfig(); }, __notifyConfig: function () { var config = this.exposeConfigForClient(); for (var iBridge = 0; iBridge < this._bridges.length; iBridge++) { var bridge = this._bridges[iBridge]; bridge.notifyConfig(config); } }, setInteractive: function () { this._mode = 'interactive'; }, _onConnectionChange: function (isOnline) { var wasOnline = this.online; this.online = this._testModeFakeNavigator ? this._testModeFakeNavigator.onLine : isOnline; console.log('Email knows that it is:', this.online ? 'online' : 'offline', 'and previously was:', wasOnline ? 'online' : 'offline'); this.minimizeNetworkUsage = true; this.networkCostsMoney = true; if (!wasOnline && this.online) { for (var iAcct = 0; iAcct < this.accounts.length; iAcct++) { this._resumeOpProcessingForAccount(this.accounts[iAcct]); } } }, _dispatchLocalOpForAccount: function (account, op) { var queues = this._opsByAccount[account.id]; queues.active = true; var mode; switch (op.lifecycle) { case 'do': mode = 'local_do'; op.localStatus = 'doing'; break; case 'undo': mode = 'local_undo'; op.localStatus = 'undoing'; break; default: throw new Error('Illegal lifecycle state for local op'); } account.runOp(op, mode, this._localOpCompleted.bind(this, account, op)); }, _dispatchServerOpForAccount: function (account, op) { var queues = this._opsByAccount[account.id]; queues.active = true; var mode = op.lifecycle; if (op.serverStatus === 'check') mode = 'check'; op.serverStatus = mode + 'ing'; account.runOp(op, mode, this._serverOpCompleted.bind(this, account, op)); }, _resumeOpProcessingForAccount: function (account) { var queues = this._opsByAccount[account.id]; if (!account.enabled) return; if (!queues.local.length && queues.server.length && (queues.server[0].serverStatus !== 'doing' && queues.server[0].serverStatus !== 'undoing')) { var op = queues.server[0]; this._dispatchServerOpForAccount(account, op); } }, areServerJobsWaiting: function (account) { var queues = this._opsByAccount[account.id]; if (!account.enabled) { return false; } return !!queues.server.length; }, registerBridge: function (mailBridge) { this._bridges.push(mailBridge); }, unregisterBridge: function (mailBridge) { var idx = this._bridges.indexOf(mailBridge); if (idx !== -1) this._bridges.splice(idx, 1); }, learnAboutAccount: function (details) { var configurator = new $acctcommon.Autoconfigurator(); return configurator.learnAboutAccount(details); }, tryToCreateAccount: function mu_tryToCreateAccount(userDetails, domainInfo, callback) { if (!this.online) { callback('offline'); return; } if (!userDetails.forceCreate) { for (var i = 0; i < this.accounts.length; i++) { if (userDetails.emailAddress === this.accounts[i].identities[0].address) { callback('user-account-exists'); return; } } } if (domainInfo) { $acctcommon.tryToManuallyCreateAccount(this, userDetails, domainInfo, callback); } else { var configurator = new $acctcommon.Autoconfigurator(); configurator.tryToCreateAccount(this, userDetails, callback); } }, deleteAccount: function (accountId) { var savedEx = null; var account = this._accountsById[accountId]; try { account.accountDeleted(); } catch (ex) { savedEx = ex; } this._db.deleteAccount(accountId); delete this._accountsById[accountId]; var idx = this.accounts.indexOf(account); this.accounts.splice(idx, 1); for (var i = 0; i < account.identities.length; i++) { var identity = account.identities[i]; idx = this.identities.indexOf(identity); this.identities.splice(idx, 1); delete this._identitiesById[identity.id]; } delete this._opsByAccount[accountId]; delete this._opCompletionListenersByAccount[accountId]; this.__notifyRemovedAccount(accountId); if (savedEx) throw savedEx; }, saveAccountDef: function (accountDef, folderInfo, callback) { this._db.saveAccountDef(this.config, accountDef, folderInfo, callback); var account = this.getAccountForAccountId(accountDef.id); if (this._cronSync) { this._cronSync.ensureSync(); } if (account) this.__notifyModifiedAccount(account); }, _loadAccount: function mu__loadAccount(accountDef, folderInfo, receiveProtoConn, callback) { $acctcommon.accountTypeToClass(accountDef.type, function (constructor) { if (!constructor) { logic(this, 'badAccountType', { type: accountDef.type }); return; } var account = new constructor(this, accountDef, folderInfo, this._db, receiveProtoConn); this.accounts.push(account); this._accountsById[account.id] = account; this._opsByAccount[account.id] = { active: false, local: [], server: [], deferred: [] }; this._opCompletionListenersByAccount[account.id] = null; for (var iIdent = 0; iIdent < accountDef.identities.length; iIdent++) { var identity = accountDef.identities[iIdent]; this.identities.push(identity); this._identitiesById[identity.id] = identity; } this.__notifyAddedAccount(account); var timeSinceLastFolderSync = Date.now() - account.meta.lastFolderSyncAt; if (timeSinceLastFolderSync >= $syncbase.SYNC_FOLDER_LIST_EVERY_MS) this.syncFolderList(account); for (var i = 0; i < account.mutations.length; i++) { var op = account.mutations[i]; if (op.lifecycle !== 'done' && op.lifecycle !== 'undone' && op.lifecycle !== 'moot') { op.serverStatus = 'check'; this._queueAccountOp(account, op); } } account.upgradeFolderStoragesIfNeeded(); callback(account); }.bind(this)); }, __reportAccountProblem: function (account, problem, whichSide) { var suppress = false; if (account.problems.indexOf(problem) !== -1) { suppress = true; } logic(this, 'reportProblem', { problem: problem, suppress: suppress, accountId: account.id }); if (suppress) { return; } account.problems.push(problem); account.enabled = false; this.__notifyModifiedAccount(account); switch (problem) { case 'bad-user-or-pass': case 'needs-oauth-reauth': case 'bad-address': case 'imap-disabled': this.__notifyBadLogin(account, problem, whichSide); break; } }, __removeAccountProblem: function (account, problem) { var idx = account.problems.indexOf(problem); if (idx === -1) return; account.problems.splice(idx, 1); account.enabled = account.problems.length === 0; this.__notifyModifiedAccount(account); if (account.enabled) this._resumeOpProcessingForAccount(account); }, clearAccountProblems: function (account) { logic(this, 'clearAccountProblems', { accountId: account.id }); account.enabled = true; account.problems = []; this._resumeOpProcessingForAccount(account); }, __notifyBadLogin: makeBridgeFn('notifyBadLogin'), __notifyAddedAccount: makeBridgeFn('notifyAccountAdded'), __notifyModifiedAccount: makeBridgeFn('notifyAccountModified'), __notifyRemovedAccount: makeBridgeFn('notifyAccountRemoved'), __notifyAddedFolder: makeBridgeFn('notifyFolderAdded'), __notifyModifiedFolder: makeBridgeFn('notifyFolderModified'), __notifyRemovedFolder: makeBridgeFn('notifyFolderRemoved'), __notifyModifiedBody: makeBridgeFn('notifyBodyModified'), __notifyStartedCronSync: makeBridgeFn('notifyCronSyncStart'), __notifyStoppedCronSync: makeBridgeFn('notifyCronSyncStop'), __notifyBackgroundSendStatus: makeBridgeFn('notifyBackgroundSendStatus'), saveUniverseState: function (callback) { var curTrans = null; var latch = $allback.latch(); logic(this, 'saveUniverseState_begin'); for (var iAcct = 0; iAcct < this.accounts.length; iAcct++) { var account = this.accounts[iAcct]; curTrans = account.saveAccountState(curTrans, latch.defer(account.id), 'saveUniverse'); } latch.then(function () { logic(this, 'saveUniverseState_end'); if (callback) { callback(); } ; }.bind(this)); }, shutdown: function (callback) { var waitCount = this.accounts.length; function accountShutdownCompleted() { if (--waitCount === 0) callback(); } for (var iAcct = 0; iAcct < this.accounts.length; iAcct++) { var account = this.accounts[iAcct]; account.shutdown(callback ? accountShutdownCompleted : null); } if (this._cronSync) { this._cronSync.shutdown(); } this._db.close(); if (!this.accounts.length) callback(); }, getAccountForAccountId: function mu_getAccountForAccountId(accountId) { return this._accountsById[accountId]; }, getAccountForFolderId: function mu_getAccountForFolderId(folderId) { var accountId = folderId.substring(0, folderId.indexOf('/')), account = this._accountsById[accountId]; return account; }, getAccountForMessageSuid: function mu_getAccountForMessageSuid(messageSuid) { var accountId = messageSuid.substring(0, messageSuid.indexOf('/')), account = this._accountsById[accountId]; return account; }, getFolderStorageForFolderId: function mu_getFolderStorageForFolderId(folderId) { var account = this.getAccountForFolderId(folderId); return account.getFolderStorageForFolderId(folderId); }, getFolderStorageForMessageSuid: function mu_getFolderStorageForFolderId(messageSuid) { var folderId = messageSuid.substring(0, messageSuid.lastIndexOf('/')), account = this.getAccountForFolderId(folderId); return account.getFolderStorageForFolderId(folderId); }, getAccountForSenderIdentityId: function mu_getAccountForSenderIdentityId(identityId) { var accountId = identityId.substring(0, identityId.indexOf('/')), account = this._accountsById[accountId]; return account; }, getIdentityForSenderIdentityId: function mu_getIdentityForSenderIdentityId(identityId) { return this._identitiesById[identityId]; }, _partitionMessagesByAccount: function (messageNamers, targetAccountId) { var results = [], acctToMsgs = {}; for (var i = 0; i < messageNamers.length; i++) { var messageNamer = messageNamers[i], messageSuid = messageNamer.suid, accountId = messageSuid.substring(0, messageSuid.indexOf('/')); if (!acctToMsgs.hasOwnProperty(accountId)) { var messages = [messageNamer]; results.push({ account: this._accountsById[accountId], messages: messages, crossAccount: targetAccountId && targetAccountId !== accountId }); acctToMsgs[accountId] = messages; } else { acctToMsgs[accountId].push(messageNamer); } } return results; }, _deferOp: function (account, op) { this._opsByAccount[account.id].deferred.push(op.longtermId); if (this._deferredOpTimeout !== null) this._deferredOpTimeout = window.setTimeout(this._boundQueueDeferredOps, $syncbase.DEFERRED_OP_DELAY_MS); }, _queueDeferredOps: function () { this._deferredOpTimeout = null; if (this._mode !== 'interactive') { console.log('delaying deferred op since mode is ' + this._mode); this._deferredOpTimeout = window.setTimeout(this._boundQueueDeferredOps, $syncbase.DEFERRED_OP_DELAY_MS); return; } for (var iAccount = 0; iAccount < this.accounts.length; iAccount++) { var account = this.accounts[iAccount], queues = this._opsByAccount[account.id]; while (queues.deferred.length) { var op = queues.deferred.shift(); if (queues.server.indexOf(op) === -1 && op.lifecycle !== 'undo') this._queueAccountOp(account, op); } } }, _localOpCompleted: function (account, op, err, resultIfAny, accountSaveSuggested) { var queues = this._opsByAccount[account.id], serverQueue = queues.server, localQueue = queues.local; var removeFromServerQueue = false, completeOp = false, wasMode = 'local_' + op.localStatus.slice(0, -3); if (err) { switch (err) { case 'defer': if (++op.tryCount < $syncbase.MAX_OP_TRY_COUNT) { logic(this, 'opDeferred', { type: op.type, longtermId: op.longtermId }); this._deferOp(account, op); removeFromServerQueue = true; break; } default: logic(this, 'opGaveUp', { type: op.type, longtermId: op.longtermId }); op.lifecycle = 'moot'; op.localStatus = 'unknown'; op.serverStatus = 'moot'; removeFromServerQueue = true; completeOp = true; break; } accountSaveSuggested = false; } else { switch (op.localStatus) { case 'doing': op.localStatus = 'done'; if (op.serverStatus === 'skip') { removeFromServerQueue = true; op.serverStatus = 'n/a'; accountSaveSuggested = true; } if (op.serverStatus === 'n/a') { op.lifecycle = 'done'; completeOp = true; } break; case 'undoing': op.localStatus = 'undone'; if (op.serverStatus === 'skip') { removeFromServerQueue = true; op.serverStatus = 'n/a'; accountSaveSuggested = true; } if (op.serverStatus === 'n/a') { op.lifecycle = 'undone'; completeOp = true; } break; } } if (removeFromServerQueue) { var idx = serverQueue.indexOf(op); if (idx !== -1) serverQueue.splice(idx, 1); } localQueue.shift(); console.log('runOp_end(' + wasMode + ': ' + JSON.stringify(op).substring(0, 160) + ')\n'); logic(account, 'runOp_end', { mode: wasMode, type: op.type, error: err, op: op }); if (op._logicAsyncEvent) { if (err) { op._logicAsyncEvent.reject(err); } else { op._logicAsyncEvent.resolve(); } } var callback; if (completeOp) { if (this._opCallbacks.hasOwnProperty(op.longtermId)) { callback = this._opCallbacks[op.longtermId]; delete this._opCallbacks[op.longtermId]; } } if (accountSaveSuggested) { account.saveAccountState(null, this._startNextOp.bind(this, account, callback, op, err, resultIfAny), 'localOp:' + op.type); return; } this._startNextOp(account, callback, op, err, resultIfAny); }, _serverOpCompleted: function (account, op, err, resultIfAny, accountSaveSuggested) { var queues = this._opsByAccount[account.id], serverQueue = queues.server, localQueue = queues.local; var scope = logic.subscope(this, { type: op.type, longtermId: op.longtermId }); if (serverQueue[0] !== op) logic(scope, 'opInvariantFailure'); var maybeRetry = false; var consumeOp = true; var completeOp = true; var wasMode = op.serverStatus.slice(0, -3); if (err) { switch (err) { case 'defer': if (++op.tryCount < $syncbase.MAX_OP_TRY_COUNT) { if (op.serverStatus === 'doing' && op.lifecycle === 'do') { logic(scope, 'opDeferred'); this._deferOp(account, op); } completeOp = false; } else { op.lifecycle = 'moot'; op.serverStatus = 'moot'; } break; case 'aborted-retry': op.tryCount++; maybeRetry = true; break; default: op.tryCount += $syncbase.OP_UNKNOWN_ERROR_TRY_COUNT_INCREMENT; maybeRetry = true; break; case 'failure-give-up': logic(scope, 'opGaveUp'); op.lifecycle = 'moot'; op.serverStatus = 'moot'; break; case 'moot': logic(scope, 'opMooted'); op.lifecycle = 'moot'; op.serverStatus = 'moot'; break; } } else { switch (op.serverStatus) { case 'checking': switch (resultIfAny) { case 'checked-notyet': case 'coherent-notyet': op.serverStatus = null; break; case 'idempotent': if (op.lifecycle === 'do' || op.lifecycle === 'done') op.serverStatus = null; else op.serverStatus = 'done'; break; case 'happened': op.serverStatus = 'done'; break; case 'moot': op.lifecycle = 'moot'; op.serverStatus = 'moot'; break; case 'bailed': logic(scope, 'opDeferred'); this._deferOp(account, op); completeOp = false; break; } break; case 'doing': op.serverStatus = 'done'; if (op.lifecycle === 'do') op.lifecycle = 'done'; break; case 'undoing': op.serverStatus = 'undone'; if (op.lifecycle === 'undo') op.lifecycle = 'undone'; break; } if (op.lifecycle === 'do' || op.lifecycle === 'undo') consumeOp = false; } if (maybeRetry) { if (op.tryCount < $syncbase.MAX_OP_TRY_COUNT) { op.serverStatus = 'check'; consumeOp = false; } else { logic(scope, 'opTryLimitReached'); op.lifecycle = 'moot'; op.serverStatus = 'moot'; } } if (consumeOp) serverQueue.shift(); console.log('runOp_end(' + wasMode + ': ' + JSON.stringify(op).substring(0, 160) + ')\n'); logic(account, 'runOp_end', { mode: wasMode, type: op.type, error: err, op: op }); if (op._logicAsyncEvent) { if (err) { op._logicAsyncEvent.reject(err); } else { op._logicAsyncEvent.resolve(); } } if (accountSaveSuggested) account._saveAccountIsImminent = true; var callback; if (completeOp) { if (this._opCallbacks.hasOwnProperty(op.longtermId)) { callback = this._opCallbacks[op.longtermId]; delete this._opCallbacks[op.longtermId]; } if (accountSaveSuggested) { account._saveAccountIsImminent = false; account.saveAccountState(null, this._startNextOp.bind(this, account, callback, op, err, resultIfAny), 'serverOp:' + op.type); return; } } this._startNextOp(account, callback, op, err, resultIfAny); }, _startNextOp: function (account, callback, lastOp, err, result) { var queues = this._opsByAccount[account.id], serverQueue = queues.server, localQueue = queues.local; var op; if (callback) { try { callback(err, result, account, lastOp); } catch (ex) { console.log(ex.message, ex.stack); logic(this, 'opCallbackErr', { type: lastOp.type }); } } queues.active = false; if (localQueue.length) { op = localQueue[0]; this._dispatchLocalOpForAccount(account, op); } else if (serverQueue.length && this.online && account.enabled) { op = serverQueue[0]; this._dispatchServerOpForAccount(account, op); } else { if (this._opCompletionListenersByAccount[account.id]) { this._opCompletionListenersByAccount[account.id](account); this._opCompletionListenersByAccount[account.id] = null; } logic(this, 'allOpsCompleted', { accountId: account.id }); account.allOperationsCompleted(); } }, _queueAccountOp: function (account, op, optionalCallback) { var queues = this._opsByAccount[account.id]; console.log('queueOp', account.id, op.type, 'pre-queues:', 'local:', queues.local.length, 'server:', queues.server.length); if (op.longtermId === null) { op.longtermId = account.id + '/' + $a64.encodeInt(account.meta.nextMutationNum++); account.mutations.push(op); while (account.mutations.length > MAX_MUTATIONS_FOR_UNDO && account.mutations[0].lifecycle === 'done' || account.mutations[0].lifecycle === 'undone' || account.mutations[0].lifecycle === 'moot') { account.mutations.shift(); } } else if (op.longtermId === 'session') { op.longtermId = account.id + '/' + $a64.encodeInt(account.meta.nextMutationNum++); } if (optionalCallback) this._opCallbacks[op.longtermId] = optionalCallback; if (!this._testModeDisablingLocalOps && (op.lifecycle === 'do' && op.localStatus === null || op.lifecycle === 'undo' && op.localStatus !== 'undone' && op.localStatus !== 'unknown')) queues.local.push(op); if (op.serverStatus !== 'n/a' && op.serverStatus !== 'moot') queues.server.push(op); if (queues.active) { } else if (queues.local.length) { if (queues.local.length === 1 && queues.local[0] === op) this._dispatchLocalOpForAccount(account, op); } else if (queues.server.length === 1 && queues.server[0] === op && this.online && account.enabled) { this._dispatchServerOpForAccount(account, op); } return op.longtermId; }, waitForAccountOps: function (account, callback) { var queues = this._opsByAccount[account.id]; if (!queues.active && queues.local.length === 0 && (queues.server.length === 0 || !this.online || !account.enabled)) callback(); else this._opCompletionListenersByAccount[account.id] = callback; }, syncFolderList: function (account, callback) { this._queueAccountOp(account, { type: 'syncFolderList', longtermId: 'session', lifecycle: 'do', localStatus: 'done', serverStatus: null, tryCount: 0, humanOp: 'syncFolderList' }, callback); }, purgeExcessMessages: function (account, folderId, callback) { this._queueAccountOp(account, { type: 'purgeExcessMessages', longtermId: 'session', lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'purgeExcessMessages', folderId: folderId }, callback); }, downloadMessageBodyReps: function (suid, date, callback) { var account = this.getAccountForMessageSuid(suid); this._queueAccountOp(account, { type: 'downloadBodyReps', longtermId: 'session', lifecycle: 'do', localStatus: 'done', serverStatus: null, tryCount: 0, humanOp: 'downloadBodyReps', messageSuid: suid, messageDate: date }, callback); }, downloadBodies: function (messages, options, callback) { if (typeof options === 'function') { callback = options; options = null; } var self = this; var pending = 0; function next() { if (!--pending) { callback(); } } this._partitionMessagesByAccount(messages, null).forEach(function (x) { pending++; self._queueAccountOp(x.account, { type: 'downloadBodies', longtermId: 'session', lifecycle: 'do', localStatus: 'done', serverStatus: null, tryCount: 0, humanOp: 'downloadBodies', messages: x.messages, options: options }, next); }); }, downloadMessageAttachments: function (messageSuid, messageDate, relPartIndices, attachmentIndices, registerAttachments, callback) { var account = this.getAccountForMessageSuid(messageSuid); var longtermId = this._queueAccountOp(account, { type: 'download', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: null, tryCount: 0, humanOp: 'download', messageSuid: messageSuid, messageDate: messageDate, relPartIndices: relPartIndices, attachmentIndices: attachmentIndices, registerAttachments: registerAttachments }, callback); }, modifyMessageTags: function (humanOp, messageSuids, addTags, removeTags) { var self = this, longtermIds = []; this._partitionMessagesByAccount(messageSuids, null).forEach(function (x) { var longtermId = self._queueAccountOp(x.account, { type: 'modtags', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: null, tryCount: 0, humanOp: humanOp, messages: x.messages, addTags: addTags, removeTags: removeTags, progress: 0 }); longtermIds.push(longtermId); }); return longtermIds; }, moveMessages: function (messageSuids, targetFolderId, callback) { var self = this, longtermIds = [], targetFolderAccount = this.getAccountForFolderId(targetFolderId); var latch = $allback.latch(); this._partitionMessagesByAccount(messageSuids, null).forEach(function (x, i) { if (x.account !== targetFolderAccount) throw new Error('cross-account moves not currently supported!'); var targetStorage = targetFolderAccount.getFolderStorageForFolderId(targetFolderId); var isLocalOnly = targetStorage.isLocalOnly; for (var j = 0; j < x.messages.length && isLocalOnly; j++) { var sourceStorage = self.getFolderStorageForMessageSuid(x.messages[j].suid); if (!sourceStorage.isLocalOnly) { isLocalOnly = false; } } var longtermId = self._queueAccountOp(x.account, { type: 'move', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: isLocalOnly ? 'n/a' : null, tryCount: 0, humanOp: 'move', messages: x.messages, targetFolder: targetFolderId }, latch.defer(i)); longtermIds.push(longtermId); }); latch.then(function (results) { var combinedMoveMap = {}; for (var key in results) { var moveMap = results[key][1]; for (var k in moveMap) { combinedMoveMap[k] = moveMap[k]; } } callback && callback(null, combinedMoveMap); }); return longtermIds; }, deleteMessages: function (messageSuids) { var self = this, longtermIds = []; this._partitionMessagesByAccount(messageSuids, null).forEach(function (x) { var longtermId = self._queueAccountOp(x.account, { type: 'delete', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: null, tryCount: 0, humanOp: 'delete', messages: x.messages }); longtermIds.push(longtermId); }); return longtermIds; }, appendMessages: function (folderId, messages, callback) { var account = this.getAccountForFolderId(folderId); var longtermId = this._queueAccountOp(account, { type: 'append', longtermId: 'session', lifecycle: 'do', localStatus: 'done', serverStatus: null, tryCount: 0, humanOp: 'append', messages: messages, folderId: folderId }, callback); return [longtermId]; }, saveSentDraft: function (folderId, sentSafeHeader, sentSafeBody, callback) { var account = this.getAccountForMessageSuid(sentSafeHeader.suid); var longtermId = this._queueAccountOp(account, { type: 'saveSentDraft', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'saveSentDraft', folderId: folderId, headerInfo: sentSafeHeader, bodyInfo: sentSafeBody }, callback); return [longtermId]; }, attachBlobToDraft: function (account, existingNamer, attachmentDef, callback) { this._queueAccountOp(account, { type: 'attachBlobToDraft', longtermId: 'session', lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'attachBlobToDraft', existingNamer: existingNamer, attachmentDef: attachmentDef }, callback); }, detachAttachmentFromDraft: function (account, existingNamer, attachmentIndex, callback) { this._queueAccountOp(account, { type: 'detachAttachmentFromDraft', longtermId: 'session', lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'detachAttachmentFromDraft', existingNamer: existingNamer, attachmentIndex: attachmentIndex }, callback); }, saveDraft: function (account, existingNamer, draftRep, callback) { var draftsFolderMeta = account.getFirstFolderWithType('localdrafts'); var draftsFolderStorage = account.getFolderStorageForFolderId(draftsFolderMeta.id); var newId = draftsFolderStorage._issueNewHeaderId(); var newDraftInfo = { id: newId, suid: draftsFolderStorage.folderId + '/' + newId, date: $date.NOW() }; this._queueAccountOp(account, { type: 'saveDraft', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'saveDraft', existingNamer: existingNamer, newDraftInfo: newDraftInfo, draftRep: draftRep }, callback); return { suid: newDraftInfo.suid, date: newDraftInfo.date }; }, sendOutboxMessages: function (account, opts, callback) { opts = opts || {}; console.log('outbox: sendOutboxMessages(', JSON.stringify(opts), ')'); if (!this.online) { this.notifyOutboxSyncDone(account); } this._queueAccountOp(account, { type: 'sendOutboxMessages', longtermId: 'session', lifecycle: 'do', localStatus: 'n/a', serverStatus: null, tryCount: 0, beforeMessage: opts.beforeMessage, emitNotifications: opts.emitNotifications, humanOp: 'sendOutboxMessages' }, callback); }, notifyOutboxSyncDone: function (account) { this.__notifyBackgroundSendStatus({ accountId: account.id, state: 'syncDone' }); }, setOutboxSyncEnabled: function (account, enabled, callback) { this._queueAccountOp(account, { type: 'setOutboxSyncEnabled', longtermId: 'session', lifecycle: 'do', localStatus: null, serverStatus: 'n/a', outboxSyncEnabled: enabled, tryCount: 0, humanOp: 'setOutboxSyncEnabled' }, callback); }, deleteDraft: function (account, messageNamer, callback) { this._queueAccountOp(account, { type: 'deleteDraft', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'deleteDraft', messageNamer: messageNamer }, callback); }, createFolder: function (accountId, parentFolderId, folderName, folderType, containOtherFolders, callback) { var account = this.getAccountForAccountId(accountId); var longtermId = this._queueAccountOp(account, { type: 'createFolder', longtermId: null, lifecycle: 'do', localStatus: null, serverStatus: null, tryCount: 0, humanOp: 'createFolder', parentFolderId: parentFolderId, folderName: folderName, folderType: folderType, containOtherFolders: containOtherFolders }, callback); return [longtermId]; }, undoMutation: function (longtermIds) { for (var i = 0; i < longtermIds.length; i++) { var longtermId = longtermIds[i], account = this.getAccountForFolderId(longtermId), queues = this._opsByAccount[account.id]; for (var iOp = 0; iOp < account.mutations.length; iOp++) { var op = account.mutations[iOp]; if (op.longtermId === longtermId) { if (op.lifecycle === 'undo' || op.lifecycle === 'undone') { continue; } if (op.lifecycle === 'done') { op.lifecycle = 'undo'; this._queueAccountOp(account, op); continue; } var idx = queues.local.indexOf(op); if (idx !== -1) { op.lifecycle = 'undone'; queues.local.splice(idx, 1); continue; } op.lifecycle = 'undo'; this._queueAccountOp(account, op); } } } }, performFolderUpgrade: function (folderId, callback) { var account = this.getAccountForFolderId(folderId); this._queueAccountOp(account, { type: 'upgradeDB', longtermId: 'session', lifecycle: 'do', localStatus: null, serverStatus: 'n/a', tryCount: 0, humanOp: 'append', folderId: folderId }, callback); } }; }); ; define('worker-setup', [ './worker-router', './mailbridge', 'logic', './mailuniverse', 'exports' ], function ($router, $mailbridge, logic, $mailuniverse, exports) { 'use strict'; var routerBridgeMaker = $router.registerInstanceType('bridge'); var bridgeUniqueIdentifier = 0; function createBridgePair(universe) { var uid = bridgeUniqueIdentifier++; var TMB = new $mailbridge.MailBridge(universe); var routerInfo = routerBridgeMaker.register(function (data) { TMB.__receiveMessage(data.msg); }); var sendMessage = routerInfo.sendMessage; TMB.__sendMessage = function (msg) { logic(TMB, 'send', { type: msg.type, msg: msg }); sendMessage(null, msg); }; TMB.__sendMessage({ type: 'hello', config: universe.exposeConfigForClient() }); } var universe = null; function onUniverse() { createBridgePair(universe); console.log('Mail universe/bridge created and notified!'); } var sendControl = $router.registerSimple('control', function (data) { var args = data.args; switch (data.cmd) { case 'hello': universe = new $mailuniverse.MailUniverse(onUniverse, args[0]); break; case 'online': case 'offline': universe._onConnectionChange(args[0]); break; } }); sendControl('hello'); }); (function (root, factory) { if (typeof define === 'function' && define.amd) { define('bleach/css-parser/tokenizer', ['exports'], factory); } else if (typeof exports !== 'undefined') { factory(exports); } else { factory(root); } }(this, function (exports) { var between = function (num, first, last) { return num >= first && num <= last; }; function digit(code) { return between(code, 48, 57); } function hexdigit(code) { return digit(code) || between(code, 65, 70) || between(code, 97, 102); } function uppercaseletter(code) { return between(code, 65, 90); } function lowercaseletter(code) { return between(code, 97, 122); } function letter(code) { return uppercaseletter(code) || lowercaseletter(code); } function nonascii(code) { return code >= 160; } function namestartchar(code) { return letter(code) || nonascii(code) || code == 95; } function namechar(code) { return namestartchar(code) || digit(code) || code == 45; } function nonprintable(code) { return between(code, 0, 8) || between(code, 14, 31) || between(code, 127, 159); } function newline(code) { return code == 10 || code == 12; } function whitespace(code) { return newline(code) || code == 9 || code == 32; } function badescape(code) { return newline(code) || isNaN(code); } var maximumallowedcodepoint = 1114111; function tokenize(str, options) { if (options == undefined) options = { transformFunctionWhitespace: false, scientificNotation: false }; var i = -1; var tokens = []; var state = 'data'; var code; var currtoken; var line = 0; var column = 0; var lastLineLength = 0; var incrLineno = function () { line += 1; lastLineLength = column; column = 0; }; var locStart = { line: line, column: column }; var next = function (num) { if (num === undefined) num = 1; return str.charCodeAt(i + num); }; var consume = function (num) { if (num === undefined) num = 1; i += num; code = str.charCodeAt(i); if (newline(code)) incrLineno(); else column += num; return true; }; var reconsume = function () { i -= 1; if (newline(code)) { line -= 1; column = lastLineLength; } else { column -= 1; } locStart.line = line; locStart.column = column; return true; }; var eof = function () { return i >= str.length; }; var donothing = function () { }; var emit = function (token) { if (token) { token.finish(); } else { token = currtoken.finish(); } if (options.loc === true) { token.loc = {}; token.loc.start = { line: locStart.line, column: locStart.column, idx: locStart.idx }; locStart = { line: line, column: column, idx: i }; token.loc.end = locStart; } tokens.push(token); currtoken = undefined; return true; }; var create = function (token) { currtoken = token; return true; }; var parseerror = function () { return true; }; var catchfire = function (msg) { return true; }; var switchto = function (newstate) { state = newstate; return true; }; var consumeEscape = function () { consume(); if (hexdigit(code)) { var digits = []; for (var total = 0; total < 6; total++) { if (hexdigit(code)) { digits.push(code); consume(); } else { break; } } var value = parseInt(digits.map(String.fromCharCode).join(''), 16); if (value > maximumallowedcodepoint) value = 65533; if (!whitespace(code)) reconsume(); return value; } else { return code; } }; for (;;) { if (i > str.length * 2) return 'I\'m infinite-looping!'; consume(); switch (state) { case 'data': if (whitespace(code)) { emit(new WhitespaceToken()); while (whitespace(next())) consume(); } else if (code == 34) switchto('double-quote-string'); else if (code == 35) switchto('hash'); else if (code == 39) switchto('single-quote-string'); else if (code == 40) emit(new OpenParenToken()); else if (code == 41) emit(new CloseParenToken()); else if (code == 43) { if (digit(next()) || next() == 46 && digit(next(2))) switchto('number') && reconsume(); else emit(new DelimToken(code)); } else if (code == 45) { if (next(1) == 45 && next(2) == 62) consume(2) && emit(new CDCToken()); else if (digit(next()) || next(1) == 46 && digit(next(2))) switchto('number') && reconsume(); else switchto('ident') && reconsume(); } else if (code == 46) { if (digit(next())) switchto('number') && reconsume(); else emit(new DelimToken(code)); } else if (code == 47) { if (next() == 42) consume() && switchto('comment'); else emit(new DelimToken(code)); } else if (code == 58) emit(new ColonToken()); else if (code == 59) emit(new SemicolonToken()); else if (code == 60) { if (next(1) == 33 && next(2) == 45 && next(3) == 45) consume(3) && emit(new CDOToken()); else emit(new DelimToken(code)); } else if (code == 64) switchto('at-keyword'); else if (code == 91) emit(new OpenSquareToken()); else if (code == 92) { if (badescape(next())) parseerror() && emit(new DelimToken(code)); else switchto('ident') && reconsume(); } else if (code == 93) emit(new CloseSquareToken()); else if (code == 123) emit(new OpenCurlyToken()); else if (code == 125) emit(new CloseCurlyToken()); else if (digit(code)) switchto('number') && reconsume(); else if (code == 85 || code == 117) { if (next(1) == 43 && hexdigit(next(2))) consume() && switchto('unicode-range'); else switchto('ident') && reconsume(); } else if (namestartchar(code)) switchto('ident') && reconsume(); else if (eof()) { emit(new EOFToken()); return tokens; } else emit(new DelimToken(code)); break; case 'double-quote-string': if (currtoken == undefined) create(new StringToken()); if (code == 34) emit() && switchto('data'); else if (eof()) parseerror() && emit() && switchto('data') && reconsume(); else if (newline(code)) parseerror() && emit(new BadStringToken()) && switchto('data') && reconsume(); else if (code == 92) { if (badescape(next())) parseerror() && emit(new BadStringToken()) && switchto('data'); else if (newline(next())) consume(); else currtoken.append(consumeEscape()); } else currtoken.append(code); break; case 'single-quote-string': if (currtoken == undefined) create(new StringToken()); if (code == 39) emit() && switchto('data'); else if (eof()) parseerror() && emit() && switchto('data'); else if (newline(code)) parseerror() && emit(new BadStringToken()) && switchto('data') && reconsume(); else if (code == 92) { if (badescape(next())) parseerror() && emit(new BadStringToken()) && switchto('data'); else if (newline(next())) consume(); else currtoken.append(consumeEscape()); } else currtoken.append(code); break; case 'hash': if (namechar(code)) create(new HashToken(code)) && switchto('hash-rest'); else if (code == 92) { if (badescape(next())) parseerror() && emit(new DelimToken(35)) && switchto('data') && reconsume(); else create(new HashToken(consumeEscape())) && switchto('hash-rest'); } else emit(new DelimToken(35)) && switchto('data') && reconsume(); break; case 'hash-rest': if (namechar(code)) currtoken.append(code); else if (code == 92) { if (badescape(next())) parseerror() && emit() && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else emit() && switchto('data') && reconsume(); break; case 'comment': if (code == 42) { if (next() == 47) consume() && switchto('data'); else donothing(); } else if (eof()) parseerror() && switchto('data') && reconsume(); else donothing(); break; case 'at-keyword': if (code == 45) { if (namestartchar(next())) create(new AtKeywordToken(45)) && switchto('at-keyword-rest'); else if (next(1) == 92 && !badescape(next(2))) create(new AtKeywordtoken(45)) && switchto('at-keyword-rest'); else parseerror() && emit(new DelimToken(64)) && switchto('data') && reconsume(); } else if (namestartchar(code)) create(new AtKeywordToken(code)) && switchto('at-keyword-rest'); else if (code == 92) { if (badescape(next())) parseerror() && emit(new DelimToken(35)) && switchto('data') && reconsume(); else create(new AtKeywordToken(consumeEscape())) && switchto('at-keyword-rest'); } else emit(new DelimToken(64)) && switchto('data') && reconsume(); break; case 'at-keyword-rest': if (namechar(code)) currtoken.append(code); else if (code == 92) { if (badescape(next())) parseerror() && emit() && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else emit() && switchto('data') && reconsume(); break; case 'ident': if (code == 45) { if (namestartchar(next())) create(new IdentifierToken(code)) && switchto('ident-rest'); else if (next(1) == 92 && !badescape(next(2))) create(new IdentifierToken(code)) && switchto('ident-rest'); else emit(new DelimToken(45)) && switchto('data'); } else if (namestartchar(code)) create(new IdentifierToken(code)) && switchto('ident-rest'); else if (code == 92) { if (badescape(next())) parseerror() && switchto('data') && reconsume(); else create(new IdentifierToken(consumeEscape())) && switchto('ident-rest'); } else catchfire('Hit the generic \'else\' clause in ident state.') && switchto('data') && reconsume(); break; case 'ident-rest': if (namechar(code)) currtoken.append(code); else if (code == 92) { if (badescape(next())) parseerror() && emit() && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else if (code == 40) { if (currtoken.ASCIImatch('url')) switchto('url'); else emit(new FunctionToken(currtoken)) && switchto('data'); } else if (whitespace(code) && options.transformFunctionWhitespace) switchto('transform-function-whitespace') && reconsume(); else emit() && switchto('data') && reconsume(); break; case 'transform-function-whitespace': if (whitespace(next())) donothing(); else if (code == 40) emit(new FunctionToken(currtoken)) && switchto('data'); else emit() && switchto('data') && reconsume(); break; case 'number': create(new NumberToken()); if (code == 45) { if (digit(next())) consume() && currtoken.append([ 45, code ]) && switchto('number-rest'); else if (next(1) == 46 && digit(next(2))) consume(2) && currtoken.append([ 45, 46, code ]) && switchto('number-fraction'); else switchto('data') && reconsume(); } else if (code == 43) { if (digit(next())) consume() && currtoken.append([ 43, code ]) && switchto('number-rest'); else if (next(1) == 46 && digit(next(2))) consume(2) && currtoken.append([ 43, 46, code ]) && switchto('number-fraction'); else switchto('data') && reconsume(); } else if (digit(code)) currtoken.append(code) && switchto('number-rest'); else if (code == 46) { if (digit(next())) consume() && currtoken.append([ 46, code ]) && switchto('number-fraction'); else switchto('data') && reconsume(); } else switchto('data') && reconsume(); break; case 'number-rest': if (digit(code)) currtoken.append(code); else if (code == 46) { if (digit(next())) consume() && currtoken.append([ 46, code ]) && switchto('number-fraction'); else emit() && switchto('data') && reconsume(); } else if (code == 37) emit(new PercentageToken(currtoken)) && switchto('data'); else if (code == 69 || code == 101) { if (digit(next())) consume() && currtoken.append([ 37, code ]) && switchto('sci-notation'); else if ((next(1) == 43 || next(1) == 45) && digit(next(2))) currtoken.append([ 37, next(1), next(2) ]) && consume(2) && switchto('sci-notation'); else create(new DimensionToken(currtoken, code)) && switchto('dimension'); } else if (code == 45) { if (namestartchar(next())) consume() && create(new DimensionToken(currtoken, [ 45, code ])) && switchto('dimension'); else if (next(1) == 92 && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume(); else if (next(1) == 92) consume() && create(new DimensionToken(currtoken, [ 45, consumeEscape() ])) && switchto('dimension'); else emit() && switchto('data') && reconsume(); } else if (namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension'); else if (code == 92) { if (badescape(next)) parseerror() && emit() && switchto('data') && reconsume(); else create(new DimensionToken(currtoken, consumeEscape)) && switchto('dimension'); } else emit() && switchto('data') && reconsume(); break; case 'number-fraction': currtoken.type = 'number'; if (digit(code)) currtoken.append(code); else if (code == 37) emit(new PercentageToken(currtoken)) && switchto('data'); else if (code == 69 || code == 101) { if (digit(next())) consume() && currtoken.append([ 101, code ]) && switchto('sci-notation'); else if ((next(1) == 43 || next(1) == 45) && digit(next(2))) currtoken.append([ 101, next(1), next(2) ]) && consume(2) && switchto('sci-notation'); else create(new DimensionToken(currtoken, code)) && switchto('dimension'); } else if (code == 45) { if (namestartchar(next())) consume() && create(new DimensionToken(currtoken, [ 45, code ])) && switchto('dimension'); else if (next(1) == 92 && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume(); else if (next(1) == 92) consume() && create(new DimensionToken(currtoken, [ 45, consumeEscape() ])) && switchto('dimension'); else emit() && switchto('data') && reconsume(); } else if (namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension'); else if (code == 92) { if (badescape(next)) parseerror() && emit() && switchto('data') && reconsume(); else create(new DimensionToken(currtoken, consumeEscape())) && switchto('dimension'); } else emit() && switchto('data') && reconsume(); break; case 'dimension': if (namechar(code)) currtoken.append(code); else if (code == 92) { if (badescape(next())) parseerror() && emit() && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else emit() && switchto('data') && reconsume(); break; case 'sci-notation': currtoken.type = 'number'; if (digit(code)) currtoken.append(code); else emit() && switchto('data') && reconsume(); break; case 'url': if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (code == 34) switchto('url-double-quote'); else if (code == 39) switchto('url-single-quote'); else if (code == 41) emit(new URLToken()) && switchto('data'); else if (whitespace(code)) donothing(); else switchto('url-unquoted') && reconsume(); break; case 'url-double-quote': if (!(currtoken instanceof URLToken)) create(new URLToken()); if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (code == 34) switchto('url-end'); else if (newline(code)) parseerror() && switchto('bad-url'); else if (code == 92) { if (newline(next())) consume(); else if (badescape(next())) parseerror() && emit(new BadURLToken()) && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else currtoken.append(code); break; case 'url-single-quote': if (!(currtoken instanceof URLToken)) create(new URLToken()); if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (code == 39) switchto('url-end'); else if (newline(code)) parseerror() && switchto('bad-url'); else if (code == 92) { if (newline(next())) consume(); else if (badescape(next())) parseerror() && emit(new BadURLToken()) && switchto('data') && reconsume(); else currtoken.append(consumeEscape()); } else currtoken.append(code); break; case 'url-end': if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (whitespace(code)) donothing(); else if (code == 41) emit() && switchto('data'); else parseerror() && switchto('bad-url') && reconsume(); break; case 'url-unquoted': if (!(currtoken instanceof URLToken)) create(new URLToken()); if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (whitespace(code)) switchto('url-end'); else if (code == 41) emit() && switchto('data'); else if (code == 34 || code == 39 || code == 40 || nonprintable(code)) parseerror() && switchto('bad-url'); else if (code == 92) { if (badescape(next())) parseerror() && switchto('bad-url'); else currtoken.append(consumeEscape()); } else currtoken.append(code); break; case 'bad-url': if (eof()) parseerror() && emit(new BadURLToken()) && switchto('data'); else if (code == 41) emit(new BadURLToken()) && switchto('data'); else if (code == 92) { if (badescape(next())) donothing(); else consumeEscape(); } else donothing(); break; case 'unicode-range': var start = [code], end = [code]; for (var total = 1; total < 6; total++) { if (hexdigit(next())) { consume(); start.push(code); end.push(code); } else break; } if (next() == 63) { for (; total < 6; total++) { if (next() == 63) { consume(); start.push('0'.charCodeAt(0)); end.push('f'.charCodeAt(0)); } else break; } emit(new UnicodeRangeToken(start, end)) && switchto('data'); } else if (next(1) == 45 && hexdigit(next(2))) { consume(); consume(); end = [code]; for (var total = 1; total < 6; total++) { if (hexdigit(next())) { consume(); end.push(code); } else break; } emit(new UnicodeRangeToken(start, end)) && switchto('data'); } else emit(new UnicodeRangeToken(start)) && switchto('data'); break; default: catchfire('Unknown state \'' + state + '\''); } } } function stringFromCodeArray(arr) { return String.fromCharCode.apply(null, arr.filter(function (e) { return e; })); } function CSSParserToken(options) { return this; } CSSParserToken.prototype.finish = function () { return this; }; CSSParserToken.prototype.toString = function () { return this.tokenType; }; CSSParserToken.prototype.toJSON = function () { return this.toString(); }; function BadStringToken() { return this; } BadStringToken.prototype = new CSSParserToken(); BadStringToken.prototype.tokenType = 'BADSTRING'; function BadURLToken() { return this; } BadURLToken.prototype = new CSSParserToken(); BadURLToken.prototype.tokenType = 'BADURL'; function WhitespaceToken() { return this; } WhitespaceToken.prototype = new CSSParserToken(); WhitespaceToken.prototype.tokenType = 'WHITESPACE'; WhitespaceToken.prototype.toString = function () { return 'WS'; }; function CDOToken() { return this; } CDOToken.prototype = new CSSParserToken(); CDOToken.prototype.tokenType = 'CDO'; function CDCToken() { return this; } CDCToken.prototype = new CSSParserToken(); CDCToken.prototype.tokenType = 'CDC'; function ColonToken() { return this; } ColonToken.prototype = new CSSParserToken(); ColonToken.prototype.tokenType = ':'; function SemicolonToken() { return this; } SemicolonToken.prototype = new CSSParserToken(); SemicolonToken.prototype.tokenType = ';'; function OpenCurlyToken() { return this; } OpenCurlyToken.prototype = new CSSParserToken(); OpenCurlyToken.prototype.tokenType = '{'; function CloseCurlyToken() { return this; } CloseCurlyToken.prototype = new CSSParserToken(); CloseCurlyToken.prototype.tokenType = '}'; function OpenSquareToken() { return this; } OpenSquareToken.prototype = new CSSParserToken(); OpenSquareToken.prototype.tokenType = '['; function CloseSquareToken() { return this; } CloseSquareToken.prototype = new CSSParserToken(); CloseSquareToken.prototype.tokenType = ']'; function OpenParenToken() { return this; } OpenParenToken.prototype = new CSSParserToken(); OpenParenToken.prototype.tokenType = '('; function CloseParenToken() { return this; } CloseParenToken.prototype = new CSSParserToken(); CloseParenToken.prototype.tokenType = ')'; function EOFToken() { return this; } EOFToken.prototype = new CSSParserToken(); EOFToken.prototype.tokenType = 'EOF'; function DelimToken(code) { this.value = String.fromCharCode(code); return this; } DelimToken.prototype = new CSSParserToken(); DelimToken.prototype.tokenType = 'DELIM'; DelimToken.prototype.toString = function () { return 'DELIM(' + this.value + ')'; }; function StringValuedToken() { return this; } StringValuedToken.prototype = new CSSParserToken(); StringValuedToken.prototype.append = function (val) { if (val instanceof Array) { for (var i = 0; i < val.length; i++) { this.value.push(val[i]); } } else { this.value.push(val); } return true; }; StringValuedToken.prototype.finish = function () { this.value = this.valueAsString(); return this; }; StringValuedToken.prototype.ASCIImatch = function (str) { return this.valueAsString().toLowerCase() == str.toLowerCase(); }; StringValuedToken.prototype.valueAsString = function () { if (typeof this.value == 'string') return this.value; return stringFromCodeArray(this.value); }; StringValuedToken.prototype.valueAsCodes = function () { if (typeof this.value == 'string') { var ret = []; for (var i = 0; i < this.value.length; i++) ret.push(this.value.charCodeAt(i)); return ret; } return this.value.filter(function (e) { return e; }); }; function IdentifierToken(val) { this.value = []; this.append(val); } IdentifierToken.prototype = new StringValuedToken(); IdentifierToken.prototype.tokenType = 'IDENT'; IdentifierToken.prototype.toString = function () { return 'IDENT(' + this.value + ')'; }; function FunctionToken(val) { this.value = val.finish().value; } FunctionToken.prototype = new StringValuedToken(); FunctionToken.prototype.tokenType = 'FUNCTION'; FunctionToken.prototype.toString = function () { return 'FUNCTION(' + this.value + ')'; }; function AtKeywordToken(val) { this.value = []; this.append(val); } AtKeywordToken.prototype = new StringValuedToken(); AtKeywordToken.prototype.tokenType = 'AT-KEYWORD'; AtKeywordToken.prototype.toString = function () { return 'AT(' + this.value + ')'; }; function HashToken(val) { this.value = []; this.append(val); } HashToken.prototype = new StringValuedToken(); HashToken.prototype.tokenType = 'HASH'; HashToken.prototype.toString = function () { return 'HASH(' + this.value + ')'; }; function StringToken(val) { this.value = []; this.append(val); } StringToken.prototype = new StringValuedToken(); StringToken.prototype.tokenType = 'STRING'; StringToken.prototype.toString = function () { return '"' + this.value + '"'; }; function URLToken(val) { this.value = []; this.append(val); } URLToken.prototype = new StringValuedToken(); URLToken.prototype.tokenType = 'URL'; URLToken.prototype.toString = function () { return 'URL(' + this.value + ')'; }; function NumberToken(val) { this.value = []; this.append(val); this.type = 'integer'; } NumberToken.prototype = new StringValuedToken(); NumberToken.prototype.tokenType = 'NUMBER'; NumberToken.prototype.toString = function () { if (this.type == 'integer') return 'INT(' + this.value + ')'; return 'NUMBER(' + this.value + ')'; }; NumberToken.prototype.finish = function () { this.repr = this.valueAsString(); this.value = this.repr * 1; if (Math.abs(this.value) % 1 != 0) this.type = 'number'; return this; }; function PercentageToken(val) { val.finish(); this.value = val.value; this.repr = val.repr; } PercentageToken.prototype = new CSSParserToken(); PercentageToken.prototype.tokenType = 'PERCENTAGE'; PercentageToken.prototype.toString = function () { return 'PERCENTAGE(' + this.value + ')'; }; function DimensionToken(val, unit) { val.finish(); this.num = val.value; this.unit = []; this.repr = val.repr; this.append(unit); } DimensionToken.prototype = new CSSParserToken(); DimensionToken.prototype.tokenType = 'DIMENSION'; DimensionToken.prototype.toString = function () { return 'DIM(' + this.num + ',' + this.unit + ')'; }; DimensionToken.prototype.append = function (val) { if (val instanceof Array) { for (var i = 0; i < val.length; i++) { this.unit.push(val[i]); } } else { this.unit.push(val); } return true; }; DimensionToken.prototype.finish = function () { this.unit = stringFromCodeArray(this.unit); this.repr += this.unit; return this; }; function UnicodeRangeToken(start, end) { start = parseInt(stringFromCodeArray(start), 16); if (end === undefined) end = start + 1; else end = parseInt(stringFromCodeArray(end), 16); if (start > maximumallowedcodepoint) end = start; if (end < start) end = start; if (end > maximumallowedcodepoint) end = maximumallowedcodepoint; this.start = start; this.end = end; return this; } UnicodeRangeToken.prototype = new CSSParserToken(); UnicodeRangeToken.prototype.tokenType = 'UNICODE-RANGE'; UnicodeRangeToken.prototype.toString = function () { if (this.start + 1 == this.end) return 'UNICODE-RANGE(' + this.start.toString(16).toUpperCase() + ')'; if (this.start < this.end) return 'UNICODE-RANGE(' + this.start.toString(16).toUpperCase() + '-' + this.end.toString(16).toUpperCase() + ')'; return 'UNICODE-RANGE()'; }; UnicodeRangeToken.prototype.contains = function (code) { return code >= this.start && code < this.end; }; exports.tokenize = tokenize; exports.EOFToken = EOFToken; })); (function (root, factory) { if (typeof define === 'function' && define.amd) { define('bleach/css-parser/parser', [ 'require', 'exports' ], factory); } else if (typeof exports !== 'undefined') { factory(require, exports); } else { factory(root); } }(this, function (require, exports) { var tokenizer = require('./tokenizer'); function parse(tokens, initialMode) { var mode = initialMode || 'top-level'; var i = -1; var token; var stylesheet; switch (mode) { case 'top-level': stylesheet = new Stylesheet(); break; case 'declaration': stylesheet = new StyleRule(); break; } stylesheet.startTok = tokens[0]; var stack = [stylesheet]; var rule = stack[0]; var consume = function (advance) { if (advance === undefined) advance = 1; i += advance; if (i < tokens.length) token = tokens[i]; else token = new EOFToken(); return true; }; var reprocess = function () { i--; return true; }; var next = function () { return tokens[i + 1]; }; var switchto = function (newmode) { if (newmode === undefined) { if (rule.fillType !== '') mode = rule.fillType; else if (rule.type == 'STYLESHEET') mode = 'top-level'; else { } } else { mode = newmode; } return true; }; var push = function (newRule) { rule = newRule; rule.startTok = token; stack.push(rule); return true; }; var parseerror = function (msg) { return true; }; var pop = function () { var oldrule = stack.pop(); oldrule.endTok = token; rule = stack[stack.length - 1]; rule.append(oldrule); return true; }; var discard = function () { stack.pop(); rule = stack[stack.length - 1]; return true; }; var finish = function () { while (stack.length > 1) { pop(); } rule.endTok = token; }; for (;;) { consume(); switch (mode) { case 'top-level': switch (token.tokenType) { case 'CDO': case 'CDC': case 'WHITESPACE': break; case 'AT-KEYWORD': push(new AtRule(token.value)) && switchto('at-rule'); break; case '{': parseerror('Attempt to open a curly-block at top-level.') && consumeAPrimitive(); break; case 'EOF': finish(); return stylesheet; default: push(new StyleRule()) && switchto('selector') && reprocess(); } break; case 'at-rule': switch (token.tokenType) { case ';': pop() && switchto(); break; case '{': if (rule.fillType !== '') switchto(rule.fillType); else parseerror('Attempt to open a curly-block in a statement-type at-rule.') && discard() && switchto('next-block') && reprocess(); break; case 'EOF': finish(); return stylesheet; default: rule.appendPrelude(consumeAPrimitive()); } break; case 'rule': switch (token.tokenType) { case 'WHITESPACE': break; case '}': pop() && switchto(); break; case 'AT-KEYWORD': push(new AtRule(token.value)) && switchto('at-rule'); break; case 'EOF': finish(); return stylesheet; default: push(new StyleRule()) && switchto('selector') && reprocess(); } break; case 'selector': switch (token.tokenType) { case '{': switchto('declaration'); break; case 'EOF': discard() && finish(); return stylesheet; default: rule.appendSelector(consumeAPrimitive()); } break; case 'declaration': switch (token.tokenType) { case 'WHITESPACE': case ';': break; case '}': pop() && switchto(); break; case 'AT-RULE': push(new AtRule(token.value)) && switchto('at-rule'); break; case 'IDENT': push(new Declaration(token.value)) && switchto('after-declaration-name'); break; case 'EOF': finish(); return stylesheet; default: parseerror() && discard() && switchto('next-declaration'); } break; case 'after-declaration-name': switch (token.tokenType) { case 'WHITESPACE': break; case ':': switchto('declaration-value'); break; case ';': parseerror('Incomplete declaration - semicolon after property name.') && discard() && switchto(); break; case 'EOF': discard() && finish(); return stylesheet; default: parseerror('Invalid declaration - additional token after property name') && discard() && switchto('next-declaration'); } break; case 'declaration-value': switch (token.tokenType) { case 'DELIM': if (token.value == '!' && next().tokenType == 'IDENTIFIER' && next().value.toLowerCase() == 'important') { consume(); rule.important = true; switchto('declaration-end'); } else { rule.append(token); } break; case ';': pop() && switchto(); break; case '}': pop() && pop() && switchto(); break; case 'EOF': finish(); return stylesheet; default: rule.append(consumeAPrimitive()); } break; case 'declaration-end': switch (token.tokenType) { case 'WHITESPACE': break; case ';': pop() && switchto(); break; case '}': pop() && pop() && switchto(); break; case 'EOF': finish(); return stylesheet; default: parseerror('Invalid declaration - additional token after !important.') && discard() && switchto('next-declaration'); } break; case 'next-block': switch (token.tokenType) { case '{': consumeAPrimitive() && switchto(); break; case 'EOF': finish(); return stylesheet; default: consumeAPrimitive(); break; } break; case 'next-declaration': switch (token.tokenType) { case ';': switchto('declaration'); break; case '}': switchto('declaration') && reprocess(); break; case 'EOF': finish(); return stylesheet; default: consumeAPrimitive(); break; } break; default: return; } } function consumeAPrimitive() { switch (token.tokenType) { case '(': case '[': case '{': return consumeASimpleBlock(); case 'FUNCTION': return consumeAFunc(); default: return token; } } function consumeASimpleBlock() { var endingTokenType = { '(': ')', '[': ']', '{': '}' }[token.tokenType]; var block = new SimpleBlock(token.tokenType); for (;;) { consume(); switch (token.tokenType) { case 'EOF': case endingTokenType: return block; default: block.append(consumeAPrimitive()); } } } function consumeAFunc() { var func = new Func(token.value); var arg = new FuncArg(); for (;;) { consume(); switch (token.tokenType) { case 'EOF': case ')': func.append(arg); return func; case 'DELIM': if (token.value == ',') { func.append(arg); arg = new FuncArg(); } else { arg.append(token); } break; default: arg.append(consumeAPrimitive()); } } } } function CSSParserRule() { return this; } CSSParserRule.prototype.fillType = ''; CSSParserRule.prototype.toString = function (indent) { return JSON.stringify(this.toJSON(), null, indent); }; CSSParserRule.prototype.append = function (val) { this.value.push(val); return this; }; function Stylesheet() { this.value = []; return this; } Stylesheet.prototype = new CSSParserRule(); Stylesheet.prototype.type = 'STYLESHEET'; Stylesheet.prototype.toJSON = function () { return { type: 'stylesheet', value: this.value.map(function (e) { return e.toJSON(); }) }; }; function AtRule(name) { this.name = name; this.prelude = []; this.value = []; if (name in AtRule.registry) this.fillType = AtRule.registry[name]; return this; } AtRule.prototype = new CSSParserRule(); AtRule.prototype.type = 'AT-RULE'; AtRule.prototype.appendPrelude = function (val) { this.prelude.push(val); return this; }; AtRule.prototype.toJSON = function () { return { type: 'at', name: this.name, prelude: this.prelude.map(function (e) { return e.toJSON(); }), value: this.value.map(function (e) { return e.toJSON(); }) }; }; AtRule.registry = { 'import': '', 'media': 'rule', 'font-face': 'declaration', 'page': 'declaration', 'keyframes': 'rule', 'namespace': '', 'counter-style': 'declaration', 'supports': 'rule', 'document': 'rule', 'font-feature-values': 'declaration', 'viewport': '', 'region-style': 'rule' }; function StyleRule() { this.selector = []; this.value = []; return this; } StyleRule.prototype = new CSSParserRule(); StyleRule.prototype.type = 'STYLE-RULE'; StyleRule.prototype.fillType = 'declaration'; StyleRule.prototype.appendSelector = function (val) { this.selector.push(val); return this; }; StyleRule.prototype.toJSON = function () { return { type: 'selector', selector: this.selector.map(function (e) { return e.toJSON(); }), value: this.value.map(function (e) { return e.toJSON(); }) }; }; function Declaration(name) { this.name = name; this.value = []; return this; } Declaration.prototype = new CSSParserRule(); Declaration.prototype.type = 'DECLARATION'; Declaration.prototype.toJSON = function () { return { type: 'declaration', name: this.name, value: this.value.map(function (e) { return e.toJSON(); }) }; }; function SimpleBlock(type) { this.name = type; this.value = []; return this; } SimpleBlock.prototype = new CSSParserRule(); SimpleBlock.prototype.type = 'BLOCK'; SimpleBlock.prototype.toJSON = function () { return { type: 'block', name: this.name, value: this.value.map(function (e) { return e.toJSON(); }) }; }; function Func(name) { this.name = name; this.value = []; return this; } Func.prototype = new CSSParserRule(); Func.prototype.type = 'FUNCTION'; Func.prototype.toJSON = function () { return { type: 'func', name: this.name, value: this.value.map(function (e) { return e.toJSON(); }) }; }; function FuncArg() { this.value = []; return this; } FuncArg.prototype = new CSSParserRule(); FuncArg.prototype.type = 'FUNCTION-ARG'; FuncArg.prototype.toJSON = function () { return this.value.map(function (e) { return e.toJSON(); }); }; exports.parse = parse; })); if (typeof exports === 'object' && typeof define !== 'function') { define = function (factory) { factory(require, exports, module); }; } define('bleach', [ 'require', 'exports', 'module', './bleach/css-parser/tokenizer', './bleach/css-parser/parser' ], function (require, exports, module) { var tokenizer = require('./bleach/css-parser/tokenizer'); var parser = require('./bleach/css-parser/parser'); var ALLOWED_TAGS = [ 'a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul' ]; var ALLOWED_ATTRIBUTES = { 'a': [ 'href', 'title' ], 'abbr': ['title'], 'acronym': ['title'] }; var ALLOWED_STYLES = []; var Node = { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }; var DEFAULTS = { tags: ALLOWED_TAGS, prune: [], attributes: ALLOWED_ATTRIBUTES, styles: ALLOWED_STYLES, strip: false, stripComments: true }; exports.clean = function (html, opts) { if (!html) return ''; html = html.replace(/]*>/gi, ''); return exports.cleanNode(html, opts); }; exports.cleanNode = function (html, opts) { try { function debug(str) { console.log('Bleach: ' + str + '\n'); } opts = opts || DEFAULTS; var attrsByTag = opts.hasOwnProperty('attributes') ? opts.attributes : DEFAULTS.attributes; var wildAttrs; if (Array.isArray(attrsByTag)) { wildAttrs = attrsByTag; attrsByTag = {}; } else if (attrsByTag.hasOwnProperty('*')) { wildAttrs = attrsByTag['*']; } else { wildAttrs = []; } var sanitizeOptions = { ignoreComment: 'stripComments' in opts ? opts.stripComments : DEFAULTS.stripComments, allowedStyles: opts.styles || DEFAULTS.styles, allowedTags: opts.tags || DEFAULTS.tags, stripMode: 'strip' in opts ? opts.strip : DEFAULTS.strip, pruneTags: opts.prune || DEFAULTS.prune, allowedAttributesByTag: attrsByTag, wildAttributes: wildAttrs, callbackRegexp: opts.callbackRegexp || null, callback: opts.callbackRegexp && opts.callback || null, maxLength: opts.maxLength || 0 }; var sanitizer = new HTMLSanitizer(sanitizeOptions); HTMLParser.HTMLParser(html, sanitizer); return sanitizer.output; } catch (e) { console.error(e, '\n', e.stack); throw e; } }; var RE_NORMALIZE_WHITESPACE = /\s+/g; var HTMLSanitizer = function (options) { this.output = ''; this.ignoreComment = options.ignoreComment; this.allowedStyles = options.allowedStyles; this.allowedTags = options.allowedTags; this.stripMode = options.stripMode; this.pruneTags = options.pruneTags; this.allowedAttributesByTag = options.allowedAttributesByTag; this.wildAttributes = options.wildAttributes; this.callbackRegexp = options.callbackRegexp; this.callback = options.callback; this.isInsideStyleTag = false; this.isInsidePrunedTag = 0; this.isInsideStrippedTag = 0; this.maxLength = options.maxLength || 0; this.complete = false; this.ignoreFragments = this.maxLength > 0; }; HTMLSanitizer.prototype = { start: function (tag, attrs, unary) { if (this.pruneTags.indexOf(tag) !== -1) { if (!unary) this.isInsidePrunedTag++; return; } else if (this.isInsidePrunedTag) { return; } if (this.allowedTags.indexOf(tag) === -1) { if (this.stripMode) { if (!unary) { this.isInsideStrippedTag++; } return; } this.output += '<' + (unary ? '/' : '') + tag + '>'; return; } this.isInsideStyleTag = tag == 'style' && !unary; var callbackRegexp = this.callbackRegexp; if (callbackRegexp && callbackRegexp.test(tag)) { attrs = this.callback(tag, attrs); } var whitelist = this.allowedAttributesByTag[tag]; var wildAttrs = this.wildAttributes; var result = '<' + tag; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; var attrName = attr.name.toLowerCase(); if (attr.safe || wildAttrs.indexOf(attrName) !== -1 || whitelist && whitelist.indexOf(attrName) !== -1) { if (attrName == 'style') { var attrValue = ''; try { attrValue = CSSParser.parseAttribute(attr.escaped, this.allowedStyles); } catch (e) { console.log('CSSParser.parseAttribute failed for: "' + attr.escaped + '", skipping. Error: ' + e); } result += ' ' + attrName + '="' + attrValue + '"'; } else { result += ' ' + attrName + '="' + attr.escaped + '"'; } } } result += (unary ? '/' : '') + '>'; this.output += result; }, end: function (tag) { if (this.pruneTags.indexOf(tag) !== -1) { this.isInsidePrunedTag--; return; } else if (this.isInsidePrunedTag) { return; } if (this.allowedTags.indexOf(tag) === -1) { if (this.isInsideStrippedTag) { this.isInsideStrippedTag--; return; } this.output += '</' + tag + '>'; return; } if (this.isInsideStyleTag) { this.isInsideStyleTag = false; } this.output += ''; }, chars: function (text) { if (this.isInsidePrunedTag || this.complete) return; if (this.isInsideStyleTag) { this.output += CSSParser.parseBody(text, this.allowedStyles); return; } if (this.maxLength) { if (this.insideTagForSnippet) { if (text.indexOf('>') !== -1) { this.insideTagForSnippet = false; } return; } else { if (text.charAt(0) === '<') { this.insideTagForSnippet = true; return; } } var normalizedText = text.replace(RE_NORMALIZE_WHITESPACE, ' '); var length = this.output.length; if (length && normalizedText[0] === ' ' && this.output[length - 1] === ' ') { normalizedText = normalizedText.substring(1); } this.output += normalizedText; if (this.output.length >= this.maxLength) { this.output = this.output.substring(0, this.maxLength); this.complete = true; } } else { this.output += escapeHTMLTextKeepingExistingEntities(text); } }, comment: function (comment) { if (this.isInsidePrunedTag) return; if (this.ignoreComment) return; this.output += ''; } }; var HTMLParser = function () { var startTag = /^<(?:[-A-Za-z0-9_]+:)?([-A-Za-z0-9_]+)([^>]*)>/, endTag = /^<\/(?:[-A-Za-z0-9_]+:)?([-A-Za-z0-9_]+)[^>]*>/, attr = /(?:[-A-Za-z0-9_]+:)?([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^>\s]+)))?/g; var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed'); var block = makeMap('address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul'); var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); var special = makeMap('script,style'); var HTMLParser = this.HTMLParser = function (html, handler) { var index, chars, match, stack = [], last = html; stack.last = function () { return this[this.length - 1]; }; while (html) { chars = true; if (!stack.last() || !special[stack.last()]) { if (html.lastIndexOf(''); if (index >= 4) { if (handler.comment) handler.comment(html.substring(4, index)); html = html.substring(index + 3); chars = false; } else { if (handler.comment) handler.comment(html.substring(4, -1)); html = ''; chars = false; } } else if (html.lastIndexOf(']*>', 'i'), function (all, text) { if (!skipWork) { text = text.replace(//g, '$1').replace(//g, '$1'); if (handler.chars) { handler.chars(text); skipWork = handler.complete; } } return ''; }); if (handler.complete) return this; parseEndTag('', stack.last()); } if (html == last) { if (handler.ignoreFragments) { return; } else { console.log(html); console.log(last); throw 'Parse Error: ' + html; } } last = html; } parseEndTag(); function parseStartTag(tag, tagName, rest) { tagName = tagName.toLowerCase(); if (block[tagName]) { while (stack.last() && inline[stack.last()]) { parseEndTag('', stack.last()); } } if (closeSelf[tagName] && stack.last() == tagName) { parseEndTag('', tagName); } var unary = empty[tagName]; if (rest.length && rest[rest.length - 1] === '/') { unary = true; rest = rest.slice(0, -1); } if (!unary) stack.push(tagName); if (handler.start) { var attrs = []; rest.replace(attr, function (match, name) { var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : ''; attrs.push({ name: name, value: value, escaped: value.replace(/"/g, '"'), safe: false }); }); if (handler.start) handler.start(tagName, attrs, unary); } } function parseEndTag(tag, tagName) { if (!tagName) var pos = 0; else { tagName = tagName.toLowerCase(); for (var pos = stack.length - 1; pos >= 0; pos--) if (stack[pos] == tagName) break; } if (pos >= 0) { for (var i = stack.length - 1; i >= pos; i--) if (handler.end) handler.end(stack[i]); stack.length = pos; } } }; function makeMap(str) { var obj = {}, items = str.split(','); for (var i = 0; i < items.length; i++) obj[items[i]] = true; return obj; } return this; }(); var CSSParser = { parseAttribute: function (data, allowedStyles) { var tokens = tokenizer.tokenize(data, { loc: true }); var rule = parser.parse(tokens, 'declaration'); var keepText = []; this._filterDeclarations(null, rule.value, allowedStyles, data, keepText); var oot = keepText.join(''); return oot; }, _filterDeclarations: function (parent, decls, allowedStyles, fullText, textOut) { for (var i = 0; i < decls.length; i++) { var decl = decls[i]; if (decl.type !== 'DECLARATION') { continue; } if (allowedStyles.indexOf(decl.name) !== -1) { textOut.push(fullText.substring(decl.startTok.loc.start.idx, parent && parent.endTok === decl.endTok ? decl.endTok.loc.start.idx : decl.endTok.loc.end.idx + 1)); } } }, parseBody: function (data, allowedStyles) { var body = ''; var oot = ''; try { var tokens = tokenizer.tokenize(data, { loc: true }); var stylesheet = parser.parse(tokens); var keepText = []; for (var i = 0; i < stylesheet.value.length; i++) { var sub = stylesheet.value[i]; if (sub.type === 'STYLE-RULE') { keepText.push(data.substring(sub.startTok.loc.start.idx, sub.value.length ? sub.value[0].startTok.loc.start.idx : sub.endTok.loc.start.idx)); this._filterDeclarations(sub, sub.value, allowedStyles, data, keepText); keepText.push(data.substring(sub.endTok.loc.start.idx, sub.endTok.loc.end.idx + 1)); } } oot = keepText.join(''); } catch (e) { console.log('bleach CSS parsing failed, skipping. Error: ' + e); oot = ''; } return oot; } }; var entities = { 34: 'quot', 38: 'amp', 39: 'apos', 60: 'lt', 62: 'gt', 160: 'nbsp', 161: 'iexcl', 162: 'cent', 163: 'pound', 164: 'curren', 165: 'yen', 166: 'brvbar', 167: 'sect', 168: 'uml', 169: 'copy', 170: 'ordf', 171: 'laquo', 172: 'not', 173: 'shy', 174: 'reg', 175: 'macr', 176: 'deg', 177: 'plusmn', 178: 'sup2', 179: 'sup3', 180: 'acute', 181: 'micro', 182: 'para', 183: 'middot', 184: 'cedil', 185: 'sup1', 186: 'ordm', 187: 'raquo', 188: 'frac14', 189: 'frac12', 190: 'frac34', 191: 'iquest', 192: 'Agrave', 193: 'Aacute', 194: 'Acirc', 195: 'Atilde', 196: 'Auml', 197: 'Aring', 198: 'AElig', 199: 'Ccedil', 200: 'Egrave', 201: 'Eacute', 202: 'Ecirc', 203: 'Euml', 204: 'Igrave', 205: 'Iacute', 206: 'Icirc', 207: 'Iuml', 208: 'ETH', 209: 'Ntilde', 210: 'Ograve', 211: 'Oacute', 212: 'Ocirc', 213: 'Otilde', 214: 'Ouml', 215: 'times', 216: 'Oslash', 217: 'Ugrave', 218: 'Uacute', 219: 'Ucirc', 220: 'Uuml', 221: 'Yacute', 222: 'THORN', 223: 'szlig', 224: 'agrave', 225: 'aacute', 226: 'acirc', 227: 'atilde', 228: 'auml', 229: 'aring', 230: 'aelig', 231: 'ccedil', 232: 'egrave', 233: 'eacute', 234: 'ecirc', 235: 'euml', 236: 'igrave', 237: 'iacute', 238: 'icirc', 239: 'iuml', 240: 'eth', 241: 'ntilde', 242: 'ograve', 243: 'oacute', 244: 'ocirc', 245: 'otilde', 246: 'ouml', 247: 'divide', 248: 'oslash', 249: 'ugrave', 250: 'uacute', 251: 'ucirc', 252: 'uuml', 253: 'yacute', 254: 'thorn', 255: 'yuml', 402: 'fnof', 913: 'Alpha', 914: 'Beta', 915: 'Gamma', 916: 'Delta', 917: 'Epsilon', 918: 'Zeta', 919: 'Eta', 920: 'Theta', 921: 'Iota', 922: 'Kappa', 923: 'Lambda', 924: 'Mu', 925: 'Nu', 926: 'Xi', 927: 'Omicron', 928: 'Pi', 929: 'Rho', 931: 'Sigma', 932: 'Tau', 933: 'Upsilon', 934: 'Phi', 935: 'Chi', 936: 'Psi', 937: 'Omega', 945: 'alpha', 946: 'beta', 947: 'gamma', 948: 'delta', 949: 'epsilon', 950: 'zeta', 951: 'eta', 952: 'theta', 953: 'iota', 954: 'kappa', 955: 'lambda', 956: 'mu', 957: 'nu', 958: 'xi', 959: 'omicron', 960: 'pi', 961: 'rho', 962: 'sigmaf', 963: 'sigma', 964: 'tau', 965: 'upsilon', 966: 'phi', 967: 'chi', 968: 'psi', 969: 'omega', 977: 'thetasym', 978: 'upsih', 982: 'piv', 8226: 'bull', 8230: 'hellip', 8242: 'prime', 8243: 'Prime', 8254: 'oline', 8260: 'frasl', 8472: 'weierp', 8465: 'image', 8476: 'real', 8482: 'trade', 8501: 'alefsym', 8592: 'larr', 8593: 'uarr', 8594: 'rarr', 8595: 'darr', 8596: 'harr', 8629: 'crarr', 8656: 'lArr', 8657: 'uArr', 8658: 'rArr', 8659: 'dArr', 8660: 'hArr', 8704: 'forall', 8706: 'part', 8707: 'exist', 8709: 'empty', 8711: 'nabla', 8712: 'isin', 8713: 'notin', 8715: 'ni', 8719: 'prod', 8721: 'sum', 8722: 'minus', 8727: 'lowast', 8730: 'radic', 8733: 'prop', 8734: 'infin', 8736: 'ang', 8743: 'and', 8744: 'or', 8745: 'cap', 8746: 'cup', 8747: 'int', 8756: 'there4', 8764: 'sim', 8773: 'cong', 8776: 'asymp', 8800: 'ne', 8801: 'equiv', 8804: 'le', 8805: 'ge', 8834: 'sub', 8835: 'sup', 8836: 'nsub', 8838: 'sube', 8839: 'supe', 8853: 'oplus', 8855: 'otimes', 8869: 'perp', 8901: 'sdot', 8968: 'lceil', 8969: 'rceil', 8970: 'lfloor', 8971: 'rfloor', 9001: 'lang', 9002: 'rang', 9674: 'loz', 9824: 'spades', 9827: 'clubs', 9829: 'hearts', 9830: 'diams', 338: 'OElig', 339: 'oelig', 352: 'Scaron', 353: 'scaron', 376: 'Yuml', 710: 'circ', 732: 'tilde', 8194: 'ensp', 8195: 'emsp', 8201: 'thinsp', 8204: 'zwnj', 8205: 'zwj', 8206: 'lrm', 8207: 'rlm', 8211: 'ndash', 8212: 'mdash', 8216: 'lsquo', 8217: 'rsquo', 8218: 'sbquo', 8220: 'ldquo', 8221: 'rdquo', 8222: 'bdquo', 8224: 'dagger', 8225: 'Dagger', 8240: 'permil', 8249: 'lsaquo', 8250: 'rsaquo', 8364: 'euro' }; var reverseEntities; var entityRegExp = /\&([#a-zA-Z0-9]+);/g; function makeReverseEntities() { reverseEntities = {}; Object.keys(entities).forEach(function (key) { reverseEntities[entities[key]] = key; }); } function escapeHTMLTextKeepingExistingEntities(text) { return text.replace(/[<>"']|&(?![#a-zA-Z0-9]+;)/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); } exports.unescapeHTMLEntities = function unescapeHTMLEntities(text) { return text.replace(entityRegExp, function (match, ref) { var converted = ''; if (ref.charAt(0) === '#') { var secondChar = ref.charAt(1); if (secondChar === 'x' || secondChar === 'X') { converted = String.fromCharCode(parseInt(ref.substring(2), 16)); } else { converted = String.fromCharCode(parseInt(ref.substring(1), 10)); } } else { if (!reverseEntities) makeReverseEntities(); if (reverseEntities.hasOwnProperty(ref)) converted = String.fromCharCode(reverseEntities[ref]); } return converted; }); }; exports.escapePlaintextIntoElementContext = function (text) { return text.replace(/[&<>"'\/]/g, function (c) { var code = c.charCodeAt(0); return '&' + (entities[code] || '#' + code) + ';'; }); }; exports.escapePlaintextIntoAttribute = function (text) { return text.replace(/[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u0100]/g, function (c) { var code = c.charCodeAt(0); return '&' + (entities[code] || '#' + code) + ';'; }); }; }); ; define('htmlchew', [ 'exports', 'bleach' ], function (exports, $bleach) { var LEGAL_TAGS = [ 'a', 'abbr', 'acronym', 'area', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'font', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'label', 'legend', 'li', 'listing', 'map', 'mark', 'nav', 'nobr', 'noscript', 'ol', 'output', 'p', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'tt', 'u', 'ul', 'var', 'wbr' ]; var PRUNE_TAGS = [ 'button', 'datalist', 'script', 'select', 'svg', 'title' ]; var LEGAL_ATTR_MAP = { '*': [ 'abbr', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'charoff', 'class', 'clear', 'color', 'cols', 'colspan', 'compact', 'coords', 'datetime', 'dir', 'face', 'frame', 'headers', 'height', 'hspace', 'id', 'lang', 'media', 'nohref', 'noshade', 'nowrap', 'open', 'pointsize', 'pubdate', 'reversed', 'rows', 'rowspan', 'rules', 'size', 'scope', 'scoped', 'shape', 'span', 'start', 'summary', 'style', 'title', 'valign', 'value', 'vspace', 'width' ], 'a': [ 'ext-href', 'hreflang' ], 'area': [ 'ext-href', 'hreflang' ], 'blockquote': [ 'cite', 'type' ], 'img': [ 'cid-src', 'ext-src', 'ismap', 'usemap' ], 'meta': ['charset'], 'ol': ['type'], 'style': ['type'] }; var LEGAL_STYLES = [ 'background-color', 'border', 'border-bottom', 'border-bottom-color', 'border-bottom-left-radius', 'border-bottom-right-radius', 'border-bottom-style', 'border-bottom-width', 'border-color', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-radius', 'border-right', 'border-right-color', 'border-right-style', 'border-right-width', 'border-style', 'border-top', 'border-top-color', 'border-top-left-radius', 'border-top-right-radius', 'border-top-style', 'border-top-width', 'border-width', 'clear', 'color', 'display', 'float', 'font-family', 'font-size', 'font-style', 'font-weight', 'height', 'line-height', 'list-style-position', 'list-style-type', 'margin', 'margin-bottom', 'margin-left', 'margin-right', 'margin-top', 'padding', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top', 'text-align', 'text-align-last', 'text-decoration', 'text-decoration-color', 'text-decoration-line', 'text-decoration-style', 'text-indent', 'vertical-align', 'white-space', 'width', 'word-break', 'word-spacing', 'word-wrap' ]; var RE_NODE_NEEDS_TRANSFORM = /^(?:a|area|img)$/; var RE_CID_URL = /^cid:/i; var RE_HTTP_URL = /^http(?:s)?/i; var RE_MAILTO_URL = /^mailto:/i; var RE_IMG_TAG = /^img$/; function getAttributeFromList(attrs, name) { var len = attrs.length; for (var i = 0; i < len; i++) { var attr = attrs[i]; if (attr.name.toLowerCase() === name) { return attr; } } return null; } function stashLinks(lowerTag, attrs) { var classAttr; if (RE_IMG_TAG.test(lowerTag)) { attrs = attrs.filter(function (attr) { switch (attr.name.toLowerCase()) { case 'cid-src': case 'ext-src': return false; case 'class': classAttr = attr; default: return true; } }); var srcAttr = getAttributeFromList(attrs, 'src'); if (srcAttr) { if (RE_CID_URL.test(srcAttr.escaped)) { srcAttr.name = 'cid-src'; if (classAttr) classAttr.escaped += ' moz-embedded-image'; else attrs.push({ name: 'class', escaped: 'moz-embedded-image' }); srcAttr.escaped = srcAttr.escaped.substring(4); } else if (RE_HTTP_URL.test(srcAttr.escaped)) { srcAttr.name = 'ext-src'; if (classAttr) classAttr.escaped += ' moz-external-image'; else attrs.push({ name: 'class', escaped: 'moz-external-image' }); } } } else { attrs = attrs.filter(function (attr) { switch (attr.name.toLowerCase()) { case 'cid-src': case 'ext-src': return false; case 'class': classAttr = attr; default: return true; } }); var linkAttr = getAttributeFromList(attrs, 'href'); if (linkAttr) { var link = linkAttr.escaped; if (RE_HTTP_URL.test(link) || RE_MAILTO_URL.test(link)) { linkAttr.name = 'ext-href'; if (classAttr) classAttr.escaped += ' moz-external-link'; else attrs.push({ name: 'class', escaped: 'moz-external-link' }); } else { attrs.splice(attrs.indexOf(linkAttr), 1); } } } return attrs; } var BLEACH_SETTINGS = { tags: LEGAL_TAGS, strip: true, stripComments: true, prune: PRUNE_TAGS, attributes: LEGAL_ATTR_MAP, styles: LEGAL_STYLES, asNode: true, callbackRegexp: RE_NODE_NEEDS_TRANSFORM, callback: stashLinks }; var BLEACH_SNIPPET_SETTINGS = { tags: [], strip: true, stripComments: true, prune: [ 'style', 'button', 'datalist', 'script', 'select', 'svg', 'title' ], asNode: true, maxLength: 100 }; exports.sanitizeAndNormalizeHtml = function sanitizeAndNormalize(htmlString) { return $bleach.clean(htmlString, BLEACH_SETTINGS); }; exports.generateSnippet = function generateSnippet(htmlString) { return $bleach.unescapeHTMLEntities($bleach.clean(htmlString, BLEACH_SNIPPET_SETTINGS)); }; var BLEACH_SEARCHABLE_TEXT_WITH_QUOTES_SETTINGS = { tags: [], strip: true, stripComments: true, prune: [ 'style', 'button', 'datalist', 'script', 'select', 'svg', 'title' ], asNode: true }; var BLEACH_SEARCHABLE_TEXT_WITHOUT_QUOTES_SETTINGS = { tags: [], strip: true, stripComments: true, prune: [ 'style', 'button', 'datalist', 'script', 'select', 'svg', 'title', 'blockquote' ], asNode: true }; exports.generateSearchableTextVersion = function (htmlString, includeQuotes) { var settings; if (includeQuotes) { settings = BLEACH_SEARCHABLE_TEXT_WITH_QUOTES_SETTINGS; } else { settings = BLEACH_SEARCHABLE_TEXT_WITHOUT_QUOTES_SETTINGS; } var cleaned = $bleach.clean(htmlString, settings); return $bleach.unescapeHTMLEntities(cleaned); }; exports.wrapTextIntoSafeHTMLString = function (text, wrapTag, transformNewlines, attrs) { if (transformNewlines === undefined) { transformNewlines = true; } wrapTag = wrapTag || 'div'; text = $bleach.escapePlaintextIntoElementContext(text); text = transformNewlines ? text.replace(/\n/g, '
') : text; var attributes = ''; if (attrs) { var len = attrs.length; for (var i = 0; i < len; i += 2) { attributes += ' ' + attrs[i] + '="' + $bleach.escapePlaintextIntoAttribute(attrs[i + 1]) + '"'; } } return '<' + wrapTag + attributes + '>' + text + ''; }; var RE_QUOTE_CHAR = /"/g; exports.escapeAttrValue = function (s) { return s.replace(RE_QUOTE_CHAR, '"'); }; }); ; define('searchfilter', [ 'logic', './util', './allback', './syncbase', './date', './htmlchew', 'module', 'exports' ], function (logic, $util, allback, $syncbase, $date, htmlchew, $module, exports) { var BEFORE = $date.BEFORE, ON_OR_BEFORE = $date.ON_OR_BEFORE, SINCE = $date.SINCE, STRICTLY_AFTER = $date.STRICTLY_AFTER; var bsearchMaybeExists = $util.bsearchMaybeExists, bsearchForInsert = $util.bsearchForInsert; function cmpMatchHeadersYoungToOld(aMatch, bMatch) { var a = aMatch.header, b = bMatch.header; var delta = b.date - a.date; if (delta) return delta; return b.id - a.id; } function matchRegexpOrString(phrase, input, fromIndex) { if (!input) { return null; } if (phrase instanceof RegExp) { return phrase.exec(fromIndex ? input.slice(fromIndex) : input); } var idx = input.indexOf(phrase, fromIndex); if (idx == -1) { return null; } var ret = [phrase]; ret.index = idx - fromIndex; return ret; } function AuthorFilter(phrase) { this.phrase = phrase; } exports.AuthorFilter = AuthorFilter; AuthorFilter.prototype = { needsBody: false, testMessage: function (header, body, match) { var author = header.author, phrase = this.phrase, ret; if (ret = matchRegexpOrString(phrase, author.name, 0)) { match.author = { text: author.name, offset: 0, matchRuns: [{ start: ret.index, length: ret[0].length }], path: null }; return true; } if (ret = matchRegexpOrString(phrase, author.address, 0)) { match.author = { text: author.address, offset: 0, matchRuns: [{ start: ret.index, length: ret[0].length }], path: null }; return true; } match.author = null; return false; } }; function RecipientFilter(phrase, stopAfterNMatches, checkTo, checkCc, checkBcc) { this.phrase = phrase; this.stopAfter = stopAfterNMatches; this.checkTo = checkTo; this.checkCc = checkCc; this.checkBcc = checkBcc; } exports.RecipientFilter = RecipientFilter; RecipientFilter.prototype = { needsBody: true, testMessage: function (header, body, match) { var phrase = this.phrase, stopAfter = this.stopAfter; var matches = []; function checkRecipList(list) { var ret; for (var i = 0; i < list.length; i++) { var recip = list[i]; if (ret = matchRegexpOrString(phrase, recip.name, 0)) { matches.push({ text: recip.name, offset: 0, matchRuns: [{ start: ret.index, length: ret[0].length }], path: null }); if (matches.length < stopAfter) continue; return; } if (ret = matchRegexpOrString(phrase, recip.address, 0)) { matches.push({ text: recip.address, offset: 0, matchRuns: [{ start: ret.index, length: ret[0].length }], path: null }); if (matches.length >= stopAfter) return; } } } if (this.checkTo && header.to) checkRecipList(header.to); if (this.checkCc && header.cc && matches.length < stopAfter) checkRecipList(header.cc); if (this.checkBcc && header.bcc && matches.length < stopAfter) checkRecipList(header.bcc); if (matches.length) { match.recipients = matches; return true; } else { match.recipients = null; return false; } } }; function snippetMatchHelper(str, start, length, contextBefore, contextAfter, path) { if (contextBefore > start) contextBefore = start; var offset = str.indexOf(' ', start - contextBefore); if (offset === -1 || offset >= start - 1) { offset = start - contextBefore; } else { offset++; } var endIdx; if (start + length + contextAfter >= str.length) { endIdx = str.length; } else { endIdx = str.lastIndexOf(' ', start + length + contextAfter - 1); if (endIdx <= start + length) { endIdx = start + length + contextAfter; } } var snippet = str.substring(offset, endIdx); return { text: snippet, offset: offset, matchRuns: [{ start: start - offset, length: length }], path: path }; } function SubjectFilter(phrase, stopAfterNMatches, contextBefore, contextAfter) { this.phrase = phrase; this.stopAfter = stopAfterNMatches; this.contextBefore = contextBefore; this.contextAfter = contextAfter; } exports.SubjectFilter = SubjectFilter; SubjectFilter.prototype = { needsBody: false, testMessage: function (header, body, match) { var subject = header.subject; if (!subject) return false; var phrase = this.phrase, slen = subject.length, stopAfter = this.stopAfter, contextBefore = this.contextBefore, contextAfter = this.contextAfter, matches = [], idx = 0; while (idx < slen && matches.length < stopAfter) { var ret = matchRegexpOrString(phrase, subject, idx); if (!ret) break; matches.push(snippetMatchHelper(subject, idx + ret.index, ret[0].length, contextBefore, contextAfter, null)); idx += ret.index + ret[0].length; } if (matches.length) { match.subject = matches; return true; } else { match.subject = null; return false; } } }; var CT_AUTHORED_CONTENT = 1; var ELEMENT_NODE = 1, TEXT_NODE = 3; function BodyFilter(phrase, matchQuotes, stopAfterNMatches, contextBefore, contextAfter) { this.phrase = phrase; this.stopAfter = stopAfterNMatches; this.contextBefore = contextBefore; this.contextAfter = contextAfter; this.matchQuotes = matchQuotes; } exports.BodyFilter = BodyFilter; BodyFilter.prototype = { needsBody: true, testMessage: function (header, body, match) { var phrase = this.phrase, stopAfter = this.stopAfter, contextBefore = this.contextBefore, contextAfter = this.contextAfter, matches = [], matchQuotes = this.matchQuotes, idx, ret; for (var iBodyRep = 0; iBodyRep < body.bodyReps.length; iBodyRep++) { var bodyType = body.bodyReps[iBodyRep].type, bodyRep = body.bodyReps[iBodyRep].content; if (bodyType === 'plain') { for (var iRep = 0; iRep < bodyRep.length && matches.length < stopAfter; iRep += 2) { var etype = bodyRep[iRep] & 15, block = bodyRep[iRep + 1], repPath = null; if (!matchQuotes && etype !== CT_AUTHORED_CONTENT) continue; for (idx = 0; idx < block.length && matches.length < stopAfter;) { ret = matchRegexpOrString(phrase, block, idx); if (!ret) { break; } if (repPath === null) { repPath = [ iBodyRep, iRep ]; } matches.push(snippetMatchHelper(block, idx + ret.index, ret[0].length, contextBefore, contextAfter, repPath)); idx += ret.index + ret[0].length; } } } else if (bodyType === 'html') { var searchableText = htmlchew.generateSearchableTextVersion(bodyRep, this.matchQuotes); for (idx = 0; idx < bodyRep.length && matches.length < stopAfter;) { ret = matchRegexpOrString(phrase, searchableText, idx); if (!ret) { break; } matches.push(snippetMatchHelper(searchableText, idx + ret.index, ret[0].length, contextBefore, contextAfter, null)); idx += ret.index + ret[0].length; } } } if (matches.length) { match.body = matches; return true; } else { match.body = null; return false; } } }; function MessageFilterer(filters) { this.filters = filters; this.bodiesNeeded = false; this.messagesChecked = 0; for (var i = 0; i < filters.length; i++) { var filter = filters[i]; if (filter.needsBody) this.bodiesNeeded = true; } } exports.MessageFilterer = MessageFilterer; MessageFilterer.prototype = { testMessage: function (header, body) { this.messagesChecked++; var matched = false, matchObj = {}; var filters = this.filters; try { for (var i = 0; i < filters.length; i++) { var filter = filters[i]; if (filter.testMessage(header, body, matchObj)) matched = true; } } catch (ex) { console.error('filter exception', ex, '\n', ex.stack); } if (matched) return matchObj; else return false; } }; var CONTEXT_CHARS_BEFORE = 16; var CONTEXT_CHARS_AFTER = 40; function SearchSlice(bridgeHandle, storage, phrase, whatToSearch) { console.log('sf: creating SearchSlice:', phrase); this._bridgeHandle = bridgeHandle; bridgeHandle.__listener = this; bridgeHandle.userCanGrowDownwards = false; this._storage = storage; logic.defineScope(this, 'SearchSlice'); SearchSlice._TEST_latestInstance = this; this.startTS = null; this.startUID = null; this.endTS = null; this.endUID = null; var filters = []; if (phrase) { if (!(phrase instanceof RegExp)) { phrase = new RegExp(phrase.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'i'); } if (whatToSearch.author) filters.push(new AuthorFilter(phrase)); if (whatToSearch.recipients) filters.push(new RecipientFilter(phrase, 1, true, true, true)); if (whatToSearch.subject) filters.push(new SubjectFilter(phrase, 1, CONTEXT_CHARS_BEFORE, CONTEXT_CHARS_AFTER)); if (whatToSearch.body) { filters.push(new BodyFilter(phrase, whatToSearch.body === 'yes-quotes', 1, CONTEXT_CHARS_BEFORE, CONTEXT_CHARS_AFTER)); this._pendingBodyLoadLatch = null; } } this.filterer = new MessageFilterer(filters); this._bound_gotOlderMessages = this._gotMessages.bind(this, 1); this._bound_gotNewerMessages = this._gotMessages.bind(this, -1); this.desiredHeaders = $syncbase.INITIAL_FILL_SIZE; this.reset(); } exports.SearchSlice = SearchSlice; SearchSlice.prototype = { type: 'search', set atTop(val) { this._bridgeHandle.atTop = val; }, get atBottom() { return this._bridgeHandle.atBottom; }, set atBottom(val) { this._bridgeHandle.atBottom = val; }, set headerCount(val) { if (this._bridgeHandle) this._bridgeHandle.headerCount = val; return val; }, IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM: 1, reset: function () { this.headers = []; this.headerCount = 0; this._loading = true; this.startTS = null; this.startUID = null; this.endTS = null; this.endUID = null; this._storage.getMessagesInImapDateRange(0, null, this.desiredHeaders, this.desiredHeaders, this._gotMessages.bind(this, 1)); }, _gotMessages: function (dir, headers, moreMessagesComing) { if (!this._bridgeHandle) { return; } var logPrefix = moreMessagesComing ? 'sf: ' : 'sf:'; console.log(logPrefix, 'gotMessages', headers.length, 'more coming?', moreMessagesComing); if (headers.length) { if (dir === -1) { this.endTS = headers[0].date; this.endUID = headers[0].id; } else { var lastHeader = headers[headers.length - 1]; this.startTS = lastHeader.date; this.startUID = lastHeader.id; if (this.endTS === null) { this.endTS = headers[0].date; this.endUID = headers[0].id; } } } var checkHandle = function checkHandle(headers, resolvedGetBodyCalls) { if (!this._bridgeHandle) { return; } var matchPairs = []; for (i = 0; i < headers.length; i++) { var header = headers[i], body = resolvedGetBodyCalls ? resolvedGetBodyCalls[header.id][0] : null; this._headersChecked++; var matchObj = this.filterer.testMessage(header, body); if (matchObj) matchPairs.push({ header: header, matches: matchObj }); } var atTop = this.atTop = this._storage.headerIsYoungestKnown(this.endTS, this.endUID); var atBottom = this.atBottom = this._storage.headerIsOldestKnown(this.startTS, this.startUID); var canGetMore = dir === -1 ? !atTop : !atBottom; var willHave = this.headers.length + matchPairs.length, wantMore = !moreMessagesComing && willHave < this.desiredHeaders && canGetMore; if (matchPairs.length) { console.log(logPrefix, 'willHave', willHave, 'of', this.desiredHeaders, 'want more?', wantMore); var insertAt = dir === -1 ? 0 : this.headers.length; logic(this, 'headersAppended', { insertAt: insertAt, matchPairs: matchPairs }); this.headers.splice.apply(this.headers, [ insertAt, 0 ].concat(matchPairs)); this.headerCount = this.headers.length + (atBottom ? 0 : this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM); this._bridgeHandle.sendSplice(insertAt, 0, matchPairs, true, moreMessagesComing || wantMore); if (wantMore) { console.log(logPrefix, 'requesting more because want more'); this.reqGrow(dir, false, true); } else if (!moreMessagesComing) { console.log(logPrefix, 'stopping (already reported), no want more.', 'can get more?', canGetMore); this._loading = false; this.desiredHeaders = this.headers.length; } } else if (!moreMessagesComing) { this.headerCount = this.headers.length + (atBottom ? 0 : this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM); if (wantMore) { console.log(logPrefix, 'requesting more because no matches but want more'); this._pendingBodyLoadLatch = null; this.reqGrow(dir, false, true); } else { console.log(logPrefix, 'stopping, no matches, no want more.', 'can get more?', canGetMore); this._bridgeHandle.sendStatus('synced', true, false); this._loading = false; this.desiredHeaders = this.headers.length; } } }.bind(this); if (this.filterer.bodiesNeeded) { if (headers.length) { var latch = this._pendingBodyLoadLatch = allback.latch(); for (var i = 0; i < headers.length; i++) { var header = headers[i]; this._storage.getMessageBody(header.suid, header.date, latch.defer(header.id)); } latch.then(checkHandle.bind(null, headers)); } else { var deferredCheck = checkHandle.bind(null, headers, null); if (this._pendingBodyLoadLatch) { this._pendingBodyLoadLatch.then(deferredCheck); } else { deferredCheck(); } } } else { checkHandle(headers, null); } }, refresh: function () { }, onHeaderAdded: function (header, body) { if (!this._bridgeHandle || this._loading) { return; } if (this.startTS === null || BEFORE(header.date, this.startTS)) { this.startTS = header.date; this.startUID = header.id; } else if (header.date === this.startTS && header.id < this.startUID) { this.startUID = header.id; } if (this.endTS === null || STRICTLY_AFTER(header.date, this.endTS)) { this.endTS = header.date; this.endUID = header.id; } else if (header.date === this.endTS && header.id > this.endUID) { this.endUID = header.id; } var matchObj = this.filterer.testMessage(header, body); if (!matchObj) { this.desiredHeaders = this.headers.length; return; } var wrappedHeader = { header: header, matches: matchObj }; var idx = bsearchForInsert(this.headers, wrappedHeader, cmpMatchHeadersYoungToOld); this.desiredHeaders = this.headers.length; logic(this, 'headerAdded', { index: idx, header: wrappedHeader }); this.headers.splice(idx, 0, wrappedHeader); this.headerCount = this.headers.length + (this.atBottom ? 0 : this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM); this._bridgeHandle.sendSplice(idx, 0, [wrappedHeader], false, false); }, onHeaderModified: function (header, body) { if (!this._bridgeHandle || this._loading) { return; } var wrappedHeader = { header: header, matches: null }; var idx = bsearchMaybeExists(this.headers, wrappedHeader, cmpMatchHeadersYoungToOld); if (idx !== null) { var existingMatch = this.headers[idx]; existingMatch.header = header; logic(this, 'headerModified', { index: idx, existingMatch: existingMatch }); this._bridgeHandle.sendUpdate([ idx, existingMatch ]); return; } if (!this.filterer.bodiesNeeded || !body) { return; } this.onHeaderAdded(header, body); }, onHeaderRemoved: function (header) { if (!this._bridgeHandle) { return; } if (header.date === this.endTS && header.id === this.endUID) { if (!this.headers.length) { this.endTS = null; this.endUID = null; } else { this.endTS = this.headers[0].header.date; this.endUID = this.headers[0].header.id; } } if (header.date === this.startTS && header.id === this.startUID) { if (!this.headers.length) { this.startTS = null; this.startUID = null; } else { var lastHeader = this.headers[this.headers.length - 1]; this.startTS = lastHeader.header.date; this.startUID = lastHeader.header.id; } } var wrappedHeader = { header: header, matches: null }; var idx = bsearchMaybeExists(this.headers, wrappedHeader, cmpMatchHeadersYoungToOld); if (idx !== null) { logic(this, 'headerRemoved', { index: idx, header: wrappedHeader }); this.headers.splice(idx, 1); this.headerCount = this.headers.length + (this.atBottom ? 0 : this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM); this._bridgeHandle.sendSplice(idx, 1, [], false, false); } }, reqNoteRanges: function (firstIndex, firstSuid, lastIndex, lastSuid) { var i; if (firstIndex >= this.headers.length || this.headers[firstIndex].suid !== firstSuid) { firstIndex = 0; for (i = 0; i < this.headers.length; i++) { if (this.headers[i].suid === firstSuid) { firstIndex = i; break; } } } if (lastIndex >= this.headers.length || this.headers[lastIndex].suid !== lastSuid) { for (i = this.headers.length - 1; i >= 0; i--) { if (this.headers[i].suid === lastSuid) { lastIndex = i; break; } } } if (lastIndex + 1 < this.headers.length) { this.atBottom = false; this.userCanGrowDownwards = false; var delCount = this.headers.length - lastIndex - 1; this.desiredHeaders -= delCount; this.headers.splice(lastIndex + 1, this.headers.length - lastIndex - 1); this.headerCount = this.headers.length + this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM; this._bridgeHandle.sendSplice(lastIndex + 1, delCount, [], true, firstIndex > 0); var lastHeader = this.headers[lastIndex].header; this.startTS = lastHeader.date; this.startUID = lastHeader.id; } if (firstIndex > 0) { this.atTop = false; this.desiredHeaders -= firstIndex; this.headers.splice(0, firstIndex); this.headerCount = this.headers.length + (this.atBottom ? 0 : this.IMAGINARY_MESSAGE_COUNT_WHEN_NOT_AT_BOTTOM); this._bridgeHandle.sendSplice(0, firstIndex, [], true, false); var firstHeader = this.headers[0].header; this.endTS = firstHeader.date; this.endUID = firstHeader.id; } }, reqGrow: function (dirMagnitude, userRequestsGrowth, autoDoNotDesireMore) { if (!autoDoNotDesireMore && this._loading) { return; } this._loading = true; var count; if (dirMagnitude < 0) { if (dirMagnitude === -1) { count = $syncbase.INITIAL_FILL_SIZE; } else { count = -dirMagnitude; } if (!autoDoNotDesireMore) { this.desiredHeaders += count; } this._storage.getMessagesAfterMessage(this.endTS, this.endUID, count, this._gotMessages.bind(this, -1)); } else { if (dirMagnitude <= 1) { count = $syncbase.INITIAL_FILL_SIZE; } else { count = dirMagnitude; } if (!autoDoNotDesireMore) { this.desiredHeaders += count; } this._storage.getMessagesBeforeMessage(this.startTS, this.startUID, count, this._gotMessages.bind(this, 1)); } }, die: function () { this._storage.dyingSlice(this); this._bridgeHandle = null; } }; }); ; define('wakelocks', [ 'require', './worker-router' ], function (require) { 'use strict'; var $router = require('./worker-router'); var sendMessage = $router.registerCallbackType('wakelocks'); function SmartWakeLock(opts) { this.timeoutMs = opts.timeout || SmartWakeLock.DEFAULT_TIMEOUT_MS; var locks = this.locks = {}; this._timeout = null; this._readyPromise = Promise.all(opts.locks.map(function (type) { return new Promise(function (resolve, reject) { sendMessage('requestWakeLock', [type], function (lockId) { locks[type] = lockId; resolve(); }); }); })).then(function () { this._debug('Acquired', this, 'for', this.timeoutMs + 'ms'); this.renew(); }.bind(this)); } SmartWakeLock.DEFAULT_TIMEOUT_MS = 45000; SmartWakeLock.prototype = { renew: function (reason, callback) { if (typeof reason === 'function') { callback = reason; reason = null; } this._readyPromise.then(function () { if (this._timeout) { clearTimeout(this._timeout); this._debug('Renewing', this, 'for another', this.timeoutMs + 'ms' + (reason ? ' (reason: ' + reason + ')' : '') + ',', 'would have expired in ' + (this.timeoutMs - (Date.now() - this._timeLastRenewed)) + 'ms if not renewed.'); } this._timeLastRenewed = Date.now(); this._timeout = setTimeout(function () { this._debug('*** Unlocking', this, 'due to a TIMEOUT. Did you remember to unlock? ***'); this.unlock.bind(this); }.bind(this), this.timeoutMs); callback && callback(); }.bind(this)); }, unlock: function (reason) { return this._readyPromise.then(function () { var desc = this.toString(); var locks = this.locks; this.locks = {}; clearTimeout(this._timeout); return Promise.all(Object.keys(locks).map(function (type) { return new Promise(function (resolve, reject) { sendMessage('unlock', [locks[type]], function (lockId) { resolve(); }); }); })).then(function () { this._debug('Unlocked', desc + '.', reason ? 'Reason: ' + reason : ''); }.bind(this)); }.bind(this)); }, toString: function () { return Object.keys(this.locks).join('+') || '(no locks)'; }, _debug: function () { var args = Array.slice(arguments); console.log.apply(console, ['SmartWakeLock:'].concat(args)); } }; return { SmartWakeLock: SmartWakeLock }; }); define('headerCounter', [ 'module', 'exports' ], function ($module, exports) { exports.countHeaders = function (storage, filter, options, callback) { var fetchClobber = null; if (typeof options === 'function') { callback = options; } else { fetchClobber = options.fetchSize; } var matched = 0; var fetchSize = fetchClobber || 100; var startTS = null; var startUID = null; function gotMessages(dir, callback, headers, moreMessagesComing) { var logPrefix = moreMessagesComing ? 'sf: ' : 'sf:'; console.log(logPrefix, 'gotMessages', headers.length, 'more coming?', moreMessagesComing); if (headers.length) { var lastHeader = headers[headers.length - 1]; startTS = lastHeader.date; startUID = lastHeader.id; } var checkHandle = function checkHandle(headers) { for (var i = 0; i < headers.length; i++) { var header = headers[i]; var isMatch = filter(header); if (isMatch) { matched++; } } var atBottom = storage.headerIsOldestKnown(startTS, startUID); var canGetMore = !atBottom, wantMore = !moreMessagesComing && canGetMore; if (wantMore) { console.log(logPrefix, 'requesting more because want more'); getNewMessages(dir, false, true, callback); } else if (!moreMessagesComing) { callback(matched); } }; checkHandle(headers); } function getNewMessages(dirMagnitude, userRequestsGrowth, autoDoNotDesireMore, callback) { storage.flushExcessCachedBlocks('countHeaders'); storage.getMessagesBeforeMessage(startTS, startUID, fetchSize, gotMessages.bind(null, 1, callback)); } storage.getMessagesInImapDateRange(0, null, fetchSize, fetchSize, gotMessages.bind(null, 1, callback)); }; }); ; define('jobmixins', [ './worker-router', './util', './allback', './wakelocks', './date', './syncbase', './mailslice', './headerCounter', 'logic', 'exports', 'require' ], function ($router, $util, $allback, $wakelocks, $date, $sync, $mailslice, $count, logic, exports, require) { var sendMessage = $router.registerCallbackType('devicestorage'); exports.local_do_modtags = function (op, doneCallback, undo) { var self = this; var addTags = undo ? op.removeTags : op.addTags, removeTags = undo ? op.addTags : op.removeTags; var mutationsPerformed = 0; this._partitionAndAccessFoldersSequentially(op.messages, false, function perFolder(ignoredConn, storage, headers, namers, callWhenDone) { var waitingOn = headers.length; function next() { if (--waitingOn === 0) callWhenDone(); } for (var iHeader = 0; iHeader < headers.length; iHeader++) { var header = headers[iHeader]; var iTag, tag, existing, modified = false; if (addTags) { for (iTag = 0; iTag < addTags.length; iTag++) { tag = addTags[iTag]; existing = header.flags.indexOf(tag); if (existing !== -1) continue; header.flags.push(tag); mutationsPerformed++; if (tag === '\\Seen') { storage.folderMeta.unreadCount--; } header.flags.sort(); modified = true; } } if (removeTags) { for (iTag = 0; iTag < removeTags.length; iTag++) { tag = removeTags[iTag]; existing = header.flags.indexOf(tag); if (existing === -1) continue; header.flags.splice(existing, 1); mutationsPerformed++; if (tag === '\\Seen') { storage.folderMeta.unreadCount++; } modified = true; } } storage.updateMessageHeader(header.date, header.id, false, header, null, next); } }, function () { if (mutationsPerformed === 0) { op.serverStatus = 'skip'; } doneCallback(null, null, true); }, null, undo, 'modtags'); }; exports.local_undo_modtags = function (op, callback) { return this.local_do_modtags(op, callback, true); }; exports.local_do_move = function (op, doneCallback, targetFolderId) { op.guids = {}; var nukeServerIds = !this.resilientServerIds; var stateDelta = this._stateDelta, addWait = 0, self = this; if (!stateDelta.moveMap) stateDelta.moveMap = {}; if (!stateDelta.serverIdMap) stateDelta.serverIdMap = {}; if (!targetFolderId) targetFolderId = op.targetFolder; this._partitionAndAccessFoldersSequentially(op.messages, false, function perFolder(ignoredConn, sourceStorage, headers, namers, perFolderDone) { function targetOpened_nowProcess(ignoredConn, _targetStorage) { targetStorage = _targetStorage; processNext(); } function processNext() { if (iNextHeader >= headers.length) { perFolderDone(); return; } header = headers[iNextHeader++]; sourceStorage.getMessageBody(header.suid, header.date, gotBody_nowDelete); } function gotBody_nowDelete(_body) { body = _body; if (header.srvid) stateDelta.serverIdMap[header.suid] = header.srvid; if (sourceStorage === targetStorage || sourceStorage.folderMeta.type === 'localdrafts' && targetStorage.folderMeta.type !== 'outbox' || sourceStorage.folderMeta.type === 'outbox' && targetStorage.folderMeta.type !== 'localdrafts') { if (op.type === 'move') { processNext(); } else { sourceStorage.deleteMessageHeaderAndBodyUsingHeader(header, processNext); } } else { sourceStorage.deleteMessageHeaderAndBodyUsingHeader(header, deleted_nowAdd); } } function deleted_nowAdd() { var sourceSuid = header.suid; header.id = targetStorage._issueNewHeaderId(); header.suid = targetStorage.folderId + '/' + header.id; if (nukeServerIds) header.srvid = null; stateDelta.moveMap[sourceSuid] = header.suid; addWait = 2; targetStorage.addMessageHeader(header, body, added); targetStorage.addMessageBody(header, body, added); } function added() { if (--addWait !== 0) return; processNext(); } var iNextHeader = 0, targetStorage = null, header = null, body = null, addWait = 0; if (sourceStorage.folderId === targetFolderId) { targetStorage = sourceStorage; processNext(); } else { self._accessFolderForMutation(targetFolderId, false, targetOpened_nowProcess, null, 'local move target'); } }, function () { doneCallback(null, stateDelta.moveMap, true); }, null, false, 'local move source'); }; exports.local_undo_move = function (op, doneCallback, targetFolderId) { doneCallback(null); }; exports.local_do_delete = function (op, doneCallback) { var trashFolder = this.account.getFirstFolderWithType('trash'); if (!trashFolder) { this.account.ensureEssentialOnlineFolders(); doneCallback('defer'); return; } this.local_do_move(op, doneCallback, trashFolder.id); }; exports.local_undo_delete = function (op, doneCallback) { var trashFolder = this.account.getFirstFolderWithType('trash'); if (!trashFolder) { doneCallback('unknown'); return; } this.local_undo_move(op, doneCallback, trashFolder.id); }; exports.do_download = function (op, callback) { var self = this; var idxLastSlash = op.messageSuid.lastIndexOf('/'), folderId = op.messageSuid.substring(0, idxLastSlash); var folderConn, folderStorage; var gotConn = function gotConn(_folderConn, _folderStorage) { folderConn = _folderConn; folderStorage = _folderStorage; folderStorage.getMessageHeader(op.messageSuid, op.messageDate, gotHeader); }; var deadConn = function deadConn() { callback('aborted-retry'); }; var partsToDownload = [], storePartsTo = [], registerDownload = [], header, bodyInfo, uid; var gotHeader = function gotHeader(_headerInfo) { header = _headerInfo; uid = header.srvid; folderStorage.getMessageBody(op.messageSuid, op.messageDate, gotBody); }; var gotBody = function gotBody(_bodyInfo) { bodyInfo = _bodyInfo; var i, partInfo; for (i = 0; i < op.relPartIndices.length; i++) { partInfo = bodyInfo.relatedParts[op.relPartIndices[i]]; if (partInfo.file) continue; partsToDownload.push(partInfo); storePartsTo.push('idb'); registerDownload.push(false); } for (i = 0; i < op.attachmentIndices.length; i++) { partInfo = bodyInfo.attachments[op.attachmentIndices[i]]; if (partInfo.file) continue; partsToDownload.push(partInfo); storePartsTo.push('sdcard'); registerDownload.push(op.registerAttachments[i]); } folderConn.downloadMessageAttachments(uid, partsToDownload, gotParts); }; var downloadErr = null; var gotParts = function gotParts(err, bodyBlobs) { if (bodyBlobs.length !== partsToDownload.length) { callback(err, null, false); return; } downloadErr = err; var pendingCbs = 1; function next() { if (!--pendingCbs) { done(); } } for (var i = 0; i < partsToDownload.length; i++) { var partInfo = partsToDownload[i], blob = bodyBlobs[i], storeTo = storePartsTo[i]; if (blob) { partInfo.sizeEstimate = blob.size; partInfo.type = blob.type; if (storeTo === 'idb') { partInfo.file = blob; } else { pendingCbs++; saveToDeviceStorage(self, blob, storeTo, registerDownload[i], partInfo.name, partInfo, next); } } } next(); }; function done() { folderStorage.updateMessageBody(header, bodyInfo, { flushBecause: 'blobs' }, { changeDetails: { attachments: op.attachmentIndices } }, function () { callback(downloadErr, null, true); }); } ; self._accessFolderForMutation(folderId, true, gotConn, deadConn, 'download'); }; var saveToDeviceStorage = exports.saveToDeviceStorage = function (scope, blob, storeTo, registerDownload, filename, partInfo, cb, isRetry) { var self = this; var callback = function (success, error, savedFilename, registered) { if (success) { logic(scope, 'savedAttachment', { storeTo: storeTo, type: blob.type, size: blob.size }); console.log('saved attachment to', storeTo, savedFilename, 'type:', blob.type, 'registered:', registered); partInfo.file = [ storeTo, savedFilename ]; cb(); } else { logic(scope, 'saveFailure', { storeTo: storeTo, type: blob.type, size: blob.size, error: error, filename: filename }); console.warn('failed to save attachment to', storeTo, filename, 'type:', blob.type); if (isRetry) { cb(error); return; } var idxLastPeriod = filename.lastIndexOf('.'); if (idxLastPeriod === -1) idxLastPeriod = filename.length; filename = filename.substring(0, idxLastPeriod) + '-' + $date.NOW() + filename.substring(idxLastPeriod); saveToDeviceStorage(scope, blob, storeTo, registerDownload, filename, partInfo, cb, true); } }; sendMessage('save', [ storeTo, blob, filename, registerDownload ], callback); }; exports.local_do_download = function (op, callback) { callback(null); }; exports.check_download = function (op, callback) { callback(null, 'coherent-notyet'); }; exports.local_undo_download = function (op, callback) { callback(null); }; exports.undo_download = function (op, callback) { callback(null); }; exports.local_do_downloadBodies = function (op, callback) { callback(null); }; exports.do_downloadBodies = function (op, callback) { var aggrErr = null, totalDownloaded = 0; this._partitionAndAccessFoldersSequentially(op.messages, true, function perFolder(folderConn, storage, headers, namers, callWhenDone) { folderConn.downloadBodies(headers, op.options, function (err, numDownloaded) { totalDownloaded += numDownloaded; if (err && !aggrErr) { aggrErr = err; } callWhenDone(); }); }, function allDone() { callback(aggrErr, null, totalDownloaded > 0); }, function deadConn() { aggrErr = 'aborted-retry'; }, false, 'downloadBodies', true); }; exports.check_downloadBodies = function (op, callback) { callback(null, 'coherent-notyet'); }; exports.check_downloadBodyReps = function (op, callback) { callback(null, 'coherent-notyet'); }; exports.do_downloadBodyReps = function (op, callback) { var self = this; var idxLastSlash = op.messageSuid.lastIndexOf('/'), folderId = op.messageSuid.substring(0, idxLastSlash); var folderConn, folderStorage; var gotConn = function gotConn(_folderConn, _folderStorage) { folderConn = _folderConn; folderStorage = _folderStorage; folderStorage.getMessageHeader(op.messageSuid, op.messageDate, gotHeader); }; var deadConn = function deadConn() { callback('aborted-retry'); }; var gotHeader = function gotHeader(header) { if (!header) { callback(); return; } folderStorage.getMessageBody(header.suid, header.date, function (body) { if (!folderStorage.messageBodyRepsDownloaded(body)) { folderConn.downloadBodyReps(header, onDownloadReps); } else { onDownloadReps(null, body, true); } }); }; var onDownloadReps = function onDownloadReps(err, bodyInfo, flushed) { if (err) { console.error('Error downloading reps', err); callback('unknown'); return; } var save = !flushed; callback(null, bodyInfo, save); }; self._accessFolderForMutation(folderId, true, gotConn, deadConn, 'downloadBodyReps'); }; exports.local_do_downloadBodyReps = function (op, callback) { callback(null); }; exports.do_sendOutboxMessages = function (op, callback) { var account = this.account; var outboxFolder = account.getFirstFolderWithType('outbox'); if (!outboxFolder) { callback('moot'); return; } if (!account.outboxSyncEnabled) { console.log('outbox: Outbox syncing temporarily disabled; not syncing.'); callback(null); return; } var outboxNeedsFreshSync = account.outboxNeedsFreshSync; if (outboxNeedsFreshSync) { console.log('outbox: This is the first outbox sync for this account.'); account.outboxNeedsFreshSync = false; } var wakeLock = new $wakelocks.SmartWakeLock({ locks: [ 'cpu', 'wifi' ] }); this._accessFolderForMutation(outboxFolder.id, false, function (nullFolderConn, folderStorage) { require(['jobs/outbox'], function ($outbox) { $outbox.sendNextAvailableOutboxMessage(account.compositeAccount || account, folderStorage, op.beforeMessage, op.emitNotifications, outboxNeedsFreshSync, wakeLock).then(function (result) { var moreExpected = result.moreExpected; var messageNamer = result.messageNamer; wakeLock.unlock('send complete'); if (moreExpected) { account.universe.sendOutboxMessages(account, { beforeMessage: messageNamer }); } else { account.universe.notifyOutboxSyncDone(account); folderStorage.markSyncRange($sync.OLDEST_SYNC_DATE, null, 'XXX', $date.NOW()); } callback(null, null, true); }).catch(function (e) { console.error('Exception while sending a message.', 'Send failure: ' + e, e.stack); wakeLock.unlock(e); callback('aborted-retry'); }); }); }, null, 'sendOutboxMessages'); }; exports.check_sendOutboxMessages = function (op, callback) { callback(null, 'moot'); }; exports.local_undo_sendOutboxMessages = function (op, callback) { callback(null); }; exports.local_do_setOutboxSyncEnabled = function (op, callback) { this.account.outboxSyncEnabled = op.outboxSyncEnabled; callback(null); }; exports.postJobCleanup = function (error) { if (!error) { var deltaMap, fullMap; if (this._stateDelta.serverIdMap) { deltaMap = this._stateDelta.serverIdMap; fullMap = this._state.suidToServerId; for (var suid in deltaMap) { var srvid = deltaMap[suid]; if (srvid === null) delete fullMap[suid]; else fullMap[suid] = srvid; } } if (this._stateDelta.moveMap) { deltaMap = this._stateDelta.moveMap; fullMap = this._state.moveMap; for (var oldSuid in deltaMap) { var newSuid = deltaMap[oldSuid]; fullMap[oldSuid] = newSuid; } } } for (var i = 0; i < this._heldMutexReleasers.length; i++) { this._heldMutexReleasers[i](error); } this._heldMutexReleasers = []; this._stateDelta.serverIdMap = null; this._stateDelta.moveMap = null; }; exports.allJobsDone = function () { this._state.suidToServerId = {}; this._state.moveMap = {}; }; exports._partitionAndAccessFoldersSequentially = function (allMessageNamers, needConn, callInFolder, callWhenDone, callOnConnLoss, reverse, label, requireHeaders) { var partitions = $util.partitionMessagesByFolderId(allMessageNamers); var folderConn, storage, self = this, folderId = null, folderMessageNamers = null, serverIds = null, iNextPartition = 0, curPartition = null, modsToGo = 0, terminated = false; if (reverse) partitions.reverse(); var openNextFolder = function openNextFolder() { if (terminated) return; if (iNextPartition >= partitions.length) { terminated = true; callWhenDone(null); return; } if (iNextPartition) { folderConn = null; var releaser = self._heldMutexReleasers.pop(); if (releaser) releaser(); folderConn = null; } curPartition = partitions[iNextPartition++]; folderMessageNamers = curPartition.messages; serverIds = null; if (curPartition.folderId !== folderId) { folderId = curPartition.folderId; self._accessFolderForMutation(folderId, needConn, gotFolderConn, connDied, label); } }; var connDied = function connDied() { if (terminated) return; if (callOnConnLoss) { try { callOnConnLoss(); } catch (ex) { self.log.error('callbackErr', { ex: ex }); } } terminated = true; callWhenDone('connection-lost'); }; var gotFolderConn = function gotFolderConn(_folderConn, _storage) { if (terminated) return; folderConn = _folderConn; storage = _storage; if (needConn && !requireHeaders) { var neededHeaders = [], suidToServerId = self._state.suidToServerId; serverIds = []; for (var i = 0; i < folderMessageNamers.length; i++) { var namer = folderMessageNamers[i]; var srvid = suidToServerId[namer.suid]; if (srvid) { serverIds.push(srvid); } else { serverIds.push(null); neededHeaders.push(namer); } } if (!neededHeaders.length) { try { callInFolder(folderConn, storage, serverIds, folderMessageNamers, openNextFolder); } catch (ex) { console.error('PAAFS error:', ex, '\n', ex.stack); } } else { storage.getMessageHeaders(neededHeaders, gotNeededHeaders); } } else { storage.getMessageHeaders(folderMessageNamers, gotHeaders); } }; var gotNeededHeaders = function gotNeededHeaders(headers) { if (terminated) return; var iNextServerId = serverIds.indexOf(null); for (var i = 0; i < headers.length; i++) { var header = headers[i]; if (header) { var srvid = header.srvid; serverIds[iNextServerId] = srvid; if (!srvid) console.warn('Header', headers[i].suid, 'missing server id in job!'); } iNextServerId = serverIds.indexOf(null, iNextServerId + 1); } if (!serverIds.length) { openNextFolder(); return; } try { callInFolder(folderConn, storage, serverIds, folderMessageNamers, openNextFolder); } catch (ex) { console.error('PAAFS error:', ex, '\n', ex.stack); } }; var gotHeaders = function gotHeaders(headers) { if (terminated) return; if (!headers.length) { openNextFolder(); return; } headers.sort(function (a, b) { return a.date > b.date; }); try { callInFolder(folderConn, storage, headers, folderMessageNamers, openNextFolder); } catch (ex) { console.error('PAAFS error:', ex, '\n', ex.stack); } }; openNextFolder(); }; exports.local_do_upgradeDB = function (op, doneCallback) { var storage = this.account.getFolderStorageForFolderId(op.folderId); var filter = function (header) { return header.flags && header.flags.indexOf('\\Seen') === -1; }; $count.countHeaders(storage, filter, function (num) { storage._dirty = true; storage.folderMeta.version = $mailslice.FOLDER_DB_VERSION; storage.folderMeta.unreadCount = num; doneCallback(null, null, true); }); }; }); ; define('jobs/outbox', ['require'], function (require) { function sendNextAvailableOutboxMessage(account, storage, beforeMessage, emitNotifications, outboxNeedsFreshSync, wakeLock) { return getNextHeader(storage, beforeMessage).then(function (header) { if (!header) { return { moreExpected: false, messageNamer: null }; } var moreExpected = !storage.headerIsOldestKnown(header.date, header.id); if (!header.sendStatus) { header.sendStatus = {}; } if (header.sendStatus.state !== 'sending' || outboxNeedsFreshSync) { return constructComposer(account, storage, header, wakeLock).then(sendMessage.bind(null, account, storage, emitNotifications)).then(function (header) { return { moreExpected: moreExpected, messageNamer: { suid: header.suid, date: header.date } }; }); } else { return sendNextAvailableOutboxMessage(account, storage, { suid: header.suid, date: header.date }, emitNotifications, outboxNeedsFreshSync, wakeLock); } }); } function getNextHeader(storage, beforeMessage) { return new Promise(function (resolve) { if (beforeMessage) { var id = parseInt(beforeMessage.suid.substring(beforeMessage.suid.lastIndexOf('/') + 1)); storage.getMessagesBeforeMessage(beforeMessage.date, id, 1, function (headers, moreExpected) { resolve(headers[0] || null); }); } else { storage.getMessagesInImapDateRange(0, null, 1, 1, function (headers, moreExpected) { resolve(headers[0]); }); } }); } function constructComposer(account, storage, header, wakeLock) { return new Promise(function (resolve, reject) { storage.getMessage(header.suid, header.date, function (msg) { if (!msg || !msg.body) { console.error('Failed to create composer; no body available.'); reject(); return; } require(['../drafts/composer'], function (cmp) { var composer = new cmp.Composer(msg, account, account.identities[0]); composer.setSmartWakeLock(wakeLock); resolve(composer); }); }); }); } function sendMessage(account, storage, emitNotifications, composer) { var header = composer.header; var progress = publishStatus.bind(null, account, storage, composer, header, emitNotifications); var oldestDate = storage.getOldestMessageTimestamp(); var willSendMore = oldestDate > 0 && oldestDate < header.date.valueOf(); progress({ state: 'sending', err: null, badAddresses: null, sendFailures: header.sendStatus && header.sendStatus.sendFailures || 0 }); return new Promise(function (resolve) { account.sendMessage(composer, function (err, badAddresses) { if (err) { console.log('Message failed to send (' + err + ')'); progress({ state: 'error', err: err, badAddresses: badAddresses, sendFailures: (header.sendStatus.sendFailures || 0) + 1 }); resolve(composer.header); } else { console.log('Message sent; deleting from outbox.'); progress({ state: 'success', err: null, badAddresses: null }); storage.deleteMessageHeaderAndBodyUsingHeader(header, function () { resolve(composer.header); }); } }); }); } function publishStatus(account, storage, composer, header, emitNotifications, status) { header.sendStatus = { state: status.state, err: status.err, badAddresses: status.badAddresses, sendFailures: status.sendFailures }; account.universe.__notifyBackgroundSendStatus({ state: status.state, err: status.err, badAddresses: status.badAddresses, sendFailures: status.sendFailures, accountId: account.id, suid: header.suid, emitNotifications: emitNotifications, messageId: composer.messageId, sentDate: composer.sentDate }); storage.updateMessageHeader(header.date, header.id, false, header, null); } return { sendNextAvailableOutboxMessage: sendNextAvailableOutboxMessage }; }); define('db/mail_rep', [], function () { function makeHeaderInfo(raw) { if (!raw.author) throw new Error('No author?!'); if (!raw.date) throw new Error('No date?!'); return { id: raw.id, srvid: raw.srvid || null, suid: raw.suid || null, guid: raw.guid || null, author: raw.author, to: raw.to || null, cc: raw.cc || null, bcc: raw.bcc || null, replyTo: raw.replyTo || null, date: raw.date, flags: raw.flags || [], hasAttachments: raw.hasAttachments || false, subject: raw.subject != null ? raw.subject : null, snippet: raw.snippet != null ? raw.snippet : null, imapMissingInSyncRange: raw.imapMissingInSyncRange || null }; } function makeBodyInfo(raw) { if (!raw.date) throw new Error('No date?!'); if (!raw.attachments || !raw.bodyReps) throw new Error('No attachments / bodyReps?!'); return { date: raw.date, size: raw.size || 0, attachments: raw.attachments, relatedParts: raw.relatedParts || null, references: raw.references || null, bodyReps: raw.bodyReps }; } function makeBodyPart(raw) { if (raw.type !== 'plain' && raw.type !== 'html') throw new Error('Bad body type: ' + raw.type); if (raw.sizeEstimate === undefined) throw new Error('Need size estimate!'); return { type: raw.type, part: raw.part || null, sizeEstimate: raw.sizeEstimate, amountDownloaded: raw.amountDownloaded || 0, isDownloaded: raw.isDownloaded || false, _partInfo: raw._partInfo || null, content: raw.content || '' }; } function makeAttachmentPart(raw) { if (raw.sizeEstimate === undefined) throw new Error('Need size estimate!'); return { name: raw.name != null ? raw.name : null, contentId: raw.contentId || null, type: raw.type || 'application/octet-stream', part: raw.part || null, encoding: raw.encoding || null, sizeEstimate: raw.sizeEstimate, file: raw.file || null, charset: raw.charset || null, textFormat: raw.textFormat || null }; } return { makeHeaderInfo: makeHeaderInfo, makeBodyInfo: makeBodyInfo, makeBodyPart: makeBodyPart, makeAttachmentPart: makeAttachmentPart }; }); ; define('drafts/draft_rep', [ 'require', '../db/mail_rep' ], function (require) { var mailRep = require('../db/mail_rep'); function mergeDraftStates(oldHeader, oldBody, newDraftRep, newDraftInfo, universe) { var identity = universe.getIdentityForSenderIdentityId(newDraftRep.senderId); var newHeader = mailRep.makeHeaderInfo({ id: newDraftInfo.id, srvid: null, suid: newDraftInfo.suid, guid: oldHeader ? oldHeader.guid : null, author: { name: identity.name, address: identity.address }, to: newDraftRep.to, cc: newDraftRep.cc, bcc: newDraftRep.bcc, replyTo: identity.replyTo, date: newDraftInfo.date, flags: [], hasAttachments: oldBody ? oldBody.attachments.length > 0 : false, subject: newDraftRep.subject, snippet: newDraftRep.body.text.substring(0, 100) }); var newBody = mailRep.makeBodyInfo({ date: newDraftInfo.date, size: 0, attachments: oldBody ? oldBody.attachments.concat() : [], relatedParts: oldBody ? oldBody.relatedParts.concat() : [], references: newDraftRep.referencesStr, bodyReps: [] }); newBody.bodyReps.push(mailRep.makeBodyPart({ type: 'plain', part: null, sizeEstimate: newDraftRep.body.text.length, amountDownloaded: newDraftRep.body.text.length, isDownloaded: true, _partInfo: {}, content: [ 1, newDraftRep.body.text ] })); if (newDraftRep.body.html) { newBody.bodyReps.push(mailRep.makeBodyPart({ type: 'html', part: null, sizeEstimate: newDraftRep.body.html.length, amountDownloaded: newDraftRep.body.html.length, isDownloaded: true, _partInfo: {}, content: newDraftRep.body.html })); } return { header: newHeader, body: newBody }; } function convertHeaderAndBodyToDraftRep(account, header, body) { var composeBody = { text: '', html: null }; if (body.bodyReps.length >= 1 && body.bodyReps[0].type === 'plain' && body.bodyReps[0].content.length === 2 && body.bodyReps[0].content[0] === 1) { composeBody.text = body.bodyReps[0].content[1]; } if (body.bodyReps.length == 2 && body.bodyReps[1].type === 'html') { composeBody.html = body.bodyReps[1].content; } var attachments = []; body.attachments.forEach(function (att) { attachments.push({ name: att.name, blob: att.file }); }); var draftRep = { identity: account.identities[0], subject: header.subject, body: composeBody, to: header.to, cc: header.cc, bcc: header.bcc, referencesStr: body.references, attachments: attachments }; } function cloneDraftMessageForSentFolderWithoutAttachments(header, body, newInfo) { var newHeader = mailRep.makeHeaderInfo(header); newHeader.id = newInfo.id; newHeader.suid = newInfo.suid; newHeader.flags = ['\\Seen']; var newBody = mailRep.makeBodyInfo(body); if (newBody.attachments) { newBody.attachments = newBody.attachments.map(function (oldAtt) { var newAtt = mailRep.makeAttachmentPart(oldAtt); newAtt.type = 'application/x-gelam-no-download'; newAtt.file = null; return newAtt; }); } if (newBody.relatedParts) { newBody.relatedParts = []; } newBody.bodyReps = newBody.bodyReps.map(function (oldRep) { return mailRep.makeBodyPart(oldRep); }); return { header: newHeader, body: newBody }; } return { mergeDraftStates: mergeDraftStates, convertHeaderAndBodyToDraftRep: convertHeaderAndBodyToDraftRep, cloneDraftMessageForSentFolderWithoutAttachments: cloneDraftMessageForSentFolderWithoutAttachments }; }); ; define('safe-base64', [ 'require', 'exports', 'module' ], function (require, exports, module) { exports.decode = function (s) { var bitsSoFar = 0, validBits = 0, iOut = 0, arr = new Uint8Array(Math.ceil(s.length * 3 / 4)); for (var i = 0; i < s.length; i++) { var c = s.charCodeAt(i), bits; if (c >= 65 && c <= 90) bits = c - 65; else if (c >= 97 && c <= 122) bits = c - 97 + 26; else if (c >= 48 && c <= 57) bits = c - 48 + 52; else if (c === 43) bits = 62; else if (c === 47) bits = 63; else if (c === 61) { validBits = 0; continue; } else continue; bitsSoFar = bitsSoFar << 6 | bits; validBits += 6; if (validBits >= 8) { validBits -= 8; arr[iOut++] = bitsSoFar >> validBits; if (validBits === 2) bitsSoFar &= 3; else if (validBits === 4) bitsSoFar &= 15; } } if (iOut < arr.length) return arr.subarray(0, iOut); return arr; }; exports.encode = function (view) { var sbits, i; sbits = new Array(view.length); for (i = 0; i < view.length; i++) { sbits[i] = String.fromCharCode(view[i]); } return window.btoa(sbits.join('')); }; exports.mimeStyleBase64Encode = function (data) { var wholeLines = Math.floor(data.length / 57); var partialBytes = data.length - wholeLines * 57; var encodedLength = wholeLines * 78; if (partialBytes) { encodedLength += Math.ceil(partialBytes / 3) * 4 + 2; } var encoded = new Uint8Array(encodedLength); function encode6Bits(nibbly) { if (nibbly <= 25) { encoded[iWrite++] = 65 + nibbly; } else if (nibbly <= 51) { encoded[iWrite++] = 97 - 26 + nibbly; } else if (nibbly <= 61) { encoded[iWrite++] = 48 - 52 + nibbly; } else if (nibbly === 62) { encoded[iWrite++] = 43; } else { encoded[iWrite++] = 47; } } var iRead = 0, iWrite = 0, bytesToRead; for (bytesToRead = data.length; bytesToRead >= 3; bytesToRead -= 3) { var b1 = data[iRead++], b2 = data[iRead++], b3 = data[iRead++]; encode6Bits(b1 >> 2); encode6Bits((b1 & 3) << 4 | b2 >> 4); encode6Bits((b2 & 15) << 2 | b3 >> 6); encode6Bits(b3 & 63); if (iRead % 57 === 0 || bytesToRead === 3) { encoded[iWrite++] = 13; encoded[iWrite++] = 10; } } switch (bytesToRead) { case 2: b1 = data[iRead++]; b2 = data[iRead++]; encode6Bits(b1 >> 2); encode6Bits((b1 & 3) << 4 | b2 >> 4); encode6Bits((b2 & 15) << 2 | 0); encoded[iWrite++] = 61; encoded[iWrite++] = 13; encoded[iWrite++] = 10; break; case 1: b1 = data[iRead++]; encode6Bits(b1 >> 2); encode6Bits((b1 & 3) << 4 | 0); encoded[iWrite++] = 61; encoded[iWrite++] = 61; encoded[iWrite++] = 13; encoded[iWrite++] = 10; break; } return encoded; }; }); ; define('async_blob_fetcher', ['exports'], function (exports) { function asyncFetchBlobAsUint8Array(blob, callback) { var blobUrl = URL.createObjectURL(blob); var xhr = new XMLHttpRequest(); xhr.open('GET', blobUrl, true); xhr.responseType = 'arraybuffer'; xhr.onload = function () { if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) { callback(xhr.status); return; } callback(null, new Uint8Array(xhr.response)); }; xhr.onerror = function () { callback('error'); }; try { xhr.send(); } catch (ex) { console.error('XHR send() failure on blob'); callback('error'); } URL.revokeObjectURL(blobUrl); } return { asyncFetchBlobAsUint8Array: asyncFetchBlobAsUint8Array }; }); ; define('drafts/jobs', [ 'require', 'exports', 'module', '../db/mail_rep', '../drafts/draft_rep', 'safe-base64', '../async_blob_fetcher' ], function (require, exports) { var mailRep = require('../db/mail_rep'); var draftRep = require('../drafts/draft_rep'); var base64 = require('safe-base64'); var asyncFetchBlobAsUint8Array = require('../async_blob_fetcher').asyncFetchBlobAsUint8Array; var draftsMixins = exports.draftsMixins = {}; draftsMixins.BLOB_BASE64_BATCH_CONVERT_SIZE = 9198 * 57; draftsMixins.local_do_attachBlobToDraft = function (op, callback) { var localDraftsFolder = this.account.getFirstFolderWithType('localdrafts'); if (!localDraftsFolder) { callback('moot'); return; } var self = this; this._accessFolderForMutation(localDraftsFolder.id, false, function (nullFolderConn, folderStorage) { var wholeBlob = op.attachmentDef.blob; var header, body; console.log('attachBlobToDraft: retrieving message'); folderStorage.getMessage(op.existingNamer.suid, op.existingNamer.date, {}, gotMessage); function gotMessage(records) { header = records.header; body = records.body; if (!header || !body) { callback('failure-give-up'); return; } body.attaching = mailRep.makeAttachmentPart({ name: op.attachmentDef.name, type: wholeBlob.type, sizeEstimate: wholeBlob.size, file: [] }); convertNextChunk(body); } var blobOffset = 0; function convertNextChunk(refreshedBody) { body = refreshedBody; var nextOffset = Math.min(wholeBlob.size, blobOffset + self.BLOB_BASE64_BATCH_CONVERT_SIZE); console.log('attachBlobToDraft: fetching', blobOffset, 'to', nextOffset, 'of', wholeBlob.size); var slicedBlob = wholeBlob.slice(blobOffset, nextOffset); blobOffset = nextOffset; asyncFetchBlobAsUint8Array(slicedBlob, gotChunk); } function gotChunk(err, binaryDataU8) { console.log('attachBlobToDraft: fetched'); if (err) { callback('failure-give-up'); return; } var lastChunk = blobOffset >= wholeBlob.size; var encodedU8 = base64.mimeStyleBase64Encode(binaryDataU8); body.attaching.file.push(new Blob([encodedU8], { type: wholeBlob.type })); var eventDetails; if (lastChunk) { var attachmentIndex = body.attachments.length; body.attachments.push(body.attaching); delete body.attaching; eventDetails = { changeDetails: { attachments: [attachmentIndex] } }; } else { eventDetails = null; } console.log('attachBlobToDraft: flushing'); folderStorage.updateMessageBody(header, body, { flushBecause: 'blobs' }, eventDetails, lastChunk ? bodyUpdatedAllDone : convertNextChunk); body = null; } function bodyUpdatedAllDone(newBodyInfo) { console.log('attachBlobToDraft: blob fully attached'); callback(null); } }, null, 'attachBlobToDraft'); }; draftsMixins.do_attachBlobToDraft = function (op, callback) { callback(null); }; draftsMixins.check_attachBlobToDraft = function (op, callback) { callback(null, 'moot'); }; draftsMixins.local_undo_attachBlobToDraft = function (op, callback) { callback(null); }; draftsMixins.undo_attachBlobToDraft = function (op, callback) { callback(null); }; draftsMixins.local_do_detachAttachmentFromDraft = function (op, callback) { var localDraftsFolder = this.account.getFirstFolderWithType('localdrafts'); if (!localDraftsFolder) { callback('moot'); return; } var self = this; this._accessFolderForMutation(localDraftsFolder.id, false, function (nullFolderConn, folderStorage) { var header, body; console.log('detachAttachmentFromDraft: retrieving message'); folderStorage.getMessage(op.existingNamer.suid, op.existingNamer.date, {}, gotMessage); function gotMessage(records) { header = records.header; body = records.body; if (!header || !body) { callback('failure-give-up'); return; } body.attachments.splice(op.attachmentIndex, 1); console.log('detachAttachmentFromDraft: flushing'); folderStorage.updateMessageBody(header, body, { flushBecause: 'blobs' }, { changeDetails: { detachedAttachments: [op.attachmentIndex] } }, bodyUpdatedAllDone); } function bodyUpdatedAllDone(newBodyInfo) { console.log('detachAttachmentFromDraft: blob fully detached'); callback(null); } }, null, 'detachAttachmentFromDraft'); }; draftsMixins.do_detachAttachmentFromDraft = function (op, callback) { callback(null); }; draftsMixins.check_detachAttachmentFromDraft = function (op, callback) { callback(null); }; draftsMixins.local_undo_detachAttachmentFromDraft = function (op, callback) { callback(null); }; draftsMixins.undo_detachAttachmentFromDraft = function (op, callback) { callback(null); }; draftsMixins.local_do_saveDraft = function (op, callback) { var localDraftsFolder = this.account.getFirstFolderWithType('localdrafts'); if (!localDraftsFolder) { callback('moot'); return; } var self = this; this._accessFolderForMutation(localDraftsFolder.id, false, function (nullFolderConn, folderStorage) { var waitingForDbMods = 2; function gotMessage(oldRecords) { var newRecords = draftRep.mergeDraftStates(oldRecords.header, oldRecords.body, op.draftRep, op.newDraftInfo, self.account.universe); if (op.existingNamer) { waitingForDbMods++; folderStorage.deleteMessageHeaderAndBody(op.existingNamer.suid, op.existingNamer.date, dbModCompleted); } folderStorage.addMessageHeader(newRecords.header, newRecords.body, dbModCompleted); folderStorage.addMessageBody(newRecords.header, newRecords.body, dbModCompleted); function dbModCompleted() { if (--waitingForDbMods === 0) { callback(null, newRecords, true); } } } if (op.existingNamer) { folderStorage.getMessage(op.existingNamer.suid, op.existingNamer.date, null, gotMessage); } else { gotMessage({ header: null, body: null }); } }, null, 'saveDraft'); }; draftsMixins.do_saveDraft = function (op, callback) { callback(null); }; draftsMixins.check_saveDraft = function (op, callback) { callback(null, 'moot'); }; draftsMixins.local_undo_saveDraft = function (op, callback) { callback(null); }; draftsMixins.undo_saveDraft = function (op, callback) { callback(null); }; draftsMixins.local_do_deleteDraft = function (op, callback) { var localDraftsFolder = this.account.getFirstFolderWithType('localdrafts'); if (!localDraftsFolder) { callback('moot'); return; } var self = this; this._accessFolderForMutation(localDraftsFolder.id, false, function (nullFolderConn, folderStorage) { folderStorage.deleteMessageHeaderAndBody(op.messageNamer.suid, op.messageNamer.date, function () { callback(null, null, true); }); }, null, 'deleteDraft'); }; draftsMixins.do_deleteDraft = function (op, callback) { callback(null); }; draftsMixins.check_deleteDraft = function (op, callback) { callback(null, 'moot'); }; draftsMixins.local_undo_deleteDraft = function (op, callback) { callback(null); }; draftsMixins.undo_deleteDraft = function (op, callback) { callback(null); }; }); ; define('disaster-recovery', [ 'require', './logic' ], function (require) { var logic = require('./logic'); var socketToAccountMap = new WeakMap(); var accountToOperationMap = new WeakMap(); var scope = logic.scope('DisasterRecovery'); var DisasterRecovery = { setCurrentAccountOp: function (account, op, jobCompletedCallback) { accountToOperationMap.set(account, { op: op, callback: jobCompletedCallback }); }, clearCurrentAccountOp: function (account) { accountToOperationMap.delete(account); }, associateSocketWithAccount: function (socket, account) { socketToAccountMap.set(socket, account); }, catchSocketExceptions: function (socket, fn) { try { fn(); } catch (e) { var account = socketToAccountMap.get(socket); try { socket.close(); } catch (socketEx) { console.error('Error attempting to close socket:', socketEx); } this.handleDisastrousError(e, account); } }, handleDisastrousError: function (e, account) { var op, jobDoneCallback; if (account) { var opInfo = accountToOperationMap.get(account); if (opInfo) { op = opInfo.op; jobDoneCallback = opInfo.callback; } } logic(scope, 'exception', { accountId: account && account.id, op: op, error: e, errorName: e && e.name, errorMessage: e && e.message, stack: e.stack }); console.error('*** Disastrous Error for email accountId', account && account.id, '-- attempting to recover...'); if (account) { if (op) { logic(scope, 'finished-job', { error: e }); console.warn('Force-completing in-progress op:', op); jobDoneCallback('disastrous-error'); } else { console.warn('No job operation was currently running.'); } } else { console.warn('No account associated with this error; nothing to abort.'); } } }; return DisasterRecovery; }); define('accountmixins', [ 'require', 'exports', 'module', './disaster-recovery', 'logic' ], function (require, exports) { var DisasterRecovery = require('./disaster-recovery'); var logic = require('logic'); function unimplementedJobOperation(op, callback) { window.setZeroTimeout(function () { callback(null, null); }); } exports.accountConstructorMixin = function (receivePiece, sendPiece) { receivePiece.outboxNeedsFreshSync = true; receivePiece.outboxSyncEnabled = true; }; exports.runOp = function runOp(op, mode, callback) { console.log('runOp(' + mode + ': ' + JSON.stringify(op).substring(0, 160) + ')'); var methodName = mode + '_' + op.type; var method = this._jobDriver[methodName]; if (!method) { console.warn('Unsupported op:', op.type, 'mode:', mode); method = unimplementedJobOperation; } var alreadyCompleted = false; var jobCompletedCallback = function (error, resultIfAny, accountSaveSuggested) { if (alreadyCompleted) { console.warn('Job already completed, ignoring secondary completion:', mode, JSON.stringify(op).substring(0, 160), error, resultIfAny); return; } alreadyCompleted = true; DisasterRecovery.clearCurrentAccountOp(this); this._jobDriver.postJobCleanup(error); window.setZeroTimeout(function () { callback(error, resultIfAny, accountSaveSuggested); }); }.bind(this); DisasterRecovery.setCurrentAccountOp(this, op, jobCompletedCallback); logic(this, 'runOp_begin', { mode: mode, type: op.type, op: op }); Object.defineProperty(op, '_logicAsyncEvent', { configurable: true, enumerable: false, value: logic.startAsync(this, 'runOp', { mode: mode, type: op.type, op: op }) }); try { method.call(this._jobDriver, op, jobCompletedCallback); } catch (ex) { DisasterRecovery.clearCurrentAccountOp(this); logic(this, 'opError', { mode: mode, type: op.type, ex: ex }); } }; exports.getFirstFolderWithType = function (type) { var folders = this.folders; for (var iFolder = 0; iFolder < folders.length; iFolder++) { if (folders[iFolder].type === type) return folders[iFolder]; } return null; }; exports.getFolderByPath = function (folderPath) { var folders = this.folders; for (var iFolder = 0; iFolder < folders.length; iFolder++) { if (folders[iFolder].path === folderPath) return folders[iFolder]; } return null; }; exports.normalizeFolderHierarchy = function () { var sibling = this.getFirstFolderWithType('drafts') || this.getFirstFolderWithType('sent'); if (!sibling) { return; } var parent = this.getFolderMetaForFolderId(sibling.parentId); var foldersToMove = [ this.getFirstFolderWithType('localdrafts'), this.getFirstFolderWithType('outbox') ]; foldersToMove.forEach(function (folder) { if (!folder || folder.parentId === sibling.parentId) { return; } console.log('Moving folder', folder.name, 'underneath', parent && parent.name || '(root)'); this.universe.__notifyRemovedFolder(this, folder); if (parent) { folder.path = parent.path + (parent.delim || '/') + folder.name; folder.delim = parent.delim || '/'; folder.parentId = parent.id; folder.depth = parent.depth + 1; } else { folder.path = folder.name; folder.delim = '/'; folder.parentId = null; folder.depth = 0; } this.universe.__notifyAddedFolder(this, folder); }, this); }; exports.saveAccountState = function (reuseTrans, callback, reason) { if (!this._alive) { logic(this, 'accountDeleted', { reason: 'saveAccountState' }); return null; } logic(this, 'saveAccountState_begin', { reason: reason, folderSaveCount: null }); this._saveAccountStateActive = true; if (!this._deferredSaveAccountCalls) { this._deferredSaveAccountCalls = []; } if (callback) this.runAfterSaves(callback); var perFolderStuff = [], self = this; for (var iFolder = 0; iFolder < this.folders.length; iFolder++) { var folderPub = this.folders[iFolder], folderStorage = this._folderStorages[folderPub.id], folderStuff = folderStorage.generatePersistenceInfo(); if (folderStuff) perFolderStuff.push(folderStuff); } var folderSaveCount = perFolderStuff.length; var trans = this._db.saveAccountFolderStates(this.id, this._folderInfos, perFolderStuff, this._deadFolderIds, function stateSaved() { this._saveAccountStateActive = false; logic(this, 'saveAccountState_end', { reason: reason, folderSaveCount: folderSaveCount }); var callbacks = this._deferredSaveAccountCalls; this._deferredSaveAccountCalls = []; callbacks.forEach(function (callback) { callback(); }); }.bind(this), reuseTrans); perFolderStuff = null; this._deadFolderIds = null; return trans; }; exports.runAfterSaves = function (callback) { if (this._saveAccountStateActive || this._saveAccountIsImminent) { this._deferredSaveAccountCalls.push(callback); } else { callback(); } }; exports.upgradeFolderStoragesIfNeeded = function () { for (var key in this._folderStorages) { var storage = this._folderStorages[key]; storage.upgradeIfNeeded(); } }; }); ; define('quotechew', ['exports'], function (exports) { var CT_AUTHORED_CONTENT = 1; var CT_AUTHORED_NICETIES = 17; var CT_SIGNATURE = 2; var CT_LEADIN_TO_QUOTE = 3; var CT_QUOTED_TYPE = 4; var CT_QUOTED_REPLY = 20; var CT_QUOTED_FORWARD = 36; var CT_QUOTED_IN_ENTIRETY = 64; var CT_QUOTED_GARDENED = 128; var CT_QUOTE_DEPTH_MASK = 65280; var CT_BOILERPLATE_DISCLAIMER = 5; var CT_BOILERPLATE_LIST_INFO = 6; var CT_BOILERPLATE_PRODUCT = 7; var CT_BOILERPLATE_ADS = 8; var CHARCODE_GT = '>'.charCodeAt(0), CHARCODE_SPACE = ' '.charCodeAt(0), CHARCODE_NBSP = '\xA0'.charCodeAt(0), CHARCODE_NEWLINE = '\n'.charCodeAt(0); var RE_ORIG_MESAGE_DELIM = /^-{5} Original Message -{5}$/; var RE_ALL_WS = /^\s+$/; var RE_SECTION_DELIM = /^[_-]{6,}$/; var RE_LIST_BOILER = /mailing list$/; var RE_WROTE_LINE = /wrote/; var RE_REPLY_LAST_LINE_IN_BLOCK_CONTAINS_WROTE = /wrote[^\n]+$/; var RE_SIGNATURE_LINE = /^-- $/; var MAX_BOILERPLATE_LINES = 20; var RE_PRODUCT_BOILER = /^(?:Sent from (?:Mobile|my .+))$/; var RE_LEGAL_BOILER_START = /^(?:This message|Este mensaje)/; function indexOfDefault(string, search, startIndex, defVal) { var idx = string.indexOf(search, startIndex); if (idx === -1) return defVal; return idx; } var NEWLINE = '\n', RE_NEWLINE = /\n/g; function countNewlinesInRegion(string, startIndex, endIndex) { var idx = startIndex - 1, count = 0; for (;;) { idx = string.indexOf(NEWLINE, idx + 1); if (idx === -1 || idx >= endIndex) return count; count++; } return null; } exports.quoteProcessTextBody = function quoteProcessTextBody(fullBodyText) { var contentRep = []; var line; function countQuoteDepthAndNormalize() { var count = 1; var lastStartOffset = 1, spaceOk = true; for (var i = 1; i < line.length; i++) { var c = line.charCodeAt(i); if (c === CHARCODE_GT) { count++; lastStartOffset++; spaceOk = true; } else if (c === CHARCODE_SPACE) { if (!spaceOk) break; lastStartOffset++; spaceOk = false; } else { break; } } if (lastStartOffset) line = line.substring(lastStartOffset); return count; } function lookBackwardsForBoilerplate(chunk) { var idxLineStart, idxLineEnd, line, idxRegionEnd = chunk.length, scanLinesLeft = MAX_BOILERPLATE_LINES, sawNonWhitespaceLine = false, lastContentLine = null, lastBoilerplateStart = null, sawProduct = false, insertAt = contentRep.length; function pushBoilerplate(contentType, merge) { var boilerChunk = chunk.substring(idxLineStart, idxRegionEnd); var idxChunkEnd = idxLineStart - 1; while (chunk.charCodeAt(idxChunkEnd - 1) === CHARCODE_NEWLINE) { idxChunkEnd--; } var newChunk = chunk.substring(0, idxChunkEnd); var ate = countNewlinesInRegion(chunk, newChunk.length, idxLineStart - 1); chunk = newChunk; idxRegionEnd = chunk.length; if (!merge) { contentRep.splice(insertAt, 0, (ate & 255) << 8 | contentType, boilerChunk); } else { contentRep[insertAt] = (ate & 255) << 8 | contentRep[insertAt] & 255; contentRep[insertAt + 1] = boilerChunk + '\n' + contentRep[insertAt + 1]; } sawNonWhitespaceLine = false; scanLinesLeft = MAX_BOILERPLATE_LINES; lastContentLine = null; lastBoilerplateStart = idxLineStart; } for (idxLineStart = chunk.lastIndexOf('\n') + 1, idxLineEnd = chunk.length; idxLineEnd > 0 && scanLinesLeft; idxLineEnd = idxLineStart - 1, idxLineStart = chunk.lastIndexOf('\n', idxLineEnd - 1) + 1, scanLinesLeft--) { line = chunk.substring(idxLineStart, idxLineEnd); if (!line.length || line.length === 1 && line.charCodeAt(0) === CHARCODE_NBSP) continue; if (RE_SIGNATURE_LINE.test(line)) { if (idxLineEnd + 1 === lastBoilerplateStart) { pushBoilerplate(null, true); } else { pushBoilerplate(CT_SIGNATURE); } continue; } if (RE_SECTION_DELIM.test(line)) { if (lastContentLine) { if (RE_LEGAL_BOILER_START.test(lastContentLine)) { pushBoilerplate(CT_BOILERPLATE_DISCLAIMER); continue; } if (RE_LIST_BOILER.test(lastContentLine)) { pushBoilerplate(CT_BOILERPLATE_LIST_INFO); continue; } } return chunk; } if (!sawNonWhitespaceLine) { if (!sawProduct && RE_PRODUCT_BOILER.test(line)) { pushBoilerplate(CT_BOILERPLATE_PRODUCT); sawProduct = true; continue; } sawNonWhitespaceLine = true; } lastContentLine = line; } return chunk; } function pushContent(considerForBoilerplate, upToPoint, forcePostLine) { if (idxRegionStart === null) { if (atePreLines) { if (contentRep.length) atePreLines--; contentRep.push((atePreLines & 255) << 8 | CT_AUTHORED_CONTENT); contentRep.push(''); } } else { if (upToPoint === undefined) upToPoint = idxLineStart; var chunk = fullBodyText.substring(idxRegionStart, idxLastNonWhitespaceLineEnd); var atePostLines = forcePostLine ? 1 : 0; if (idxLastNonWhitespaceLineEnd + 1 !== upToPoint) { atePostLines += countNewlinesInRegion(fullBodyText, idxLastNonWhitespaceLineEnd + 1, upToPoint); } contentRep.push((atePreLines & 255) << 8 | (atePostLines & 255) << 16 | CT_AUTHORED_CONTENT); var iChunk = contentRep.push(chunk) - 1; if (considerForBoilerplate) { var newChunk = lookBackwardsForBoilerplate(chunk); if (chunk.length !== newChunk.length) { if (atePostLines) { var iLastMeta = contentRep.length - 2; contentRep[iLastMeta] = (atePostLines & 255) << 16 | contentRep[iLastMeta]; contentRep[iChunk - 1] = (atePreLines & 255) << 8 | CT_AUTHORED_CONTENT; } if (!newChunk.length) { if (atePreLines) { var bpAte = contentRep[iChunk + 1] >> 8 & 255; bpAte += atePreLines; contentRep[iChunk + 1] = (bpAte & 255) << 8 | contentRep[iChunk + 1] & 4294902015; } contentRep.splice(iChunk - 1, 2); } else { contentRep[iChunk] = newChunk; } } } } atePreLines = 0; idxRegionStart = null; lastNonWhitespaceLine = null; idxLastNonWhitespaceLineEnd = null; idxPrevLastNonWhitespaceLineEnd = null; } function pushQuote(newQuoteDepth) { var atePostLines = 0; while (quoteRunLines.length && !quoteRunLines[quoteRunLines.length - 1]) { quoteRunLines.pop(); atePostLines++; } contentRep.push((atePostLines & 255) << 24 | (ateQuoteLines & 255) << 16 | inQuoteDepth - 1 << 8 | CT_QUOTED_REPLY); contentRep.push(quoteRunLines.join('\n')); inQuoteDepth = newQuoteDepth; if (inQuoteDepth) quoteRunLines = []; else quoteRunLines = null; ateQuoteLines = 0; generatedQuoteBlock = true; } var idxLineStart, idxLineEnd, bodyLength = fullBodyText.length, idxRegionStart = null, lastNonWhitespaceLine = null, idxLastNonWhitespaceLineEnd = null, idxPrevLastNonWhitespaceLineEnd = null, inQuoteDepth = 0, quoteRunLines = null, generatedQuoteBlock = false, atePreLines = 0, ateQuoteLines = 0; for (idxLineStart = 0, idxLineEnd = indexOfDefault(fullBodyText, '\n', idxLineStart, fullBodyText.length); idxLineStart < bodyLength; idxLineStart = idxLineEnd + 1, idxLineEnd = indexOfDefault(fullBodyText, '\n', idxLineStart, fullBodyText.length)) { line = fullBodyText.substring(idxLineStart, idxLineEnd); if (!line.length || line.length === 1 && line.charCodeAt(0) === CHARCODE_NBSP) { if (inQuoteDepth) pushQuote(0); if (idxRegionStart === null) atePreLines++; continue; } if (line.charCodeAt(0) === CHARCODE_GT) { var lineDepth = countQuoteDepthAndNormalize(); if (!inQuoteDepth) { if (lastNonWhitespaceLine && RE_WROTE_LINE.test(lastNonWhitespaceLine)) { var upToPoint = idxLastNonWhitespaceLineEnd; if (idxPrevLastNonWhitespaceLineEnd !== null) { var considerIndex = idxPrevLastNonWhitespaceLineEnd + 1; while (considerIndex < idxLastNonWhitespaceLineEnd) { if (fullBodyText[considerIndex++] === '\n') { break; } } if (considerIndex === idxLastNonWhitespaceLineEnd) { upToPoint = fullBodyText.lastIndexOf('\n', idxPrevLastNonWhitespaceLineEnd - 1); lastNonWhitespaceLine = fullBodyText.substring(upToPoint + 1, idxLastNonWhitespaceLineEnd).replace(/\s*\n\s*/, ' '); idxPrevLastNonWhitespaceLineEnd = upToPoint - 1; if (idxPrevLastNonWhitespaceLineEnd <= idxRegionStart) { idxRegionStart = null; } } } idxLastNonWhitespaceLineEnd = idxPrevLastNonWhitespaceLineEnd; if (idxLastNonWhitespaceLineEnd === null) idxRegionStart = null; var leadin = lastNonWhitespaceLine; pushContent(!generatedQuoteBlock, upToPoint); var leadinNewlines = 0; if (upToPoint + 1 !== idxLineStart) leadinNewlines = countNewlinesInRegion(fullBodyText, upToPoint + 1, idxLineStart); contentRep.push(leadinNewlines << 8 | CT_LEADIN_TO_QUOTE); contentRep.push(leadin); } else { pushContent(!generatedQuoteBlock); } quoteRunLines = []; inQuoteDepth = lineDepth; } else if (lineDepth !== inQuoteDepth) { pushQuote(lineDepth); } if (quoteRunLines.length || line.length) { quoteRunLines.push(line); } else { ateQuoteLines++; } } else { if (inQuoteDepth) { pushQuote(0); idxLastNonWhitespaceLineEnd = null; } if (idxRegionStart === null) idxRegionStart = idxLineStart; lastNonWhitespaceLine = line; idxPrevLastNonWhitespaceLineEnd = idxLastNonWhitespaceLineEnd; idxLastNonWhitespaceLineEnd = idxLineEnd; } } if (inQuoteDepth) { pushQuote(0); } else { pushContent(true, fullBodyText.length, fullBodyText.charCodeAt(fullBodyText.length - 1) === CHARCODE_NEWLINE); } return contentRep; }; var MAX_WORD_SHRINK = 8; var RE_NORMALIZE_WHITESPACE = /\s+/g; exports.generateSnippet = function generateSnippet(rep, desiredLength) { for (var i = 0; i < rep.length; i += 2) { var etype = rep[i] & 15, block = rep[i + 1]; switch (etype) { case CT_AUTHORED_CONTENT: if (!block.length) break; if (block.length < desiredLength) return block.trim().replace(RE_NORMALIZE_WHITESPACE, ' '); var idxPrevSpace = block.lastIndexOf(' ', desiredLength); if (desiredLength - idxPrevSpace < MAX_WORD_SHRINK) return block.substring(0, idxPrevSpace).trim().replace(RE_NORMALIZE_WHITESPACE, ' '); return block.substring(0, desiredLength).trim().replace(RE_NORMALIZE_WHITESPACE, ' '); } } return ''; }; var MAX_QUOTE_REPEAT_DEPTH = 5; var replyQuotePrefixStrings = [ '> ', '>> ', '>>> ', '>>>> ', '>>>>> ', '>>>>>> ', '>>>>>>> ', '>>>>>>>> ', '>>>>>>>>> ' ]; var replyQuotePrefixStringsNoSpace = [ '>', '>>', '>>>', '>>>>', '>>>>>', '>>>>>>', '>>>>>>>', '>>>>>>>>', '>>>>>>>>>' ]; var replyQuoteNewlineReplaceStrings = [ '\n> ', '\n>> ', '\n>>> ', '\n>>>> ', '\n>>>>> ', '\n>>>>>> ', '\n>>>>>>> ', '\n>>>>>>>> ' ]; var replyQuoteNewlineReplaceStringsNoSpace = [ '\n>', '\n>>', '\n>>>', '\n>>>>', '\n>>>>>', '\n>>>>>>', '\n>>>>>>>', '\n>>>>>>>>' ]; var replyQuoteBlankLine = [ '\n', '>\n', '>>\n', '>>>\n', '>>>>\n', '>>>>>\n', '>>>>>>\n', '>>>>>>>\n', '>>>>>>>>\n' ]; var replyPrefix = '> ', replyNewlineReplace = '\n> '; function expandQuotedPrefix(s, depth) { if (s.charCodeAt(0) === CHARCODE_NEWLINE) return replyQuotePrefixStringsNoSpace[depth]; return replyQuotePrefixStrings[depth]; } function expandQuoted(s, depth) { var ws = replyQuoteNewlineReplaceStrings[depth], nows = replyQuoteNewlineReplaceStringsNoSpace[depth]; return s.replace(RE_NEWLINE, function (m, idx) { if (s.charCodeAt(idx + 1) === CHARCODE_NEWLINE) return nows; else return ws; }); } exports.generateReplyText = function generateReplyText(rep) { var strBits = []; var lastContentDepth = null; var suppressWhitespaceBlankLine = false; for (var i = 0; i < rep.length; i += 2) { var etype = rep[i] & 15, block = rep[i + 1]; switch (etype) { default: case CT_AUTHORED_CONTENT: case CT_SIGNATURE: case CT_LEADIN_TO_QUOTE: if (block.length) { if (lastContentDepth !== null) { strBits.push(NEWLINE); if (!suppressWhitespaceBlankLine) { strBits.push(replyQuoteBlankLine[lastContentDepth]); } } strBits.push(expandQuotedPrefix(block, 0)); strBits.push(expandQuoted(block, 0)); lastContentDepth = 1; suppressWhitespaceBlankLine = etype === CT_LEADIN_TO_QUOTE; } break; case CT_QUOTED_TYPE: var depth = (rep[i] >> 8 & 255) + 1; if (depth < MAX_QUOTE_REPEAT_DEPTH) { if (lastContentDepth !== null) { strBits.push(NEWLINE); if (!suppressWhitespaceBlankLine) { strBits.push(replyQuoteBlankLine[lastContentDepth]); } } strBits.push(expandQuotedPrefix(block, depth)); strBits.push(expandQuoted(block, depth)); lastContentDepth = depth; suppressWhitespaceBlankLine = RE_REPLY_LAST_LINE_IN_BLOCK_CONTAINS_WROTE.test(block); } break; case CT_BOILERPLATE_DISCLAIMER: case CT_BOILERPLATE_LIST_INFO: case CT_BOILERPLATE_PRODUCT: case CT_BOILERPLATE_ADS: break; } } return strBits.join(''); }; exports.generateForwardBodyText = function generateForwardBodyText(rep) { var strBits = [], nl; for (var i = 0; i < rep.length; i += 2) { if (i) { strBits.push(NEWLINE); } var etype = rep[i] & 15, block = rep[i + 1]; switch (etype) { default: case CT_AUTHORED_CONTENT: for (nl = rep[i] >> 8 & 255; nl; nl--) { strBits.push(NEWLINE); } strBits.push(block); for (nl = rep[i] >> 16 & 255; nl; nl--) { strBits.push(NEWLINE); } break; case CT_LEADIN_TO_QUOTE: strBits.push(block); for (nl = rep[i] >> 8 & 255; nl; nl--) { strBits.push(NEWLINE); } break; case CT_SIGNATURE: case CT_BOILERPLATE_DISCLAIMER: case CT_BOILERPLATE_LIST_INFO: case CT_BOILERPLATE_PRODUCT: case CT_BOILERPLATE_ADS: for (nl = rep[i] >> 8 & 255; nl; nl--) { strBits.push(NEWLINE); } strBits.push(block); for (nl = rep[i] >> 16 & 255; nl; nl--) { strBits.push(NEWLINE); } break; case CT_QUOTED_TYPE: var depth = Math.min(rep[i] >> 8 & 255, 8); for (nl = rep[i] >> 16 & 255; nl; nl--) { strBits.push(replyQuotePrefixStringsNoSpace[depth]); strBits.push(NEWLINE); } strBits.push(expandQuotedPrefix(block, depth)); strBits.push(expandQuoted(block, depth)); for (nl = rep[i] >> 24 & 255; nl; nl--) { strBits.push(NEWLINE); strBits.push(replyQuotePrefixStringsNoSpace[depth]); } break; } } return strBits.join(''); }; }); ; define('mailchew', [ 'exports', 'logic', './util', './mailchew-strings', './quotechew', './htmlchew' ], function (exports, logic, $util, $mailchewStrings, $quotechew, $htmlchew) { var DESIRED_SNIPPET_LENGTH = 100; var scope = logic.scope('MailChew'); exports.generateBaseComposeBody = function generateBaseComposeBody(identity) { if (identity.signatureEnabled && identity.signature && identity.signature.length > 0) { var body = '\n\n--\n' + identity.signature; return body; } else { return ''; } }; var RE_RE = /^[Rr][Ee]:/; exports.generateReplySubject = function generateReplySubject(origSubject) { var re = 'Re: '; if (origSubject) { if (RE_RE.test(origSubject)) return origSubject; return re + origSubject; } return re; }; var FWD_FWD = /^[Ff][Ww][Dd]:/; exports.generateForwardSubject = function generateForwardSubject(origSubject) { var fwd = 'Fwd: '; if (origSubject) { if (FWD_FWD.test(origSubject)) return origSubject; return fwd + origSubject; } return fwd; }; var l10n_wroteString = '{name} wrote', l10n_originalMessageString = 'Original Message'; var l10n_forward_header_labels = { subject: 'Subject', date: 'Date', from: 'From', replyTo: 'Reply-To', to: 'To', cc: 'CC' }; exports.setLocalizedStrings = function (strings) { l10n_wroteString = strings.wrote; l10n_originalMessageString = strings.originalMessage; l10n_forward_header_labels = strings.forwardHeaderLabels; }; if ($mailchewStrings.strings) { exports.setLocalizedStrings($mailchewStrings.strings); } $mailchewStrings.events.on('strings', function (strings) { exports.setLocalizedStrings(strings); }); exports.generateReplyBody = function generateReplyMessage(reps, authorPair, msgDate, identity, refGuid) { var useName = authorPair.name ? authorPair.name.trim() : authorPair.address; var textMsg = '\n\n' + l10n_wroteString.replace('{name}', useName) + ':\n', htmlMsg = null; for (var i = 0; i < reps.length; i++) { var repType = reps[i].type, rep = reps[i].content; if (repType === 'plain') { var replyText = $quotechew.generateReplyText(rep); if (htmlMsg) { htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(replyText) + '\n'; } else { textMsg += replyText; } } else if (repType === 'html') { if (!htmlMsg) { htmlMsg = ''; if (textMsg.slice(-1) === '\n') { textMsg = textMsg.slice(0, -1); } } htmlMsg += '
'; } } if (identity.signature && identity.signatureEnabled) { if (htmlMsg) htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(identity.signature, 'pre', false, [ 'class', 'moz-signature', 'cols', '72' ]); else textMsg += '\n\n-- \n' + identity.signature; } return { text: textMsg, html: htmlMsg }; }; exports.generateForwardMessage = function (author, date, subject, headerInfo, bodyInfo, identity) { var textMsg = '\n\n', htmlMsg = null; if (identity.signature && identity.signatureEnabled) textMsg += '-- \n' + identity.signature + '\n\n'; textMsg += '-------- ' + l10n_originalMessageString + ' --------\n'; textMsg += l10n_forward_header_labels['subject'] + ': ' + subject + '\n'; textMsg += l10n_forward_header_labels['date'] + ': ' + new Date(date) + '\n'; textMsg += l10n_forward_header_labels['from'] + ': ' + $util.formatAddresses([author]) + '\n'; if (headerInfo.replyTo) textMsg += l10n_forward_header_labels['replyTo'] + ': ' + $util.formatAddresses([headerInfo.replyTo]) + '\n'; if (headerInfo.to) textMsg += l10n_forward_header_labels['to'] + ': ' + $util.formatAddresses(headerInfo.to) + '\n'; if (headerInfo.cc) textMsg += l10n_forward_header_labels['cc'] + ': ' + $util.formatAddresses(headerInfo.cc) + '\n'; textMsg += '\n'; var reps = bodyInfo.bodyReps; for (var i = 0; i < reps.length; i++) { var repType = reps[i].type, rep = reps[i].content; if (repType === 'plain') { var forwardText = $quotechew.generateForwardBodyText(rep); if (htmlMsg) { htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(forwardText) + '\n'; } else { textMsg += forwardText; } } else if (repType === 'html') { if (!htmlMsg) { htmlMsg = ''; if (textMsg.slice(-1) === '\n') { textMsg = textMsg.slice(0, -1); } } htmlMsg += rep; } } return { text: textMsg, html: htmlMsg }; }; var HTML_WRAP_TOP = ''; var HTML_WRAP_BOTTOM = ''; exports.mergeUserTextWithHTML = function mergeReplyTextWithHTML(text, html) { return HTML_WRAP_TOP + $htmlchew.wrapTextIntoSafeHTMLString(text, 'div') + html + HTML_WRAP_BOTTOM; }; exports.processMessageContent = function processMessageContent(content, type, isDownloaded, generateSnippet) { if (content.slice(-1) === '\n') { content = content.slice(0, -1); } var parsedContent, snippet; switch (type) { case 'plain': try { parsedContent = $quotechew.quoteProcessTextBody(content); } catch (ex) { logic(scope, 'textChewError', { ex: ex }); parsedContent = []; } if (generateSnippet) { try { snippet = $quotechew.generateSnippet(parsedContent, DESIRED_SNIPPET_LENGTH); } catch (ex) { logic(scope, 'textSnippetError', { ex: ex }); snippet = ''; } } break; case 'html': if (generateSnippet) { try { snippet = $htmlchew.generateSnippet(content); } catch (ex) { logic(scope, 'htmlSnippetError', { ex: ex }); snippet = ''; } } if (isDownloaded) { try { parsedContent = $htmlchew.sanitizeAndNormalizeHtml(content); } catch (ex) { logic(scope, 'htmlParseError', { ex: ex }); parsedContent = ''; } } break; } return { content: parsedContent, snippet: snippet }; }; }); ; (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define('addressparser', factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.addressparser = factory(); } }(this, function () { 'use strict'; var addressparser = {}; addressparser.parse = function (str) { var tokenizer = new addressparser.Tokenizer(str), tokens = tokenizer.tokenize(); var addresses = [], address = [], parsedAddresses = []; tokens.forEach(function (token) { if (token.type === 'operator' && (token.value === ',' || token.value === ';')) { if (address.length) { addresses.push(address); } address = []; } else { address.push(token); } }); if (address.length) { addresses.push(address); } addresses.forEach(function (address) { address = addressparser._handleAddress(address); if (address.length) { parsedAddresses = parsedAddresses.concat(address); } }); return parsedAddresses; }; addressparser._handleAddress = function (tokens) { var token, isGroup = false, state = 'text', address, addresses = [], data = { address: [], comment: [], group: [], text: [] }, i, len; for (i = 0, len = tokens.length; i < len; i++) { token = tokens[i]; if (token.type === 'operator') { switch (token.value) { case '<': state = 'address'; break; case '(': state = 'comment'; break; case ':': state = 'group'; isGroup = true; break; default: state = 'text'; } } else { if (token.value) { data[state].push(token.value); } } } if (!data.text.length && data.comment.length) { data.text = data.comment; data.comment = []; } if (isGroup) { data.text = data.text.join(' '); addresses.push({ name: data.text || address && address.name, group: data.group.length ? addressparser.parse(data.group.join(',')) : [] }); } else { if (!data.address.length && data.text.length) { for (i = data.text.length - 1; i >= 0; i--) { if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) { data.address = data.text.splice(i, 1); break; } } var _regexHandler = function (address) { if (!data.address.length) { data.address = [address.trim()]; return ' '; } else { return address; } }; if (!data.address.length) { for (i = data.text.length - 1; i >= 0; i--) { data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, _regexHandler).trim(); if (data.address.length) { break; } } } } if (!data.text.length && data.comment.length) { data.text = data.comment; data.comment = []; } if (data.address.length > 1) { data.text = data.text.concat(data.address.splice(1)); } data.text = data.text.join(' '); data.address = data.address.join(' '); if (!data.address && isGroup) { return []; } else { address = { address: data.address || data.text || '', name: data.text || data.address || '' }; if (address.address === address.name) { if ((address.address || '').match(/@/)) { address.name = ''; } else { address.address = ''; } } addresses.push(address); } } return addresses; }; addressparser.Tokenizer = function (str) { this.str = (str || '').toString(); this.operatorCurrent = ''; this.operatorExpecting = ''; this.node = null; this.escaped = false; this.list = []; }; addressparser.Tokenizer.prototype.operators = { '"': '"', '(': ')', '<': '>', ',': '', ':': ';', ';': '' }; addressparser.Tokenizer.prototype.tokenize = function () { var chr, list = []; for (var i = 0, len = this.str.length; i < len; i++) { chr = this.str.charAt(i); this.checkChar(chr); } this.list.forEach(function (node) { node.value = (node.value || '').toString().trim(); if (node.value) { list.push(node); } }); return list; }; addressparser.Tokenizer.prototype.checkChar = function (chr) { if ((chr in this.operators || chr === '\\') && this.escaped) { this.escaped = false; } else if (this.operatorExpecting && chr === this.operatorExpecting) { this.node = { type: 'operator', value: chr }; this.list.push(this.node); this.node = null; this.operatorExpecting = ''; this.escaped = false; return; } else if (!this.operatorExpecting && chr in this.operators) { this.node = { type: 'operator', value: chr }; this.list.push(this.node); this.node = null; this.operatorExpecting = this.operators[chr]; this.escaped = false; return; } if (!this.escaped && chr === '\\') { this.escaped = true; return; } if (!this.node) { this.node = { type: 'text', value: '' }; this.list.push(this.node); } if (this.escaped && chr !== '\\') { this.node.value += '\\'; } this.node.value += chr; this.escaped = false; }; return addressparser; })); define('tcp-socket', [ 'require', 'exports', 'module', 'worker-router', 'disaster-recovery' ], function (require, exports, module) { var router = require('worker-router'); var routerMaker = router.registerInstanceType('netsocket'); var DisasterRecovery = require('disaster-recovery'); function TCPSocketProxy(host, port, options) { options = options || {}; options.binaryType = 'arraybuffer'; this.host = host; this.port = port; this.ssl = !!options.useSecureTransport; this.binaryType = options.binaryType; this.bufferedAmount = 0; this.readyState = 'connecting'; var routerInfo = routerMaker.register(function (data) { var eventHandlerName = data.cmd; var internalHandler = this['_' + eventHandlerName]; var externalHandler = this[eventHandlerName]; internalHandler && internalHandler.call(this, data.args); DisasterRecovery.catchSocketExceptions(this, function () { externalHandler && externalHandler.call(this, { data: data.args }); }); }.bind(this)); this._sendMessage = routerInfo.sendMessage; this._unregisterWithRouter = routerInfo.unregister; this._sendMessage('open', [ host, port, options ]); } TCPSocketProxy.prototype = { _onopen: function () { this.readyState = 'open'; }, _onclose: function () { this._unregisterWithRouter(); this.readyState = 'closed'; }, upgradeToSecure: function () { this._sendMessage('upgradeToSecure', []); }, suspend: function () { throw new Error('tcp-socket.js does not support suspend().'); }, resume: function () { throw new Error('tcp-socket.js does not support resume().'); }, close: function () { if (this.readyState !== 'closed') { this._sendMessage('close'); } }, send: function (u8array) { if (u8array instanceof Blob) { this._sendMessage('write', [u8array]); } else if (u8array instanceof ArrayBuffer) { this._sendMessage('write', [ u8array, 0, u8array.byteLength ]); } else if (u8array.byteOffset !== 0 || u8array.length !== u8array.buffer.byteLength) { var buf = u8array.buffer.slice(u8array.byteOffset, u8array.byteOffset + u8array.length); this._sendMessage('write', [ buf, 0, buf.byteLength ], [buf]); } else { this._sendMessage('write', [ u8array.buffer, u8array.byteOffset, u8array.length ]); } return true; } }; return { open: function (host, port, options) { return new TCPSocketProxy(host, port, options); } }; }); define('mix', [], function () { return function mix(target, source, override) { Object.keys(source).forEach(function (key) { if (!target.hasOwnProperty(key) || override) target[key] = source[key]; }); return target; }; }); define('axe-logger', [], function () { return { debug: function () { }, log: function () { }, warn: console.warn.bind(console), error: console.error.bind(console) }; }); define('axe', ['axe-logger'], function (axe) { return axe; }); define('errorutils', ['exports'], function (exports) { var ALL_ERRORS = { 'offline': { reachable: false, retry: true, report: false }, 'server-maintenance': { reachable: true, retry: true, report: false }, 'unresponsive-server': { reachable: false, retry: true, report: false }, 'port-not-listening': { reachable: false, retry: true, report: false }, 'bad-user-or-pass': { reachable: true, retry: false, report: true }, 'needs-oauth-reauth': { reachable: true, retry: false, report: true }, 'imap-disabled': { reachable: true, retry: false, report: true }, 'pop3-disabled': { reachable: true, retry: false, report: true }, 'not-authorized': { reachable: true, retry: false, report: true }, 'bad-security': { reachable: true, retry: false, report: true }, 'no-config-info': { reachable: false, retry: false, report: false }, 'user-account-exists': { reachable: false, retry: false, report: false }, 'pop-server-not-great': { reachable: true, retry: false, report: false }, 'no-dns-entry': { reachable: false, retry: false, report: false }, 'bad-address': { reachable: true, retry: false, report: false }, 'server-problem': { reachable: true, retry: false, report: false }, 'unknown': { reachable: false, retry: false, report: false } }; exports.shouldReportProblem = function (err) { return (ALL_ERRORS[err] || ALL_ERRORS['unknown']).report; }; exports.shouldRetry = function (err) { return (ALL_ERRORS[err] || ALL_ERRORS['unknown']).retry; }; exports.wasErrorFromReachableState = function (err) { return (ALL_ERRORS[err] || ALL_ERRORS['unknown']).reachable; }; exports.analyzeException = function (err) { if (err === 'Connection refused') { err = { name: 'ConnectionRefusedError' }; } else if (typeof err === 'string') { return err; } if (!err.name) { return null; } if (/^Security/.test(err.name)) { return 'bad-security'; } else if (/^ConnectionRefused/i.test(err.name)) { return 'unresponsive-server'; } else { return null; } }; }); define('db/folder_info_rep', ['require'], function (require) { function makeFolderMeta(raw) { return { id: raw.id || null, serverId: raw.serverId || null, name: raw.name || null, type: raw.type || null, path: raw.path || null, parentId: raw.parentId || null, depth: raw.depth || 0, lastSyncedAt: raw.lastSyncedAt || 0, unreadCount: raw.unreadCount || 0, syncKey: raw.syncKey || null, version: raw.version || null }; } ; return { makeFolderMeta: makeFolderMeta }; }); ; (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define('mimetypes', factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.mimetypes = factory(); } }(this, function () { 'use strict'; function detectExtension(mimeType) { mimeType = (mimeType || '').toString().toLowerCase().replace(/\s/g, ''); if (!(mimeType in mimetypesList)) { return 'bin'; } if (typeof mimetypesList[mimeType] === 'string') { return mimetypesList[mimeType]; } var mimeParts = mimeType.split('/'); for (var i = 0, len = mimetypesList[mimeType].length; i < len; i++) { if (mimeParts[1] === mimetypesList[mimeType][i]) { return mimetypesList[mimeType][i]; } } return mimetypesList[mimeType][0]; } function detectMimeType(extension) { extension = (extension || '').toString().toLowerCase().replace(/\s/g, '').replace(/^\./g, ''); if (!(extension in mimetypesExtensions)) { return 'application/octet-stream'; } if (typeof mimetypesExtensions[extension] === 'string') { return mimetypesExtensions[extension]; } var mimeParts; for (var i = 0, len = mimetypesExtensions[extension].length; i < len; i++) { mimeParts = mimetypesExtensions[extension][i].split('/'); if (mimeParts[1] === extension) { return mimetypesExtensions[extension][i]; } } return mimetypesExtensions[extension][0]; } var mimetypesList = { 'application/acad': 'dwg', 'application/andrew-inset': '', 'application/applixware': 'aw', 'application/arj': 'arj', 'application/atom+xml': 'xml', 'application/atomcat+xml': 'atomcat', 'application/atomsvc+xml': 'atomsvc', 'application/base64': [ 'mm', 'mme' ], 'application/binhex': 'hqx', 'application/binhex4': 'hqx', 'application/book': [ 'boo', 'book' ], 'application/ccxml+xml,': 'ccxml', 'application/cdf': 'cdf', 'application/cdmi-capability': 'cdmia', 'application/cdmi-container': 'cdmic', 'application/cdmi-domain': 'cdmid', 'application/cdmi-object': 'cdmio', 'application/cdmi-queue': 'cdmiq', 'application/clariscad': 'ccad', 'application/commonground': 'dp', 'application/cu-seeme': 'cu', 'application/davmount+xml': 'davmount', 'application/drafting': 'drw', 'application/dsptype': 'tsp', 'application/dssc+der': 'dssc', 'application/dssc+xml': 'xdssc', 'application/dxf': 'dxf', 'application/ecmascript': [ 'js', 'es' ], 'application/emma+xml': 'emma', 'application/envoy': 'evy', 'application/epub+zip': 'epub', 'application/excel': [ 'xl', 'xla', 'xlb', 'xlc', 'xld', 'xlk', 'xll', 'xlm', 'xls', 'xlt', 'xlv', 'xlw' ], 'application/exi': 'exi', 'application/font-tdpfr': 'pfr', 'application/fractals': 'fif', 'application/freeloader': 'frl', 'application/futuresplash': 'spl', 'application/gnutar': 'tgz', 'application/groupwise': 'vew', 'application/hlp': 'hlp', 'application/hta': 'hta', 'application/hyperstudio': 'stk', 'application/i-deas': 'unv', 'application/iges': [ 'iges', 'igs' ], 'application/inf': 'inf', 'application/internet-property-stream': 'acx', 'application/ipfix': 'ipfix', 'application/java': 'class', 'application/java-archive': 'jar', 'application/java-byte-code': 'class', 'application/java-serialized-object': 'ser', 'application/java-vm': 'class', 'application/javascript': 'js', 'application/json': 'json', 'application/lha': 'lha', 'application/lzx': 'lzx', 'application/mac-binary': 'bin', 'application/mac-binhex': 'hqx', 'application/mac-binhex40': 'hqx', 'application/mac-compactpro': 'cpt', 'application/macbinary': 'bin', 'application/mads+xml': 'mads', 'application/marc': 'mrc', 'application/marcxml+xml': 'mrcx', 'application/mathematica': 'ma', 'application/mathml+xml': 'mathml', 'application/mbedlet': 'mbd', 'application/mbox': 'mbox', 'application/mcad': 'mcd', 'application/mediaservercontrol+xml': 'mscml', 'application/metalink4+xml': 'meta4', 'application/mets+xml': 'mets', 'application/mime': 'aps', 'application/mods+xml': 'mods', 'application/mp21': 'm21', 'application/mp4': 'mp4', 'application/mspowerpoint': [ 'pot', 'pps', 'ppt', 'ppz' ], 'application/msword': [ 'doc', 'dot', 'w6w', 'wiz', 'word' ], 'application/mswrite': 'wri', 'application/mxf': 'mxf', 'application/netmc': 'mcp', 'application/octet-stream': ['*'], 'application/oda': 'oda', 'application/oebps-package+xml': 'opf', 'application/ogg': 'ogx', 'application/olescript': 'axs', 'application/onenote': 'onetoc', 'application/patch-ops-error+xml': 'xer', 'application/pdf': 'pdf', 'application/pgp-encrypted': '', 'application/pgp-signature': 'pgp', 'application/pics-rules': 'prf', 'application/pkcs-12': 'p12', 'application/pkcs-crl': 'crl', 'application/pkcs10': 'p10', 'application/pkcs7-mime': [ 'p7c', 'p7m' ], 'application/pkcs7-signature': 'p7s', 'application/pkcs8': 'p8', 'application/pkix-attr-cert': 'ac', 'application/pkix-cert': [ 'cer', 'crt' ], 'application/pkix-crl': 'crl', 'application/pkix-pkipath': 'pkipath', 'application/pkixcmp': 'pki', 'application/plain': 'text', 'application/pls+xml': 'pls', 'application/postscript': [ 'ai', 'eps', 'ps' ], 'application/powerpoint': 'ppt', 'application/pro_eng': [ 'part', 'prt' ], 'application/prs.cww': 'cww', 'application/pskc+xml': 'pskcxml', 'application/rdf+xml': 'rdf', 'application/reginfo+xml': 'rif', 'application/relax-ng-compact-syntax': 'rnc', 'application/resource-lists+xml': 'rl', 'application/resource-lists-diff+xml': 'rld', 'application/ringing-tones': 'rng', 'application/rls-services+xml': 'rs', 'application/rsd+xml': 'rsd', 'application/rss+xml': 'xml', 'application/rtf': [ 'rtf', 'rtx' ], 'application/sbml+xml': 'sbml', 'application/scvp-cv-request': 'scq', 'application/scvp-cv-response': 'scs', 'application/scvp-vp-request': 'spq', 'application/scvp-vp-response': 'spp', 'application/sdp': 'sdp', 'application/sea': 'sea', 'application/set': 'set', 'application/set-payment-initiation': 'setpay', 'application/set-registration-initiation': 'setreg', 'application/shf+xml': 'shf', 'application/sla': 'stl', 'application/smil': [ 'smi', 'smil' ], 'application/smil+xml': 'smi', 'application/solids': 'sol', 'application/sounder': 'sdr', 'application/sparql-query': 'rq', 'application/sparql-results+xml': 'srx', 'application/srgs': 'gram', 'application/srgs+xml': 'grxml', 'application/sru+xml': 'sru', 'application/ssml+xml': 'ssml', 'application/step': [ 'step', 'stp' ], 'application/streamingmedia': 'ssm', 'application/tei+xml': 'tei', 'application/thraud+xml': 'tfi', 'application/timestamped-data': 'tsd', 'application/toolbook': 'tbk', 'application/vda': 'vda', 'application/vnd.3gpp.pic-bw-large': 'plb', 'application/vnd.3gpp.pic-bw-small': 'psb', 'application/vnd.3gpp.pic-bw-var': 'pvb', 'application/vnd.3gpp2.tcap': 'tcap', 'application/vnd.3m.post-it-notes': 'pwn', 'application/vnd.accpac.simply.aso': 'aso', 'application/vnd.accpac.simply.imp': 'imp', 'application/vnd.acucobol': 'acu', 'application/vnd.acucorp': 'atc', 'application/vnd.adobe.air-application-installer-package+zip': 'air', 'application/vnd.adobe.fxp': 'fxp', 'application/vnd.adobe.xdp+xml': 'xdp', 'application/vnd.adobe.xfdf': 'xfdf', 'application/vnd.ahead.space': 'ahead', 'application/vnd.airzip.filesecure.azf': 'azf', 'application/vnd.airzip.filesecure.azs': 'azs', 'application/vnd.amazon.ebook': 'azw', 'application/vnd.americandynamics.acc': 'acc', 'application/vnd.amiga.ami': 'ami', 'application/vnd.android.package-archive': 'apk', 'application/vnd.anser-web-certificate-issue-initiation': 'cii', 'application/vnd.anser-web-funds-transfer-initiation': 'fti', 'application/vnd.antix.game-component': 'atx', 'application/vnd.apple.installer+xml': 'mpkg', 'application/vnd.apple.mpegurl': 'm3u8', 'application/vnd.aristanetworks.swi': 'swi', 'application/vnd.audiograph': 'aep', 'application/vnd.blueice.multipass': 'mpm', 'application/vnd.bmi': 'bmi', 'application/vnd.businessobjects': 'rep', 'application/vnd.chemdraw+xml': 'cdxml', 'application/vnd.chipnuts.karaoke-mmd': 'mmd', 'application/vnd.cinderella': 'cdy', 'application/vnd.claymore': 'cla', 'application/vnd.cloanto.rp9': 'rp9', 'application/vnd.clonk.c4group': 'c4g', 'application/vnd.cluetrust.cartomobile-config': 'c11amc', 'application/vnd.cluetrust.cartomobile-config-pkg': 'c11amz', 'application/vnd.commonspace': 'csp', 'application/vnd.contact.cmsg': 'cdbcmsg', 'application/vnd.cosmocaller': 'cmc', 'application/vnd.crick.clicker': 'clkx', 'application/vnd.crick.clicker.keyboard': 'clkk', 'application/vnd.crick.clicker.palette': 'clkp', 'application/vnd.crick.clicker.template': 'clkt', 'application/vnd.crick.clicker.wordbank': 'clkw', 'application/vnd.criticaltools.wbs+xml': 'wbs', 'application/vnd.ctc-posml': 'pml', 'application/vnd.cups-ppd': 'ppd', 'application/vnd.curl.car': 'car', 'application/vnd.curl.pcurl': 'pcurl', 'application/vnd.data-vision.rdz': 'rdz', 'application/vnd.denovo.fcselayout-link': 'fe_launch', 'application/vnd.dna': 'dna', 'application/vnd.dolby.mlp': 'mlp', 'application/vnd.dpgraph': 'dpg', 'application/vnd.dreamfactory': 'dfac', 'application/vnd.dvb.ait': 'ait', 'application/vnd.dvb.service': 'svc', 'application/vnd.dynageo': 'geo', 'application/vnd.ecowin.chart': 'mag', 'application/vnd.enliven': 'nml', 'application/vnd.epson.esf': 'esf', 'application/vnd.epson.msf': 'msf', 'application/vnd.epson.quickanime': 'qam', 'application/vnd.epson.salt': 'slt', 'application/vnd.epson.ssf': 'ssf', 'application/vnd.eszigno3+xml': 'es3', 'application/vnd.ezpix-album': 'ez2', 'application/vnd.ezpix-package': 'ez3', 'application/vnd.fdf': 'fdf', 'application/vnd.fdsn.seed': 'seed', 'application/vnd.flographit': 'gph', 'application/vnd.fluxtime.clip': 'ftc', 'application/vnd.framemaker': 'fm', 'application/vnd.frogans.fnc': 'fnc', 'application/vnd.frogans.ltf': 'ltf', 'application/vnd.fsc.weblaunch': 'fsc', 'application/vnd.fujitsu.oasys': 'oas', 'application/vnd.fujitsu.oasys2': 'oa2', 'application/vnd.fujitsu.oasys3': 'oa3', 'application/vnd.fujitsu.oasysgp': 'fg5', 'application/vnd.fujitsu.oasysprs': 'bh2', 'application/vnd.fujixerox.ddd': 'ddd', 'application/vnd.fujixerox.docuworks': 'xdw', 'application/vnd.fujixerox.docuworks.binder': 'xbd', 'application/vnd.fuzzysheet': 'fzs', 'application/vnd.genomatix.tuxedo': 'txd', 'application/vnd.geogebra.file': 'ggb', 'application/vnd.geogebra.tool': 'ggt', 'application/vnd.geometry-explorer': 'gex', 'application/vnd.geonext': 'gxt', 'application/vnd.geoplan': 'g2w', 'application/vnd.geospace': 'g3w', 'application/vnd.gmx': 'gmx', 'application/vnd.google-earth.kml+xml': 'kml', 'application/vnd.google-earth.kmz': 'kmz', 'application/vnd.grafeq': 'gqf', 'application/vnd.groove-account': 'gac', 'application/vnd.groove-help': 'ghf', 'application/vnd.groove-identity-message': 'gim', 'application/vnd.groove-injector': 'grv', 'application/vnd.groove-tool-message': 'gtm', 'application/vnd.groove-tool-template': 'tpl', 'application/vnd.groove-vcard': 'vcg', 'application/vnd.hal+xml': 'hal', 'application/vnd.handheld-entertainment+xml': 'zmm', 'application/vnd.hbci': 'hbci', 'application/vnd.hhe.lesson-player': 'les', 'application/vnd.hp-hpgl': [ 'hgl', 'hpg', 'hpgl' ], 'application/vnd.hp-hpid': 'hpid', 'application/vnd.hp-hps': 'hps', 'application/vnd.hp-jlyt': 'jlt', 'application/vnd.hp-pcl': 'pcl', 'application/vnd.hp-pclxl': 'pclxl', 'application/vnd.hydrostatix.sof-data': 'sfd-hdstx', 'application/vnd.hzn-3d-crossword': 'x3d', 'application/vnd.ibm.minipay': 'mpy', 'application/vnd.ibm.modcap': 'afp', 'application/vnd.ibm.rights-management': 'irm', 'application/vnd.ibm.secure-container': 'sc', 'application/vnd.iccprofile': 'icc', 'application/vnd.igloader': 'igl', 'application/vnd.immervision-ivp': 'ivp', 'application/vnd.immervision-ivu': 'ivu', 'application/vnd.insors.igm': 'igm', 'application/vnd.intercon.formnet': 'xpw', 'application/vnd.intergeo': 'i2g', 'application/vnd.intu.qbo': 'qbo', 'application/vnd.intu.qfx': 'qfx', 'application/vnd.ipunplugged.rcprofile': 'rcprofile', 'application/vnd.irepository.package+xml': 'irp', 'application/vnd.is-xpr': 'xpr', 'application/vnd.isac.fcs': 'fcs', 'application/vnd.jam': 'jam', 'application/vnd.jcp.javame.midlet-rms': 'rms', 'application/vnd.jisp': 'jisp', 'application/vnd.joost.joda-archive': 'joda', 'application/vnd.kahootz': 'ktz', 'application/vnd.kde.karbon': 'karbon', 'application/vnd.kde.kchart': 'chrt', 'application/vnd.kde.kformula': 'kfo', 'application/vnd.kde.kivio': 'flw', 'application/vnd.kde.kontour': 'kon', 'application/vnd.kde.kpresenter': 'kpr', 'application/vnd.kde.kspread': 'ksp', 'application/vnd.kde.kword': 'kwd', 'application/vnd.kenameaapp': 'htke', 'application/vnd.kidspiration': 'kia', 'application/vnd.kinar': 'kne', 'application/vnd.koan': 'skp', 'application/vnd.kodak-descriptor': 'sse', 'application/vnd.las.las+xml': 'lasxml', 'application/vnd.llamagraphics.life-balance.desktop': 'lbd', 'application/vnd.llamagraphics.life-balance.exchange+xml': 'lbe', 'application/vnd.lotus-1-2-3': '123', 'application/vnd.lotus-approach': 'apr', 'application/vnd.lotus-freelance': 'pre', 'application/vnd.lotus-notes': 'nsf', 'application/vnd.lotus-organizer': 'org', 'application/vnd.lotus-screencam': 'scm', 'application/vnd.lotus-wordpro': 'lwp', 'application/vnd.macports.portpkg': 'portpkg', 'application/vnd.mcd': 'mcd', 'application/vnd.medcalcdata': 'mc1', 'application/vnd.mediastation.cdkey': 'cdkey', 'application/vnd.mfer': 'mwf', 'application/vnd.mfmp': 'mfm', 'application/vnd.micrografx.flo': 'flo', 'application/vnd.micrografx.igx': 'igx', 'application/vnd.mif': 'mif', 'application/vnd.mobius.daf': 'daf', 'application/vnd.mobius.dis': 'dis', 'application/vnd.mobius.mbk': 'mbk', 'application/vnd.mobius.mqy': 'mqy', 'application/vnd.mobius.msl': 'msl', 'application/vnd.mobius.plc': 'plc', 'application/vnd.mobius.txf': 'txf', 'application/vnd.mophun.application': 'mpn', 'application/vnd.mophun.certificate': 'mpc', 'application/vnd.mozilla.xul+xml': 'xul', 'application/vnd.ms-artgalry': 'cil', 'application/vnd.ms-cab-compressed': 'cab', 'application/vnd.ms-excel': [ 'xla', 'xlc', 'xlm', 'xls', 'xlt', 'xlw', 'xlb', 'xll' ], 'application/vnd.ms-excel.addin.macroenabled.12': 'xlam', 'application/vnd.ms-excel.sheet.binary.macroenabled.12': 'xlsb', 'application/vnd.ms-excel.sheet.macroenabled.12': 'xlsm', 'application/vnd.ms-excel.template.macroenabled.12': 'xltm', 'application/vnd.ms-fontobject': 'eot', 'application/vnd.ms-htmlhelp': 'chm', 'application/vnd.ms-ims': 'ims', 'application/vnd.ms-lrm': 'lrm', 'application/vnd.ms-officetheme': 'thmx', 'application/vnd.ms-outlook': 'msg', 'application/vnd.ms-pki.certstore': 'sst', 'application/vnd.ms-pki.pko': 'pko', 'application/vnd.ms-pki.seccat': 'cat', 'application/vnd.ms-pki.stl': 'stl', 'application/vnd.ms-pkicertstore': 'sst', 'application/vnd.ms-pkiseccat': 'cat', 'application/vnd.ms-pkistl': 'stl', 'application/vnd.ms-powerpoint': [ 'pot', 'pps', 'ppt', 'ppa', 'pwz' ], 'application/vnd.ms-powerpoint.addin.macroenabled.12': 'ppam', 'application/vnd.ms-powerpoint.presentation.macroenabled.12': 'pptm', 'application/vnd.ms-powerpoint.slide.macroenabled.12': 'sldm', 'application/vnd.ms-powerpoint.slideshow.macroenabled.12': 'ppsm', 'application/vnd.ms-powerpoint.template.macroenabled.12': 'potm', 'application/vnd.ms-project': 'mpp', 'application/vnd.ms-word.document.macroenabled.12': 'docm', 'application/vnd.ms-word.template.macroenabled.12': 'dotm', 'application/vnd.ms-works': [ 'wcm', 'wdb', 'wks', 'wps' ], 'application/vnd.ms-wpl': 'wpl', 'application/vnd.ms-xpsdocument': 'xps', 'application/vnd.mseq': 'mseq', 'application/vnd.musician': 'mus', 'application/vnd.muvee.style': 'msty', 'application/vnd.neurolanguage.nlu': 'nlu', 'application/vnd.noblenet-directory': 'nnd', 'application/vnd.noblenet-sealer': 'nns', 'application/vnd.noblenet-web': 'nnw', 'application/vnd.nokia.configuration-message': 'ncm', 'application/vnd.nokia.n-gage.data': 'ngdat', 'application/vnd.nokia.n-gage.symbian.install': 'n-gage', 'application/vnd.nokia.radio-preset': 'rpst', 'application/vnd.nokia.radio-presets': 'rpss', 'application/vnd.nokia.ringing-tone': 'rng', 'application/vnd.novadigm.edm': 'edm', 'application/vnd.novadigm.edx': 'edx', 'application/vnd.novadigm.ext': 'ext', 'application/vnd.oasis.opendocument.chart': 'odc', 'application/vnd.oasis.opendocument.chart-template': 'otc', 'application/vnd.oasis.opendocument.database': 'odb', 'application/vnd.oasis.opendocument.formula': 'odf', 'application/vnd.oasis.opendocument.formula-template': 'odft', 'application/vnd.oasis.opendocument.graphics': 'odg', 'application/vnd.oasis.opendocument.graphics-template': 'otg', 'application/vnd.oasis.opendocument.image': 'odi', 'application/vnd.oasis.opendocument.image-template': 'oti', 'application/vnd.oasis.opendocument.presentation': 'odp', 'application/vnd.oasis.opendocument.presentation-template': 'otp', 'application/vnd.oasis.opendocument.spreadsheet': 'ods', 'application/vnd.oasis.opendocument.spreadsheet-template': 'ots', 'application/vnd.oasis.opendocument.text': 'odt', 'application/vnd.oasis.opendocument.text-master': 'odm', 'application/vnd.oasis.opendocument.text-template': 'ott', 'application/vnd.oasis.opendocument.text-web': 'oth', 'application/vnd.olpc-sugar': 'xo', 'application/vnd.oma.dd2+xml': 'dd2', 'application/vnd.openofficeorg.extension': 'oxt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx', 'application/vnd.openxmlformats-officedocument.presentationml.slide': 'sldx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': 'ppsx', 'application/vnd.openxmlformats-officedocument.presentationml.template': 'potx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': 'xltx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template': 'dotx', 'application/vnd.osgeo.mapguide.package': 'mgp', 'application/vnd.osgi.dp': 'dp', 'application/vnd.palm': 'pdb', 'application/vnd.pawaafile': 'paw', 'application/vnd.pg.format': 'str', 'application/vnd.pg.osasli': 'ei6', 'application/vnd.picsel': 'efif', 'application/vnd.pmi.widget': 'wg', 'application/vnd.pocketlearn': 'plf', 'application/vnd.powerbuilder6': 'pbd', 'application/vnd.previewsystems.box': 'box', 'application/vnd.proteus.magazine': 'mgz', 'application/vnd.publishare-delta-tree': 'qps', 'application/vnd.pvi.ptid1': 'ptid', 'application/vnd.quark.quarkxpress': 'qxd', 'application/vnd.realvnc.bed': 'bed', 'application/vnd.recordare.musicxml': 'mxl', 'application/vnd.recordare.musicxml+xml': 'musicxml', 'application/vnd.rig.cryptonote': 'cryptonote', 'application/vnd.rim.cod': 'cod', 'application/vnd.rn-realmedia': 'rm', 'application/vnd.rn-realplayer': 'rnx', 'application/vnd.route66.link66+xml': 'link66', 'application/vnd.sailingtracker.track': 'st', 'application/vnd.seemail': 'see', 'application/vnd.sema': 'sema', 'application/vnd.semd': 'semd', 'application/vnd.semf': 'semf', 'application/vnd.shana.informed.formdata': 'ifm', 'application/vnd.shana.informed.formtemplate': 'itp', 'application/vnd.shana.informed.interchange': 'iif', 'application/vnd.shana.informed.package': 'ipk', 'application/vnd.simtech-mindmapper': 'twd', 'application/vnd.smaf': 'mmf', 'application/vnd.smart.teacher': 'teacher', 'application/vnd.solent.sdkm+xml': 'sdkm', 'application/vnd.spotfire.dxp': 'dxp', 'application/vnd.spotfire.sfs': 'sfs', 'application/vnd.stardivision.calc': 'sdc', 'application/vnd.stardivision.draw': 'sda', 'application/vnd.stardivision.impress': 'sdd', 'application/vnd.stardivision.math': 'smf', 'application/vnd.stardivision.writer': 'sdw', 'application/vnd.stardivision.writer-global': 'sgl', 'application/vnd.stepmania.stepchart': 'sm', 'application/vnd.sun.xml.calc': 'sxc', 'application/vnd.sun.xml.calc.template': 'stc', 'application/vnd.sun.xml.draw': 'sxd', 'application/vnd.sun.xml.draw.template': 'std', 'application/vnd.sun.xml.impress': 'sxi', 'application/vnd.sun.xml.impress.template': 'sti', 'application/vnd.sun.xml.math': 'sxm', 'application/vnd.sun.xml.writer': 'sxw', 'application/vnd.sun.xml.writer.global': 'sxg', 'application/vnd.sun.xml.writer.template': 'stw', 'application/vnd.sus-calendar': 'sus', 'application/vnd.svd': 'svd', 'application/vnd.symbian.install': 'sis', 'application/vnd.syncml+xml': 'xsm', 'application/vnd.syncml.dm+wbxml': 'bdm', 'application/vnd.syncml.dm+xml': 'xdm', 'application/vnd.tao.intent-module-archive': 'tao', 'application/vnd.tmobile-livetv': 'tmo', 'application/vnd.trid.tpt': 'tpt', 'application/vnd.triscape.mxs': 'mxs', 'application/vnd.trueapp': 'tra', 'application/vnd.ufdl': 'ufd', 'application/vnd.uiq.theme': 'utz', 'application/vnd.umajin': 'umj', 'application/vnd.unity': 'unityweb', 'application/vnd.uoml+xml': 'uoml', 'application/vnd.vcx': 'vcx', 'application/vnd.visio': 'vsd', 'application/vnd.visionary': 'vis', 'application/vnd.vsf': 'vsf', 'application/vnd.wap.wbxml': 'wbxml', 'application/vnd.wap.wmlc': 'wmlc', 'application/vnd.wap.wmlscriptc': 'wmlsc', 'application/vnd.webturbo': 'wtb', 'application/vnd.wolfram.player': 'nbp', 'application/vnd.wordperfect': 'wpd', 'application/vnd.wqd': 'wqd', 'application/vnd.wt.stf': 'stf', 'application/vnd.xara': [ 'web', 'xar' ], 'application/vnd.xfdl': 'xfdl', 'application/vnd.yamaha.hv-dic': 'hvd', 'application/vnd.yamaha.hv-script': 'hvs', 'application/vnd.yamaha.hv-voice': 'hvp', 'application/vnd.yamaha.openscoreformat': 'osf', 'application/vnd.yamaha.openscoreformat.osfpvg+xml': 'osfpvg', 'application/vnd.yamaha.smaf-audio': 'saf', 'application/vnd.yamaha.smaf-phrase': 'spf', 'application/vnd.yellowriver-custom-menu': 'cmp', 'application/vnd.zul': 'zir', 'application/vnd.zzazz.deck+xml': 'zaz', 'application/vocaltec-media-desc': 'vmd', 'application/vocaltec-media-file': 'vmf', 'application/voicexml+xml': 'vxml', 'application/widget': 'wgt', 'application/winhlp': 'hlp', 'application/wordperfect': [ 'wp', 'wp5', 'wp6', 'wpd' ], 'application/wordperfect6.0': [ 'w60', 'wp5' ], 'application/wordperfect6.1': 'w61', 'application/wsdl+xml': 'wsdl', 'application/wspolicy+xml': 'wspolicy', 'application/x-123': 'wk1', 'application/x-7z-compressed': '7z', 'application/x-abiword': 'abw', 'application/x-ace-compressed': 'ace', 'application/x-aim': 'aim', 'application/x-authorware-bin': 'aab', 'application/x-authorware-map': 'aam', 'application/x-authorware-seg': 'aas', 'application/x-bcpio': 'bcpio', 'application/x-binary': 'bin', 'application/x-binhex40': 'hqx', 'application/x-bittorrent': 'torrent', 'application/x-bsh': [ 'bsh', 'sh', 'shar' ], 'application/x-bytecode.elisp': 'elc', 'applicaiton/x-bytecode.python': 'pyc', 'application/x-bzip': 'bz', 'application/x-bzip2': [ 'boz', 'bz2' ], 'application/x-cdf': 'cdf', 'application/x-cdlink': 'vcd', 'application/x-chat': [ 'cha', 'chat' ], 'application/x-chess-pgn': 'pgn', 'application/x-cmu-raster': 'ras', 'application/x-cocoa': 'cco', 'application/x-compactpro': 'cpt', 'application/x-compress': 'z', 'application/x-compressed': [ 'tgz', 'gz', 'z', 'zip' ], 'application/x-conference': 'nsc', 'application/x-cpio': 'cpio', 'application/x-cpt': 'cpt', 'application/x-csh': 'csh', 'application/x-debian-package': 'deb', 'application/x-deepv': 'deepv', 'application/x-director': [ 'dcr', 'dir', 'dxr' ], 'application/x-doom': 'wad', 'application/x-dtbncx+xml': 'ncx', 'application/x-dtbook+xml': 'dtb', 'application/x-dtbresource+xml': 'res', 'application/x-dvi': 'dvi', 'application/x-elc': 'elc', 'application/x-envoy': [ 'env', 'evy' ], 'application/x-esrehber': 'es', 'application/x-excel': [ 'xla', 'xlb', 'xlc', 'xld', 'xlk', 'xll', 'xlm', 'xls', 'xlt', 'xlv', 'xlw' ], 'application/x-font-bdf': 'bdf', 'application/x-font-ghostscript': 'gsf', 'application/x-font-linux-psf': 'psf', 'application/x-font-otf': 'otf', 'application/x-font-pcf': 'pcf', 'application/x-font-snf': 'snf', 'application/x-font-ttf': 'ttf', 'application/x-font-type1': 'pfa', 'application/x-font-woff': 'woff', 'application/x-frame': 'mif', 'application/x-freelance': 'pre', 'application/x-futuresplash': 'spl', 'application/x-gnumeric': 'gnumeric', 'application/x-gsp': 'gsp', 'application/x-gss': 'gss', 'application/x-gtar': 'gtar', 'application/x-gzip': [ 'gz', 'gzip' ], 'application/x-hdf': 'hdf', 'application/x-helpfile': [ 'help', 'hlp' ], 'application/x-httpd-imap': 'imap', 'application/x-ima': 'ima', 'application/x-internet-signup': [ 'ins', 'isp' ], 'application/x-internett-signup': 'ins', 'application/x-inventor': 'iv', 'application/x-ip2': 'ip', 'application/x-iphone': 'iii', 'application/x-java-class': 'class', 'application/x-java-commerce': 'jcm', 'application/x-java-jnlp-file': 'jnlp', 'application/x-javascript': 'js', 'application/x-koan': [ 'skd', 'skm', 'skp', 'skt' ], 'application/x-ksh': 'ksh', 'application/x-latex': [ 'latex', 'ltx' ], 'application/x-lha': 'lha', 'application/x-lisp': 'lsp', 'application/x-livescreen': 'ivy', 'application/x-lotus': 'wq1', 'application/x-lotusscreencam': 'scm', 'application/x-lzh': 'lzh', 'application/x-lzx': 'lzx', 'application/x-mac-binhex40': 'hqx', 'application/x-macbinary': 'bin', 'application/x-magic-cap-package-1.0': 'mc$', 'application/x-mathcad': 'mcd', 'application/x-meme': 'mm', 'application/x-midi': [ 'mid', 'midi' ], 'application/x-mif': 'mif', 'application/x-mix-transfer': 'nix', 'application/x-mobipocket-ebook': 'prc', 'application/x-mplayer2': 'asx', 'application/x-ms-application': 'application', 'application/x-ms-wmd': 'wmd', 'application/x-ms-wmz': 'wmz', 'application/x-ms-xbap': 'xbap', 'application/x-msaccess': 'mdb', 'application/x-msbinder': 'obd', 'application/x-mscardfile': 'crd', 'application/x-msclip': 'clp', 'application/x-msdownload': [ 'dll', 'exe' ], 'application/x-msexcel': [ 'xla', 'xls', 'xlw' ], 'application/x-msmediaview': [ 'm13', 'm14', 'mvb' ], 'application/x-msmetafile': 'wmf', 'application/x-msmoney': 'mny', 'application/x-mspowerpoint': 'ppt', 'application/x-mspublisher': 'pub', 'application/x-msschedule': 'scd', 'application/x-msterminal': 'trm', 'application/x-mswrite': 'wri', 'application/x-navi-animation': 'ani', 'application/x-navidoc': 'nvd', 'application/x-navimap': 'map', 'application/x-navistyle': 'stl', 'application/x-netcdf': [ 'cdf', 'nc' ], 'application/x-newton-compatible-pkg': 'pkg', 'application/x-nokia-9000-communicator-add-on-software': 'aos', 'application/x-omc': 'omc', 'application/x-omcdatamaker': 'omcd', 'application/x-omcregerator': 'omcr', 'application/x-pagemaker': [ 'pm4', 'pm5' ], 'application/x-pcl': 'pcl', 'application/x-perfmon': [ 'pma', 'pmc', 'pml', 'pmr', 'pmw' ], 'application/x-pixclscript': 'plx', 'application/x-pkcs10': 'p10', 'application/x-pkcs12': [ 'p12', 'pfx' ], 'application/x-pkcs7-certificates': [ 'p7b', 'spc' ], 'application/x-pkcs7-certreqresp': 'p7r', 'application/x-pkcs7-mime': [ 'p7c', 'p7m' ], 'application/x-pkcs7-signature': [ 'p7s', 'p7a' ], 'application/x-pointplus': 'css', 'application/x-portable-anymap': 'pnm', 'application/x-project': [ 'mpc', 'mpt', 'mpv', 'mpx' ], 'application/x-qpro': 'wb1', 'application/x-rar-compressed': 'rar', 'application/x-rtf': 'rtf', 'application/x-sdp': 'sdp', 'application/x-sea': 'sea', 'application/x-seelogo': 'sl', 'application/x-sh': 'sh', 'application/x-shar': [ 'shar', 'sh' ], 'application/x-shockwave-flash': 'swf', 'application/x-silverlight-app': 'xap', 'application/x-sit': 'sit', 'application/x-sprite': [ 'spr', 'sprite' ], 'application/x-stuffit': 'sit', 'application/x-stuffitx': 'sitx', 'application/x-sv4cpio': 'sv4cpio', 'application/x-sv4crc': 'sv4crc', 'application/x-tar': 'tar', 'application/x-tbook': [ 'sbk', 'tbk' ], 'application/x-tcl': 'tcl', 'application/x-tex': 'tex', 'application/x-tex-tfm': 'tfm', 'application/x-texinfo': [ 'texi', 'texinfo' ], 'application/x-troff': [ 'roff', 't', 'tr' ], 'application/x-troff-man': 'man', 'application/x-troff-me': 'me', 'application/x-troff-ms': 'ms', 'application/x-troff-msvideo': 'avi', 'application/x-ustar': 'ustar', 'application/x-visio': [ 'vsd', 'vst', 'vsw' ], 'application/x-vnd.audioexplosion.mzz': 'mzz', 'application/x-vnd.ls-xpix': 'xpix', 'application/x-vrml': 'vrml', 'application/x-wais-source': [ 'src', 'wsrc' ], 'application/x-winhelp': 'hlp', 'application/x-wintalk': 'wtk', 'application/x-world': [ 'svr', 'wrl' ], 'application/x-wpwin': 'wpd', 'application/x-wri': 'wri', 'application/x-x509-ca-cert': [ 'cer', 'crt', 'der' ], 'application/x-x509-user-cert': 'crt', 'application/x-xfig': 'fig', 'application/x-xpinstall': 'xpi', 'application/x-zip-compressed': 'zip', 'application/xcap-diff+xml': 'xdf', 'application/xenc+xml': 'xenc', 'application/xhtml+xml': 'xhtml', 'application/xml': 'xml', 'application/xml-dtd': 'dtd', 'application/xop+xml': 'xop', 'application/xslt+xml': 'xslt', 'application/xspf+xml': 'xspf', 'application/xv+xml': 'mxml', 'application/yang': 'yang', 'application/yin+xml': 'yin', 'application/ynd.ms-pkipko': 'pko', 'application/zip': 'zip', 'audio/adpcm': 'adp', 'audio/aiff': [ 'aif', 'aifc', 'aiff' ], 'audio/basic': [ 'au', 'snd' ], 'audio/it': 'it', 'audio/make': [ 'funk', 'my', 'pfunk' ], 'audio/make.my.funk': 'pfunk', 'audio/mid': [ 'mid', 'rmi' ], 'audio/midi': [ 'kar', 'mid', 'midi' ], 'audio/mod': 'mod', 'audio/mp4': 'mp4a', 'audio/mpeg': [ 'mp3', 'm2a', 'mp2', 'mpa', 'mpg', 'mpga' ], 'audio/mpeg3': 'mp3', 'audio/nspaudio': [ 'la', 'lma' ], 'audio/ogg': 'oga', 'audio/s3m': 's3m', 'audio/tsp-audio': 'tsi', 'audio/tsplayer': 'tsp', 'audio/vnd.dece.audio': 'uva', 'audio/vnd.digital-winds': 'eol', 'audio/vnd.dra': 'dra', 'audio/vnd.dts': 'dts', 'audio/vnd.dts.hd': 'dtshd', 'audio/vnd.lucent.voice': 'lvp', 'audio/vnd.ms-playready.media.pya': 'pya', 'audio/vnd.nuera.ecelp4800': 'ecelp4800', 'audio/vnd.nuera.ecelp7470': 'ecelp7470', 'audio/vnd.nuera.ecelp9600': 'ecelp9600', 'audio/vnd.qcelp': 'qcp', 'audio/vnd.rip': 'rip', 'audio/voc': 'voc', 'audio/voxware': 'vox', 'audio/wav': 'wav', 'audio/webm': 'weba', 'audio/x-aac': 'aac', 'audio/x-adpcm': 'snd', 'audio/x-aiff': [ 'aif', 'aifc', 'aiff' ], 'audio/x-au': 'au', 'audio/x-gsm': [ 'gsd', 'gsm' ], 'audio/x-jam': 'jam', 'audio/x-liveaudio': 'lam', 'audio/x-mid': [ 'mid', 'midi' ], 'audio/x-midi': [ 'mid', 'midi' ], 'audio/x-mod': 'mod', 'audio/x-mpeg': 'mp2', 'audio/x-mpeg-3': 'mp3', 'audio/x-mpegurl': 'm3u', 'audio/x-mpequrl': 'm3u', 'audio/x-ms-wax': 'wax', 'audio/x-ms-wma': 'wma', 'audio/x-nspaudio': [ 'la', 'lma' ], 'audio/x-pn-realaudio': [ 'ra', 'ram', 'rm', 'rmm', 'rmp' ], 'audio/x-pn-realaudio-plugin': [ 'ra', 'rmp', 'rpm' ], 'audio/x-psid': 'sid', 'audio/x-realaudio': 'ra', 'audio/x-twinvq': 'vqf', 'audio/x-twinvq-plugin': [ 'vqe', 'vql' ], 'audio/x-vnd.audioexplosion.mjuicemediafile': 'mjf', 'audio/x-voc': 'voc', 'audio/x-wav': 'wav', 'audio/xm': 'xm', 'chemical/x-cdx': 'cdx', 'chemical/x-cif': 'cif', 'chemical/x-cmdf': 'cmdf', 'chemical/x-cml': 'cml', 'chemical/x-csml': 'csml', 'chemical/x-pdb': [ 'pdb', 'xyz' ], 'chemical/x-xyz': 'xyz', 'drawing/x-dwf': 'dwf', 'i-world/i-vrml': 'ivr', 'image/bmp': [ 'bmp', 'bm' ], 'image/cgm': 'cgm', 'image/cis-cod': 'cod', 'image/cmu-raster': [ 'ras', 'rast' ], 'image/fif': 'fif', 'image/florian': [ 'flo', 'turbot' ], 'image/g3fax': 'g3', 'image/gif': 'gif', 'image/ief': [ 'ief', 'iefs' ], 'image/jpeg': [ 'jpe', 'jpeg', 'jpg', 'jfif', 'jfif-tbnl' ], 'image/jutvision': 'jut', 'image/ktx': 'ktx', 'image/naplps': [ 'nap', 'naplps' ], 'image/pict': [ 'pic', 'pict' ], 'image/pipeg': 'jfif', 'image/pjpeg': [ 'jfif', 'jpe', 'jpeg', 'jpg' ], 'image/png': [ 'png', 'x-png' ], 'image/prs.btif': 'btif', 'image/svg+xml': 'svg', 'image/tiff': [ 'tif', 'tiff' ], 'image/vasa': 'mcf', 'image/vnd.adobe.photoshop': 'psd', 'image/vnd.dece.graphic': 'uvi', 'image/vnd.djvu': 'djvu', 'image/vnd.dvb.subtitle': 'sub', 'image/vnd.dwg': [ 'dwg', 'dxf', 'svf' ], 'image/vnd.dxf': 'dxf', 'image/vnd.fastbidsheet': 'fbs', 'image/vnd.fpx': 'fpx', 'image/vnd.fst': 'fst', 'image/vnd.fujixerox.edmics-mmr': 'mmr', 'image/vnd.fujixerox.edmics-rlc': 'rlc', 'image/vnd.ms-modi': 'mdi', 'image/vnd.net-fpx': [ 'fpx', 'npx' ], 'image/vnd.rn-realflash': 'rf', 'image/vnd.rn-realpix': 'rp', 'image/vnd.wap.wbmp': 'wbmp', 'image/vnd.xiff': 'xif', 'image/webp': 'webp', 'image/x-cmu-raster': 'ras', 'image/x-cmx': 'cmx', 'image/x-dwg': [ 'dwg', 'dxf', 'svf' ], 'image/x-freehand': 'fh', 'image/x-icon': 'ico', 'image/x-jg': 'art', 'image/x-jps': 'jps', 'image/x-niff': [ 'nif', 'niff' ], 'image/x-pcx': 'pcx', 'image/x-pict': [ 'pct', 'pic' ], 'image/x-portable-anymap': 'pnm', 'image/x-portable-bitmap': 'pbm', 'image/x-portable-graymap': 'pgm', 'image/x-portable-greymap': 'pgm', 'image/x-portable-pixmap': 'ppm', 'image/x-quicktime': [ 'qif', 'qti', 'qtif' ], 'image/x-rgb': 'rgb', 'image/x-tiff': [ 'tif', 'tiff' ], 'image/x-windows-bmp': 'bmp', 'image/x-xbitmap': 'xbm', 'image/x-xbm': 'xbm', 'image/x-xpixmap': [ 'xpm', 'pm' ], 'image/x-xwd': 'xwd', 'image/x-xwindowdump': 'xwd', 'image/xbm': 'xbm', 'image/xpm': 'xpm', 'message/rfc822': [ 'mht', 'mhtml', 'nws', 'mime', 'eml' ], 'model/iges': [ 'iges', 'igs' ], 'model/mesh': 'msh', 'model/vnd.collada+xml': 'dae', 'model/vnd.dwf': 'dwf', 'model/vnd.gdl': 'gdl', 'model/vnd.gtw': 'gtw', 'model/vnd.mts': 'mts', 'model/vnd.vtu': 'vtu', 'model/vrml': [ 'vrml', 'wrl', 'wrz' ], 'model/x-pov': 'pov', 'multipart/x-gzip': 'gzip', 'multipart/x-ustar': 'ustar', 'multipart/x-zip': 'zip', 'music/crescendo': [ 'mid', 'midi' ], 'music/x-karaoke': 'kar', 'paleovu/x-pv': 'pvu', 'text/asp': 'asp', 'text/calendar': 'ics', 'text/css': 'css', 'text/csv': 'csv', 'text/ecmascript': 'js', 'text/h323': '323', 'text/html': [ 'htm', 'html', 'stm', 'acgi', 'htmls', 'htx', 'shtml' ], 'text/iuls': 'uls', 'text/javascript': 'js', 'text/mcf': 'mcf', 'text/n3': 'n3', 'text/pascal': 'pas', 'text/plain': [ 'bas', 'c', 'h', 'txt', 'c++', 'cc', 'com', 'conf', 'cxx', 'def', 'f', 'f90', 'for', 'g', 'hh', 'idc', 'jav', 'java', 'list', 'log', 'lst', 'm', 'mar', 'pl', 'sdml', 'text' ], 'text/plain-bas': 'par', 'text/prs.lines.tag': 'dsc', 'text/richtext': [ 'rtx', 'rt', 'rtf' ], 'text/scriplet': 'wsc', 'text/scriptlet': 'sct', 'text/sgml': [ 'sgm', 'sgml' ], 'text/tab-separated-values': 'tsv', 'text/troff': 't', 'text/turtle': 'ttl', 'text/uri-list': [ 'uni', 'unis', 'uri', 'uris' ], 'text/vnd.abc': 'abc', 'text/vnd.curl': 'curl', 'text/vnd.curl.dcurl': 'dcurl', 'text/vnd.curl.mcurl': 'mcurl', 'text/vnd.curl.scurl': 'scurl', 'text/vnd.fly': 'fly', 'text/vnd.fmi.flexstor': 'flx', 'text/vnd.graphviz': 'gv', 'text/vnd.in3d.3dml': '3dml', 'text/vnd.in3d.spot': 'spot', 'text/vnd.rn-realtext': 'rt', 'text/vnd.sun.j2me.app-descriptor': 'jad', 'text/vnd.wap.wml': 'wml', 'text/vnd.wap.wmlscript': 'wmls', 'text/webviewhtml': 'htt', 'text/x-asm': [ 'asm', 's' ], 'text/x-audiosoft-intra': 'aip', 'text/x-c': [ 'c', 'cc', 'cpp' ], 'text/x-component': 'htc', 'text/x-fortran': [ 'f', 'f77', 'f90', 'for' ], 'text/x-h': [ 'h', 'hh' ], 'text/x-java-source': [ 'jav', 'java' ], 'text/x-java-source,java': 'java', 'text/x-la-asf': 'lsx', 'text/x-m': 'm', 'text/x-pascal': 'p', 'text/x-script': 'hlb', 'text/x-script.csh': 'csh', 'text/x-script.elisp': 'el', 'text/x-script.guile': 'scm', 'text/x-script.ksh': 'ksh', 'text/x-script.lisp': 'lsp', 'text/x-script.perl': 'pl', 'text/x-script.perl-module': 'pm', 'text/x-script.phyton': 'py', 'text/x-script.rexx': 'rexx', 'text/x-script.scheme': 'scm', 'text/x-script.sh': 'sh', 'text/x-script.tcl': 'tcl', 'text/x-script.tcsh': 'tcsh', 'text/x-script.zsh': 'zsh', 'text/x-server-parsed-html': [ 'shtml', 'ssi' ], 'text/x-setext': 'etx', 'text/x-sgml': [ 'sgm', 'sgml' ], 'text/x-speech': [ 'spc', 'talk' ], 'text/x-uil': 'uil', 'text/x-uuencode': [ 'uu', 'uue' ], 'text/x-vcalendar': 'vcs', 'text/x-vcard': 'vcf', 'text/xml': 'xml', 'video/3gpp': '3gp', 'video/3gpp2': '3g2', 'video/animaflex': 'afl', 'video/avi': 'avi', 'video/avs-video': 'avs', 'video/dl': 'dl', 'video/fli': 'fli', 'video/gl': 'gl', 'video/h261': 'h261', 'video/h263': 'h263', 'video/h264': 'h264', 'video/jpeg': 'jpgv', 'video/jpm': 'jpm', 'video/mj2': 'mj2', 'video/mp4': 'mp4', 'video/mpeg': [ 'mp2', 'mpa', 'mpe', 'mpeg', 'mpg', 'mpv2', 'm1v', 'm2v', 'mp3' ], 'video/msvideo': 'avi', 'video/ogg': 'ogv', 'video/quicktime': [ 'mov', 'qt', 'moov' ], 'video/vdo': 'vdo', 'video/vivo': [ 'viv', 'vivo' ], 'video/vnd.dece.hd': 'uvh', 'video/vnd.dece.mobile': 'uvm', 'video/vnd.dece.pd': 'uvp', 'video/vnd.dece.sd': 'uvs', 'video/vnd.dece.video': 'uvv', 'video/vnd.fvt': 'fvt', 'video/vnd.mpegurl': 'mxu', 'video/vnd.ms-playready.media.pyv': 'pyv', 'video/vnd.rn-realvideo': 'rv', 'video/vnd.uvvu.mp4': 'uvu', 'video/vnd.vivo': [ 'viv', 'vivo' ], 'video/vosaic': 'vos', 'video/webm': 'webm', 'video/x-amt-demorun': 'xdr', 'video/x-amt-showrun': 'xsr', 'video/x-atomic3d-feature': 'fmf', 'video/x-dl': 'dl', 'video/x-dv': [ 'dif', 'dv' ], 'video/x-f4v': 'f4v', 'video/x-fli': 'fli', 'video/x-flv': 'flv', 'video/x-gl': 'gl', 'video/x-isvideo': 'isu', 'video/x-la-asf': [ 'lsf', 'lsx' ], 'video/x-m4v': 'm4v', 'video/x-motion-jpeg': 'mjpg', 'video/x-mpeg': [ 'mp2', 'mp3' ], 'video/x-mpeq2a': 'mp2', 'video/x-ms-asf': [ 'asf', 'asr', 'asx' ], 'video/x-ms-asf-plugin': 'asx', 'video/x-ms-wm': 'wm', 'video/x-ms-wmv': 'wmv', 'video/x-ms-wmx': 'wmx', 'video/x-ms-wvx': 'wvx', 'video/x-msvideo': 'avi', 'video/x-qtc': 'qtc', 'video/x-scm': 'scm', 'video/x-sgi-movie': [ 'movie', 'mv' ], 'windows/metafile': 'wmf', 'www/mime': 'mime', 'x-conference/x-cooltalk': 'ice', 'x-music/x-midi': [ 'mid', 'midi' ], 'x-world/x-3dmf': [ '3dm', '3dmf', 'qd3', 'qd3d' ], 'x-world/x-svr': 'svr', 'x-world/x-vrml': [ 'flr', 'vrml', 'wrl', 'wrz', 'xaf', 'xof' ], 'x-world/x-vrt': 'vrt', 'xgl/drawing': 'xgz', 'xgl/movie': 'xmz' }; var mimetypesExtensions = { '': [ 'application/andrew-inset', 'application/pgp-encrypted' ], '*': 'application/octet-stream', '123': 'application/vnd.lotus-1-2-3', '323': 'text/h323', '3dm': 'x-world/x-3dmf', '3dmf': 'x-world/x-3dmf', '3dml': 'text/vnd.in3d.3dml', '3g2': 'video/3gpp2', '3gp': 'video/3gpp', '7z': 'application/x-7z-compressed', 'a': 'application/octet-stream', 'aab': 'application/x-authorware-bin', 'aac': 'audio/x-aac', 'aam': 'application/x-authorware-map', 'aas': 'application/x-authorware-seg', 'abc': 'text/vnd.abc', 'abw': 'application/x-abiword', 'ac': 'application/pkix-attr-cert', 'acc': 'application/vnd.americandynamics.acc', 'ace': 'application/x-ace-compressed', 'acgi': 'text/html', 'acu': 'application/vnd.acucobol', 'acx': 'application/internet-property-stream', 'adp': 'audio/adpcm', 'aep': 'application/vnd.audiograph', 'afl': 'video/animaflex', 'afp': 'application/vnd.ibm.modcap', 'ahead': 'application/vnd.ahead.space', 'ai': 'application/postscript', 'aif': [ 'audio/aiff', 'audio/x-aiff' ], 'aifc': [ 'audio/aiff', 'audio/x-aiff' ], 'aiff': [ 'audio/aiff', 'audio/x-aiff' ], 'aim': 'application/x-aim', 'aip': 'text/x-audiosoft-intra', 'air': 'application/vnd.adobe.air-application-installer-package+zip', 'ait': 'application/vnd.dvb.ait', 'ami': 'application/vnd.amiga.ami', 'ani': 'application/x-navi-animation', 'aos': 'application/x-nokia-9000-communicator-add-on-software', 'apk': 'application/vnd.android.package-archive', 'application': 'application/x-ms-application', 'apr': 'application/vnd.lotus-approach', 'aps': 'application/mime', 'arc': 'application/octet-stream', 'arj': [ 'application/arj', 'application/octet-stream' ], 'art': 'image/x-jg', 'asf': 'video/x-ms-asf', 'asm': 'text/x-asm', 'aso': 'application/vnd.accpac.simply.aso', 'asp': 'text/asp', 'asr': 'video/x-ms-asf', 'asx': [ 'video/x-ms-asf', 'application/x-mplayer2', 'video/x-ms-asf-plugin' ], 'atc': 'application/vnd.acucorp', 'atomcat': 'application/atomcat+xml', 'atomsvc': 'application/atomsvc+xml', 'atx': 'application/vnd.antix.game-component', 'au': [ 'audio/basic', 'audio/x-au' ], 'avi': [ 'video/avi', 'video/msvideo', 'application/x-troff-msvideo', 'video/x-msvideo' ], 'avs': 'video/avs-video', 'aw': 'application/applixware', 'axs': 'application/olescript', 'azf': 'application/vnd.airzip.filesecure.azf', 'azs': 'application/vnd.airzip.filesecure.azs', 'azw': 'application/vnd.amazon.ebook', 'bas': 'text/plain', 'bcpio': 'application/x-bcpio', 'bdf': 'application/x-font-bdf', 'bdm': 'application/vnd.syncml.dm+wbxml', 'bed': 'application/vnd.realvnc.bed', 'bh2': 'application/vnd.fujitsu.oasysprs', 'bin': [ 'application/octet-stream', 'application/mac-binary', 'application/macbinary', 'application/x-macbinary', 'application/x-binary' ], 'bm': 'image/bmp', 'bmi': 'application/vnd.bmi', 'bmp': [ 'image/bmp', 'image/x-windows-bmp' ], 'boo': 'application/book', 'book': 'application/book', 'box': 'application/vnd.previewsystems.box', 'boz': 'application/x-bzip2', 'bsh': 'application/x-bsh', 'btif': 'image/prs.btif', 'bz': 'application/x-bzip', 'bz2': 'application/x-bzip2', 'c': [ 'text/plain', 'text/x-c' ], 'c++': 'text/plain', 'c11amc': 'application/vnd.cluetrust.cartomobile-config', 'c11amz': 'application/vnd.cluetrust.cartomobile-config-pkg', 'c4g': 'application/vnd.clonk.c4group', 'cab': 'application/vnd.ms-cab-compressed', 'car': 'application/vnd.curl.car', 'cat': [ 'application/vnd.ms-pkiseccat', 'application/vnd.ms-pki.seccat' ], 'cc': [ 'text/plain', 'text/x-c' ], 'ccad': 'application/clariscad', 'cco': 'application/x-cocoa', 'ccxml': 'application/ccxml+xml,', 'cdbcmsg': 'application/vnd.contact.cmsg', 'cdf': [ 'application/cdf', 'application/x-cdf', 'application/x-netcdf' ], 'cdkey': 'application/vnd.mediastation.cdkey', 'cdmia': 'application/cdmi-capability', 'cdmic': 'application/cdmi-container', 'cdmid': 'application/cdmi-domain', 'cdmio': 'application/cdmi-object', 'cdmiq': 'application/cdmi-queue', 'cdx': 'chemical/x-cdx', 'cdxml': 'application/vnd.chemdraw+xml', 'cdy': 'application/vnd.cinderella', 'cer': [ 'application/pkix-cert', 'application/x-x509-ca-cert' ], 'cgm': 'image/cgm', 'cha': 'application/x-chat', 'chat': 'application/x-chat', 'chm': 'application/vnd.ms-htmlhelp', 'chrt': 'application/vnd.kde.kchart', 'cif': 'chemical/x-cif', 'cii': 'application/vnd.anser-web-certificate-issue-initiation', 'cil': 'application/vnd.ms-artgalry', 'cla': 'application/vnd.claymore', 'class': [ 'application/octet-stream', 'application/java', 'application/java-byte-code', 'application/java-vm', 'application/x-java-class' ], 'clkk': 'application/vnd.crick.clicker.keyboard', 'clkp': 'application/vnd.crick.clicker.palette', 'clkt': 'application/vnd.crick.clicker.template', 'clkw': 'application/vnd.crick.clicker.wordbank', 'clkx': 'application/vnd.crick.clicker', 'clp': 'application/x-msclip', 'cmc': 'application/vnd.cosmocaller', 'cmdf': 'chemical/x-cmdf', 'cml': 'chemical/x-cml', 'cmp': 'application/vnd.yellowriver-custom-menu', 'cmx': 'image/x-cmx', 'cod': [ 'image/cis-cod', 'application/vnd.rim.cod' ], 'com': [ 'application/octet-stream', 'text/plain' ], 'conf': 'text/plain', 'cpio': 'application/x-cpio', 'cpp': 'text/x-c', 'cpt': [ 'application/mac-compactpro', 'application/x-compactpro', 'application/x-cpt' ], 'crd': 'application/x-mscardfile', 'crl': [ 'application/pkix-crl', 'application/pkcs-crl' ], 'crt': [ 'application/pkix-cert', 'application/x-x509-user-cert', 'application/x-x509-ca-cert' ], 'cryptonote': 'application/vnd.rig.cryptonote', 'csh': [ 'text/x-script.csh', 'application/x-csh' ], 'csml': 'chemical/x-csml', 'csp': 'application/vnd.commonspace', 'css': [ 'text/css', 'application/x-pointplus' ], 'csv': 'text/csv', 'cu': 'application/cu-seeme', 'curl': 'text/vnd.curl', 'cww': 'application/prs.cww', 'cxx': 'text/plain', 'dae': 'model/vnd.collada+xml', 'daf': 'application/vnd.mobius.daf', 'davmount': 'application/davmount+xml', 'dcr': 'application/x-director', 'dcurl': 'text/vnd.curl.dcurl', 'dd2': 'application/vnd.oma.dd2+xml', 'ddd': 'application/vnd.fujixerox.ddd', 'deb': 'application/x-debian-package', 'deepv': 'application/x-deepv', 'def': 'text/plain', 'der': 'application/x-x509-ca-cert', 'dfac': 'application/vnd.dreamfactory', 'dif': 'video/x-dv', 'dir': 'application/x-director', 'dis': 'application/vnd.mobius.dis', 'djvu': 'image/vnd.djvu', 'dl': [ 'video/dl', 'video/x-dl' ], 'dll': 'application/x-msdownload', 'dms': 'application/octet-stream', 'dna': 'application/vnd.dna', 'doc': 'application/msword', 'docm': 'application/vnd.ms-word.document.macroenabled.12', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot': 'application/msword', 'dotm': 'application/vnd.ms-word.template.macroenabled.12', 'dotx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp': [ 'application/commonground', 'application/vnd.osgi.dp' ], 'dpg': 'application/vnd.dpgraph', 'dra': 'audio/vnd.dra', 'drw': 'application/drafting', 'dsc': 'text/prs.lines.tag', 'dssc': 'application/dssc+der', 'dtb': 'application/x-dtbook+xml', 'dtd': 'application/xml-dtd', 'dts': 'audio/vnd.dts', 'dtshd': 'audio/vnd.dts.hd', 'dump': 'application/octet-stream', 'dv': 'video/x-dv', 'dvi': 'application/x-dvi', 'dwf': [ 'model/vnd.dwf', 'drawing/x-dwf' ], 'dwg': [ 'application/acad', 'image/vnd.dwg', 'image/x-dwg' ], 'dxf': [ 'application/dxf', 'image/vnd.dwg', 'image/vnd.dxf', 'image/x-dwg' ], 'dxp': 'application/vnd.spotfire.dxp', 'dxr': 'application/x-director', 'ecelp4800': 'audio/vnd.nuera.ecelp4800', 'ecelp7470': 'audio/vnd.nuera.ecelp7470', 'ecelp9600': 'audio/vnd.nuera.ecelp9600', 'edm': 'application/vnd.novadigm.edm', 'edx': 'application/vnd.novadigm.edx', 'efif': 'application/vnd.picsel', 'ei6': 'application/vnd.pg.osasli', 'el': 'text/x-script.elisp', 'elc': [ 'application/x-elc', 'application/x-bytecode.elisp' ], 'eml': 'message/rfc822', 'emma': 'application/emma+xml', 'env': 'application/x-envoy', 'eol': 'audio/vnd.digital-winds', 'eot': 'application/vnd.ms-fontobject', 'eps': 'application/postscript', 'epub': 'application/epub+zip', 'es': [ 'application/ecmascript', 'application/x-esrehber' ], 'es3': 'application/vnd.eszigno3+xml', 'esf': 'application/vnd.epson.esf', 'etx': 'text/x-setext', 'evy': [ 'application/envoy', 'application/x-envoy' ], 'exe': [ 'application/octet-stream', 'application/x-msdownload' ], 'exi': 'application/exi', 'ext': 'application/vnd.novadigm.ext', 'ez2': 'application/vnd.ezpix-album', 'ez3': 'application/vnd.ezpix-package', 'f': [ 'text/plain', 'text/x-fortran' ], 'f4v': 'video/x-f4v', 'f77': 'text/x-fortran', 'f90': [ 'text/plain', 'text/x-fortran' ], 'fbs': 'image/vnd.fastbidsheet', 'fcs': 'application/vnd.isac.fcs', 'fdf': 'application/vnd.fdf', 'fe_launch': 'application/vnd.denovo.fcselayout-link', 'fg5': 'application/vnd.fujitsu.oasysgp', 'fh': 'image/x-freehand', 'fif': [ 'application/fractals', 'image/fif' ], 'fig': 'application/x-xfig', 'fli': [ 'video/fli', 'video/x-fli' ], 'flo': [ 'image/florian', 'application/vnd.micrografx.flo' ], 'flr': 'x-world/x-vrml', 'flv': 'video/x-flv', 'flw': 'application/vnd.kde.kivio', 'flx': 'text/vnd.fmi.flexstor', 'fly': 'text/vnd.fly', 'fm': 'application/vnd.framemaker', 'fmf': 'video/x-atomic3d-feature', 'fnc': 'application/vnd.frogans.fnc', 'for': [ 'text/plain', 'text/x-fortran' ], 'fpx': [ 'image/vnd.fpx', 'image/vnd.net-fpx' ], 'frl': 'application/freeloader', 'fsc': 'application/vnd.fsc.weblaunch', 'fst': 'image/vnd.fst', 'ftc': 'application/vnd.fluxtime.clip', 'fti': 'application/vnd.anser-web-funds-transfer-initiation', 'funk': 'audio/make', 'fvt': 'video/vnd.fvt', 'fxp': 'application/vnd.adobe.fxp', 'fzs': 'application/vnd.fuzzysheet', 'g': 'text/plain', 'g2w': 'application/vnd.geoplan', 'g3': 'image/g3fax', 'g3w': 'application/vnd.geospace', 'gac': 'application/vnd.groove-account', 'gdl': 'model/vnd.gdl', 'geo': 'application/vnd.dynageo', 'gex': 'application/vnd.geometry-explorer', 'ggb': 'application/vnd.geogebra.file', 'ggt': 'application/vnd.geogebra.tool', 'ghf': 'application/vnd.groove-help', 'gif': 'image/gif', 'gim': 'application/vnd.groove-identity-message', 'gl': [ 'video/gl', 'video/x-gl' ], 'gmx': 'application/vnd.gmx', 'gnumeric': 'application/x-gnumeric', 'gph': 'application/vnd.flographit', 'gqf': 'application/vnd.grafeq', 'gram': 'application/srgs', 'grv': 'application/vnd.groove-injector', 'grxml': 'application/srgs+xml', 'gsd': 'audio/x-gsm', 'gsf': 'application/x-font-ghostscript', 'gsm': 'audio/x-gsm', 'gsp': 'application/x-gsp', 'gss': 'application/x-gss', 'gtar': 'application/x-gtar', 'gtm': 'application/vnd.groove-tool-message', 'gtw': 'model/vnd.gtw', 'gv': 'text/vnd.graphviz', 'gxt': 'application/vnd.geonext', 'gz': [ 'application/x-gzip', 'application/x-compressed' ], 'gzip': [ 'multipart/x-gzip', 'application/x-gzip' ], 'h': [ 'text/plain', 'text/x-h' ], 'h261': 'video/h261', 'h263': 'video/h263', 'h264': 'video/h264', 'hal': 'application/vnd.hal+xml', 'hbci': 'application/vnd.hbci', 'hdf': 'application/x-hdf', 'help': 'application/x-helpfile', 'hgl': 'application/vnd.hp-hpgl', 'hh': [ 'text/plain', 'text/x-h' ], 'hlb': 'text/x-script', 'hlp': [ 'application/winhlp', 'application/hlp', 'application/x-helpfile', 'application/x-winhelp' ], 'hpg': 'application/vnd.hp-hpgl', 'hpgl': 'application/vnd.hp-hpgl', 'hpid': 'application/vnd.hp-hpid', 'hps': 'application/vnd.hp-hps', 'hqx': [ 'application/mac-binhex40', 'application/binhex', 'application/binhex4', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40' ], 'hta': 'application/hta', 'htc': 'text/x-component', 'htke': 'application/vnd.kenameaapp', 'htm': 'text/html', 'html': 'text/html', 'htmls': 'text/html', 'htt': 'text/webviewhtml', 'htx': 'text/html', 'hvd': 'application/vnd.yamaha.hv-dic', 'hvp': 'application/vnd.yamaha.hv-voice', 'hvs': 'application/vnd.yamaha.hv-script', 'i2g': 'application/vnd.intergeo', 'icc': 'application/vnd.iccprofile', 'ice': 'x-conference/x-cooltalk', 'ico': 'image/x-icon', 'ics': 'text/calendar', 'idc': 'text/plain', 'ief': 'image/ief', 'iefs': 'image/ief', 'ifm': 'application/vnd.shana.informed.formdata', 'iges': [ 'application/iges', 'model/iges' ], 'igl': 'application/vnd.igloader', 'igm': 'application/vnd.insors.igm', 'igs': [ 'application/iges', 'model/iges' ], 'igx': 'application/vnd.micrografx.igx', 'iif': 'application/vnd.shana.informed.interchange', 'iii': 'application/x-iphone', 'ima': 'application/x-ima', 'imap': 'application/x-httpd-imap', 'imp': 'application/vnd.accpac.simply.imp', 'ims': 'application/vnd.ms-ims', 'inf': 'application/inf', 'ins': [ 'application/x-internet-signup', 'application/x-internett-signup' ], 'ip': 'application/x-ip2', 'ipfix': 'application/ipfix', 'ipk': 'application/vnd.shana.informed.package', 'irm': 'application/vnd.ibm.rights-management', 'irp': 'application/vnd.irepository.package+xml', 'isp': 'application/x-internet-signup', 'isu': 'video/x-isvideo', 'it': 'audio/it', 'itp': 'application/vnd.shana.informed.formtemplate', 'iv': 'application/x-inventor', 'ivp': 'application/vnd.immervision-ivp', 'ivr': 'i-world/i-vrml', 'ivu': 'application/vnd.immervision-ivu', 'ivy': 'application/x-livescreen', 'jad': 'text/vnd.sun.j2me.app-descriptor', 'jam': [ 'application/vnd.jam', 'audio/x-jam' ], 'jar': 'application/java-archive', 'jav': [ 'text/plain', 'text/x-java-source' ], 'java': [ 'text/plain', 'text/x-java-source,java', 'text/x-java-source' ], 'jcm': 'application/x-java-commerce', 'jfif': [ 'image/pipeg', 'image/jpeg', 'image/pjpeg' ], 'jfif-tbnl': 'image/jpeg', 'jisp': 'application/vnd.jisp', 'jlt': 'application/vnd.hp-jlyt', 'jnlp': 'application/x-java-jnlp-file', 'joda': 'application/vnd.joost.joda-archive', 'jpe': [ 'image/jpeg', 'image/pjpeg' ], 'jpeg': [ 'image/jpeg', 'image/pjpeg' ], 'jpg': [ 'image/jpeg', 'image/pjpeg' ], 'jpgv': 'video/jpeg', 'jpm': 'video/jpm', 'jps': 'image/x-jps', 'js': [ 'application/javascript', 'application/ecmascript', 'text/javascript', 'text/ecmascript', 'application/x-javascript' ], 'json': 'application/json', 'jut': 'image/jutvision', 'kar': [ 'audio/midi', 'music/x-karaoke' ], 'karbon': 'application/vnd.kde.karbon', 'kfo': 'application/vnd.kde.kformula', 'kia': 'application/vnd.kidspiration', 'kml': 'application/vnd.google-earth.kml+xml', 'kmz': 'application/vnd.google-earth.kmz', 'kne': 'application/vnd.kinar', 'kon': 'application/vnd.kde.kontour', 'kpr': 'application/vnd.kde.kpresenter', 'ksh': [ 'application/x-ksh', 'text/x-script.ksh' ], 'ksp': 'application/vnd.kde.kspread', 'ktx': 'image/ktx', 'ktz': 'application/vnd.kahootz', 'kwd': 'application/vnd.kde.kword', 'la': [ 'audio/nspaudio', 'audio/x-nspaudio' ], 'lam': 'audio/x-liveaudio', 'lasxml': 'application/vnd.las.las+xml', 'latex': 'application/x-latex', 'lbd': 'application/vnd.llamagraphics.life-balance.desktop', 'lbe': 'application/vnd.llamagraphics.life-balance.exchange+xml', 'les': 'application/vnd.hhe.lesson-player', 'lha': [ 'application/octet-stream', 'application/lha', 'application/x-lha' ], 'lhx': 'application/octet-stream', 'link66': 'application/vnd.route66.link66+xml', 'list': 'text/plain', 'lma': [ 'audio/nspaudio', 'audio/x-nspaudio' ], 'log': 'text/plain', 'lrm': 'application/vnd.ms-lrm', 'lsf': 'video/x-la-asf', 'lsp': [ 'application/x-lisp', 'text/x-script.lisp' ], 'lst': 'text/plain', 'lsx': [ 'video/x-la-asf', 'text/x-la-asf' ], 'ltf': 'application/vnd.frogans.ltf', 'ltx': 'application/x-latex', 'lvp': 'audio/vnd.lucent.voice', 'lwp': 'application/vnd.lotus-wordpro', 'lzh': [ 'application/octet-stream', 'application/x-lzh' ], 'lzx': [ 'application/lzx', 'application/octet-stream', 'application/x-lzx' ], 'm': [ 'text/plain', 'text/x-m' ], 'm13': 'application/x-msmediaview', 'm14': 'application/x-msmediaview', 'm1v': 'video/mpeg', 'm21': 'application/mp21', 'm2a': 'audio/mpeg', 'm2v': 'video/mpeg', 'm3u': [ 'audio/x-mpegurl', 'audio/x-mpequrl' ], 'm3u8': 'application/vnd.apple.mpegurl', 'm4v': 'video/x-m4v', 'ma': 'application/mathematica', 'mads': 'application/mads+xml', 'mag': 'application/vnd.ecowin.chart', 'man': 'application/x-troff-man', 'map': 'application/x-navimap', 'mar': 'text/plain', 'mathml': 'application/mathml+xml', 'mbd': 'application/mbedlet', 'mbk': 'application/vnd.mobius.mbk', 'mbox': 'application/mbox', 'mc$': 'application/x-magic-cap-package-1.0', 'mc1': 'application/vnd.medcalcdata', 'mcd': [ 'application/mcad', 'application/vnd.mcd', 'application/x-mathcad' ], 'mcf': [ 'image/vasa', 'text/mcf' ], 'mcp': 'application/netmc', 'mcurl': 'text/vnd.curl.mcurl', 'mdb': 'application/x-msaccess', 'mdi': 'image/vnd.ms-modi', 'me': 'application/x-troff-me', 'meta4': 'application/metalink4+xml', 'mets': 'application/mets+xml', 'mfm': 'application/vnd.mfmp', 'mgp': 'application/vnd.osgeo.mapguide.package', 'mgz': 'application/vnd.proteus.magazine', 'mht': 'message/rfc822', 'mhtml': 'message/rfc822', 'mid': [ 'audio/mid', 'audio/midi', 'music/crescendo', 'x-music/x-midi', 'audio/x-midi', 'application/x-midi', 'audio/x-mid' ], 'midi': [ 'audio/midi', 'music/crescendo', 'x-music/x-midi', 'audio/x-midi', 'application/x-midi', 'audio/x-mid' ], 'mif': [ 'application/vnd.mif', 'application/x-mif', 'application/x-frame' ], 'mime': [ 'message/rfc822', 'www/mime' ], 'mj2': 'video/mj2', 'mjf': 'audio/x-vnd.audioexplosion.mjuicemediafile', 'mjpg': 'video/x-motion-jpeg', 'mlp': 'application/vnd.dolby.mlp', 'mm': [ 'application/base64', 'application/x-meme' ], 'mmd': 'application/vnd.chipnuts.karaoke-mmd', 'mme': 'application/base64', 'mmf': 'application/vnd.smaf', 'mmr': 'image/vnd.fujixerox.edmics-mmr', 'mny': 'application/x-msmoney', 'mod': [ 'audio/mod', 'audio/x-mod' ], 'mods': 'application/mods+xml', 'moov': 'video/quicktime', 'mov': 'video/quicktime', 'movie': 'video/x-sgi-movie', 'mp2': [ 'video/mpeg', 'audio/mpeg', 'video/x-mpeg', 'audio/x-mpeg', 'video/x-mpeq2a' ], 'mp3': [ 'audio/mpeg', 'audio/mpeg3', 'video/mpeg', 'audio/x-mpeg-3', 'video/x-mpeg' ], 'mp4': [ 'video/mp4', 'application/mp4' ], 'mp4a': 'audio/mp4', 'mpa': [ 'video/mpeg', 'audio/mpeg' ], 'mpc': [ 'application/vnd.mophun.certificate', 'application/x-project' ], 'mpe': 'video/mpeg', 'mpeg': 'video/mpeg', 'mpg': [ 'video/mpeg', 'audio/mpeg' ], 'mpga': 'audio/mpeg', 'mpkg': 'application/vnd.apple.installer+xml', 'mpm': 'application/vnd.blueice.multipass', 'mpn': 'application/vnd.mophun.application', 'mpp': 'application/vnd.ms-project', 'mpt': 'application/x-project', 'mpv': 'application/x-project', 'mpv2': 'video/mpeg', 'mpx': 'application/x-project', 'mpy': 'application/vnd.ibm.minipay', 'mqy': 'application/vnd.mobius.mqy', 'mrc': 'application/marc', 'mrcx': 'application/marcxml+xml', 'ms': 'application/x-troff-ms', 'mscml': 'application/mediaservercontrol+xml', 'mseq': 'application/vnd.mseq', 'msf': 'application/vnd.epson.msf', 'msg': 'application/vnd.ms-outlook', 'msh': 'model/mesh', 'msl': 'application/vnd.mobius.msl', 'msty': 'application/vnd.muvee.style', 'mts': 'model/vnd.mts', 'mus': 'application/vnd.musician', 'musicxml': 'application/vnd.recordare.musicxml+xml', 'mv': 'video/x-sgi-movie', 'mvb': 'application/x-msmediaview', 'mwf': 'application/vnd.mfer', 'mxf': 'application/mxf', 'mxl': 'application/vnd.recordare.musicxml', 'mxml': 'application/xv+xml', 'mxs': 'application/vnd.triscape.mxs', 'mxu': 'video/vnd.mpegurl', 'my': 'audio/make', 'mzz': 'application/x-vnd.audioexplosion.mzz', 'n-gage': 'application/vnd.nokia.n-gage.symbian.install', 'n3': 'text/n3', 'nap': 'image/naplps', 'naplps': 'image/naplps', 'nbp': 'application/vnd.wolfram.player', 'nc': 'application/x-netcdf', 'ncm': 'application/vnd.nokia.configuration-message', 'ncx': 'application/x-dtbncx+xml', 'ngdat': 'application/vnd.nokia.n-gage.data', 'nif': 'image/x-niff', 'niff': 'image/x-niff', 'nix': 'application/x-mix-transfer', 'nlu': 'application/vnd.neurolanguage.nlu', 'nml': 'application/vnd.enliven', 'nnd': 'application/vnd.noblenet-directory', 'nns': 'application/vnd.noblenet-sealer', 'nnw': 'application/vnd.noblenet-web', 'npx': 'image/vnd.net-fpx', 'nsc': 'application/x-conference', 'nsf': 'application/vnd.lotus-notes', 'nvd': 'application/x-navidoc', 'nws': 'message/rfc822', 'o': 'application/octet-stream', 'oa2': 'application/vnd.fujitsu.oasys2', 'oa3': 'application/vnd.fujitsu.oasys3', 'oas': 'application/vnd.fujitsu.oasys', 'obd': 'application/x-msbinder', 'oda': 'application/oda', 'odb': 'application/vnd.oasis.opendocument.database', 'odc': 'application/vnd.oasis.opendocument.chart', 'odf': 'application/vnd.oasis.opendocument.formula', 'odft': 'application/vnd.oasis.opendocument.formula-template', 'odg': 'application/vnd.oasis.opendocument.graphics', 'odi': 'application/vnd.oasis.opendocument.image', 'odm': 'application/vnd.oasis.opendocument.text-master', 'odp': 'application/vnd.oasis.opendocument.presentation', 'ods': 'application/vnd.oasis.opendocument.spreadsheet', 'odt': 'application/vnd.oasis.opendocument.text', 'oga': 'audio/ogg', 'ogv': 'video/ogg', 'ogx': 'application/ogg', 'omc': 'application/x-omc', 'omcd': 'application/x-omcdatamaker', 'omcr': 'application/x-omcregerator', 'onetoc': 'application/onenote', 'opf': 'application/oebps-package+xml', 'org': 'application/vnd.lotus-organizer', 'osf': 'application/vnd.yamaha.openscoreformat', 'osfpvg': 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'otc': 'application/vnd.oasis.opendocument.chart-template', 'otf': 'application/x-font-otf', 'otg': 'application/vnd.oasis.opendocument.graphics-template', 'oth': 'application/vnd.oasis.opendocument.text-web', 'oti': 'application/vnd.oasis.opendocument.image-template', 'otp': 'application/vnd.oasis.opendocument.presentation-template', 'ots': 'application/vnd.oasis.opendocument.spreadsheet-template', 'ott': 'application/vnd.oasis.opendocument.text-template', 'oxt': 'application/vnd.openofficeorg.extension', 'p': 'text/x-pascal', 'p10': [ 'application/pkcs10', 'application/x-pkcs10' ], 'p12': [ 'application/pkcs-12', 'application/x-pkcs12' ], 'p7a': 'application/x-pkcs7-signature', 'p7b': 'application/x-pkcs7-certificates', 'p7c': [ 'application/pkcs7-mime', 'application/x-pkcs7-mime' ], 'p7m': [ 'application/pkcs7-mime', 'application/x-pkcs7-mime' ], 'p7r': 'application/x-pkcs7-certreqresp', 'p7s': [ 'application/pkcs7-signature', 'application/x-pkcs7-signature' ], 'p8': 'application/pkcs8', 'par': 'text/plain-bas', 'part': 'application/pro_eng', 'pas': 'text/pascal', 'paw': 'application/vnd.pawaafile', 'pbd': 'application/vnd.powerbuilder6', 'pbm': 'image/x-portable-bitmap', 'pcf': 'application/x-font-pcf', 'pcl': [ 'application/vnd.hp-pcl', 'application/x-pcl' ], 'pclxl': 'application/vnd.hp-pclxl', 'pct': 'image/x-pict', 'pcurl': 'application/vnd.curl.pcurl', 'pcx': 'image/x-pcx', 'pdb': [ 'application/vnd.palm', 'chemical/x-pdb' ], 'pdf': 'application/pdf', 'pfa': 'application/x-font-type1', 'pfr': 'application/font-tdpfr', 'pfunk': [ 'audio/make', 'audio/make.my.funk' ], 'pfx': 'application/x-pkcs12', 'pgm': [ 'image/x-portable-graymap', 'image/x-portable-greymap' ], 'pgn': 'application/x-chess-pgn', 'pgp': 'application/pgp-signature', 'pic': [ 'image/pict', 'image/x-pict' ], 'pict': 'image/pict', 'pkg': 'application/x-newton-compatible-pkg', 'pki': 'application/pkixcmp', 'pkipath': 'application/pkix-pkipath', 'pko': [ 'application/ynd.ms-pkipko', 'application/vnd.ms-pki.pko' ], 'pl': [ 'text/plain', 'text/x-script.perl' ], 'plb': 'application/vnd.3gpp.pic-bw-large', 'plc': 'application/vnd.mobius.plc', 'plf': 'application/vnd.pocketlearn', 'pls': 'application/pls+xml', 'plx': 'application/x-pixclscript', 'pm': [ 'text/x-script.perl-module', 'image/x-xpixmap' ], 'pm4': 'application/x-pagemaker', 'pm5': 'application/x-pagemaker', 'pma': 'application/x-perfmon', 'pmc': 'application/x-perfmon', 'pml': [ 'application/vnd.ctc-posml', 'application/x-perfmon' ], 'pmr': 'application/x-perfmon', 'pmw': 'application/x-perfmon', 'png': 'image/png', 'pnm': [ 'application/x-portable-anymap', 'image/x-portable-anymap' ], 'portpkg': 'application/vnd.macports.portpkg', 'pot': [ 'application/vnd.ms-powerpoint', 'application/mspowerpoint' ], 'potm': 'application/vnd.ms-powerpoint.template.macroenabled.12', 'potx': 'application/vnd.openxmlformats-officedocument.presentationml.template', 'pov': 'model/x-pov', 'ppa': 'application/vnd.ms-powerpoint', 'ppam': 'application/vnd.ms-powerpoint.addin.macroenabled.12', 'ppd': 'application/vnd.cups-ppd', 'ppm': 'image/x-portable-pixmap', 'pps': [ 'application/vnd.ms-powerpoint', 'application/mspowerpoint' ], 'ppsm': 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 'ppsx': 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppt': [ 'application/vnd.ms-powerpoint', 'application/mspowerpoint', 'application/powerpoint', 'application/x-mspowerpoint' ], 'pptm': 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ppz': 'application/mspowerpoint', 'prc': 'application/x-mobipocket-ebook', 'pre': [ 'application/vnd.lotus-freelance', 'application/x-freelance' ], 'prf': 'application/pics-rules', 'prt': 'application/pro_eng', 'ps': 'application/postscript', 'psb': 'application/vnd.3gpp.pic-bw-small', 'psd': [ 'application/octet-stream', 'image/vnd.adobe.photoshop' ], 'psf': 'application/x-font-linux-psf', 'pskcxml': 'application/pskc+xml', 'ptid': 'application/vnd.pvi.ptid1', 'pub': 'application/x-mspublisher', 'pvb': 'application/vnd.3gpp.pic-bw-var', 'pvu': 'paleovu/x-pv', 'pwn': 'application/vnd.3m.post-it-notes', 'pwz': 'application/vnd.ms-powerpoint', 'py': 'text/x-script.phyton', 'pya': 'audio/vnd.ms-playready.media.pya', 'pyc': 'applicaiton/x-bytecode.python', 'pyv': 'video/vnd.ms-playready.media.pyv', 'qam': 'application/vnd.epson.quickanime', 'qbo': 'application/vnd.intu.qbo', 'qcp': 'audio/vnd.qcelp', 'qd3': 'x-world/x-3dmf', 'qd3d': 'x-world/x-3dmf', 'qfx': 'application/vnd.intu.qfx', 'qif': 'image/x-quicktime', 'qps': 'application/vnd.publishare-delta-tree', 'qt': 'video/quicktime', 'qtc': 'video/x-qtc', 'qti': 'image/x-quicktime', 'qtif': 'image/x-quicktime', 'qxd': 'application/vnd.quark.quarkxpress', 'ra': [ 'audio/x-realaudio', 'audio/x-pn-realaudio', 'audio/x-pn-realaudio-plugin' ], 'ram': 'audio/x-pn-realaudio', 'rar': 'application/x-rar-compressed', 'ras': [ 'image/cmu-raster', 'application/x-cmu-raster', 'image/x-cmu-raster' ], 'rast': 'image/cmu-raster', 'rcprofile': 'application/vnd.ipunplugged.rcprofile', 'rdf': 'application/rdf+xml', 'rdz': 'application/vnd.data-vision.rdz', 'rep': 'application/vnd.businessobjects', 'res': 'application/x-dtbresource+xml', 'rexx': 'text/x-script.rexx', 'rf': 'image/vnd.rn-realflash', 'rgb': 'image/x-rgb', 'rif': 'application/reginfo+xml', 'rip': 'audio/vnd.rip', 'rl': 'application/resource-lists+xml', 'rlc': 'image/vnd.fujixerox.edmics-rlc', 'rld': 'application/resource-lists-diff+xml', 'rm': [ 'application/vnd.rn-realmedia', 'audio/x-pn-realaudio' ], 'rmi': 'audio/mid', 'rmm': 'audio/x-pn-realaudio', 'rmp': [ 'audio/x-pn-realaudio-plugin', 'audio/x-pn-realaudio' ], 'rms': 'application/vnd.jcp.javame.midlet-rms', 'rnc': 'application/relax-ng-compact-syntax', 'rng': [ 'application/ringing-tones', 'application/vnd.nokia.ringing-tone' ], 'rnx': 'application/vnd.rn-realplayer', 'roff': 'application/x-troff', 'rp': 'image/vnd.rn-realpix', 'rp9': 'application/vnd.cloanto.rp9', 'rpm': 'audio/x-pn-realaudio-plugin', 'rpss': 'application/vnd.nokia.radio-presets', 'rpst': 'application/vnd.nokia.radio-preset', 'rq': 'application/sparql-query', 'rs': 'application/rls-services+xml', 'rsd': 'application/rsd+xml', 'rt': [ 'text/richtext', 'text/vnd.rn-realtext' ], 'rtf': [ 'application/rtf', 'text/richtext', 'application/x-rtf' ], 'rtx': [ 'text/richtext', 'application/rtf' ], 'rv': 'video/vnd.rn-realvideo', 's': 'text/x-asm', 's3m': 'audio/s3m', 'saf': 'application/vnd.yamaha.smaf-audio', 'saveme': 'application/octet-stream', 'sbk': 'application/x-tbook', 'sbml': 'application/sbml+xml', 'sc': 'application/vnd.ibm.secure-container', 'scd': 'application/x-msschedule', 'scm': [ 'application/vnd.lotus-screencam', 'video/x-scm', 'text/x-script.guile', 'application/x-lotusscreencam', 'text/x-script.scheme' ], 'scq': 'application/scvp-cv-request', 'scs': 'application/scvp-cv-response', 'sct': 'text/scriptlet', 'scurl': 'text/vnd.curl.scurl', 'sda': 'application/vnd.stardivision.draw', 'sdc': 'application/vnd.stardivision.calc', 'sdd': 'application/vnd.stardivision.impress', 'sdkm': 'application/vnd.solent.sdkm+xml', 'sdml': 'text/plain', 'sdp': [ 'application/sdp', 'application/x-sdp' ], 'sdr': 'application/sounder', 'sdw': 'application/vnd.stardivision.writer', 'sea': [ 'application/sea', 'application/x-sea' ], 'see': 'application/vnd.seemail', 'seed': 'application/vnd.fdsn.seed', 'sema': 'application/vnd.sema', 'semd': 'application/vnd.semd', 'semf': 'application/vnd.semf', 'ser': 'application/java-serialized-object', 'set': 'application/set', 'setpay': 'application/set-payment-initiation', 'setreg': 'application/set-registration-initiation', 'sfd-hdstx': 'application/vnd.hydrostatix.sof-data', 'sfs': 'application/vnd.spotfire.sfs', 'sgl': 'application/vnd.stardivision.writer-global', 'sgm': [ 'text/sgml', 'text/x-sgml' ], 'sgml': [ 'text/sgml', 'text/x-sgml' ], 'sh': [ 'application/x-shar', 'application/x-bsh', 'application/x-sh', 'text/x-script.sh' ], 'shar': [ 'application/x-bsh', 'application/x-shar' ], 'shf': 'application/shf+xml', 'shtml': [ 'text/html', 'text/x-server-parsed-html' ], 'sid': 'audio/x-psid', 'sis': 'application/vnd.symbian.install', 'sit': [ 'application/x-stuffit', 'application/x-sit' ], 'sitx': 'application/x-stuffitx', 'skd': 'application/x-koan', 'skm': 'application/x-koan', 'skp': [ 'application/vnd.koan', 'application/x-koan' ], 'skt': 'application/x-koan', 'sl': 'application/x-seelogo', 'sldm': 'application/vnd.ms-powerpoint.slide.macroenabled.12', 'sldx': 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'slt': 'application/vnd.epson.salt', 'sm': 'application/vnd.stepmania.stepchart', 'smf': 'application/vnd.stardivision.math', 'smi': [ 'application/smil', 'application/smil+xml' ], 'smil': 'application/smil', 'snd': [ 'audio/basic', 'audio/x-adpcm' ], 'snf': 'application/x-font-snf', 'sol': 'application/solids', 'spc': [ 'text/x-speech', 'application/x-pkcs7-certificates' ], 'spf': 'application/vnd.yamaha.smaf-phrase', 'spl': [ 'application/futuresplash', 'application/x-futuresplash' ], 'spot': 'text/vnd.in3d.spot', 'spp': 'application/scvp-vp-response', 'spq': 'application/scvp-vp-request', 'spr': 'application/x-sprite', 'sprite': 'application/x-sprite', 'src': 'application/x-wais-source', 'sru': 'application/sru+xml', 'srx': 'application/sparql-results+xml', 'sse': 'application/vnd.kodak-descriptor', 'ssf': 'application/vnd.epson.ssf', 'ssi': 'text/x-server-parsed-html', 'ssm': 'application/streamingmedia', 'ssml': 'application/ssml+xml', 'sst': [ 'application/vnd.ms-pkicertstore', 'application/vnd.ms-pki.certstore' ], 'st': 'application/vnd.sailingtracker.track', 'stc': 'application/vnd.sun.xml.calc.template', 'std': 'application/vnd.sun.xml.draw.template', 'step': 'application/step', 'stf': 'application/vnd.wt.stf', 'sti': 'application/vnd.sun.xml.impress.template', 'stk': 'application/hyperstudio', 'stl': [ 'application/vnd.ms-pkistl', 'application/sla', 'application/vnd.ms-pki.stl', 'application/x-navistyle' ], 'stm': 'text/html', 'stp': 'application/step', 'str': 'application/vnd.pg.format', 'stw': 'application/vnd.sun.xml.writer.template', 'sub': 'image/vnd.dvb.subtitle', 'sus': 'application/vnd.sus-calendar', 'sv4cpio': 'application/x-sv4cpio', 'sv4crc': 'application/x-sv4crc', 'svc': 'application/vnd.dvb.service', 'svd': 'application/vnd.svd', 'svf': [ 'image/vnd.dwg', 'image/x-dwg' ], 'svg': 'image/svg+xml', 'svr': [ 'x-world/x-svr', 'application/x-world' ], 'swf': 'application/x-shockwave-flash', 'swi': 'application/vnd.aristanetworks.swi', 'sxc': 'application/vnd.sun.xml.calc', 'sxd': 'application/vnd.sun.xml.draw', 'sxg': 'application/vnd.sun.xml.writer.global', 'sxi': 'application/vnd.sun.xml.impress', 'sxm': 'application/vnd.sun.xml.math', 'sxw': 'application/vnd.sun.xml.writer', 't': [ 'text/troff', 'application/x-troff' ], 'talk': 'text/x-speech', 'tao': 'application/vnd.tao.intent-module-archive', 'tar': 'application/x-tar', 'tbk': [ 'application/toolbook', 'application/x-tbook' ], 'tcap': 'application/vnd.3gpp2.tcap', 'tcl': [ 'text/x-script.tcl', 'application/x-tcl' ], 'tcsh': 'text/x-script.tcsh', 'teacher': 'application/vnd.smart.teacher', 'tei': 'application/tei+xml', 'tex': 'application/x-tex', 'texi': 'application/x-texinfo', 'texinfo': 'application/x-texinfo', 'text': [ 'application/plain', 'text/plain' ], 'tfi': 'application/thraud+xml', 'tfm': 'application/x-tex-tfm', 'tgz': [ 'application/gnutar', 'application/x-compressed' ], 'thmx': 'application/vnd.ms-officetheme', 'tif': [ 'image/tiff', 'image/x-tiff' ], 'tiff': [ 'image/tiff', 'image/x-tiff' ], 'tmo': 'application/vnd.tmobile-livetv', 'torrent': 'application/x-bittorrent', 'tpl': 'application/vnd.groove-tool-template', 'tpt': 'application/vnd.trid.tpt', 'tr': 'application/x-troff', 'tra': 'application/vnd.trueapp', 'trm': 'application/x-msterminal', 'tsd': 'application/timestamped-data', 'tsi': 'audio/tsp-audio', 'tsp': [ 'application/dsptype', 'audio/tsplayer' ], 'tsv': 'text/tab-separated-values', 'ttf': 'application/x-font-ttf', 'ttl': 'text/turtle', 'turbot': 'image/florian', 'twd': 'application/vnd.simtech-mindmapper', 'txd': 'application/vnd.genomatix.tuxedo', 'txf': 'application/vnd.mobius.txf', 'txt': 'text/plain', 'ufd': 'application/vnd.ufdl', 'uil': 'text/x-uil', 'uls': 'text/iuls', 'umj': 'application/vnd.umajin', 'uni': 'text/uri-list', 'unis': 'text/uri-list', 'unityweb': 'application/vnd.unity', 'unv': 'application/i-deas', 'uoml': 'application/vnd.uoml+xml', 'uri': 'text/uri-list', 'uris': 'text/uri-list', 'ustar': [ 'application/x-ustar', 'multipart/x-ustar' ], 'utz': 'application/vnd.uiq.theme', 'uu': [ 'application/octet-stream', 'text/x-uuencode' ], 'uue': 'text/x-uuencode', 'uva': 'audio/vnd.dece.audio', 'uvh': 'video/vnd.dece.hd', 'uvi': 'image/vnd.dece.graphic', 'uvm': 'video/vnd.dece.mobile', 'uvp': 'video/vnd.dece.pd', 'uvs': 'video/vnd.dece.sd', 'uvu': 'video/vnd.uvvu.mp4', 'uvv': 'video/vnd.dece.video', 'vcd': 'application/x-cdlink', 'vcf': 'text/x-vcard', 'vcg': 'application/vnd.groove-vcard', 'vcs': 'text/x-vcalendar', 'vcx': 'application/vnd.vcx', 'vda': 'application/vda', 'vdo': 'video/vdo', 'vew': 'application/groupwise', 'vis': 'application/vnd.visionary', 'viv': [ 'video/vivo', 'video/vnd.vivo' ], 'vivo': [ 'video/vivo', 'video/vnd.vivo' ], 'vmd': 'application/vocaltec-media-desc', 'vmf': 'application/vocaltec-media-file', 'voc': [ 'audio/voc', 'audio/x-voc' ], 'vos': 'video/vosaic', 'vox': 'audio/voxware', 'vqe': 'audio/x-twinvq-plugin', 'vqf': 'audio/x-twinvq', 'vql': 'audio/x-twinvq-plugin', 'vrml': [ 'model/vrml', 'x-world/x-vrml', 'application/x-vrml' ], 'vrt': 'x-world/x-vrt', 'vsd': [ 'application/vnd.visio', 'application/x-visio' ], 'vsf': 'application/vnd.vsf', 'vst': 'application/x-visio', 'vsw': 'application/x-visio', 'vtu': 'model/vnd.vtu', 'vxml': 'application/voicexml+xml', 'w60': 'application/wordperfect6.0', 'w61': 'application/wordperfect6.1', 'w6w': 'application/msword', 'wad': 'application/x-doom', 'wav': [ 'audio/wav', 'audio/x-wav' ], 'wax': 'audio/x-ms-wax', 'wb1': 'application/x-qpro', 'wbmp': 'image/vnd.wap.wbmp', 'wbs': 'application/vnd.criticaltools.wbs+xml', 'wbxml': 'application/vnd.wap.wbxml', 'wcm': 'application/vnd.ms-works', 'wdb': 'application/vnd.ms-works', 'web': 'application/vnd.xara', 'weba': 'audio/webm', 'webm': 'video/webm', 'webp': 'image/webp', 'wg': 'application/vnd.pmi.widget', 'wgt': 'application/widget', 'wiz': 'application/msword', 'wk1': 'application/x-123', 'wks': 'application/vnd.ms-works', 'wm': 'video/x-ms-wm', 'wma': 'audio/x-ms-wma', 'wmd': 'application/x-ms-wmd', 'wmf': [ 'windows/metafile', 'application/x-msmetafile' ], 'wml': 'text/vnd.wap.wml', 'wmlc': 'application/vnd.wap.wmlc', 'wmls': 'text/vnd.wap.wmlscript', 'wmlsc': 'application/vnd.wap.wmlscriptc', 'wmv': 'video/x-ms-wmv', 'wmx': 'video/x-ms-wmx', 'wmz': 'application/x-ms-wmz', 'woff': 'application/x-font-woff', 'word': 'application/msword', 'wp': 'application/wordperfect', 'wp5': [ 'application/wordperfect', 'application/wordperfect6.0' ], 'wp6': 'application/wordperfect', 'wpd': [ 'application/wordperfect', 'application/vnd.wordperfect', 'application/x-wpwin' ], 'wpl': 'application/vnd.ms-wpl', 'wps': 'application/vnd.ms-works', 'wq1': 'application/x-lotus', 'wqd': 'application/vnd.wqd', 'wri': [ 'application/mswrite', 'application/x-wri', 'application/x-mswrite' ], 'wrl': [ 'model/vrml', 'x-world/x-vrml', 'application/x-world' ], 'wrz': [ 'model/vrml', 'x-world/x-vrml' ], 'wsc': 'text/scriplet', 'wsdl': 'application/wsdl+xml', 'wspolicy': 'application/wspolicy+xml', 'wsrc': 'application/x-wais-source', 'wtb': 'application/vnd.webturbo', 'wtk': 'application/x-wintalk', 'wvx': 'video/x-ms-wvx', 'x-png': 'image/png', 'x3d': 'application/vnd.hzn-3d-crossword', 'xaf': 'x-world/x-vrml', 'xap': 'application/x-silverlight-app', 'xar': 'application/vnd.xara', 'xbap': 'application/x-ms-xbap', 'xbd': 'application/vnd.fujixerox.docuworks.binder', 'xbm': [ 'image/xbm', 'image/x-xbm', 'image/x-xbitmap' ], 'xdf': 'application/xcap-diff+xml', 'xdm': 'application/vnd.syncml.dm+xml', 'xdp': 'application/vnd.adobe.xdp+xml', 'xdr': 'video/x-amt-demorun', 'xdssc': 'application/dssc+xml', 'xdw': 'application/vnd.fujixerox.docuworks', 'xenc': 'application/xenc+xml', 'xer': 'application/patch-ops-error+xml', 'xfdf': 'application/vnd.adobe.xfdf', 'xfdl': 'application/vnd.xfdl', 'xgz': 'xgl/drawing', 'xhtml': 'application/xhtml+xml', 'xif': 'image/vnd.xiff', 'xl': 'application/excel', 'xla': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-msexcel', 'application/x-excel' ], 'xlam': 'application/vnd.ms-excel.addin.macroenabled.12', 'xlb': [ 'application/excel', 'application/vnd.ms-excel', 'application/x-excel' ], 'xlc': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-excel' ], 'xld': [ 'application/excel', 'application/x-excel' ], 'xlk': [ 'application/excel', 'application/x-excel' ], 'xll': [ 'application/excel', 'application/vnd.ms-excel', 'application/x-excel' ], 'xlm': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-excel' ], 'xls': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-msexcel', 'application/x-excel' ], 'xlsb': 'application/vnd.ms-excel.sheet.binary.macroenabled.12', 'xlsm': 'application/vnd.ms-excel.sheet.macroenabled.12', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-excel' ], 'xltm': 'application/vnd.ms-excel.template.macroenabled.12', 'xltx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlv': [ 'application/excel', 'application/x-excel' ], 'xlw': [ 'application/vnd.ms-excel', 'application/excel', 'application/x-msexcel', 'application/x-excel' ], 'xm': 'audio/xm', 'xml': [ 'application/xml', 'text/xml', 'application/atom+xml', 'application/rss+xml' ], 'xmz': 'xgl/movie', 'xo': 'application/vnd.olpc-sugar', 'xof': 'x-world/x-vrml', 'xop': 'application/xop+xml', 'xpi': 'application/x-xpinstall', 'xpix': 'application/x-vnd.ls-xpix', 'xpm': [ 'image/xpm', 'image/x-xpixmap' ], 'xpr': 'application/vnd.is-xpr', 'xps': 'application/vnd.ms-xpsdocument', 'xpw': 'application/vnd.intercon.formnet', 'xslt': 'application/xslt+xml', 'xsm': 'application/vnd.syncml+xml', 'xspf': 'application/xspf+xml', 'xsr': 'video/x-amt-showrun', 'xul': 'application/vnd.mozilla.xul+xml', 'xwd': [ 'image/x-xwd', 'image/x-xwindowdump' ], 'xyz': [ 'chemical/x-xyz', 'chemical/x-pdb' ], 'yang': 'application/yang', 'yin': 'application/yin+xml', 'z': [ 'application/x-compressed', 'application/x-compress' ], 'zaz': 'application/vnd.zzazz.deck+xml', 'zip': [ 'application/zip', 'multipart/x-zip', 'application/x-zip-compressed', 'application/x-compressed' ], 'zir': 'application/vnd.zul', 'zmm': 'application/vnd.handheld-entertainment+xml', 'zoo': 'application/octet-stream', 'zsh': 'text/x-script.zsh' }; return { detectExtension: detectExtension, detectMimeType: detectMimeType }; })); (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define('utf7', factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.utf7 = factory(); } }(this, function () { 'use strict'; function encode(str) { var b = new Uint8Array(str.length * 2), octets = '', i, bi, len, c, encoded; for (i = 0, bi = 0, len = str.length; i < len; i++) { c = str.charCodeAt(i); b[bi++] = c >> 8; b[bi++] = c & 255; } for (i = 0, len = b.length; i < len; i++) { octets += String.fromCharCode(b[i]); } encoded = ''; if (typeof window !== 'undefined' && btoa) { encoded = btoa(octets); } else { encoded = new Buffer(octets, 'binary').toString('base64'); } return encoded.replace(/=+$/, ''); } function base64toTypedArray(base64Str) { var bitsSoFar = 0; var validBits = 0; var iOut = 0; var arr = new Uint8Array(Math.ceil(base64Str.length * 3 / 4)); var c; var bits; for (var i = 0, len = base64Str.length; i < len; i++) { c = base64Str.charCodeAt(i); if (c >= 65 && c <= 90) { bits = c - 65; } else if (c >= 97 && c <= 122) { bits = c - 97 + 26; } else if (c >= 48 && c <= 57) { bits = c - 48 + 52; } else if (c === 43) { bits = 62; } else if (c === 47) { bits = 63; } else if (c === 61) { validBits = 0; continue; } else { continue; } bitsSoFar = bitsSoFar << 6 | bits; validBits += 6; if (validBits >= 8) { validBits -= 8; arr[iOut++] = bitsSoFar >> validBits; if (validBits === 2) { bitsSoFar &= 3; } else if (validBits === 4) { bitsSoFar &= 15; } } } if (iOut < arr.length) { return arr.subarray(0, iOut); } return arr; } function decode(str) { var octets = base64toTypedArray(str), r = []; for (var i = 0, len = octets.length; i < len;) { r.push(String.fromCharCode(octets[i++] << 8 | octets[i++])); } return r.join(''); } function escape(chars) { return chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } var setD = 'A-Za-z0-9' + escape('\'(),-./:?'), setO = escape('!"#$%&*;<=>@[]^_\'{|}'), setW = escape(' \r\n\t'), regexes = {}, regexAll = new RegExp('[^' + setW + setD + setO + ']+', 'g'); return { encode: function (str, mask) { if (!mask) { mask = ''; } if (!regexes[mask]) { regexes[mask] = new RegExp('[^' + setD + escape(mask) + ']+', 'g'); } return str.replace(regexes[mask], function (chunk) { return '+' + (chunk === '+' ? '' : encode(chunk)) + '-'; }); }, encodeAll: function (str) { return str.replace(regexAll, function (chunk) { return '+' + (chunk === '+' ? '' : encode(chunk)) + '-'; }); }, decode: function (str) { return str.replace(/\+([A-Za-z0-9\/]*)-?/gi, function (_, chunk) { if (chunk === '') { return '+'; } return decode(chunk); }); }, imap: { encode: function (str) { return str.replace(/&/g, '&-').replace(/[^\x20-\x7e]+/g, function (chunk) { chunk = (chunk === '&' ? '' : encode(chunk)).replace(/\//g, ','); return '&' + chunk + '-'; }); }, decode: function (str) { return str.replace(/&([^-]*)-/g, function (_, chunk) { if (chunk === '') { return '&'; } return decode(chunk.replace(/,/g, '/')); }); } } }; })); define('stringencoding', [ 'require', 'utf7', 'mimefuncs' ], function (require) { var utf7 = require('utf7'); return { TextEncoder: function (encoding) { var encoder = new TextEncoder(encoding); this.encode = encoder.encode.bind(encoder); }, TextDecoder: function (encoding) { encoding = encoding && encoding.toLowerCase(); if (encoding === 'utf-7' || encoding === 'utf7') { this.decode = function (buf) { var mimefuncs = require('mimefuncs'); return utf7.decode(mimefuncs.fromTypedArray(buf)); }; } else { var decoder = new TextDecoder(encoding); this.decode = decoder.decode.bind(decoder); } } }; }); (function (root, factory) { 'use strict'; var encoding; if (typeof define === 'function' && define.amd) { define('mimefuncs', ['stringencoding'], function (encoding) { return factory(encoding.TextEncoder, encoding.TextDecoder, root.btoa); }); } else if (typeof exports === 'object' && typeof navigator !== 'undefined') { encoding = require('wo-stringencoding'); module.exports = factory(encoding.TextEncoder, encoding.TextDecoder, root.btoa); } else if (typeof exports === 'object') { encoding = require('wo-stringencoding'); module.exports = factory(encoding.TextEncoder, encoding.TextDecoder, function (str) { var NodeBuffer = require('buffer').Buffer; return new NodeBuffer(str, 'binary').toString('base64'); }); } else { root.mimefuncs = factory(root.TextEncoder, root.TextDecoder, root.btoa); } }(this, function (TextEncoder, TextDecoder, btoa) { 'use strict'; btoa = btoa || base64Encode; var mimefuncs = { mimeEncode: function (data, fromCharset) { fromCharset = fromCharset || 'UTF-8'; var buffer = mimefuncs.charset.convert(data || '', fromCharset), ranges = [ [9], [10], [13], [32], [33], [ 35, 60 ], [62], [ 64, 94 ], [ 96, 126 ] ], result = '', ord; for (var i = 0, len = buffer.length; i < len; i++) { ord = buffer[i]; if (mimefuncs._checkRanges(ord, ranges) && !((ord === 32 || ord === 9) && (i === len - 1 || buffer[i + 1] === 10 || buffer[i + 1] === 13))) { result += String.fromCharCode(ord); continue; } result += '=' + (ord < 16 ? '0' : '') + ord.toString(16).toUpperCase(); } return result; }, mimeDecode: function (str, fromCharset) { str = (str || '').toString(); fromCharset = fromCharset || 'UTF-8'; var encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length, bufferLength = str.length - encodedBytesCount * 2, chr, hex, buffer = new Uint8Array(bufferLength), bufferPos = 0; for (var i = 0, len = str.length; i < len; i++) { chr = str.charAt(i); if (chr === '=' && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { buffer[bufferPos++] = parseInt(hex, 16); i += 2; continue; } buffer[bufferPos++] = chr.charCodeAt(0); } return mimefuncs.charset.decode(buffer, fromCharset); }, base64Encode: function (data, fromCharset) { var buf, b64; if (fromCharset !== 'binary' && typeof data !== 'string') { buf = mimefuncs.charset.convert(data || '', fromCharset); } else { buf = data; } b64 = mimefuncs.base64.encode(buf); return mimefuncs._addSoftLinebreaks(b64, 'base64'); }, base64Decode: function (str, fromCharset) { var buf = mimefuncs.base64.decode(str || '', 'buffer'); return mimefuncs.charset.decode(buf, fromCharset); }, quotedPrintableEncode: function (data, fromCharset) { var mimeEncodedStr = mimefuncs.mimeEncode(data, fromCharset); mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, '\r\n').replace(/[\t ]+$/gm, function (spaces) { return spaces.replace(/ /g, '=20').replace(/\t/g, '=09'); }); return mimefuncs._addSoftLinebreaks(mimeEncodedStr, 'qp'); }, quotedPrintableDecode: function (str, fromCharset) { str = (str || '').toString(); str = str.replace(/[\t ]+$/gm, '').replace(/\=(?:\r?\n|$)/g, ''); return mimefuncs.mimeDecode(str, fromCharset); }, mimeWordEncode: function (data, mimeWordEncoding, maxLength, fromCharset) { mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0); if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) { fromCharset = maxLength; maxLength = undefined; } maxLength = maxLength || 0; var encodedStr, toCharset = 'UTF-8', i, len, parts; if (maxLength && maxLength > 7 + toCharset.length) { maxLength -= 7 + toCharset.length; } if (mimeWordEncoding === 'Q') { encodedStr = mimefuncs.mimeEncode(data, fromCharset); encodedStr = encodedStr.replace(/[\r\n\t_]/g, function (chr) { var code = chr.charCodeAt(0); return '=' + (code < 16 ? '0' : '') + code.toString(16).toUpperCase(); }).replace(/\s/g, '_'); } else if (mimeWordEncoding === 'B') { encodedStr = typeof data === 'string' ? data : mimefuncs.decode(data, fromCharset); maxLength = Math.max(3, (maxLength - maxLength % 4) / 4 * 3); } if (maxLength && encodedStr.length > maxLength) { if (mimeWordEncoding === 'Q') { encodedStr = mimefuncs._splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?'); } else { parts = []; for (i = 0, len = encodedStr.length; i < len; i += maxLength) { parts.push(mimefuncs.base64.encode(encodedStr.substr(i, maxLength))); } if (parts.length > 1) { return '=?' + toCharset + '?' + mimeWordEncoding + '?' + parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?') + '?='; } else { encodedStr = parts.join(''); } } } else if (mimeWordEncoding === 'B') { encodedStr = mimefuncs.base64.encode(encodedStr); } return '=?' + toCharset + '?' + mimeWordEncoding + '?' + encodedStr + (encodedStr.substr(-2) === '?=' ? '' : '?='); }, mimeWordsEncode: function (data, mimeWordEncoding, maxLength, fromCharset) { if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) { fromCharset = maxLength; maxLength = undefined; } maxLength = maxLength || 0; var decodedValue = mimefuncs.charset.decode(mimefuncs.charset.convert(data || '', fromCharset)), encodedValue; encodedValue = decodedValue.replace(/([^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*(?:\s+[^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*\s*)?)+/g, function (match) { return match.length ? mimefuncs.mimeWordEncode(match, mimeWordEncoding || 'Q', maxLength) : ''; }); return encodedValue; }, mimeWordDecode: function (str) { str = (str || '').toString().trim(); var fromCharset, encoding, match; match = str.match(/^\=\?([\w_\-\*]+)\?([QqBb])\?([^\?]+)\?\=$/i); if (!match) { return str; } fromCharset = match[1].split('*').shift(); encoding = (match[2] || 'Q').toString().toUpperCase(); str = (match[3] || '').replace(/_/g, ' '); if (encoding === 'B') { return mimefuncs.base64Decode(str, fromCharset); } else if (encoding === 'Q') { return mimefuncs.mimeDecode(str, fromCharset); } else { return str; } }, mimeWordsDecode: function (str) { str = (str || '').toString(); str = str.replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]+\?=)/g, '$1').replace(/\=\?([\w_\-\*]+)\?([QqBb])\?[^\?]+\?\=/g, function (mimeWord) { return mimefuncs.mimeWordDecode(mimeWord); }); return str; }, foldLines: function (str, lineLengthMax, afterSpace) { str = (str || '').toString(); lineLengthMax = lineLengthMax || 76; var pos = 0, len = str.length, result = '', line, match; while (pos < len) { line = str.substr(pos, lineLengthMax); if (line.length < lineLengthMax) { result += line; break; } if (match = line.match(/^[^\n\r]*(\r?\n|\r)/)) { line = match[0]; result += line; pos += line.length; continue; } else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) { line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0))); } else if (match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/)) { line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0)); } result += line; pos += line.length; if (pos < len) { result += '\r\n'; } } return result; }, headerLineEncode: function (key, value, fromCharset) { var encodedValue = mimefuncs.mimeWordsEncode(value, 'Q', 52, fromCharset); return mimefuncs.foldLines(key + ': ' + encodedValue, 76); }, headerLineDecode: function (headerLine) { var line = (headerLine || '').toString().replace(/(?:\r?\n|\r)[ \t]*/g, ' ').trim(), match = line.match(/^\s*([^:]+):(.*)$/), key = (match && match[1] || '').trim(), value = (match && match[2] || '').trim(); return { key: key, value: value }; }, headerLinesDecode: function (headers) { var lines = headers.split(/\r?\n|\r/), headersObj = {}, key, value, header, i, len; for (i = lines.length - 1; i >= 0; i--) { if (i && lines[i].match(/^\s/)) { lines[i - 1] += '\r\n' + lines[i]; lines.splice(i, 1); } } for (i = 0, len = lines.length; i < len; i++) { header = mimefuncs.headerLineDecode(lines[i]); key = (header.key || '').toString().toLowerCase().trim(); value = header.value || ''; if (!headersObj[key]) { headersObj[key] = value; } else { headersObj[key] = [].concat(headersObj[key], value); } } return headersObj; }, toTypedArray: function (binaryString) { var buf = new Uint8Array(binaryString.length); for (var i = 0, len = binaryString.length; i < len; i++) { buf[i] = binaryString.charCodeAt(i); } return buf; }, fromTypedArray: function (buf) { var i, l; if (!buf.buffer) { buf = new Uint8Array(buf); } var sbits = new Array(buf.length); for (i = 0, l = buf.length; i < l; i++) { sbits[i] = String.fromCharCode(buf[i]); } return sbits.join(''); }, parseHeaderValue: function (str) { var response = { value: false, params: {} }, key = false, value = '', type = 'value', quote = false, escaped = false, chr; for (var i = 0, len = str.length; i < len; i++) { chr = str.charAt(i); if (type === 'key') { if (chr === '=') { key = value.trim().toLowerCase(); type = 'value'; value = ''; continue; } value += chr; } else { if (escaped) { value += chr; } else if (chr === '\\') { escaped = true; continue; } else if (quote && chr === quote) { quote = false; } else if (!quote && chr === '"') { quote = chr; } else if (!quote && chr === ';') { if (key === false) { response.value = value.trim(); } else { response.params[key] = value.trim(); } type = 'key'; value = ''; } else { value += chr; } escaped = false; } } if (type === 'value') { if (key === false) { response.value = value.trim(); } else { response.params[key] = value.trim(); } } else if (value.trim()) { response.params[value.trim().toLowerCase()] = ''; } Object.keys(response.params).forEach(function (key) { var actualKey, nr, match, value; if (match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/)) { actualKey = key.substr(0, match.index); nr = Number(match[2] || match[3]) || 0; if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') { response.params[actualKey] = { charset: false, values: [] }; } value = response.params[key]; if (nr === 0 && match[0].substr(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) { response.params[actualKey].charset = match[1] || 'iso-8859-1'; value = match[2]; } response.params[actualKey].values[nr] = value; delete response.params[key]; } }); Object.keys(response.params).forEach(function (key) { var value; if (response.params[key] && Array.isArray(response.params[key].values)) { value = response.params[key].values.map(function (val) { return val || ''; }).join(''); if (response.params[key].charset) { response.params[key] = '=?' + response.params[key].charset + '?Q?' + value.replace(/[=\?_\s]/g, function (s) { var c = s.charCodeAt(0).toString(16); if (s === ' ') { return '_'; } else { return '%' + (c.length < 2 ? '0' : '') + c; } }).replace(/%/g, '=') + '?='; } else { response.params[key] = value; } } }.bind(this)); return response; }, continuationEncode: function (key, data, maxLength, fromCharset) { var list = []; var encodedStr = typeof data === 'string' ? data : mimefuncs.decode(data, fromCharset); var chr; var line; var startPos = 0; var isEncoded = false; maxLength = maxLength || 50; if (/^[\w.\- ]*$/.test(data)) { if (encodedStr.length <= maxLength) { return [{ key: key, value: /[\s";=]/.test(encodedStr) ? '"' + encodedStr + '"' : encodedStr }]; } encodedStr = encodedStr.replace(new RegExp('.{' + maxLength + '}', 'g'), function (str) { list.push({ line: str }); return ''; }); if (encodedStr) { list.push({ line: encodedStr }); } } else { line = 'utf-8\'\''; isEncoded = true; startPos = 0; for (var i = 0, len = encodedStr.length; i < len; i++) { chr = encodedStr[i]; if (isEncoded) { chr = encodeURIComponent(chr); } else { chr = chr === ' ' ? chr : encodeURIComponent(chr); if (chr !== encodedStr[i]) { if ((encodeURIComponent(line) + chr).length >= maxLength) { list.push({ line: line, encoded: isEncoded }); line = ''; startPos = i - 1; } else { isEncoded = true; i = startPos; line = ''; continue; } } } if ((line + chr).length >= maxLength) { list.push({ line: line, encoded: isEncoded }); line = chr = encodedStr[i] === ' ' ? ' ' : encodeURIComponent(encodedStr[i]); if (chr === encodedStr[i]) { isEncoded = false; startPos = i - 1; } else { isEncoded = true; } } else { line += chr; } } if (line) { list.push({ line: line, encoded: isEncoded }); } } return list.map(function (item, i) { return { key: key + '*' + i + (item.encoded ? '*' : ''), value: /[\s";=]/.test(item.line) ? '"' + item.line + '"' : item.line }; }); }, _splitMimeEncodedString: function (str, maxlen) { var curLine, match, chr, done, lines = []; maxlen = Math.max(maxlen || 0, 12); while (str.length) { curLine = str.substr(0, maxlen); if (match = curLine.match(/\=[0-9A-F]?$/i)) { curLine = curLine.substr(0, match.index); } done = false; while (!done) { done = true; if (match = str.substr(curLine.length).match(/^\=([0-9A-F]{2})/i)) { chr = parseInt(match[1], 16); if (chr < 194 && chr > 127) { curLine = curLine.substr(0, curLine.length - 3); done = false; } } } if (curLine.length) { lines.push(curLine); } str = str.substr(curLine.length); } return lines; }, _addSoftLinebreaks: function (str, encoding) { var lineLengthMax = 76; encoding = (encoding || 'base64').toString().toLowerCase().trim(); if (encoding === 'qp') { return mimefuncs._addQPSoftLinebreaks(str, lineLengthMax); } else { return mimefuncs._addBase64SoftLinebreaks(str, lineLengthMax); } }, _addBase64SoftLinebreaks: function (base64EncodedStr, lineLengthMax) { base64EncodedStr = (base64EncodedStr || '').toString().trim(); return base64EncodedStr.replace(new RegExp('.{' + lineLengthMax + '}', 'g'), '$&\r\n').trim(); }, _addQPSoftLinebreaks: function (qpEncodedStr, lineLengthMax) { qpEncodedStr = (qpEncodedStr || '').toString(); lineLengthMax = lineLengthMax || 76; var pos = 0, len = qpEncodedStr.length, match, code, line, lineMargin = Math.floor(lineLengthMax / 3), result = ''; while (pos < len) { line = qpEncodedStr.substr(pos, lineLengthMax); if (match = line.match(/\r\n/)) { line = line.substr(0, match.index + match[0].length); result += line; pos += line.length; continue; } if (line.substr(-1) === '\n') { result += line; pos += line.length; continue; } else if (match = line.substr(-lineMargin).match(/\n.*?$/)) { line = line.substr(0, line.length - (match[0].length - 1)); result += line; pos += line.length; continue; } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) { line = line.substr(0, line.length - (match[0].length - 1)); } else if (line.substr(-1) === '\r') { line = line.substr(0, line.length - 1); } else { if (line.match(/\=[\da-f]{0,2}$/i)) { if (match = line.match(/\=[\da-f]{0,1}$/i)) { line = line.substr(0, line.length - match[0].length); } while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/gi))) { code = parseInt(match[0].substr(1, 2), 16); if (code < 128) { break; } line = line.substr(0, line.length - 3); if (code >= 192) { break; } } } } if (pos + line.length < len && line.substr(-1) !== '\n') { if (line.length === lineLengthMax && line.match(/\=[\da-f]{2}$/i)) { line = line.substr(0, line.length - 3); } else if (line.length === lineLengthMax) { line = line.substr(0, line.length - 1); } pos += line.length; line += '=\r\n'; } else { pos += line.length; } result += line; } return result; }, _checkRanges: function (nr, ranges) { for (var i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) { continue; } if (ranges[i].length === 1 && nr === ranges[i][0]) { return true; } if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) { return true; } } return false; } }; mimefuncs.charset = { encode: function (str) { return new TextEncoder('UTF-8').encode(str); }, decode: function (buf, fromCharset) { fromCharset = mimefuncs.charset.normalizeCharset(fromCharset || 'UTF-8'); if (!buf.buffer) { buf = new Uint8Array(buf); } try { return new TextDecoder(fromCharset).decode(buf); } catch (E) { try { return new TextDecoder('utf-8', { fatal: true }).decode(buf); } catch (E) { try { return new TextDecoder('iso-8859-15').decode(buf); } catch (E) { return mimefuncs.fromTypedArray(buf); } } } }, convert: function (data, fromCharset) { fromCharset = mimefuncs.charset.normalizeCharset(fromCharset || 'UTF-8'); var bufString; if (typeof data !== 'string') { if (fromCharset.match(/^utf[\-_]?8$/)) { return data; } bufString = mimefuncs.charset.decode(data, fromCharset); return mimefuncs.charset.encode(bufString); } return mimefuncs.charset.encode(data); }, normalizeCharset: function (charset) { var match; if (match = charset.match(/^utf[\-_]?(\d+)$/i)) { return 'UTF-' + match[1]; } if (match = charset.match(/^win[\-_]?(\d+)$/i)) { return 'WINDOWS-' + match[1]; } if (match = charset.match(/^latin[\-_]?(\d+)$/i)) { return 'ISO-8859-' + match[1]; } return charset; } }; mimefuncs.base64 = { encode: function (data) { if (!data) { return ''; } if (typeof data === 'string') { return btoa(unescape(encodeURIComponent(data))); } var len = data.byteLength, binStr = ''; if (!data.buffer) { data.buffer = new Uint8Array(data); } for (var i = 0; i < len; i++) { binStr += String.fromCharCode(data[i]); } return btoa(binStr); }, decode: function (data, outputEncoding) { outputEncoding = (outputEncoding || 'buffer').toLowerCase().trim(); var buf = mimefuncs.base64.toTypedArray(data); if (outputEncoding === 'string') { return mimefuncs.charset.decode(buf); } else { return buf; } }, toTypedArray: function (base64Str) { var bitsSoFar = 0; var validBits = 0; var iOut = 0; var arr = new Uint8Array(Math.ceil(base64Str.length * 3 / 4)); var c; var bits; for (var i = 0, len = base64Str.length; i < len; i++) { c = base64Str.charCodeAt(i); if (c >= 65 && c <= 90) { bits = c - 65; } else if (c >= 97 && c <= 122) { bits = c - 97 + 26; } else if (c >= 48 && c <= 57) { bits = c - 48 + 52; } else if (c === 43) { bits = 62; } else if (c === 47) { bits = 63; } else if (c === 61) { validBits = 0; continue; } else { continue; } bitsSoFar = bitsSoFar << 6 | bits; validBits += 6; if (validBits >= 8) { validBits -= 8; arr[iOut++] = bitsSoFar >> validBits; if (validBits === 2) { bitsSoFar &= 3; } else if (validBits === 4) { bitsSoFar &= 15; } } } if (iOut < arr.length) { return arr.subarray(0, iOut); } return arr; } }; function base64Encode(input) { var str = String(input); var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; for (var block, charCode, idx = 0, map = chars, output = ''; str.charAt(idx | 0) || (map = '=', idx % 1); output += map.charAt(63 & block >> 8 - idx % 1 * 8)) { charCode = str.charCodeAt(idx += 3 / 4); if (charCode > 255) { throw new Error('\'btoa\' failed: The string to be encoded contains characters outside of the Latin1 range.'); } block = block << 8 | charCode; } return output; } return mimefuncs; }));