| |
| /* |
| * Copyright (C) Igor Sysoev |
| * Copyright (C) NGINX, Inc. |
| */ |
| |
| |
| #include <njs_main.h> |
| |
| |
| #define NJS_TRIM_START 1 |
| #define NJS_TRIM_END 2 |
| |
| |
| static u_char njs_basis64[] = { |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63, |
| 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77, |
| 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77, |
| 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
| 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77, |
| |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 |
| }; |
| |
| |
| static u_char njs_basis64url[] = { |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, |
| 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77, |
| 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 63, |
| 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
| 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77, |
| |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, |
| 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 |
| }; |
| |
| |
| static void njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src, |
| const u_char *basis, njs_uint_t padding); |
| 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); |
| 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); |
| 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); |
| 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); |
| static njs_int_t njs_string_bytes_from(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused); |
| static njs_int_t njs_string_bytes_from_array_like(njs_vm_t *vm, |
| njs_value_t *value); |
| static njs_int_t njs_string_bytes_from_string(njs_vm_t *vm, |
| const njs_value_t *string, const njs_value_t *encoding); |
| static njs_int_t njs_string_match_multiple(njs_vm_t *vm, njs_value_t *args, |
| njs_regexp_pattern_t *pattern); |
| static 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); |
| |
| |
| #define njs_base64_encoded_length(len) (((len + 2) / 3) * 4) |
| #define njs_base64_decoded_length(len, pad) (((len / 4) * 3) - pad) |
| |
| |
| njs_int_t |
| njs_string_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, |
| uint32_t size) |
| { |
| u_char *dst; |
| const u_char *src; |
| njs_string_t *string; |
| |
| value->type = NJS_STRING; |
| njs_string_truth(value, size); |
| |
| if (size <= NJS_STRING_SHORT) { |
| value->short_string.size = size; |
| value->short_string.length = 0; |
| |
| dst = value->short_string.start; |
| src = start; |
| |
| while (size != 0) { |
| /* The maximum size is just 14 bytes. */ |
| njs_pragma_loop_disable_vectorization; |
| |
| *dst++ = *src++; |
| size--; |
| } |
| |
| } else { |
| /* |
| * Setting UTF-8 length is not required here, it just allows |
| * to store the constant in whole byte instead of bit twiddling. |
| */ |
| value->short_string.size = NJS_STRING_LONG; |
| value->short_string.length = 0; |
| value->long_string.external = 0xff; |
| value->long_string.size = size; |
| |
| string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t)); |
| if (njs_slow_path(string == NULL)) { |
| njs_memory_error(vm); |
| return NJS_ERROR; |
| } |
| |
| value->long_string.data = string; |
| |
| string->start = (u_char *) start; |
| string->length = 0; |
| string->retain = 1; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_int_t |
| njs_string_create(njs_vm_t *vm, njs_value_t *value, const char *src, |
| size_t size) |
| { |
| njs_str_t str; |
| |
| str.start = (u_char *) src; |
| str.length = size; |
| |
| return njs_string_decode_utf8(vm, value, &str); |
| } |
| |
| |
| njs_int_t |
| njs_string_new(njs_vm_t *vm, njs_value_t *value, const u_char *start, |
| uint32_t size, uint32_t length) |
| { |
| u_char *p; |
| |
| p = njs_string_alloc(vm, value, size, length); |
| |
| if (njs_fast_path(p != NULL)) { |
| memcpy(p, start, size); |
| return NJS_OK; |
| } |
| |
| return NJS_ERROR; |
| } |
| |
| |
| u_char * |
| njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size, |
| uint64_t length) |
| { |
| uint32_t total, map_offset, *map; |
| njs_string_t *string; |
| |
| if (njs_slow_path(size > NJS_STRING_MAX_LENGTH)) { |
| njs_range_error(vm, "invalid string length"); |
| return NULL; |
| } |
| |
| value->type = NJS_STRING; |
| njs_string_truth(value, size); |
| |
| if (size <= NJS_STRING_SHORT) { |
| value->short_string.size = size; |
| value->short_string.length = length; |
| |
| return value->short_string.start; |
| } |
| |
| /* |
| * Setting UTF-8 length is not required here, it just allows |
| * to store the constant in whole byte instead of bit twiddling. |
| */ |
| value->short_string.size = NJS_STRING_LONG; |
| value->short_string.length = 0; |
| value->long_string.external = 0; |
| value->long_string.size = size; |
| |
| if (size != length && length > NJS_STRING_MAP_STRIDE) { |
| map_offset = njs_string_map_offset(size); |
| total = map_offset + njs_string_map_size(length); |
| |
| } else { |
| map_offset = 0; |
| total = size; |
| } |
| |
| string = njs_mp_alloc(vm->mem_pool, sizeof(njs_string_t) + total); |
| |
| if (njs_fast_path(string != NULL)) { |
| value->long_string.data = string; |
| |
| string->start = (u_char *) string + sizeof(njs_string_t); |
| string->length = length; |
| string->retain = 1; |
| |
| if (map_offset != 0) { |
| map = (uint32_t *) (string->start + map_offset); |
| map[0] = 0; |
| } |
| |
| return string->start; |
| } |
| |
| njs_memory_error(vm); |
| |
| return NULL; |
| } |
| |
| |
| void |
| njs_string_truncate(njs_value_t *value, uint32_t size, uint32_t length) |
| { |
| u_char *dst, *src; |
| uint32_t n; |
| |
| if (size <= NJS_STRING_SHORT) { |
| if (value->short_string.size == NJS_STRING_LONG) { |
| dst = value->short_string.start; |
| src = value->long_string.data->start; |
| |
| n = size; |
| |
| while (n != 0) { |
| /* The maximum size is just 14 bytes. */ |
| njs_pragma_loop_disable_vectorization; |
| |
| *dst++ = *src++; |
| n--; |
| } |
| } |
| |
| value->short_string.size = size; |
| value->short_string.length = length; |
| |
| } else { |
| value->long_string.size = size; |
| value->long_string.data->length = length; |
| } |
| } |
| |
| |
| njs_int_t |
| njs_string_hex(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| size_t length; |
| njs_str_t dst; |
| |
| length = njs_encode_hex_length(src, &dst.length); |
| |
| dst.start = njs_string_alloc(vm, value, dst.length, length); |
| if (njs_fast_path(dst.start != NULL)) { |
| njs_encode_hex(&dst, src); |
| return NJS_OK; |
| } |
| |
| return NJS_ERROR; |
| } |
| |
| |
| void |
| njs_encode_hex(njs_str_t *dst, const njs_str_t *src) |
| { |
| u_char *p, c; |
| size_t i, len; |
| const u_char *start; |
| |
| static const u_char hex[16] = "0123456789abcdef"; |
| |
| len = src->length; |
| start = src->start; |
| |
| p = dst->start; |
| |
| for (i = 0; i < len; i++) { |
| c = start[i]; |
| *p++ = hex[c >> 4]; |
| *p++ = hex[c & 0x0f]; |
| } |
| } |
| |
| |
| size_t |
| njs_encode_hex_length(const njs_str_t *src, size_t *out_size) |
| { |
| size_t size; |
| |
| size = src->length * 2; |
| |
| if (out_size != NULL) { |
| *out_size = size; |
| } |
| |
| return size; |
| } |
| |
| |
| void |
| njs_encode_base64(njs_str_t *dst, const njs_str_t *src) |
| { |
| static u_char basis64[] = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| |
| njs_encode_base64_core(dst, src, basis64, 1); |
| } |
| |
| |
| size_t |
| njs_encode_base64_length(const njs_str_t *src, size_t *out_size) |
| { |
| size_t size; |
| |
| size = (src->length == 0) ? 0 : njs_base64_encoded_length(src->length); |
| |
| if (out_size != NULL) { |
| *out_size = size; |
| } |
| |
| return size; |
| } |
| |
| |
| static void |
| njs_encode_base64url(njs_str_t *dst, const njs_str_t *src) |
| { |
| static u_char basis64[] = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; |
| |
| njs_encode_base64_core(dst, src, basis64, 0); |
| } |
| |
| |
| static void |
| njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src, |
| const u_char *basis, njs_bool_t padding) |
| { |
| u_char *d, *s, c0, c1, c2; |
| size_t len; |
| |
| len = src->length; |
| s = src->start; |
| d = dst->start; |
| |
| while (len > 2) { |
| c0 = s[0]; |
| c1 = s[1]; |
| c2 = s[2]; |
| |
| *d++ = basis[c0 >> 2]; |
| *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)]; |
| *d++ = basis[((c1 & 0x0f) << 2) | (c2 >> 6)]; |
| *d++ = basis[c2 & 0x3f]; |
| |
| s += 3; |
| len -= 3; |
| } |
| |
| if (len > 0) { |
| c0 = s[0]; |
| *d++ = basis[c0 >> 2]; |
| |
| if (len == 1) { |
| *d++ = basis[(c0 & 0x03) << 4]; |
| if (padding) { |
| *d++ = '='; |
| *d++ = '='; |
| } |
| |
| } else { |
| c1 = s[1]; |
| |
| *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)]; |
| *d++ = basis[(c1 & 0x0f) << 2]; |
| |
| if (padding) { |
| *d++ = '='; |
| } |
| } |
| |
| } |
| |
| dst->length = d - dst->start; |
| } |
| |
| |
| njs_int_t |
| njs_string_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| size_t length; |
| njs_str_t dst; |
| |
| length = njs_encode_base64_length(src, &dst.length); |
| |
| if (njs_slow_path(dst.length == 0)) { |
| vm->retval = njs_string_empty; |
| return NJS_OK; |
| } |
| |
| dst.start = njs_string_alloc(vm, value, dst.length, length); |
| if (njs_slow_path(dst.start == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_encode_base64(&dst, src); |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_int_t |
| njs_string_base64url(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| size_t padding; |
| njs_str_t dst; |
| |
| if (njs_slow_path(src->length == 0)) { |
| vm->retval = njs_string_empty; |
| return NJS_OK; |
| } |
| |
| padding = src->length % 3; |
| |
| /* |
| * Calculating the padding length: 0 -> 0, 1 -> 2, 2 -> 1. |
| */ |
| padding = (4 >> padding) & 0x03; |
| |
| dst.length = njs_base64_encoded_length(src->length) - padding; |
| |
| dst.start = njs_string_alloc(vm, value, dst.length, dst.length); |
| if (njs_slow_path(dst.start == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_encode_base64url(&dst, src); |
| |
| return NJS_OK; |
| } |
| |
| |
| void |
| njs_string_copy(njs_value_t *dst, njs_value_t *src) |
| { |
| *dst = *src; |
| |
| /* GC: long string retain */ |
| } |
| |
| |
| /* |
| * njs_string_validate() validates an UTF-8 string, evaluates its length, |
| * sets njs_string_prop_t struct. |
| */ |
| |
| njs_int_t |
| njs_string_validate(njs_vm_t *vm, njs_string_prop_t *string, njs_value_t *value) |
| { |
| u_char *start; |
| size_t new_size, map_offset; |
| ssize_t size, length; |
| uint32_t *map; |
| |
| size = value->short_string.size; |
| |
| if (size != NJS_STRING_LONG) { |
| string->start = value->short_string.start; |
| length = value->short_string.length; |
| |
| if (length == 0 && length != size) { |
| length = njs_utf8_length(value->short_string.start, size); |
| |
| if (njs_slow_path(length < 0)) { |
| /* Invalid UTF-8 string. */ |
| return length; |
| } |
| |
| value->short_string.length = length; |
| } |
| |
| } else { |
| string->start = value->long_string.data->start; |
| size = value->long_string.size; |
| length = value->long_string.data->length; |
| |
| if (length == 0 && length != size) { |
| length = njs_utf8_length(string->start, size); |
| |
| if (length != size) { |
| if (njs_slow_path(length < 0)) { |
| /* Invalid UTF-8 string. */ |
| return length; |
| } |
| |
| if (length > NJS_STRING_MAP_STRIDE) { |
| /* |
| * Reallocate the long string with offset map |
| * after the string. |
| */ |
| map_offset = njs_string_map_offset(size); |
| new_size = map_offset + njs_string_map_size(length); |
| |
| start = njs_mp_alloc(vm->mem_pool, new_size); |
| if (njs_slow_path(start == NULL)) { |
| njs_memory_error(vm); |
| return NJS_ERROR; |
| } |
| |
| memcpy(start, string->start, size); |
| string->start = start; |
| value->long_string.data->start = start; |
| |
| map = (uint32_t *) (start + map_offset); |
| map[0] = 0; |
| } |
| } |
| |
| value->long_string.data->length = length; |
| } |
| } |
| |
| string->size = size; |
| string->length = length; |
| |
| return length; |
| } |
| |
| |
| size_t |
| njs_string_prop(njs_string_prop_t *string, const njs_value_t *value) |
| { |
| size_t size; |
| uintptr_t length; |
| |
| size = value->short_string.size; |
| |
| if (size != NJS_STRING_LONG) { |
| string->start = (u_char *) value->short_string.start; |
| length = value->short_string.length; |
| |
| } else { |
| string->start = (u_char *) value->long_string.data->start; |
| size = value->long_string.size; |
| length = value->long_string.data->length; |
| } |
| |
| string->size = size; |
| string->length = length; |
| |
| return (length == 0) ? size : length; |
| } |
| |
| |
| static njs_int_t |
| njs_string_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| njs_int_t ret; |
| njs_value_t *value; |
| njs_object_t *object; |
| |
| if (nargs == 1) { |
| value = njs_value_arg(&njs_string_empty); |
| |
| } else { |
| value = &args[1]; |
| |
| if (njs_slow_path(!njs_is_string(value))) { |
| if (!vm->top_frame->ctor && njs_is_symbol(value)) { |
| return njs_symbol_descriptive_string(vm, &vm->retval, value); |
| } |
| |
| ret = njs_value_to_string(vm, value, value); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| } |
| |
| if (vm->top_frame->ctor) { |
| object = njs_object_value_alloc(vm, value, value->type); |
| if (njs_slow_path(object == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_set_type_object(&vm->retval, object, NJS_OBJECT_STRING); |
| |
| } else { |
| vm->retval = *value; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static const njs_object_prop_t njs_string_constructor_properties[] = |
| { |
| { |
| .type = NJS_PROPERTY, |
| .name = njs_string("name"), |
| .value = njs_string("String"), |
| .configurable = 1, |
| }, |
| |
| { |
| .type = NJS_PROPERTY, |
| .name = njs_string("length"), |
| .value = njs_value(NJS_NUMBER, 1, 1.0), |
| .configurable = 1, |
| }, |
| |
| { |
| .type = NJS_PROPERTY_HANDLER, |
| .name = njs_string("prototype"), |
| .value = njs_prop_handler(njs_object_prototype_create), |
| }, |
| |
| { |
| .type = NJS_PROPERTY, |
| .name = njs_string("bytesFrom"), |
| .value = njs_native_function(njs_string_bytes_from, 0), |
| .writable = 1, |
| .configurable = 1, |
| }, |
| |
| { |
| .type = NJS_PROPERTY, |
| .name = njs_string("fromCharCode"), |
| .value = njs_native_function2(njs_string_from_char_code, 1, 0), |
| .writable = 1, |
| .configurable = 1, |
| }, |
| |
| { |
| .type = NJS_PROPERTY, |
| .name = njs_string("fromCodePoint"), |
| .value = njs_native_function2(njs_string_from_char_code, 1, 1), |
| .writable = 1, |
| .configurable = 1, |
| }, |
| }; |
| |
| |
| const njs_object_init_t njs_string_constructor_init = { |
| njs_string_constructor_properties, |
| njs_nitems(njs_string_constructor_properties), |
| }; |
| |
| |
| 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) |
| { |
| size_t size; |
| uintptr_t length; |
| njs_object_t *proto; |
| njs_object_value_t *ov; |
| |
| /* |
| * This getter can be called for string primitive, String object, |
| * String.prototype. The zero should be returned for the latter case. |
| */ |
| length = 0; |
| |
| if (njs_slow_path(njs_is_object(value))) { |
| proto = njs_object(value); |
| |
| do { |
| if (njs_fast_path(proto->type == NJS_OBJECT_STRING)) { |
| break; |
| } |
| |
| proto = proto->__proto__; |
| } while (proto != NULL); |
| |
| if (proto != NULL) { |
| ov = (njs_object_value_t *) proto; |
| value = &ov->value; |
| } |
| } |
| |
| if (njs_is_string(value)) { |
| size = value->short_string.size; |
| length = value->short_string.length; |
| |
| if (size == NJS_STRING_LONG) { |
| size = value->long_string.size; |
| length = value->long_string.data->length; |
| } |
| |
| length = (length == 0) ? size : length; |
| } |
| |
| njs_set_number(retval, length); |
| |
| njs_release(vm, value); |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_bool_t |
| njs_string_eq(const njs_value_t *v1, const njs_value_t *v2) |
| { |
| size_t size, length1, length2; |
| const u_char *start1, *start2; |
| |
| size = v1->short_string.size; |
| |
| if (size != v2->short_string.size) { |
| return 0; |
| } |
| |
| if (size != NJS_STRING_LONG) { |
| length1 = v1->short_string.length; |
| length2 = v2->short_string.length; |
| |
| /* |
| * Using full memcmp() comparison if at least one string |
| * is a Byte string. |
| */ |
| if (length1 != 0 && length2 != 0 && length1 != length2) { |
| return 0; |
| } |
| |
| start1 = v1->short_string.start; |
| start2 = v2->short_string.start; |
| |
| } else { |
| size = v1->long_string.size; |
| |
| if (size != v2->long_string.size) { |
| return 0; |
| } |
| |
| length1 = v1->long_string.data->length; |
| length2 = v2->long_string.data->length; |
| |
| /* |
| * Using full memcmp() comparison if at least one string |
| * is a Byte string. |
| */ |
| if (length1 != 0 && length2 != 0 && length1 != length2) { |
| return 0; |
| } |
| |
| start1 = v1->long_string.data->start; |
| start2 = v2->long_string.data->start; |
| } |
| |
| return (memcmp(start1, start2, size) == 0); |
| } |
| |
| |
| njs_int_t |
| njs_string_cmp(const njs_value_t *v1, const njs_value_t *v2) |
| { |
| size_t size, size1, size2; |
| njs_int_t ret; |
| const u_char *start1, *start2; |
| |
| size1 = v1->short_string.size; |
| |
| if (size1 != NJS_STRING_LONG) { |
| start1 = v1->short_string.start; |
| |
| } else { |
| size1 = v1->long_string.size; |
| start1 = v1->long_string.data->start; |
| } |
| |
| size2 = v2->short_string.size; |
| |
| if (size2 != NJS_STRING_LONG) { |
| start2 = v2->short_string.start; |
| |
| } else { |
| size2 = v2->long_string.size; |
| start2 = v2->long_string.data->start; |
| } |
| |
| size = njs_min(size1, size2); |
| |
| ret = memcmp(start1, start2, size); |
| |
| if (ret != 0) { |
| return ret; |
| } |
| |
| return (size1 - size2); |
| } |
| |
| |
| 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) |
| { |
| njs_value_t *value; |
| |
| value = &args[0]; |
| |
| if (value->type != NJS_STRING) { |
| |
| if (value->type == NJS_OBJECT_STRING) { |
| value = njs_object_value(value); |
| |
| } else { |
| njs_type_error(vm, "unexpected value type:%s", |
| njs_type_string(value->type)); |
| return NJS_ERROR; |
| } |
| } |
| |
| vm->retval = *value; |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * String.prototype.toString([encoding]). |
| * Returns the string as is if no additional argument is provided, |
| * otherwise converts a string into an encoded string: hex, base64, |
| * base64url. |
| */ |
| |
| 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) |
| { |
| njs_int_t ret; |
| njs_str_t enc, str; |
| njs_value_t value; |
| njs_string_prop_t string; |
| |
| ret = njs_string_prototype_value_of(vm, args, nargs, unused); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (nargs < 2) { |
| return NJS_OK; |
| } |
| |
| if (njs_slow_path(!njs_is_string(&args[1]))) { |
| njs_type_error(vm, "encoding must be a string"); |
| return NJS_ERROR; |
| } |
| |
| value = vm->retval; |
| |
| (void) njs_string_prop(&string, &value); |
| |
| njs_string_get(&args[1], &enc); |
| |
| str.length = string.size; |
| str.start = string.start; |
| |
| if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) { |
| return njs_string_hex(vm, &vm->retval, &str); |
| |
| } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) { |
| return njs_string_base64(vm, &vm->retval, &str); |
| |
| } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) { |
| return njs_string_base64url(vm, &vm->retval, &str); |
| } |
| |
| njs_type_error(vm, "Unknown encoding: \"%V\"", &enc); |
| |
| return NJS_ERROR; |
| } |
| |
| |
| njs_int_t |
| njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| u_char *p, *start; |
| uint64_t size, length, mask; |
| njs_int_t ret; |
| njs_uint_t i; |
| njs_string_prop_t string; |
| |
| if (njs_is_null_or_undefined(&args[0])) { |
| njs_type_error(vm, "\"this\" argument is null or undefined"); |
| return NJS_ERROR; |
| } |
| |
| for (i = 0; i < nargs; i++) { |
| if (!njs_is_string(&args[i])) { |
| ret = njs_value_to_string(vm, &args[i], &args[i]); |
| if (ret != NJS_OK) { |
| return ret; |
| } |
| } |
| } |
| |
| if (nargs == 1) { |
| njs_string_copy(&vm->retval, &args[0]); |
| return NJS_OK; |
| } |
| |
| size = 0; |
| length = 0; |
| mask = -1; |
| |
| for (i = 0; i < nargs; i++) { |
| (void) njs_string_prop(&string, &args[i]); |
| |
| size += string.size; |
| length += string.length; |
| |
| if (string.length == 0 && string.size != 0) { |
| mask = 0; |
| } |
| } |
| |
| length &= mask; |
| |
| start = njs_string_alloc(vm, &vm->retval, size, length); |
| if (njs_slow_path(start == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| p = start; |
| |
| for (i = 0; i < nargs; i++) { |
| (void) njs_string_prop(&string, &args[i]); |
| |
| p = memcpy(p, string.start, string.size); |
| p += string.size; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_inline njs_int_t |
| njs_string_object_validate(njs_vm_t *vm, njs_value_t *object) |
| { |
| njs_int_t ret; |
| |
| if (njs_slow_path(njs_is_null_or_undefined(object))) { |
| njs_type_error(vm, "cannot convert undefined to object"); |
| return NJS_ERROR; |
| } |
| |
| if (njs_slow_path(!njs_is_string(object))) { |
| ret = njs_value_to_string(vm, object, object); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * String.fromUTF8(start[, end]). |
| * The method converts an UTF-8 encoded byte string to an Unicode string. |
| */ |
| |
| 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) |
| { |
| ssize_t length; |
| njs_int_t ret; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (string.length != 0) { |
| /* ASCII or UTF8 string. */ |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| string.start += slice.start; |
| |
| length = njs_utf8_length(string.start, slice.length); |
| |
| if (length >= 0) { |
| return njs_string_new(vm, &vm->retval, string.start, slice.length, |
| length); |
| } |
| |
| vm->retval = njs_value_null; |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * String.toUTF8(start[, end]). |
| * The method serializes Unicode string to an UTF-8 encoded byte string. |
| */ |
| |
| 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) |
| { |
| njs_int_t ret; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| (void) njs_string_prop(&string, njs_argument(args, 0)); |
| |
| string.length = 0; |
| slice.string_length = string.size; |
| |
| ret = njs_string_slice_args(vm, &slice, args, nargs); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| |
| /* |
| * String.fromBytes(start[, end]). |
| * The method converts a byte string to an Unicode string. |
| */ |
| |
| 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) |
| { |
| u_char *p, *s, *start, *end; |
| size_t size; |
| njs_int_t ret; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (string.length != 0) { |
| /* ASCII or UTF8 string. */ |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| size = 0; |
| string.start += slice.start; |
| end = string.start + slice.length; |
| |
| for (p = string.start; p < end; p++) { |
| size += (*p < 0x80) ? 1 : 2; |
| } |
| |
| start = njs_string_alloc(vm, &vm->retval, size, slice.length); |
| |
| if (njs_fast_path(start != NULL)) { |
| |
| if (size == slice.length) { |
| memcpy(start, string.start, size); |
| |
| } else { |
| s = start; |
| end = string.start + slice.length; |
| |
| for (p = string.start; p < end; p++) { |
| s = njs_utf8_encode(s, *p); |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| return NJS_ERROR; |
| } |
| |
| |
| /* |
| * String.toBytes(start[, end]). |
| * The method serializes an Unicode string to a byte string. |
| * The method returns null if a character larger than 255 is |
| * encountered in the Unicode string. |
| */ |
| |
| 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) |
| { |
| u_char *p; |
| size_t length; |
| uint32_t byte; |
| njs_int_t ret; |
| const u_char *s, *end; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| njs_unicode_decode_t ctx; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (string.length == 0) { |
| /* Byte string. */ |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| p = njs_string_alloc(vm, &vm->retval, slice.length, 0); |
| |
| if (njs_fast_path(p != NULL)) { |
| |
| if (string.length != string.size) { |
| /* UTF-8 string. */ |
| end = string.start + string.size; |
| |
| s = njs_string_offset(string.start, end, slice.start); |
| |
| length = slice.length; |
| |
| njs_utf8_decode_init(&ctx); |
| |
| while (length != 0 && s < end) { |
| byte = njs_utf8_decode(&ctx, &s, end); |
| |
| if (njs_slow_path(byte > 0xFF)) { |
| njs_release(vm, &vm->retval); |
| vm->retval = njs_value_null; |
| |
| return NJS_OK; |
| } |
| |
| *p++ = (u_char) byte; |
| length--; |
| } |
| |
| } else { |
| /* ASCII string. */ |
| memcpy(p, string.start + slice.start, slice.length); |
| } |
| |
| return NJS_OK; |
| } |
| |
| return NJS_ERROR; |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| njs_int_t ret; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_substring(njs_vm_t *vm, njs_value_t *args, |
| njs_uint_t nargs, njs_index_t unused) |
| { |
| int64_t start, end, length; |
| njs_int_t ret; |
| njs_value_t *value; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| length = njs_string_prop(&string, njs_argument(args, 0)); |
| |
| slice.string_length = length; |
| start = 0; |
| |
| if (nargs > 1) { |
| value = njs_argument(args, 1); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &start); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| start = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (start < 0) { |
| start = 0; |
| |
| } else if (start > length) { |
| start = length; |
| } |
| |
| end = length; |
| |
| if (nargs > 2) { |
| value = njs_arg(args, nargs, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &end); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| end = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (end < 0) { |
| end = 0; |
| |
| } else if (end >= length) { |
| end = length; |
| } |
| } |
| |
| length = end - start; |
| |
| if (length < 0) { |
| length = -length; |
| start = end; |
| } |
| } |
| |
| slice.start = start; |
| slice.length = length; |
| |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_substr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| int64_t start, length, n; |
| njs_int_t ret; |
| njs_value_t *value; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| length = njs_string_prop(&string, njs_argument(args, 0)); |
| |
| slice.string_length = length; |
| start = 0; |
| |
| if (nargs > 1) { |
| value = njs_arg(args, nargs, 1); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &start); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| start = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (start < length) { |
| if (start < 0) { |
| start += length; |
| |
| if (start < 0) { |
| start = 0; |
| } |
| } |
| |
| length -= start; |
| |
| if (nargs > 2) { |
| value = njs_arg(args, nargs, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &n); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| n = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (n < 0) { |
| length = 0; |
| |
| } else if (n < length) { |
| length = n; |
| } |
| } |
| |
| } else { |
| start = 0; |
| length = 0; |
| } |
| } |
| |
| slice.start = start; |
| slice.length = length; |
| |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| |
| 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) |
| { |
| size_t length; |
| int64_t start; |
| njs_int_t ret; |
| njs_slice_prop_t slice; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| slice.string_length = njs_string_prop(&string, njs_argument(args, 0)); |
| |
| ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| length = 1; |
| |
| if (start < 0 || start >= (int64_t) slice.string_length) { |
| start = 0; |
| length = 0; |
| } |
| |
| slice.start = start; |
| slice.length = length; |
| |
| return njs_string_slice(vm, &vm->retval, &string, &slice); |
| } |
| |
| |
| 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) |
| { |
| slice->string_length = njs_string_prop(string, &args[0]); |
| |
| return njs_string_slice_args(vm, slice, args, nargs); |
| } |
| |
| |
| 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) |
| { |
| int64_t start, end, length; |
| njs_int_t ret; |
| njs_value_t *value; |
| |
| length = slice->string_length; |
| |
| value = njs_arg(args, nargs, 1); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &start); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| start = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (start < 0) { |
| start += length; |
| |
| if (start < 0) { |
| start = 0; |
| } |
| } |
| |
| if (start >= length) { |
| start = 0; |
| length = 0; |
| |
| } else { |
| value = njs_arg(args, nargs, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| if (njs_is_defined(value)) { |
| ret = njs_value_to_integer(vm, value, &end); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| end = length; |
| } |
| |
| } else { |
| end = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (end < 0) { |
| end += length; |
| } |
| |
| if (length >= end) { |
| length = end - start; |
| |
| if (length < 0) { |
| start = 0; |
| length = 0; |
| } |
| |
| } else { |
| length -= start; |
| } |
| } |
| |
| slice->start = start; |
| slice->length = length; |
| |
| return NJS_OK; |
| } |
| |
| |
| void |
| njs_string_slice_string_prop(njs_string_prop_t *dst, |
| const njs_string_prop_t *string, const njs_slice_prop_t *slice) |
| { |
| size_t size, n, length; |
| const u_char *p, *start, *end; |
| |
| length = slice->length; |
| start = string->start; |
| |
| if (string->size == slice->string_length) { |
| /* Byte or ASCII string. */ |
| start += slice->start; |
| size = slice->length; |
| |
| if (string->length == 0) { |
| /* Byte string. */ |
| length = 0; |
| } |
| |
| } else { |
| /* UTF-8 string. */ |
| end = start + string->size; |
| |
| if (slice->start < slice->string_length) { |
| start = njs_string_offset(start, end, slice->start); |
| |
| /* Evaluate size of the slice in bytes and adjust length. */ |
| p = start; |
| n = length; |
| |
| while (n != 0 && p < end) { |
| p = njs_utf8_next(p, end); |
| n--; |
| } |
| |
| size = p - start; |
| length -= n; |
| |
| } else { |
| length = 0; |
| size = 0; |
| } |
| } |
| |
| dst->start = (u_char *) start; |
| dst->length = length; |
| dst->size = size; |
| } |
| |
| |
| 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) |
| { |
| njs_string_prop_t prop; |
| |
| njs_string_slice_string_prop(&prop, string, slice); |
| |
| if (njs_fast_path(prop.size != 0)) { |
| return njs_string_new(vm, dst, prop.start, prop.size, prop.length); |
| } |
| |
| *dst = njs_string_empty; |
| |
| return NJS_OK; |
| } |
| |
| |
| 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) |
| { |
| double num; |
| size_t length; |
| int64_t index; |
| uint32_t code; |
| njs_int_t ret; |
| const u_char *start, *end; |
| njs_string_prop_t string; |
| njs_unicode_decode_t ctx; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| length = njs_string_prop(&string, njs_argument(args, 0)); |
| |
| ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &index); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (njs_slow_path(index < 0 || index >= (int64_t) length)) { |
| num = NAN; |
| goto done; |
| } |
| |
| if (length == string.size) { |
| /* Byte or ASCII string. */ |
| code = string.start[index]; |
| |
| } else { |
| njs_utf8_decode_init(&ctx); |
| |
| /* UTF-8 string. */ |
| end = string.start + string.size; |
| start = njs_string_offset(string.start, end, index); |
| code = njs_utf8_decode(&ctx, &start, end); |
| } |
| |
| num = code; |
| |
| done: |
| |
| njs_set_number(&vm->retval, num); |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * String.bytesFrom(array-like). |
| * Converts an array-like object containing octets into a byte string. |
| * |
| * String.bytesFrom(string[, encoding]). |
| * Converts a string using provided encoding: hex, base64, base64url to |
| * a byte string. |
| */ |
| |
| static njs_int_t |
| njs_string_bytes_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| njs_value_t *value; |
| |
| value = njs_arg(args, nargs, 1); |
| |
| switch (value->type) { |
| case NJS_OBJECT_STRING: |
| value = njs_object_value(value); |
| |
| /* Fall through. */ |
| |
| case NJS_STRING: |
| return njs_string_bytes_from_string(vm, value, njs_arg(args, nargs, 2)); |
| |
| default: |
| if (njs_is_object(value)) { |
| return njs_string_bytes_from_array_like(vm, value); |
| } |
| } |
| |
| njs_type_error(vm, "value must be a string or array-like object"); |
| |
| return NJS_ERROR; |
| } |
| |
| |
| static njs_int_t |
| njs_string_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value) |
| { |
| u_char *p; |
| int64_t length; |
| uint32_t u32; |
| njs_int_t ret; |
| njs_array_t *array; |
| njs_value_t *octet, index, prop; |
| njs_array_buffer_t *buffer; |
| |
| array = NULL; |
| buffer = NULL; |
| |
| switch (value->type) { |
| case NJS_ARRAY: |
| array = njs_array(value); |
| length = array->length; |
| break; |
| |
| case NJS_ARRAY_BUFFER: |
| case NJS_TYPED_ARRAY: |
| |
| if (njs_is_typed_array(value)) { |
| buffer = njs_typed_array(value)->buffer; |
| |
| } else { |
| buffer = njs_array_buffer(value); |
| } |
| |
| length = buffer->size; |
| break; |
| |
| default: |
| ret = njs_object_length(vm, value, &length); |
| if (njs_slow_path(ret == NJS_ERROR)) { |
| return ret; |
| } |
| } |
| |
| p = njs_string_alloc(vm, &vm->retval, length, 0); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| if (array != NULL) { |
| octet = array->start; |
| |
| while (length != 0) { |
| ret = njs_value_to_uint32(vm, octet, &u32); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| *p++ = (u_char) u32; |
| octet++; |
| length--; |
| } |
| |
| } else if (buffer != NULL) { |
| memcpy(p, buffer->u.u8, length); |
| |
| } else { |
| p += length - 1; |
| |
| while (length != 0) { |
| njs_set_number(&index, length - 1); |
| |
| ret = njs_value_property(vm, value, &index, &prop); |
| if (njs_slow_path(ret == NJS_ERROR)) { |
| return ret; |
| } |
| |
| ret = njs_value_to_uint32(vm, &prop, &u32); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| *p-- = (u_char) u32; |
| length--; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_string_bytes_from_string(njs_vm_t *vm, const njs_value_t *string, |
| const njs_value_t *encoding) |
| { |
| njs_str_t enc, str; |
| |
| if (!njs_is_string(encoding)) { |
| njs_type_error(vm, "\"encoding\" must be a string"); |
| return NJS_ERROR; |
| } |
| |
| njs_string_get(encoding, &enc); |
| njs_string_get(string, &str); |
| |
| if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) { |
| return njs_string_decode_hex(vm, &vm->retval, &str); |
| |
| } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) { |
| return njs_string_decode_base64(vm, &vm->retval, &str); |
| |
| } else if (enc.length == 9 && memcmp(enc.start, "base64url", 6) == 0) { |
| return njs_string_decode_base64url(vm, &vm->retval, &str); |
| } |
| |
| njs_type_error(vm, "Unknown encoding: \"%V\"", &enc); |
| |
| return NJS_ERROR; |
| } |
| |
| |
| size_t |
| njs_decode_hex_length(const njs_str_t *src, size_t *out_size) |
| { |
| if (out_size != NULL) { |
| *out_size = src->length / 2; |
| } |
| |
| return 0; |
| } |
| |
| |
| void |
| njs_decode_hex(njs_str_t *dst, const njs_str_t *src) |
| { |
| u_char *p; |
| size_t len; |
| njs_int_t c; |
| njs_uint_t i, n; |
| const u_char *start; |
| |
| n = 0; |
| p = dst->start; |
| |
| start = src->start; |
| len = src->length; |
| |
| for (i = 0; i < len; i++) { |
| c = njs_char_to_hex(start[i]); |
| if (njs_slow_path(c < 0)) { |
| break; |
| } |
| |
| n = n * 16 + c; |
| |
| if ((i & 1) != 0) { |
| *p++ = (u_char) n; |
| n = 0; |
| } |
| } |
| |
| dst->length -= (dst->start + dst->length) - p; |
| } |
| |
| |
| void |
| njs_decode_utf8(njs_str_t *dst, const njs_str_t *src) |
| { |
| njs_unicode_decode_t ctx; |
| |
| njs_utf8_decode_init(&ctx); |
| |
| (void) njs_utf8_stream_encode(&ctx, src->start, src->start + src->length, |
| dst->start, 1, 0); |
| } |
| |
| |
| size_t |
| njs_decode_utf8_length(const njs_str_t *src, size_t *out_size) |
| { |
| njs_unicode_decode_t ctx; |
| |
| njs_utf8_decode_init(&ctx); |
| |
| return njs_utf8_stream_length(&ctx, src->start, src->length, 1, 0, |
| out_size); |
| } |
| |
| |
| njs_int_t |
| njs_string_decode_utf8(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| size_t length; |
| njs_str_t dst; |
| |
| length = njs_decode_utf8_length(src, &dst.length); |
| dst.start = njs_string_alloc(vm, value, dst.length, length); |
| |
| if (njs_fast_path(dst.start != NULL)) { |
| njs_decode_utf8(&dst, src); |
| return NJS_OK; |
| } |
| |
| return NJS_ERROR; |
| } |
| |
| |
| njs_int_t |
| njs_string_decode_hex(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| size_t size, length; |
| njs_str_t dst; |
| |
| length = njs_decode_hex_length(src, &size); |
| |
| if (njs_slow_path(size == 0)) { |
| vm->retval = njs_string_empty; |
| return NJS_OK; |
| } |
| |
| dst.start = njs_string_alloc(vm, value, size, length); |
| if (njs_slow_path(dst.start == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| dst.length = size; |
| |
| njs_decode_hex(&dst, src); |
| |
| if (njs_slow_path(dst.length != size)) { |
| njs_string_truncate(value, dst.length, 0); |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static size_t |
| njs_decode_base64_length_core(const njs_str_t *src, const u_char *basis, |
| size_t *out_size) |
| { |
| uint pad; |
| size_t len; |
| |
| for (len = 0; len < src->length; len++) { |
| if (src->start[len] == '=') { |
| break; |
| } |
| |
| if (basis[src->start[len]] == 77) { |
| break; |
| } |
| } |
| |
| pad = 0; |
| |
| if (len % 4 != 0) { |
| pad = 4 - (len % 4); |
| len += pad; |
| } |
| |
| len = njs_base64_decoded_length(len, pad); |
| |
| if (out_size != NULL) { |
| *out_size = len; |
| } |
| |
| return 0; |
| } |
| |
| |
| size_t |
| njs_decode_base64_length(const njs_str_t *src, size_t *out_size) |
| { |
| return njs_decode_base64_length_core(src, njs_basis64, out_size); |
| } |
| |
| |
| size_t |
| njs_decode_base64url_length(const njs_str_t *src, size_t *out_size) |
| { |
| return njs_decode_base64_length_core(src, njs_basis64url, out_size); |
| } |
| |
| |
| static void |
| njs_decode_base64_core(njs_str_t *dst, const njs_str_t *src, |
| const u_char *basis) |
| { |
| size_t len; |
| u_char *d, *s; |
| |
| s = src->start; |
| d = dst->start; |
| |
| len = dst->length; |
| |
| while (len >= 3) { |
| *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4); |
| *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2); |
| *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]); |
| |
| s += 4; |
| len -= 3; |
| } |
| |
| if (len >= 1) { |
| *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4); |
| } |
| |
| if (len >= 2) { |
| *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2); |
| } |
| } |
| |
| |
| void |
| njs_decode_base64(njs_str_t *dst, const njs_str_t *src) |
| { |
| njs_decode_base64_core(dst, src, njs_basis64); |
| } |
| |
| |
| void |
| njs_decode_base64url(njs_str_t *dst, const njs_str_t *src) |
| { |
| njs_decode_base64_core(dst, src, njs_basis64url); |
| } |
| |
| |
| 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) |
| { |
| size_t length; |
| const u_char *basis; |
| njs_str_t dst; |
| |
| basis = (url) ? njs_basis64url : njs_basis64; |
| |
| length = njs_decode_base64_length_core(src, basis, &dst.length); |
| |
| if (njs_slow_path(dst.length == 0)) { |
| vm->retval = njs_string_empty; |
| return NJS_OK; |
| } |
| |
| dst.start = njs_string_alloc(vm, value, dst.length, length); |
| if (njs_slow_path(dst.start == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_decode_base64_core(&dst, src, basis); |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_int_t |
| njs_string_decode_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src) |
| { |
| return njs_string_decode_base64_core(vm, value, src, 0); |
| } |
| |
| |
| njs_int_t |
| njs_string_decode_base64url(njs_vm_t *vm, njs_value_t *value, |
| const njs_str_t *src) |
| { |
| return njs_string_decode_base64_core(vm, value, src, 1); |
| } |
| |
| |
| 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) |
| { |
| double num; |
| u_char *p, *start, *end; |
| ssize_t len; |
| int32_t code; |
| uint32_t cp; |
| uint64_t length, size; |
| njs_int_t ret; |
| njs_uint_t i; |
| njs_unicode_decode_t ctx; |
| u_char buf[4]; |
| |
| size = 0; |
| length = 0; |
| |
| cp = 0x00; |
| end = buf + sizeof(buf); |
| |
| njs_utf16_decode_init(&ctx); |
| |
| for (i = 1; i < nargs; i++) { |
| if (!njs_is_numeric(&args[i])) { |
| ret = njs_value_to_numeric(vm, &args[i], &args[i]); |
| if (ret != NJS_OK) { |
| return ret; |
| } |
| } |
| |
| if (is_point) { |
| num = njs_number(&args[i]); |
| if (isnan(num)) { |
| goto range_error; |
| } |
| |
| code = num; |
| |
| if (code != num || code < 0 || code > 0x10FFFF) { |
| goto range_error; |
| } |
| |
| } else { |
| code = njs_number_to_uint16(njs_number(&args[i])); |
| } |
| |
| start = buf; |
| len = njs_utf16_encode(code, &start, end); |
| |
| start = buf; |
| cp = njs_utf16_decode(&ctx, (const u_char **) &start, start + len); |
| |
| if (cp > NJS_UNICODE_MAX_CODEPOINT) { |
| if (cp == NJS_UNICODE_CONTINUE) { |
| continue; |
| } |
| |
| cp = NJS_UNICODE_REPLACEMENT; |
| } |
| |
| size += njs_utf8_size(cp); |
| length++; |
| } |
| |
| if (cp == NJS_UNICODE_CONTINUE) { |
| size += njs_utf8_size(NJS_UNICODE_REPLACEMENT); |
| length++; |
| } |
| |
| p = njs_string_alloc(vm, &vm->retval, size, length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_utf16_decode_init(&ctx); |
| |
| for (i = 1; i < nargs; i++) { |
| if (is_point) { |
| code = njs_number(&args[i]); |
| |
| } else { |
| code = njs_number_to_uint16(njs_number(&args[i])); |
| } |
| |
| start = buf; |
| len = njs_utf16_encode(code, &start, end); |
| |
| start = buf; |
| cp = njs_utf16_decode(&ctx, (const u_char **) &start, start + len); |
| |
| if (cp > NJS_UNICODE_MAX_CODEPOINT) { |
| if (cp == NJS_UNICODE_CONTINUE && i + 1 != nargs) { |
| continue; |
| } |
| |
| cp = NJS_UNICODE_REPLACEMENT; |
| } |
| |
| p = njs_utf8_encode(p, cp); |
| } |
| |
| return NJS_OK; |
| |
| range_error: |
| |
| njs_range_error(vm, NULL); |
| |
| return NJS_ERROR; |
| } |
| |
| |
| static int64_t |
| njs_string_index_of(njs_string_prop_t *string, njs_string_prop_t *search, |
| size_t from) |
| { |
| size_t index, length, search_length; |
| const u_char *p, *end; |
| |
| length = (string->length == 0) ? string->size : string->length; |
| |
| if (njs_slow_path(search->size == 0)) { |
| return (from < length) ? from : length; |
| } |
| |
| index = from; |
| search_length = (search->length == 0) ? search->size : search->length; |
| |
| if (length - index >= search_length) { |
| end = string->start + string->size; |
| |
| if (string->size == length) { |
| /* Byte or ASCII string. */ |
| |
| end -= (search->size - 1); |
| |
| for (p = string->start + index; p < end; p++) { |
| if (memcmp(p, search->start, search->size) == 0) { |
| return index; |
| } |
| |
| index++; |
| } |
| |
| } else { |
| /* UTF-8 string. */ |
| |
| p = njs_string_offset(string->start, end, index); |
| end -= search->size - 1; |
| |
| while (p < end) { |
| if (memcmp(p, search->start, search->size) == 0) { |
| return index; |
| } |
| |
| index++; |
| p = njs_utf8_next(p, end); |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| |
| 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) |
| { |
| int64_t from, length; |
| njs_int_t ret; |
| njs_value_t *this, *search, *pos, search_lvalue, pos_lvalue; |
| njs_string_prop_t string, s; |
| |
| this = njs_argument(args, 0); |
| |
| if (njs_slow_path(njs_is_null_or_undefined(this))) { |
| njs_type_error(vm, "cannot convert \"%s\"to object", |
| njs_type_string(this->type)); |
| return NJS_ERROR; |
| } |
| |
| ret = njs_value_to_string(vm, this, this); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_ERROR; |
| } |
| |
| search = njs_lvalue_arg(&search_lvalue, args, nargs, 1); |
| ret = njs_value_to_string(vm, search, search); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| pos = njs_lvalue_arg(&pos_lvalue, args, nargs, 2); |
| ret = njs_value_to_integer(vm, pos, &from); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| length = njs_string_prop(&string, this); |
| (void) njs_string_prop(&s, search); |
| |
| from = njs_min(njs_max(from, 0), length); |
| |
| njs_set_number(&vm->retval, njs_string_index_of(&string, &s, from)); |
| |
| return NJS_OK; |
| } |
| |
| |
| 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) |
| { |
| double pos; |
| ssize_t index, start, length, search_length; |
| njs_int_t ret; |
| njs_value_t *value, *search_string, lvalue; |
| const u_char *p, *end; |
| njs_string_prop_t string, search; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| index = -1; |
| |
| length = njs_string_prop(&string, njs_argument(args, 0)); |
| |
| search_string = njs_lvalue_arg(&lvalue, args, nargs, 1); |
| |
| if (njs_slow_path(!njs_is_string(search_string))) { |
| ret = njs_value_to_string(vm, search_string, search_string); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| search_length = njs_string_prop(&search, search_string); |
| |
| if (length < search_length) { |
| goto done; |
| } |
| |
| value = njs_arg(args, nargs, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_number(vm, value, &pos); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| pos = njs_number(value); |
| } |
| |
| if (isnan(pos)) { |
| index = NJS_STRING_MAX_LENGTH; |
| |
| } else { |
| index = njs_number_to_integer(pos); |
| |
| if (index < 0) { |
| index = 0; |
| } |
| } |
| |
| if (search_length == 0) { |
| index = njs_min(index, length); |
| goto done; |
| } |
| |
| if (index >= length) { |
| index = length - 1; |
| } |
| |
| if (string.size == (size_t) length) { |
| /* Byte or ASCII string. */ |
| |
| start = length - search.size; |
| |
| if (index > start) { |
| index = start; |
| } |
| |
| p = string.start + index; |
| |
| do { |
| if (memcmp(p, search.start, search.size) == 0) { |
| goto done; |
| } |
| |
| index--; |
| p--; |
| |
| } while (p >= string.start); |
| |
| } else { |
| /* UTF-8 string. */ |
| |
| end = string.start + string.size; |
| p = njs_string_offset(string.start, end, index); |
| end -= search.size; |
| |
| while (p > end) { |
| index--; |
| p = njs_utf8_prev(p); |
| } |
| |
| for ( ;; ) { |
| if (memcmp(p, search.start, search.size) == 0) { |
| goto done; |
| } |
| |
| index--; |
| |
| if (p <= string.start) { |
| break; |
| } |
| |
| p = njs_utf8_prev(p); |
| } |
| } |
| |
| done: |
| |
| njs_set_number(&vm->retval, index); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_includes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| int64_t index, length, search_length; |
| njs_int_t ret; |
| njs_value_t *value; |
| const u_char *p, *end; |
| const njs_value_t *retval; |
| njs_string_prop_t string, search; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| retval = &njs_value_true; |
| |
| if (nargs > 1) { |
| value = njs_argument(args, 1); |
| |
| if (njs_slow_path(!njs_is_string(value))) { |
| ret = njs_value_to_string(vm, value, value); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| search_length = njs_string_prop(&search, value); |
| |
| if (nargs > 2) { |
| value = njs_argument(args, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| ret = njs_value_to_integer(vm, value, &index); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| } else { |
| index = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (index < 0) { |
| index = 0; |
| } |
| |
| } else { |
| index = 0; |
| } |
| |
| if (search_length == 0) { |
| goto done; |
| } |
| |
| length = njs_string_prop(&string, &args[0]); |
| |
| if (length - index >= search_length) { |
| end = string.start + string.size; |
| |
| if (string.size == (size_t) length) { |
| /* Byte or ASCII string. */ |
| p = string.start + index; |
| |
| } else { |
| /* UTF-8 string. */ |
| p = njs_string_offset(string.start, end, index); |
| } |
| |
| end -= search.size - 1; |
| |
| while (p < end) { |
| if (memcmp(p, search.start, search.size) == 0) { |
| goto done; |
| } |
| |
| p++; |
| } |
| } |
| } |
| |
| retval = &njs_value_false; |
| |
| done: |
| |
| vm->retval = *retval; |
| |
| return NJS_OK; |
| } |
| |
| |
| 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) |
| { |
| int64_t index, length, search_length; |
| njs_int_t ret; |
| njs_value_t *value, lvalue; |
| const u_char *p, *end; |
| const njs_value_t *retval; |
| njs_string_prop_t string, search; |
| |
| retval = &njs_value_true; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| value = njs_lvalue_arg(&lvalue, args, nargs, 1); |
| |
| if (njs_slow_path(!njs_is_string(value))) { |
| ret = njs_value_to_string(vm, value, value); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| search_length = njs_string_prop(&search, value); |
| |
| value = njs_arg(args, nargs, 2); |
| |
| if (njs_slow_path(!njs_is_number(value))) { |
| index = -1; |
| |
| if (!njs_is_undefined(value)) { |
| ret = njs_value_to_integer(vm, value, &index); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| } else { |
| index = njs_number_to_integer(njs_number(value)); |
| } |
| |
| if (search_length == 0) { |
| goto done; |
| } |
| |
| if (nargs > 1) { |
| length = njs_string_prop(&string, &args[0]); |
| |
| if (starts) { |
| if (index < 0) { |
| index = 0; |
| } |
| |
| if (length - index < search_length) { |
| goto small; |
| } |
| |
| } else { |
| if (index < 0 || index > length) { |
| index = length; |
| } |
| |
| index -= search_length; |
| |
| if (index < 0) { |
| goto small; |
| } |
| } |
| |
| end = string.start + string.size; |
| |
| if (string.size == (size_t) length) { |
| /* Byte or ASCII string. */ |
| p = string.start + index; |
| |
| } else { |
| /* UTF-8 string. */ |
| p = njs_string_offset(string.start, end, index); |
| } |
| |
| if ((size_t) (end - p) >= search.size |
| && memcmp(p, search.start, search.size) == 0) |
| { |
| goto done; |
| } |
| } |
| |
| small: |
| |
| retval = &njs_value_false; |
| |
| done: |
| |
| vm->retval = *retval; |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * njs_string_offset() assumes that index is correct. |
| */ |
| |
| const u_char * |
| njs_string_offset(const u_char *start, const u_char *end, size_t index) |
| { |
| uint32_t *map; |
| njs_uint_t skip; |
| |
| if (index >= NJS_STRING_MAP_STRIDE) { |
| map = njs_string_map_start(end); |
| |
| if (map[0] == 0) { |
| njs_string_offset_map_init(start, end - start); |
| } |
| |
| start += map[index / NJS_STRING_MAP_STRIDE - 1]; |
| } |
| |
| for (skip = index % NJS_STRING_MAP_STRIDE; skip != 0; skip--) { |
| start = njs_utf8_next(start, end); |
| } |
| |
| return start; |
| } |
| |
| |
| /* |
| * njs_string_index() assumes that offset is correct. |
| */ |
| |
| uint32_t |
| njs_string_index(njs_string_prop_t *string, uint32_t offset) |
| { |
| uint32_t *map, last, index; |
| const u_char *p, *start, *end; |
| |
| if (string->size == string->length) { |
| return offset; |
| } |
| |
| last = 0; |
| index = 0; |
| |
| if (string->length >= NJS_STRING_MAP_STRIDE) { |
| |
| end = string->start + string->size; |
| map = njs_string_map_start(end); |
| |
| if (map[0] == 0) { |
| njs_string_offset_map_init(string->start, string->size); |
| } |
| |
| while (index + NJS_STRING_MAP_STRIDE < string->length |
| && *map <= offset) |
| { |
| last = *map++; |
| index += NJS_STRING_MAP_STRIDE; |
| } |
| } |
| |
| p = string->start + last; |
| start = string->start + offset; |
| end = string->start + string->size; |
| |
| while (p < start) { |
| index++; |
| p = njs_utf8_next(p, end); |
| } |
| |
| return index; |
| } |
| |
| |
| void |
| njs_string_offset_map_init(const u_char *start, size_t size) |
| { |
| size_t offset; |
| uint32_t *map; |
| njs_uint_t n; |
| const u_char *p, *end; |
| |
| end = start + size; |
| map = njs_string_map_start(end); |
| p = start; |
| n = 0; |
| offset = NJS_STRING_MAP_STRIDE; |
| |
| do { |
| if (offset == 0) { |
| map[n++] = p - start; |
| offset = NJS_STRING_MAP_STRIDE; |
| } |
| |
| /* The UTF-8 string should be valid since its length is known. */ |
| p = njs_utf8_next(p, end); |
| |
| offset--; |
| |
| } while (p < end); |
| } |
| |
| |
| /* |
| * The method supports only simple folding. For example, Turkish "İ" |
| * folding "\u0130" to "\u0069\u0307" is not supported. |
| */ |
| |
| 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) |
| { |
| size_t size, length; |
| u_char *p; |
| uint32_t code; |
| njs_int_t ret; |
| const u_char *s, *end; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| (void) njs_string_prop(&string, njs_argument(args, 0)); |
| |
| if (string.length == 0 || string.length == string.size) { |
| /* Byte or ASCII string. */ |
| |
| p = njs_string_alloc(vm, &vm->retval, string.size, string.length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| s = string.start; |
| size = string.size; |
| |
| while (size != 0) { |
| *p++ = njs_lower_case(*s++); |
| size--; |
| } |
| |
| } else { |
| /* UTF-8 string. */ |
| s = string.start; |
| end = s + string.size; |
| length = string.length; |
| |
| size = 0; |
| |
| while (length != 0) { |
| code = njs_utf8_lower_case(&s, end); |
| size += njs_utf8_size(code); |
| length--; |
| } |
| |
| p = njs_string_alloc(vm, &vm->retval, size, string.length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| s = string.start; |
| length = string.length; |
| |
| while (length != 0) { |
| code = njs_utf8_lower_case(&s, end); |
| p = njs_utf8_encode(p, code); |
| length--; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| /* |
| * The method supports only simple folding. For example, German "ß" |
| * folding "\u00DF" to "\u0053\u0053" is not supported. |
| */ |
| |
| 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) |
| { |
| size_t size, length; |
| u_char *p; |
| uint32_t code; |
| njs_int_t ret; |
| const u_char *s, *end; |
| njs_string_prop_t string; |
| |
| ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| (void) njs_string_prop(&string, njs_argument(args, 0)); |
| |
| if (string.length == 0 || string.length == string.size) { |
| /* Byte or ASCII string. */ |
| |
| p = njs_string_alloc(vm, &vm->retval, string.size, string.length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| s = string.start; |
| size = string.size; |
| |
| while (size != 0) { |
| *p++ = njs_upper_case(*s++); |
| size--; |
| } |
| |
| } else { |
| /* UTF-8 string. */ |
| s = string.start; |
| end = s + string.size; |
| length = string.length; |
| |
| size = 0; |
| |
| while (length != 0) { |
| code = njs_utf8_upper_case(&s, end); |
| size += njs_utf8_size(code); |
| length--; |
| } |
| |
| p = njs_string_alloc(vm, &vm->retval, size, string.length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| s = string.start; |
| length = string.length; |
| |
| while (length != 0) { |
| code = njs_utf8_upper_case(&s, end); |
| p = njs_utf8_encode(p, code); |
| length--; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_trim(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t mode) |
| { |
| uint32_t u, trim, length; |
| njs_int_t ret; |
| njs_value_t *value; |
| const u_char *p, *prev, *start, *end; |
| njs_string_prop_t string; |
| njs_unicode_decode_t ctx; |
| |
| value = njs_argument(args, 0); |
| ret = njs_string_object_validate(vm, value); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| trim = 0; |
| |
| njs_string_prop(&string, value); |
| |
| start = string.start; |
| end = string.start + string.size; |
| |
| if (string.length == 0 || string.length == string.size) { |
| /* Byte or ASCII string. */ |
| |
| if (mode & NJS_TRIM_START) { |
| for ( ;; ) { |
| if (start == end) { |
| goto empty; |
| } |
| |
| if (njs_is_whitespace(*start)) { |
| start++; |
| trim++; |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| if (mode & NJS_TRIM_END) { |
| for ( ;; ) { |
| if (start == end) { |
| goto empty; |
| } |
| |
| end--; |
| |
| if (njs_is_whitespace(*end)) { |
| trim++; |
| continue; |
| } |
| |
| end++; |
| break; |
| } |
| } |
| |
| } else { |
| /* UTF-8 string. */ |
| |
| if (mode & NJS_TRIM_START) { |
| njs_utf8_decode_init(&ctx); |
| |
| for ( ;; ) { |
| if (start == end) { |
| goto empty; |
| } |
| |
| p = start; |
| u = njs_utf8_decode(&ctx, &start, end); |
| |
| if (njs_utf8_is_whitespace(u)) { |
| trim++; |
| continue; |
| } |
| |
| start = p; |
| break; |
| } |
| } |
| |
| if (mode & NJS_TRIM_END) { |
| prev = end; |
| |
| njs_utf8_decode_init(&ctx); |
| |
| for ( ;; ) { |
| if (start == prev) { |
| goto empty; |
| } |
| |
| prev = njs_utf8_prev(prev); |
| p = prev; |
| u = njs_utf8_decode(&ctx, &p, end); |
| |
| if (njs_utf8_is_whitespace(u)) { |
| trim++; |
| continue; |
| } |
| |
| end = p; |
| break; |
| } |
| } |
| } |
| |
| if (trim == 0) { |
| /* GC: retain. */ |
| vm->retval = *value; |
| |
| return NJS_OK; |
| } |
| |
| length = (string.length != 0) ? string.length - trim : 0; |
| |
| return njs_string_new(vm, &vm->retval, start, end - start, length); |
| |
| empty: |
| |
| vm->retval = njs_string_empty; |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_repeat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
| njs_index_t unused) |
| { |
| u_char *p; |
| double count; |
| int64_t n, max; |
| uint64_t size, length; |
| njs_int_t ret; |
| njs_value_t *this; |
| njs_string_prop_t string; |
| |
| this = njs_argument(args, 0); |
| |
| if (njs_slow_path(njs_is_null_or_undefined(this))) { |
| njs_type_error(vm, "cannot convert \"%s\"to object", |
| njs_type_string(this->type)); |
| return NJS_ERROR; |
| } |
| |
| ret = njs_value_to_string(vm, this, this); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| ret = njs_value_to_number(vm, njs_arg(args, nargs, 1), &count); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| |
| if (njs_slow_path(!isnan(count) && (count < 0 || isinf(count)))) { |
| njs_range_error(vm, NULL); |
| return NJS_ERROR; |
| } |
| |
| n = njs_number_to_integer(count); |
| |
| (void) njs_string_prop(&string, this); |
| |
| if (njs_slow_path(n == 0 || string.size == 0)) { |
| vm->retval = njs_string_empty; |
| return NJS_OK; |
| } |
| |
| max = NJS_STRING_MAX_LENGTH / string.size; |
| |
| if (njs_slow_path(n >= max)) { |
| njs_range_error(vm, NULL); |
| return NJS_ERROR; |
| } |
| |
| size = string.size * n; |
| length = string.length * n; |
| |
| p = njs_string_alloc(vm, &vm->retval, size, length); |
| if (njs_slow_path(p == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| while (n != 0) { |
| p = memcpy(p, string.start, string.size); |
| p += string.size; |
| n--; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_string_prototype_pad( |