Migrate to string-based escaping implementation

This commit is contained in:
Simon Warta 2023-03-09 18:14:44 +01:00
parent a7b8710d10
commit 2c4e9178f9
2 changed files with 56 additions and 34 deletions

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Random } from "@cosmjs/crypto";
import { fromUtf8, toBech32, toUtf8 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import { AminoMsg, escapeCharacters, makeSignDoc, sortedJsonStringify } from "./signdoc";
@ -133,13 +133,42 @@ describe("encoding", () => {
});
});
describe("escape characters after utf8 encoding", () => {
describe("escapeCharacters", () => {
it("works", () => {
const test = JSON.stringify({ memo: "ampersand:&,lt:<,gt:>" });
const utf8Encoding = toUtf8(test);
const escapedEncoding = escapeCharacters(utf8Encoding);
// Unchanged originals
expect(escapeCharacters(`""`)).toEqual(`""`);
expect(escapeCharacters(`{}`)).toEqual(`{}`);
expect(escapeCharacters(`[]`)).toEqual(`[]`);
expect(escapeCharacters(`[123,null,"foo",[{}]]`)).toEqual(`[123,null,"foo",[{}]]`);
expect(escapeCharacters(`{"num":123}`)).toEqual(`{"num":123}`);
expect(escapeCharacters(`{"memo":"123"}`)).toEqual(`{"memo":"123"}`);
expect(escapeCharacters(`{"memo":"\\u0026"}`)).toEqual(`{"memo":"\\u0026"}`);
expect(JSON.parse(fromUtf8(utf8Encoding))).toEqual(JSON.parse(fromUtf8(escapedEncoding)));
// Escapes one
expect(escapeCharacters(`{"m":"with amp: &"}`)).toEqual(`{"m":"with amp: \\u0026"}`);
expect(escapeCharacters(`{"m":"with lt: <"}`)).toEqual(`{"m":"with lt: \\u003c"}`);
expect(escapeCharacters(`{"m":"with gt: >"}`)).toEqual(`{"m":"with gt: \\u003e"}`);
// Escapes multiple
expect(escapeCharacters(`{"m":"with amp: &&"}`)).toEqual(`{"m":"with amp: \\u0026\\u0026"}`);
expect(escapeCharacters(`{"m":"with lt: <<"}`)).toEqual(`{"m":"with lt: \\u003c\\u003c"}`);
expect(escapeCharacters(`{"m":"with gt: >>"}`)).toEqual(`{"m":"with gt: \\u003e\\u003e"}`);
expect(escapeCharacters(`{"m":"with all: &<>"}`)).toEqual(`{"m":"with all: \\u0026\\u003c\\u003e"}`);
});
it("escaped encoding can be decoded to the same document", () => {
const docs = [
{ memo: "ampersand:&,lt:<,gt:>", value: 123.421 },
"",
123,
["foo", "ampersand:&,lt:<,gt:>"],
];
for (const doc of docs) {
const normalEncoding = JSON.stringify(doc);
const escapedEncoding = escapeCharacters(normalEncoding);
expect(JSON.parse(escapedEncoding)).toEqual(JSON.parse(normalEncoding));
expect(JSON.parse(escapedEncoding)).toEqual(doc);
}
});
});
});

View File

@ -72,35 +72,28 @@ export function makeSignDoc(
};
}
export function escapeCharacters(encodedArray: Uint8Array): Uint8Array {
const AmpersandUnicode = new Uint8Array([92, 117, 48, 48, 50, 54]);
const LtSignUnicode = new Uint8Array([92, 117, 48, 48, 51, 99]);
const GtSignUnicode = new Uint8Array([92, 117, 48, 48, 51, 101]);
const AmpersandAscii = 38;
const LtSign = 60; // <
const GtSign = 62; // >
const filteredIndex: number[] = [];
encodedArray.forEach((value, index) => {
if (value === AmpersandAscii || value === LtSign || value === GtSign) filteredIndex.push(index);
});
let result = new Uint8Array([...encodedArray]);
const reversedFilteredIndex = filteredIndex.reverse();
reversedFilteredIndex.forEach((value) => {
let unicode = AmpersandUnicode;
if (result[value] === LtSign) {
unicode = LtSignUnicode;
} else if (result[value] === GtSign) {
unicode = GtSignUnicode;
}
result = new Uint8Array([...result.slice(0, value), ...unicode, ...result.slice(value + 1)]);
});
return result;
/**
* Takes a valid JSON document and performs the following escapings in string values:
*
* `&` -> `\u0026`
* `<` -> `\u003c`
* `>` -> `\u003e`
*
* Since those characters do not occur in other places of the JSON document, only
* string values are affected.
*
* If the input is invalid JSON, the behaviour is undefined.
*/
export function escapeCharacters(input: string): string {
// When we migrate to target es2021 or above, we can use replaceAll instead of global patterns.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
const amp = /&/g;
const lt = /</g;
const gt = />/g;
return input.replace(amp, "\\u0026").replace(lt, "\\u003c").replace(gt, "\\u003e");
}
export function serializeSignDoc(signDoc: StdSignDoc): Uint8Array {
return escapeCharacters(toUtf8(sortedJsonStringify(signDoc)));
const serialized = escapeCharacters(sortedJsonStringify(signDoc));
return toUtf8(serialized);
}