Coverage Report

Created: 2025-06-20 06:23

/src/njs/external/njs_query_string_module.c
Line
Count
Source (jump to first uncovered line)
1
2
/*
3
 * Copyright (C) Alexander Borisov
4
 * Copyright (C) Dmitry Volyntsev
5
 * Copyright (C) NGINX, Inc.
6
 */
7
8
9
#include <njs.h>
10
#include <njs_string.h>
11
12
13
static njs_int_t njs_query_string_parser(njs_vm_t *vm, u_char *query,
14
    u_char *end, const njs_str_t *sep, const njs_str_t *eq,
15
    njs_function_t *decode, njs_uint_t max_keys, njs_value_t *retval);
16
static njs_int_t njs_query_string_parse(njs_vm_t *vm, njs_value_t *args,
17
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
18
static njs_int_t njs_query_string_stringify(njs_vm_t *vm, njs_value_t *args,
19
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
20
static njs_int_t njs_query_string_escape(njs_vm_t *vm, njs_value_t *args,
21
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
22
static njs_int_t njs_query_string_unescape(njs_vm_t *vm, njs_value_t *args,
23
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
24
25
static njs_int_t njs_query_string_init(njs_vm_t *vm);
26
27
28
static njs_external_t  njs_ext_query_string[] = {
29
30
    {
31
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
32
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
33
        .u.property = {
34
            .value = "querystring",
35
        }
36
    },
37
38
    {
39
        .flags = NJS_EXTERN_METHOD,
40
        .name.string = njs_str("parse"),
41
        .writable = 1,
42
        .configurable = 1,
43
        .u.method = {
44
            .native = njs_query_string_parse,
45
            .magic8 = 0,
46
        }
47
    },
48
49
    {
50
        .flags = NJS_EXTERN_METHOD,
51
        .name.string = njs_str("stringify"),
52
        .writable = 1,
53
        .configurable = 1,
54
        .u.method = {
55
            .native = njs_query_string_stringify,
56
            .magic8 = 0,
57
        }
58
    },
59
60
    {
61
        .flags = NJS_EXTERN_METHOD,
62
        .name.string = njs_str("decode"),
63
        .writable = 1,
64
        .configurable = 1,
65
        .u.method = {
66
            .native = njs_query_string_parse,
67
            .magic8 = 0,
68
        }
69
    },
70
71
    {
72
        .flags = NJS_EXTERN_METHOD,
73
        .name.string = njs_str("encode"),
74
        .writable = 1,
75
        .configurable = 1,
76
        .u.method = {
77
            .native = njs_query_string_stringify,
78
            .magic8 = 0,
79
        }
80
    },
81
82
    {
83
        .flags = NJS_EXTERN_METHOD,
84
        .name.string = njs_str("escape"),
85
        .writable = 1,
86
        .configurable = 1,
87
        .u.method = {
88
            .native = njs_query_string_escape,
89
            .magic8 = 0,
90
        }
91
    },
92
93
    {
94
        .flags = NJS_EXTERN_METHOD,
95
        .name.string = njs_str("unescape"),
96
        .writable = 1,
97
        .configurable = 1,
98
        .u.method = {
99
            .native = njs_query_string_unescape,
100
            .magic8 = 0,
101
        }
102
    },
103
};
104
105
106
njs_module_t  njs_query_string_module = {
107
    .name = njs_str("querystring"),
108
    .preinit = NULL,
109
    .init = njs_query_string_init,
110
};
111
112
113
static const njs_str_t  njs_escape_str = njs_str("escape");
114
static const njs_str_t  njs_unescape_str = njs_str("unescape");
115
static const njs_str_t  njs_encode_uri_str = njs_str("encodeURIComponent");
116
static const njs_str_t  njs_decode_uri_str = njs_str("decodeURIComponent");
117
static const njs_str_t  njs_max_keys_str = njs_str("maxKeys");
118
119
static const njs_str_t njs_sep_default = njs_str("&");
120
static const njs_str_t njs_eq_default = njs_str("=");
121
122
123
static njs_int_t
124
njs_query_string_decode(njs_vm_t *vm, njs_value_t *value, const u_char *start,
125
    size_t size)
126
0
{
127
0
    u_char                *dst;
128
0
    uint32_t              cp;
129
0
    njs_int_t             ret;
130
0
    njs_chb_t             chain;
131
0
    const u_char          *p, *end;
132
0
    njs_unicode_decode_t  ctx;
133
134
0
    static const int8_t  hex[256]
135
0
        njs_aligned(32) =
136
0
    {
137
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
138
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
139
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
140
0
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
141
0
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
142
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
143
0
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
144
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
145
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
146
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
147
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
148
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
149
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
150
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
151
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
152
0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
153
0
    };
154
155
0
    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
156
0
    njs_utf8_decode_init(&ctx);
157
158
0
    cp = 0;
159
160
0
    p = start;
161
0
    end = p + size;
162
163
0
    while (p < end) {
164
0
        if (*p == '%' && end - p > 2 && hex[p[1]] >= 0 && hex[p[2]] >= 0) {
165
0
            cp = njs_utf8_consume(&ctx, (hex[p[1]] << 4) | hex[p[2]]);
166
0
            p += 3;
167
168
0
        } else {
169
0
            if (*p == '+') {
170
0
                cp = ' ';
171
0
                p++;
172
173
0
            } else {
174
0
                cp = njs_utf8_decode(&ctx, &p, end);
175
0
            }
176
0
        }
177
178
0
        if (cp > NJS_UNICODE_MAX_CODEPOINT) {
179
0
            if (cp == NJS_UNICODE_CONTINUE) {
180
0
                continue;
181
0
            }
182
183
0
            cp = NJS_UNICODE_REPLACEMENT;
184
0
        }
185
186
0
        dst = njs_chb_reserve(&chain, 4);
187
0
        if (njs_slow_path(dst == NULL)) {
188
0
            return NJS_ERROR;
189
0
        }
190
191
0
        njs_chb_written(&chain, njs_utf8_encode(dst, cp) - dst);
192
0
    }
193
194
0
    if (njs_slow_path(cp == NJS_UNICODE_CONTINUE)) {
195
0
        dst = njs_chb_reserve(&chain, 3);
196
0
        if (njs_slow_path(dst == NULL)) {
197
0
            return NJS_ERROR;
198
0
        }
199
200
0
        njs_chb_written(&chain,
201
0
                        njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst);
202
0
    }
203
204
0
    ret = njs_vm_value_string_create_chb(vm, value, &chain);
205
206
0
    njs_chb_destroy(&chain);
207
208
0
    return ret;
209
0
}
210
211
212
njs_inline njs_bool_t
213
njs_query_string_is_native_decoder(njs_function_t *decoder)
214
0
{
215
0
    njs_opaque_value_t     function;
216
0
    njs_function_native_t  native;
217
218
0
    if (decoder == NULL) {
219
0
        return 1;
220
0
    }
221
222
0
    njs_value_function_set(njs_value_arg(&function), decoder);
223
224
0
    native = njs_value_native_function(njs_value_arg(&function));
225
226
0
    return native == njs_query_string_unescape;
227
0
}
228
229
230
njs_inline njs_int_t
231
njs_query_string_append(njs_vm_t *vm, njs_value_t *object, const u_char *key,
232
    size_t key_size, const u_char *val, size_t val_size,
233
    njs_function_t *decoder)
234
0
{
235
0
    njs_int_t           ret;
236
0
    njs_value_t         *push;
237
0
    njs_opaque_value_t  array, name, value, retval;
238
239
0
    if (njs_query_string_is_native_decoder(decoder)) {
240
0
        ret = njs_query_string_decode(vm, njs_value_arg(&name), key, key_size);
241
0
        if (njs_slow_path(ret != NJS_OK)) {
242
0
            return ret;
243
0
        }
244
245
0
        ret = njs_query_string_decode(vm, njs_value_arg(&value), val, val_size);
246
0
        if (njs_slow_path(ret != NJS_OK)) {
247
0
            return ret;
248
0
        }
249
250
0
    } else {
251
252
0
        ret = njs_vm_value_string_create(vm, njs_value_arg(&name), key,
253
0
                                         key_size);
254
0
        if (njs_slow_path(ret != NJS_OK)) {
255
0
            return ret;
256
0
        }
257
258
0
        if (key_size > 0) {
259
0
            ret = njs_vm_invoke(vm, decoder, njs_value_arg(&name), 1,
260
0
                                njs_value_arg(&name));
261
0
            if (njs_slow_path(ret != NJS_OK)) {
262
0
                return ret;
263
0
            }
264
265
0
            if (!njs_value_is_string(njs_value_arg(&name))) {
266
0
                ret = njs_value_to_string(vm, njs_value_arg(&name),
267
0
                                          njs_value_arg(&name));
268
0
                if (njs_slow_path(ret != NJS_OK)) {
269
0
                    return ret;
270
0
                }
271
0
            }
272
0
        }
273
274
0
        ret = njs_vm_value_string_create(vm, njs_value_arg(&value), val,
275
0
                                         val_size);
276
0
        if (njs_slow_path(ret != NJS_OK)) {
277
0
            return ret;
278
0
        }
279
280
0
        if (val_size > 0) {
281
0
            ret = njs_vm_invoke(vm, decoder, njs_value_arg(&value), 1,
282
0
                                njs_value_arg(&value));
283
0
            if (njs_slow_path(ret != NJS_OK)) {
284
0
                return ret;
285
0
            }
286
287
0
            if (!njs_value_is_string(njs_value_arg(&value))) {
288
0
                ret = njs_value_to_string(vm, njs_value_arg(&value),
289
0
                                          njs_value_arg(&value));
290
0
                if (njs_slow_path(ret != NJS_OK)) {
291
0
                    return ret;
292
0
                }
293
0
            }
294
0
        }
295
0
    }
296
297
0
    ret = njs_value_property_val(vm, object, njs_value_arg(&name),
298
0
                                 njs_value_arg(&retval));
299
300
0
    if (ret == NJS_OK) {
301
0
        if (njs_value_is_array(njs_value_arg(&retval))) {
302
0
            push = njs_vm_array_push(vm, njs_value_arg(&retval));
303
0
            if (njs_slow_path(push == NULL)) {
304
0
                return NJS_ERROR;
305
0
            }
306
307
0
            njs_value_assign(push, njs_value_arg(&value));
308
309
0
            return NJS_OK;
310
0
        }
311
312
0
        ret = njs_vm_array_alloc(vm, njs_value_arg(&array), 2);
313
0
        if (njs_slow_path(ret != NJS_OK)) {
314
0
            return ret;
315
0
        }
316
317
0
        push = njs_vm_array_push(vm, njs_value_arg(&array));
318
0
        if (njs_slow_path(push == NULL)) {
319
0
            return NJS_ERROR;
320
0
        }
321
322
0
        njs_value_assign(push, njs_value_arg(&retval));
323
324
0
        push = njs_vm_array_push(vm, njs_value_arg(&array));
325
0
        if (njs_slow_path(push == NULL)) {
326
0
            return NJS_ERROR;
327
0
        }
328
329
0
        njs_value_assign(push, njs_value_arg(&value));
330
331
0
        njs_value_assign(&value, &array);
332
0
    }
333
334
0
    return njs_value_property_val_set(vm, object, njs_value_arg(&name),
335
0
                                      njs_value_arg(&value));
336
0
}
337
338
339
static u_char *
340
njs_query_string_match(u_char *p, u_char *end, const njs_str_t *v)
341
0
{
342
0
    size_t  length;
343
344
0
    length = v->length;
345
346
0
    if (njs_fast_path(length == 1)) {
347
0
        p = njs_strlchr(p, end, v->start[0]);
348
349
0
        if (p == NULL) {
350
0
            p = end;
351
0
        }
352
353
0
        return p;
354
0
    }
355
356
0
    while (p <= (end - length)) {
357
0
        if (memcmp(p, v->start, length) == 0) {
358
0
            return p;
359
0
        }
360
361
0
        p++;
362
0
    }
363
364
0
    return end;
365
0
}
366
367
368
static njs_int_t
369
njs_query_string_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
370
    njs_index_t unused, njs_value_t *retval)
371
0
{
372
0
    int64_t             max_keys;
373
0
    njs_int_t           ret;
374
0
    njs_str_t           str, sep, eq;
375
0
    njs_value_t         *this, *string, *options, *arg, *val;
376
0
    njs_function_t      *decode;
377
0
    njs_opaque_value_t  value, val_sep, val_eq;
378
379
0
    decode = NULL;
380
0
    max_keys = 1000;
381
382
0
    this = njs_argument(args, 0);
383
0
    string = njs_arg(args, nargs, 1);
384
385
0
    if (njs_value_is_string(string)) {
386
0
        njs_value_string_get(vm, string, &str);
387
388
0
    } else {
389
0
        str = njs_str_value("");
390
0
    }
391
392
0
    sep = njs_sep_default;
393
0
    eq = njs_eq_default;
394
395
0
    arg = njs_arg(args, nargs, 2);
396
0
    if (!njs_value_is_null_or_undefined(arg)) {
397
0
        ret = njs_value_to_string(vm, njs_value_arg(&val_sep), arg);
398
0
        if (njs_slow_path(ret != NJS_OK)) {
399
0
            return ret;
400
0
        }
401
402
0
        if (njs_value_string_length(vm, njs_value_arg(&val_sep)) != 0) {
403
0
            njs_value_string_get(vm, njs_value_arg(&val_sep), &sep);
404
0
        }
405
0
    }
406
407
0
    arg = njs_arg(args, nargs, 3);
408
0
    if (!njs_value_is_null_or_undefined(arg)) {
409
0
        ret = njs_value_to_string(vm, njs_value_arg(&val_eq), arg);
410
0
        if (njs_slow_path(ret != NJS_OK)) {
411
0
            return ret;
412
0
        }
413
414
0
        if (njs_value_string_length(vm, njs_value_arg(&val_eq)) != 0) {
415
0
            njs_value_string_get(vm, njs_value_arg(&val_eq), &eq);
416
0
        }
417
0
    }
418
419
0
    options = njs_arg(args, nargs, 4);
420
421
0
    if (njs_value_is_object(options)) {
422
0
        val = njs_vm_object_prop(vm, options, &njs_max_keys_str, &value);
423
424
0
        if (val != NULL) {
425
0
            if (!njs_value_is_valid_number(val)) {
426
0
                njs_vm_type_error(vm, "is not a number");
427
0
                return NJS_ERROR;
428
0
            }
429
430
0
            max_keys = njs_value_number(val);
431
432
0
            if (max_keys == 0) {
433
0
                max_keys = INT64_MAX;
434
0
            }
435
0
        }
436
437
0
        val = njs_vm_object_prop(vm, options, &njs_decode_uri_str, &value);
438
439
0
        if (val != NULL) {
440
0
            if (njs_slow_path(!njs_value_is_function(val))) {
441
0
                njs_vm_type_error(vm, "option decodeURIComponent is not "
442
0
                                  "a function");
443
0
                return NJS_ERROR;
444
0
            }
445
446
0
            decode = njs_value_function(val);
447
0
        }
448
0
    }
449
450
0
    if (decode == NULL) {
451
0
        val = njs_vm_object_prop(vm, this, &njs_unescape_str, &value);
452
453
0
        if (val == NULL || !njs_value_is_function(val)) {
454
0
            njs_vm_type_error(vm, "QueryString.unescape is not a function");
455
0
            return NJS_ERROR;
456
0
        }
457
458
0
        decode = njs_value_function(val);
459
0
    }
460
461
0
    return njs_query_string_parser(vm, str.start, str.start + str.length,
462
0
                                   &sep, &eq, decode, max_keys, retval);
463
0
}
464
465
466
njs_int_t
467
njs_vm_query_string_parse(njs_vm_t *vm, u_char *start, u_char *end,
468
    njs_value_t *retval)
469
0
{
470
0
    return njs_query_string_parser(vm, start, end, &njs_sep_default,
471
0
                                   &njs_eq_default, NULL, 1000, retval);
472
0
}
473
474
475
static njs_int_t
476
njs_query_string_parser(njs_vm_t *vm, u_char *query, u_char *end,
477
    const njs_str_t *sep, const njs_str_t *eq, njs_function_t *decode,
478
    njs_uint_t max_keys, njs_value_t *retval)
479
0
{
480
0
    size_t      size;
481
0
    u_char      *part, *key, *val;
482
0
    njs_int_t   ret;
483
0
    njs_uint_t  count;
484
485
0
    ret = njs_vm_object_alloc(vm, retval, NULL);
486
0
    if (njs_slow_path(ret != NJS_OK)) {
487
0
        return NJS_ERROR;
488
0
    }
489
490
0
    count = 0;
491
492
0
    key = query;
493
494
0
    while (key < end) {
495
0
        if (count++ == max_keys) {
496
0
            break;
497
0
        }
498
499
0
        part = njs_query_string_match(key, end, sep);
500
501
0
        if (part == key) {
502
0
            goto next;
503
0
        }
504
505
0
        val = njs_query_string_match(key, part, eq);
506
507
0
        size = val - key;
508
509
0
        if (val != part) {
510
0
            val += eq->length;
511
0
        }
512
513
0
        ret = njs_query_string_append(vm, retval, key, size, val, part - val,
514
0
                                      decode);
515
0
        if (njs_slow_path(ret != NJS_OK)) {
516
0
            return ret;
517
0
        }
518
519
0
    next:
520
521
0
        key = part + sep->length;
522
0
    }
523
524
0
    return NJS_OK;
525
0
}
526
527
528
njs_inline njs_int_t
529
njs_query_string_encode(njs_chb_t *chain, njs_str_t *str)
530
0
{
531
0
    size_t  size;
532
0
    u_char  *p, *start, *end;
533
534
0
    static const uint32_t  escape[] = {
535
0
        0xffffffff,  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
536
537
                     /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
538
0
        0xfc00987d,  /* 1111 1100 0000 0000  1001 1000 0111 1101 */
539
540
                     /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
541
0
        0x78000001,  /* 0111 1000 0000 0000  0000 0000 0000 0001 */
542
543
                     /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
544
0
        0xb8000001,  /* 1011 1000 0000 0000  0000 0000 0000 0001 */
545
546
0
        0xffffffff,  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
547
0
        0xffffffff,  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
548
0
        0xffffffff,  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
549
0
        0xffffffff,  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
550
0
    };
551
552
0
    if (chain->error) {
553
0
        return NJS_ERROR;
554
0
    }
555
556
0
    if (str->length == 0) {
557
0
        return NJS_OK;
558
0
    }
559
560
0
    p = str->start;
561
0
    end = p + str->length;
562
0
    size = str->length;
563
564
0
    while (p < end) {
565
0
        if (njs_need_escape(escape, *p++)) {
566
0
            size += 2;
567
0
        }
568
0
    }
569
570
0
    start = njs_chb_reserve(chain, size);
571
0
    if (njs_slow_path(start == NULL)) {
572
0
        return NJS_ERROR;
573
0
    }
574
575
0
    if (size == str->length) {
576
0
        memcpy(start, str->start, str->length);
577
0
        njs_chb_written(chain, str->length);
578
0
        return NJS_OK;
579
0
    }
580
581
0
    (void) njs_string_encode(escape, str->length, str->start, start);
582
583
0
    njs_chb_written(chain, size);
584
585
0
    return NJS_OK;
586
0
}
587
588
589
njs_inline njs_bool_t
590
njs_query_string_is_native_encoder(njs_function_t *encoder)
591
0
{
592
0
    njs_opaque_value_t  function;
593
594
0
    njs_value_function_set(njs_value_arg(&function), encoder);
595
596
0
    return njs_value_native_function(njs_value_arg(&function))
597
0
                                                   == njs_query_string_escape;
598
0
}
599
600
601
njs_inline njs_int_t
602
njs_query_string_encoder_call(njs_vm_t *vm, njs_chb_t *chain,
603
    njs_function_t *encoder, njs_value_t *string)
604
0
{
605
0
    njs_str_t           str;
606
0
    njs_int_t           ret;
607
0
    njs_opaque_value_t  retval;
608
609
0
    if (njs_slow_path(!njs_value_is_string(string))) {
610
0
        ret = njs_value_to_string(vm, string, string);
611
0
        if (njs_slow_path(ret != NJS_OK)) {
612
0
            return NJS_ERROR;
613
0
        }
614
0
    }
615
616
0
    if (njs_fast_path(njs_query_string_is_native_encoder(encoder))) {
617
0
        njs_value_string_get(vm, string, &str);
618
0
        return njs_query_string_encode(chain, &str);
619
0
    }
620
621
0
    ret = njs_vm_invoke(vm, encoder, string, 1, njs_value_arg(&retval));
622
0
    if (njs_slow_path(ret != NJS_OK)) {
623
0
        return ret;
624
0
    }
625
626
0
    if (njs_slow_path(!njs_value_is_string(njs_value_arg(&retval)))) {
627
0
        ret = njs_value_to_string(vm, njs_value_arg(&retval),
628
0
                                  njs_value_arg(&retval));
629
0
        if (njs_slow_path(ret != NJS_OK)) {
630
0
            return NJS_ERROR;
631
0
        }
632
0
    }
633
634
0
    njs_value_string_get(vm, njs_value_arg(&retval), &str);
635
636
0
    njs_chb_append_str(chain, &str);
637
638
0
    return NJS_OK;
639
0
}
640
641
642
njs_inline njs_int_t
643
njs_query_string_push(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *key,
644
    njs_value_t *value, njs_str_t *eq, njs_function_t *encoder)
645
0
{
646
0
    njs_int_t  ret;
647
648
0
    ret = njs_query_string_encoder_call(vm, chain, encoder, key);
649
0
    if (njs_slow_path(ret != NJS_OK)) {
650
0
        return NJS_ERROR;
651
0
    }
652
653
0
    njs_chb_append(chain, eq->start, eq->length);
654
655
0
    if (njs_value_is_valid_number(value)
656
0
        || njs_value_is_boolean(value)
657
0
        || njs_value_is_string(value))
658
0
    {
659
0
        if (!njs_value_is_string(value)) {
660
0
            ret = njs_value_to_string(vm, value, value);
661
0
            if (njs_slow_path(ret != NJS_OK)) {
662
0
                return NJS_ERROR;
663
0
            }
664
0
        }
665
666
0
        ret = njs_query_string_encoder_call(vm, chain, encoder, value);
667
0
        if (njs_slow_path(ret < 0)) {
668
0
            return NJS_ERROR;
669
0
        }
670
0
    }
671
672
0
    return NJS_OK;
673
0
}
674
675
676
static njs_int_t
677
njs_query_string_stringify(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
678
    njs_index_t unused, njs_value_t *retval)
679
0
{
680
0
    int64_t             len, keys_length;
681
0
    uint32_t            n, i;
682
0
    njs_int_t           ret;
683
0
    njs_str_t           sep, eq;
684
0
    njs_chb_t           chain;
685
0
    njs_value_t         *this, *object, *arg, *options, *val, *keys;
686
0
    njs_function_t      *encode;
687
0
    njs_opaque_value_t  value, result, key, *string;
688
689
0
    encode = NULL;
690
0
    sep = njs_sep_default;
691
0
    eq = njs_eq_default;
692
693
0
    this = njs_argument(args, 0);
694
0
    object = njs_arg(args, nargs, 1);
695
696
0
    if (njs_slow_path(!njs_value_is_object(object))) {
697
0
        njs_vm_value_string_create(vm, retval, (u_char *) "", 0);
698
0
        return NJS_OK;
699
0
    }
700
701
0
    arg = njs_arg(args, nargs, 2);
702
0
    if (!njs_value_is_null_or_undefined(arg)) {
703
0
        ret = njs_value_to_string(vm, arg, arg);
704
0
        if (njs_slow_path(ret != NJS_OK)) {
705
0
            return ret;
706
0
        }
707
708
0
        if (njs_value_string_length(vm, arg) > 0) {
709
0
            njs_value_string_get(vm, arg, &sep);
710
0
        }
711
0
    }
712
713
0
    arg = njs_arg(args, nargs, 3);
714
0
    if (!njs_value_is_null_or_undefined(arg)) {
715
0
        ret = njs_value_to_string(vm, arg, arg);
716
0
        if (njs_slow_path(ret != NJS_OK)) {
717
0
            return ret;
718
0
        }
719
720
0
        if (njs_value_string_length(vm, arg) > 0) {
721
0
            njs_value_string_get(vm, arg, &eq);
722
0
        }
723
0
    }
724
725
0
    options = njs_arg(args, nargs, 4);
726
727
0
    if (njs_value_is_object(options)) {
728
0
        val = njs_vm_object_prop(vm, options, &njs_encode_uri_str, &value);
729
730
0
        if (val != NULL) {
731
0
            if (njs_slow_path(!njs_value_is_function(val))) {
732
0
                njs_vm_type_error(vm, "option encodeURIComponent is not "
733
0
                                  "a function");
734
0
                return NJS_ERROR;
735
0
            }
736
737
0
            encode = njs_value_function(val);
738
0
        }
739
0
    }
740
741
0
    if (encode == NULL) {
742
0
        val = njs_vm_object_prop(vm, this, &njs_escape_str, &value);
743
744
0
        if (val == NULL || !njs_value_is_function(val)) {
745
0
            njs_vm_type_error(vm, "QueryString.escape is not a function");
746
0
            return NJS_ERROR;
747
0
        }
748
749
0
        encode = njs_value_function(val);
750
0
    }
751
752
0
    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
753
754
0
    keys = njs_vm_object_keys(vm, object, njs_value_arg(&value));
755
0
    if (njs_slow_path(keys == NULL)) {
756
0
        return NJS_ERROR;
757
0
    }
758
759
0
    (void) njs_vm_array_length(vm, keys, &keys_length);
760
761
0
    string = (njs_opaque_value_t *) njs_vm_array_start(vm, keys);
762
0
    if (njs_slow_path(string == NULL)) {
763
0
        return NJS_ERROR;
764
0
    }
765
766
0
    for (n = 0; n < keys_length; n++, string++) {
767
0
        ret = njs_value_property_val(vm, object, njs_value_arg(string),
768
0
                                     njs_value_arg(&value));
769
0
        if (njs_slow_path(ret == NJS_ERROR)) {
770
0
            goto failed;
771
0
        }
772
773
0
        if (njs_value_is_array(njs_value_arg(&value))) {
774
0
            (void) njs_vm_array_length(vm, njs_value_arg(&value), &len);
775
776
0
            for (i = 0; i < len; i++) {
777
0
                njs_value_number_set(njs_value_arg(&key), i);
778
0
                ret = njs_value_property_val(vm, njs_value_arg(&value),
779
0
                                             njs_value_arg(&key),
780
0
                                             njs_value_arg(&result));
781
0
                if (njs_slow_path(ret == NJS_ERROR)) {
782
0
                    goto failed;
783
0
                }
784
785
0
                if (chain.last != NULL) {
786
0
                    njs_chb_append(&chain, sep.start, sep.length);
787
0
                }
788
789
0
                ret = njs_query_string_push(vm, &chain, njs_value_arg(string),
790
0
                                            njs_value_arg(&result), &eq,
791
0
                                            encode);
792
0
                if (njs_slow_path(ret != NJS_OK)) {
793
0
                    goto failed;
794
0
                }
795
0
            }
796
797
0
            continue;
798
0
        }
799
800
0
        if (n != 0) {
801
0
            njs_chb_append(&chain, sep.start, sep.length);
802
0
        }
803
804
0
        ret = njs_query_string_push(vm, &chain, njs_value_arg(string),
805
0
                                    njs_value_arg(&value), &eq, encode);
806
0
        if (njs_slow_path(ret != NJS_OK)) {
807
0
            goto failed;
808
0
        }
809
0
    }
810
811
0
    ret = njs_vm_value_string_create_chb(vm, retval, &chain);
812
813
0
failed:
814
815
0
    njs_chb_destroy(&chain);
816
817
0
    return ret;
818
0
}
819
820
821
static njs_int_t
822
njs_query_string_escape(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
823
    njs_index_t unused, njs_value_t *retval)
824
0
{
825
0
    njs_int_t           ret;
826
0
    njs_str_t           str;
827
0
    njs_chb_t           chain;
828
0
    njs_value_t         *string;
829
0
    njs_opaque_value_t  value;
830
831
0
    string = njs_arg(args, nargs, 1);
832
833
0
    if (!njs_value_is_string(string)) {
834
0
        ret = njs_value_to_string(vm, njs_value_arg(&value), string);
835
0
        if (njs_slow_path(ret != NJS_OK)) {
836
0
            return ret;
837
0
        }
838
839
0
        string = njs_value_arg(&value);
840
0
    }
841
842
0
    njs_value_string_get(vm, string, &str);
843
844
0
    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
845
846
0
    ret = njs_query_string_encode(&chain, &str);
847
0
    if (njs_slow_path(ret != NJS_OK)) {
848
0
        return NJS_ERROR;
849
0
    }
850
851
0
    ret = njs_vm_value_string_create_chb(vm, retval, &chain);
852
853
0
    njs_chb_destroy(&chain);
854
855
0
    return ret;
856
0
}
857
858
859
static njs_int_t
860
njs_query_string_unescape(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
861
    njs_index_t unused, njs_value_t *retval)
862
0
{
863
0
    njs_int_t           ret;
864
0
    njs_str_t           str;
865
0
    njs_value_t         *string;
866
0
    njs_opaque_value_t  value;
867
868
0
    string = njs_arg(args, nargs, 1);
869
870
0
    if (!njs_value_is_string(string)) {
871
0
        ret = njs_value_to_string(vm, njs_value_arg(&value), string);
872
0
        if (njs_slow_path(ret != NJS_OK)) {
873
0
            return ret;
874
0
        }
875
876
0
        string = njs_value_arg(&value);
877
0
    }
878
879
0
    njs_value_string_get(vm, string, &str);
880
881
0
    return njs_query_string_decode(vm, retval, str.start, str.length);
882
0
}
883
884
885
static njs_int_t
886
njs_query_string_init(njs_vm_t *vm)
887
11.8k
{
888
11.8k
    njs_int_t           ret, proto_id;
889
11.8k
    njs_mod_t           *module;
890
11.8k
    njs_opaque_value_t  value;
891
892
11.8k
    proto_id = njs_vm_external_prototype(vm, njs_ext_query_string,
893
11.8k
                                         njs_nitems(njs_ext_query_string));
894
11.8k
    if (njs_slow_path(proto_id < 0)) {
895
0
        return NJS_ERROR;
896
0
    }
897
898
11.8k
    ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1);
899
11.8k
    if (njs_slow_path(ret != NJS_OK)) {
900
0
        return NJS_ERROR;
901
0
    }
902
903
11.8k
    module = njs_vm_add_module(vm, &njs_str_value("querystring"),
904
11.8k
                               njs_value_arg(&value));
905
11.8k
    if (njs_slow_path(module == NULL)) {
906
0
        return NJS_ERROR;
907
0
    }
908
909
11.8k
    return NJS_OK;
910
11.8k
}