Merge pull request #1388 from cosmos/amino-escaping
Amino JSON string escaping (v2)
This commit is contained in:
commit
41f07c1575
@ -6,6 +6,14 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- @cosmjs/amino: Fix escaping of "&", "<" and ">" characters in Amino JSON
|
||||
encoding to match the Go implementation ([#1373], [#1388]).
|
||||
|
||||
[#1373]: https://github.com/cosmos/cosmjs/pull/1373
|
||||
[#1388]: https://github.com/cosmos/cosmjs/pull/1388
|
||||
|
||||
## [0.30.0] - 2023-03-09
|
||||
|
||||
### Changed
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { Random } from "@cosmjs/crypto";
|
||||
import { toBech32 } from "@cosmjs/encoding";
|
||||
|
||||
import { AminoMsg, makeSignDoc, sortedJsonStringify } from "./signdoc";
|
||||
import { AminoMsg, escapeCharacters, makeSignDoc, sortedJsonStringify } from "./signdoc";
|
||||
|
||||
function makeRandomAddress(): string {
|
||||
return toBech32("cosmos", Random.getBytes(20));
|
||||
@ -132,4 +132,43 @@ describe("encoding", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("escapeCharacters", () => {
|
||||
it("works", () => {
|
||||
// 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"}`);
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -72,6 +72,28 @@ export function makeSignDoc(
|
||||
};
|
||||
}
|
||||
|
||||
export function serializeSignDoc(signDoc: StdSignDoc): Uint8Array {
|
||||
return toUtf8(sortedJsonStringify(signDoc));
|
||||
/**
|
||||
* 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 {
|
||||
const serialized = escapeCharacters(sortedJsonStringify(signDoc));
|
||||
return toUtf8(serialized);
|
||||
}
|
||||
|
||||
@ -455,6 +455,33 @@ describe("SigningStargateClient", () => {
|
||||
});
|
||||
|
||||
describe("legacy Amino mode", () => {
|
||||
it("works with special characters in memo", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = await SigningStargateClient.connectWithSigner(
|
||||
simapp.tendermintUrl,
|
||||
wallet,
|
||||
defaultSigningClientOptions,
|
||||
);
|
||||
|
||||
const msgSend: MsgSend = {
|
||||
fromAddress: faucet.address0,
|
||||
toAddress: makeRandomAddress(),
|
||||
amount: coins(1234, "ucosm"),
|
||||
};
|
||||
const msgAny: MsgSendEncodeObject = {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: msgSend,
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "200000",
|
||||
};
|
||||
const memo = "ampersand:&,lt:<,gt:>";
|
||||
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
|
||||
assertIsDeliverTxSuccess(result);
|
||||
});
|
||||
|
||||
it("works with bank MsgSend", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user