Partial Merge commit 'bf904ddd129ceba8fa363ccf6d10ecd256c65f63' into signal-1.23

# Conflicts:
#	_locales/en/messages.json
#	app/sql.js
#	js/background.js
#	js/conversation_controller.js
#	js/models/conversations.js
#	js/views/inbox_view.js
#	main.js
pull/272/head
Mikunj 6 years ago
commit 48c4ed1a29

@ -702,9 +702,8 @@
"message": "Quit",
"description": "Command in the tray icon menu, to quit the application"
},
"trayTooltip": {
"message": "Loki Messenger",
"description": "Tooltip for the tray icon"
"lokiMessenger": {
"message": "Loki Messenger"
},
"searchForPeopleOrGroups": {
"message": "Enter name or public key",

@ -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) {

@ -232,6 +232,7 @@ function updateForMac(template, messages, options) {
// Add the OSX-specific Signal Desktop menu at the far left
template.unshift({
label: messages.lokiMessenger.message,
submenu: [
{
label: messages.aboutSignalDesktop.message,

@ -802,30 +802,30 @@ async function initialize({ configDir, key, messages }) {
_initializePaths(configDir);
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);
// });
try {
await setupSQLCipher(promisified, { key });
await updateSchema(promisified);
} catch (e) {
await promisified.close();
throw e;
}
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);
// });
try {
await setupSQLCipher(promisified, { key });
await updateSchema(promisified);
} catch (e) {
await promisified.close();
throw e;
}
db = promisified;
db = promisified;
// test database
try {
// test database
await getMessageCount();
} catch (error) {
console.log('Database startup error:', error.stack);

@ -97,7 +97,7 @@ function createTrayIcon(getMainWindow, messages) {
tray.on('click', tray.showWindow);
tray.setToolTip(messages.trayTooltip.message);
tray.setToolTip(messages.lokiMessenger.message);
tray.updateContextMenu();
return tray;

@ -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;

@ -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;

@ -352,69 +352,6 @@
Views.Initialization.setMessage(window.i18n('optimizingApplication'));
window.log.info('Cleanup: starting...');
const results = await Promise.all([
window.Signal.Data.getOutgoingWithoutExpiresAt({
MessageCollection: Whisper.MessageCollection,
}),
window.Signal.Data.getAllUnsentMessages({
MessageCollection: Whisper.MessageCollection,
}),
]);
// Combine the models
const messagesForCleanup = results.reduce(
(array, current) => array.concat(current.toArray()),
[]
);
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'
);
// Make sure we only target outgoing messages
if (
message.isFriendRequest() &&
message.get('direction') === 'incoming'
) {
return;
}
if (message.isEndSession()) {
return;
}
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();
}
@ -517,6 +454,73 @@
manageExpiringData();
window.dispatchEvent(new Event('storage_ready'));
window.log.info('Cleanup: starting...');
const results = await Promise.all([
window.Signal.Data.getOutgoingWithoutExpiresAt({
MessageCollection: Whisper.MessageCollection,
}),
window.Signal.Data.getAllUnsentMessages({
MessageCollection: Whisper.MessageCollection,
}),
]);
// Combine the models
const messagesForCleanup = results.reduce(
(array, current) => array.concat(current.toArray()),
[]
);
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'
);
// Make sure we only target outgoing messages
if (
message.isFriendRequest() &&
message.get('direction') === 'incoming'
) {
return;
}
if (message.isEndSession()) {
return;
}
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');

@ -311,8 +311,11 @@
this._initialFetchComplete = true;
const promises = [];
conversations.forEach(conversation => {
if (!conversation.get('lastMessage')) {
promises.push(conversation.updateLastMessage());
}
promises.concat([
conversation.updateLastMessage(),
conversation.updateProfile(),
conversation.updateProfileAvatar(),
conversation.resetPendingSend(),

@ -158,8 +158,6 @@
this.unset('unidentifiedDeliveryUnrestricted');
this.unset('hasFetchedProfile');
this.unset('tokens');
this.unset('lastMessage');
this.unset('lastMessageStatus');
this.typingRefreshTimer = null;
this.typingPauseTimer = null;
@ -420,11 +418,10 @@
isTyping: typingKeys.length > 0,
lastMessage: {
status: this.lastMessageStatus,
text: this.lastMessage,
status: this.get('lastMessageStatus'),
text: this.get('lastMessage'),
},
isOnline: this.isOnline(),
isMe: this.isMe(),
hasNickname: !!this.getNickname(),
onClick: () => this.trigger('select', this),
@ -1248,8 +1245,6 @@
}
const message = this.addSingleMessage(messageWithSchema);
this.lastMessage = message.getNotificationText();
this.lastMessageStatus = 'sending';
if (this.isPrivate()) {
message.set({ destination });
@ -1261,6 +1256,8 @@
message.set({ id });
this.set({
lastMessage: message.getNotificationText(),
lastMessageStatus: 'sending',
active_at: now,
timestamp: now,
});
@ -1542,17 +1539,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 = {};
@ -1562,8 +1548,6 @@
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
} else if (hasChanged) {
this.trigger('change');
}
},

@ -623,11 +623,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}`;
});
},
@ -1280,12 +1279,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

@ -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`
);
}

@ -0,0 +1 @@
export function isLinkSneaky(link: string): boolean;

@ -2,6 +2,7 @@
const { isNumber, compact } = require('lodash');
const he = require('he');
const nodeUrl = require('url');
const LinkifyIt = require('linkify-it');
const linkify = LinkifyIt();
@ -16,6 +17,7 @@ module.exports = {
getImageMetaTag,
isLinkInWhitelist,
isMediaLinkInWhitelist,
isLinkSneaky,
};
const SUPPORTED_DOMAINS = [
@ -194,3 +196,149 @@ 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
const unicodeDomain = nodeUrl.domainToUnicode(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;
}

@ -1331,8 +1331,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;
}
@ -1346,8 +1350,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'),
});
};
@ -1360,8 +1365,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);
},

