[njs] Added btoa() and atob() from WHATWG spec.
Dmitry Volyntsev
xeioex at nginx.com
Tue Jul 12 15:57:09 UTC 2022
details: https://hg.nginx.org/njs/rev/3acc8a1d9088
branches:
changeset: 1906:3acc8a1d9088
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Tue Jul 12 08:56:35 2022 -0700
description:
Added btoa() and atob() from WHATWG spec.
The functions encode and decode to Base64 and from Base64.
diffstat:
src/njs_builtin.c | 16 +++
src/njs_string.c | 239 +++++++++++++++++++++++++++++++++++++++++++++-
src/njs_string.h | 4 +
src/test/njs_unit_test.c | 64 ++++++++++++
4 files changed, 315 insertions(+), 8 deletions(-)
diffs (384 lines):
diff -r efff48e84bdb -r 3acc8a1d9088 src/njs_builtin.c
--- a/src/njs_builtin.c Mon Jul 11 07:25:03 2022 -0700
+++ b/src/njs_builtin.c Tue Jul 12 08:56:35 2022 -0700
@@ -1247,6 +1247,22 @@ static const njs_object_prop_t njs_glob
{
.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"),
.value = njs_native_function(njs_eval_function, 1),
.writable = 1,
diff -r efff48e84bdb -r 3acc8a1d9088 src/njs_string.c
--- a/src/njs_string.c Mon Jul 11 07:25:03 2022 -0700
+++ b/src/njs_string.c Tue Jul 12 08:56:35 2022 -0700
@@ -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 *s
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
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 -r efff48e84bdb -r 3acc8a1d9088 src/njs_string.h
--- a/src/njs_string.h Mon Jul 11 07:25:03 2022 -0700
+++ b/src/njs_string.h Tue Jul 12 08:56:35 2022 -0700
@@ -251,6 +251,10 @@ njs_int_t njs_string_encode_uri(njs_vm_t
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 -r efff48e84bdb -r 3acc8a1d9088 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Mon Jul 11 07:25:03 2022 -0700
+++ b/src/test/njs_unit_test.c Tue Jul 12 08:56:35 2022 -0700
@@ -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"),
More information about the nginx-devel
mailing list