Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
17293 lines
No EOL
661 KiB
JavaScript
17293 lines
No EOL
661 KiB
JavaScript
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 '<LogicMatcher ' + (this.ns ? this.ns + '/' : '') + this.type + ' ' + new ObjectSimplifier().simplify(this.detailPredicate) + '>';
|
|
}
|
|
};
|
|
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 '<LogicEvent ' + this.namespace + '/' + this.type + ' ' + JSON.stringify(this.jsonRepresentation.details) + '>';
|
|
},
|
|
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(/<!DOCTYPE\s+[^>]*>/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 += '</' + tag + '>';
|
|
},
|
|
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 += '<!--' + comment + '-->';
|
|
}
|
|
};
|
|
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('<!--', 0) == 0) {
|
|
index = html.indexOf('-->');
|
|
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('</', 0) == 0) {
|
|
match = html.match(endTag);
|
|
if (match) {
|
|
html = html.substring(match[0].length);
|
|
match[0].replace(endTag, parseEndTag);
|
|
chars = false;
|
|
}
|
|
} else if (html.lastIndexOf('<', 0) == 0) {
|
|
match = html.match(startTag);
|
|
if (match) {
|
|
html = html.substring(match[0].length);
|
|
match[0].replace(startTag, parseStartTag);
|
|
chars = false;
|
|
}
|
|
}
|
|
if (chars) {
|
|
index = html.indexOf('<');
|
|
if (index === 0) {
|
|
var text = html.substring(0, 1);
|
|
html = html.substring(1);
|
|
} else {
|
|
var text = index < 0 ? html : html.substring(0, index);
|
|
html = index < 0 ? '' : html.substring(index);
|
|
}
|
|
if (handler.chars) {
|
|
handler.chars(text);
|
|
if (handler.complete)
|
|
return this;
|
|
}
|
|
}
|
|
} else {
|
|
var skipWork = false;
|
|
html = html.replace(new RegExp('^([^]*?)</' + stack.last() + '[^>]*>', 'i'), function (all, text) {
|
|
if (!skipWork) {
|
|
text = text.replace(/<!--([^]*?)-->/g, '$1').replace(/<!\[CDATA\[([^]*?)]]>/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, '<br/>') : 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 + '</' + wrapTag + '>';
|
|
};
|
|
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 += '<blockquote ';
|
|
if (refGuid) {
|
|
htmlMsg += 'cite="mid:' + $htmlchew.escapeAttrValue(refGuid) + '" ';
|
|
}
|
|
htmlMsg += 'type="cite">' + rep + '</blockquote>';
|
|
}
|
|
}
|
|
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 = '<html><body><body bgcolor="#FFFFFF" text="#000000">';
|
|
var HTML_WRAP_BOTTOM = '</body></html>';
|
|
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;
|
|
})); |