/* * Copyright (C) Alexander Borisov * Copyright (C) NGINX, Inc. */ #include typedef enum { NJS_ENCODING_UTF8, } njs_encoding_t; typedef struct { njs_encoding_t encoding; njs_bool_t fatal; njs_bool_t ignore_bom; njs_unicode_decode_t ctx; } njs_encoding_decode_t; typedef struct { njs_str_t name; njs_encoding_t encoding; } njs_encoding_label_t; static njs_encoding_label_t njs_encoding_labels[] = { { njs_str("utf-8"), NJS_ENCODING_UTF8 }, { njs_str("utf8") , NJS_ENCODING_UTF8 }, { njs_null_str, 0 } }; static njs_int_t njs_text_encoder_encode_utf8(njs_vm_t *vm, njs_string_prop_t *prop, njs_value_t *retval); static njs_int_t njs_text_decoder_arg_encoding(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_encoding_decode_t *data); static njs_int_t njs_text_decoder_arg_options(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_encoding_decode_t *data); static njs_int_t njs_text_encoder_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_object_value_t *encoder; if (!vm->top_frame->ctor) { njs_type_error(vm, "Constructor of TextEncoder requires 'new'"); return NJS_ERROR; } encoder = njs_object_value_alloc(vm, NJS_OBJ_TYPE_TEXT_ENCODER, 0, NULL); if (njs_slow_path(encoder == NULL)) { return NJS_ERROR; } njs_set_data(&encoder->value, NULL, NJS_DATA_TAG_TEXT_ENCODER); njs_set_object_value(retval, encoder); return NJS_OK; } static njs_int_t njs_text_encoder_encode(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { u_char *dst; size_t size; njs_int_t ret; njs_value_t *this, *input, value; const u_char *start, *end; njs_string_prop_t prop; njs_typed_array_t *array; njs_unicode_decode_t ctx; this = njs_argument(args, 0); if (njs_slow_path(!njs_is_object_data(this, NJS_DATA_TAG_TEXT_ENCODER))) { njs_type_error(vm, "\"this\" is not a TextEncoder"); return NJS_ERROR; } start = NULL; end = NULL; if (nargs > 1) { input = njs_argument(args, 1); if (njs_slow_path(!njs_is_string(input))) { ret = njs_value_to_string(vm, input, input); if (njs_slow_path(ret != NJS_OK)) { return ret; } } (void) njs_string_prop(vm, &prop, input); if (prop.length != 0) { return njs_text_encoder_encode_utf8(vm, &prop, retval); } start = prop.start; end = start + prop.size; } njs_utf8_decode_init(&ctx); (void) njs_utf8_stream_length(&ctx, start, end - start, 1, 0, &size); njs_set_number(&value, size); array = njs_typed_array_alloc(vm, &value, 1, 0, NJS_OBJ_TYPE_UINT8_ARRAY); if (njs_slow_path(array == NULL)) { return NJS_ERROR; } dst = njs_typed_array_buffer(array)->u.u8; njs_utf8_decode_init(&ctx); (void) njs_utf8_stream_encode(&ctx, start, end, dst, 1, 0); njs_set_typed_array(retval, array); return NJS_OK; } static njs_int_t njs_text_encoder_encode_utf8(njs_vm_t *vm, njs_string_prop_t *prop, njs_value_t *retval) { njs_value_t value; njs_typed_array_t *array; njs_set_number(&value, prop->size); array = njs_typed_array_alloc(vm, &value, 1, 0, NJS_OBJ_TYPE_UINT8_ARRAY); if (njs_slow_path(array == NULL)) { return NJS_ERROR; } memcpy(njs_typed_array_buffer(array)->u.u8, prop->start, prop->size); njs_set_typed_array(retval, array); return NJS_OK; } static njs_int_t njs_text_encoder_encode_into(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { u_char *to, *to_end; size_t size; uint32_t cp; njs_int_t ret; njs_str_t str; njs_value_t *this, *input, *dest, value, read, written; njs_object_t *object; const u_char *start, *end; njs_typed_array_t *array; njs_unicode_decode_t ctx; this = njs_argument(args, 0); input = njs_arg(args, nargs, 1); dest = njs_arg(args, nargs, 2); if (njs_slow_path(!njs_is_object_data(this, NJS_DATA_TAG_TEXT_ENCODER))) { njs_type_error(vm, "\"this\" is not a TextEncoder"); return NJS_ERROR; } if (njs_slow_path(!njs_is_string(input))) { ret = njs_value_to_string(vm, &value, input); if (njs_slow_path(ret != NJS_OK)) { return ret; } input = &value; } if (njs_slow_path(!njs_is_typed_array_uint8(dest))) { njs_type_error(vm, "The \"destination\" argument must be an instance " "of Uint8Array"); return NJS_ERROR; } njs_string_get(vm, input, &str); start = str.start; end = start + str.length; array = njs_typed_array(dest); to = njs_typed_array_start(array); to_end = to + array->byte_length; njs_set_number(&read, 0); njs_set_number(&written, 0); njs_utf8_decode_init(&ctx); while (start < end) { cp = njs_utf8_decode(&ctx, &start, end); if (cp > NJS_UNICODE_MAX_CODEPOINT) { cp = NJS_UNICODE_REPLACEMENT; } size = njs_utf8_size(cp); if (to + size > to_end) { break; } njs_number(&read) += (cp > 0xFFFF) ? 2 : 1; njs_number(&written) += size; to = njs_utf8_encode(to, cp); } object = njs_object_alloc(vm); if (njs_slow_path(object == NULL)) { return NJS_ERROR; } njs_set_object(retval, object); ret = njs_object_prop_define(vm, retval, NJS_ATOM_STRING_read, &read, NJS_OBJECT_PROP_VALUE_CW); if (njs_slow_path(ret != NJS_OK)) { return ret; } ret = njs_object_prop_define(vm, retval, NJS_ATOM_STRING_written, &written, NJS_OBJECT_PROP_VALUE_CW); if (njs_slow_path(ret != NJS_OK)) { return ret; } return NJS_OK; } static const njs_object_prop_init_t njs_text_encoder_properties[] = { NJS_DECLARE_PROP_HANDLER(STRING_constructor, njs_object_prototype_create_constructor, 0, NJS_OBJECT_PROP_VALUE_CW), NJS_DECLARE_PROP_VALUE(STRING_encoding, njs_ascii_strval("utf-8"), 0), NJS_DECLARE_PROP_NATIVE(STRING_encode, njs_text_encoder_encode, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_encodeInto, njs_text_encoder_encode_into, 2, 0), }; const njs_object_init_t njs_text_encoder_init = { njs_text_encoder_properties, njs_nitems(njs_text_encoder_properties), }; static const njs_object_prop_init_t njs_text_encoder_constructor_properties[] = { NJS_DECLARE_PROP_LENGTH(0), NJS_DECLARE_PROP_NAME("TextEncoder"), NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create, 0, 0), }; const njs_object_init_t njs_text_encoder_constructor_init = { njs_text_encoder_constructor_properties, njs_nitems(njs_text_encoder_constructor_properties), }; const njs_object_type_init_t njs_text_encoder_type_init = { .constructor = njs_native_ctor(njs_text_encoder_constructor, 0, 0), .prototype_props = &njs_text_encoder_init, .constructor_props = &njs_text_encoder_constructor_init, .prototype_value = { .object = { .type = NJS_OBJECT } }, }; static njs_int_t njs_text_decoder_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_int_t ret; njs_object_value_t *decoder; njs_encoding_decode_t *data; if (!vm->top_frame->ctor) { njs_type_error(vm, "Constructor of TextDecoder requires 'new'"); return NJS_ERROR; } decoder = njs_object_value_alloc(vm, NJS_OBJ_TYPE_TEXT_DECODER, sizeof(njs_encoding_decode_t), NULL); if (njs_slow_path(decoder == NULL)) { return NJS_ERROR; } data = (njs_encoding_decode_t *) ((uint8_t *) decoder + sizeof(njs_object_value_t)); ret = njs_text_decoder_arg_encoding(vm, args, nargs, data); if (njs_slow_path(ret != NJS_OK)) { return ret; } ret = njs_text_decoder_arg_options(vm, args, nargs, data); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_utf8_decode_init(&data->ctx); njs_set_data(&decoder->value, data, NJS_DATA_TAG_TEXT_DECODER); njs_set_object_value(retval, decoder); return NJS_OK; } static njs_int_t njs_text_decoder_arg_encoding(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_encoding_decode_t *data) { njs_str_t str; njs_int_t ret; njs_value_t *value; njs_encoding_label_t *label; if (nargs < 2) { data->encoding = NJS_ENCODING_UTF8; return NJS_OK; } 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; } } njs_string_get(vm, value, &str); for (label = &njs_encoding_labels[0]; label->name.length != 0; label++) { if (njs_strstr_eq(&str, &label->name)) { data->encoding = label->encoding; return NJS_OK; } } njs_range_error(vm, "The \"%V\" encoding is not supported", &str); return NJS_ERROR; } static njs_int_t njs_text_decoder_arg_options(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_encoding_decode_t *data) { njs_int_t ret; njs_value_t retval, *value; if (nargs < 3) { data->fatal = 0; data->ignore_bom = 0; return NJS_OK; } value = njs_argument(args, 2); if (njs_slow_path(!njs_is_object(value))) { njs_type_error(vm, "The \"options\" argument must be of type object"); return NJS_ERROR; } ret = njs_value_property(vm, value, NJS_ATOM_STRING_fatal, &retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } data->fatal = njs_bool(&retval); ret = njs_value_property(vm, value, NJS_ATOM_STRING_ignoreBOM, &retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } data->ignore_bom = njs_bool(&retval); return NJS_OK; } static njs_int_t njs_text_decoder_encoding(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { njs_encoding_decode_t *data; if (njs_slow_path(!njs_is_object_data(value, NJS_DATA_TAG_TEXT_DECODER))) { njs_set_undefined(retval); return NJS_DECLINED; } data = njs_object_data(value); switch (data->encoding) { case NJS_ENCODING_UTF8: njs_atom_to_value(vm, retval, NJS_ATOM_STRING_utf_8); break; default: njs_type_error(vm, "unknown encoding"); return NJS_ERROR; } return NJS_OK; } static njs_int_t njs_text_decoder_fatal(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { njs_encoding_decode_t *data; if (njs_slow_path(!njs_is_object_data(value, NJS_DATA_TAG_TEXT_DECODER))) { njs_set_undefined(retval); return NJS_DECLINED; } data = njs_object_data(value); njs_set_boolean(retval, data->fatal); return NJS_OK; } static njs_int_t njs_text_decoder_ignore_bom(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { njs_encoding_decode_t *data; if (njs_slow_path(!njs_is_object_data(value, NJS_DATA_TAG_TEXT_DECODER))) { njs_set_undefined(retval); return NJS_DECLINED; } data = njs_object_data(value); njs_set_boolean(retval, data->ignore_bom); return NJS_OK; } static njs_int_t njs_text_decoder_decode(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { u_char *dst; size_t size; ssize_t length; njs_int_t ret; njs_bool_t stream; njs_value_t *this, *value, *options; const u_char *start, *end; njs_unicode_decode_t ctx; njs_encoding_decode_t *data; const njs_typed_array_t *array; const njs_array_buffer_t *buffer; start = NULL; end = NULL; stream = 0; this = njs_argument(args, 0); if (njs_slow_path(!njs_is_object_data(this, NJS_DATA_TAG_TEXT_DECODER))) { njs_type_error(vm, "\"this\" is not a TextDecoder"); return NJS_ERROR; } if (njs_fast_path(nargs > 1)) { value = njs_argument(args, 1); if (njs_is_typed_array(value)) { array = njs_typed_array(value); start = njs_typed_array_start(array); end = start + array->byte_length; } else if (njs_is_array_buffer(value)) { buffer = njs_array_buffer(value); start = buffer->u.u8; end = start + buffer->size; } else { njs_type_error(vm, "The \"input\" argument must be an instance " "of TypedArray"); return NJS_ERROR; } } if (nargs > 2) { options = njs_argument(args, 2); if (njs_slow_path(!njs_is_object(options))) { njs_type_error(vm, "The \"options\" argument must be " "of type object"); return NJS_ERROR; } ret = njs_value_property(vm, options, NJS_ATOM_STRING_stream, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } stream = njs_bool(retval); } data = njs_object_data(this); ctx = data->ctx; /* Looking for BOM. */ if (start != NULL && !data->ignore_bom) { start += njs_utf8_bom(start, end); } length = njs_utf8_stream_length(&ctx, start, end - start, !stream, data->fatal, &size); if (length == -1) { njs_type_error(vm, "The encoded data was not valid"); return NJS_ERROR; } dst = njs_string_alloc(vm, retval, size, length); if (njs_slow_path(dst == NULL)) { return NJS_ERROR; } (void) njs_utf8_stream_encode(&data->ctx, start, end, dst, !stream, 0); if (!stream) { njs_utf8_decode_init(&data->ctx); } return NJS_OK; } static const njs_object_prop_init_t njs_text_decoder_properties[] = { NJS_DECLARE_PROP_HANDLER(STRING_constructor, njs_object_prototype_create_constructor, 0, NJS_OBJECT_PROP_VALUE_CW), NJS_DECLARE_PROP_HANDLER(STRING_encoding, njs_text_decoder_encoding, 0, 0), NJS_DECLARE_PROP_HANDLER(STRING_fatal, njs_text_decoder_fatal, 0, 0), NJS_DECLARE_PROP_HANDLER(STRING_ignoreBOM, njs_text_decoder_ignore_bom, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_decode, njs_text_decoder_decode, 0, 0), }; const njs_object_init_t njs_text_decoder_init = { njs_text_decoder_properties, njs_nitems(njs_text_decoder_properties), }; static const njs_object_prop_init_t njs_text_decoder_constructor_properties[] = { NJS_DECLARE_PROP_LENGTH(0), NJS_DECLARE_PROP_NAME("TextDecoder"), NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create, 0, 0), }; const njs_object_init_t njs_text_decoder_constructor_init = { njs_text_decoder_constructor_properties, njs_nitems(njs_text_decoder_constructor_properties), }; const njs_object_type_init_t njs_text_decoder_type_init = { .constructor = njs_native_ctor(njs_text_decoder_constructor, 0, 0), .prototype_props = &njs_text_decoder_init, .constructor_props = &njs_text_decoder_constructor_init, .prototype_value = { .object = { .type = NJS_OBJECT } }, };