From eccaf723f5a5f4d33578daad05add389395daf4e Mon Sep 17 00:00:00 2001 From: loki1205 <87192195+loki1205@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:09:33 +0530 Subject: [PATCH] Fix base32 unicode alphabet (#2380) --- src/core/operations/ToBase32.mjs | 24 +++++++++++++++++---- tests/node/tests/NodeDish.mjs | 36 +++++++++++++++++++++++++++++++ tests/node/tests/nodeApi.mjs | 32 +++++++++++++++++++++++++++ tests/operations/tests/Base32.mjs | 22 +++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/core/operations/ToBase32.mjs b/src/core/operations/ToBase32.mjs index 44eb8b48..b2ae0ef3 100644 --- a/src/core/operations/ToBase32.mjs +++ b/src/core/operations/ToBase32.mjs @@ -43,7 +43,14 @@ class ToBase32 extends Operation { if (!input) return ""; input = new Uint8Array(input); - const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + const alphabet = args[0] ? + Utils.expandAlphRange(args[0]).join("") : + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + + // Unicode-safe alphabet handling + // Supports BMP + non-BMP characters (emoji, Mahjong tiles, etc.) + const alphabetChars = Array.from(alphabet); + let output = "", chr1, chr2, chr3, chr4, chr5, enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, @@ -74,10 +81,19 @@ class ToBase32 extends Operation { enc8 = 32; } - output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) + - alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) + - alphabet.charAt(enc7) + alphabet.charAt(enc8); + // Preserve original charAt() behavior: + // out-of-range indexes return "" + output += + (alphabetChars[enc1] || "") + + (alphabetChars[enc2] || "") + + (alphabetChars[enc3] || "") + + (alphabetChars[enc4] || "") + + (alphabetChars[enc5] || "") + + (alphabetChars[enc6] || "") + + (alphabetChars[enc7] || "") + + (alphabetChars[enc8] || ""); } + return output; } diff --git a/tests/node/tests/NodeDish.mjs b/tests/node/tests/NodeDish.mjs index 3ec8b7e2..958e1338 100644 --- a/tests/node/tests/NodeDish.mjs +++ b/tests/node/tests/NodeDish.mjs @@ -65,6 +65,42 @@ TestRegister.addApiTests([ assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0"); }), + it("Composable Dish: toBase32 should support non-BMP Unicode alphabets", () => { + const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"; + + const result = new Dish("hello") + .apply(toBase32, {alphabet}) + .toString(); + + // Should not contain replacement characters + assert.equal(result.includes("�"), false); + + // Should contain only symbols from the alphabet + for (const ch of Array.from(result)) { + assert.ok(Array.from(alphabet).includes(ch)); + } + + // "hello" => 8 Base32 symbols + assert.equal(Array.from(result).length, 8); + }), + + it("Composable Dish: toBase32 should omit padding for 32-character Unicode alphabets", () => { + const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"; + + const result = new Dish("hell") + .apply(toBase32, {alphabet}) + .toString(); + + // Should not leak undefined from array indexing + assert.equal(result.includes("undefined"), false); + + // Should not contain replacement characters + assert.equal(result.includes("�"), false); + + // Unpadded Base32 output for 4-byte input should be 7 symbols + assert.equal(Array.from(result).length, 7); + }), + it("Dish translation: ArrayBuffer to ArrayBuffer", () => { const dish = new Dish(new ArrayBuffer(10), 4); dish.get("array buffer"); diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs index 5f2476ee..b65b9abc 100644 --- a/tests/node/tests/nodeApi.mjs +++ b/tests/node/tests/nodeApi.mjs @@ -109,6 +109,38 @@ TestRegister.addApiTests([ assert.equal(3 + result, 35); }), + it("toBase32: should support non-BMP Unicode alphabets", () => { + const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"; + + const result = chef.toBase32("hello", {alphabet}).toString(); + + // Should not contain replacement characters + assert.equal(result.includes("�"), false); + + // Should contain only symbols from the alphabet + for (const ch of Array.from(result)) { + assert.ok(Array.from(alphabet).includes(ch)); + } + + // "hello" => 8 Base32 symbols + assert.equal(Array.from(result).length, 8); + }), + + it("toBase32: should omit padding for 32-character Unicode alphabets", () => { + const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"; + + const result = chef.toBase32("hell", {alphabet}).toString(); + + // Should not leak undefined from array indexing + assert.equal(result.includes("undefined"), false); + + // Should not contain replacement characters + assert.equal(result.includes("�"), false); + + // Unpadded Base32 output for 4-byte input should be 7 symbols + assert.equal(Array.from(result).length, 7); + }), + it("chef.help: should exist", () => { assert(chef.help); }), diff --git a/tests/operations/tests/Base32.mjs b/tests/operations/tests/Base32.mjs index 760cdf14..558d7df6 100644 --- a/tests/operations/tests/Base32.mjs +++ b/tests/operations/tests/Base32.mjs @@ -172,5 +172,27 @@ TestRegister.addTests([ }, ], }, + { + name: "To Base32: should support non-BMP Unicode alphabets", + input: "hello", + expectedOutput: "🀝🀈🀐🀔🀖🀀🀊🀟", + recipeConfig: [ + { + op: "To Base32", + args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"], + }, + ], + }, + { + name: "To Base32: should omit padding for 32-character Unicode alphabets", + input: "hell", + expectedOutput: "🀝🀈🀐🀔🀖🀀🀇", + recipeConfig: [ + { + op: "To Base32", + args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"], + }, + ], + }, ]);