1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) NGINX, Inc.
5 */
6
7
8 #include <njs_main.h>
9
10
11 #define NJS_TRIM_START 1
12 #define NJS_TRIM_END 2
13
14
15 static u_char njs_basis64[] = {
16 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
17 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
18 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
19 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
20 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
21 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
22 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
23 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
24
25 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
26 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
27 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
28 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
29 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
30 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
31 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
32 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
33 };
34
35
36 static u_char njs_basis64url[] = {
37 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
38 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
39 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77,
40 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
41 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
42 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 63,
43 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
44 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
45
46 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
47 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
48 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
49 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
50 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
51 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
52 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
53 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
54 };
55
56
57 static void njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src,
58 const u_char *basis, njs_uint_t padding);
59 static njs_int_t njs_string_decode_base64_core(njs_vm_t *vm,
60 njs_value_t *value, const njs_str_t *src, njs_bool_t url);
61 static njs_int_t njs_string_slice_prop(njs_vm_t *vm, njs_string_prop_t *string,
62 njs_slice_prop_t *slice, njs_value_t *args, njs_uint_t nargs);
63 static njs_int_t njs_string_slice_args(njs_vm_t *vm, njs_slice_prop_t *slice,
64 njs_value_t *args, njs_uint_t nargs);
65 static njs_int_t njs_string_from_char_code(njs_vm_t *vm, njs_value_t *args,
66 njs_uint_t nargs, njs_index_t is_point);
67 static njs_int_t njs_string_bytes_from(njs_vm_t *vm, njs_value_t *args,
68 njs_uint_t nargs, njs_index_t unused);
69 static njs_int_t njs_string_bytes_from_array_like(njs_vm_t *vm,
70 njs_value_t *value);
71 static njs_int_t njs_string_bytes_from_string(njs_vm_t *vm,
72 const njs_value_t *string, const njs_value_t *encoding);
73 static njs_int_t njs_string_match_multiple(njs_vm_t *vm, njs_value_t *args,
74 njs_regexp_pattern_t *pattern);
75
76
77 #define njs_base64_encoded_length(len) (((len + 2) / 3) * 4)
78 #define njs_base64_decoded_length(len, pad) (((len / 4) * 3) - pad)
79
80
81 njs_int_t
njs_string_set(njs_vm_t * vm,njs_value_t * value,const u_char * start,uint32_t size)82 njs_string_set(njs_vm_t *vm, njs_value_t *value, const u_char *start,
83 uint32_t size)
84 {
85 u_char *dst;
86 const u_char *src;
87 njs_string_t *string;
88
89 value->type = NJS_STRING;
90 njs_string_truth(value, size);
91
92 if (size <= NJS_STRING_SHORT) {
93 value->short_string.size = size;
94 value->short_string.length = 0;
95
96 dst = value->short_string.start;
97 src = start;
98
99 while (size != 0) {
100 /* The maximum size is just 14 bytes. */
101 njs_pragma_loop_disable_vectorization;
102
103 *dst++ = *src++;
104 size--;
105 }
106
107 } else {
108 /*
109 * Setting UTF-8 length is not required here, it just allows
110 * to store the constant in whole byte instead of bit twiddling.
111 */
112 value->short_string.size = NJS_STRING_LONG;
113 value->short_string.length = 0;
114 value->long_string.external = 0xff;
115 value->long_string.size = size;
116
117 string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t));
118 if (njs_slow_path(string == NULL)) {
119 njs_memory_error(vm);
120 return NJS_ERROR;
121 }
122
123 value->long_string.data = string;
124
125 string->start = (u_char *) start;
126 string->length = 0;
127 string->retain = 1;
128 }
129
130 return NJS_OK;
131 }
132
133
134 njs_int_t
njs_string_create(njs_vm_t * vm,njs_value_t * value,const char * src,size_t size)135 njs_string_create(njs_vm_t *vm, njs_value_t *value, const char *src,
136 size_t size)
137 {
138 njs_str_t str;
139
140 str.start = (u_char *) src;
141 str.length = size;
142
143 return njs_string_decode_utf8(vm, value, &str);
144 }
145
146
147 njs_int_t
njs_string_new(njs_vm_t * vm,njs_value_t * value,const u_char * start,uint32_t size,uint32_t length)148 njs_string_new(njs_vm_t *vm, njs_value_t *value, const u_char *start,
149 uint32_t size, uint32_t length)
150 {
151 u_char *p;
152
153 p = njs_string_alloc(vm, value, size, length);
154
155 if (njs_fast_path(p != NULL)) {
156 memcpy(p, start, size);
157 return NJS_OK;
158 }
159
160 return NJS_ERROR;
161 }
162
163
164 u_char *
njs_string_alloc(njs_vm_t * vm,njs_value_t * value,uint64_t size,uint64_t length)165 njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size,
166 uint64_t length)
167 {
168 uint32_t total, map_offset, *map;
169 njs_string_t *string;
170
171 if (njs_slow_path(size > NJS_STRING_MAX_LENGTH)) {
172 njs_range_error(vm, "invalid string length");
173 return NULL;
174 }
175
176 value->type = NJS_STRING;
177 njs_string_truth(value, size);
178
179 if (size <= NJS_STRING_SHORT) {
180 value->short_string.size = size;
181 value->short_string.length = length;
182
183 return value->short_string.start;
184 }
185
186 /*
187 * Setting UTF-8 length is not required here, it just allows
188 * to store the constant in whole byte instead of bit twiddling.
189 */
190 value->short_string.size = NJS_STRING_LONG;
191 value->short_string.length = 0;
192 value->long_string.external = 0;
193 value->long_string.size = size;
194
195 if (size != length && length > NJS_STRING_MAP_STRIDE) {
196 map_offset = njs_string_map_offset(size);
197 total = map_offset + njs_string_map_size(length);
198
199 } else {
200 map_offset = 0;
201 total = size;
202 }
203
204 string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t) + total);
205
206 if (njs_fast_path(string != NULL)) {
207 value->long_string.data = string;
208
209 string->start = (u_char *) string + sizeof(njs_string_t);
210 string->length = length;
211 string->retain = 1;
212
213 if (map_offset != 0) {
214 map = (uint32_t *) (string->start + map_offset);
215 map[0] = 0;
216 }
217
218 return string->start;
219 }
220
221 njs_memory_error(vm);
222
223 return NULL;
224 }
225
226
227 void
njs_string_truncate(njs_value_t * value,uint32_t size,uint32_t length)228 njs_string_truncate(njs_value_t *value, uint32_t size, uint32_t length)
229 {
230 u_char *dst, *src;
231 uint32_t n;
232
233 if (size <= NJS_STRING_SHORT) {
234 if (value->short_string.size == NJS_STRING_LONG) {
235 dst = value->short_string.start;
236 src = value->long_string.data->start;
237
238 n = size;
239
240 while (n != 0) {
241 /* The maximum size is just 14 bytes. */
242 njs_pragma_loop_disable_vectorization;
243
244 *dst++ = *src++;
245 n--;
246 }
247 }
248
249 value->short_string.size = size;
250 value->short_string.length = length;
251
252 } else {
253 value->long_string.size = size;
254 value->long_string.data->length = length;
255 }
256 }
257
258
259 njs_int_t
njs_string_hex(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)260 njs_string_hex(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
261 {
262 size_t length;
263 njs_str_t dst;
264
265 length = njs_encode_hex_length(src, &dst.length);
266
267 dst.start = njs_string_alloc(vm, value, dst.length, length);
268 if (njs_fast_path(dst.start != NULL)) {
269 njs_encode_hex(&dst, src);
270 return NJS_OK;
271 }
272
273 return NJS_ERROR;
274 }
275
276
277 void
njs_encode_hex(njs_str_t * dst,const njs_str_t * src)278 njs_encode_hex(njs_str_t *dst, const njs_str_t *src)
279 {
280 u_char *p, c;
281 size_t i, len;
282 const u_char *start;
283
284 static const u_char hex[16] = "0123456789abcdef";
285
286 len = src->length;
287 start = src->start;
288
289 p = dst->start;
290
291 for (i = 0; i < len; i++) {
292 c = start[i];
293 *p++ = hex[c >> 4];
294 *p++ = hex[c & 0x0f];
295 }
296 }
297
298
299 size_t
njs_encode_hex_length(const njs_str_t * src,size_t * out_size)300 njs_encode_hex_length(const njs_str_t *src, size_t *out_size)
301 {
302 size_t size;
303
304 size = src->length * 2;
305
306 if (out_size != NULL) {
307 *out_size = size;
308 }
309
310 return size;
311 }
312
313
314 void
njs_encode_base64(njs_str_t * dst,const njs_str_t * src)315 njs_encode_base64(njs_str_t *dst, const njs_str_t *src)
316 {
317 static u_char basis64[] =
318 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
319
320 njs_encode_base64_core(dst, src, basis64, 1);
321 }
322
323
324 size_t
njs_encode_base64_length(const njs_str_t * src,size_t * out_size)325 njs_encode_base64_length(const njs_str_t *src, size_t *out_size)
326 {
327 size_t size;
328
329 size = (src->length == 0) ? 0 : njs_base64_encoded_length(src->length);
330
331 if (out_size != NULL) {
332 *out_size = size;
333 }
334
335 return size;
336 }
337
338
339 static void
njs_encode_base64url(njs_str_t * dst,const njs_str_t * src)340 njs_encode_base64url(njs_str_t *dst, const njs_str_t *src)
341 {
342 static u_char basis64[] =
343 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
344
345 njs_encode_base64_core(dst, src, basis64, 0);
346 }
347
348
349 static void
njs_encode_base64_core(njs_str_t * dst,const njs_str_t * src,const u_char * basis,njs_bool_t padding)350 njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src,
351 const u_char *basis, njs_bool_t padding)
352 {
353 u_char *d, *s, c0, c1, c2;
354 size_t len;
355
356 len = src->length;
357 s = src->start;
358 d = dst->start;
359
360 while (len > 2) {
361 c0 = s[0];
362 c1 = s[1];
363 c2 = s[2];
364
365 *d++ = basis[c0 >> 2];
366 *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
367 *d++ = basis[((c1 & 0x0f) << 2) | (c2 >> 6)];
368 *d++ = basis[c2 & 0x3f];
369
370 s += 3;
371 len -= 3;
372 }
373
374 if (len > 0) {
375 c0 = s[0];
376 *d++ = basis[c0 >> 2];
377
378 if (len == 1) {
379 *d++ = basis[(c0 & 0x03) << 4];
380 if (padding) {
381 *d++ = '=';
382 *d++ = '=';
383 }
384
385 } else {
386 c1 = s[1];
387
388 *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
389 *d++ = basis[(c1 & 0x0f) << 2];
390
391 if (padding) {
392 *d++ = '=';
393 }
394 }
395
396 }
397
398 dst->length = d - dst->start;
399 }
400
401
402 njs_int_t
njs_string_base64(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)403 njs_string_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
404 {
405 size_t length;
406 njs_str_t dst;
407
408 length = njs_encode_base64_length(src, &dst.length);
409
410 if (njs_slow_path(dst.length == 0)) {
411 vm->retval = njs_string_empty;
412 return NJS_OK;
413 }
414
415 dst.start = njs_string_alloc(vm, value, dst.length, length);
416 if (njs_slow_path(dst.start == NULL)) {
417 return NJS_ERROR;
418 }
419
420 njs_encode_base64(&dst, src);
421
422 return NJS_OK;
423 }
424
425
426 njs_int_t
njs_string_base64url(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)427 njs_string_base64url(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
428 {
429 size_t padding;
430 njs_str_t dst;
431
432 if (njs_slow_path(src->length == 0)) {
433 vm->retval = njs_string_empty;
434 return NJS_OK;
435 }
436
437 padding = src->length % 3;
438
439 /*
440 * Calculating the padding length: 0 -> 0, 1 -> 2, 2 -> 1.
441 */
442 padding = (4 >> padding) & 0x03;
443
444 dst.length = njs_base64_encoded_length(src->length) - padding;
445
446 dst.start = njs_string_alloc(vm, value, dst.length, dst.length);
447 if (njs_slow_path(dst.start == NULL)) {
448 return NJS_ERROR;
449 }
450
451 njs_encode_base64url(&dst, src);
452
453 return NJS_OK;
454 }
455
456
457 void
njs_string_copy(njs_value_t * dst,njs_value_t * src)458 njs_string_copy(njs_value_t *dst, njs_value_t *src)
459 {
460 *dst = *src;
461
462 /* GC: long string retain */
463 }
464
465
466 /*
467 * njs_string_validate() validates an UTF-8 string, evaluates its length,
468 * sets njs_string_prop_t struct.
469 */
470
471 njs_int_t
njs_string_validate(njs_vm_t * vm,njs_string_prop_t * string,njs_value_t * value)472 njs_string_validate(njs_vm_t *vm, njs_string_prop_t *string, njs_value_t *value)
473 {
474 u_char *start;
475 size_t new_size, map_offset;
476 ssize_t size, length;
477 uint32_t *map;
478
479 size = value->short_string.size;
480
481 if (size != NJS_STRING_LONG) {
482 string->start = value->short_string.start;
483 length = value->short_string.length;
484
485 if (length == 0 && length != size) {
486 length = njs_utf8_length(value->short_string.start, size);
487
488 if (njs_slow_path(length < 0)) {
489 /* Invalid UTF-8 string. */
490 return length;
491 }
492
493 value->short_string.length = length;
494 }
495
496 } else {
497 string->start = value->long_string.data->start;
498 size = value->long_string.size;
499 length = value->long_string.data->length;
500
501 if (length == 0 && length != size) {
502 length = njs_utf8_length(string->start, size);
503
504 if (length != size) {
505 if (njs_slow_path(length < 0)) {
506 /* Invalid UTF-8 string. */
507 return length;
508 }
509
510 if (length > NJS_STRING_MAP_STRIDE) {
511 /*
512 * Reallocate the long string with offset map
513 * after the string.
514 */
515 map_offset = njs_string_map_offset(size);
516 new_size = map_offset + njs_string_map_size(length);
517
518 start = njs_mp_alloc(vm->mem_pool, new_size);
519 if (njs_slow_path(start == NULL)) {
520 njs_memory_error(vm);
521 return NJS_ERROR;
522 }
523
524 memcpy(start, string->start, size);
525 string->start = start;
526 value->long_string.data->start = start;
527
528 map = (uint32_t *) (start + map_offset);
529 map[0] = 0;
530 }
531 }
532
533 value->long_string.data->length = length;
534 }
535 }
536
537 string->size = size;
538 string->length = length;
539
540 return length;
541 }
542
543
544 size_t
njs_string_prop(njs_string_prop_t * string,const njs_value_t * value)545 njs_string_prop(njs_string_prop_t *string, const njs_value_t *value)
546 {
547 size_t size;
548 uintptr_t length;
549
550 size = value->short_string.size;
551
552 if (size != NJS_STRING_LONG) {
553 string->start = (u_char *) value->short_string.start;
554 length = value->short_string.length;
555
556 } else {
557 string->start = (u_char *) value->long_string.data->start;
558 size = value->long_string.size;
559 length = value->long_string.data->length;
560 }
561
562 string->size = size;
563 string->length = length;
564
565 return (length == 0) ? size : length;
566 }
567
568
569 static njs_int_t
njs_string_constructor(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)570 njs_string_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
571 njs_index_t unused)
572 {
573 njs_int_t ret;
574 njs_value_t *value;
575 njs_object_value_t *object;
576
577 if (nargs == 1) {
578 value = njs_value_arg(&njs_string_empty);
579
580 } else {
581 value = &args[1];
582
583 if (njs_slow_path(!njs_is_string(value))) {
584 if (!vm->top_frame->ctor && njs_is_symbol(value)) {
585 return njs_symbol_descriptive_string(vm, &vm->retval, value);
586 }
587
588 ret = njs_value_to_string(vm, value, value);
589 if (njs_slow_path(ret != NJS_OK)) {
590 return ret;
591 }
592 }
593 }
594
595 if (vm->top_frame->ctor) {
596 object = njs_object_value_alloc(vm, NJS_OBJ_TYPE_STRING, 0, value);
597 if (njs_slow_path(object == NULL)) {
598 return NJS_ERROR;
599 }
600
601 njs_set_object_value(&vm->retval, object);
602
603 } else {
604 vm->retval = *value;
605 }
606
607 return NJS_OK;
608 }
609
610
611 static const njs_object_prop_t njs_string_constructor_properties[] =
612 {
613 {
614 .type = NJS_PROPERTY,
615 .name = njs_string("name"),
616 .value = njs_string("String"),
617 .configurable = 1,
618 },
619
620 {
621 .type = NJS_PROPERTY,
622 .name = njs_string("length"),
623 .value = njs_value(NJS_NUMBER, 1, 1.0),
624 .configurable = 1,
625 },
626
627 {
628 .type = NJS_PROPERTY_HANDLER,
629 .name = njs_string("prototype"),
630 .value = njs_prop_handler(njs_object_prototype_create),
631 },
632
633 {
634 .type = NJS_PROPERTY,
635 .name = njs_string("bytesFrom"),
636 .value = njs_native_function(njs_string_bytes_from, 0),
637 .writable = 1,
638 .configurable = 1,
639 },
640
641 {
642 .type = NJS_PROPERTY,
643 .name = njs_string("fromCharCode"),
644 .value = njs_native_function2(njs_string_from_char_code, 1, 0),
645 .writable = 1,
646 .configurable = 1,
647 },
648
649 {
650 .type = NJS_PROPERTY,
651 .name = njs_string("fromCodePoint"),
652 .value = njs_native_function2(njs_string_from_char_code, 1, 1),
653 .writable = 1,
654 .configurable = 1,
655 },
656 };
657
658
659 const njs_object_init_t njs_string_constructor_init = {
660 njs_string_constructor_properties,
661 njs_nitems(njs_string_constructor_properties),
662 };
663
664
665 static njs_int_t
njs_string_instance_length(njs_vm_t * vm,njs_object_prop_t * prop,njs_value_t * value,njs_value_t * setval,njs_value_t * retval)666 njs_string_instance_length(njs_vm_t *vm, njs_object_prop_t *prop,
667 njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
668 {
669 size_t size;
670 uintptr_t length;
671 njs_object_t *proto;
672 njs_object_value_t *ov;
673
674 /*
675 * This getter can be called for string primitive, String object,
676 * String.prototype. The zero should be returned for the latter case.
677 */
678 length = 0;
679
680 if (njs_slow_path(njs_is_object(value))) {
681 proto = njs_object(value);
682
683 do {
684 if (njs_fast_path(proto->type == NJS_OBJECT_VALUE)) {
685 break;
686 }
687
688 proto = proto->__proto__;
689 } while (proto != NULL);
690
691 if (proto != NULL) {
692 ov = (njs_object_value_t *) proto;
693 value = &ov->value;
694 }
695 }
696
697 if (njs_is_string(value)) {
698 size = value->short_string.size;
699 length = value->short_string.length;
700
701 if (size == NJS_STRING_LONG) {
702 size = value->long_string.size;
703 length = value->long_string.data->length;
704 }
705
706 length = (length == 0) ? size : length;
707 }
708
709 njs_set_number(retval, length);
710
711 njs_release(vm, value);
712
713 return NJS_OK;
714 }
715
716
717 njs_bool_t
njs_string_eq(const njs_value_t * v1,const njs_value_t * v2)718 njs_string_eq(const njs_value_t *v1, const njs_value_t *v2)
719 {
720 size_t size, length1, length2;
721 const u_char *start1, *start2;
722
723 size = v1->short_string.size;
724
725 if (size != v2->short_string.size) {
726 return 0;
727 }
728
729 if (size != NJS_STRING_LONG) {
730 length1 = v1->short_string.length;
731 length2 = v2->short_string.length;
732
733 /*
734 * Using full memcmp() comparison if at least one string
735 * is a Byte string.
736 */
737 if (length1 != 0 && length2 != 0 && length1 != length2) {
738 return 0;
739 }
740
741 start1 = v1->short_string.start;
742 start2 = v2->short_string.start;
743
744 } else {
745 size = v1->long_string.size;
746
747 if (size != v2->long_string.size) {
748 return 0;
749 }
750
751 length1 = v1->long_string.data->length;
752 length2 = v2->long_string.data->length;
753
754 /*
755 * Using full memcmp() comparison if at least one string
756 * is a Byte string.
757 */
758 if (length1 != 0 && length2 != 0 && length1 != length2) {
759 return 0;
760 }
761
762 start1 = v1->long_string.data->start;
763 start2 = v2->long_string.data->start;
764 }
765
766 return (memcmp(start1, start2, size) == 0);
767 }
768
769
770 njs_int_t
njs_string_cmp(const njs_value_t * v1,const njs_value_t * v2)771 njs_string_cmp(const njs_value_t *v1, const njs_value_t *v2)
772 {
773 size_t size, size1, size2;
774 njs_int_t ret;
775 const u_char *start1, *start2;
776
777 size1 = v1->short_string.size;
778
779 if (size1 != NJS_STRING_LONG) {
780 start1 = v1->short_string.start;
781
782 } else {
783 size1 = v1->long_string.size;
784 start1 = v1->long_string.data->start;
785 }
786
787 size2 = v2->short_string.size;
788
789 if (size2 != NJS_STRING_LONG) {
790 start2 = v2->short_string.start;
791
792 } else {
793 size2 = v2->long_string.size;
794 start2 = v2->long_string.data->start;
795 }
796
797 size = njs_min(size1, size2);
798
799 ret = memcmp(start1, start2, size);
800
801 if (ret != 0) {
802 return ret;
803 }
804
805 return (size1 - size2);
806 }
807
808
809 static njs_int_t
njs_string_prototype_value_of(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)810 njs_string_prototype_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
811 njs_index_t unused)
812 {
813 njs_value_t *value;
814
815 value = &args[0];
816
817 if (value->type != NJS_STRING) {
818
819 if (njs_is_object_string(value)) {
820 value = njs_object_value(value);
821
822 } else {
823 njs_type_error(vm, "unexpected value type:%s",
824 njs_type_string(value->type));
825 return NJS_ERROR;
826 }
827 }
828
829 vm->retval = *value;
830
831 return NJS_OK;
832 }
833
834
835 /*
836 * String.prototype.toString([encoding]).
837 * Returns the string as is if no additional argument is provided,
838 * otherwise converts a string into an encoded string: hex, base64,
839 * base64url.
840 */
841
842 static njs_int_t
njs_string_prototype_to_string(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)843 njs_string_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
844 njs_uint_t nargs, njs_index_t unused)
845 {
846 njs_int_t ret;
847 njs_str_t enc, str;
848 njs_value_t value;
849 njs_string_prop_t string;
850
851 ret = njs_string_prototype_value_of(vm, args, nargs, unused);
852 if (njs_slow_path(ret != NJS_OK)) {
853 return ret;
854 }
855
856 if (nargs < 2) {
857 return NJS_OK;
858 }
859
860 if (njs_slow_path(!njs_is_string(&args[1]))) {
861 njs_type_error(vm, "encoding must be a string");
862 return NJS_ERROR;
863 }
864
865 value = vm->retval;
866
867 (void) njs_string_prop(&string, &value);
868
869 njs_string_get(&args[1], &enc);
870
871 str.length = string.size;
872 str.start = string.start;
873
874 if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) {
875 return njs_string_hex(vm, &vm->retval, &str);
876
877 } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) {
878 return njs_string_base64(vm, &vm->retval, &str);
879
880 } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) {
881 return njs_string_base64url(vm, &vm->retval, &str);
882 }
883
884 njs_type_error(vm, "Unknown encoding: \"%V\"", &enc);
885
886 return NJS_ERROR;
887 }
888
889
890 njs_int_t
njs_string_prototype_concat(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)891 njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
892 njs_index_t unused)
893 {
894 u_char *p, *start;
895 uint64_t size, length, mask;
896 njs_int_t ret;
897 njs_uint_t i;
898 njs_string_prop_t string;
899
900 if (njs_is_null_or_undefined(&args[0])) {
901 njs_type_error(vm, "\"this\" argument is null or undefined");
902 return NJS_ERROR;
903 }
904
905 for (i = 0; i < nargs; i++) {
906 if (!njs_is_string(&args[i])) {
907 ret = njs_value_to_string(vm, &args[i], &args[i]);
908 if (ret != NJS_OK) {
909 return ret;
910 }
911 }
912 }
913
914 if (nargs == 1) {
915 njs_string_copy(&vm->retval, &args[0]);
916 return NJS_OK;
917 }
918
919 size = 0;
920 length = 0;
921 mask = -1;
922
923 for (i = 0; i < nargs; i++) {
924 (void) njs_string_prop(&string, &args[i]);
925
926 size += string.size;
927 length += string.length;
928
929 if (string.length == 0 && string.size != 0) {
930 mask = 0;
931 }
932 }
933
934 length &= mask;
935
936 start = njs_string_alloc(vm, &vm->retval, size, length);
937 if (njs_slow_path(start == NULL)) {
938 return NJS_ERROR;
939 }
940
941 p = start;
942
943 for (i = 0; i < nargs; i++) {
944 (void) njs_string_prop(&string, &args[i]);
945
946 p = memcpy(p, string.start, string.size);
947 p += string.size;
948 }
949
950 return NJS_OK;
951 }
952
953
954 njs_inline njs_int_t
njs_string_object_validate(njs_vm_t * vm,njs_value_t * object)955 njs_string_object_validate(njs_vm_t *vm, njs_value_t *object)
956 {
957 njs_int_t ret;
958
959 if (njs_slow_path(njs_is_null_or_undefined(object))) {
960 njs_type_error(vm, "cannot convert undefined to object");
961 return NJS_ERROR;
962 }
963
964 if (njs_slow_path(!njs_is_string(object))) {
965 ret = njs_value_to_string(vm, object, object);
966 if (njs_slow_path(ret != NJS_OK)) {
967 return ret;
968 }
969 }
970
971 return NJS_OK;
972 }
973
974
975 /*
976 * String.fromUTF8(start[, end]).
977 * The method converts an UTF-8 encoded byte string to an Unicode string.
978 */
979
980 static njs_int_t
njs_string_prototype_from_utf8(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)981 njs_string_prototype_from_utf8(njs_vm_t *vm, njs_value_t *args,
982 njs_uint_t nargs, njs_index_t unused)
983 {
984 ssize_t length;
985 njs_int_t ret;
986 njs_slice_prop_t slice;
987 njs_string_prop_t string;
988
989 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
990 if (njs_slow_path(ret != NJS_OK)) {
991 return ret;
992 }
993
994 ret = njs_string_slice_prop(vm, &string, &slice, args, nargs);
995 if (njs_slow_path(ret != NJS_OK)) {
996 return ret;
997 }
998
999 if (string.length != 0) {
1000 /* ASCII or UTF8 string. */
1001 return njs_string_slice(vm, &vm->retval, &string, &slice);
1002 }
1003
1004 string.start += slice.start;
1005
1006 length = njs_utf8_length(string.start, slice.length);
1007
1008 if (length >= 0) {
1009 return njs_string_new(vm, &vm->retval, string.start, slice.length,
1010 length);
1011 }
1012
1013 vm->retval = njs_value_null;
1014
1015 return NJS_OK;
1016 }
1017
1018
1019 /*
1020 * String.toUTF8(start[, end]).
1021 * The method serializes Unicode string to an UTF-8 encoded byte string.
1022 */
1023
1024 static njs_int_t
njs_string_prototype_to_utf8(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1025 njs_string_prototype_to_utf8(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1026 njs_index_t unused)
1027 {
1028 njs_int_t ret;
1029 njs_slice_prop_t slice;
1030 njs_string_prop_t string;
1031
1032 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1033 if (njs_slow_path(ret != NJS_OK)) {
1034 return ret;
1035 }
1036
1037 (void) njs_string_prop(&string, njs_argument(args, 0));
1038
1039 string.length = 0;
1040 slice.string_length = string.size;
1041
1042 ret = njs_string_slice_args(vm, &slice, args, nargs);
1043 if (njs_slow_path(ret != NJS_OK)) {
1044 return ret;
1045 }
1046
1047 return njs_string_slice(vm, &vm->retval, &string, &slice);
1048 }
1049
1050
1051 /*
1052 * String.fromBytes(start[, end]).
1053 * The method converts a byte string to an Unicode string.
1054 */
1055
1056 static njs_int_t
njs_string_prototype_from_bytes(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1057 njs_string_prototype_from_bytes(njs_vm_t *vm, njs_value_t *args,
1058 njs_uint_t nargs, njs_index_t unused)
1059 {
1060 u_char *p, *s, *start, *end;
1061 size_t size;
1062 njs_int_t ret;
1063 njs_slice_prop_t slice;
1064 njs_string_prop_t string;
1065
1066 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1067 if (njs_slow_path(ret != NJS_OK)) {
1068 return ret;
1069 }
1070
1071 ret = njs_string_slice_prop(vm, &string, &slice, args, nargs);
1072 if (njs_slow_path(ret != NJS_OK)) {
1073 return ret;
1074 }
1075
1076 if (string.length != 0) {
1077 /* ASCII or UTF8 string. */
1078 return njs_string_slice(vm, &vm->retval, &string, &slice);
1079 }
1080
1081 size = 0;
1082 string.start += slice.start;
1083 end = string.start + slice.length;
1084
1085 for (p = string.start; p < end; p++) {
1086 size += (*p < 0x80) ? 1 : 2;
1087 }
1088
1089 start = njs_string_alloc(vm, &vm->retval, size, slice.length);
1090
1091 if (njs_fast_path(start != NULL)) {
1092
1093 if (size == slice.length) {
1094 memcpy(start, string.start, size);
1095
1096 } else {
1097 s = start;
1098 end = string.start + slice.length;
1099
1100 for (p = string.start; p < end; p++) {
1101 s = njs_utf8_encode(s, *p);
1102 }
1103 }
1104
1105 return NJS_OK;
1106 }
1107
1108 return NJS_ERROR;
1109 }
1110
1111
1112 /*
1113 * String.toBytes(start[, end]).
1114 * The method serializes an Unicode string to a byte string.
1115 * The method returns null if a character larger than 255 is
1116 * encountered in the Unicode string.
1117 */
1118
1119 static njs_int_t
njs_string_prototype_to_bytes(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1120 njs_string_prototype_to_bytes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1121 njs_index_t unused)
1122 {
1123 u_char *p;
1124 size_t length;
1125 uint32_t byte;
1126 njs_int_t ret;
1127 const u_char *s, *end;
1128 njs_slice_prop_t slice;
1129 njs_string_prop_t string;
1130 njs_unicode_decode_t ctx;
1131
1132 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1133 if (njs_slow_path(ret != NJS_OK)) {
1134 return ret;
1135 }
1136
1137 ret = njs_string_slice_prop(vm, &string, &slice, args, nargs);
1138 if (njs_slow_path(ret != NJS_OK)) {
1139 return ret;
1140 }
1141
1142 if (string.length == 0) {
1143 /* Byte string. */
1144 return njs_string_slice(vm, &vm->retval, &string, &slice);
1145 }
1146
1147 p = njs_string_alloc(vm, &vm->retval, slice.length, 0);
1148
1149 if (njs_fast_path(p != NULL)) {
1150
1151 if (string.length != string.size) {
1152 /* UTF-8 string. */
1153 end = string.start + string.size;
1154
1155 s = njs_string_offset(string.start, end, slice.start);
1156
1157 length = slice.length;
1158
1159 njs_utf8_decode_init(&ctx);
1160
1161 while (length != 0 && s < end) {
1162 byte = njs_utf8_decode(&ctx, &s, end);
1163
1164 if (njs_slow_path(byte > 0xFF)) {
1165 njs_release(vm, &vm->retval);
1166 vm->retval = njs_value_null;
1167
1168 return NJS_OK;
1169 }
1170
1171 *p++ = (u_char) byte;
1172 length--;
1173 }
1174
1175 } else {
1176 /* ASCII string. */
1177 memcpy(p, string.start + slice.start, slice.length);
1178 }
1179
1180 return NJS_OK;
1181 }
1182
1183 return NJS_ERROR;
1184 }
1185
1186
1187 static njs_int_t
njs_string_prototype_slice(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1188 njs_string_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1189 njs_index_t unused)
1190 {
1191 njs_int_t ret;
1192 njs_slice_prop_t slice;
1193 njs_string_prop_t string;
1194
1195 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1196 if (njs_slow_path(ret != NJS_OK)) {
1197 return ret;
1198 }
1199
1200 ret = njs_string_slice_prop(vm, &string, &slice, args, nargs);
1201 if (njs_slow_path(ret != NJS_OK)) {
1202 return ret;
1203 }
1204
1205 return njs_string_slice(vm, &vm->retval, &string, &slice);
1206 }
1207
1208
1209 static njs_int_t
njs_string_prototype_substring(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1210 njs_string_prototype_substring(njs_vm_t *vm, njs_value_t *args,
1211 njs_uint_t nargs, njs_index_t unused)
1212 {
1213 int64_t start, end, length;
1214 njs_int_t ret;
1215 njs_value_t *value;
1216 njs_slice_prop_t slice;
1217 njs_string_prop_t string;
1218
1219 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1220 if (njs_slow_path(ret != NJS_OK)) {
1221 return ret;
1222 }
1223
1224 length = njs_string_prop(&string, njs_argument(args, 0));
1225
1226 slice.string_length = length;
1227 start = 0;
1228
1229 if (nargs > 1) {
1230 value = njs_argument(args, 1);
1231
1232 if (njs_slow_path(!njs_is_number(value))) {
1233 ret = njs_value_to_integer(vm, value, &start);
1234 if (njs_slow_path(ret != NJS_OK)) {
1235 return ret;
1236 }
1237
1238 } else {
1239 start = njs_number_to_integer(njs_number(value));
1240 }
1241
1242 if (start < 0) {
1243 start = 0;
1244
1245 } else if (start > length) {
1246 start = length;
1247 }
1248
1249 end = length;
1250
1251 if (nargs > 2) {
1252 value = njs_arg(args, nargs, 2);
1253
1254 if (njs_slow_path(!njs_is_number(value))) {
1255 ret = njs_value_to_integer(vm, value, &end);
1256 if (njs_slow_path(ret != NJS_OK)) {
1257 return ret;
1258 }
1259
1260 } else {
1261 end = njs_number_to_integer(njs_number(value));
1262 }
1263
1264 if (end < 0) {
1265 end = 0;
1266
1267 } else if (end >= length) {
1268 end = length;
1269 }
1270 }
1271
1272 length = end - start;
1273
1274 if (length < 0) {
1275 length = -length;
1276 start = end;
1277 }
1278 }
1279
1280 slice.start = start;
1281 slice.length = length;
1282
1283 return njs_string_slice(vm, &vm->retval, &string, &slice);
1284 }
1285
1286
1287 static njs_int_t
njs_string_prototype_substr(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1288 njs_string_prototype_substr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1289 njs_index_t unused)
1290 {
1291 int64_t start, length, n;
1292 njs_int_t ret;
1293 njs_value_t *value;
1294 njs_slice_prop_t slice;
1295 njs_string_prop_t string;
1296
1297 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1298 if (njs_slow_path(ret != NJS_OK)) {
1299 return ret;
1300 }
1301
1302 length = njs_string_prop(&string, njs_argument(args, 0));
1303
1304 slice.string_length = length;
1305 start = 0;
1306
1307 if (nargs > 1) {
1308 value = njs_arg(args, nargs, 1);
1309
1310 if (njs_slow_path(!njs_is_number(value))) {
1311 ret = njs_value_to_integer(vm, value, &start);
1312 if (njs_slow_path(ret != NJS_OK)) {
1313 return ret;
1314 }
1315
1316 } else {
1317 start = njs_number_to_integer(njs_number(value));
1318 }
1319
1320 if (start < length) {
1321 if (start < 0) {
1322 start += length;
1323
1324 if (start < 0) {
1325 start = 0;
1326 }
1327 }
1328
1329 length -= start;
1330
1331 if (nargs > 2) {
1332 value = njs_arg(args, nargs, 2);
1333
1334 if (njs_slow_path(!njs_is_number(value))) {
1335 ret = njs_value_to_integer(vm, value, &n);
1336 if (njs_slow_path(ret != NJS_OK)) {
1337 return ret;
1338 }
1339
1340 } else {
1341 n = njs_number_to_integer(njs_number(value));
1342 }
1343
1344 if (n < 0) {
1345 length = 0;
1346
1347 } else if (n < length) {
1348 length = n;
1349 }
1350 }
1351
1352 } else {
1353 start = 0;
1354 length = 0;
1355 }
1356 }
1357
1358 slice.start = start;
1359 slice.length = length;
1360
1361 return njs_string_slice(vm, &vm->retval, &string, &slice);
1362 }
1363
1364
1365 static njs_int_t
njs_string_prototype_char_at(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1366 njs_string_prototype_char_at(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1367 njs_index_t unused)
1368 {
1369 size_t length;
1370 int64_t start;
1371 njs_int_t ret;
1372 njs_slice_prop_t slice;
1373 njs_string_prop_t string;
1374
1375 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1376 if (njs_slow_path(ret != NJS_OK)) {
1377 return ret;
1378 }
1379
1380 slice.string_length = njs_string_prop(&string, njs_argument(args, 0));
1381
1382 ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
1383 if (njs_slow_path(ret != NJS_OK)) {
1384 return ret;
1385 }
1386
1387 length = 1;
1388
1389 if (start < 0 || start >= (int64_t) slice.string_length) {
1390 start = 0;
1391 length = 0;
1392 }
1393
1394 slice.start = start;
1395 slice.length = length;
1396
1397 return njs_string_slice(vm, &vm->retval, &string, &slice);
1398 }
1399
1400
1401 static njs_int_t
njs_string_slice_prop(njs_vm_t * vm,njs_string_prop_t * string,njs_slice_prop_t * slice,njs_value_t * args,njs_uint_t nargs)1402 njs_string_slice_prop(njs_vm_t *vm, njs_string_prop_t *string,
1403 njs_slice_prop_t *slice, njs_value_t *args, njs_uint_t nargs)
1404 {
1405 slice->string_length = njs_string_prop(string, &args[0]);
1406
1407 return njs_string_slice_args(vm, slice, args, nargs);
1408 }
1409
1410
1411 static njs_int_t
njs_string_slice_args(njs_vm_t * vm,njs_slice_prop_t * slice,njs_value_t * args,njs_uint_t nargs)1412 njs_string_slice_args(njs_vm_t *vm, njs_slice_prop_t *slice, njs_value_t *args,
1413 njs_uint_t nargs)
1414 {
1415 int64_t start, end, length;
1416 njs_int_t ret;
1417 njs_value_t *value;
1418
1419 length = slice->string_length;
1420
1421 value = njs_arg(args, nargs, 1);
1422
1423 if (njs_slow_path(!njs_is_number(value))) {
1424 ret = njs_value_to_integer(vm, value, &start);
1425 if (njs_slow_path(ret != NJS_OK)) {
1426 return ret;
1427 }
1428
1429 } else {
1430 start = njs_number_to_integer(njs_number(value));
1431 }
1432
1433 if (start < 0) {
1434 start += length;
1435
1436 if (start < 0) {
1437 start = 0;
1438 }
1439 }
1440
1441 if (start >= length) {
1442 start = 0;
1443 length = 0;
1444
1445 } else {
1446 value = njs_arg(args, nargs, 2);
1447
1448 if (njs_slow_path(!njs_is_number(value))) {
1449 if (njs_is_defined(value)) {
1450 ret = njs_value_to_integer(vm, value, &end);
1451 if (njs_slow_path(ret != NJS_OK)) {
1452 return ret;
1453 }
1454
1455 } else {
1456 end = length;
1457 }
1458
1459 } else {
1460 end = njs_number_to_integer(njs_number(value));
1461 }
1462
1463 if (end < 0) {
1464 end += length;
1465 }
1466
1467 if (length >= end) {
1468 length = end - start;
1469
1470 if (length < 0) {
1471 start = 0;
1472 length = 0;
1473 }
1474
1475 } else {
1476 length -= start;
1477 }
1478 }
1479
1480 slice->start = start;
1481 slice->length = length;
1482
1483 return NJS_OK;
1484 }
1485
1486
1487 void
njs_string_slice_string_prop(njs_string_prop_t * dst,const njs_string_prop_t * string,const njs_slice_prop_t * slice)1488 njs_string_slice_string_prop(njs_string_prop_t *dst,
1489 const njs_string_prop_t *string, const njs_slice_prop_t *slice)
1490 {
1491 size_t size, n, length;
1492 const u_char *p, *start, *end;
1493
1494 length = slice->length;
1495 start = string->start;
1496
1497 if (string->size == slice->string_length) {
1498 /* Byte or ASCII string. */
1499 start += slice->start;
1500 size = slice->length;
1501
1502 if (string->length == 0) {
1503 /* Byte string. */
1504 length = 0;
1505 }
1506
1507 } else {
1508 /* UTF-8 string. */
1509 end = start + string->size;
1510
1511 if (slice->start < slice->string_length) {
1512 start = njs_string_offset(start, end, slice->start);
1513
1514 /* Evaluate size of the slice in bytes and adjust length. */
1515 p = start;
1516 n = length;
1517
1518 while (n != 0 && p < end) {
1519 p = njs_utf8_next(p, end);
1520 n--;
1521 }
1522
1523 size = p - start;
1524 length -= n;
1525
1526 } else {
1527 length = 0;
1528 size = 0;
1529 }
1530 }
1531
1532 dst->start = (u_char *) start;
1533 dst->length = length;
1534 dst->size = size;
1535 }
1536
1537
1538 njs_int_t
njs_string_slice(njs_vm_t * vm,njs_value_t * dst,const njs_string_prop_t * string,const njs_slice_prop_t * slice)1539 njs_string_slice(njs_vm_t *vm, njs_value_t *dst,
1540 const njs_string_prop_t *string, const njs_slice_prop_t *slice)
1541 {
1542 njs_string_prop_t prop;
1543
1544 njs_string_slice_string_prop(&prop, string, slice);
1545
1546 if (njs_fast_path(prop.size != 0)) {
1547 return njs_string_new(vm, dst, prop.start, prop.size, prop.length);
1548 }
1549
1550 *dst = njs_string_empty;
1551
1552 return NJS_OK;
1553 }
1554
1555
1556 static njs_int_t
njs_string_prototype_char_code_at(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1557 njs_string_prototype_char_code_at(njs_vm_t *vm, njs_value_t *args,
1558 njs_uint_t nargs, njs_index_t unused)
1559 {
1560 double num;
1561 size_t length;
1562 int64_t index;
1563 uint32_t code;
1564 njs_int_t ret;
1565 const u_char *start, *end;
1566 njs_string_prop_t string;
1567 njs_unicode_decode_t ctx;
1568
1569 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
1570 if (njs_slow_path(ret != NJS_OK)) {
1571 return ret;
1572 }
1573
1574 length = njs_string_prop(&string, njs_argument(args, 0));
1575
1576 ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &index);
1577 if (njs_slow_path(ret != NJS_OK)) {
1578 return ret;
1579 }
1580
1581 if (njs_slow_path(index < 0 || index >= (int64_t) length)) {
1582 num = NAN;
1583 goto done;
1584 }
1585
1586 if (length == string.size) {
1587 /* Byte or ASCII string. */
1588 code = string.start[index];
1589
1590 } else {
1591 njs_utf8_decode_init(&ctx);
1592
1593 /* UTF-8 string. */
1594 end = string.start + string.size;
1595 start = njs_string_offset(string.start, end, index);
1596 code = njs_utf8_decode(&ctx, &start, end);
1597 }
1598
1599 num = code;
1600
1601 done:
1602
1603 njs_set_number(&vm->retval, num);
1604
1605 return NJS_OK;
1606 }
1607
1608
1609 /*
1610 * String.bytesFrom(array-like).
1611 * Converts an array-like object containing octets into a byte string.
1612 *
1613 * String.bytesFrom(string[, encoding]).
1614 * Converts a string using provided encoding: hex, base64, base64url to
1615 * a byte string.
1616 */
1617
1618 static njs_int_t
njs_string_bytes_from(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1619 njs_string_bytes_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1620 njs_index_t unused)
1621 {
1622 njs_value_t *value;
1623
1624 value = njs_arg(args, nargs, 1);
1625
1626 if (njs_is_string(value)) {
1627 return njs_string_bytes_from_string(vm, value, njs_arg(args, nargs, 2));
1628
1629 } else if (njs_is_object(value)) {
1630
1631 if (njs_is_object_string(value)) {
1632 value = njs_object_value(value);
1633 return njs_string_bytes_from_string(vm, value,
1634 njs_arg(args, nargs, 2));
1635 }
1636
1637 return njs_string_bytes_from_array_like(vm, value);
1638 }
1639
1640 njs_type_error(vm, "value must be a string or array-like object");
1641
1642 return NJS_ERROR;
1643 }
1644
1645
1646 static njs_int_t
njs_string_bytes_from_array_like(njs_vm_t * vm,njs_value_t * value)1647 njs_string_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value)
1648 {
1649 u_char *p;
1650 int64_t length;
1651 uint32_t u32;
1652 njs_int_t ret;
1653 njs_array_t *array;
1654 njs_value_t *octet, index, prop;
1655 njs_array_buffer_t *buffer;
1656
1657 array = NULL;
1658 buffer = NULL;
1659
1660 switch (value->type) {
1661 case NJS_ARRAY:
1662 array = njs_array(value);
1663 length = array->length;
1664 break;
1665
1666 case NJS_ARRAY_BUFFER:
1667 case NJS_TYPED_ARRAY:
1668
1669 if (njs_is_typed_array(value)) {
1670 buffer = njs_typed_array(value)->buffer;
1671
1672 } else {
1673 buffer = njs_array_buffer(value);
1674 }
1675
1676 length = buffer->size;
1677 break;
1678
1679 default:
1680 ret = njs_object_length(vm, value, &length);
1681 if (njs_slow_path(ret == NJS_ERROR)) {
1682 return ret;
1683 }
1684 }
1685
1686 p = njs_string_alloc(vm, &vm->retval, length, 0);
1687 if (njs_slow_path(p == NULL)) {
1688 return NJS_ERROR;
1689 }
1690
1691 if (array != NULL) {
1692 octet = array->start;
1693
1694 while (length != 0) {
1695 ret = njs_value_to_uint32(vm, octet, &u32);
1696 if (njs_slow_path(ret != NJS_OK)) {
1697 return ret;
1698 }
1699
1700 *p++ = (u_char) u32;
1701 octet++;
1702 length--;
1703 }
1704
1705 } else if (buffer != NULL) {
1706 memcpy(p, buffer->u.u8, length);
1707
1708 } else {
1709 p += length - 1;
1710
1711 while (length != 0) {
1712 njs_set_number(&index, length - 1);
1713
1714 ret = njs_value_property(vm, value, &index, &prop);
1715 if (njs_slow_path(ret == NJS_ERROR)) {
1716 return ret;
1717 }
1718
1719 ret = njs_value_to_uint32(vm, &prop, &u32);
1720 if (njs_slow_path(ret != NJS_OK)) {
1721 return ret;
1722 }
1723
1724 *p-- = (u_char) u32;
1725 length--;
1726 }
1727 }
1728
1729 return NJS_OK;
1730 }
1731
1732
1733 static njs_int_t
njs_string_bytes_from_string(njs_vm_t * vm,const njs_value_t * string,const njs_value_t * encoding)1734 njs_string_bytes_from_string(njs_vm_t *vm, const njs_value_t *string,
1735 const njs_value_t *encoding)
1736 {
1737 njs_str_t enc, str;
1738
1739 if (!njs_is_string(encoding)) {
1740 njs_type_error(vm, "\"encoding\" must be a string");
1741 return NJS_ERROR;
1742 }
1743
1744 njs_string_get(encoding, &enc);
1745 njs_string_get(string, &str);
1746
1747 if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) {
1748 return njs_string_decode_hex(vm, &vm->retval, &str);
1749
1750 } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) {
1751 return njs_string_decode_base64(vm, &vm->retval, &str);
1752
1753 } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) {
1754 return njs_string_decode_base64url(vm, &vm->retval, &str);
1755 }
1756
1757 njs_type_error(vm, "Unknown encoding: \"%V\"", &enc);
1758
1759 return NJS_ERROR;
1760 }
1761
1762
1763 size_t
njs_decode_hex_length(const njs_str_t * src,size_t * out_size)1764 njs_decode_hex_length(const njs_str_t *src, size_t *out_size)
1765 {
1766 if (out_size != NULL) {
1767 *out_size = src->length / 2;
1768 }
1769
1770 return 0;
1771 }
1772
1773
1774 void
njs_decode_hex(njs_str_t * dst,const njs_str_t * src)1775 njs_decode_hex(njs_str_t *dst, const njs_str_t *src)
1776 {
1777 u_char *p;
1778 size_t len;
1779 njs_int_t c;
1780 njs_uint_t i, n;
1781 const u_char *start;
1782
1783 n = 0;
1784 p = dst->start;
1785
1786 start = src->start;
1787 len = src->length;
1788
1789 for (i = 0; i < len; i++) {
1790 c = njs_char_to_hex(start[i]);
1791 if (njs_slow_path(c < 0)) {
1792 break;
1793 }
1794
1795 n = n * 16 + c;
1796
1797 if ((i & 1) != 0) {
1798 *p++ = (u_char) n;
1799 n = 0;
1800 }
1801 }
1802
1803 dst->length -= (dst->start + dst->length) - p;
1804 }
1805
1806
1807 void
njs_decode_utf8(njs_str_t * dst,const njs_str_t * src)1808 njs_decode_utf8(njs_str_t *dst, const njs_str_t *src)
1809 {
1810 njs_unicode_decode_t ctx;
1811
1812 njs_utf8_decode_init(&ctx);
1813
1814 (void) njs_utf8_stream_encode(&ctx, src->start, src->start + src->length,
1815 dst->start, 1, 0);
1816 }
1817
1818
1819 size_t
njs_decode_utf8_length(const njs_str_t * src,size_t * out_size)1820 njs_decode_utf8_length(const njs_str_t *src, size_t *out_size)
1821 {
1822 njs_unicode_decode_t ctx;
1823
1824 njs_utf8_decode_init(&ctx);
1825
1826 return njs_utf8_stream_length(&ctx, src->start, src->length, 1, 0,
1827 out_size);
1828 }
1829
1830
1831 njs_int_t
njs_string_decode_utf8(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)1832 njs_string_decode_utf8(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
1833 {
1834 size_t length;
1835 njs_str_t dst;
1836
1837 length = njs_decode_utf8_length(src, &dst.length);
1838 dst.start = njs_string_alloc(vm, value, dst.length, length);
1839
1840 if (njs_fast_path(dst.start != NULL)) {
1841 njs_decode_utf8(&dst, src);
1842 return NJS_OK;
1843 }
1844
1845 return NJS_ERROR;
1846 }
1847
1848
1849 njs_int_t
njs_string_decode_hex(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)1850 njs_string_decode_hex(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
1851 {
1852 size_t size, length;
1853 njs_str_t dst;
1854
1855 length = njs_decode_hex_length(src, &size);
1856
1857 if (njs_slow_path(size == 0)) {
1858 vm->retval = njs_string_empty;
1859 return NJS_OK;
1860 }
1861
1862 dst.start = njs_string_alloc(vm, value, size, length);
1863 if (njs_slow_path(dst.start == NULL)) {
1864 return NJS_ERROR;
1865 }
1866
1867 dst.length = size;
1868
1869 njs_decode_hex(&dst, src);
1870
1871 if (njs_slow_path(dst.length != size)) {
1872 njs_string_truncate(value, dst.length, 0);
1873 }
1874
1875 return NJS_OK;
1876 }
1877
1878
1879 static size_t
njs_decode_base64_length_core(const njs_str_t * src,const u_char * basis,size_t * out_size)1880 njs_decode_base64_length_core(const njs_str_t *src, const u_char *basis,
1881 size_t *out_size)
1882 {
1883 uint pad;
1884 size_t len;
1885
1886 for (len = 0; len < src->length; len++) {
1887 if (basis[src->start[len]] == 77) {
1888 break;
1889 }
1890 }
1891
1892 pad = 0;
1893
1894 if (len % 4 != 0) {
1895 pad = 4 - (len % 4);
1896 len += pad;
1897 }
1898
1899 len = njs_base64_decoded_length(len, pad);
1900
1901 if (out_size != NULL) {
1902 *out_size = len;
1903 }
1904
1905 return 0;
1906 }
1907
1908
1909 size_t
njs_decode_base64_length(const njs_str_t * src,size_t * out_size)1910 njs_decode_base64_length(const njs_str_t *src, size_t *out_size)
1911 {
1912 return njs_decode_base64_length_core(src, njs_basis64, out_size);
1913 }
1914
1915
1916 size_t
njs_decode_base64url_length(const njs_str_t * src,size_t * out_size)1917 njs_decode_base64url_length(const njs_str_t *src, size_t *out_size)
1918 {
1919 return njs_decode_base64_length_core(src, njs_basis64url, out_size);
1920 }
1921
1922
1923 static void
njs_decode_base64_core(njs_str_t * dst,const njs_str_t * src,const u_char * basis)1924 njs_decode_base64_core(njs_str_t *dst, const njs_str_t *src,
1925 const u_char *basis)
1926 {
1927 size_t len;
1928 u_char *d, *s;
1929
1930 s = src->start;
1931 d = dst->start;
1932
1933 len = dst->length;
1934
1935 while (len >= 3) {
1936 *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
1937 *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
1938 *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
1939
1940 s += 4;
1941 len -= 3;
1942 }
1943
1944 if (len >= 1) {
1945 *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
1946 }
1947
1948 if (len >= 2) {
1949 *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
1950 }
1951 }
1952
1953
1954 void
njs_decode_base64(njs_str_t * dst,const njs_str_t * src)1955 njs_decode_base64(njs_str_t *dst, const njs_str_t *src)
1956 {
1957 njs_decode_base64_core(dst, src, njs_basis64);
1958 }
1959
1960
1961 void
njs_decode_base64url(njs_str_t * dst,const njs_str_t * src)1962 njs_decode_base64url(njs_str_t *dst, const njs_str_t *src)
1963 {
1964 njs_decode_base64_core(dst, src, njs_basis64url);
1965 }
1966
1967
1968 static njs_int_t
njs_string_decode_base64_core(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src,njs_bool_t url)1969 njs_string_decode_base64_core(njs_vm_t *vm, njs_value_t *value,
1970 const njs_str_t *src, njs_bool_t url)
1971 {
1972 size_t length;
1973 const u_char *basis;
1974 njs_str_t dst;
1975
1976 basis = (url) ? njs_basis64url : njs_basis64;
1977
1978 length = njs_decode_base64_length_core(src, basis, &dst.length);
1979
1980 if (njs_slow_path(dst.length == 0)) {
1981 vm->retval = njs_string_empty;
1982 return NJS_OK;
1983 }
1984
1985 dst.start = njs_string_alloc(vm, value, dst.length, length);
1986 if (njs_slow_path(dst.start == NULL)) {
1987 return NJS_ERROR;
1988 }
1989
1990 njs_decode_base64_core(&dst, src, basis);
1991
1992 return NJS_OK;
1993 }
1994
1995
1996 njs_int_t
njs_string_decode_base64(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)1997 njs_string_decode_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
1998 {
1999 return njs_string_decode_base64_core(vm, value, src, 0);
2000 }
2001
2002
2003 njs_int_t
njs_string_decode_base64url(njs_vm_t * vm,njs_value_t * value,const njs_str_t * src)2004 njs_string_decode_base64url(njs_vm_t *vm, njs_value_t *value,
2005 const njs_str_t *src)
2006 {
2007 return njs_string_decode_base64_core(vm, value, src, 1);
2008 }
2009
2010
2011 static njs_int_t
njs_string_from_char_code(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t is_point)2012 njs_string_from_char_code(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2013 njs_index_t is_point)
2014 {
2015 double num;
2016 u_char *p, *start, *end;
2017 ssize_t len;
2018 int32_t code;
2019 uint32_t cp;
2020 uint64_t length, size;
2021 njs_int_t ret;
2022 njs_uint_t i;
2023 njs_unicode_decode_t ctx;
2024 u_char buf[4];
2025
2026 size = 0;
2027 length = 0;
2028
2029 cp = 0x00;
2030 end = buf + sizeof(buf);
2031
2032 njs_utf16_decode_init(&ctx);
2033
2034 for (i = 1; i < nargs; i++) {
2035 if (!njs_is_numeric(&args[i])) {
2036 ret = njs_value_to_numeric(vm, &args[i], &args[i]);
2037 if (ret != NJS_OK) {
2038 return ret;
2039 }
2040 }
2041
2042 if (is_point) {
2043 num = njs_number(&args[i]);
2044 if (isnan(num)) {
2045 goto range_error;
2046 }
2047
2048 code = num;
2049
2050 if (code != num || code < 0 || code > 0x10FFFF) {
2051 goto range_error;
2052 }
2053
2054 } else {
2055 code = njs_number_to_uint16(njs_number(&args[i]));
2056 }
2057
2058 start = buf;
2059 len = njs_utf16_encode(code, &start, end);
2060
2061 start = buf;
2062 cp = njs_utf16_decode(&ctx, (const u_char **) &start, start + len);
2063
2064 if (cp > NJS_UNICODE_MAX_CODEPOINT) {
2065 if (cp == NJS_UNICODE_CONTINUE) {
2066 continue;
2067 }
2068
2069 cp = NJS_UNICODE_REPLACEMENT;
2070 }
2071
2072 size += njs_utf8_size(cp);
2073 length++;
2074 }
2075
2076 if (cp == NJS_UNICODE_CONTINUE) {
2077 size += njs_utf8_size(NJS_UNICODE_REPLACEMENT);
2078 length++;
2079 }
2080
2081 p = njs_string_alloc(vm, &vm->retval, size, length);
2082 if (njs_slow_path(p == NULL)) {
2083 return NJS_ERROR;
2084 }
2085
2086 njs_utf16_decode_init(&ctx);
2087
2088 for (i = 1; i < nargs; i++) {
2089 if (is_point) {
2090 code = njs_number(&args[i]);
2091
2092 } else {
2093 code = njs_number_to_uint16(njs_number(&args[i]));
2094 }
2095
2096 start = buf;
2097 len = njs_utf16_encode(code, &start, end);
2098
2099 start = buf;
2100 cp = njs_utf16_decode(&ctx, (const u_char **) &start, start + len);
2101
2102 if (cp > NJS_UNICODE_MAX_CODEPOINT) {
2103 if (cp == NJS_UNICODE_CONTINUE && i + 1 != nargs) {
2104 continue;
2105 }
2106
2107 cp = NJS_UNICODE_REPLACEMENT;
2108 }
2109
2110 p = njs_utf8_encode(p, cp);
2111 }
2112
2113 return NJS_OK;
2114
2115 range_error:
2116
2117 njs_range_error(vm, NULL);
2118
2119 return NJS_ERROR;
2120 }
2121
2122
2123 static int64_t
njs_string_index_of(njs_string_prop_t * string,njs_string_prop_t * search,size_t from)2124 njs_string_index_of(njs_string_prop_t *string, njs_string_prop_t *search,
2125 size_t from)
2126 {
2127 size_t index, length, search_length;
2128 const u_char *p, *end;
2129
2130 length = (string->length == 0) ? string->size : string->length;
2131
2132 if (njs_slow_path(search->size == 0)) {
2133 return (from < length) ? from : length;
2134 }
2135
2136 index = from;
2137 search_length = (search->length == 0) ? search->size : search->length;
2138
2139 if (length - index >= search_length) {
2140 end = string->start + string->size;
2141
2142 if (string->size == length) {
2143 /* Byte or ASCII string. */
2144
2145 end -= (search->size - 1);
2146
2147 for (p = string->start + index; p < end; p++) {
2148 if (memcmp(p, search->start, search->size) == 0) {
2149 return index;
2150 }
2151
2152 index++;
2153 }
2154
2155 } else {
2156 /* UTF-8 string. */
2157
2158 p = njs_string_offset(string->start, end, index);
2159 end -= search->size - 1;
2160
2161 while (p < end) {
2162 if (memcmp(p, search->start, search->size) == 0) {
2163 return index;
2164 }
2165
2166 index++;
2167 p = njs_utf8_next(p, end);
2168 }
2169 }
2170 }
2171
2172 return -1;
2173 }
2174
2175
2176 static njs_int_t
njs_string_prototype_index_of(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2177 njs_string_prototype_index_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2178 njs_index_t unused)
2179 {
2180 int64_t from, length;
2181 njs_int_t ret;
2182 njs_value_t *this, *search, *pos, search_lvalue, pos_lvalue;
2183 njs_string_prop_t string, s;
2184
2185 this = njs_argument(args, 0);
2186
2187 if (njs_slow_path(njs_is_null_or_undefined(this))) {
2188 njs_type_error(vm, "cannot convert \"%s\"to object",
2189 njs_type_string(this->type));
2190 return NJS_ERROR;
2191 }
2192
2193 ret = njs_value_to_string(vm, this, this);
2194 if (njs_slow_path(ret != NJS_OK)) {
2195 return NJS_ERROR;
2196 }
2197
2198 search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
2199 ret = njs_value_to_string(vm, search, search);
2200 if (njs_slow_path(ret != NJS_OK)) {
2201 return ret;
2202 }
2203
2204 pos = njs_lvalue_arg(&pos_lvalue, args, nargs, 2);
2205 ret = njs_value_to_integer(vm, pos, &from);
2206 if (njs_slow_path(ret != NJS_OK)) {
2207 return ret;
2208 }
2209
2210 length = njs_string_prop(&string, this);
2211 (void) njs_string_prop(&s, search);
2212
2213 from = njs_min(njs_max(from, 0), length);
2214
2215 njs_set_number(&vm->retval, njs_string_index_of(&string, &s, from));
2216
2217 return NJS_OK;
2218 }
2219
2220
2221 static njs_int_t
njs_string_prototype_last_index_of(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2222 njs_string_prototype_last_index_of(njs_vm_t *vm, njs_value_t *args,
2223 njs_uint_t nargs, njs_index_t unused)
2224 {
2225 double pos;
2226 int64_t index, start, length, search_length;
2227 njs_int_t ret;
2228 njs_value_t *this, *search, search_lvalue;
2229 const u_char *p, *end;
2230 njs_string_prop_t string, s;
2231
2232 this = njs_argument(args, 0);
2233
2234 if (njs_slow_path(njs_is_null_or_undefined(this))) {
2235 njs_type_error(vm, "cannot convert \"%s\"to object",
2236 njs_type_string(this->type));
2237 return NJS_ERROR;
2238 }
2239
2240 ret = njs_value_to_string(vm, this, this);
2241 if (njs_slow_path(ret != NJS_OK)) {
2242 return NJS_ERROR;
2243 }
2244
2245 search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
2246 ret = njs_value_to_string(vm, search, search);
2247 if (njs_slow_path(ret != NJS_OK)) {
2248 return ret;
2249 }
2250
2251 ret = njs_value_to_number(vm, njs_arg(args, nargs, 2), &pos);
2252 if (njs_slow_path(ret != NJS_OK)) {
2253 return ret;
2254 }
2255
2256 if (!isnan(pos)) {
2257 start = njs_number_to_integer(pos);
2258
2259 } else {
2260 start = INT64_MAX;
2261 }
2262
2263 length = njs_string_prop(&string, this);
2264
2265 start = njs_min(njs_max(start, 0), length);
2266
2267 search_length = njs_string_prop(&s, search);
2268
2269 index = length - search_length;
2270
2271 if (index > start) {
2272 index = start;
2273 }
2274
2275 end = string.start + string.size;
2276
2277 if (string.size == (size_t) length) {
2278 /* Byte or ASCII string. */
2279
2280 p = &string.start[index];
2281
2282 if (p > end - s.size) {
2283 p = end - s.size;
2284 }
2285
2286 for (; p >= string.start; p--) {
2287 if (memcmp(p, s.start, s.size) == 0) {
2288 index = p - string.start;
2289 goto done;
2290 }
2291 }
2292
2293 index = -1;
2294
2295 } else {
2296 /* UTF-8 string. */
2297
2298 if (index < 0 || index == length) {
2299 index = (search_length == 0) ? index : -1;
2300 goto done;
2301 }
2302
2303 p = njs_string_offset(string.start, end, index);
2304
2305 for (; p >= string.start; p = njs_utf8_prev(p)) {
2306 if ((p + s.size) <= end && memcmp(p, s.start, s.size) == 0) {
2307 goto done;
2308 }
2309
2310 index--;
2311 }
2312
2313 index = -1;
2314 }
2315
2316 done:
2317
2318 njs_set_number(&vm->retval, index);
2319
2320 return NJS_OK;
2321 }
2322
2323
2324 static njs_int_t
njs_string_prototype_includes(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2325 njs_string_prototype_includes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2326 njs_index_t unused)
2327 {
2328 int64_t index, length, search_length;
2329 njs_int_t ret;
2330 njs_value_t *value;
2331 const u_char *p, *end;
2332 const njs_value_t *retval;
2333 njs_string_prop_t string, search;
2334
2335 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
2336 if (njs_slow_path(ret != NJS_OK)) {
2337 return ret;
2338 }
2339
2340 retval = &njs_value_true;
2341
2342 if (nargs > 1) {
2343 value = njs_argument(args, 1);
2344
2345 if (njs_slow_path(!njs_is_string(value))) {
2346 ret = njs_value_to_string(vm, value, value);
2347 if (njs_slow_path(ret != NJS_OK)) {
2348 return ret;
2349 }
2350 }
2351
2352 search_length = njs_string_prop(&search, value);
2353
2354 if (nargs > 2) {
2355 value = njs_argument(args, 2);
2356
2357 if (njs_slow_path(!njs_is_number(value))) {
2358 ret = njs_value_to_integer(vm, value, &index);
2359 if (njs_slow_path(ret != NJS_OK)) {
2360 return ret;
2361 }
2362
2363 } else {
2364 index = njs_number_to_integer(njs_number(value));
2365 }
2366
2367 if (index < 0) {
2368 index = 0;
2369 }
2370
2371 } else {
2372 index = 0;
2373 }
2374
2375 if (search_length == 0) {
2376 goto done;
2377 }
2378
2379 length = njs_string_prop(&string, &args[0]);
2380
2381 if (length - index >= search_length) {
2382 end = string.start + string.size;
2383
2384 if (string.size == (size_t) length) {
2385 /* Byte or ASCII string. */
2386 p = string.start + index;
2387
2388 } else {
2389 /* UTF-8 string. */
2390 p = njs_string_offset(string.start, end, index);
2391 }
2392
2393 end -= search.size - 1;
2394
2395 while (p < end) {
2396 if (memcmp(p, search.start, search.size) == 0) {
2397 goto done;
2398 }
2399
2400 p++;
2401 }
2402 }
2403 }
2404
2405 retval = &njs_value_false;
2406
2407 done:
2408
2409 vm->retval = *retval;
2410
2411 return NJS_OK;
2412 }
2413
2414
2415 static njs_int_t
njs_string_prototype_starts_or_ends_with(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t starts)2416 njs_string_prototype_starts_or_ends_with(njs_vm_t *vm, njs_value_t *args,
2417 njs_uint_t nargs, njs_index_t starts)
2418 {
2419 int64_t index, length, search_length;
2420 njs_int_t ret;
2421 njs_value_t *value, lvalue;
2422 const u_char *p, *end;
2423 const njs_value_t *retval;
2424 njs_string_prop_t string, search;
2425
2426 retval = &njs_value_true;
2427
2428 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
2429 if (njs_slow_path(ret != NJS_OK)) {
2430 return ret;
2431 }
2432
2433 value = njs_lvalue_arg(&lvalue, args, nargs, 1);
2434
2435 if (njs_slow_path(!njs_is_string(value))) {
2436 ret = njs_value_to_string(vm, value, value);
2437 if (njs_slow_path(ret != NJS_OK)) {
2438 return ret;
2439 }
2440 }
2441
2442 search_length = njs_string_prop(&search, value);
2443
2444 value = njs_arg(args, nargs, 2);
2445
2446 if (njs_slow_path(!njs_is_number(value))) {
2447 index = -1;
2448
2449 if (!njs_is_undefined(value)) {
2450 ret = njs_value_to_integer(vm, value, &index);
2451 if (njs_slow_path(ret != NJS_OK)) {
2452 return ret;
2453 }
2454 }
2455
2456 } else {
2457 index = njs_number_to_integer(njs_number(value));
2458 }
2459
2460 if (search_length == 0) {
2461 goto done;
2462 }
2463
2464 if (nargs > 1) {
2465 length = njs_string_prop(&string, &args[0]);
2466
2467 if (starts) {
2468 if (index < 0) {
2469 index = 0;
2470 }
2471
2472 if (length - index < search_length) {
2473 goto small;
2474 }
2475
2476 } else {
2477 if (index < 0 || index > length) {
2478 index = length;
2479 }
2480
2481 index -= search_length;
2482
2483 if (index < 0) {
2484 goto small;
2485 }
2486 }
2487
2488 end = string.start + string.size;
2489
2490 if (string.size == (size_t) length) {
2491 /* Byte or ASCII string. */
2492 p = string.start + index;
2493
2494 } else {
2495 /* UTF-8 string. */
2496 p = njs_string_offset(string.start, end, index);
2497 }
2498
2499 if ((size_t) (end - p) >= search.size
2500 && memcmp(p, search.start, search.size) == 0)
2501 {
2502 goto done;
2503 }
2504 }
2505
2506 small:
2507
2508 retval = &njs_value_false;
2509
2510 done:
2511
2512 vm->retval = *retval;
2513
2514 return NJS_OK;
2515 }
2516
2517
2518 /*
2519 * njs_string_offset() assumes that index is correct.
2520 */
2521
2522 const u_char *
njs_string_offset(const u_char * start,const u_char * end,size_t index)2523 njs_string_offset(const u_char *start, const u_char *end, size_t index)
2524 {
2525 uint32_t *map;
2526 njs_uint_t skip;
2527
2528 if (index >= NJS_STRING_MAP_STRIDE) {
2529 map = njs_string_map_start(end);
2530
2531 if (map[0] == 0) {
2532 njs_string_offset_map_init(start, end - start);
2533 }
2534
2535 start += map[index / NJS_STRING_MAP_STRIDE - 1];
2536 }
2537
2538 for (skip = index % NJS_STRING_MAP_STRIDE; skip != 0; skip--) {
2539 start = njs_utf8_next(start, end);
2540 }
2541
2542 return start;
2543 }
2544
2545
2546 /*
2547 * njs_string_index() assumes that offset is correct.
2548 */
2549
2550 uint32_t
njs_string_index(njs_string_prop_t * string,uint32_t offset)2551 njs_string_index(njs_string_prop_t *string, uint32_t offset)
2552 {
2553 uint32_t *map, last, index;
2554 const u_char *p, *start, *end;
2555
2556 if (string->size == string->length) {
2557 return offset;
2558 }
2559
2560 last = 0;
2561 index = 0;
2562
2563 if (string->length > NJS_STRING_MAP_STRIDE) {
2564
2565 end = string->start + string->size;
2566 map = njs_string_map_start(end);
2567
2568 if (map[0] == 0) {
2569 njs_string_offset_map_init(string->start, string->size);
2570 }
2571
2572 while (index + NJS_STRING_MAP_STRIDE < string->length
2573 && *map <= offset)
2574 {
2575 last = *map++;
2576 index += NJS_STRING_MAP_STRIDE;
2577 }
2578 }
2579
2580 p = string->start + last;
2581 start = string->start + offset;
2582 end = string->start + string->size;
2583
2584 while (p < start) {
2585 index++;
2586 p = njs_utf8_next(p, end);
2587 }
2588
2589 return index;
2590 }
2591
2592
2593 void
njs_string_offset_map_init(const u_char * start,size_t size)2594 njs_string_offset_map_init(const u_char *start, size_t size)
2595 {
2596 size_t offset;
2597 uint32_t *map;
2598 njs_uint_t n;
2599 const u_char *p, *end;
2600
2601 end = start + size;
2602 map = njs_string_map_start(end);
2603 p = start;
2604 n = 0;
2605 offset = NJS_STRING_MAP_STRIDE;
2606
2607 do {
2608 if (offset == 0) {
2609 map[n++] = p - start;
2610 offset = NJS_STRING_MAP_STRIDE;
2611 }
2612
2613 /* The UTF-8 string should be valid since its length is known. */
2614 p = njs_utf8_next(p, end);
2615
2616 offset--;
2617
2618 } while (p < end);
2619 }
2620
2621
2622 /*
2623 * The method supports only simple folding. For example, Turkish "İ"
2624 * folding "\u0130" to "\u0069\u0307" is not supported.
2625 */
2626
2627 static njs_int_t
njs_string_prototype_to_lower_case(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2628 njs_string_prototype_to_lower_case(njs_vm_t *vm, njs_value_t *args,
2629 njs_uint_t nargs, njs_index_t unused)
2630 {
2631 size_t size, length;
2632 u_char *p;
2633 uint32_t code;
2634 njs_int_t ret;
2635 const u_char *s, *end;
2636 njs_string_prop_t string;
2637
2638 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
2639 if (njs_slow_path(ret != NJS_OK)) {
2640 return ret;
2641 }
2642
2643 (void) njs_string_prop(&string, njs_argument(args, 0));
2644
2645 if (string.length == 0 || string.length == string.size) {
2646 /* Byte or ASCII string. */
2647
2648 p = njs_string_alloc(vm, &vm->retval, string.size, string.length);
2649 if (njs_slow_path(p == NULL)) {
2650 return NJS_ERROR;
2651 }
2652
2653 s = string.start;
2654 size = string.size;
2655
2656 while (size != 0) {
2657 *p++ = njs_lower_case(*s++);
2658 size--;
2659 }
2660
2661 } else {
2662 /* UTF-8 string. */
2663 s = string.start;
2664 end = s + string.size;
2665 length = string.length;
2666
2667 size = 0;
2668
2669 while (length != 0) {
2670 code = njs_utf8_lower_case(&s, end);
2671 size += njs_utf8_size(code);
2672 length--;
2673 }
2674
2675 p = njs_string_alloc(vm, &vm->retval, size, string.length);
2676 if (njs_slow_path(p == NULL)) {
2677 return NJS_ERROR;
2678 }
2679
2680 s = string.start;
2681 length = string.length;
2682
2683 while (length != 0) {
2684 code = njs_utf8_lower_case(&s, end);
2685 p = njs_utf8_encode(p, code);
2686 length--;
2687 }
2688 }
2689
2690 return NJS_OK;
2691 }
2692
2693
2694 /*
2695 * The method supports only simple folding. For example, German "ß"
2696 * folding "\u00DF" to "\u0053\u0053" is not supported.
2697 */
2698
2699 static njs_int_t
njs_string_prototype_to_upper_case(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2700 njs_string_prototype_to_upper_case(njs_vm_t *vm, njs_value_t *args,
2701 njs_uint_t nargs, njs_index_t unused)
2702 {
2703 size_t size, length;
2704 u_char *p;
2705 uint32_t code;
2706 njs_int_t ret;
2707 const u_char *s, *end;
2708 njs_string_prop_t string;
2709
2710 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
2711 if (njs_slow_path(ret != NJS_OK)) {
2712 return ret;
2713 }
2714
2715 (void) njs_string_prop(&string, njs_argument(args, 0));
2716
2717 if (string.length == 0 || string.length == string.size) {
2718 /* Byte or ASCII string. */
2719
2720 p = njs_string_alloc(vm, &vm->retval, string.size, string.length);
2721 if (njs_slow_path(p == NULL)) {
2722 return NJS_ERROR;
2723 }
2724
2725 s = string.start;
2726 size = string.size;
2727
2728 while (size != 0) {
2729 *p++ = njs_upper_case(*s++);
2730 size--;
2731 }
2732
2733 } else {
2734 /* UTF-8 string. */
2735 s = string.start;
2736 end = s + string.size;
2737 length = string.length;
2738
2739 size = 0;
2740
2741 while (length != 0) {
2742 code = njs_utf8_upper_case(&s, end);
2743 size += njs_utf8_size(code);
2744 length--;
2745 }
2746
2747 p = njs_string_alloc(vm, &vm->retval, size, string.length);
2748 if (njs_slow_path(p == NULL)) {
2749 return NJS_ERROR;
2750 }
2751
2752 s = string.start;
2753 length = string.length;
2754
2755 while (length != 0) {
2756 code = njs_utf8_upper_case(&s, end);
2757 p = njs_utf8_encode(p, code);
2758 length--;
2759 }
2760 }
2761
2762 return NJS_OK;
2763 }
2764
2765
2766 static njs_int_t
njs_string_prototype_trim(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t mode)2767 njs_string_prototype_trim(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2768 njs_index_t mode)
2769 {
2770 uint32_t u, trim, length;
2771 njs_int_t ret;
2772 njs_value_t *value;
2773 const u_char *p, *prev, *start, *end;
2774 njs_string_prop_t string;
2775 njs_unicode_decode_t ctx;
2776
2777 value = njs_argument(args, 0);
2778 ret = njs_string_object_validate(vm, value);
2779 if (njs_slow_path(ret != NJS_OK)) {
2780 return ret;
2781 }
2782
2783 trim = 0;
2784
2785 njs_string_prop(&string, value);
2786
2787 start = string.start;
2788 end = string.start + string.size;
2789
2790 if (string.length == 0 || string.length == string.size) {
2791 /* Byte or ASCII string. */
2792
2793 if (mode & NJS_TRIM_START) {
2794 for ( ;; ) {
2795 if (start == end) {
2796 goto empty;
2797 }
2798
2799 if (njs_is_whitespace(*start)) {
2800 start++;
2801 trim++;
2802 continue;
2803 }
2804
2805 break;
2806 }
2807 }
2808
2809 if (mode & NJS_TRIM_END) {
2810 for ( ;; ) {
2811 if (start == end) {
2812 goto empty;
2813 }
2814
2815 end--;
2816
2817 if (njs_is_whitespace(*end)) {
2818 trim++;
2819 continue;
2820 }
2821
2822 end++;
2823 break;
2824 }
2825 }
2826
2827 } else {
2828 /* UTF-8 string. */
2829
2830 if (mode & NJS_TRIM_START) {
2831 njs_utf8_decode_init(&ctx);
2832
2833 for ( ;; ) {
2834 if (start == end) {
2835 goto empty;
2836 }
2837
2838 p = start;
2839 u = njs_utf8_decode(&ctx, &start, end);
2840
2841 if (njs_utf8_is_whitespace(u)) {
2842 trim++;
2843 continue;
2844 }
2845
2846 start = p;
2847 break;
2848 }
2849 }
2850
2851 if (mode & NJS_TRIM_END) {
2852 prev = end;
2853
2854 njs_utf8_decode_init(&ctx);
2855
2856 for ( ;; ) {
2857 if (start == prev) {
2858 goto empty;
2859 }
2860
2861 prev = njs_utf8_prev(prev);
2862 p = prev;
2863 u = njs_utf8_decode(&ctx, &p, end);
2864
2865 if (njs_utf8_is_whitespace(u)) {
2866 trim++;
2867 continue;
2868 }
2869
2870 end = p;
2871 break;
2872 }
2873 }
2874 }
2875
2876 if (trim == 0) {
2877 /* GC: retain. */
2878 vm->retval = *value;
2879
2880 return NJS_OK;
2881 }
2882
2883 length = (string.length != 0) ? string.length - trim : 0;
2884
2885 return njs_string_new(vm, &vm->retval, start, end - start, length);
2886
2887 empty:
2888
2889 vm->retval = njs_string_empty;
2890
2891 return NJS_OK;
2892 }
2893
2894
2895 static njs_int_t
njs_string_prototype_repeat(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)2896 njs_string_prototype_repeat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2897 njs_index_t unused)
2898 {
2899 u_char *p;
2900 int64_t n, max;
2901 uint64_t size, length;
2902 njs_int_t ret;
2903 njs_value_t *this;
2904 njs_string_prop_t string;
2905
2906 this = njs_argument(args, 0);
2907
2908 if (njs_slow_path(njs_is_null_or_undefined(this))) {
2909 njs_type_error(vm, "cannot convert \"%s\"to object",
2910 njs_type_string(this->type));
2911 return NJS_ERROR;
2912 }
2913
2914 ret = njs_value_to_string(vm, this, this);
2915 if (njs_slow_path(ret != NJS_OK)) {
2916 return ret;
2917 }
2918
2919 ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &n);
2920 if (njs_slow_path(ret != NJS_OK)) {
2921 return ret;
2922 }
2923
2924 if (njs_slow_path(n < 0 || n == INT64_MAX)) {
2925 njs_range_error(vm, NULL);
2926 return NJS_ERROR;
2927 }
2928
2929 (void) njs_string_prop(&string, this);
2930
2931 if (njs_slow_path(n == 0 || string.size == 0)) {
2932 vm->retval = njs_string_empty;
2933 return NJS_OK;
2934 }
2935
2936 max = NJS_STRING_MAX_LENGTH / string.size;
2937
2938 if (njs_slow_path(n >= max)) {
2939 njs_range_error(vm, NULL);
2940 return NJS_ERROR;
2941 }
2942
2943 size = string.size * n;
2944 length = string.length * n;
2945
2946 p = njs_string_alloc(vm, &vm->retval, size, length);
2947 if (njs_slow_path(p == NULL)) {
2948 return NJS_ERROR;
2949 }
2950
2951 while (n != 0) {
2952 p = memcpy(p, string.start, string.size);
2953 p += string.size;
2954 n--;
2955 }
2956
2957 return NJS_OK;
2958 }
2959
2960
2961 static njs_int_t
njs_string_prototype_pad(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t pad_start)2962 njs_string_prototype_pad(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
2963 njs_index_t pad_start)
2964 {
2965 u_char *p, *start;
2966 size_t padding, trunc, new_size;
2967 int64_t length, new_length;
2968 uint32_t n, pad_length;
2969 njs_int_t ret;
2970 njs_value_t *value, *pad;
2971 const u_char *end;
2972 njs_string_prop_t string, pad_string;
2973
2974 static const njs_value_t string_space = njs_string(" ");
2975
2976 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
2977 if (njs_slow_path(ret != NJS_OK)) {
2978 return ret;
2979 }
2980
2981 length = njs_string_prop(&string, njs_argument(args, 0));
2982
2983 new_length = 0;
2984
2985 if (nargs > 1) {
2986 value = njs_argument(args, 1);
2987
2988 if (njs_slow_path(!njs_is_number(value))) {
2989 ret = njs_value_to_integer(vm, value, &new_length);
2990 if (njs_slow_path(ret != NJS_OK)) {
2991 return ret;
2992 }
2993
2994 } else {
2995 new_length = njs_number_to_integer(njs_number(value));
2996 }
2997 }
2998
2999 if (new_length <= length) {
3000 vm->retval = args[0];
3001 return NJS_OK;
3002 }
3003
3004 if (njs_slow_path(new_length >= NJS_STRING_MAX_LENGTH)) {
3005 njs_range_error(vm, NULL);
3006 return NJS_ERROR;
3007 }
3008
3009 padding = new_length - length;
3010
3011 /* GCC and Clang complain about uninitialized n and trunc. */
3012 n = 0;
3013 trunc = 0;
3014
3015 pad = njs_arg(args, nargs, 2);
3016
3017 if (njs_slow_path(!njs_is_string(pad))) {
3018 if (njs_is_undefined(pad)) {
3019 pad = njs_value_arg(&string_space);
3020
3021 } else {
3022 ret = njs_value_to_string(vm, pad, pad);
3023 if (njs_slow_path(ret != NJS_OK)) {
3024 return NJS_ERROR;
3025 }
3026 }
3027 }
3028
3029 pad_length = njs_string_prop(&pad_string, pad);
3030
3031 if (pad_string.size == 0) {
3032 vm->retval = args[0];
3033 return NJS_OK;
3034 }
3035
3036 if (pad_string.size > 1) {
3037 n = padding / pad_length;
3038 trunc = padding % pad_length;
3039
3040 if (pad_string.size != (size_t) pad_length) {
3041 /* UTF-8 string. */
3042 end = pad_string.start + pad_string.size;
3043 end = njs_string_offset(pad_string.start, end, trunc);
3044
3045 trunc = end - pad_string.start;
3046 padding = pad_string.size * n + trunc;
3047 }
3048 }
3049
3050 new_size = string.size + padding;
3051
3052 start = njs_string_alloc(vm, &vm->retval, new_size, new_length);
3053 if (njs_slow_path(start == NULL)) {
3054 return NJS_ERROR;
3055 }
3056
3057 p = start;
3058
3059 if (pad_start) {
3060 start += padding;
3061
3062 } else {
3063 p += string.size;
3064 }
3065
3066 memcpy(start, string.start, string.size);
3067
3068 if (pad_string.size == 1) {
3069 njs_memset(p, pad_string.start[0], padding);
3070
3071 } else {
3072 while (n != 0) {
3073 memcpy(p, pad_string.start, pad_string.size);
3074 p += pad_string.size;
3075 n--;
3076 }
3077
3078 memcpy(p, pad_string.start, trunc);
3079 }
3080
3081 return NJS_OK;
3082 }
3083
3084
3085 static njs_int_t
njs_string_prototype_search(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)3086 njs_string_prototype_search(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3087 njs_index_t unused)
3088 {
3089 size_t c;
3090 njs_int_t ret, index;
3091 njs_uint_t n;
3092 njs_value_t *value;
3093 njs_string_prop_t string;
3094 njs_regexp_pattern_t *pattern;
3095
3096 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
3097 if (njs_slow_path(ret != NJS_OK)) {
3098 return ret;
3099 }
3100
3101 index = 0;
3102
3103 if (nargs > 1) {
3104 value = njs_argument(args, 1);
3105
3106 switch (value->type) {
3107
3108 case NJS_REGEXP:
3109 pattern = njs_regexp_pattern(value);
3110 break;
3111
3112 case NJS_UNDEFINED:
3113 goto done;
3114
3115 default:
3116 if (njs_slow_path(!njs_is_string(value))) {
3117 ret = njs_value_to_string(vm, value, value);
3118 if (njs_slow_path(ret != NJS_OK)) {
3119 return ret;
3120 }
3121 }
3122
3123 (void) njs_string_prop(&string, value);
3124
3125 if (string.size != 0) {
3126 pattern = njs_regexp_pattern_create(vm, string.start,
3127 string.size, 0);
3128 if (njs_slow_path(pattern == NULL)) {
3129 return NJS_ERROR;
3130 }
3131
3132 break;
3133 }
3134
3135 goto done;
3136 }
3137
3138 index = -1;
3139
3140 (void) njs_string_prop(&string, &args[0]);
3141
3142 n = (string.length != 0);
3143
3144 if (njs_regex_is_valid(&pattern->regex[n])) {
3145 ret = njs_regexp_match(vm, &pattern->regex[n], string.start,
3146 0, string.size, vm->single_match_data);
3147 if (ret >= 0) {
3148 c = njs_regex_capture(vm->single_match_data, 0);
3149 index = njs_string_index(&string, c);
3150
3151 } else if (ret == NJS_ERROR) {
3152 return NJS_ERROR;
3153 }
3154 }
3155 }
3156
3157 done:
3158
3159 njs_set_number(&vm->retval, index);
3160
3161 return NJS_OK;
3162 }
3163
3164
3165 static njs_int_t
njs_string_prototype_match(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)3166 njs_string_prototype_match(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3167 njs_index_t unused)
3168 {
3169 njs_str_t string;
3170 njs_int_t ret;
3171 njs_value_t arguments[2];
3172 njs_regexp_pattern_t *pattern;
3173
3174 ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
3175 if (njs_slow_path(ret != NJS_OK)) {
3176 return ret;
3177 }
3178
3179 arguments[1] = args[0];
3180
3181 string.start = NULL;
3182 string.length = 0;
3183
3184 if (nargs > 1) {
3185
3186 if (njs_is_regexp(&args[1])) {
3187 pattern = njs_regexp_pattern(&args[1]);
3188
3189 if (pattern->global) {
3190 return njs_string_match_multiple(vm, args, pattern);
3191 }
3192
3193 /*
3194 * string.match(regexp) is the same as regexp.exec(string)
3195 * if the regexp has no global flag.
3196 */
3197 arguments[0] = args[1];
3198
3199 goto match;
3200 }
3201
3202 if (!njs_is_string(&args[1])) {
3203 if (!njs_is_undefined(&args[1])) {
3204 ret = njs_value_to_string(vm, &args[1], &args[1]);
3205 if (njs_slow_path(ret != NJS_OK)) {
3206 return ret;
3207 }
3208
3209 njs_string_get(&args[1], &string);
3210 }
3211
3212 } else {
3213 njs_string_get(&args[1], &string);
3214 }
3215
3216 /* A void value. */
3217 }
3218
3219 ret = njs_regexp_create(vm, &arguments[0], string.start, string.length, 0);
3220 if (njs_slow_path(ret != NJS_OK)) {
3221 return ret;
3222 }
3223
3224 match:
3225
3226 return njs_regexp_prototype_exec(vm, arguments, nargs, unused);
3227 }
3228
3229
3230 static njs_int_t
njs_string_match_multiple(njs_vm_t * vm,njs_value_t * args,njs_regexp_pattern_t * pattern)3231 njs_string_match_multiple(njs_vm_t *vm, njs_value_t *args,
3232 njs_regexp_pattern_t *pattern)
3233 {
3234 size_t c0, c1;
3235 int32_t size, length;
3236 njs_int_t ret;
3237 njs_utf8_t utf8;
3238 njs_array_t *array;
3239 const u_char *p, *start, *end;
3240 njs_regexp_utf8_t type;
3241 njs_string_prop_t string;
3242
3243 njs_set_number(&args[1].data.u.regexp->last_index, 0);
3244 vm->retval = njs_value_null;
3245
3246 (void) njs_string_prop(&string, &args[0]);
3247
3248 utf8 = NJS_STRING_BYTE;
3249 type = NJS_REGEXP_BYTE;
3250
3251 if (string.length != 0) {
3252 utf8 = NJS_STRING_ASCII;
3253 type = NJS_REGEXP_UTF8;
3254
3255 if (string.length != string.size) {
3256 utf8 = NJS_STRING_UTF8;
3257 }
3258 }
3259
3260 if (njs_regex_is_valid(&pattern->regex[type])) {
3261
3262 array = njs_array_alloc(vm, 0, 0, NJS_ARRAY_SPARE);
3263 if (njs_slow_path(array == NULL)) {
3264 return NJS_ERROR;
3265 }
3266
3267 p = string.start;
3268 end = p + string.size;
3269
3270 do {
3271 ret = njs_regexp_match(vm, &pattern->regex[type], p, 0, string.size,
3272 vm->single_match_data);
3273 if (ret < 0) {
3274 if (njs_fast_path(ret == NJS_DECLINED)) {
3275 break;
3276 }
3277
3278 njs_internal_error(vm, "njs_regexp_match() failed");
3279
3280 return NJS_ERROR;
3281 }
3282
3283 ret = njs_array_expand(vm, array, 0, 1);
3284 if (njs_slow_path(ret != NJS_OK)) {
3285 return ret;
3286 }
3287
3288 c0 = njs_regex_capture(vm->single_match_data, 0);
3289 c1 = njs_regex_capture(vm->single_match_data, 1);
3290 start = p + c0;
3291
3292 if (c1 == 0) {
3293 if (start < end) {
3294 p = (utf8 != NJS_STRING_BYTE) ? njs_utf8_next(start, end)
3295 : start + 1;
3296 string.size = end - p;
3297
3298 } else {
3299 /* To exit the loop. */
3300 p++;
3301 }
3302
3303 size = 0;
3304 length = 0;
3305
3306 } else {
3307 p += c1;
3308 string.size -= c1;
3309
3310 size = c1 - c0;
3311 length = njs_string_calc_length(utf8, start, size);
3312 }
3313
3314 ret = njs_string_new(vm, &array->start[array->length],
3315 start, size, length);
3316 if (njs_slow_path(ret != NJS_OK)) {
3317 return ret;
3318 }
3319
3320 array->length++;
3321
3322 } while (p <= end);
3323
3324 njs_set_array(&vm->retval, array);
3325 }
3326
3327 return NJS_OK;
3328 }
3329
3330
3331 static njs_int_t
njs_string_prototype_split(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)3332 njs_string_prototype_split(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3333 njs_index_t unused)
3334 {
3335 size_t size;
3336 uint32_t limit;
3337 njs_int_t ret;
3338 njs_utf8_t utf8;
3339 njs_bool_t undefined;
3340 njs_value_t *this, *separator, *value;
3341 njs_value_t separator_lvalue, limit_lvalue, splitter;
3342 njs_array_t *array;
3343 const u_char *p, *start, *next, *last, *end;
3344 njs_string_prop_t string, split;
3345 njs_value_t arguments[3];
3346
3347 static const njs_value_t split_key =
3348 njs_wellknown_symbol(NJS_SYMBOL_SPLIT);
3349
3350 this = njs_argument(args, 0);
3351
3352 if (njs_slow_path(njs_is_null_or_undefined(this))) {
3353 njs_type_error(vm, "cannot convert \"%s\"to object",
3354 njs_type_string(this->type));
3355 return NJS_ERROR;
3356 }
3357
3358 separator = njs_lvalue_arg(&separator_lvalue, args, nargs, 1);
3359 value = njs_lvalue_arg(&limit_lvalue, args, nargs, 2);
3360
3361 if (!njs_is_null_or_undefined(separator)) {
3362 ret = njs_value_method(vm, separator, njs_value_arg(&split_key),
3363 &splitter);
3364 if (njs_slow_path(ret != NJS_OK)) {
3365 return ret;
3366 }
3367
3368 if (njs_is_defined(&splitter)) {
3369 arguments[0] = *this;
3370 arguments[1] = *value;
3371
3372 return njs_function_call(vm, njs_function(&splitter), separator,
3373 arguments, 2, &vm->retval);
3374 }
3375 }
3376
3377 ret = njs_value_to_string(vm, this, this);
3378 if (njs_slow_path(ret != NJS_OK)) {
3379 return ret;
3380 }
3381
3382 array = njs_array_alloc(vm, 0, 0, NJS_ARRAY_SPARE);
3383 if (njs_slow_path(array == NULL)) {
3384 return NJS_ERROR;
3385 }
3386
3387 limit = UINT32_MAX;
3388
3389 if (njs_is_defined(value)) {
3390 ret = njs_value_to_uint32(vm, value, &limit);
3391 if (njs_slow_path(ret != NJS_OK)) {
3392 return ret;
3393 }
3394 }
3395
3396 undefined = njs_is_undefined(separator);
3397
3398 ret = njs_value_to_string(vm, separator, separator);
3399 if (njs_slow_path(ret != NJS_OK)) {
3400 return ret;
3401 }
3402
3403 if (njs_slow_path(limit == 0)) {
3404 goto done;
3405 }
3406
3407 if (njs_slow_path(undefined)) {
3408 goto single;
3409 }
3410
3411 (void) njs_string_prop(&string, this);
3412 (void) njs_string_prop(&split, separator);
3413
3414 if (njs_slow_path(string.size == 0)) {
3415 if (split.size != 0) {
3416 goto single;
3417 }
3418
3419 goto done;
3420 }
3421
3422 utf8 = NJS_STRING_BYTE;
3423
3424 if (string.length != 0) {
3425 utf8 = NJS_STRING_ASCII;
3426
3427 if (string.length != string.size) {
3428 utf8 = NJS_STRING_UTF8;
3429 }
3430 }
3431
3432 start = string.start;
3433 end = string.start + string.size;
3434 last = end - split.size;
3435
3436 do {
3437
3438 for (p = start; p <= last; p++) {
3439 if (memcmp(p, split.start, split.size) == 0) {
3440 goto found;
3441 }
3442 }
3443
3444 p = end;
3445
3446 found:
3447
3448 next = p + split.size;
3449
3450 /* Empty split string. */
3451
3452 if (p == next) {
3453 p = (utf8 != NJS_STRING_BYTE) ? njs_utf8_next(p, end)
3454 : p + 1;
3455 next = p;
3456 }
3457
3458 size = p - start;
3459
3460 ret = njs_string_split_part_add(vm, array, utf8, start, size);
3461 if (njs_slow_path(ret != NJS_OK)) {
3462 return ret;
3463 }
3464
3465 start = next;
3466 limit--;
3467
3468 } while (limit != 0 && p < end);
3469
3470 goto done;
3471
3472 single:
3473
3474 value = njs_array_push(vm, array);
3475 if (njs_slow_path(value == NULL)) {
3476 return NJS_ERROR;
3477 }
3478
3479 *value = *this;
3480
3481 done:
3482
3483 njs_set_array(&vm->retval, array);
3484
3485 return NJS_OK;
3486 }
3487
3488
3489 njs_int_t
njs_string_split_part_add(njs_vm_t * vm,njs_array_t * array,njs_utf8_t utf8,const u_char * start,size_t size)3490 njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array, njs_utf8_t utf8,
3491 const u_char *start, size_t size)
3492 {
3493 ssize_t length;
3494
3495 length = njs_string_calc_length(utf8, start, size);
3496
3497 return njs_array_string_add(vm, array, start, size, length);
3498 }
3499
3500
3501 njs_int_t
njs_string_get_substitution(njs_vm_t * vm,njs_value_t * matched,njs_value_t * string,int64_t pos,njs_value_t * captures,int64_t ncaptures,njs_value_t * groups,njs_value_t * replacement,njs_value_t * retval)3502 njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched,
3503 njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures,
3504 njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval)
3505 {
3506 int64_t tail, size, length, n;
3507 u_char c, c2, *p, *r, *end;
3508 njs_str_t rep, m, str, cap;
3509 njs_int_t ret;
3510 njs_chb_t chain;
3511 njs_value_t name, value;
3512
3513 njs_string_get(replacement, &rep);
3514 p = rep.start;
3515 end = rep.start + rep.length;
3516
3517 njs_chb_init(&chain, vm->mem_pool);
3518
3519 while (p < end) {
3520 r = njs_strlchr(p, end, '$');
3521 if (r == NULL || r == &end[-1]) {
3522 if (njs_fast_path(p == rep.start)) {
3523 *retval = *replacement;
3524 return NJS_OK;
3525 }
3526
3527 njs_chb_append(&chain, p, end - p);
3528 goto done;
3529 }
3530
3531 njs_chb_append(&chain, p, r - p);
3532 p = r;
3533
3534 c = r[1];
3535
3536 switch (c) {
3537 case '$':
3538 njs_chb_append_literal(&chain, "$");
3539 p += 2;
3540 break;
3541
3542 case '&':
3543 njs_string_get(matched, &m);
3544 njs_chb_append_str(&chain, &m);
3545 p += 2;
3546 break;
3547
3548 case '`':
3549 njs_string_get(string, &str);
3550 njs_chb_append(&chain, str.start, pos);
3551 p += 2;
3552 break;
3553
3554 case '\'':
3555 njs_string_get(matched, &m);
3556 tail = pos + m.length;
3557
3558 njs_string_get(string, &str);
3559 njs_chb_append(&chain, &str.start[tail],
3560 njs_max((int64_t) str.length - tail, 0));
3561 p += 2;
3562 break;
3563
3564 case '<':
3565 r = njs_strlchr(p, end, '>');
3566 if (groups == NULL || njs_is_undefined(groups) || r == NULL) {
3567 njs_chb_append(&chain, p, 2);
3568 p += 2;
3569 break;
3570 }
3571
3572 p += 2;
3573
3574 ret = njs_vm_value_string_set(vm, &name, p, r - p);
3575 if (njs_slow_path(ret != NJS_OK)) {
3576 goto exception;
3577 }
3578
3579 p = r + 1;
3580
3581 ret = njs_value_property(vm, groups, &name, &value);
3582 if (njs_slow_path(ret == NJS_ERROR)) {
3583 goto exception;
3584 }
3585
3586 if (njs_is_defined(&value)) {
3587 ret = njs_value_to_string(vm, &value, &value);
3588 if (njs_slow_path(ret == NJS_ERROR)) {
3589 goto exception;
3590 }
3591
3592 njs_string_get(&value, &str);
3593 njs_chb_append_str(&chain, &str);
3594 }
3595
3596 break;
3597
3598 default:
3599 if (c >= '0' && c <= '9') {
3600 n = c - '0';
3601
3602 c2 = (&r[2] < end) ? r[2] : 0;
3603
3604 if (c2 >= '0' && c2 <= '9'
3605 && (n * 10 + (c2 - '0')) <= ncaptures)
3606 {
3607 n = n * 10 + (c2 - '0');
3608
3609 } else {
3610 c2 = 0;
3611 }
3612
3613 if (n == 0 || n > ncaptures) {
3614 njs_chb_append(&chain, p, (c2 != 0) ? 3 : 2);
3615 p += (c2 != 0) ? 3 : 2;
3616 break;
3617 }
3618
3619 p += (c2 != 0) ? 3 : 2;
3620
3621 if (njs_is_defined(&captures[n])) {
3622 njs_string_get(&captures[n], &cap);
3623 njs_chb_append_str(&chain, &cap);
3624 }
3625
3626 break;
3627 }
3628
3629 njs_chb_append_literal(&chain, "$");
3630 p += 1;
3631 break;
3632 }
3633 }
3634
3635 done:
3636
3637 size = njs_chb_size(&chain);
3638 if (njs_slow_path(size < 0)) {
3639 njs_memory_error(vm);
3640 ret = NJS_ERROR;
3641 goto exception;
3642 }
3643
3644 length = njs_chb_utf8_length(&chain);
3645
3646 p = njs_string_alloc(vm, retval, size, length);
3647 if (njs_slow_path(p == NULL)) {
3648 ret = NJS_ERROR;
3649 goto exception;
3650 }
3651
3652 njs_chb_join_to(&chain, p);
3653
3654 ret = NJS_OK;
3655
3656 exception:
3657
3658 njs_chb_destroy(&chain);
3659
3660 return ret;
3661 }
3662
3663
3664 static njs_int_t
njs_string_prototype_replace(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)3665 njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3666 njs_index_t unused)
3667 {
3668 u_char *r;
3669 size_t length, search_length, ret_length, size;
3670 int64_t pos;
3671 njs_int_t ret;
3672 njs_value_t *this, *search, *replace;
3673 njs_value_t search_lvalue, replace_lvalue, replacer, retval,
3674 arguments[3];
3675 const u_char *p;
3676 njs_function_t *func_replace;
3677 njs_string_prop_t string, s, ret_string;
3678
3679 static const njs_value_t replace_key =
3680 njs_wellknown_symbol(NJS_SYMBOL_REPLACE);
3681
3682 this = njs_argument(args, 0);
3683
3684 if (njs_slow_path(njs_is_null_or_undefined(this))) {
3685 njs_type_error(vm, "cannot convert \"%s\"to object",
3686 njs_type_string(this->type));
3687 return NJS_ERROR;
3688 }
3689
3690 search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
3691 replace = njs_lvalue_arg(&replace_lvalue, args, nargs, 2);
3692
3693 if (!njs_is_null_or_undefined(search)) {
3694 ret = njs_value_method(vm, search, njs_value_arg(&replace_key),
3695 &replacer);
3696 if (njs_slow_path(ret != NJS_OK)) {
3697 return ret;
3698 }
3699
3700 if (njs_is_defined(&replacer)) {
3701 arguments[0] = *this;
3702 arguments[1] = *replace;
3703
3704 return njs_function_call(vm, njs_function(&replacer), search,
3705 arguments, 2, &vm->retval);
3706 }
3707 }
3708
3709 ret = njs_value_to_string(vm, this, this);
3710 if (njs_slow_path(ret != NJS_OK)) {
3711 return ret;
3712 }
3713
3714 ret = njs_value_to_string(vm, search, search);
3715 if (njs_slow_path(ret != NJS_OK)) {
3716 return ret;
3717 }
3718
3719 func_replace = njs_is_function(replace) ? njs_function(replace) : NULL;
3720
3721 if (func_replace == NULL) {
3722 ret = njs_value_to_string(vm, replace, replace);
3723 if (njs_slow_path(ret != NJS_OK)) {
3724 return ret;
3725 }
3726 }
3727
3728 length = njs_string_prop(&string, this);
3729 search_length = njs_string_prop(&s, search);
3730
3731 pos = njs_string_index_of(&string, &s, 0);
3732 if (pos < 0) {
3733 vm->retval = *this;
3734 return NJS_OK;
3735 }
3736
3737 if (func_replace == NULL) {
3738 ret = njs_string_get_substitution(vm, search, this, pos, NULL, 0, NULL,
3739 replace, &retval);
3740 if (njs_slow_path(ret != NJS_OK)) {
3741 return ret;
3742 }
3743
3744 } else {
3745 arguments[0] = *search;
3746 njs_set_number(&arguments[1], pos);
3747 arguments[2] = *this;
3748
3749 ret = njs_function_call(vm, func_replace,
3750 njs_value_arg(&njs_value_undefined),
3751 arguments, 3, &retval);
3752
3753 if (njs_slow_path(ret != NJS_OK)) {
3754 return ret;
3755 }
3756
3757 ret = njs_value_to_string(vm, &retval, &retval);
3758 if (njs_slow_path(ret != NJS_OK)) {
3759 return NJS_ERROR;
3760 }
3761 }
3762
3763 if (length == string.size) {
3764 p = string.start + pos;
3765
3766 } else {
3767 /* UTF-8 string. */
3768 p = njs_string_offset(string.start, string.start + string.size, pos);
3769 }
3770
3771 ret_length = njs_string_prop(&ret_string, &retval);
3772
3773 size = string.size + ret_string.size - s.size;
3774 length += ret_length - search_length;
3775
3776 r = njs_string_alloc(vm, &vm->retval, size, length);
3777 if (njs_slow_path(r == NULL)) {
3778 return NJS_ERROR;
3779 }
3780
3781 r = njs_cpymem(r, string.start, p - string.start);
3782 r = njs_cpymem(r, ret_string.start, ret_string.size);
3783 memcpy(r, p + s.size, string.size - s.size - (p - string.start));
3784
3785 return NJS_OK;
3786 }
3787
3788
3789 static njs_int_t
njs_string_prototype_iterator_obj(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t kind)3790 njs_string_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args,
3791 njs_uint_t nargs, njs_index_t kind)
3792 {
3793 njs_int_t ret;
3794 njs_value_t *this;
3795
3796 this = njs_argument(args, 0);
3797
3798 ret = njs_string_object_validate(vm, this);
3799 if (njs_slow_path(ret != NJS_OK)) {
3800 return ret;
3801 }
3802
3803 return njs_array_iterator_create(vm, this, &vm->retval, kind);
3804 }
3805
3806
3807 double
njs_string_to_number(const njs_value_t * value,njs_bool_t parse_float)3808 njs_string_to_number(const njs_value_t *value, njs_bool_t parse_float)
3809 {
3810 double num;
3811 size_t size;
3812 uint32_t u;
3813 njs_bool_t minus;
3814 const u_char *p, *start, *end;
3815 njs_unicode_decode_t ctx;
3816
3817 const size_t infinity = njs_length("Infinity");
3818
3819 size = value->short_string.size;
3820
3821 if (size != NJS_STRING_LONG) {
3822 p = value->short_string.start;
3823
3824 } else {
3825 size = value->long_string.size;
3826 p = value->long_string.data->start;
3827 }
3828
3829 end = p + size;
3830
3831 njs_utf8_decode_init(&ctx);
3832
3833 while (p < end) {
3834 start = p;
3835 u = njs_utf8_decode(&ctx, &p, end);
3836
3837 if (!njs_utf8_is_whitespace(u)) {
3838 p = start;
3839 break;
3840 }
3841 }
3842
3843 if (p == end) {
3844 return parse_float ? NAN : 0.0;
3845 }
3846
3847 minus = 0;
3848
3849 if (*p == '+') {
3850 p++;
3851
3852 } else if (*p == '-') {
3853 p++;
3854 minus = 1;
3855 }
3856
3857 if (p == end) {
3858 return NAN;
3859 }
3860
3861 if (!parse_float
3862 && p + 2 < end && p[0] == '0' && (p[1] == 'x' || p[1] == 'X'))
3863 {
3864 p += 2;
3865 num = njs_number_hex_parse(&p, end, 0);
3866
3867 } else {
3868 start = p;
3869 num = njs_number_dec_parse(&p, end, 0);
3870
3871 if (p == start) {
3872 if (p + infinity > end || memcmp(p, "Infinity", infinity) != 0) {
3873 return NAN;
3874 }
3875
3876 num = INFINITY;
3877 p += infinity;
3878 }
3879 }
3880
3881 if (!parse_float) {
3882 while (p < end) {
3883 if (*p != ' ' && *p != '\t') {
3884 return NAN;
3885 }
3886
3887 p++;
3888 }
3889 }
3890
3891 return minus ? -num : num;
3892 }
3893
3894
3895 double
njs_string_to_index(const njs_value_t * value)3896 njs_string_to_index(const njs_value_t *value)
3897 {
3898 size_t size, len;
3899 double num;
3900 njs_bool_t minus;
3901 const u_char *p, *start, *end;
3902 u_char buf[128];
3903
3904 size = value->short_string.size;
3905
3906 if (size != NJS_STRING_LONG) {
3907 start = value->short_string.start;
3908
3909 } else {
3910 size = value->long_string.size;
3911 start = value->long_string.data->start;
3912 }
3913
3914 p = start;
3915 end = p + size;
3916 minus = 0;
3917
3918 if (size > 1) {
3919 switch (p[0]) {
3920 case '0':
3921 if (size != 1) {
3922 return NAN;
3923 }
3924
3925 /* Fall through. */
3926
3927 case '1':
3928 case '2':
3929 case '3':
3930 case '4':
3931 case '5':
3932 case '6':
3933 case '7':
3934 case '8':
3935 case '9':
3936 break;
3937
3938 case '-':
3939 if (size == 2 && p[1] == '0') {
3940 return -0.0;
3941 }
3942
3943 if (size == njs_length("-Infinity")
3944 && memcmp(&p[1], "Infinity", njs_length("Infinity")) == 0)
3945 {
3946 return -INFINITY;
3947 }
3948
3949 p++;
3950 minus = 1;
3951
3952 break;
3953
3954 case 'I':
3955 if (size == njs_length("Infinity")
3956 && memcmp(p, "Infinity", njs_length("Infinity")) == 0)
3957 {
3958 return INFINITY;
3959 }
3960
3961 /* Fall through. */
3962
3963 default:
3964 return NAN;
3965 }
3966 }
3967
3968 num = njs_strtod(&p, end, 0);
3969 if (p != end) {
3970 return NAN;
3971 }
3972
3973 num = minus ? -num : num;
3974
3975 len = njs_dtoa(num, (char *) buf);
3976 if (size != len || memcmp(start, buf, size) != 0) {
3977 return NAN;
3978 }
3979
3980 return num;
3981 }
3982
3983
3984 /*
3985 * If string value is null-terminated the corresponding C string
3986 * is returned as is, otherwise the new copy is allocated with
3987 * the terminating zero byte.
3988 */
3989 const char *
njs_string_to_c_string(njs_vm_t * vm,njs_value_t * value)3990 njs_string_to_c_string(njs_vm_t *vm, njs_value_t *value)
3991 {
3992 u_char *p, *data, *start;
3993 size_t size;
3994
3995 if (value->short_string.size != NJS_STRING_LONG) {
3996 start = value->short_string.start;
3997 size = value->short_string.size;
3998
3999 if (size < NJS_STRING_SHORT) {
4000 start[size] = '\0';
4001 return (const char *) start;
4002 }
4003
4004 } else {
4005 start = value->long_string.data->start;
4006 size = value->long_string.size;
4007 }
4008
4009 data = njs_mp_alloc(vm->mem_pool, size + 1);
4010 if (njs_slow_path(data == NULL)) {
4011 njs_memory_error(vm);
4012 return NULL;
4013 }
4014
4015 p = njs_cpymem(data, start, size);
4016 *p++ = '\0';
4017
4018 return (const char *) data;
4019 }
4020
4021
4022 static const njs_object_prop_t njs_string_prototype_properties[] =
4023 {
4024 {
4025 .type = NJS_PROPERTY,
4026 .name = njs_string("length"),
4027 .value = njs_value(NJS_NUMBER, 0, 0.0),
4028 },
4029
4030 {
4031 .type = NJS_PROPERTY_HANDLER,
4032 .name = njs_string("__proto__"),
4033 .value = njs_prop_handler(njs_primitive_prototype_get_proto),
4034 .configurable = 1,
4035 },
4036
4037 {
4038 .type = NJS_PROPERTY_HANDLER,
4039 .name = njs_string("constructor"),
4040 .value = njs_prop_handler(njs_object_prototype_create_constructor),
4041 .writable = 1,
4042 .configurable = 1,
4043 },
4044
4045 {
4046 .type = NJS_PROPERTY,
4047 .name = njs_string("valueOf"),
4048 .value = njs_native_function(njs_string_prototype_value_of, 0),
4049 .writable = 1,
4050 .configurable = 1,
4051 },
4052
4053 {
4054 .type = NJS_PROPERTY,
4055 .name = njs_string("toString"),
4056 .value = njs_native_function(njs_string_prototype_to_string, 0),
4057 .writable = 1,
4058 .configurable = 1,
4059 },
4060
4061 {
4062 .type = NJS_PROPERTY,
4063 .name = njs_string("concat"),
4064 .value = njs_native_function(njs_string_prototype_concat, 1),
4065 .writable = 1,
4066 .configurable = 1,
4067 },
4068
4069 {
4070 .type = NJS_PROPERTY,
4071 .name = njs_string("fromUTF8"),
4072 .value = njs_native_function(njs_string_prototype_from_utf8, 0),
4073 .writable = 1,
4074 .configurable = 1,
4075 },
4076
4077 {
4078 .type = NJS_PROPERTY,
4079 .name = njs_string("toUTF8"),
4080 .value = njs_native_function(njs_string_prototype_to_utf8, 0),
4081 .writable = 1,
4082 .configurable = 1,
4083 },
4084
4085 {
4086 .type = NJS_PROPERTY,
4087 .name = njs_string("fromBytes"),
4088 .value = njs_native_function(njs_string_prototype_from_bytes, 0),
4089 .writable = 1,
4090 .configurable = 1,
4091 },
4092
4093 {
4094 .type = NJS_PROPERTY,
4095 .name = njs_string("toBytes"),
4096 .value = njs_native_function(njs_string_prototype_to_bytes, 0),
4097 .writable = 1,
4098 .configurable = 1,
4099 },
4100
4101 {
4102 .type = NJS_PROPERTY,
4103 .name = njs_string("slice"),
4104 .value = njs_native_function(njs_string_prototype_slice, 2),
4105 .writable = 1,
4106 .configurable = 1,
4107 },
4108
4109 {
4110 .type = NJS_PROPERTY,
4111 .name = njs_string("substring"),
4112 .value = njs_native_function(njs_string_prototype_substring, 2),
4113 .writable = 1,
4114 .configurable = 1,
4115 },
4116
4117 {
4118 .type = NJS_PROPERTY,
4119 .name = njs_string("substr"),
4120 .value = njs_native_function(njs_string_prototype_substr, 2),
4121 .writable = 1,
4122 .configurable = 1,
4123 },
4124
4125 {
4126 .type = NJS_PROPERTY,
4127 .name = njs_string("charAt"),
4128 .value = njs_native_function(njs_string_prototype_char_at, 1),
4129 .writable = 1,
4130 .configurable = 1,
4131 },
4132
4133 {
4134 .type = NJS_PROPERTY,
4135 .name = njs_string("charCodeAt"),
4136 .value = njs_native_function(njs_string_prototype_char_code_at, 1),
4137 .writable = 1,
4138 .configurable = 1,
4139 },
4140
4141 {
4142 .type = NJS_PROPERTY,
4143 .name = njs_string("codePointAt"),
4144 .value = njs_native_function(njs_string_prototype_char_code_at, 1),
4145 .writable = 1,
4146 .configurable = 1,
4147 },
4148
4149 {
4150 .type = NJS_PROPERTY,
4151 .name = njs_string("indexOf"),
4152 .value = njs_native_function(njs_string_prototype_index_of, 1),
4153 .writable = 1,
4154 .configurable = 1,
4155 },
4156
4157 {
4158 .type = NJS_PROPERTY,
4159 .name = njs_string("lastIndexOf"),
4160 .value = njs_native_function(njs_string_prototype_last_index_of, 1),
4161 .writable = 1,
4162 .configurable = 1,
4163 },
4164
4165 {
4166 .type = NJS_PROPERTY,
4167 .name = njs_string("includes"),
4168 .value = njs_native_function(njs_string_prototype_includes, 1),
4169 .writable = 1,
4170 .configurable = 1,
4171 },
4172
4173 {
4174 .type = NJS_PROPERTY,
4175 .name = njs_string("startsWith"),
4176 .value = njs_native_function2(njs_string_prototype_starts_or_ends_with,
4177 1, 1),
4178 .writable = 1,
4179 .configurable = 1,
4180 },
4181
4182 {
4183 .type = NJS_PROPERTY,
4184 .name = njs_string("endsWith"),
4185 .value = njs_native_function2(njs_string_prototype_starts_or_ends_with,
4186 1, 0),
4187 .writable = 1,
4188 .configurable = 1,
4189 },
4190
4191 {
4192 .type = NJS_PROPERTY,
4193 .name = njs_string("toLowerCase"),
4194 .value = njs_native_function(njs_string_prototype_to_lower_case, 0),
4195 .writable = 1,
4196 .configurable = 1,
4197 },
4198
4199 {
4200 .type = NJS_PROPERTY,
4201 .name = njs_string("toUpperCase"),
4202 .value = njs_native_function(njs_string_prototype_to_upper_case, 0),
4203 .writable = 1,
4204 .configurable = 1,
4205 },
4206
4207 {
4208 .type = NJS_PROPERTY,
4209 .name = njs_string("trim"),
4210 .value = njs_native_function2(njs_string_prototype_trim, 0,
4211 NJS_TRIM_START | NJS_TRIM_END),
4212 .writable = 1,
4213 .configurable = 1,
4214 },
4215
4216 {
4217 .type = NJS_PROPERTY,
4218 .name = njs_string("trimStart"),
4219 .value = njs_native_function2(njs_string_prototype_trim, 0,
4220 NJS_TRIM_START),
4221 .writable = 1,
4222 .configurable = 1,
4223 },
4224
4225 {
4226 .type = NJS_PROPERTY,
4227 .name = njs_string("trimEnd"),
4228 .value = njs_native_function2(njs_string_prototype_trim, 0,
4229 NJS_TRIM_END),
4230 .writable = 1,
4231 .configurable = 1,
4232 },
4233
4234 {
4235 .type = NJS_PROPERTY,
4236 .name = njs_string("repeat"),
4237 .value = njs_native_function(njs_string_prototype_repeat, 1),
4238 .writable = 1,
4239 .configurable = 1,
4240 },
4241
4242 {
4243 .type = NJS_PROPERTY,
4244 .name = njs_string("padStart"),
4245 .value = njs_native_function2(njs_string_prototype_pad, 1, 1),
4246 .writable = 1,
4247 .configurable = 1,
4248 },
4249
4250 {
4251 .type = NJS_PROPERTY,
4252 .name = njs_string("padEnd"),
4253 .value = njs_native_function2(njs_string_prototype_pad, 1, 0),
4254 .writable = 1,
4255 .configurable = 1,
4256 },
4257
4258 {
4259 .type = NJS_PROPERTY,
4260 .name = njs_string("search"),
4261 .value = njs_native_function(njs_string_prototype_search, 1),
4262 .writable = 1,
4263 .configurable = 1,
4264 },
4265
4266 {
4267 .type = NJS_PROPERTY,
4268 .name = njs_string("match"),
4269 .value = njs_native_function(njs_string_prototype_match, 1),
4270 .writable = 1,
4271 .configurable = 1,
4272 },
4273
4274 {
4275 .type = NJS_PROPERTY,
4276 .name = njs_string("split"),
4277 .value = njs_native_function(njs_string_prototype_split, 2),
4278 .writable = 1,
4279 .configurable = 1,
4280 },
4281
4282 {
4283 .type = NJS_PROPERTY,
4284 .name = njs_string("replace"),
4285 .value = njs_native_function(njs_string_prototype_replace, 2),
4286 .writable = 1,
4287 .configurable = 1,
4288 },
4289
4290 {
4291 .type = NJS_PROPERTY,
4292 .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR),
4293 .value = njs_native_function2(njs_string_prototype_iterator_obj, 0,
4294 NJS_ENUM_VALUES),
4295 .writable = 1,
4296 .configurable = 1,
4297 },
4298 };
4299
4300
4301 const njs_object_init_t njs_string_prototype_init = {
4302 njs_string_prototype_properties,
4303 njs_nitems(njs_string_prototype_properties),
4304 };
4305
4306
4307 const njs_object_prop_t njs_string_instance_properties[] =
4308 {
4309 {
4310 .type = NJS_PROPERTY_HANDLER,
4311 .name = njs_string("length"),
4312 .value = njs_prop_handler(njs_string_instance_length),
4313 },
4314 };
4315
4316
4317 const njs_object_init_t njs_string_instance_init = {
4318 njs_string_instance_properties,
4319 njs_nitems(njs_string_instance_properties),
4320 };
4321
4322
4323 njs_int_t
njs_string_encode_uri(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t component)4324 njs_string_encode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
4325 njs_index_t component)
4326 {
4327 u_char byte, *dst;
4328 uint64_t size;
4329 uint32_t cp, cp_low;
4330 njs_int_t ret;
4331 njs_value_t *value;
4332 const u_char *src, *end;
4333 const uint32_t *escape;
4334 njs_string_prop_t string;
4335 njs_unicode_decode_t ctx;
4336 u_char encode[4];
4337
4338 static const uint32_t escape_uri[] = {
4339 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4340
4341 /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
4342 0x50000025, /* 0101 0000 0000 0000 0000 0000 0010 0101 */
4343
4344 /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
4345 0x78000000, /* 0111 1000 0000 0000 0000 0000 0000 0000 */
4346
4347 /* ~}| {zyx wvut srqp onml kjih gfed cba` */
4348 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
4349
4350 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4351 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4352 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4353 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4354 };
4355
4356 static const uint32_t escape_uri_component[] = {
4357 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4358
4359 /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
4360 0xfc00987d, /* 1111 1100 0000 0000 1001 1000 0111 1101 */
4361
4362 /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
4363 0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */
4364
4365 /* ~}| {zyx wvut srqp onml kjih gfed cba` */
4366 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
4367
4368 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4369 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4370 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4371 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
4372 };
4373
4374 if (nargs < 2) {
4375 vm->retval = njs_string_undefined;
4376 return NJS_OK;
4377 }
4378
4379 value = njs_argument(args, 1);
4380 ret = njs_value_to_string(vm, value, value);
4381 if (njs_slow_path(ret != NJS_OK)) {
4382 return ret;
4383 }
4384
4385 escape = (component) ? escape_uri_component : escape_uri;
4386
4387 njs_prefetch(escape);
4388
4389 (void) njs_string_prop(&string, value);
4390
4391 size = 0;
4392 src = string.start;
4393 end = src + string.size;
4394
4395 if (string.length == 0 || string.length == string.size) {
4396 /* Byte or ASCII string. */
4397
4398 while (src < end) {
4399 byte = *src++;
4400 size += njs_need_escape(escape, byte) ? 3 : 1;
4401 }
4402
4403 } else {
4404 /* UTF-8 string. */
4405
4406 njs_utf8_decode_init(&ctx);
4407
4408 while (src < end) {
4409 cp = njs_utf8_decode(&ctx, &src, end);
4410
4411 if (cp < 0x80 && !njs_need_escape(escape, cp)) {
4412 size++;
4413 continue;
4414 }
4415
4416 if (njs_slow_path(njs_surrogate_any(cp))) {
4417 if (src == end) {
4418 goto uri_error;
4419 }
4420
4421 if (njs_surrogate_leading(cp)) {
4422 cp_low = njs_utf8_decode(&ctx, &src, end);
4423
4424 if (njs_slow_path(!njs_surrogate_trailing(cp_low))) {
4425 goto uri_error;
4426 }
4427
4428 cp = njs_surrogate_pair(cp, cp_low);
4429 size += njs_utf8_size(cp) * 3;
4430 continue;
4431 }
4432
4433 goto uri_error;
4434 }
4435
4436 size += njs_utf8_size(cp) * 3;
4437 }
4438 }
4439
4440 if (size == 0) {
4441 /* GC: retain src. */
4442 vm->retval = *value;
4443 return NJS_OK;
4444 }
4445
4446 dst = njs_string_alloc(vm, &vm->retval, size, size);
4447 if (njs_slow_path(dst == NULL)) {
4448 return NJS_ERROR;
4449 }
4450
4451 src = string.start;
4452
4453 if (string.length == 0 || string.length == string.size) {
4454 /* Byte or ASCII string. */
4455 (void) njs_string_encode(escape, string.size, src, dst);
4456 return NJS_OK;
4457 }
4458
4459 /* UTF-8 string. */
4460
4461 njs_utf8_decode_init(&ctx);
4462
4463 while (src < end) {
4464 cp = njs_utf8_decode(&ctx, &src, end);
4465
4466 if (njs_slow_path(njs_surrogate_leading(cp))) {
4467 cp_low = njs_utf8_decode(&ctx, &src, end);
4468 cp = njs_surrogate_pair(cp, cp_low);
4469 }
4470
4471 njs_utf8_encode(encode, cp);
4472
4473 dst = njs_string_encode(escape, njs_utf8_size(cp), encode, dst);
4474 }
4475
4476 return NJS_OK;
4477
4478 uri_error:
4479
4480 njs_uri_error(vm, "malformed URI");
4481
4482 return NJS_ERROR;
4483 }
4484
4485
4486 njs_inline uint32_t
njs_string_decode_uri_cp(const int8_t * hex,const u_char ** start,const u_char * end,njs_bool_t expect_percent)4487 njs_string_decode_uri_cp(const int8_t *hex, const u_char **start,
4488 const u_char *end, njs_bool_t expect_percent)
4489 {
4490 int8_t d0, d1;
4491 uint32_t cp;
4492 const u_char *p;
4493 njs_unicode_decode_t ctx;
4494
4495 njs_utf8_decode_init(&ctx);
4496
4497 cp = njs_utf8_decode(&ctx, start, end);
4498 if (njs_fast_path(cp != '%')) {
4499 return expect_percent ? NJS_UNICODE_ERROR : cp;
4500 }
4501
4502 p = *start;
4503
4504 if (njs_slow_path((p + 1) >= end)) {
4505 return NJS_UNICODE_ERROR;
4506 }
4507
4508 d0 = hex[*p++];
4509 if (njs_slow_path(d0 < 0)) {
4510 return NJS_UNICODE_ERROR;
4511 }
4512
4513 d1 = hex[*p++];
4514 if (njs_slow_path(d1 < 0)) {
4515 return NJS_UNICODE_ERROR;
4516 }
4517
4518 *start += 2;
4519
4520 return (d0 << 4) + d1;
4521 }
4522
4523
4524 njs_inline njs_bool_t
njs_reserved(const uint32_t * reserve,uint32_t byte)4525 njs_reserved(const uint32_t *reserve, uint32_t byte)
4526 {
4527 return ((reserve[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0);
4528 }
4529
4530
4531 njs_int_t
njs_string_decode_uri(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t component)4532 njs_string_decode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
4533 njs_index_t component)
4534 {
4535 u_char *dst;
4536 int64_t size, length;
4537 uint32_t cp;
4538 njs_int_t ret;
4539 njs_chb_t chain;
4540 njs_uint_t i, n;
4541 njs_bool_t percent;
4542 njs_value_t *value;
4543 const u_char *src, *p, *end;
4544 const uint32_t *reserve;
4545 njs_string_prop_t string;
4546 njs_unicode_decode_t ctx;
4547 u_char encode[4];
4548
4549 static const uint32_t reserve_uri[] = {
4550 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4551
4552 /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
4553 0xac009858, /* 1010 1100 0000 0000 1001 1000 0101 1000 */
4554
4555 /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
4556 0x00000001, /* 0000 0000 0000 0000 0000 0000 0000 0001 */
4557
4558 /* ~}| {zyx wvut srqp onml kjih gfed cba` */
4559 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4560
4561 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4562 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4563 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4564 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4565 };
4566
4567 static const uint32_t reserve_uri_component[] = {
4568 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4569
4570 /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
4571 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4572
4573 /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
4574 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4575
4576 /* ~}| {zyx wvut srqp onml kjih gfed cba` */
4577 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4578
4579 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4580 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4581 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4582 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
4583 };
4584
4585 static const int8_t hex[256]
4586 njs_aligned(32) =
4587 {
4588 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4589 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4590 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4591 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
4592 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4593 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4594 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4595 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4596 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4597 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4598 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4599 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4600 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4601 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4602 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4603 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4604 };
4605
4606 if (nargs < 2) {
4607 vm->retval = njs_string_undefined;
4608 return NJS_OK;
4609 }
4610
4611 value = njs_argument(args, 1);
4612 ret = njs_value_to_string(vm, value, value);
4613 if (njs_slow_path(ret != NJS_OK)) {
4614 return ret;
4615 }
4616
4617 reserve = component ? reserve_uri_component : reserve_uri;
4618
4619 njs_prefetch(reserve);
4620 njs_prefetch(&hex['0']);
4621
4622 (void) njs_string_prop(&string, value);
4623
4624 length = 0;
4625 src = string.start;
4626 end = string.start + string.size;
4627
4628 njs_chb_init(&chain, vm->mem_pool);
4629
4630 njs_utf8_decode_init(&ctx);
4631
4632 while (src < end) {
4633 percent = (src[0] == '%');
4634 cp = njs_string_decode_uri_cp(hex, &src, end, 0);
4635 if (njs_slow_path(cp > NJS_UNICODE_MAX_CODEPOINT)) {
4636 goto uri_error;
4637 }
4638
4639 if (!percent) {
4640 length += 1;
4641 dst = njs_chb_reserve(&chain, 4);
4642 if (dst != NULL) {
4643 njs_utf8_encode(dst, cp);
4644 njs_chb_written(&chain, njs_utf8_size(cp));
4645 }
4646
4647 continue;
4648 }
4649
4650 if (cp < 0x80) {
4651 if (njs_reserved(reserve, cp)) {
4652 length += 3;
4653 njs_chb_append(&chain, &src[-3], 3);
4654
4655 } else {
4656 length += 1;
4657 dst = njs_chb_reserve(&chain, 1);
4658 if (dst != NULL) {
4659 *dst = cp;
4660 njs_chb_written(&chain, 1);
4661 }
4662 }
4663
4664 continue;
4665 }
4666
4667 n = 1;
4668
4669 do {
4670 n++;
4671 } while (((cp << n) & 0x80));
4672
4673 if (njs_slow_path(n > 4)) {
4674 goto uri_error;
4675 }
4676
4677 encode[0] = cp;
4678
4679 for (i = 1; i < n; i++) {
4680 cp = njs_string_decode_uri_cp(hex, &src, end, 1);
4681 if (njs_slow_path(cp > NJS_UNICODE_MAX_CODEPOINT)) {
4682 goto uri_error;
4683 }
4684
4685 encode[i] = cp;
4686 }
4687
4688 p = encode;
4689 cp = njs_utf8_decode(&ctx, &p, p + n);
4690 if (njs_slow_path(cp > NJS_UNICODE_MAX_CODEPOINT)) {
4691 goto uri_error;
4692 }
4693
4694 dst = njs_chb_reserve(&chain, 4);
4695 if (dst != NULL) {
4696 njs_utf8_encode(dst, cp);
4697 njs_chb_written(&chain, njs_utf8_size(cp));
4698 }
4699
4700 length += 1;
4701 }
4702
4703 size = njs_chb_size(&chain);
4704 if (njs_slow_path(size < 0)) {
4705 njs_memory_error(vm);
4706 return NJS_ERROR;
4707 }
4708
4709 if (size == 0) {
4710 /* GC: retain src. */
4711 vm->retval = *value;
4712 return NJS_OK;
4713 }
4714
4715 dst = njs_string_alloc(vm, &vm->retval, size, length);
4716 if (njs_slow_path(dst == NULL)) {
4717 return NJS_ERROR;
4718 }
4719
4720 njs_chb_join_to(&chain, dst);
4721 njs_chb_destroy(&chain);
4722
4723 return NJS_OK;
4724
4725 uri_error:
4726
4727 njs_uri_error(vm, "malformed URI");
4728
4729 return NJS_ERROR;
4730 }
4731
4732
4733 const njs_object_type_init_t njs_string_type_init = {
4734 .constructor = njs_native_ctor(njs_string_constructor, 1, 0),
4735 .constructor_props = &njs_string_constructor_init,
4736 .prototype_props = &njs_string_prototype_init,
4737 .prototype_value = { .object_value = {
4738 .value = njs_string(""),
4739 .object = { .type = NJS_OBJECT_VALUE } }
4740 },
4741 };
4742