Handle both given and family name in decrypted profile name
* Decrypt given and family names from profile name string * Handle both given and family name from decrypted profile name * Ensure we properly handle profiles with no family name
This commit is contained in:
parent
4f50c0b093
commit
11266cb775
9 changed files with 326 additions and 39 deletions
90
app/sql.js
90
app/sql.js
|
@ -5,6 +5,7 @@ const sql = require('@journeyapps/sqlcipher');
|
|||
const { app, dialog, clipboard } = require('electron');
|
||||
const { redactAll } = require('../js/modules/privacy');
|
||||
const { remove: removeUserConfig } = require('./user_config');
|
||||
const { combineNames } = require('../ts/util/combineNames');
|
||||
|
||||
const pify = require('pify');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
|
@ -938,18 +939,18 @@ async function updateToSchemaVersion15(currentVersion, instance) {
|
|||
await instance.run('ALTER TABLE emojis RENAME TO emojis_old;');
|
||||
|
||||
await instance.run(`CREATE TABLE emojis(
|
||||
shortName TEXT PRIMARY KEY,
|
||||
lastUsage INTEGER
|
||||
);`);
|
||||
shortName TEXT PRIMARY KEY,
|
||||
lastUsage INTEGER
|
||||
);`);
|
||||
await instance.run(`CREATE INDEX emojis_lastUsage
|
||||
ON emojis (
|
||||
lastUsage
|
||||
);`);
|
||||
ON emojis (
|
||||
lastUsage
|
||||
);`);
|
||||
|
||||
await instance.run('DELETE FROM emojis WHERE shortName = 1');
|
||||
await instance.run(`INSERT INTO emojis(shortName, lastUsage)
|
||||
SELECT shortName, lastUsage FROM emojis_old;
|
||||
`);
|
||||
SELECT shortName, lastUsage FROM emojis_old;
|
||||
`);
|
||||
|
||||
await instance.run('DROP TABLE emojis_old;');
|
||||
|
||||
|
@ -1180,7 +1181,35 @@ async function updateToSchemaVersion18(currentVersion, instance) {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
async function updateToSchemaVersion19(currentVersion, instance) {
|
||||
if (currentVersion >= 19) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('updateToSchemaVersion19: starting...');
|
||||
await instance.run('BEGIN TRANSACTION;');
|
||||
|
||||
await instance.run(
|
||||
`ALTER TABLE conversations
|
||||
ADD COLUMN profileFamilyName TEXT;`
|
||||
);
|
||||
await instance.run(
|
||||
`ALTER TABLE conversations
|
||||
ADD COLUMN profileFullName TEXT;`
|
||||
);
|
||||
|
||||
// Preload new field with the profileName we already have
|
||||
await instance.run('UPDATE conversations SET profileFullName = profileName');
|
||||
|
||||
try {
|
||||
await instance.run('PRAGMA user_version = 19;');
|
||||
await instance.run('COMMIT TRANSACTION;');
|
||||
console.log('updateToSchemaVersion19: success!');
|
||||
} catch (error) {
|
||||
await instance.run('ROLLBACK;');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const SCHEMA_VERSIONS = [
|
||||
updateToSchemaVersion1,
|
||||
updateToSchemaVersion2,
|
||||
|
@ -1200,6 +1229,7 @@ const SCHEMA_VERSIONS = [
|
|||
updateToSchemaVersion16,
|
||||
updateToSchemaVersion17,
|
||||
updateToSchemaVersion18,
|
||||
updateToSchemaVersion19,
|
||||
];
|
||||
|
||||
async function updateSchema(instance) {
|
||||
|
@ -1605,8 +1635,16 @@ async function getConversationCount() {
|
|||
}
|
||||
|
||||
async function saveConversation(data) {
|
||||
// eslint-disable-next-line camelcase
|
||||
const { id, active_at, type, members, name, profileName } = data;
|
||||
const {
|
||||
id,
|
||||
// eslint-disable-next-line camelcase
|
||||
active_at,
|
||||
type,
|
||||
members,
|
||||
name,
|
||||
profileName,
|
||||
profileFamilyName,
|
||||
} = data;
|
||||
|
||||
await db.run(
|
||||
`INSERT INTO conversations (
|
||||
|
@ -1617,7 +1655,9 @@ async function saveConversation(data) {
|
|||
type,
|
||||
members,
|
||||
name,
|
||||
profileName
|
||||
profileName,
|
||||
profileFamilyName,
|
||||
profileFullName
|
||||
) values (
|
||||
$id,
|
||||
$json,
|
||||
|
@ -1626,7 +1666,9 @@ async function saveConversation(data) {
|
|||
$type,
|
||||
$members,
|
||||
$name,
|
||||
$profileName
|
||||
$profileName,
|
||||
$profileFamilyName,
|
||||
$profileFullName
|
||||
);`,
|
||||
{
|
||||
$id: id,
|
||||
|
@ -1637,6 +1679,8 @@ async function saveConversation(data) {
|
|||
$members: members ? members.join(' ') : null,
|
||||
$name: name,
|
||||
$profileName: profileName,
|
||||
$profileFamilyName: profileFamilyName,
|
||||
$profileFullName: combineNames(profileName, profileFamilyName),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1660,8 +1704,16 @@ async function saveConversations(arrayOfConversations) {
|
|||
saveConversations.needsSerial = true;
|
||||
|
||||
async function updateConversation(data) {
|
||||
// eslint-disable-next-line camelcase
|
||||
const { id, active_at, type, members, name, profileName } = data;
|
||||
const {
|
||||
id,
|
||||
// eslint-disable-next-line camelcase
|
||||
active_at,
|
||||
type,
|
||||
members,
|
||||
name,
|
||||
profileName,
|
||||
profileFamilyName,
|
||||
} = data;
|
||||
|
||||
await db.run(
|
||||
`UPDATE conversations SET
|
||||
|
@ -1671,7 +1723,9 @@ async function updateConversation(data) {
|
|||
type = $type,
|
||||
members = $members,
|
||||
name = $name,
|
||||
profileName = $profileName
|
||||
profileName = $profileName,
|
||||
profileFamilyName = $profileFamilyName,
|
||||
profileFullName = $profileFullName
|
||||
WHERE id = $id;`,
|
||||
{
|
||||
$id: id,
|
||||
|
@ -1682,6 +1736,8 @@ async function updateConversation(data) {
|
|||
$members: members ? members.join(' ') : null,
|
||||
$name: name,
|
||||
$profileName: profileName,
|
||||
$profileFamilyName: profileFamilyName,
|
||||
$profileFullName: combineNames(profileName, profileFamilyName),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1769,14 +1825,14 @@ async function searchConversations(query, { limit } = {}) {
|
|||
(
|
||||
id LIKE $id OR
|
||||
name LIKE $name OR
|
||||
profileName LIKE $profileName
|
||||
profileFullName LIKE $profileFullName
|
||||
)
|
||||
ORDER BY active_at DESC
|
||||
LIMIT $limit`,
|
||||
{
|
||||
$id: `%${query}%`,
|
||||
$name: `%${query}%`,
|
||||
$profileName: `%${query}%`,
|
||||
$profileFullName: `%${query}%`,
|
||||
$limit: limit || 100,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1787,16 +1787,19 @@
|
|||
const data = window.Signal.Crypto.base64ToArrayBuffer(encryptedName);
|
||||
|
||||
// decrypt
|
||||
const decrypted = await textsecure.crypto.decryptProfileName(
|
||||
const { given, family } = await textsecure.crypto.decryptProfileName(
|
||||
data,
|
||||
keyBuffer
|
||||
);
|
||||
|
||||
// encode
|
||||
const profileName = window.Signal.Crypto.stringFromBytes(decrypted);
|
||||
const profileName = window.Signal.Crypto.stringFromBytes(given);
|
||||
const profileFamilyName = family
|
||||
? window.Signal.Crypto.stringFromBytes(family)
|
||||
: null;
|
||||
|
||||
// set
|
||||
this.set({ profileName });
|
||||
this.set({ profileName, profileFamilyName });
|
||||
},
|
||||
async setProfileAvatar(avatarPath) {
|
||||
if (!avatarPath) {
|
||||
|
@ -1840,6 +1843,7 @@
|
|||
profileKey,
|
||||
accessKey: null,
|
||||
profileName: null,
|
||||
profileFamilyName: null,
|
||||
profileAvatar: null,
|
||||
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||
});
|
||||
|
@ -1865,6 +1869,7 @@
|
|||
profileAvatar: null,
|
||||
profileKey: null,
|
||||
profileName: null,
|
||||
profileFamilyName: null,
|
||||
accessKey: null,
|
||||
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||
});
|
||||
|
@ -1951,7 +1956,10 @@
|
|||
|
||||
getProfileName() {
|
||||
if (this.isPrivate()) {
|
||||
return this.get('profileName');
|
||||
return Util.combineNames(
|
||||
this.get('profileName'),
|
||||
this.get('profileFamilyName')
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
const PROFILE_IV_LENGTH = 12; // bytes
|
||||
const PROFILE_KEY_LENGTH = 32; // bytes
|
||||
const PROFILE_TAG_LENGTH = 128; // bits
|
||||
const PROFILE_NAME_PADDED_LENGTH = 26; // bytes
|
||||
const PROFILE_NAME_PADDED_LENGTH = 53; // bytes
|
||||
|
||||
function verifyDigest(data, theirDigest) {
|
||||
return crypto.subtle.digest({ name: 'SHA-256' }, data).then(ourDigest => {
|
||||
|
@ -208,18 +208,39 @@
|
|||
'base64'
|
||||
).toArrayBuffer();
|
||||
return textsecure.crypto.decryptProfile(data, key).then(decrypted => {
|
||||
// unpad
|
||||
const padded = new Uint8Array(decrypted);
|
||||
let i;
|
||||
for (i = padded.length; i > 0; i -= 1) {
|
||||
if (padded[i - 1] !== 0x00) {
|
||||
|
||||
// Given name is the start of the string to the first null character
|
||||
let givenEnd;
|
||||
for (givenEnd = 0; givenEnd < padded.length; givenEnd += 1) {
|
||||
if (padded[givenEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(0, i)
|
||||
.toArrayBuffer();
|
||||
// Family name is the next chunk of non-null characters after that first null
|
||||
let familyEnd;
|
||||
for (
|
||||
familyEnd = givenEnd + 1;
|
||||
familyEnd < padded.length;
|
||||
familyEnd += 1
|
||||
) {
|
||||
if (padded[familyEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const foundFamilyName = familyEnd > givenEnd + 1;
|
||||
|
||||
return {
|
||||
given: dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(0, givenEnd)
|
||||
.toArrayBuffer(),
|
||||
family: foundFamilyName
|
||||
? dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(givenEnd + 1, familyEnd)
|
||||
.toArrayBuffer()
|
||||
: null,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global libsignal, textsecure */
|
||||
|
||||
describe('encrypting and decrypting profile data', () => {
|
||||
const NAME_PADDED_LENGTH = 26;
|
||||
const NAME_PADDED_LENGTH = 53;
|
||||
describe('encrypting and decrypting profile names', () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
||||
const name = 'Alice';
|
||||
|
@ -14,11 +14,78 @@ describe('encrypting and decrypting profile data', () => {
|
|||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(decrypted => {
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
name
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles a given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789012345678901234567890123456789123';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
name
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles family/given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789\u000001234567890123456789012345678912';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
'01234567890123456789'
|
||||
);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(family).toString('utf8'),
|
||||
'01234567890123456789012345678912'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles a string with family/given name', () => {
|
||||
const name = 'Alice\0Jones';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
'Alice'
|
||||
);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(family).toString('utf8'),
|
||||
'Jones'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,10 +99,11 @@ describe('encrypting and decrypting profile data', () => {
|
|||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(decrypted => {
|
||||
assert.strictEqual(decrypted.byteLength, 0);
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(given.byteLength, 0);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
''
|
||||
);
|
||||
});
|
||||
|
|
|
@ -127,8 +127,8 @@
|
|||
"redux-ts-utils": "3.2.2",
|
||||
"reselect": "4.0.0",
|
||||
"rimraf": "2.6.2",
|
||||
"sanitize.css": "11.0.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize.css": "11.0.0",
|
||||
"semver": "5.4.1",
|
||||
"sharp": "0.23.0",
|
||||
"spellchecker": "3.7.0",
|
||||
|
|
29
ts/test/util/combineNames_test.ts
Normal file
29
ts/test/util/combineNames_test.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { combineNames } from '../../util/combineNames';
|
||||
|
||||
describe('combineNames', () => {
|
||||
it('returns null if no names provided', () => {
|
||||
assert.strictEqual(combineNames('', ''), null);
|
||||
});
|
||||
|
||||
it('returns first name only if family name not provided', () => {
|
||||
assert.strictEqual(combineNames('Alice'), 'Alice');
|
||||
});
|
||||
|
||||
it('returns returns combined names', () => {
|
||||
assert.strictEqual(combineNames('Alice', 'Jones'), 'Alice Jones');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Chinese', () => {
|
||||
assert.strictEqual(combineNames('振宁', '杨'), '杨振宁');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Japanese', () => {
|
||||
assert.strictEqual(combineNames('泰夫', '木田'), '木田泰夫');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Korean', () => {
|
||||
assert.strictEqual(combineNames('채원', '도윤'), '도윤채원');
|
||||
});
|
||||
});
|
95
ts/util/combineNames.ts
Normal file
95
ts/util/combineNames.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
// We don't include unicode-12.1.0 because it's over 100MB in size
|
||||
|
||||
// From https://github.com/mathiasbynens/unicode-12.1.0/tree/master/Block
|
||||
|
||||
// tslint:disable variable-name
|
||||
|
||||
const CJK_Compatibility = /[\u3300-\u33FF]/;
|
||||
const CJK_Compatibility_Forms = /[\uFE30-\uFE4F]/;
|
||||
const CJK_Compatibility_Ideographs = /[\uF900-\uFAFF]/;
|
||||
const CJK_Compatibility_Ideographs_Supplement = /\uD87E[\uDC00-\uDE1F]/;
|
||||
const CJK_Radicals_Supplement = /[\u2E80-\u2EFF]/;
|
||||
const CJK_Strokes = /[\u31C0-\u31EF]/;
|
||||
const CJK_Symbols_And_Punctuation = /[\u3000-\u303F]/;
|
||||
const CJK_Unified_Ideographs = /[\u4E00-\u9FFF]/;
|
||||
const CJK_Unified_Ideographs_Extension_A = /[\u3400-\u4DBF]/;
|
||||
const CJK_Unified_Ideographs_Extension_B = /[\uD840-\uD868][\uDC00-\uDFFF]|\uD869[\uDC00-\uDEDF]/;
|
||||
const CJK_Unified_Ideographs_Extension_C = /\uD869[\uDF00-\uDFFF]|[\uD86A-\uD86C][\uDC00-\uDFFF]|\uD86D[\uDC00-\uDF3F]/;
|
||||
const CJK_Unified_Ideographs_Extension_D = /\uD86D[\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1F]/;
|
||||
const CJK_Unified_Ideographs_Extension_E = /\uD86E[\uDC20-\uDFFF]|[\uD86F-\uD872][\uDC00-\uDFFF]|\uD873[\uDC00-\uDEAF]/;
|
||||
const Enclosed_CJK_Letters_And_Months = /[\u3200-\u32FF]/;
|
||||
const Kangxi_Radicals = /[\u2F00-\u2FDF]/;
|
||||
const Ideographic_Description_Characters = /[\u2FF0-\u2FFF]/;
|
||||
const Hiragana = /[\u3040-\u309F]/;
|
||||
const Katakana = /[\u30A0-\u30FF]/;
|
||||
const Katakana_Phonetic_Extensions = /[\u31F0-\u31FF]/;
|
||||
const Hangul_Compatibility_Jamo = /[\u3130-\u318F]/;
|
||||
const Hangul_Jamo = /[\u1100-\u11FF]/;
|
||||
const Hangul_Jamo_Extended_A = /[\uA960-\uA97F]/;
|
||||
const Hangul_Jamo_Extended_B = /[\uD7B0-\uD7FF]/;
|
||||
const Hangul_Syllables = /[\uAC00-\uD7AF]/;
|
||||
|
||||
// From https://github.com/mathiasbynens/unicode-12.1.0/tree/master/Binary_Property/Ideographic
|
||||
const isIdeographic = /[\u3006\u3007\u3021-\u3029\u3038-\u303A\u3400-\u4DB5\u4E00-\u9FEF\uF900-\uFA6D\uFA70-\uFAD9]|[\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD821[\uDC00-\uDFF7]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDD70-\uDEFB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/;
|
||||
|
||||
export function combineNames(given: string, family?: string): null | string {
|
||||
if (!given) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Users who haven't upgraded to dual-name, or went minimal, will just have a given name
|
||||
if (!family) {
|
||||
return given;
|
||||
}
|
||||
|
||||
if (isAllCKJV(family) && isAllCKJV(given)) {
|
||||
return `${family}${given}`;
|
||||
}
|
||||
|
||||
return `${given} ${family}`;
|
||||
}
|
||||
|
||||
function isAllCKJV(name: string): boolean {
|
||||
for (const codePoint of name) {
|
||||
if (!isCKJV(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line cyclomatic-complexity
|
||||
function isCKJV(codePoint: string) {
|
||||
if (codePoint === ' ') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
CJK_Compatibility.test(codePoint) ||
|
||||
CJK_Compatibility_Forms.test(codePoint) ||
|
||||
CJK_Compatibility_Ideographs.test(codePoint) ||
|
||||
CJK_Compatibility_Ideographs_Supplement.test(codePoint) ||
|
||||
CJK_Radicals_Supplement.test(codePoint) ||
|
||||
CJK_Strokes.test(codePoint) ||
|
||||
CJK_Symbols_And_Punctuation.test(codePoint) ||
|
||||
CJK_Unified_Ideographs.test(codePoint) ||
|
||||
CJK_Unified_Ideographs_Extension_A.test(codePoint) ||
|
||||
CJK_Unified_Ideographs_Extension_B.test(codePoint) ||
|
||||
CJK_Unified_Ideographs_Extension_C.test(codePoint) ||
|
||||
CJK_Unified_Ideographs_Extension_D.test(codePoint) ||
|
||||
CJK_Unified_Ideographs_Extension_E.test(codePoint) ||
|
||||
Enclosed_CJK_Letters_And_Months.test(codePoint) ||
|
||||
Kangxi_Radicals.test(codePoint) ||
|
||||
Ideographic_Description_Characters.test(codePoint) ||
|
||||
Hiragana.test(codePoint) ||
|
||||
Katakana.test(codePoint) ||
|
||||
Katakana_Phonetic_Extensions.test(codePoint) ||
|
||||
Hangul_Compatibility_Jamo.test(codePoint) ||
|
||||
Hangul_Jamo.test(codePoint) ||
|
||||
Hangul_Jamo_Extended_A.test(codePoint) ||
|
||||
Hangul_Jamo_Extended_B.test(codePoint) ||
|
||||
Hangul_Syllables.test(codePoint) ||
|
||||
isIdeographic.test(codePoint)
|
||||
);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import * as GoogleChrome from './GoogleChrome';
|
||||
import { arrayBufferToObjectURL } from './arrayBufferToObjectURL';
|
||||
import { combineNames } from './combineNames';
|
||||
import { createBatcher } from './batcher';
|
||||
import { createWaitBatcher } from './waitBatcher';
|
||||
import { isFileDangerous } from './isFileDangerous';
|
||||
|
@ -9,6 +10,7 @@ import { makeLookup } from './makeLookup';
|
|||
|
||||
export {
|
||||
arrayBufferToObjectURL,
|
||||
combineNames,
|
||||
createBatcher,
|
||||
createWaitBatcher,
|
||||
GoogleChrome,
|
||||
|
|
|
@ -1212,10 +1212,18 @@
|
|||
{
|
||||
"rule": "jQuery-wrap(",
|
||||
"path": "libtextsecure/crypto.js",
|
||||
"line": " return dcodeIO.ByteBuffer.wrap(padded)",
|
||||
"lineNumber": 220,
|
||||
"line": " given: dcodeIO.ByteBuffer.wrap(padded)",
|
||||
"lineNumber": 235,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-19T18:13:29.628Z"
|
||||
"updated": "2020-01-10T23:53:06.768Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-wrap(",
|
||||
"path": "libtextsecure/crypto.js",
|
||||
"line": " ? dcodeIO.ByteBuffer.wrap(padded)",
|
||||
"lineNumber": 239,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-01-10T23:53:06.768Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-wrap(",
|
||||
|
|
Loading…
Reference in a new issue