blob: 68e198d62c5ad2169d8a398f611608c4982b5cb9 [file] [log] [blame]
/*
* 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(