diff --git a/package.json b/package.json index 97a00a5a1a3..096d6d3c60b 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "lodash.isequal": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.omit": "^4.5.0", - "lodash.unescape": "^4.0.1", "lodash.union": "^4.6.0", "lodash.uniqby": "^4.4.0", "react": "16.11.0", diff --git a/src/__tests__/lib/exampleData.js b/src/__tests__/lib/exampleData.js index 5eb81dcce71..4439e0d8679 100644 --- a/src/__tests__/lib/exampleData.js +++ b/src/__tests__/lib/exampleData.js @@ -80,6 +80,23 @@ const makeUniqueRandInt = (itemsType: string, end: number): (() => number) => { /** Return a string that's almost surely different every time. */ export const randString = () => randInt(2 ** 54).toString(36); +const intRange = (start, len) => Array.from({ length: len }, (k, i) => i + start); + +/** A string with diverse characters to exercise encoding/decoding bugs. */ +/* eslint-disable prefer-template */ +export const diverseCharacters = + // The whole range of lowest code points, including control codes + // and ASCII punctuation like `"` and `&` used in various syntax... + String.fromCharCode(...intRange(0, 0x100)) + // ... some characters from other scripts... + + 'いい文字🎇' + // ... some unpaired surrogates, which JS strings can have... + + String.fromCharCode(...intRange(0xdbf0, 0x20)) + // ... some characters beyond the BMP (≥ U+10000)... + + '𐂷𠂢' + // ... and some code points at the very end of the Unicode range. + + String.fromCodePoint(...intRange(0x10fff0, 0x10)); + /* ======================================================================== * Users and bots */ diff --git a/src/utils/__tests__/narrow-test.js b/src/utils/__tests__/narrow-test.js index ba65920b66d..05f34029074 100644 --- a/src/utils/__tests__/narrow-test.js +++ b/src/utils/__tests__/narrow-test.js @@ -420,15 +420,8 @@ describe('isSameNarrow', () => { }); }); -describe('parseNarrow', () => { - test('straightforward arrays are parsed', () => { - expect(parseNarrow('[]')).toEqual([]); - expect(parseNarrow('[{"operator":"hey"}]')).toEqual([{ operator: 'hey' }]); - }); -}); - describe('keyFromNarrow+parseNarrow', () => { - const narrows = [ + const baseNarrows = [ ['whole stream', streamNarrow(eg.stream.name)], ['stream conversation', topicNarrow(eg.stream.name, 'a topic')], ['1:1 PM conversation, non-self', pm1to1NarrowFromUser(eg.otherUser)], @@ -440,8 +433,30 @@ describe('keyFromNarrow+parseNarrow', () => { ['all-PMs', ALL_PRIVATE_NARROW], ['search narrow', SEARCH_NARROW('a query')], ]; + + // The only character not allowed in Zulip stream names is '\x00'. + // (See `check_stream_name` in zulip.git:zerver/lib/streams.py.) + // Try approximately everything else. + /* eslint-disable no-control-regex */ + const diverseCharacters = eg.diverseCharacters.replace(/\x00/g, ''); + const htmlEntities = 'h & t & &lquo;ml"'; + const awkwardNarrows = [ + ['whole stream about awkward characters', streamNarrow(diverseCharacters)], + ['whole stream about HTML entities', streamNarrow(htmlEntities)], + [ + 'stream conversation about awkward characters', + topicNarrow(diverseCharacters, `regarding ${diverseCharacters}`), + ], + [ + 'stream conversation about HTML entities', + topicNarrow(htmlEntities, `regarding ${htmlEntities}`), + ], + ['search narrow for awkward characters', SEARCH_NARROW(diverseCharacters)], + ['search narrow for HTML entities', SEARCH_NARROW(htmlEntities)], + ]; + describe('round-trips', () => { - for (const [description, narrow] of narrows) { + for (const [description, narrow] of [...baseNarrows, ...awkwardNarrows]) { test(description, () => { expect(parseNarrow(keyFromNarrow(narrow))).toEqual(narrow); }); diff --git a/src/utils/narrow.js b/src/utils/narrow.js index a3ab7ab13bd..d7be54294d0 100644 --- a/src/utils/narrow.js +++ b/src/utils/narrow.js @@ -1,6 +1,5 @@ /* @flow strict-local */ import isEqual from 'lodash.isequal'; -import unescape from 'lodash.unescape'; import type { Narrow, Message, Outbox, User, UserOrBot } from '../types'; import { @@ -18,7 +17,7 @@ export const isSameNarrow = (narrow1: Narrow, narrow2: Narrow): boolean => /** * Parse a narrow previously encoded with keyFromNarrow. */ -export const parseNarrow = (narrowStr: string): Narrow => JSON.parse(unescape(narrowStr)); +export const parseNarrow = (narrowStr: string): Narrow => JSON.parse(narrowStr); /** * The key we use for this narrow in our Redux data structures.