From: Dmitry Volyntsev Date: Tue, 12 Jul 2022 15:56:35 +0000 (-0700) Subject: Added btoa() and atob() from WHATWG spec. X-Git-Tag: 0.7.6~6 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=3a14c2f63adc1792ca59b4c86822819558aea69d;p=njs.git Added btoa() and atob() from WHATWG spec. The functions encode and decode to Base64 and from Base64. --- diff --git a/src/njs_builtin.c b/src/njs_builtin.c index fc102aa3..f8dc76f2 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -1245,6 +1245,22 @@ static const njs_object_prop_t njs_global_this_object_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_long_string("atob"), + .value = njs_native_function(njs_string_atob, 1), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("btoa"), + .value = njs_native_function(njs_string_btoa, 1), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("eval"), diff --git a/src/njs_string.c b/src/njs_string.c index 1ba45815..a7096e8b 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -50,6 +50,12 @@ static u_char njs_basis64url[] = { }; +static u_char njs_basis64_enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static u_char njs_basis64url_enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + static void njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src, const u_char *basis, njs_uint_t padding); static njs_int_t njs_string_decode_base64_core(njs_vm_t *vm, @@ -310,10 +316,8 @@ njs_encode_hex_length(const njs_str_t *src, size_t *out_size) void njs_encode_base64(njs_str_t *dst, const njs_str_t *src) { - static u_char basis64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - njs_encode_base64_core(dst, src, basis64, 1); + njs_encode_base64_core(dst, src, njs_basis64_enc, 1); } @@ -335,10 +339,7 @@ njs_encode_base64_length(const njs_str_t *src, size_t *out_size) static void njs_encode_base64url(njs_str_t *dst, const njs_str_t *src) { - static u_char basis64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - - njs_encode_base64_core(dst, src, basis64, 0); + njs_encode_base64_core(dst, src, njs_basis64url_enc, 0); } @@ -4728,6 +4729,228 @@ uri_error: } +njs_int_t +njs_string_btoa(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + u_char *dst; + size_t len, length; + uint32_t cp0, cp1, cp2; + njs_int_t ret; + njs_value_t *value, lvalue; + const u_char *p, *end; + njs_string_prop_t string; + njs_unicode_decode_t ctx; + + value = njs_lvalue_arg(&lvalue, args, nargs, 1); + + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + len = njs_string_prop(&string, value); + + p = string.start; + end = string.start + string.size; + + njs_utf8_decode_init(&ctx); + + length = njs_base64_encoded_length(len); + + dst = njs_string_alloc(vm, &vm->retval, length, length); + if (njs_slow_path(dst == NULL)) { + return NJS_ERROR; + } + + while (len > 2 && p < end) { + cp0 = njs_utf8_decode(&ctx, &p, end); + cp1 = njs_utf8_decode(&ctx, &p, end); + cp2 = njs_utf8_decode(&ctx, &p, end); + + if (njs_slow_path(cp0 > 0xff || cp1 > 0xff || cp2 > 0xff)) { + goto error; + } + + *dst++ = njs_basis64_enc[cp0 >> 2]; + *dst++ = njs_basis64_enc[((cp0 & 0x03) << 4) | (cp1 >> 4)]; + *dst++ = njs_basis64_enc[((cp1 & 0x0f) << 2) | (cp2 >> 6)]; + *dst++ = njs_basis64_enc[cp2 & 0x3f]; + + len -= 3; + } + + if (len > 0) { + cp0 = njs_utf8_decode(&ctx, &p, end); + if (njs_slow_path(cp0 > 0xff)) { + goto error; + } + + *dst++ = njs_basis64_enc[cp0 >> 2]; + + if (len == 1) { + *dst++ = njs_basis64_enc[(cp0 & 0x03) << 4]; + *dst++ = '='; + *dst++ = '='; + + } else { + cp1 = njs_utf8_decode(&ctx, &p, end); + if (njs_slow_path(cp1 > 0xff)) { + goto error; + } + + *dst++ = njs_basis64_enc[((cp0 & 0x03) << 4) | (cp1 >> 4)]; + *dst++ = njs_basis64_enc[(cp1 & 0x0f) << 2]; + *dst++ = '='; + } + + } + + return NJS_OK; + +error: + + njs_type_error(vm, "invalid character (>= U+00FF)"); + + return NJS_ERROR; +} + + +njs_inline void +njs_chb_write_byte_as_utf8(njs_chb_t *chain, u_char byte) +{ + njs_utf8_encode(njs_chb_current(chain), byte); + njs_chb_written(chain, njs_utf8_size(byte)); +} + + +njs_int_t +njs_string_atob(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + size_t i, n, len, pad; + u_char *dst, *tmp, *p; + ssize_t size; + njs_str_t str; + njs_int_t ret; + njs_chb_t chain; + njs_value_t *value, lvalue; + const u_char *b64, *s; + + value = njs_lvalue_arg(&lvalue, args, nargs, 1); + + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + /* Forgiving-base64 decode. */ + + b64 = njs_basis64; + njs_string_get(value, &str); + + tmp = njs_mp_alloc(vm->mem_pool, str.length); + if (tmp == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + + p = tmp; + + for (i = 0; i < str.length; i++) { + if (njs_slow_path(str.start[i] == ' ')) { + continue; + } + + *p++ = str.start[i]; + } + + pad = 0; + str.start = tmp; + str.length = p - tmp; + + if (str.length % 4 == 0) { + if (str.length > 0) { + if (str.start[str.length - 1] == '=') { + pad += 1; + } + + if (str.start[str.length - 2] == '=') { + pad += 1; + } + } + + } else if (str.length % 4 == 1) { + goto error; + } + + for (i = 0; i < str.length - pad; i++) { + if (njs_slow_path(b64[str.start[i]] == 77)) { + goto error; + } + } + + len = njs_base64_decoded_length(str.length, pad); + + njs_chb_init(&chain, vm->mem_pool); + + dst = njs_chb_reserve(&chain, len * 2); + if (njs_slow_path(dst == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + n = len; + s = str.start; + + while (n >= 3) { + njs_chb_write_byte_as_utf8(&chain, b64[s[0]] << 2 | b64[s[1]] >> 4); + njs_chb_write_byte_as_utf8(&chain, b64[s[1]] << 4 | b64[s[2]] >> 2); + njs_chb_write_byte_as_utf8(&chain, b64[s[2]] << 6 | b64[s[3]]); + + s += 4; + n -= 3; + } + + if (n >= 1) { + njs_chb_write_byte_as_utf8(&chain, b64[s[0]] << 2 | b64[s[1]] >> 4); + } + + if (n >= 2) { + njs_chb_write_byte_as_utf8(&chain, b64[s[1]] << 4 | b64[s[2]] >> 2); + } + + size = njs_chb_size(&chain); + if (njs_slow_path(size < 0)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + if (size == 0) { + njs_value_assign(&vm->retval, &njs_string_empty); + return NJS_OK; + } + + dst = njs_string_alloc(vm, &vm->retval, size, len); + if (njs_slow_path(dst == NULL)) { + return NJS_ERROR; + } + + njs_chb_join_to(&chain, dst); + njs_chb_destroy(&chain); + + njs_mp_free(vm->mem_pool, tmp); + + return NJS_OK; + +error: + + njs_type_error(vm, "the string to be decoded is not correctly encoded"); + + return NJS_ERROR; +} + + const njs_object_type_init_t njs_string_type_init = { .constructor = njs_native_ctor(njs_string_constructor, 1, 0), .constructor_props = &njs_string_constructor_init, diff --git a/src/njs_string.h b/src/njs_string.h index a126d3c8..99f9d145 100644 --- a/src/njs_string.h +++ b/src/njs_string.h @@ -251,6 +251,10 @@ njs_int_t njs_string_encode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t component); njs_int_t njs_string_decode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t component); +njs_int_t njs_string_btoa(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused); +njs_int_t njs_string_atob(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused); njs_int_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 39345a01..7935b011 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -9439,6 +9439,70 @@ static njs_unit_test_t njs_test[] = ".every(v=>{var r = v(); return (typeof r === 'string') && r === 'undefined';})"), njs_str("true")}, + /* btoa() */ + + { njs_str("[" + " undefined," + " ''," + " '\\x00'," + " '\\x00\\x01'," + " '\\x00\\x01\\x02'," + " '\\x00\\xfe\\xff'," + " String.fromCodePoint(0x100)," + " String.fromCodePoint(0x00, 0x100)," + " String.fromCodePoint(0x00, 0x01, 0x100)," + " String.bytesFrom([0x80])," + " String.bytesFrom([0x60, 0x80])," + " String.bytesFrom([0x60, 0x60, 0x80])," + "].map(v => { try { return btoa(v); } catch (e) { return '#'} })"), + njs_str("dW5kZWZpbmVk,,AA==,AAE=,AAEC,AP7/,#,#,#,#,#,#")}, + + /* atob() */ + + { njs_str("function c(s) {" + " let cp = [];" + " for (var i = 0; i < s.length; i++) {" + " cp.push(s.codePointAt(i));" + " }" + " return cp;" + "};" + "" + "[" + " undefined," + " ''," + " '='," + " '=='," + " '==='," + " '===='," + " 'AA@'," + " '@'," + " 'A==A'," + " btoa(String.fromCharCode.apply(null, [1]))," + " btoa(String.fromCharCode.apply(null, [1, 2]))," + " btoa(String.fromCharCode.apply(null, [1, 2, 255]))," + " btoa(String.fromCharCode.apply(null, [255, 1, 2, 3]))," + "].map(v => { try { return njs.dump(c(atob(v))); } catch (e) { return '#'} })"), + njs_str("#,[],#,#,#,#,#,#,#,[1],[1,2],[1,2,255],[255,1,2,3]")}, + + { njs_str("function c(s) {" + " let cp = [];" + " for (var i = 0; i < s.length; i++) {" + " cp.push(s.codePointAt(i));" + " }" + " return cp;" + "};" + "" + "[" + " 'CDRW'," + " ' CDRW'," + " 'C DRW'," + " 'CD RW'," + " 'CDR W'," + " 'CDRW '," + " ' C D R W '," + "].every(v => c(atob(v)).toString() == '8,52,86')"), + njs_str("true")}, + /* Functions. */ { njs_str("return"),