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