/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 | } |