diff options
author | bptato <nincsnevem662@gmail.com> | 2024-04-21 13:47:27 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-04-21 13:47:42 +0200 |
commit | 7802c914b3b25b4149a8f1333e41550733042820 (patch) | |
tree | 546cd6aea28205aef3b8359c8e4e84f5b50f64f0 /src/js | |
parent | c5fbbf475fbbb9cc6e2e98131f6223e7deced601 (diff) | |
download | chawan-7802c914b3b25b4149a8f1333e41550733042820.tar.gz |
base64: rewrite atob
Turns out std/base64's `decode' is broken: atob(" ") would panic. So we no longer use that. Basic testing indicates that the new version is closer to the standard- mandated behavior than the old one was. OTOH I assume it's somewhat slower, but that can be improved later if it proves to be a bottleneck.
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/base64.nim | 71 |
1 files changed, 65 insertions, 6 deletions
diff --git a/src/js/base64.nim b/src/js/base64.nim index a8362910..5139ad28 100644 --- a/src/js/base64.nim +++ b/src/js/base64.nim @@ -5,17 +5,76 @@ import js/domexception import js/javascript import js/jstypes import types/opt +import utils/twtstr # atob and btoa convert Latin-1 to base64 and vice versa. (And throw on # anything above latin-1.) +func atob(c: char): uint8 {.inline.} = + # see RFC 4648 table + if c in AsciiUpperAlpha: + return uint8(c) - uint8('A') + if c in AsciiLowerAlpha: + return uint8(c) - uint8('a') + 26 + if c in AsciiDigit: + return uint8(c) - uint8('0') + 52 + if c == '+': + return 62 + if c == '/': + return 63 + return uint8.high + proc atob*(data: string): DOMResult[NarrowString] = - try: - let ds = NarrowString(base64.decode(data)) - return ok(ds) - except ValueError: - return errDOMException("Invalid character in string", + var outs = newStringOfCap(data.len div 4 * 3) + var buf: array[4, uint8] + var i = 0 + var j = 0 + var pad = 0 + while true: + i = data.skipBlanks(i) + if i >= data.len: + break + if data[i] == '=': + i = data.skipBlanks(i + 1) + inc pad + break + buf[j] = atob(data[i]) + if buf[j] == uint8.high: + return errDOMException("Invalid character in encoded string", + "InvalidCharacterError") + if j == 3: + let ob1 = (buf[0] shl 2) or (buf[1] shr 4) # 6 bits of b0 | 2 bits of b1 + let ob2 = (buf[1] shl 4) or (buf[2] shr 2) # 4 bits of b1 | 4 bits of b2 + let ob3 = (buf[2] shl 6) or buf[3] # 2 bits of b2 | 6 bits of b3 + outs &= char(ob1) + outs &= char(ob2) + outs &= char(ob3) + j = 0 + else: + inc j + inc i + if i < data.len: + if i < data.len and data[i] == '=': + inc pad + inc i + i = data.skipBlanks(i) + if pad > 0 and j + pad != 4: + return errDOMException("Too much padding", "InvalidCharacterError") + if i < data.len: + return errDOMException("Invalid character after encoded string", + "InvalidCharacterError") + if j == 3: + let ob1 = (buf[0] shl 2) or (buf[1] shr 4) # 6 bits of b0 | 2 bits of b1 + let ob2 = (buf[1] shl 4) or (buf[2] shr 2) # 4 bits of b1 | 4 bits of b2 + outs &= char(ob1) + outs &= char(ob2) + elif j == 2: + let ob1 = (buf[0] shl 2) or (buf[1] shr 4) # 6 bits of b0 | 2 bits of b1 + outs &= char(ob1) + elif j != 0: + return errDOMException("Incorrect number of characters in encoded string", "InvalidCharacterError") + return ok(NarrowString(outs)) proc btoa*(ctx: JSContext; data: JSValue): DOMResult[string] = let data = JS_ToString(ctx, data) @@ -31,6 +90,6 @@ proc btoa*(ctx: JSContext; data: JSValue): DOMResult[string] = JS_FreeValue(ctx, data) return ok("") let buf = JS_GetNarrowStringBuffer(data) - let res = base64.encode(toOpenArray(buf, 0, len - 1)) + let res = base64.encode(buf.toOpenArray(0, len - 1)) JS_FreeValue(ctx, data) return ok(res) |