From 4b1ae1535df1da9ac86ff0b5323baa7f50fccbea Mon Sep 17 00:00:00 2001 From: 2-4601 <2-4601@users.noreply.github.com> Date: Mon, 25 Feb 2019 19:44:49 +0200 Subject: [PATCH 01/18] Remove gconf dependency from deb package (#3174) Fixes #2344 //FREEBIE --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 738d58fff..c5ae5ad43 100644 --- a/package.json +++ b/package.json @@ -199,8 +199,6 @@ }, "deb": { "depends": [ - "gconf2", - "gconf-service", "libnotify4", "libappindicator1", "libxtst6", From e4b0901620a3ca2113580f4acc4d87fae4ef7e0f Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 11:40:05 -0800 Subject: [PATCH 02/18] If logs are malformed on startup, delete them all and start over --- app/logging.js | 20 +++++++++++++++----- main.js | 12 +----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/logging.js b/app/logging.js index ba4f98231..7897b2879 100644 --- a/app/logging.js +++ b/app/logging.js @@ -104,21 +104,31 @@ async function deleteAllLogs(logPath) { }); } -function cleanupLogs(logPath) { +async function cleanupLogs(logPath) { const now = new Date(); const earliestDate = new Date( Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 3) ); - return eliminateOutOfDateFiles(logPath, earliestDate).then(remaining => { + try { + const remaining = await eliminateOutOfDateFiles(logPath, earliestDate); const files = _.filter(remaining, file => !file.start && file.end); if (!files.length) { - return null; + return; } - return eliminateOldEntries(files, earliestDate); - }); + await eliminateOldEntries(files, earliestDate); + } catch (error) { + console.error( + 'Error cleaning logs; deleting and starting over from scratch.', + error.stack + ); + + // delete and re-create the log directory + await deleteAllLogs(logPath); + mkdirp.sync(logPath); + } } function isLineAfterDate(line, date) { diff --git a/main.js b/main.js index 0e90caa42..512d04a71 100644 --- a/main.js +++ b/main.js @@ -632,17 +632,7 @@ app.on('ready', async () => { installPermissionsHandler({ session, userConfig }); - let loggingSetupError; - try { - await logging.initialize(); - } catch (error) { - loggingSetupError = error; - } - - if (loggingSetupError) { - console.error('Problem setting up logging', loggingSetupError.stack); - } - + await logging.initialize(); logger = logging.getLogger(); logger.info('app ready'); From ae2db9f09ac5de9356ee47f02eb629e35e370d47 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 12:28:13 -0800 Subject: [PATCH 03/18] Improve handling for URLs composed of mixed character sets --- js/modules/link_previews.d.ts | 1 + js/modules/link_previews.js | 149 +++++++++++++++++++++++++ package.json | 1 + test/modules/link_previews_test.js | 27 +++++ ts/components/conversation/Linkify.tsx | 3 +- yarn.lock | 5 + 6 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 js/modules/link_previews.d.ts diff --git a/js/modules/link_previews.d.ts b/js/modules/link_previews.d.ts new file mode 100644 index 000000000..ea3e3e3d1 --- /dev/null +++ b/js/modules/link_previews.d.ts @@ -0,0 +1 @@ +export function isLinkSneaky(link: string): boolean; diff --git a/js/modules/link_previews.js b/js/modules/link_previews.js index 6e35613fb..4d72da45c 100644 --- a/js/modules/link_previews.js +++ b/js/modules/link_previews.js @@ -2,6 +2,7 @@ const { isNumber, compact } = require('lodash'); const he = require('he'); +const punycode = require('punycode'); const LinkifyIt = require('linkify-it'); const linkify = LinkifyIt(); @@ -16,6 +17,7 @@ module.exports = { getImageMetaTag, isLinkInWhitelist, isMediaLinkInWhitelist, + isLinkSneaky, }; const SUPPORTED_DOMAINS = [ @@ -194,3 +196,150 @@ function assembleChunks(chunkDescriptors) { return concatenateBytes(...chunks); } + +const LATIN_PATTERN = new RegExp( + '[' + + '\\u0041-\\u005A' + + '\\u0061-\\u007A' + + '\\u00AA' + + '\\u00BA' + + '\\u00C0-\\u00DC' + + '\\u00D8-\\u00F6' + + '\\u00F8-\\u01BA' + + ']' +); + +const CYRILLIC_PATTERN = new RegExp( + '[' + + '\\u0400-\\u0481' + + '\\u0482' + + '\\u0483-\\u0484' + + '\\u0487' + + '\\u0488-\\u0489' + + '\\u048A-\\u052F' + + '\\u1C80-\\u1C88' + + '\\u1D2B' + + '\\u1D78' + + '\\u2DE0-\\u2DFF' + + '\\uA640-\\uA66D' + + '\\uA66E' + + '\\uA66F' + + '\\uA670-\\uA672' + + '\\uA673' + + '\\uA674-\\uA67D' + + '\\uA67E' + + '\\uA67F' + + '\\uA680-\\uA69B' + + '\\uA69C-\\uA69D' + + '\\uA69E-\\uA69F' + + '\\uFE2E-\\uFE2F' + + ']' +); + +const GREEK_PATTERN = new RegExp( + '[' + + '\\u0370-\\u0373' + + '\\u0375' + + '\\u0376-\\u0377' + + '\\u037A' + + '\\u037B-\\u037D' + + '\\u037F' + + '\\u0384' + + '\\u0386' + + '\\u0388-\\u038A' + + '\\u038C' + + '\\u038E-\\u03A1' + + '\\u03A3-\\u03E1' + + '\\u03F0-\\u03F5' + + '\\u03F6' + + '\\u03F7-\\u03FF' + + '\\u1D26-\\u1D2A' + + '\\u1D5D-\\u1D61' + + '\\u1D66-\\u1D6A' + + '\\u1DBF' + + '\\u1F00-\\u1F15' + + '\\u1F18-\\u1F1D' + + '\\u1F20-\\u1F45' + + '\\u1F48-\\u1F4D' + + '\\u1F50-\\u1F57' + + '\\u1F59' + + '\\u1F5B' + + '\\u1F5D' + + '\\u1F5F-\\u1F7D' + + '\\u1F80-\\u1FB4' + + '\\u1FB6-\\u1FBC' + + '\\u1FBD' + + '\\u1FBE' + + '\\u1FBF-\\u1FC1' + + '\\u1FC2-\\u1FC4' + + '\\u1FC6-\\u1FCC' + + '\\u1FCD-\\u1FCF' + + '\\u1FD0-\\u1FD3' + + '\\u1FD6-\\u1FDB' + + '\\u1FDD-\\u1FDF' + + '\\u1FE0-\\u1FEC' + + '\\u1FED-\\u1FEF' + + '\\u1FF2-\\u1FF4' + + '\\u1FF6-\\u1FFC' + + '\\u1FFD-\\u1FFE' + + '\\u2126' + + '\\uAB65' + + ']' +); + +const HIGH_GREEK_PATTERN = new RegExp( + '[' + + `${String.fromCodePoint(0x10140)}-${String.fromCodePoint(0x10174)}` + + `${String.fromCodePoint(0x10175)}-${String.fromCodePoint(0x10178)}` + + `${String.fromCodePoint(0x10179)}-${String.fromCodePoint(0x10189)}` + + `${String.fromCodePoint(0x1018a)}-${String.fromCodePoint(0x1018b)}` + + `${String.fromCodePoint(0x1018c)}-${String.fromCodePoint(0x1018e)}` + + `${String.fromCodePoint(0x101a0)}` + + `${String.fromCodePoint(0x1d200)}-${String.fromCodePoint(0x1d241)}` + + `${String.fromCodePoint(0x1d242)}-${String.fromCodePoint(0x1d244)}` + + `${String.fromCodePoint(0x1d245)}` + + ']', + 'u' +); + +function isChunkSneaky(chunk) { + const hasLatin = LATIN_PATTERN.test(chunk); + if (!hasLatin) { + return false; + } + + const hasCyrillic = CYRILLIC_PATTERN.test(chunk); + if (hasCyrillic) { + return true; + } + + const hasGreek = GREEK_PATTERN.test(chunk); + if (hasGreek) { + return true; + } + + const hasHighGreek = HIGH_GREEK_PATTERN.test(chunk); + if (hasHighGreek) { + return true; + } + + return false; +} + +function isLinkSneaky(link) { + const domain = getDomain(link); + + // This is necesary because getDomain returns domains in punycode form + // We'd like to use require('url').domainToUnicode() but it's a no-op in a BrowserWindow + const unicodeDomain = punycode.toUnicode(domain); + + const chunks = unicodeDomain.split('.'); + for (let i = 0, max = chunks.length; i < max; i += 1) { + const chunk = chunks[i]; + if (isChunkSneaky(chunk)) { + return true; + } + } + + return false; +} diff --git a/package.json b/package.json index c5ae5ad43..c36d9fbcf 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "pify": "3.0.0", "protobufjs": "~6.8.6", "proxy-agent": "3.0.3", + "punycode": "2.1.1", "react": "16.2.0", "react-contextmenu": "2.9.2", "react-dom": "16.2.0", diff --git a/test/modules/link_previews_test.js b/test/modules/link_previews_test.js index 8baec0e96..0cae46b93 100644 --- a/test/modules/link_previews_test.js +++ b/test/modules/link_previews_test.js @@ -5,6 +5,7 @@ const { getTitleMetaTag, getImageMetaTag, isLinkInWhitelist, + isLinkSneaky, isMediaLinkInWhitelist, } = require('../../js/modules/link_previews'); @@ -305,4 +306,30 @@ describe('Link previews', () => { assert.deepEqual(expected, actual); }); }); + + describe('#isLinkSneaky', () => { + it('returns false for all-latin domain', () => { + const link = 'https://www.amazon.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, false); + }); + + it('returns true for Latin + Cyrillic domain', () => { + const link = 'https://www.aмazon.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); + + it('returns true for Latin + Greek domain', () => { + const link = 'https://www.αpple.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); + + it('returns true for Latin + High Greek domain', () => { + const link = `https://www.apple${String.fromCodePoint(0x101a0)}.com`; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); + }); }); diff --git a/ts/components/conversation/Linkify.tsx b/ts/components/conversation/Linkify.tsx index 0bc4af763..86e651629 100644 --- a/ts/components/conversation/Linkify.tsx +++ b/ts/components/conversation/Linkify.tsx @@ -3,6 +3,7 @@ import React from 'react'; import LinkifyIt from 'linkify-it'; import { RenderTextCallback } from '../../types/Util'; +import { isLinkSneaky } from '../../../js/modules/link_previews'; const linkify = LinkifyIt(); @@ -49,7 +50,7 @@ export class Linkify extends React.Component { } const { url, text: originalText } = match; - if (SUPPORTED_PROTOCOLS.test(url)) { + if (SUPPORTED_PROTOCOLS.test(url) && !isLinkSneaky(url)) { results.push( {originalText} diff --git a/yarn.lock b/yarn.lock index c7af81636..8ed4e56d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6896,6 +6896,11 @@ punycode@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + q-i@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/q-i/-/q-i-2.0.1.tgz#fec7e3f0e713f3467358bb5ac80bcc4c115187d6" From 667b2e63f17a25354dfd42da0584e298b0f3d361 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 14:15:43 -0800 Subject: [PATCH 04/18] Ensure image lightbox is aware of its underlying message changes --- js/views/conversation_view.js | 14 ++++++++++++-- js/views/react_wrapper_view.js | 7 +++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 41ed7c6bd..51914325f 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1275,8 +1275,12 @@ className: 'lightbox-wrapper', Component: Signal.Components.Lightbox, props, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), + onClose: () => { + Signal.Backbone.Views.Lightbox.hide(); + this.stopListening(message); + }, }); + this.listenTo(message, 'expired', () => this.lightboxView.remove()); Signal.Backbone.Views.Lightbox.show(this.lightboxView.el); return; } @@ -1304,8 +1308,14 @@ className: 'lightbox-wrapper', Component: Signal.Components.LightboxGallery, props, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), + onClose: () => { + Signal.Backbone.Views.Lightbox.hide(); + this.stopListening(message); + }, }); + this.listenTo(message, 'expired', () => + this.lightboxGalleryView.remove() + ); Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el); }, diff --git a/js/views/react_wrapper_view.js b/js/views/react_wrapper_view.js index d335d38aa..a5a5ccaa8 100644 --- a/js/views/react_wrapper_view.js +++ b/js/views/react_wrapper_view.js @@ -53,16 +53,15 @@ augmentProps(props) { return Object.assign({}, props, { close: () => { - if (this.onClose) { - this.onClose(); - return; - } this.remove(); }, i18n, }); }, remove() { + if (this.onClose) { + this.onClose(); + } ReactDOM.unmountComponentAtNode(this.el); Backbone.View.prototype.remove.call(this); }, From ae161c6cf69af5fc3ff0a6ddcdd1479cc6f2232f Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 14:41:17 -0800 Subject: [PATCH 05/18] Update to Electron 4.x --- _locales/en/messages.json | 2 +- app/menu.js | 1 + app/tray_icon.js | 2 +- js/modules/link_previews.js | 5 +-- main.js | 37 ++++++++-------- package.json | 3 +- test/app/fixtures/menu-mac-os-setup.json | 1 + test/app/fixtures/menu-mac-os.json | 1 + ts/util/lint/exceptions.json | 54 +++++++++++++++--------- yarn.lock | 22 ++++------ 10 files changed, 71 insertions(+), 57 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fbcb0d1ed..eab144415 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -706,7 +706,7 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, diff --git a/app/menu.js b/app/menu.js index 9c938700a..00396176f 100644 --- a/app/menu.js +++ b/app/menu.js @@ -237,6 +237,7 @@ function updateForMac(template, messages, options) { // Add the OSX-specific Signal Desktop menu at the far left template.unshift({ + label: messages.signalDesktop.message, submenu: [ { label: messages.aboutSignalDesktop.message, diff --git a/app/tray_icon.js b/app/tray_icon.js index d913cf06b..93dad3644 100644 --- a/app/tray_icon.js +++ b/app/tray_icon.js @@ -97,7 +97,7 @@ function createTrayIcon(getMainWindow, messages) { tray.on('click', tray.showWindow); - tray.setToolTip(messages.trayTooltip.message); + tray.setToolTip(messages.signalDesktop.message); tray.updateContextMenu(); return tray; diff --git a/js/modules/link_previews.js b/js/modules/link_previews.js index 4d72da45c..42b66f3aa 100644 --- a/js/modules/link_previews.js +++ b/js/modules/link_previews.js @@ -2,7 +2,7 @@ const { isNumber, compact } = require('lodash'); const he = require('he'); -const punycode = require('punycode'); +const nodeUrl = require('url'); const LinkifyIt = require('linkify-it'); const linkify = LinkifyIt(); @@ -330,8 +330,7 @@ function isLinkSneaky(link) { const domain = getDomain(link); // This is necesary because getDomain returns domains in punycode form - // We'd like to use require('url').domainToUnicode() but it's a no-op in a BrowserWindow - const unicodeDomain = punycode.toUnicode(domain); + const unicodeDomain = nodeUrl.domainToUnicode(domain); const chunks = unicodeDomain.split('.'); for (let i = 0, max = chunks.length; i < max; i += 1) { diff --git a/main.js b/main.js index 512d04a71..3d1729db0 100644 --- a/main.js +++ b/main.js @@ -98,21 +98,22 @@ function showWindow() { if (!process.mas) { console.log('making app single instance'); - const shouldQuit = app.makeSingleInstance(() => { - // Someone tried to run a second instance, we should focus our window - if (mainWindow) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } - - showWindow(); - } - return true; - }); - - if (shouldQuit) { + const gotLock = app.requestSingleInstanceLock(); + if (!gotLock) { console.log('quitting; we are the second instance'); app.exit(); + } else { + app.on('second-instance', () => { + // Someone tried to run a second instance, we should focus our window + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + + showWindow(); + } + return true; + }); } } @@ -217,7 +218,7 @@ function createWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, - // sandbox: true, + contextIsolation: false, preload: path.join(__dirname, 'preload.js'), nativeWindowOpen: true, }, @@ -444,8 +445,8 @@ function showAbout() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'about_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -490,8 +491,8 @@ async function showSettingsWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'settings_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -535,8 +536,8 @@ async function showDebugLogWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'debug_log_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -583,8 +584,8 @@ async function showPermissionsPopupWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'permissions_popup_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, diff --git a/package.json b/package.json index c36d9fbcf..0e7a5f724 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "pify": "3.0.0", "protobufjs": "~6.8.6", "proxy-agent": "3.0.3", - "punycode": "2.1.1", "react": "16.2.0", "react-contextmenu": "2.9.2", "react-dom": "16.2.0", @@ -113,7 +112,7 @@ "asar": "0.14.0", "bower": "1.8.2", "chai": "4.1.2", - "electron": "3.0.14", + "electron": "4.0.5", "electron-builder": "20.13.5", "electron-icon-maker": "0.0.3", "eslint": "4.14.0", diff --git a/test/app/fixtures/menu-mac-os-setup.json b/test/app/fixtures/menu-mac-os-setup.json index eccb2a24c..dd06a5a1b 100644 --- a/test/app/fixtures/menu-mac-os-setup.json +++ b/test/app/fixtures/menu-mac-os-setup.json @@ -1,5 +1,6 @@ [ { + "label": "Signal Desktop", "submenu": [ { "label": "About Signal Desktop", diff --git a/test/app/fixtures/menu-mac-os.json b/test/app/fixtures/menu-mac-os.json index 88515d072..02d2e0f8c 100644 --- a/test/app/fixtures/menu-mac-os.json +++ b/test/app/fixtures/menu-mac-os.json @@ -1,5 +1,6 @@ [ { + "label": "Signal Desktop", "submenu": [ { "label": "About Signal Desktop", diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index abd3bd004..b6ed333af 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -2386,49 +2386,65 @@ "rule": "eval", "path": "node_modules/electron/electron.d.ts", "line": " eval(code: string): void;", - "lineNumber": 1834, + "lineNumber": 2031, "reasonCategory": "falseMatch", - "updated": "2018-09-15T00:38:04.183Z" + "updated": "2019-02-22T01:08:09.603Z" }, { "rule": "jQuery-append(", "path": "node_modules/electron/electron.d.ts", "line": " append(menuItem: MenuItem): void;", - "lineNumber": 3232, + "lineNumber": 3431, "reasonCategory": "falseMatch", - "updated": "2018-09-19T18:13:29.628Z" + "updated": "2019-02-22T01:08:09.603Z" }, { "rule": "jQuery-wrap(", - "path": "node_modules/electron/node_modules/@types/node/index.d.ts", + "path": "node_modules/electron/node_modules/@types/node/globals.d.ts", "line": " wrap(oldStream: ReadableStream): this;", - "lineNumber": 437, + "lineNumber": 573, "reasonCategory": "falseMatch", - "updated": "2018-09-20T21:02:15.849Z" + "updated": "2019-02-22T01:08:09.603Z" }, { "rule": "jQuery-wrap(", - "path": "node_modules/electron/node_modules/@types/node/index.d.ts", + "path": "node_modules/electron/node_modules/@types/node/globals.d.ts", "line": " static wrap(code: string): string;", - "lineNumber": 792, + "lineNumber": 976, "reasonCategory": "falseMatch", - "updated": "2018-09-20T20:45:24.002Z" + "updated": "2019-02-22T01:08:09.603Z" }, { - "rule": "jQuery-append(", - "path": "node_modules/electron/node_modules/@types/node/index.d.ts", - "line": " append(name: string, value: string): void;", - "lineNumber": 2355, + "rule": "eval", + "path": "node_modules/electron/node_modules/@types/node/repl.d.ts", + "line": " * Default: an async wrapper for the JavaScript `eval()` function. An `eval` function can", + "lineNumber": 31, + "reasonCategory": "falseMatch", + "updated": "2019-02-22T01:08:09.603Z" + }, + { + "rule": "eval", + "path": "node_modules/electron/node_modules/@types/node/repl.d.ts", + "line": " * for the JavaScript `eval()` function.", + "lineNumber": 180, "reasonCategory": "falseMatch", - "updated": "2018-09-20T21:02:15.849Z" + "updated": "2019-02-22T01:08:09.603Z" }, { "rule": "jQuery-wrap(", - "path": "node_modules/electron/node_modules/@types/node/index.d.ts", + "path": "node_modules/electron/node_modules/@types/node/stream.d.ts", "line": " wrap(oldStream: NodeJS.ReadableStream): this;", - "lineNumber": 5277, + "lineNumber": 32, "reasonCategory": "falseMatch", - "updated": "2018-09-20T20:45:24.002Z" + "updated": "2019-02-22T01:08:09.603Z" + }, + { + "rule": "jQuery-append(", + "path": "node_modules/electron/node_modules/@types/node/url.d.ts", + "line": " append(name: string, value: string): void;", + "lineNumber": 90, + "reasonCategory": "falseMatch", + "updated": "2019-02-22T01:08:09.603Z" }, { "rule": "jQuery-$(", @@ -6040,4 +6056,4 @@ "updated": "2018-09-17T20:50:40.689Z", "reasonDetail": "Hard-coded value" } -] +] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8ed4e56d8..81d1b40ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,9 +139,10 @@ version "10.10.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.10.1.tgz#d5c96ca246a418404914d180b7fdd625ad18eca6" -"@types/node@^8.0.24": - version "8.9.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.9.4.tgz#dfd327582a06c114eb6e0441fa3d6fab35edad48" +"@types/node@^10.12.18": + version "10.12.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.26.tgz#2dec19f1f7981c95cb54bab8f618ecb5dc983d0e" + integrity sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg== "@types/qs@6.5.1": version "6.5.1" @@ -2517,12 +2518,12 @@ electron-updater@2.21.10: semver "^5.5.0" source-map-support "^0.5.5" -electron@3.0.14: - version "3.0.14" - resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.14.tgz#d54c51de3651c0fe48a6a6e9aef1ca98e5ea5796" - integrity sha512-1fG9bE0LzL5QXeEq2MC0dHdVO0pbZOnNlVAIyOyJaCFAu/TjLhxQfWj38bFUEojzuVlaR87tZz0iy2qlVZj3sw== +electron@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-4.0.5.tgz#d8e7d8a581a3e31071b2226129b26b6110c1d877" + integrity sha512-UWFH6SrzNtzfvusGUFYxXDrgsUEbtBXkH/66hpDWxjA2Ckt7ozcYIujZpshbr7LPy8kV3ZRxIvoyCMdaS5DkVQ== dependencies: - "@types/node" "^8.0.24" + "@types/node" "^10.12.18" electron-download "^4.1.0" extract-zip "^1.0.3" @@ -6896,11 +6897,6 @@ punycode@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" -punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - q-i@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/q-i/-/q-i-2.0.1.tgz#fec7e3f0e713f3467358bb5ac80bcc4c115187d6" From fbda313d09adb7959467f32e14efdc8a0fafaf76 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 17:16:37 -0800 Subject: [PATCH 06/18] Add job details to attachment download log warning --- js/modules/attachment_downloads.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/js/modules/attachment_downloads.js b/js/modules/attachment_downloads.js index 60136b67b..51ba6aeea 100644 --- a/js/modules/attachment_downloads.js +++ b/js/modules/attachment_downloads.js @@ -310,6 +310,8 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { return; } + const logPrefix = `${message.idForLogging()} (type: ${type}, index: ${index})`; + if (type === 'attachment') { const attachments = message.get('attachments'); if (!attachments || attachments.length <= index) { @@ -317,7 +319,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { `_addAttachmentToMessage: attachments didn't exist or ${index} was too large` ); } - _replaceAttachment(attachments, index, attachment); + _replaceAttachment(attachments, index, attachment, logPrefix); return; } @@ -332,7 +334,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { if (!item) { throw new Error(`_addAttachmentToMessage: preview ${index} was falsey`); } - _replaceAttachment(item, 'image', attachment); + _replaceAttachment(item, 'image', attachment, logPrefix); return; } @@ -345,7 +347,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { } const item = contact[index]; if (item && item.avatar && item.avatar.avatar) { - _replaceAttachment(item.avatar, 'avatar', attachment); + _replaceAttachment(item.avatar, 'avatar', attachment, logPrefix); } else { logger.warn( `_addAttachmentToMessage: Couldn't update contact with avatar attachment for message ${message.idForLogging()}` @@ -373,7 +375,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { `_addAttachmentToMessage: attachment ${index} was falsey` ); } - _replaceAttachment(item, 'thumbnail', attachment); + _replaceAttachment(item, 'thumbnail', attachment, logPrefix); return; } @@ -388,7 +390,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { await Signal.Migrations.deleteAttachmentData(existingAvatar.path); } - _replaceAttachment(group, 'avatar', attachment); + _replaceAttachment(group, 'avatar', attachment, logPrefix); return; } @@ -397,11 +399,11 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) { ); } -function _replaceAttachment(object, key, newAttachment) { +function _replaceAttachment(object, key, newAttachment, logPrefix) { const oldAttachment = object[key]; if (oldAttachment && oldAttachment.path) { logger.warn( - '_replaceAttachment: Old attachment already had path, not replacing' + `_replaceAttachment: ${logPrefix} - old attachment already had path, not replacing` ); } From ba501a6df6c9e7e1124447d26da3079dbe8e627e Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 21 Feb 2019 17:25:15 -0800 Subject: [PATCH 07/18] Fix ASM warnings in MP3 library --- components/mp3lameencoder/lib/Mp3LameEncoder.js | 6 +++--- js/Mp3LameEncoder.min.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/mp3lameencoder/lib/Mp3LameEncoder.js b/components/mp3lameencoder/lib/Mp3LameEncoder.js index 71fc756a8..572ec8628 100644 --- a/components/mp3lameencoder/lib/Mp3LameEncoder.js +++ b/components/mp3lameencoder/lib/Mp3LameEncoder.js @@ -44293,7 +44293,7 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } @@ -44349,14 +44349,14 @@ function _memcpy(dest, src, num) { num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; diff --git a/js/Mp3LameEncoder.min.js b/js/Mp3LameEncoder.min.js index 71fc756a8..572ec8628 100644 --- a/js/Mp3LameEncoder.min.js +++ b/js/Mp3LameEncoder.min.js @@ -44293,7 +44293,7 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } @@ -44349,14 +44349,14 @@ function _memcpy(dest, src, num) { num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; From eb7ad4895192187dbc69c672a0c310d2c0461d5b Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 22 Feb 2019 10:55:37 -0800 Subject: [PATCH 08/18] Only put nbsp before last token in msg body if token is <12 chars --- js/models/messages.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 5fddb62e0..b825a0e5d 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -458,11 +458,10 @@ const nbsp = '\xa0'; const regex = /(\S)( +)(\S+\s*)$/; return text.replace(regex, (match, start, spaces, end) => { - const newSpaces = _.reduce( - spaces, - accumulator => accumulator + nbsp, - '' - ); + const newSpaces = + end.length < 12 + ? _.reduce(spaces, accumulator => accumulator + nbsp, '') + : spaces; return `${start}${newSpaces}${end}`; }); }, From dcd16775c3fead93f11e4c1b9fdcb93f1a381807 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 26 Feb 2019 16:14:47 -0800 Subject: [PATCH 09/18] Note to Self: set expirationStartTimestamp before sending sync --- js/models/messages.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index b825a0e5d..2e19f96c2 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1066,12 +1066,15 @@ this.set({ dataMessage }); try { - const result = await this.sendSyncMessage(); this.set({ - // These are the same as a normal send + // These are the same as a normal send() sent_to: [this.OUR_NUMBER], sent: true, expirationStartTimestamp: Date.now(), + }); + const result = await this.sendSyncMessage(); + this.set({ + // We have to do this afterward, since we didn't have a previous send! unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null, // These are unique to a Note to Self message - immediately read/delivered From 812f895e127943893b37c15a0ed8a0e7ea76b518 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 27 Feb 2019 18:15:24 -0800 Subject: [PATCH 10/18] Bring lastMessage and lastMessageStatus back to conversation --- js/conversation_controller.js | 8 +++++++- js/models/conversations.js | 23 ++++------------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 035963e3b..e736a2cb5 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -224,7 +224,13 @@ this._initialFetchComplete = true; await Promise.all( - conversations.map(conversation => conversation.updateLastMessage()) + conversations.map(conversation => { + if (!conversation.get('lastMessage')) { + return conversation.updateLastMessage(); + } + + return null; + }) ); window.log.info('ConversationController: done with initial fetch'); } catch (error) { diff --git a/js/models/conversations.js b/js/models/conversations.js index 396161bc1..0d675f65d 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -135,8 +135,6 @@ this.unset('unidentifiedDeliveryUnrestricted'); this.unset('hasFetchedProfile'); this.unset('tokens'); - this.unset('lastMessage'); - this.unset('lastMessageStatus'); this.typingRefreshTimer = null; this.typingPauseTimer = null; @@ -323,8 +321,8 @@ isTyping: typingKeys.length > 0, lastMessage: { - status: this.lastMessageStatus, - text: this.lastMessage, + status: this.get('lastMessageStatus'), + text: this.get('lastMessage'), }, onClick: () => this.trigger('select', this), @@ -879,10 +877,10 @@ }); const message = this.addSingleMessage(messageWithSchema); - this.lastMessage = message.getNotificationText(); - this.lastMessageStatus = 'sending'; this.set({ + lastMessage: message.getNotificationText(), + lastMessageStatus: 'sending', active_at: now, timestamp: now, }); @@ -1154,17 +1152,6 @@ : null, }); - let hasChanged = false; - const { lastMessage, lastMessageStatus } = lastMessageUpdate; - delete lastMessageUpdate.lastMessage; - delete lastMessageUpdate.lastMessageStatus; - - hasChanged = hasChanged || lastMessage !== this.lastMessage; - this.lastMessage = lastMessage; - - hasChanged = hasChanged || lastMessageStatus !== this.lastMessageStatus; - this.lastMessageStatus = lastMessageStatus; - // Because we're no longer using Backbone-integrated saves, we need to manually // clear the changed fields here so our hasChanged() check below is useful. this.changed = {}; @@ -1174,8 +1161,6 @@ await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); - } else if (hasChanged) { - this.trigger('change'); } }, From ca5132f7122f4e3388ef4a6fc2ffad1d9c8ac412 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 27 Feb 2019 10:17:22 -0800 Subject: [PATCH 11/18] Present 'database error' dialog in more situations --- app/sql.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/sql.js b/app/sql.js index 4a46dae4e..79daedb95 100644 --- a/app/sql.js +++ b/app/sql.js @@ -696,25 +696,25 @@ async function initialize({ configDir, key, messages }) { filePath = path.join(dbDir, 'db.sqlite'); - const sqlInstance = await openDatabase(filePath); - const promisified = promisify(sqlInstance); + try { + const sqlInstance = await openDatabase(filePath); + const promisified = promisify(sqlInstance); - // promisified.on('trace', async statement => { - // if (!db || statement.startsWith('--')) { - // console._log(statement); - // return; - // } - // const data = await db.get(`EXPLAIN QUERY PLAN ${statement}`); - // console._log(`EXPLAIN QUERY PLAN ${statement}\n`, data && data.detail); - // }); + // promisified.on('trace', async statement => { + // if (!db || statement.startsWith('--')) { + // console._log(statement); + // return; + // } + // const data = await db.get(`EXPLAIN QUERY PLAN ${statement}`); + // console._log(`EXPLAIN QUERY PLAN ${statement}\n`, data && data.detail); + // }); - await setupSQLCipher(promisified, { key }); - await updateSchema(promisified); + await setupSQLCipher(promisified, { key }); + await updateSchema(promisified); - db = promisified; + db = promisified; - // test database - try { + // test database await getMessageCount(); } catch (error) { console.log('Database startup error:', error.stack); From 009c58749ba979aa611c497bd411dff5f3078f78 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 26 Feb 2019 14:14:19 -0800 Subject: [PATCH 12/18] Don't continue to log if socket is returning unknown status --- js/views/inbox_view.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index b621480cc..317ff4b2b 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -206,11 +206,8 @@ this.onEmpty(); break; default: - window.log.error( - 'Whisper.InboxView::startConnectionListener:', - 'Unknown web socket status:', - status - ); + // We also replicate empty here + this.onEmpty(); break; } }, 1000); From e4f18f9daabf34213e0c7338fd48296e6f49281d Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 1 Mar 2019 12:03:05 -0800 Subject: [PATCH 13/18] libsignal-protocol: Protect against null thrown Errors --- libtextsecure/libsignal-protocol.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index ac4c28b5a..ac9b7fd7b 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -36155,7 +36155,11 @@ SessionCipher.prototype = { // using each one at a time. Stop and return the result if we get // a valid result if (sessionList.length === 0) { - return Promise.reject(errors[0]); + var error = errors[0]; + if (!error) { + error = new Error('decryptWithSessionList: list is empty, but no errors in array'); + } + return Promise.reject(error); } var session = sessionList.pop(); From e68367fe0d0be75fdba600c406bd21e94562e672 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 22 Feb 2019 15:23:34 -0800 Subject: [PATCH 14/18] Remove reference to nonexistent view --- js/views/inbox_view.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 317ff4b2b..5bccdd94a 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -165,10 +165,6 @@ .find('.network-status-container') .append(this.networkStatusView.render().el); - extension.windows.onClosed(() => { - this.inboxListView.stopListening(); - }); - if (extension.expired()) { const banner = new Whisper.ExpiredAlertBanner().render(); banner.$el.prependTo(this.$el); From 782516186f17ecb7a8804f9f1fbec736cb7c689a Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 26 Feb 2019 15:08:43 -0800 Subject: [PATCH 15/18] Move expiring message cleanup after ConversationController.load() --- js/background.js | 86 +++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/js/background.js b/js/background.js index 619cf6e14..aea05034b 100644 --- a/js/background.js +++ b/js/background.js @@ -330,47 +330,6 @@ Views.Initialization.setMessage(window.i18n('optimizingApplication')); - window.log.info('Cleanup: starting...'); - const messagesForCleanup = await window.Signal.Data.getOutgoingWithoutExpiresAt( - { - MessageCollection: Whisper.MessageCollection, - } - ); - window.log.info( - `Cleanup: Found ${messagesForCleanup.length} messages for cleanup` - ); - await Promise.all( - messagesForCleanup.map(async message => { - const delivered = message.get('delivered'); - const sentAt = message.get('sent_at'); - const expirationStartTimestamp = message.get( - 'expirationStartTimestamp' - ); - - if (message.hasErrors()) { - return; - } - - if (delivered) { - window.log.info( - `Cleanup: Starting timer for delivered message ${sentAt}` - ); - message.set( - 'expirationStartTimestamp', - expirationStartTimestamp || sentAt - ); - await message.setToExpire(); - return; - } - - window.log.info(`Cleanup: Deleting unsent message ${sentAt}`); - await window.Signal.Data.removeMessage(message.id, { - Message: Whisper.Message, - }); - }) - ); - window.log.info('Cleanup: complete'); - if (newVersion) { await window.Signal.Data.cleanupOrphanedAttachments(); } @@ -465,6 +424,51 @@ async function start() { window.dispatchEvent(new Event('storage_ready')); + window.log.info('Cleanup: starting...'); + const messagesForCleanup = await window.Signal.Data.getOutgoingWithoutExpiresAt( + { + MessageCollection: Whisper.MessageCollection, + } + ); + window.log.info( + `Cleanup: Found ${messagesForCleanup.length} messages for cleanup` + ); + await Promise.all( + messagesForCleanup.map(async message => { + const delivered = message.get('delivered'); + const sentAt = message.get('sent_at'); + const expirationStartTimestamp = message.get( + 'expirationStartTimestamp' + ); + + if (message.hasErrors()) { + return; + } + + if (delivered) { + window.log.info( + `Cleanup: Starting timer for delivered message ${sentAt}` + ); + message.set( + 'expirationStartTimestamp', + expirationStartTimestamp || sentAt + ); + await message.setToExpire(); + return; + } + + window.log.info(`Cleanup: Deleting unsent message ${sentAt}`); + await window.Signal.Data.removeMessage(message.id, { + Message: Whisper.Message, + }); + const conversation = message.getConversation(); + if (conversation) { + await conversation.updateLastMessage(); + } + }) + ); + window.log.info('Cleanup: complete'); + window.log.info('listening for registration events'); Whisper.events.on('registration_done', () => { window.log.info('handling registration event'); From e980e3cd22489eac6f64370e28ddbfeec0b4c4b0 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 6 Mar 2019 10:49:21 -0800 Subject: [PATCH 16/18] Lint fixes --- ts/util/lint/exceptions.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index b6ed333af..f1cc0fd7d 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -172,7 +172,7 @@ "rule": "jQuery-load(", "path": "js/conversation_controller.js", "line": " this._initialPromise = load();", - "lineNumber": 239, + "lineNumber": 245, "reasonCategory": "falseMatch", "updated": "2018-10-02T21:00:44.007Z" }, @@ -207,7 +207,7 @@ "rule": "jQuery-wrap(", "path": "js/models/messages.js", "line": " return this.send(wrap(promise));", - "lineNumber": 871, + "lineNumber": 870, "reasonCategory": "falseMatch", "updated": "2018-10-05T23:12:28.961Z" }, @@ -215,7 +215,7 @@ "rule": "jQuery-wrap(", "path": "js/models/messages.js", "line": " return wrap(", - "lineNumber": 1115, + "lineNumber": 1117, "reasonCategory": "falseMatch", "updated": "2018-10-05T23:12:28.961Z" }, @@ -769,7 +769,7 @@ "rule": "jQuery-prependTo(", "path": "js/views/inbox_view.js", "line": " banner.$el.prependTo(this.$el);", - "lineNumber": 174, + "lineNumber": 170, "reasonCategory": "usageTrusted", "updated": "2018-09-19T18:13:29.628Z", "reasonDetail": "Interacting with already-existing DOM nodes" @@ -778,7 +778,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " if (e && this.$(e.target).closest('.placeholder').length) {", - "lineNumber": 232, + "lineNumber": 225, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -787,7 +787,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('#header, .gutter').addClass('inactive');", - "lineNumber": 236, + "lineNumber": 229, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -796,7 +796,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.conversation-stack').addClass('inactive');", - "lineNumber": 240, + "lineNumber": 233, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -805,7 +805,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.conversation:first .menu').trigger('close');", - "lineNumber": 242, + "lineNumber": 235, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -814,7 +814,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " const input = this.$('input.search');", - "lineNumber": 249, + "lineNumber": 242, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -823,7 +823,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {", - "lineNumber": 273, + "lineNumber": 266, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" @@ -832,7 +832,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.conversation:first .recorder').trigger('close');", - "lineNumber": 276, + "lineNumber": 269, "reasonCategory": "usageTrusted", "updated": "2018-09-19T21:59:32.770Z", "reasonDetail": "Protected from arbitrary input" From 0b0dfbce9dc2ff5b69a3ff7dbcfb9706601775a4 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 8 Mar 2019 12:03:20 -0800 Subject: [PATCH 17/18] Don't return error for groups with no members but yourself --- libtextsecure/sendmessage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index e92c480e8..3bb4fa396 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -734,7 +734,12 @@ MessageSender.prototype = { const me = textsecure.storage.user.getNumber(); const numbers = providedNumbers.filter(number => number !== me); if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); + return Promise.resolve({ + successfulNumbers: [], + failoverNumbers: [], + errors: [], + unidentifiedDeliveries: [], + }); } return new Promise((resolve, reject) => { From bf904ddd129ceba8fa363ccf6d10ecd256c65f63 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 8 Mar 2019 12:27:45 -0800 Subject: [PATCH 18/18] On attachment save include date, include album index --- js/views/conversation_view.js | 3 ++- ts/components/LightboxGallery.tsx | 6 +++--- ts/test/types/Attachment_test.ts | 16 ++++++++++++++++ ts/types/Attachment.ts | 10 ++++++++-- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 51914325f..2f601c2cc 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1294,8 +1294,9 @@ Signal.Types.Attachment.save({ attachment: options.attachment, document, + index: options.index + 1, getAbsolutePath: getAbsoluteAttachmentPath, - timestamp: options.message.received_at, + timestamp: options.message.get('sent_at'), }); }; diff --git a/ts/components/LightboxGallery.tsx b/ts/components/LightboxGallery.tsx index e3ebea472..663d35ae1 100644 --- a/ts/components/LightboxGallery.tsx +++ b/ts/components/LightboxGallery.tsx @@ -24,7 +24,7 @@ interface Props { i18n: Localizer; media: Array; onSave?: ( - { attachment, message }: { attachment: AttachmentType; message: Message } + options: { attachment: AttachmentType; message: Message; index: number } ) => void; selectedIndex: number; } @@ -98,8 +98,8 @@ export class LightboxGallery extends React.Component { const { selectedIndex } = this.state; const mediaItem = media[selectedIndex]; - const { attachment, message } = mediaItem; + const { attachment, message, index } = mediaItem; - onSave({ attachment, message }); + onSave({ attachment, message, index }); }; } diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index a7d251697..834c338c4 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -53,6 +53,22 @@ describe('Attachment', () => { assert.strictEqual(actual, expected); }); }); + context('for attachment with index', () => { + it('should generate a filename based on timestamp', () => { + const attachment: Attachment.Attachment = { + data: stringToArrayBuffer('foo'), + contentType: MIME.VIDEO_QUICKTIME, + }; + const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); + const actual = Attachment.getSuggestedFilename({ + attachment, + timestamp, + index: 3, + }); + const expected = 'signal-attachment-1970-01-01-000000_003.mov'; + assert.strictEqual(actual, expected); + }); + }); }); describe('isVisualMedia', () => { diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index b2451420d..7f812d473 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import moment from 'moment'; +import { padStart } from 'lodash'; import * as MIME from './MIME'; import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL'; @@ -82,11 +83,13 @@ export const isVoiceMessage = (attachment: Attachment): boolean => { export const save = ({ attachment, document, + index, getAbsolutePath, timestamp, }: { attachment: Attachment; document: Document; + index: number; getAbsolutePath: (relativePath: string) => string; timestamp?: number; }): void => { @@ -97,7 +100,7 @@ export const save = ({ data: attachment.data, type: MIME.APPLICATION_OCTET_STREAM, }); - const filename = getSuggestedFilename({ attachment, timestamp }); + const filename = getSuggestedFilename({ attachment, timestamp, index }); saveURLAsFile({ url, filename, document }); if (isObjectURLRequired) { URL.revokeObjectURL(url); @@ -107,9 +110,11 @@ export const save = ({ export const getSuggestedFilename = ({ attachment, timestamp, + index, }: { attachment: Attachment; timestamp?: number | Date; + index?: number; }): string => { if (attachment.fileName) { return attachment.fileName; @@ -121,8 +126,9 @@ export const getSuggestedFilename = ({ : ''; const fileType = getFileExtension(attachment); const extension = fileType ? `.${fileType}` : ''; + const indexSuffix = index ? `_${padStart(index.toString(), 3, '0')}` : ''; - return `${prefix}${suffix}${extension}`; + return `${prefix}${suffix}${indexSuffix}${extension}`; }; export const getFileExtension = (attachment: Attachment): string | null => {