[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