more cleanup
parent
d02d77a212
commit
1e8c8991ad
@ -1,12 +0,0 @@
|
||||
{
|
||||
"S3PublishEndpoints": {
|
||||
"signal-desktop-apt": {
|
||||
"region": "us-east-1",
|
||||
"bucket": "updates.signal.org",
|
||||
"prefix": "desktop/apt",
|
||||
"acl": "public-read",
|
||||
"plusWorkaround": false,
|
||||
"disableMultiDel": false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "session-desktop",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/loki-project/session-desktop",
|
||||
"license": "GPLV3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"indexeddb-backbonejs-adapter": "*",
|
||||
"protobuf": "~3.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mock-socket": "~0.3.2"
|
||||
},
|
||||
"preen": {
|
||||
"indexeddb-backbonejs-adapter": [
|
||||
"backbone-indexeddb.js"
|
||||
],
|
||||
"mock-socket": [
|
||||
"dist/mock-socket.js"
|
||||
],
|
||||
"protobuf": [
|
||||
"dist/ProtoBuf.js"
|
||||
]
|
||||
},
|
||||
"concat": {
|
||||
"app": [
|
||||
"node_modules/jquery/dist/jquery.js",
|
||||
"node_modules/bytebuffer/dist/bytebuffer.min.js",
|
||||
"node_modules/long/dist/long.js",
|
||||
"components/protobuf/**/*.js",
|
||||
"node_modules/mustache/mustache.js",
|
||||
"node_modules/underscore/underscore.js",
|
||||
"node_modules/backbone/backbone.js",
|
||||
"components/indexeddb-backbonejs-adapter/**/*.js"
|
||||
],
|
||||
"libtextsecure": [
|
||||
"node_modules/long/dist/long.js",
|
||||
"components/protobuf/**/*.js"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html class='no-js' lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta content='width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' name='viewport'>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Session</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href='/images/session/session_icon_128.png' rel='shortcut icon'>
|
||||
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="js/chromium.js"></script>
|
||||
</head>
|
||||
<body id="signal-container" class='signal index'>
|
||||
<div class='app-loading-screen'>
|
||||
<div class="content session-full-logo">
|
||||
<img src="images/session/brand.svg" class="session-brand-logo" />
|
||||
<img src="images/session/session-text.svg" class="session-text-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
</body>
|
||||
<html class="no-js" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
|
||||
name="viewport"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Session</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link href="/images/session/session_icon_128.png" rel="shortcut icon" />
|
||||
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body id="signal-container" class="signal index">
|
||||
<div class="app-loading-screen">
|
||||
<div class="content session-full-logo">
|
||||
<img src="images/session/brand.svg" class="session-brand-logo" />
|
||||
<img src="images/session/session-text.svg" class="session-text-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,15 +0,0 @@
|
||||
/* global extension: false */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Browser specific functions for Chrom*
|
||||
window.extension = window.extension || {};
|
||||
|
||||
extension.windows = {
|
||||
onClosed(callback) {
|
||||
window.addEventListener('beforeunload', callback);
|
||||
},
|
||||
};
|
||||
})();
|
@ -1,122 +0,0 @@
|
||||
/* global _: false */
|
||||
/* global Backbone: false */
|
||||
|
||||
/* global Whisper: false */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
window.Whisper.Database = window.Whisper.Database || {};
|
||||
window.Whisper.Database.id = window.Whisper.Database.id || 'loki-messenger';
|
||||
window.Whisper.Database.nolog = true;
|
||||
|
||||
Whisper.Database.handleDOMException = (prefix, error, reject) => {
|
||||
window.log.error(
|
||||
`${prefix}:`,
|
||||
error && error.name,
|
||||
error && error.message,
|
||||
error && error.code
|
||||
);
|
||||
reject(error || new Error(prefix));
|
||||
};
|
||||
|
||||
function clearStores(db, names) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const storeNames = names || db.objectStoreNames;
|
||||
window.log.info('Clearing these indexeddb stores:', storeNames);
|
||||
const transaction = db.transaction(storeNames, 'readwrite');
|
||||
|
||||
let finished = false;
|
||||
const finish = via => {
|
||||
window.log.info('clearing all stores done via', via);
|
||||
if (finished) {
|
||||
resolve();
|
||||
}
|
||||
finished = true;
|
||||
};
|
||||
|
||||
transaction.oncomplete = finish.bind(null, 'transaction complete');
|
||||
transaction.onerror = () => {
|
||||
Whisper.Database.handleDOMException(
|
||||
'clearStores transaction error',
|
||||
transaction.error,
|
||||
reject
|
||||
);
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
|
||||
// can't use built-in .forEach because db.objectStoreNames is not a plain array
|
||||
_.forEach(storeNames, storeName => {
|
||||
const store = transaction.objectStore(storeName);
|
||||
const request = store.clear();
|
||||
|
||||
request.onsuccess = () => {
|
||||
count += 1;
|
||||
window.log.info('Done clearing store', storeName);
|
||||
|
||||
if (count >= storeNames.length) {
|
||||
window.log.info('Done clearing indexeddb stores');
|
||||
finish('clears complete');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
Whisper.Database.handleDOMException('clearStores request error', request.error, reject);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Whisper.Database.open = () => {
|
||||
const { migrations } = Whisper.Database;
|
||||
const { version } = migrations[migrations.length - 1];
|
||||
const DBOpenRequest = window.indexedDB.open(Whisper.Database.id, version);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// these two event handlers act on the IDBDatabase object,
|
||||
// when the database is opened successfully, or not
|
||||
DBOpenRequest.onerror = reject;
|
||||
DBOpenRequest.onsuccess = () => resolve(DBOpenRequest.result);
|
||||
|
||||
// This event handles the event whereby a new version of
|
||||
// the database needs to be created Either one has not
|
||||
// been created before, or a new version number has been
|
||||
// submitted via the window.indexedDB.open line above
|
||||
DBOpenRequest.onupgradeneeded = reject;
|
||||
});
|
||||
};
|
||||
|
||||
Whisper.Database.clear = async () => {
|
||||
const db = await Whisper.Database.open();
|
||||
await clearStores(db);
|
||||
db.close();
|
||||
};
|
||||
|
||||
Whisper.Database.clearStores = async storeNames => {
|
||||
const db = await Whisper.Database.open();
|
||||
await clearStores(db, storeNames);
|
||||
db.close();
|
||||
};
|
||||
|
||||
Whisper.Database.close = () => window.wrapDeferred(Backbone.sync('closeall'));
|
||||
|
||||
Whisper.Database.drop = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = window.indexedDB.deleteDatabase(Whisper.Database.id);
|
||||
|
||||
request.onblocked = () => {
|
||||
reject(new Error('Error deleting database: Blocked.'));
|
||||
};
|
||||
request.onupgradeneeded = () => {
|
||||
reject(new Error('Error deleting database: Upgrade needed.'));
|
||||
};
|
||||
request.onerror = () => {
|
||||
reject(new Error('Error deleting database.'));
|
||||
};
|
||||
|
||||
request.onsuccess = resolve;
|
||||
});
|
||||
})();
|
@ -1,89 +0,0 @@
|
||||
/* global Backbone, Whisper */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
const Item = Backbone.Model.extend({
|
||||
database: Whisper.Database,
|
||||
storeName: 'items',
|
||||
});
|
||||
const ItemCollection = Backbone.Collection.extend({
|
||||
model: Item,
|
||||
storeName: 'items',
|
||||
database: Whisper.Database,
|
||||
});
|
||||
|
||||
let ready = false;
|
||||
const items = new ItemCollection();
|
||||
items.on('reset', () => {
|
||||
ready = true;
|
||||
});
|
||||
window.legacyStorage = {
|
||||
/** ***************************
|
||||
*** Base Storage Routines ***
|
||||
**************************** */
|
||||
put(key, value) {
|
||||
if (value === undefined) {
|
||||
throw new Error('Tried to store undefined');
|
||||
}
|
||||
if (!ready) {
|
||||
window.log.warn('Called storage.put before storage is ready. key:', key);
|
||||
}
|
||||
const item = items.add({ id: key, value }, { merge: true });
|
||||
return new Promise((resolve, reject) => {
|
||||
item.save().then(resolve, reject);
|
||||
});
|
||||
},
|
||||
|
||||
get(key, defaultValue) {
|
||||
const item = items.get(`${key}`);
|
||||
if (!item) {
|
||||
return defaultValue;
|
||||
}
|
||||
return item.get('value');
|
||||
},
|
||||
|
||||
remove(key) {
|
||||
const item = items.get(`${key}`);
|
||||
if (item) {
|
||||
items.remove(item);
|
||||
return new Promise((resolve, reject) => {
|
||||
item.destroy().then(resolve, reject);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onready(callback) {
|
||||
if (ready) {
|
||||
callback();
|
||||
} else {
|
||||
items.on('reset', callback);
|
||||
}
|
||||
},
|
||||
|
||||
fetch() {
|
||||
return new Promise((resolve, reject) => {
|
||||
items
|
||||
.fetch({ reset: true })
|
||||
.fail(() =>
|
||||
reject(
|
||||
new Error(
|
||||
'Failed to fetch from storage.' +
|
||||
' This may be due to an unexpected database version.'
|
||||
)
|
||||
)
|
||||
)
|
||||
.always(resolve);
|
||||
});
|
||||
},
|
||||
|
||||
reset() {
|
||||
items.reset();
|
||||
},
|
||||
};
|
||||
})();
|
@ -1,65 +0,0 @@
|
||||
/* global indexedDB */
|
||||
|
||||
// Module for interacting with IndexedDB without Backbone IndexedDB adapter
|
||||
// and using promises. Revisit use of `idb` dependency as it might cover
|
||||
// this functionality.
|
||||
|
||||
const { isObject, isNumber } = require('lodash');
|
||||
|
||||
exports.open = (name, version, { onUpgradeNeeded } = {}) => {
|
||||
const request = indexedDB.open(name, version);
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onblocked = () => reject(new Error('Database blocked'));
|
||||
|
||||
request.onupgradeneeded = event => {
|
||||
const hasRequestedSpecificVersion = isNumber(version);
|
||||
if (!hasRequestedSpecificVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { newVersion, oldVersion } = event;
|
||||
if (onUpgradeNeeded) {
|
||||
const { transaction } = event.target;
|
||||
onUpgradeNeeded({ oldVersion, transaction });
|
||||
return;
|
||||
}
|
||||
|
||||
reject(
|
||||
new Error(`Database upgrade required: oldVersion: ${oldVersion}, newVersion: ${newVersion}`)
|
||||
);
|
||||
};
|
||||
|
||||
request.onerror = event => reject(event.target.error);
|
||||
|
||||
request.onsuccess = event => {
|
||||
const connection = event.target.result;
|
||||
resolve(connection);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
exports.completeTransaction = transaction =>
|
||||
new Promise((resolve, reject) => {
|
||||
transaction.addEventListener('abort', event => reject(event.target.error));
|
||||
transaction.addEventListener('error', event => reject(event.target.error));
|
||||
transaction.addEventListener('complete', () => resolve());
|
||||
});
|
||||
|
||||
exports.getVersion = async name => {
|
||||
const connection = await exports.open(name);
|
||||
const { version } = connection;
|
||||
connection.close();
|
||||
return version;
|
||||
};
|
||||
|
||||
exports.getCount = async ({ store } = {}) => {
|
||||
if (!isObject(store)) {
|
||||
throw new TypeError("'store' is required");
|
||||
}
|
||||
|
||||
const request = store.count();
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onerror = event => reject(event.target.error);
|
||||
request.onsuccess = event => resolve(event.target.result);
|
||||
});
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
/* eslint-disable more/no-then */
|
||||
class JobQueue {
|
||||
constructor() {
|
||||
this.pending = Promise.resolve();
|
||||
}
|
||||
|
||||
add(job) {
|
||||
const previous = this.pending || Promise.resolve();
|
||||
this.pending = previous.then(job, job);
|
||||
const current = this.pending;
|
||||
|
||||
current.then(() => {
|
||||
if (this.pending === current) {
|
||||
delete this.pending;
|
||||
}
|
||||
});
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
JobQueue,
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
/* global setTimeout */
|
||||
|
||||
exports.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
@ -1,82 +0,0 @@
|
||||
/* global window, Event, textsecure */
|
||||
|
||||
/*
|
||||
* Implements EventTarget
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
||||
*/
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
function EventTarget() {}
|
||||
|
||||
EventTarget.prototype = {
|
||||
constructor: EventTarget,
|
||||
dispatchEvent(ev) {
|
||||
if (!(ev instanceof Event)) {
|
||||
throw new Error('Expects an event');
|
||||
}
|
||||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
const listeners = this.listeners[ev.type];
|
||||
const results = [];
|
||||
if (typeof listeners === 'object') {
|
||||
for (let i = 0, max = listeners.length; i < max; i += 1) {
|
||||
const listener = listeners[i];
|
||||
if (typeof listener === 'function') {
|
||||
results.push(listener.call(null, ev));
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
addEventListener(eventName, callback) {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Second argument expects a function');
|
||||
}
|
||||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
let listeners = this.listeners[eventName];
|
||||
if (typeof listeners !== 'object') {
|
||||
listeners = [];
|
||||
}
|
||||
listeners.push(callback);
|
||||
this.listeners[eventName] = listeners;
|
||||
},
|
||||
removeEventListener(eventName, callback) {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Second argument expects a function');
|
||||
}
|
||||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
const listeners = this.listeners[eventName];
|
||||
if (typeof listeners === 'object') {
|
||||
for (let i = 0; i < listeners.length; i += 1) {
|
||||
if (listeners[i] === callback) {
|
||||
listeners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.listeners[eventName] = listeners;
|
||||
},
|
||||
extend(obj) {
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const prop in obj) {
|
||||
this[prop] = obj[prop];
|
||||
}
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
textsecure.EventTarget = EventTarget;
|
||||
})();
|
@ -1,37 +0,0 @@
|
||||
/* global window, dcodeIO, textsecure */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.protobuf = {};
|
||||
|
||||
function loadProtoBufs(filename) {
|
||||
return dcodeIO.ProtoBuf.loadProtoFile(
|
||||
{ root: window.PROTO_ROOT, file: filename },
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT}) ${
|
||||
error && error.stack ? error.stack : error
|
||||
}`;
|
||||
window.log.error(text);
|
||||
throw error;
|
||||
}
|
||||
const protos = result.build('signalservice');
|
||||
if (!protos) {
|
||||
const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT})`;
|
||||
window.log.error(text);
|
||||
throw new Error(text);
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const protoName in protos) {
|
||||
textsecure.protobuf[protoName] = protos[protoName];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// this is all the Session Protocols
|
||||
loadProtoBufs('SignalService.proto');
|
||||
// this is for websocket wrapping of messages
|
||||
loadProtoBufs('SubProtocol.proto');
|
||||
})();
|
@ -1,100 +0,0 @@
|
||||
/* global window, StringView */
|
||||
|
||||
/* eslint-disable no-bitwise, no-nested-ternary, */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.StringView = {
|
||||
/*
|
||||
* These functions from the Mozilla Developer Network
|
||||
* and have been placed in the public domain.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
* https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
|
||||
*/
|
||||
|
||||
b64ToUint6(nChr) {
|
||||
return nChr > 64 && nChr < 91
|
||||
? nChr - 65
|
||||
: nChr > 96 && nChr < 123
|
||||
? nChr - 71
|
||||
: nChr > 47 && nChr < 58
|
||||
? nChr + 4
|
||||
: nChr === 43
|
||||
? 62
|
||||
: nChr === 47
|
||||
? 63
|
||||
: 0;
|
||||
},
|
||||
|
||||
// This is not a "standard" base64, do not use!
|
||||
base64ToBytes(sBase64, nBlocksSize) {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, '');
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = nBlocksSize
|
||||
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
|
||||
: (nInLen * 3 + 1) >> 2;
|
||||
const aBBytes = new ArrayBuffer(nOutLen);
|
||||
const taBytes = new Uint8Array(aBBytes);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
for (let nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx += 1) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3 += 1, nOutIdx += 1) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
return aBBytes;
|
||||
},
|
||||
|
||||
uint6ToB64(nUint6) {
|
||||
return nUint6 < 26
|
||||
? nUint6 + 65
|
||||
: nUint6 < 52
|
||||
? nUint6 + 71
|
||||
: nUint6 < 62
|
||||
? nUint6 - 4
|
||||
: nUint6 === 62
|
||||
? 43
|
||||
: nUint6 === 63
|
||||
? 47
|
||||
: 65;
|
||||
},
|
||||
|
||||
bytesToBase64(aBytes) {
|
||||
let nMod3;
|
||||
let sB64Enc = '';
|
||||
for (let nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx += 1) {
|
||||
nMod3 = nIdx % 3;
|
||||
if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
|
||||
sB64Enc += '\r\n';
|
||||
}
|
||||
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
|
||||
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||
sB64Enc += String.fromCharCode(
|
||||
StringView.uint6ToB64((nUint24 >>> 18) & 63),
|
||||
StringView.uint6ToB64((nUint24 >>> 12) & 63),
|
||||
StringView.uint6ToB64((nUint24 >>> 6) & 63),
|
||||
StringView.uint6ToB64(nUint24 & 63)
|
||||
);
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
return sB64Enc.replace(/A(?=A$|$)/g, '=');
|
||||
},
|
||||
|
||||
arrayBufferToHex(aArrayBuffer) {
|
||||
return Array.prototype.map
|
||||
.call(new Uint8Array(aArrayBuffer), x => `00${x.toString(16)}`.slice(-2))
|
||||
.join('');
|
||||
},
|
||||
|
||||
hexToArrayBuffer(aString) {
|
||||
return new Uint8Array(aString.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))).buffer;
|
||||
},
|
||||
};
|
||||
})();
|
@ -1,35 +0,0 @@
|
||||
import ByteBuffer from 'bytebuffer';
|
||||
|
||||
/**
|
||||
* Converts any object to a valid ts protobuf object.
|
||||
*
|
||||
* This is needed because there's a very jarring difference between `protobufjs` and `protobufts`.
|
||||
* `protobufjs` returns all `bytes` as `ByteBuffer` where as `protobufts` returns all `bytes` as `Uint8Array`.
|
||||
*/
|
||||
export function convertToTS(object: any): any {
|
||||
// No idea why js `ByteBuffer` and ts `ByteBuffer` differ ...
|
||||
if (object instanceof Uint8Array) {
|
||||
return object;
|
||||
} else if (object && object.constructor && object.constructor.name === 'ByteBuffer') {
|
||||
return new Uint8Array(object.toArrayBuffer());
|
||||
} else if (
|
||||
object instanceof ByteBuffer ||
|
||||
object instanceof Buffer ||
|
||||
object instanceof ArrayBuffer ||
|
||||
object instanceof SharedArrayBuffer
|
||||
) {
|
||||
const arrayBuffer = ByteBuffer.wrap(object).toArrayBuffer();
|
||||
return new Uint8Array(arrayBuffer);
|
||||
} else if (Array.isArray(object)) {
|
||||
return object.map(convertToTS);
|
||||
} else if (object && typeof object === 'object') {
|
||||
const keys = Object.keys(object);
|
||||
const values: { [key: string]: any } = {};
|
||||
for (const key of keys) {
|
||||
values[key] = convertToTS(object[key]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
// tslint:disable no-console
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import { fromPairs, groupBy, map } from 'lodash';
|
||||
|
||||
import { ExceptionType } from './types';
|
||||
import { loadJSON } from './util';
|
||||
|
||||
const exceptionsPath = join(__dirname, 'exceptions.json');
|
||||
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
|
||||
const byRule = groupBy(exceptions, 'rule');
|
||||
|
||||
const byRuleThenByCategory = fromPairs(
|
||||
map(byRule, (list, ruleName) => {
|
||||
const byCategory = groupBy(list, 'reasonCategory');
|
||||
|
||||
return [
|
||||
ruleName,
|
||||
fromPairs(
|
||||
map(byCategory, (innerList, categoryName) => {
|
||||
return [categoryName, innerList.length];
|
||||
})
|
||||
),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
console.log(JSON.stringify(byRuleThenByCategory, null, ' '));
|
File diff suppressed because it is too large
Load Diff
@ -1,266 +0,0 @@
|
||||
// tslint:disable no-console
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
|
||||
// @ts-ignore
|
||||
import * as glob from 'glob';
|
||||
import { forEach, some, values } from 'lodash';
|
||||
|
||||
import { ExceptionType, REASONS, RuleType } from './types';
|
||||
import { ENCODING, loadJSON, sortExceptions } from './util';
|
||||
|
||||
const ALL_REASONS = REASONS.join('|');
|
||||
const now = new Date();
|
||||
|
||||
function getExceptionKey(exception: any) {
|
||||
return `${exception.rule}-${exception.path}-${exception.lineNumber}`;
|
||||
}
|
||||
|
||||
function createLookup(list: Array<any>) {
|
||||
const lookup = Object.create(null);
|
||||
|
||||
forEach(list, exception => {
|
||||
const key = getExceptionKey(exception);
|
||||
|
||||
if (lookup[key]) {
|
||||
throw new Error(`Duplicate exception found for key ${key}`);
|
||||
}
|
||||
|
||||
lookup[key] = exception;
|
||||
});
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
const rulesPath = join(__dirname, 'rules.json');
|
||||
const exceptionsPath = join(__dirname, 'exceptions.json');
|
||||
const basePath = join(__dirname, '../../..');
|
||||
|
||||
const searchPattern = join(basePath, '**/*.{js,ts,tsx}');
|
||||
|
||||
const rules: Array<RuleType> = loadJSON(rulesPath);
|
||||
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
|
||||
const exceptionsLookup = createLookup(exceptions);
|
||||
let scannedCount = 0;
|
||||
|
||||
const allSourceFiles = glob.sync(searchPattern, { nodir: true });
|
||||
|
||||
const results: Array<ExceptionType> = [];
|
||||
|
||||
const excludedFiles = [
|
||||
// High-traffic files in our project
|
||||
'^js/background.js',
|
||||
|
||||
// Generated files
|
||||
'^js/components.js',
|
||||
'^js/curve/',
|
||||
'^js/libtextsecure.js',
|
||||
'^js/util_worker.js',
|
||||
'^libtextsecure/components.js',
|
||||
'^libtextsecure/test/test.js',
|
||||
'^test/test.js',
|
||||
|
||||
// From libsignal-protocol-javascript project
|
||||
'^libtextsecure/libsignal-protocol.js',
|
||||
|
||||
// Test files
|
||||
'^libtextsecure/test/*',
|
||||
'^test/*',
|
||||
|
||||
// Modules we trust
|
||||
'^node_modules/react/*',
|
||||
'^node_modules/react-dom/*',
|
||||
|
||||
// Modules used only in test/development scenarios
|
||||
'^node_modules/@types/*',
|
||||
'^node_modules/ajv/*',
|
||||
'^node_modules/amdefine/*',
|
||||
'^node_modules/anymatch/*',
|
||||
'^node_modules/app-builder-lib/*',
|
||||
'^node_modules/asn1\\.js/*',
|
||||
'^node_modules/autoprefixer/*',
|
||||
'^node_modules/babel*',
|
||||
'^node_modules/bluebird/*',
|
||||
'^node_modules/body-parser/*',
|
||||
'^node_modules/bower/*',
|
||||
'^node_modules/buble/*',
|
||||
'^node_modules/builder-util/*',
|
||||
'^node_modules/builder-util-runtime/*',
|
||||
'^node_modules/chai/*',
|
||||
'^node_modules/cli-table2/*',
|
||||
'^node_modules/codemirror/*',
|
||||
'^node_modules/coffee-script/*',
|
||||
'^node_modules/compression/*',
|
||||
'^node_modules/degenerator/*',
|
||||
'^node_modules/detect-port-alt/*',
|
||||
'^node_modules/electron-builder/*',
|
||||
'^node_modules/electron-osx-sign/*',
|
||||
'^node_modules/electron-publish/*',
|
||||
'^node_modules/escodegen/*',
|
||||
'^node_modules/eslint*',
|
||||
'^node_modules/esprima/*',
|
||||
'^node_modules/express/*',
|
||||
'^node_modules/finalhandler/*',
|
||||
'^node_modules/fsevents/*',
|
||||
'^node_modules/globule/*',
|
||||
'^node_modules/grunt*',
|
||||
'^node_modules/handle-thing/*',
|
||||
'^node_modules/har-validator/*',
|
||||
'^node_modules/highlight\\.js/*',
|
||||
'^node_modules/hpack\\.js/*',
|
||||
'^node_modules/http-proxy-middlewar/*',
|
||||
'^node_modules/icss-utils/*',
|
||||
'^node_modules/istanbul*',
|
||||
'^node_modules/jimp/*',
|
||||
'^node_modules/jquery/*',
|
||||
'^node_modules/jss/*',
|
||||
'^node_modules/jss-global/*',
|
||||
'^node_modules/livereload-js/*',
|
||||
'^node_modules/lolex/*',
|
||||
'^node_modules/magic-string/*',
|
||||
'^node_modules/mocha/*',
|
||||
'^node_modules/minimatch/*',
|
||||
'^node_modules/nise/*',
|
||||
'^node_modules/node-sass-import-once/*',
|
||||
'^node_modules/node-sass/*',
|
||||
'^node_modules/nsp/*',
|
||||
'^node_modules/phantomjs-prebuilt/*',
|
||||
'^node_modules/postcss*',
|
||||
'^node_modules/preserve/*',
|
||||
'^node_modules/prettier/*',
|
||||
'^node_modules/protobufjs/cli/*',
|
||||
'^node_modules/ramda/*',
|
||||
'^node_modules/react-docgen/*',
|
||||
'^node_modules/react-error-overlay/*',
|
||||
'^node_modules/recast/*',
|
||||
'^node_modules/reduce-css-calc/*',
|
||||
'^node_modules/resolve/*',
|
||||
'^node_modules/sass-graph/*',
|
||||
'^node_modules/scss-tokenizer/*',
|
||||
'^node_modules/send/*',
|
||||
'^node_modules/serve-index/*',
|
||||
'^node_modules/sinon/*',
|
||||
'^node_modules/snapdragon-util/*',
|
||||
'^node_modules/snapdragon/*',
|
||||
'^node_modules/sockjs-client/*',
|
||||
'^node_modules/style-loader/*',
|
||||
'^node_modules/svgo/*',
|
||||
'^node_modules/text-encoding/*',
|
||||
'^node_modules/tinycolor2/*',
|
||||
'^node_modules/to-ast/*',
|
||||
'^node_modules/trough/*',
|
||||
'^node_modules/ts-loader/*',
|
||||
'^node_modules/tslint*',
|
||||
'^node_modules/tweetnacl/*',
|
||||
'^node_modules/typescript/*',
|
||||
'^node_modules/uglify-es/*',
|
||||
'^node_modules/uglify-js/*',
|
||||
'^node_modules/use/*',
|
||||
'^node_modules/vary/*',
|
||||
'^node_modules/vm-browserify/*',
|
||||
'^node_modules/webdriverio/*',
|
||||
'^node_modules/webpack*',
|
||||
'^node_modules/xmldom/*',
|
||||
'^node_modules/xml-parse-from-string/*',
|
||||
];
|
||||
|
||||
function setupRules(allRules: Array<RuleType>) {
|
||||
forEach(allRules, (rule, index) => {
|
||||
if (!rule.name) {
|
||||
throw new Error(`Rule at index ${index} is missing a name`);
|
||||
}
|
||||
|
||||
if (!rule.expression) {
|
||||
throw new Error(`Rule '${rule.name}' is missing an expression`);
|
||||
}
|
||||
|
||||
rule.regex = new RegExp(rule.expression, 'g');
|
||||
});
|
||||
}
|
||||
|
||||
setupRules(rules);
|
||||
|
||||
forEach(allSourceFiles, file => {
|
||||
const relativePath = relative(basePath, file).replace(/\\/g, '/');
|
||||
if (
|
||||
some(excludedFiles, excluded => {
|
||||
const regex = new RegExp(excluded);
|
||||
|
||||
return regex.test(relativePath);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
scannedCount += 1;
|
||||
|
||||
const fileContents = readFileSync(file, ENCODING);
|
||||
const lines = fileContents.split('\n');
|
||||
|
||||
forEach(rules, (rule: RuleType) => {
|
||||
const excludedModules = rule.excludedModules || [];
|
||||
if (some(excludedModules, module => relativePath.startsWith(module))) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEach(lines, (rawLine, lineIndex) => {
|
||||
const line = rawLine.replace(/\r/g, '');
|
||||
if (!rule.regex.test(line)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = relativePath;
|
||||
const lineNumber = lineIndex + 1;
|
||||
|
||||
const exceptionKey = getExceptionKey({
|
||||
rule: rule.name,
|
||||
path: relativePath,
|
||||
lineNumber,
|
||||
});
|
||||
|
||||
const exception = exceptionsLookup[exceptionKey];
|
||||
if (exception && (!exception.line || exception.line === line)) {
|
||||
// tslint:disable-next-line no-dynamic-delete
|
||||
delete exceptionsLookup[exceptionKey];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
results.push({
|
||||
rule: rule.name,
|
||||
path,
|
||||
line: line.length < 300 ? line : undefined,
|
||||
lineNumber,
|
||||
reasonCategory: ALL_REASONS,
|
||||
updated: now.toJSON(),
|
||||
reasonDetail: '<optional>',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const unusedExceptions = values(exceptionsLookup);
|
||||
|
||||
console.log(
|
||||
`${scannedCount} files scanned.`,
|
||||
`${results.length} questionable lines,`,
|
||||
`${unusedExceptions.length} unused exceptions,`,
|
||||
`${exceptions.length} total exceptions.`
|
||||
);
|
||||
|
||||
if (results.length === 0 && unusedExceptions.length === 0) {
|
||||
process.exit();
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log('Questionable lines:');
|
||||
console.log(JSON.stringify(sortExceptions(results), null, ' '));
|
||||
|
||||
if (unusedExceptions.length) {
|
||||
console.log();
|
||||
console.log('Unused exceptions!');
|
||||
console.log(JSON.stringify(sortExceptions(unusedExceptions), null, ' '));
|
||||
}
|
||||
|
||||
process.exit(1);
|
@ -1,135 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "eval",
|
||||
"expression": "\\beval\\(",
|
||||
"reason": "Arbitrary code execution"
|
||||
},
|
||||
{
|
||||
"name": "DOM-innerHTML",
|
||||
"expression": "\\binnerHTML\\b",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "DOM-outerHTML",
|
||||
"expression": "\\bouterHTML\\b",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "DOM-document.write(",
|
||||
"expression": "\\bdocument.write(ln)?\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-$(",
|
||||
"expression": "\\$\\(",
|
||||
"reason": "Potential XSS",
|
||||
"excludedModules": ["node_modules/prelude-ls"]
|
||||
},
|
||||
{
|
||||
"name": "jQuery-html(",
|
||||
"expression": "\\bhtml\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-append(",
|
||||
"expression": "\\bappend\\(",
|
||||
"reason": "Potential XSS",
|
||||
"excludedModules": [
|
||||
"components/bytebuffer",
|
||||
"components/protobuf",
|
||||
"node_modules/google-libphonenumber",
|
||||
"node_modules/handlebars"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jQuery-appendTo(",
|
||||
"expression": "\\bappendTo\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-insertAfter(",
|
||||
"expression": "\\binsertAfter\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-insertBefore(",
|
||||
"expression": "\\binsertBefore\\(",
|
||||
"reason": "Potential XSS",
|
||||
"excludedModules": ["node_modules/react-dom"]
|
||||
},
|
||||
{
|
||||
"name": "jQuery-prepend(",
|
||||
"expression": "\\bprepend\\(",
|
||||
"reason": "Potential XSS",
|
||||
"excludedModules": ["components/bytebuffer", "node_modules/handlebars"]
|
||||
},
|
||||
{
|
||||
"name": "jQuery-prependTo(",
|
||||
"expression": "\\bprependTo\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-wrap(",
|
||||
"expression": "\\bwrap\\(",
|
||||
"reason": "Potential XSS",
|
||||
"excludedModules": [
|
||||
"components/bytebuffer",
|
||||
"components/protobuf",
|
||||
"node_modules/handlebars",
|
||||
"node_modules/lodash"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jQuery-wrapInner(",
|
||||
"expression": "\\bwrapInner\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-wrapAll(",
|
||||
"expression": "\\bwrapAll\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-before(",
|
||||
"expression": "\\bbefore\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-after(",
|
||||
"expression": "\\bafter\\(",
|
||||
"reason": "Potential XSS"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-globalEval(",
|
||||
"expression": "\\bglobalEval\\(",
|
||||
"reason": "Arbitrary code execution"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-getScript(",
|
||||
"expression": "\\bgetScript\\(",
|
||||
"reason": "Arbitrary code execution"
|
||||
},
|
||||
{
|
||||
"name": "jQuery-load(",
|
||||
"expression": "\\bload\\(",
|
||||
"reason": "Arbitrary code execution"
|
||||
},
|
||||
{
|
||||
"name": "fbjs-createNodesFromMarkup",
|
||||
"expression": "\\bcreateNodesFromMarkup\\b",
|
||||
"reason": "Potential XSS, pipes input to innerHTML",
|
||||
"excludedModules": ["node_modules/react-dom", "node_modules/fbjs"]
|
||||
},
|
||||
{
|
||||
"name": "thenify-multiArgs",
|
||||
"expression": "\\bmultiArgs\\b",
|
||||
"reason": "Potential arbitrary code execution, piped to eval",
|
||||
"excludedModules": ["node_modules/thenify"]
|
||||
},
|
||||
{
|
||||
"name": "bluebird-toFastProperties",
|
||||
"expression": "\\btoFastProperties\\b",
|
||||
"reason": "Whatever is provided is sent straight to eval()",
|
||||
"excludedModules": []
|
||||
}
|
||||
]
|
@ -1,14 +0,0 @@
|
||||
// tslint:disable no-console
|
||||
|
||||
import { join } from 'path';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
import { ExceptionType } from './types';
|
||||
import { loadJSON, sortExceptions } from './util';
|
||||
|
||||
const exceptionsPath = join(__dirname, 'exceptions.json');
|
||||
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
|
||||
|
||||
const sorted = sortExceptions(exceptions);
|
||||
|
||||
writeFileSync(exceptionsPath, JSON.stringify(sorted, null, ' '));
|
@ -1,63 +0,0 @@
|
||||
// Tool requirements:
|
||||
// - Feed it a set of regular expressions with descriptions as to what the risks are
|
||||
// - Feed it also a set of exceptions
|
||||
// - It would tell us if there were any new matches that didn't already have exceptions
|
||||
//
|
||||
// Rules:
|
||||
// {
|
||||
// "name": "rule-name",
|
||||
// "expression": "^regex-as-string$",
|
||||
// "reason": "Reason that this expression is dangerous"
|
||||
// }
|
||||
//
|
||||
// Categories of reasons - low to high risk:
|
||||
// "falseMatch"
|
||||
// "testCode"
|
||||
// "exampleCode"
|
||||
// "otherUtilityCode"
|
||||
// "regexMatchedSafeCode"
|
||||
// "notExercisedByOurApp"
|
||||
// "ruleNeeded"
|
||||
// "usageTrusted"
|
||||
//
|
||||
// Exceptions:
|
||||
// [{
|
||||
// "rule": "rule-name",
|
||||
// "path": "path/to/filename.js",
|
||||
// "lineNumber": 45,
|
||||
// "reasonCategory": "<category from list above>",
|
||||
// "updated": "2018-09-08T00:21:13.180Z",
|
||||
// "reasonDetail": "<Optional additional information about why this is okay>"
|
||||
// }]
|
||||
//
|
||||
// When the tool finds issues it outputs them in exception format to make it easy to add
|
||||
// to the exceptions.json file
|
||||
|
||||
export const REASONS = [
|
||||
'falseMatch',
|
||||
'testCode',
|
||||
'exampleCode',
|
||||
'otherUtilityCode',
|
||||
'regexMatchedSafeCode',
|
||||
'notExercisedByOurApp',
|
||||
'ruleNeeded',
|
||||
'usageTrusted',
|
||||
];
|
||||
|
||||
export type RuleType = {
|
||||
name: string;
|
||||
expression?: string;
|
||||
reason: string;
|
||||
regex: RegExp;
|
||||
excludedModules?: Array<string>;
|
||||
};
|
||||
|
||||
export type ExceptionType = {
|
||||
rule: string;
|
||||
path: string;
|
||||
line?: string;
|
||||
lineNumber: number;
|
||||
reasonCategory: string;
|
||||
updated: string;
|
||||
reasonDetail: string;
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
// tslint:disable no-console
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
|
||||
import { ExceptionType } from './types';
|
||||
|
||||
export const ENCODING = 'utf8';
|
||||
|
||||
export function loadJSON(target: string) {
|
||||
try {
|
||||
const contents = readFileSync(target, ENCODING);
|
||||
|
||||
return JSON.parse(contents);
|
||||
} catch (error) {
|
||||
console.log(`Error loading JSON from ${target}: ${error.stack}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function sortExceptions(exceptions: Array<ExceptionType>) {
|
||||
return orderBy(exceptions, ['path', 'lineNumber', 'rule']);
|
||||
}
|
Loading…
Reference in New Issue