Skip to content

Commit eccaf72

Browse files
authored
Fix base32 unicode alphabet (#2380)
1 parent 740d350 commit eccaf72

4 files changed

Lines changed: 110 additions & 4 deletions

File tree

src/core/operations/ToBase32.mjs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ class ToBase32 extends Operation {
4343
if (!input) return "";
4444
input = new Uint8Array(input);
4545

46-
const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
46+
const alphabet = args[0] ?
47+
Utils.expandAlphRange(args[0]).join("") :
48+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
49+
50+
// Unicode-safe alphabet handling
51+
// Supports BMP + non-BMP characters (emoji, Mahjong tiles, etc.)
52+
const alphabetChars = Array.from(alphabet);
53+
4754
let output = "",
4855
chr1, chr2, chr3, chr4, chr5,
4956
enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
@@ -74,10 +81,19 @@ class ToBase32 extends Operation {
7481
enc8 = 32;
7582
}
7683

77-
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
78-
alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
79-
alphabet.charAt(enc7) + alphabet.charAt(enc8);
84+
// Preserve original charAt() behavior:
85+
// out-of-range indexes return ""
86+
output +=
87+
(alphabetChars[enc1] || "") +
88+
(alphabetChars[enc2] || "") +
89+
(alphabetChars[enc3] || "") +
90+
(alphabetChars[enc4] || "") +
91+
(alphabetChars[enc5] || "") +
92+
(alphabetChars[enc6] || "") +
93+
(alphabetChars[enc7] || "") +
94+
(alphabetChars[enc8] || "");
8095
}
96+
8197
return output;
8298
}
8399

tests/node/tests/NodeDish.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,42 @@ TestRegister.addApiTests([
6565
assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0");
6666
}),
6767

68+
it("Composable Dish: toBase32 should support non-BMP Unicode alphabets", () => {
69+
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
70+
71+
const result = new Dish("hello")
72+
.apply(toBase32, {alphabet})
73+
.toString();
74+
75+
// Should not contain replacement characters
76+
assert.equal(result.includes("�"), false);
77+
78+
// Should contain only symbols from the alphabet
79+
for (const ch of Array.from(result)) {
80+
assert.ok(Array.from(alphabet).includes(ch));
81+
}
82+
83+
// "hello" => 8 Base32 symbols
84+
assert.equal(Array.from(result).length, 8);
85+
}),
86+
87+
it("Composable Dish: toBase32 should omit padding for 32-character Unicode alphabets", () => {
88+
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
89+
90+
const result = new Dish("hell")
91+
.apply(toBase32, {alphabet})
92+
.toString();
93+
94+
// Should not leak undefined from array indexing
95+
assert.equal(result.includes("undefined"), false);
96+
97+
// Should not contain replacement characters
98+
assert.equal(result.includes("�"), false);
99+
100+
// Unpadded Base32 output for 4-byte input should be 7 symbols
101+
assert.equal(Array.from(result).length, 7);
102+
}),
103+
68104
it("Dish translation: ArrayBuffer to ArrayBuffer", () => {
69105
const dish = new Dish(new ArrayBuffer(10), 4);
70106
dish.get("array buffer");

tests/node/tests/nodeApi.mjs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,38 @@ TestRegister.addApiTests([
109109
assert.equal(3 + result, 35);
110110
}),
111111

112+
it("toBase32: should support non-BMP Unicode alphabets", () => {
113+
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
114+
115+
const result = chef.toBase32("hello", {alphabet}).toString();
116+
117+
// Should not contain replacement characters
118+
assert.equal(result.includes("�"), false);
119+
120+
// Should contain only symbols from the alphabet
121+
for (const ch of Array.from(result)) {
122+
assert.ok(Array.from(alphabet).includes(ch));
123+
}
124+
125+
// "hello" => 8 Base32 symbols
126+
assert.equal(Array.from(result).length, 8);
127+
}),
128+
129+
it("toBase32: should omit padding for 32-character Unicode alphabets", () => {
130+
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
131+
132+
const result = chef.toBase32("hell", {alphabet}).toString();
133+
134+
// Should not leak undefined from array indexing
135+
assert.equal(result.includes("undefined"), false);
136+
137+
// Should not contain replacement characters
138+
assert.equal(result.includes("�"), false);
139+
140+
// Unpadded Base32 output for 4-byte input should be 7 symbols
141+
assert.equal(Array.from(result).length, 7);
142+
}),
143+
112144
it("chef.help: should exist", () => {
113145
assert(chef.help);
114146
}),

tests/operations/tests/Base32.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,27 @@ TestRegister.addTests([
172172
},
173173
],
174174
},
175+
{
176+
name: "To Base32: should support non-BMP Unicode alphabets",
177+
input: "hello",
178+
expectedOutput: "🀝🀈🀐🀔🀖🀀🀊🀟",
179+
recipeConfig: [
180+
{
181+
op: "To Base32",
182+
args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"],
183+
},
184+
],
185+
},
186+
{
187+
name: "To Base32: should omit padding for 32-character Unicode alphabets",
188+
input: "hell",
189+
expectedOutput: "🀝🀈🀐🀔🀖🀀🀇",
190+
recipeConfig: [
191+
{
192+
op: "To Base32",
193+
args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"],
194+
},
195+
],
196+
},
175197
]);
176198

0 commit comments

Comments
 (0)