diff --git a/web/src/filter.ts b/web/src/filter.ts index cc5a960159..718c1a1550 100644 --- a/web/src/filter.ts +++ b/web/src/filter.ts @@ -448,20 +448,19 @@ export class Filter { This is just for the search bar, not for saving the narrow in the URL fragment. There we do use full URI encoding to avoid problematic characters. */ - static encodeOperand(operand: string): string { - return operand - .replaceAll("%", "%25") - .replaceAll("+", "%2B") - .replaceAll(" ", "+") - .replaceAll('"', "%22"); + static encodeOperand(operand: string, operator: string): string { + if (USER_OPERATORS.has(operator)) { + return operand.replaceAll(/[\s"%]/g, (c) => encodeURIComponent(c)); + } + return operand.replaceAll(/[\s"%+]/g, (c) => (c === " " ? "+" : encodeURIComponent(c))); } static decodeOperand(encoded: string, operator: string): string { - encoded = encoded.replaceAll('"', ""); + encoded = encoded.trim().replaceAll('"', ""); if (!USER_OPERATORS.has(operator)) { encoded = encoded.replaceAll("+", " "); } - return util.robust_url_decode(encoded).trim(); + return util.robust_url_decode(encoded); } // Parse a string into a list of terms (see below). @@ -618,7 +617,9 @@ export class Filter { return term.operand; } const operator = Filter.canonicalize_operator(term.operator); - return sign + operator + ":" + Filter.encodeOperand(term.operand.toString()); + return ( + sign + operator + ":" + Filter.encodeOperand(term.operand.toString(), term.operator) + ); }); return term_strings.join(" "); } diff --git a/web/tests/filter.test.js b/web/tests/filter.test.js index f7e80192fc..f11dda06d2 100644 --- a/web/tests/filter.test.js +++ b/web/tests/filter.test.js @@ -1461,6 +1461,14 @@ test("unparse", () => { ]; string = `channel:${foo_stream_id} topic:Bar`; assert.deepEqual(Filter.unparse(terms), string); + + terms = [ + {operator: "dm", operand: '\t "%+.\u00A0'}, + {operator: "topic", operand: '\t "%+.\u00A0'}, + ]; + string = "dm:%09%20%22%25+.%C2%A0 topic:%09+%22%25%2B.%C2%A0"; + assert.equal(Filter.unparse(terms), string); + assert_same_terms(Filter.parse(string), terms); }); test("describe", ({mock_template}) => {