[njs] QuickJS: added querystring module.
noreply at nginx.com
noreply at nginx.com
Sat Feb 8 02:15:02 UTC 2025
details: https://github.com/nginx/njs/commit/f289dcb99a9e4c9b72ca8d1c60659a43e58547cd
branches: master
commit: f289dcb99a9e4c9b72ca8d1c60659a43e58547cd
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Wed, 29 Jan 2025 21:35:08 -0800
description:
QuickJS: added querystring module.
---
auto/qjs_modules | 6 +
external/qjs_query_string_module.c | 1009 ++++++++++++++++++++++++++++++++++++
src/test/njs_unit_test.c | 354 -------------
test/harness/compareObjects.js | 10 +-
test/querystring.t.mjs | 253 +++++++++
5 files changed, 1277 insertions(+), 355 deletions(-)
diff --git a/auto/qjs_modules b/auto/qjs_modules
index 50ce9476..d82f9d9f 100644
--- a/auto/qjs_modules
+++ b/auto/qjs_modules
@@ -13,6 +13,12 @@ njs_module_srcs=external/qjs_fs_module.c
. auto/qjs_module
+njs_module_name=qjs_query_string_module
+njs_module_incs=
+njs_module_srcs=external/qjs_query_string_module.c
+
+. auto/qjs_module
+
if [ $NJS_OPENSSL = YES -a $NJS_HAVE_OPENSSL = YES ]; then
njs_module_name=qjs_webcrypto_module
njs_module_incs=
diff --git a/external/qjs_query_string_module.c b/external/qjs_query_string_module.c
new file mode 100644
index 00000000..85d2fcb3
--- /dev/null
+++ b/external/qjs_query_string_module.c
@@ -0,0 +1,1009 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) F5, Inc.
+ */
+
+
+#include <qjs.h>
+
+static JSValue qjs_query_string_parse(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_query_string_stringify(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_query_string_escape(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_query_string_unescape(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_query_string_decode(JSContext *cx, const u_char *start,
+ size_t size);
+static int qjs_query_string_encode(njs_chb_t *chain, njs_str_t *str);
+static JSValue qjs_query_string_parser(JSContext *cx, u_char *query,
+ u_char *end, njs_str_t *sep, njs_str_t *eq, JSValue decode,
+ unsigned max_keys);
+static JSModuleDef *qjs_querystring_init(JSContext *ctx, const char *name);
+
+
+static const JSCFunctionListEntry qjs_querystring_export[] = {
+ JS_CFUNC_DEF("decode", 4, qjs_query_string_parse),
+ JS_CFUNC_DEF("encode", 4, qjs_query_string_stringify),
+ JS_CFUNC_DEF("escape", 1, qjs_query_string_escape),
+ JS_CFUNC_DEF("parse", 4, qjs_query_string_parse),
+ JS_CFUNC_DEF("stringify", 4, qjs_query_string_stringify),
+ JS_CFUNC_DEF("unescape", 1, qjs_query_string_unescape),
+};
+
+
+qjs_module_t qjs_query_string_module = {
+ .name = "querystring",
+ .init = qjs_querystring_init,
+};
+
+
+static JSValue
+qjs_query_string_parse(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ int64_t max_keys;
+ JSValue options, ret, decode, native;
+ njs_str_t str, sep, eq;
+
+ sep.start = NULL;
+ eq.start = NULL;
+ str.start = NULL;
+
+ max_keys = 1000;
+ decode = JS_UNDEFINED;
+
+ if (!JS_IsNullOrUndefined(argv[1])) {
+ sep.start = (u_char *) JS_ToCStringLen(cx, &sep.length, argv[1]);
+ if (sep.start == NULL) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ if (!JS_IsNullOrUndefined(argv[2])) {
+ eq.start = (u_char *) JS_ToCStringLen(cx, &eq.length, argv[2]);
+ if (eq.start == NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ return JS_EXCEPTION;
+ }
+ }
+
+ options = argv[3];
+ if (JS_IsObject(options)) {
+ ret = JS_GetPropertyStr(cx, options, "maxKeys");
+ if (JS_IsException(ret)) {
+ goto fail;
+ }
+
+ if (!JS_IsUndefined(ret)) {
+ if (JS_ToInt64(cx, &max_keys, ret) < 0) {
+ JS_FreeValue(cx, ret);
+ goto fail;
+ }
+
+ JS_FreeValue(cx, ret);
+
+ if (max_keys < 0) {
+ max_keys = INT64_MAX;
+ }
+ }
+
+ decode = JS_GetPropertyStr(cx, options, "decodeURIComponent");
+ if (JS_IsException(decode)) {
+ goto fail;
+ }
+
+ if (!JS_IsUndefined(decode) && !JS_IsFunction(cx, decode)) {
+ JS_ThrowTypeError(cx, "option decodeURIComponent is not "
+ "a function");
+ goto fail;
+ }
+ }
+
+ if (JS_IsNullOrUndefined(decode)) {
+ decode = JS_GetPropertyStr(cx, this_val, "unescape");
+ if (JS_IsException(decode)) {
+ goto fail;
+ }
+
+ if (!JS_IsFunction(cx, decode)) {
+ JS_ThrowTypeError(cx, "QueryString.unescape is not a function");
+ goto fail;
+ }
+
+ native = JS_GetPropertyStr(cx, decode, "native");
+ if (JS_IsException(native)) {
+ goto fail;
+ }
+
+ if (JS_IsBool(native)) {
+ JS_FreeValue(cx, decode);
+ decode = JS_NULL;
+ }
+ }
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]);
+ if (str.start == NULL) {
+ goto fail;
+ }
+
+ ret = qjs_query_string_parser(cx, str.start, str.start + str.length,
+ sep.start ? &sep : NULL,
+ eq.start ? &eq : NULL, decode, max_keys);
+
+ JS_FreeValue(cx, decode);
+
+ if (sep.start != NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ }
+
+ if (eq.start != NULL) {
+ JS_FreeCString(cx, (char *) eq.start);
+ }
+
+ JS_FreeCString(cx, (char *) str.start);
+
+ return ret;
+
+fail:
+
+ JS_FreeValue(cx, decode);
+
+ if (sep.start != NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ }
+
+ if (eq.start != NULL) {
+ JS_FreeCString(cx, (char *) eq.start);
+ }
+
+ if (str.start != NULL) {
+ JS_FreeCString(cx, (char *) str.start);
+ }
+
+ return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_query_string_decode(JSContext *cx, const u_char *start, size_t size)
+{
+ u_char *dst;
+ JSValue ret;
+ uint32_t cp;
+ njs_chb_t chain;
+ const u_char *p, *end;
+ njs_unicode_decode_t ctx;
+
+ static const int8_t hex[256]
+ njs_aligned(32) =
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ NJS_CHB_CTX_INIT(&chain, cx);
+ njs_utf8_decode_init(&ctx);
+
+ cp = 0;
+
+ p = start;
+ end = p + size;
+
+ while (p < end) {
+ if (*p == '%' && end - p > 2 && hex[p[1]] >= 0 && hex[p[2]] >= 0) {
+ cp = njs_utf8_consume(&ctx, (hex[p[1]] << 4) | hex[p[2]]);
+ p += 3;
+
+ } else {
+ if (*p == '+') {
+ cp = ' ';
+ p++;
+
+ } else {
+ cp = njs_utf8_decode(&ctx, &p, end);
+ }
+ }
+
+ if (cp > NJS_UNICODE_MAX_CODEPOINT) {
+ if (cp == NJS_UNICODE_CONTINUE) {
+ continue;
+ }
+
+ cp = NJS_UNICODE_REPLACEMENT;
+ }
+
+ dst = njs_chb_reserve(&chain, 4);
+ if (dst == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return JS_EXCEPTION;
+ }
+
+ njs_chb_written(&chain, njs_utf8_encode(dst, cp) - dst);
+ }
+
+ if (cp == NJS_UNICODE_CONTINUE) {
+ dst = njs_chb_reserve(&chain, 3);
+ if (dst == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return JS_EXCEPTION;
+ }
+
+ njs_chb_written(&chain,
+ njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst);
+ }
+
+
+ ret = qjs_string_create_chb(cx, &chain);
+
+ njs_chb_destroy(&chain);
+
+ return ret;
+}
+
+
+static JSValue
+qjs_query_string_escape(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue ret;
+ njs_str_t str;
+ njs_chb_t chain;
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]);
+ if (str.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ NJS_CHB_CTX_INIT(&chain, cx);
+
+ if (qjs_query_string_encode(&chain, &str) < 0) {
+ JS_FreeCString(cx, (char *) str.start);
+ njs_chb_destroy(&chain);
+ return JS_EXCEPTION;
+ }
+
+ ret = qjs_string_create_chb(cx, &chain);
+
+ njs_chb_destroy(&chain);
+
+ JS_FreeCString(cx, (char *) str.start);
+
+ return ret;
+}
+
+
+static JSValue
+qjs_query_string_unescape(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ u_char *p, *end;
+ JSValue ret;
+ njs_str_t str;
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]);
+ if (str.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ p = str.start;
+ end = p + str.length;
+
+ ret = qjs_query_string_decode(cx, p, end - p);
+
+ JS_FreeCString(cx, (char *) str.start);
+
+ return ret;
+}
+
+
+static u_char *
+qjs_query_string_match(u_char *p, u_char *end, const njs_str_t *v)
+{
+ size_t length;
+
+ length = v->length;
+
+ if (length == 1) {
+ p = njs_strlchr(p, end, v->start[0]);
+
+ if (p == NULL) {
+ p = end;
+ }
+
+ return p;
+ }
+
+ while (p <= (end - length)) {
+ if (memcmp(p, v->start, length) == 0) {
+ return p;
+ }
+
+ p++;
+ }
+
+ return end;
+}
+
+
+static int
+qjs_query_string_append(JSContext *cx, JSValue object, const u_char *key,
+ size_t key_size, const u_char *val, size_t val_size, JSValue decoder)
+{
+ JSAtom prop;
+ JSValue name, value, prev, length, ret;
+ uint32_t len;
+
+ if (JS_IsNullOrUndefined(decoder)) {
+ name = qjs_query_string_decode(cx, key, key_size);
+ if (JS_IsException(name)) {
+ return -1;
+ }
+
+ value = qjs_query_string_decode(cx, val, val_size);
+ if (JS_IsException(value)) {
+ JS_FreeValue(cx, name);
+ return -1;
+ }
+
+ } else {
+ name = JS_NewStringLen(cx, (const char *) key, key_size);
+ if (JS_IsException(name)) {
+ return -1;
+ }
+
+ ret = JS_Call(cx, decoder, JS_UNDEFINED, 1, &name);
+ JS_FreeValue(cx, name);
+ if (JS_IsException(ret)) {
+ return -1;
+ }
+
+ name = ret;
+
+ value = JS_NewStringLen(cx, (const char *) val, val_size);
+ if (JS_IsException(value)) {
+ return -1;
+ }
+
+ ret = JS_Call(cx, decoder, JS_UNDEFINED, 1, &value);
+ JS_FreeValue(cx, value);
+ if (JS_IsException(ret)) {
+ JS_FreeValue(cx, name);
+ return -1;
+ }
+
+ value = ret;
+ }
+
+ prop = JS_ValueToAtom(cx, name);
+ JS_FreeValue(cx, name);
+ if (prop == JS_ATOM_NULL) {
+ JS_FreeValue(cx, value);
+ return -1;
+ }
+
+ prev = JS_GetProperty(cx, object, prop);
+ if (JS_IsException(prev)) {
+ JS_FreeAtom(cx, prop);
+ JS_FreeValue(cx, value);
+ return -1;
+ }
+
+ if (JS_IsUndefined(prev)) {
+ if (JS_SetProperty(cx, object, prop, value) < 0) {
+ goto exception;
+ }
+
+ } else if (JS_IsArray(cx, prev)) {
+ length = JS_GetPropertyStr(cx, prev, "length");
+
+ if (JS_ToUint32(cx, &len, length) < 0) {
+ JS_FreeValue(cx, length);
+ goto exception;
+ }
+
+ JS_FreeValue(cx, length);
+
+ if (JS_SetPropertyUint32(cx, prev, len, value) < 0) {
+ goto exception;
+ }
+
+ JS_FreeValue(cx, prev);
+
+ } else {
+ ret = JS_NewArray(cx);
+ if (JS_IsException(ret)) {
+ goto exception;
+ }
+
+ if (JS_SetPropertyUint32(cx, ret, 0, prev) < 0) {
+ JS_FreeValue(cx, ret);
+ goto exception;
+ }
+
+ prev = JS_UNDEFINED;
+
+ if (JS_SetPropertyUint32(cx, ret, 1, value) < 0) {
+ JS_FreeValue(cx, ret);
+ goto exception;
+ }
+
+ value = JS_UNDEFINED;
+
+ if (JS_SetProperty(cx, object, prop, ret) < 0) {
+ JS_FreeValue(cx, ret);
+ goto exception;
+ }
+ }
+
+ JS_FreeAtom(cx, prop);
+
+ return 0;
+
+exception:
+
+ JS_FreeAtom(cx, prop);
+ JS_FreeValue(cx, prev);
+ JS_FreeValue(cx, value);
+
+ return -1;
+}
+
+
+static JSValue
+qjs_query_string_parser(JSContext *cx, u_char *query, u_char *end,
+ njs_str_t *sep, njs_str_t *eq, JSValue decode, unsigned max_keys)
+{
+ size_t size;
+ u_char *part, *key, *val;
+ JSValue obj;
+ unsigned count;
+ njs_str_t sep_val, eq_val;
+
+ if (sep == NULL || sep->length == 0) {
+ sep = &sep_val;
+ sep->start = (u_char *) "&";
+ sep->length = 1;
+ }
+
+ if (eq == NULL || eq->length == 0) {
+ eq = &eq_val;
+ eq->start = (u_char *) "=";
+ eq->length = 1;
+ }
+
+ obj = JS_NewObject(cx);
+ if (JS_IsException(obj)) {
+ return JS_EXCEPTION;
+ }
+
+ count = 0;
+
+ key = query;
+
+ while (key < end) {
+ if (count++ == max_keys) {
+ break;
+ }
+
+ part = qjs_query_string_match(key, end, sep);
+
+ if (part == key) {
+ goto next;
+ }
+
+ val = qjs_query_string_match(key, part, eq);
+
+ size = val - key;
+
+ if (val != part) {
+ val += eq->length;
+ }
+
+ if (qjs_query_string_append(cx, obj, key, size, val, part - val,
+ decode) < 0)
+ {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+next:
+
+ key = part + sep->length;
+ }
+
+ return obj;
+}
+
+
+static inline int
+qjs_need_escape(const uint32_t *escape, uint32_t byte)
+{
+ return ((escape[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0);
+}
+
+
+static inline u_char *
+qjs_string_encode(const uint32_t *escape, size_t size, const u_char *src,
+ u_char *dst)
+{
+ uint8_t byte;
+ static const u_char hex[16] = "0123456789ABCDEF";
+
+ do {
+ byte = *src++;
+
+ if (qjs_need_escape(escape, byte)) {
+ *dst++ = '%';
+ *dst++ = hex[byte >> 4];
+ *dst++ = hex[byte & 0xf];
+
+ } else {
+ *dst++ = byte;
+ }
+
+ size--;
+
+ } while (size != 0);
+
+ return dst;
+}
+
+
+static int
+qjs_query_string_encode(njs_chb_t *chain, njs_str_t *str)
+{
+ size_t size;
+ u_char *p, *start, *end;
+
+ static const uint32_t escape[] = {
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+
+ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
+ 0xfc00987d, /* 1111 1100 0000 0000 1001 1000 0111 1101 */
+
+ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
+ 0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */
+
+ /* ~}| {zyx wvut srqp onml kjih gfed cba` */
+ 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
+
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ };
+
+ if (chain->error) {
+ return -1;
+ }
+
+ if (str->length == 0) {
+ return 0;
+ }
+
+ p = str->start;
+ end = p + str->length;
+
+ size = str->length;
+ while (p < end) {
+ if (qjs_need_escape(escape, *p++)) {
+ size += 2;
+ }
+ }
+
+ start = njs_chb_reserve(chain, size);
+ if (start == NULL) {
+ return -1;
+ }
+
+ if (size == str->length) {
+ memcpy(start, str->start, str->length);
+ njs_chb_written(chain, str->length);
+ return 0;
+ }
+
+ qjs_string_encode(escape, str->length, str->start, start);
+
+ njs_chb_written(chain, size);
+
+ return 0;
+}
+
+
+static inline int
+qjs_query_string_encoder_call(JSContext *cx, njs_chb_t *chain,
+ JSValue encoder, JSValue value)
+{
+ int rc;
+ JSValue ret;
+ njs_str_t str;
+
+ if (JS_IsNullOrUndefined(encoder)) {
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, value);
+ if (str.start == NULL) {
+ return -1;
+ }
+
+ rc = qjs_query_string_encode(chain, &str);
+ JS_FreeCString(cx, (char *) str.start);
+ return rc;
+ }
+
+ ret = JS_Call(cx, encoder, JS_UNDEFINED, 1, &value);
+ if (JS_IsException(ret)) {
+ return -1;
+ }
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, ret);
+ JS_FreeValue(cx, ret);
+ if (str.start == NULL) {
+ return -1;
+ }
+
+ njs_chb_append_str(chain, &str);
+
+ JS_FreeCString(cx, (char *) str.start);
+
+ return 0;
+}
+
+
+static inline int
+qjs_query_string_push(JSContext *cx, njs_chb_t *chain, JSValue key,
+ JSValue value, njs_str_t *eq, JSValue encoder)
+{
+ if (qjs_query_string_encoder_call(cx, chain, encoder, key) < 0) {
+ return -1;
+ }
+
+ njs_chb_append(chain, eq->start, eq->length);
+
+ if (JS_IsNumber(value)
+ || JS_IsBool(value)
+ || JS_IsString(value))
+ {
+ return qjs_query_string_encoder_call(cx, chain, encoder, value);
+ }
+
+ return 0;
+}
+
+
+static inline int
+qjs_query_string_push_array(JSContext *cx, njs_chb_t *chain, JSValue key,
+ JSValue array, njs_str_t *eq, njs_str_t *sep, JSValue encoder)
+{
+ int rc;
+ JSValue val, len;
+ uint32_t i, length;
+
+ len = JS_GetPropertyStr(cx, array, "length");
+ if (JS_IsException(len)) {
+ return -1;
+ }
+
+ if (JS_ToUint32(cx, &length, len) < 0) {
+ JS_FreeValue(cx, len);
+ return -1;
+ }
+
+ JS_FreeValue(cx, len);
+
+ for (i = 0; i < length; i++) {
+ if (chain->last != NULL) {
+ njs_chb_append(chain, sep->start, sep->length);
+ }
+
+ val = JS_GetPropertyUint32(cx, array, i);
+ if (JS_IsException(val)) {
+ return -1;
+ }
+
+ rc = qjs_query_string_push(cx, chain, key, val, eq, encoder);
+ JS_FreeValue(cx, val);
+ if (rc != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+qjs_free_prop_enum(JSContext *cx, JSPropertyEnum *tab, uint32_t len)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++) {
+ JS_FreeAtom(cx, tab[i].atom);
+ }
+
+ js_free(cx, tab);
+}
+
+
+static JSValue
+qjs_query_string_stringify_internal(JSContext *cx, JSValue obj, njs_str_t *sep,
+ njs_str_t *eq, JSValue encoder)
+{
+ int rc;
+ uint32_t n, length;
+ JSValue key, val, ret;
+ njs_str_t sep_val, eq_val;
+ njs_chb_t chain;
+ JSPropertyEnum *ptab;
+
+ if (!JS_IsObject(obj)) {
+ return JS_NewString(cx, "");
+ }
+
+ if (JS_GetOwnPropertyNames(cx, &ptab, &length, obj,
+ JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)
+ < 0)
+ {
+ return JS_EXCEPTION;
+ }
+
+ if (sep == NULL || sep->length == 0) {
+ sep = &sep_val;
+ sep->start = (u_char *) "&";
+ sep->length = 1;
+ }
+
+ if (eq == NULL || eq->length == 0) {
+ eq = &eq_val;
+ eq->start = (u_char *) "=";
+ eq->length = 1;
+ }
+
+ NJS_CHB_CTX_INIT(&chain, cx);
+
+ for (n = 0; n < length; n++) {
+ val = JS_GetProperty(cx, obj, ptab[n].atom);
+ if (JS_IsException(val)) {
+ goto fail;
+ }
+
+ if (JS_IsArray(cx, val)) {
+ key = JS_AtomToString(cx, ptab[n].atom);
+ if (JS_IsException(key)) {
+ JS_FreeValue(cx, val);
+ goto fail;
+ }
+
+ rc = qjs_query_string_push_array(cx, &chain, key, val, eq, sep,
+ encoder);
+ JS_FreeValue(cx, key);
+ JS_FreeValue(cx, val);
+ if (rc != 0) {
+ goto fail;
+ }
+
+ continue;
+ }
+
+ if (n != 0) {
+ njs_chb_append(&chain, sep->start, sep->length);
+ }
+
+ key = JS_AtomToString(cx, ptab[n].atom);
+ if (JS_IsException(key)) {
+ JS_FreeValue(cx, val);
+ goto fail;
+ }
+
+ rc = qjs_query_string_push(cx, &chain, key, val, eq, encoder);
+ JS_FreeValue(cx, key);
+ JS_FreeValue(cx, val);
+ if (rc != 0) {
+ goto fail;
+ }
+ }
+
+ if (ptab != NULL) {
+ qjs_free_prop_enum(cx, ptab, length);
+ }
+
+ ret = qjs_string_create_chb(cx, &chain);
+
+ njs_chb_destroy(&chain);
+
+ return ret;
+
+fail:
+
+ if (ptab != NULL) {
+ qjs_free_prop_enum(cx, ptab, length);
+ }
+
+ njs_chb_destroy(&chain);
+
+ return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_query_string_stringify(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue options, ret, encode, native;
+ njs_str_t sep, eq;
+
+ sep.start = NULL;
+ eq.start = NULL;
+
+ encode = JS_UNDEFINED;
+
+ if (!JS_IsNullOrUndefined(argv[1])) {
+ sep.start = (u_char *) JS_ToCStringLen(cx, &sep.length, argv[1]);
+ if (sep.start == NULL) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ if (!JS_IsNullOrUndefined(argv[2])) {
+ eq.start = (u_char *) JS_ToCStringLen(cx, &eq.length, argv[2]);
+ if (eq.start == NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ return JS_EXCEPTION;
+ }
+ }
+
+ options = argv[3];
+ if (JS_IsObject(options)) {
+ encode = JS_GetPropertyStr(cx, options, "encodeURIComponent");
+ if (JS_IsException(encode)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(encode) && !JS_IsFunction(cx, encode)) {
+ JS_ThrowTypeError(cx, "option encodeURIComponent is not "
+ "a function");
+ goto fail;
+ }
+ }
+
+ if (JS_IsNullOrUndefined(encode)) {
+ encode = JS_GetPropertyStr(cx, this_val, "escape");
+ if (JS_IsException(encode)) {
+ goto fail;
+ }
+
+ if (!JS_IsFunction(cx, encode)) {
+ JS_ThrowTypeError(cx, "QueryString.escape is not a function");
+ goto fail;
+ }
+
+ native = JS_GetPropertyStr(cx, encode, "native");
+ if (JS_IsException(native)) {
+ goto fail;
+ }
+
+ if (JS_IsBool(native)) {
+ JS_FreeValue(cx, encode);
+ encode = JS_NULL;
+ }
+ }
+
+ ret = qjs_query_string_stringify_internal(cx, argv[0],
+ sep.start ? &sep : NULL,
+ eq.start ? &eq : NULL, encode);
+
+ JS_FreeValue(cx, encode);
+
+ if (sep.start != NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ }
+
+ if (eq.start != NULL) {
+ JS_FreeCString(cx, (char *) eq.start);
+ }
+
+ return ret;
+
+fail:
+
+ JS_FreeValue(cx, encode);
+
+ if (sep.start != NULL) {
+ JS_FreeCString(cx, (char *) sep.start);
+ }
+
+ if (eq.start != NULL) {
+ JS_FreeCString(cx, (char *) eq.start);
+ }
+
+ return JS_EXCEPTION;
+}
+
+
+static int
+qjs_querystring_module_init(JSContext *ctx, JSModuleDef *m)
+{
+ int rc;
+ JSValue proto, method;
+
+ proto = JS_NewObject(ctx);
+ if (JS_IsException(proto)) {
+ return -1;
+ }
+
+ JS_SetPropertyFunctionList(ctx, proto, qjs_querystring_export,
+ njs_nitems(qjs_querystring_export));
+
+ method = JS_GetPropertyStr(ctx, proto, "escape");
+ if (JS_IsException(method)) {
+ return -1;
+ }
+
+ /* Marking the default "escape" function for the fast path. */
+
+ if (JS_SetPropertyStr(ctx, method, "native", JS_NewBool(ctx, 1)) < 0) {
+ JS_FreeValue(ctx, method);
+ return -1;
+ }
+
+ JS_FreeValue(ctx, method);
+
+ method = JS_GetPropertyStr(ctx, proto, "unescape");
+ if (JS_IsException(method)) {
+ return -1;
+ }
+
+ /* Marking the default "unescape" function for the fast path. */
+
+ if (JS_SetPropertyStr(ctx, method, "native", JS_NewBool(ctx, 1)) < 0) {
+ JS_FreeValue(ctx, method);
+ return -1;
+ }
+
+ JS_FreeValue(ctx, method);
+
+ rc = JS_SetModuleExport(ctx, m, "default", proto);
+ if (rc != 0) {
+ return -1;
+ }
+
+ return JS_SetModuleExportList(ctx, m, qjs_querystring_export,
+ njs_nitems(qjs_querystring_export));
+}
+
+
+static JSModuleDef *
+qjs_querystring_init(JSContext *ctx, const char *name)
+{
+ int rc;
+ JSModuleDef *m;
+
+ m = JS_NewCModule(ctx, name, qjs_querystring_module_init);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ JS_AddModuleExport(ctx, m, "default");
+ rc = JS_AddModuleExportList(ctx, m, qjs_querystring_export,
+ njs_nitems(qjs_querystring_export));
+ if (rc != 0) {
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index 81fee436..e99a6d8a 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -20491,354 +20491,6 @@ static njs_unit_test_t njs_crypto_module_test[] =
njs_str("TypeError: \"this\" is not a hash object") },
};
-static njs_unit_test_t njs_querystring_module_test[] =
-{
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=');"
- "njs.dump(obj)"),
- njs_str("{baz:''}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('&baz=fuz');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('&&&&&baz=fuz');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('=fuz');"
- "njs.dump(obj)"),
- njs_str("{:'fuz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('=fuz=');"
- "njs.dump(obj)"),
- njs_str("{:'fuz='}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('===fu=z');"
- "njs.dump(obj)"),
- njs_str("{:'==fu=z'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&baz=tax');"
- "njs.dump(obj)"),
- njs_str("{baz:['fuz','tax']}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('freespace');"
- "njs.dump(obj)"),
- njs_str("{freespace:''}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('name&value=12');"
- "njs.dump(obj)"),
- njs_str("{name:'',value:'12'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', 'fuz');"
- "njs.dump(obj)"),
- njs_str("{baz:'',&muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', '');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', null);"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', undefined);"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz123muz=tax', 123);"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuzαααmuz=tax', 'ααα');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', '=');"
- "njs.dump(obj)"),
- njs_str("{baz:'',fuz&muz:'',tax:''}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', null, 'fuz');"
- "njs.dump(obj)"),
- njs_str("{baz=:'',muz=tax:''}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', null, '&');"
- "njs.dump(obj)"),
- njs_str("{baz=fuz:'',muz=tax:''}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz123fuz&muz123tax', null, 123);"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('bazαααfuz&muzαααtax', null, 'ααα');"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz',muz:'tax'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=fuz&muz=tax', null, null, {maxKeys: 1});"
- "njs.dump(obj)"),
- njs_str("{baz:'fuz'}") },
-
- { njs_str("var qs = require('querystring'); var out = [];"
- "var obj = qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: (key) => {out.push(key)}});"
- "out.join('; ');"),
- njs_str("baz; fuz; muz; tax") },
-
- { njs_str("var qs = require('querystring'); var i = 0;"
- "var obj = qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: (key) => 'α' + i++});"
- "njs.dump(obj);"),
- njs_str("{α0:'α1',α2:'α3'}") },
-
- { njs_str("var qs = require('querystring');"
- "qs.parse('baz=fuz&muz=tax', null, null, {decodeURIComponent: 123});"),
- njs_str("TypeError: option decodeURIComponent is not a function") },
-
- { njs_str("var qs = require('querystring');"
- "qs.unescape = 123;"
- "qs.parse('baz=fuz&muz=tax');"),
- njs_str("TypeError: QueryString.unescape is not a function") },
-
- { njs_str("var qs = require('querystring'); var out = [];"
- "qs.unescape = (key) => {out.push(key)};"
- "qs.parse('baz=fuz&muz=tax');"
- "out.join('; ');"),
- njs_str("baz; fuz; muz; tax") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('ba%32z=f%32uz');"
- "njs.dump(obj)"),
- njs_str("{ba2z:'f2uz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('ba%32z=f%32uz');"
- "njs.dump(obj)"),
- njs_str("{ba2z:'f2uz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('ba%F0%9F%92%A9z=f%F0%9F%92%A9uz');"
- "njs.dump(obj)"),
- njs_str("{ba💩z:'f💩uz'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('======');"
- "njs.dump(obj)"),
- njs_str("{:'====='}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%F0%9F%A9');"
- "njs.dump(obj)"),
- njs_str("{baz:'�'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=αααααα%\x00\x01\x02αααα');"
- "njs.dump(obj)"),
- njs_str("{baz:'αααααα%\\u0000\\u0001\\u0002αααα'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%F6α');"
- "njs.dump(obj)"),
- njs_str("{baz:'�α'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%F6');"
- "njs.dump(obj)"),
- njs_str("{baz:'�'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%FG');"
- "njs.dump(obj)"),
- njs_str("{baz:'%FG'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%F');"
- "njs.dump(obj)"),
- njs_str("{baz:'%F'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('baz=%');"
- "njs.dump(obj)"),
- njs_str("{baz:'%'}") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = qs.parse('ba+z=f+uz');"
- "njs.dump(obj)"),
- njs_str("{ba z:'f uz'}") },
-
-
- { njs_str("var qs = require('querystring');"
- "qs.parse('X='+'α'.repeat(33)).X.length"),
- njs_str("33") },
-
- { njs_str("var qs = require('querystring');"
- "var x = qs.parse('X='+'α1'.repeat(33)).X;"
- "[x.length, x[33], x[34]]"),
- njs_str("66,1,α") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz'})"),
- njs_str("baz=fuz") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'})"),
- njs_str("baz=fuz&muz=tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baαz': 'fαuz', 'muαz': 'tαax'});"),
- njs_str("ba%CE%B1z=f%CE%B1uz&mu%CE%B1z=t%CE%B1ax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': ['fuz', 'tax']})"),
- njs_str("baz=fuz&baz=tax") },
-
- { njs_str("var qs = require('querystring');"
- njs_declare_sparse_array("arr", 2)
- "arr[0] = 0; arr[1] = 1.5;"
- "qs.stringify({'baz': arr})"),
- njs_str("baz=0&baz=1.5") },
-
- { njs_str("var qs = require('querystring'); var out = [];"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, null, {encodeURIComponent: (key) => {out.push(key)}});"
- "out.join('; ')"),
- njs_str("baz; fuz; muz; tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, null, {encodeURIComponent: 123});"
- "out.join('; ')"),
- njs_str("TypeError: option encodeURIComponent is not a function") },
-
- { njs_str("var qs = require('querystring');"
- "qs.escape = 123;"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'})"),
- njs_str("TypeError: QueryString.escape is not a function") },
-
- { njs_str("var qs = require('querystring'); var out = [];"
- "qs.escape = (key) => {out.push(key)};"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'});"
- "out.join('; ')"),
- njs_str("baz; fuz; muz; tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '****')"),
- njs_str("baz=fuz****muz=tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, null, '^^^^')"),
- njs_str("baz^^^^fuz&muz^^^^tax") },
-
- { njs_str("var qs = require('querystring');"
- "var obj = {A:'α'}; obj['δ'] = 'D';"
- "var s = qs.stringify(obj,'γ=','&β'); [s, s.length]"),
- njs_str("A&β%CE%B1γ=%CE%B4&βD,20") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '', '')"),
- njs_str("baz=fuz&muz=tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, undefined, undefined)"),
- njs_str("baz=fuz&muz=tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({'baz': 'fuz', 'muz': 'tax'}, '?', '/')"),
- njs_str("baz/fuz?muz/tax") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify('123')"),
- njs_str("") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify(123)"),
- njs_str("") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({X:{toString(){return 3}}})"),
- njs_str("X=") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify({ name: undefined, age: 12 })"),
- njs_str("name=&age=12") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify(Object.create({ name: undefined, age: 12 }))"),
- njs_str("") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify([])"),
- njs_str("") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify(['','',''])"),
- njs_str("0=&1=&2=") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify([undefined, null, Symbol(), Object(0), Object('test'), Object(false),,,])"),
- njs_str("0=&1=&2=&3=&4=&5=") },
-
-#if 0
- { njs_str("var qs = require('querystring');"
- "qs.stringify([NaN, Infinity, -Infinity, 2**69, 2**70])"),
- njs_str("0=&1=&2=&3=590295810358705700000&4=1.1805916207174113e%2B21") },
-#else
- { njs_str("var qs = require('querystring');"
- "qs.stringify([NaN, Infinity, -Infinity, 2**69, 2**70])"),
- njs_str("0=&1=&2=&3=590295810358705700000&4=1.1805916207174114e%2B21") },
-#endif
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify([[1,2,3],[4,5,6]])"),
- njs_str("0=1&0=2&0=3&1=4&1=5&1=6") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify([['a',,,],['b',,,]])"),
- njs_str("0=a&0=&0=&1=b&1=&1=") },
-
- { njs_str("var qs = require('querystring');"
- "qs.stringify([[,'a','b',,]])"),
- njs_str("0=&0=a&0=b&0=") },
-
- { njs_str("var qs = require('querystring');"
- "qs.escape('abcααααdef')"),
- njs_str("abc%CE%B1%CE%B1%CE%B1%CE%B1def") },
-
- { njs_str("var qs = require('querystring');"
- "qs.unescape('abc%CE%B1%CE%B1%CE%B1%CE%B1def')"),
- njs_str("abcααααdef") },
-};
-
#define NJS_XML_DOC "const xml = require('xml');" \
"let data = `<note><to b=\"bar\" a= \"foo\" >Tove</to><from>Jani</from></note>`;" \
@@ -23918,12 +23570,6 @@ static njs_test_suite_t njs_suites[] =
njs_nitems(njs_crypto_module_test),
njs_unit_test },
- { njs_str("querystring module"),
- { .repeat = 1, .unsafe = 1 },
- njs_querystring_module_test,
- njs_nitems(njs_querystring_module_test),
- njs_unit_test },
-
{ njs_str("externals"),
{ .externals = 1, .repeat = 1, .unsafe = 1 },
njs_externals_test,
diff --git a/test/harness/compareObjects.js b/test/harness/compareObjects.js
index d4a20c15..ea0dad18 100644
--- a/test/harness/compareObjects.js
+++ b/test/harness/compareObjects.js
@@ -4,6 +4,14 @@ function compareObjects(ref, obj) {
}
for (const key in ref) {
+ if (!Object.prototype.hasOwnProperty.call(ref, key)) {
+ continue;
+ }
+
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
+ return false;
+ }
+
if (!compareObjects(ref[key], obj[key])) {
return false;
}
@@ -13,5 +21,5 @@ function compareObjects(ref, obj) {
}
function isObject(object) {
- return object != null && typeof object === 'object';
+ return object !== null && typeof object === 'object';
}
diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs
new file mode 100644
index 00000000..b382d10d
--- /dev/null
+++ b/test/querystring.t.mjs
@@ -0,0 +1,253 @@
+/*---
+includes: [runTsuite.js, compareObjects.js]
+flags: [async]
+---*/
+
+import qs from 'querystring';
+
+let escape_tsuite = {
+ name: "querystring.escape() tests",
+ T: async (params) => {
+ let r = qs.escape(params.value);
+
+ if (r !== params.expected) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+ prepare_args: (args, default_opts) => {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+ },
+ opts: { },
+
+ tests: [
+ { value: '', expected: '' },
+ { value: 'baz=fuz', expected: 'baz%3Dfuz' },
+ { value: 'abcαdef', expected: 'abc%CE%B1def' },
+]};
+
+let parse_tsuite = {
+ name: "querystring.parse() tests",
+ T: async (params) => {
+ let r;
+ let unescape = qs.unescape;
+
+ if (params.unescape) {
+ qs.unescape = params.unescape;
+ }
+
+ try {
+ if (params.options !== undefined) {
+ r = qs.parse(params.value, params.sep, params.eq, params.options);
+
+ } else if (params.eq !== undefined) {
+ r = qs.parse(params.value, params.sep, params.eq);
+
+ } else if (params.sep !== undefined) {
+ r = qs.parse(params.value, params.sep);
+
+ } else {
+ r = qs.parse(params.value);
+ }
+
+ } finally {
+ if (params.unescape) {
+ qs.unescape = unescape;
+ }
+ }
+
+ if (!compareObjects(r, params.expected)) {
+ throw Error(`unexpected output "${JSON.stringify(r)}" != "${JSON.stringify(params.expected)}"`);
+ }
+
+ return 'SUCCESS';
+ },
+ prepare_args: (args, default_opts) => {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+ },
+ opts: { },
+
+ tests: [
+ { value: '', expected: {} },
+ { value: 'baz=fuz', expected: { baz:'fuz' } },
+ { value: 'baz=fuz', expected: { baz:'fuz' } },
+ { value: 'baz=fuz&', expected: { baz:'fuz' } },
+ { value: '&baz=fuz', expected: { baz:'fuz' } },
+ { value: '&&baz=fuz', expected: { baz:'fuz' } },
+ { value: 'baz=fuz&muz=tax', expected: { baz:'fuz', muz:'tax' } },
+ { value: 'baz=fuz&baz=bar', expected: { baz:['fuz', 'bar'] } },
+
+ { value: qs.encode({ baz:'fuz', muz:'tax' }), expected: { baz:'fuz', muz:'tax' } },
+
+ { value: 'baz=fuz&baz=bar', sep: '&', eq: '=', expected: { baz:['fuz', 'bar'] } },
+ { value: 'baz=fuz&baz=bar&baz=zap', expected: { baz:['fuz', 'bar', 'zap'] } },
+ { value: 'baz=', expected: { baz:'' } },
+ { value: '=fuz', expected: { '':'fuz' } },
+ { value: '=fuz=', expected: { '':'fuz=' } },
+ { value: '==fu=z', expected: { '':'=fu=z' } },
+ { value: '===fu=z&baz=bar', expected: { baz:'bar', '':'==fu=z' } },
+ { value: 'freespace', expected: { freespace:'' } },
+ { value: 'name&value=12', expected: { name:'', value:'12' } },
+ { value: 'baz=fuz&muz=tax', sep: 'fuz', expected: { baz:'', '&muz':'tax' } },
+ { value: 'baz=fuz&muz=tax', sep: '', expected: { baz:'fuz', 'muz':'tax' } },
+ { value: 'baz=fuz&muz=tax', sep: null, expected: { baz:'fuz', 'muz':'tax' } },
+ { value: 'baz=fuz123muz=tax', sep: 123, expected: { baz:'fuz', 'muz':'tax' } },
+ { value: 'baz=fuzαααmuz=tax', sep: 'ααα', expected: { baz:'fuz', 'muz':'tax' } },
+ { value: 'baz=fuz&muz=tax', sep: '=', expected: { baz:'', 'fuz&muz':'', 'tax':'' } },
+
+ { value: 'baz=fuz&muz=tax', sep: '', eq: '', expected: { baz:'fuz', muz:'tax' } },
+ { value: 'baz=fuz&muz=tax', sep: null, eq: 'fuz', expected: { 'baz=':'','muz=tax':'' } },
+ { value: 'baz123fuz&muz123tax', sep: null, eq: '123', expected: { baz:'fuz', 'muz':'tax' } },
+ { value: 'bazαααfuz&muzαααtax', sep: null, eq: 'ααα', expected: { baz:'fuz', 'muz':'tax' } },
+
+ { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: 1 }, expected: { baz:'fuz' } },
+ { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: -1 },
+ expected: { baz:'fuz', muz:'tax' } },
+ { value: 'baz=fuz&muz=tax', sep: null, eq: null, options: { maxKeys: { valueOf: () => { throw 'Oops'; } }},
+ exception: 'Oops' },
+ { value: 'baz=fuz&muz=tax', sep: null, eq: null,
+ options: { decodeURIComponent: (s) => `|${s}|` }, expected: { '|baz|':'|fuz|', '|muz|':'|tax|' } },
+ { value: 'baz=fuz&muz=tax', sep: null, eq: null,
+ options: { decodeURIComponent: 123 },
+ exception: 'TypeError: option decodeURIComponent is not a function' },
+ { value: 'baz=fuz&muz=tax', unescape: (s) => `|${s}|`, expected: { '|baz|':'|fuz|', '|muz|':'|tax|' } },
+ { value: 'baz=fuz&muz=tax', unescape: 123,
+ exception: 'TypeError: QueryString.unescape is not a function' },
+
+ { value: 'ba%32z=f%32uz', expected: { ba2z:'f2uz' } },
+ { value: 'ba%F0%9F%92%A9z=f%F0%9F%92%A9uz', expected: { 'ba💩z':'f💩uz' } },
+ { value: '==', expected: { '':'=' } },
+ { value: 'baz=%F0%9F%A9', expected: { baz:'�' } },
+ { value: 'baz=α%00%01%02α', expected: { baz:'α' + String.fromCharCode(0, 1, 2) + 'α' } },
+ { value: 'baz=%F6', expected: { baz:'�' } },
+ { value: 'baz=%FG', expected: { baz:'%FG' } },
+ { value: 'baz=%F', expected: { baz:'%F' } },
+ { value: 'baz=%', expected: { baz:'%' } },
+ { value: 'ba+z=f+uz', expected: { 'ba z':'f uz' } },
+ { value: 'X=' + 'α'.repeat(33), expected: { X:'α'.repeat(33) } },
+ { value: 'X=' + 'α1'.repeat(33), expected: { X:'α1'.repeat(33) } },
+
+ { value: {toString: () => { throw 'Oops'; }}, sep: "&", eq: "=",
+ exception: 'TypeError: Cannot convert object to primitive value' },
+]};
+
+let stringify_tsuite = {
+ name: "querystring.stringify() tests",
+ T: async (params) => {
+ let r;
+ let escape = qs.escape;
+
+ if (params.escape) {
+ qs.escape = params.escape;
+ }
+
+ try {
+ if (params.options !== undefined) {
+ r = qs.stringify(params.obj, params.sep, params.eq, params.options);
+
+ } else if (params.eq !== undefined) {
+ r = qs.stringify(params.obj, params.sep, params.eq);
+
+ } else if (params.sep !== undefined) {
+ r = qs.stringify(params.obj, params.sep);
+
+ } else {
+ r = qs.stringify(params.obj);
+ }
+
+ } finally {
+ if (params.escape) {
+ qs.escape = escape;
+ }
+ }
+
+ if (r !== params.expected) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+ prepare_args: (args, default_opts) => {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+ },
+ opts: { },
+
+ tests: [
+ { obj: {}, expected: '' },
+ { obj: { baz:'fuz', muz:'tax' }, expected: 'baz=fuz&muz=tax' },
+ { obj: { baz:['fuz', 'tax'] }, expected: 'baz=fuz&baz=tax' },
+ { obj: {'baαz': 'fαuz', 'muαz': 'tαax' }, expected: 'ba%CE%B1z=f%CE%B1uz&mu%CE%B1z=t%CE%B1ax' },
+ { obj: {A:'α', 'δ': 'D' }, expected: 'A=%CE%B1&%CE%B4=D' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: '*', expected: 'baz=fuz*muz=tax' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: '^', expected: 'baz^fuz&muz^tax' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: '', eq: '', expected: 'baz=fuz&muz=tax' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: '?', eq: '/', expected: 'baz/fuz?muz/tax' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: null, options: { encodeURIComponent: (key) => `|${key}|` },
+ expected: '|baz|=|fuz|&|muz|=|tax|' },
+ { obj: { baz:'fuz', muz:'tax' }, sep: null, eq: null, options: { encodeURIComponent: 123 },
+ exception: 'TypeError: option encodeURIComponent is not a function' },
+ { obj: { baz:'fuz', muz:'tax' }, escape: (key) => `|${key}|`, expected: '|baz|=|fuz|&|muz|=|tax|' },
+ { obj: { '':'' }, escape: (s) => s.length == 0 ? '#' : s, expected: '#=#' },
+ { obj: { baz:'fuz', muz:'tax' }, escape: 123,
+ exception: 'TypeError: QueryString.escape is not a function' },
+
+ { obj: qs.decode('baz=fuz&muz=tax'), expected: 'baz=fuz&muz=tax' },
+
+ { obj: '123', expected: '' },
+ { obj: 123, expected: '' },
+ { obj: { baz:'fuz' }, expected: 'baz=fuz' },
+ { obj: { baz:undefined }, expected: 'baz=' },
+ { obj: Object.create({ baz:'fuz' }), expected: '' },
+ { obj: [], expected: '' },
+ { obj: ['a'], expected: '0=a' },
+ { obj: ['a', 'b'], expected: '0=a&1=b' },
+ { obj: ['', ''], expected: '0=&1=' },
+ { obj: [undefined, null, Symbol(), Object(0), Object('test'), Object(false),,,],
+ expected: '0=&1=&2=&3=&4=&5=' },
+ { obj: [['a', 'b'], ['c', 'd']], expected: '0=a&0=b&1=c&1=d' },
+ { obj: [['a',,,], ['b',,,]], expected: '0=a&0=&0=&1=b&1=&1=' },
+ { obj: [[,'a','b',,]], expected: '0=&0=a&0=b&0=' },
+]};
+
+let unescape_tsuite = {
+ name: "querystring.unescape() tests",
+ T: async (params) => {
+ let r = qs.unescape(params.value);
+
+ if (r !== params.expected) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+ prepare_args: (args, default_opts) => {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+ },
+ opts: { },
+
+ tests: [
+ { value: '', expected: '' },
+ { value: 'baz%3Dfuz', expected: 'baz=fuz' },
+ { value: 'abc%CE%B1def', expected: 'abcαdef' },
+]};
+
+run([
+ escape_tsuite,
+ parse_tsuite,
+ stringify_tsuite,
+ unescape_tsuite,
+])
+.then($DONE, $DONE);
More information about the nginx-devel
mailing list