@ -219,11 +219,6 @@
.find('.network-status-container')
.append(this.networkStatusView.render().el);
extension.windows.onClosed(() => {
this.inboxListView.stopListening();
this.contactListView.stopListening();
});
if (extension.expired()) {
const banner = new Whisper.ExpiredAlertBanner().render();
banner.$el.prependTo(this.$el);
@ -267,11 +262,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);

@ -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);
},

@ -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();

@ -749,7 +749,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) => {

@ -101,21 +101,25 @@ 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();
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
// Don't allow second instance if we are in prod
if (appInstance === 0) {
console.log('quitting; we are the second instance');
app.exit();
}
return true;
});
} else {
app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
if (appInstance === 0 && shouldQuit) {
console.log('quitting; we are the second instance');
app.exit();
showWindow();
}
return true;
});
}
}
@ -220,7 +224,7 @@ function createWindow() {
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
// sandbox: true,
contextIsolation: false,
preload: path.join(__dirname, 'preload.js'),
nativeWindowOpen: true,
},
@ -515,8 +519,8 @@ function showAbout() {
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: false,
preload: path.join(__dirname, 'about_preload.js'),
// sandbox: true,
nativeWindowOpen: true,
},
parent: mainWindow,
@ -561,8 +565,8 @@ async function showSettingsWindow() {
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: false,
preload: path.join(__dirname, 'settings_preload.js'),
// sandbox: true,
nativeWindowOpen: true,
},
parent: mainWindow,
@ -606,8 +610,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,
@ -654,8 +658,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,
@ -707,17 +711,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');

@ -124,7 +124,7 @@
"axios": "0.18.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",
@ -210,8 +210,6 @@
},
"deb": {
"depends": [
"gconf2",
"gconf-service",
"libnotify4",
"libappindicator1",
"libxtst6",

@ -1,5 +1,6 @@
[
{
"label": "Signal Desktop",
"submenu": [
{
"label": "About Loki Messenger",

@ -1,5 +1,6 @@
[
{
"label": "Signal Desktop",
"submenu": [
{
"label": "About Loki Messenger",

@ -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);
});
});
});

@ -24,7 +24,7 @@ interface Props {
i18n: Localizer;
media: Array<MediaItemType>;
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<Props, State> {
const { selectedIndex } = this.state;
const mediaItem = media[selectedIndex];
const { attachment, message } = mediaItem;
const { attachment, message, index } = mediaItem;
onSave({ attachment, message });
onSave({ attachment, message, index });
};
}

@ -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<Props> {
}
const { url, text: originalText } = match;
if (SUPPORTED_PROTOCOLS.test(url)) {
if (SUPPORTED_PROTOCOLS.test(url) && !isLinkSneaky(url)) {
results.push(
<a key={count++} href={url}>
{originalText}

@ -57,6 +57,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', () => {

@ -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 => {

@ -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"
@ -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"
}
]
]

@ -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"
@ -2525,12 +2526,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"

Loading…
Cancel
Save