Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2010 litl, LLC
4 : : // SPDX-FileCopyrightText: 2021 Evan Welsh
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <limits.h> // for SSIZE_MAX
9 : : #include <stddef.h> // for size_t
10 : : #include <stdint.h>
11 : : #include <string.h> // for strcmp, memchr, strlen
12 : :
13 : : #include <algorithm>
14 : : #include <iosfwd> // for nullptr_t
15 : : #include <iterator> // for distance
16 : : #include <memory> // for unique_ptr
17 : : #include <string> // for u16string
18 : : #include <tuple> // for tuple
19 : :
20 : : #include <gio/gio.h>
21 : : #include <glib-object.h>
22 : : #include <glib.h>
23 : :
24 : : #include <js/ArrayBuffer.h>
25 : : #include <js/CallArgs.h>
26 : : #include <js/CharacterEncoding.h>
27 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory, JSEXN_TYPEERR
28 : : #include <js/Exception.h> // for JS_ClearPendingException, JS_...
29 : : #include <js/GCAPI.h> // for AutoCheckCannotGC
30 : : #include <js/PropertyAndElement.h>
31 : : #include <js/PropertySpec.h>
32 : : #include <js/RootingAPI.h>
33 : : #include <js/String.h>
34 : : #include <js/TypeDecls.h>
35 : : #include <js/Utility.h> // for UniqueChars
36 : : #include <js/Value.h>
37 : : #include <js/experimental/TypedData.h>
38 : : #include <jsapi.h> // for JS_NewPlainObject, JS_InstanceOf
39 : : #include <jsfriendapi.h> // for ProtoKeyToClass
40 : : #include <jspubtd.h> // for JSProto_InternalError
41 : : #include <mozilla/Maybe.h>
42 : : #include <mozilla/Span.h>
43 : : #include <mozilla/Unused.h>
44 : :
45 : : #include "gjs/jsapi-util-args.h"
46 : : #include "gjs/jsapi-util.h"
47 : : #include "gjs/macros.h"
48 : : #include "gjs/text-encoding.h"
49 : :
50 : : // Callback to use with JS::NewExternalArrayBuffer()
51 : :
52 : 1 : static void gfree_arraybuffer_contents(void* contents, void*) {
53 : 1 : g_free(contents);
54 : 1 : }
55 : :
56 : 1 : static std::nullptr_t gjs_throw_type_error_from_gerror(
57 : : JSContext* cx, GjsAutoError const& error) {
58 : 1 : g_return_val_if_fail(error, nullptr);
59 : 1 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "%s", error->message);
60 : 1 : return nullptr;
61 : : }
62 : :
63 : : // UTF16_CODESET is used to encode and decode UTF-16 buffers with
64 : : // iconv. To ensure the output of iconv is laid out in memory correctly
65 : : // we have to use UTF-16LE on little endian systems and UTF-16BE on big
66 : : // endian systems.
67 : : //
68 : : // This ensures we can simply reinterpret_cast<char16_t> iconv's output.
69 : : #if G_BYTE_ORDER == G_LITTLE_ENDIAN
70 : : static const char* UTF16_CODESET = "UTF-16LE";
71 : : #else
72 : : static const char* UTF16_CODESET = "UTF-16BE";
73 : : #endif
74 : :
75 : : GJS_JSAPI_RETURN_CONVENTION
76 : 174 : static JSString* gjs_lossy_decode_from_uint8array_slow(
77 : : JSContext* cx, const uint8_t* bytes, size_t bytes_len,
78 : : const char* from_codeset) {
79 : 174 : GjsAutoError error;
80 : : GjsAutoUnref<GCharsetConverter> converter(
81 : 174 : g_charset_converter_new(UTF16_CODESET, from_codeset, &error));
82 : :
83 : : // This should only throw if an encoding is not available.
84 [ - + ]: 174 : if (error)
85 : 0 : return gjs_throw_type_error_from_gerror(cx, error);
86 : :
87 : : // This function converts *to* UTF-16, using a std::u16string
88 : : // as its buffer.
89 : : //
90 : : // UTF-16 represents each character with 2 bytes or
91 : : // 4 bytes, the best case scenario when converting to
92 : : // UTF-16 is that every input byte encodes to two bytes,
93 : : // this is typical for ASCII and non-supplementary characters.
94 : : // Because we are converting from an unknown encoding
95 : : // technically a single byte could be supplementary in
96 : : // Unicode (4 bytes) or even represent multiple Unicode characters.
97 : : //
98 : : // std::u16string does not care about these implementation
99 : : // details, its only concern is that is consists of byte pairs.
100 : : // Given this, a single UTF-16 character could be represented
101 : : // by one or two std::u16string characters.
102 : :
103 : : // Allocate bytes_len * 2 + 12 as our initial buffer.
104 : : // bytes_len * 2 is the "best case" for LATIN1 strings
105 : : // and strings which are in the basic multilingual plane.
106 : : // Add 12 as a slight cushion and set the minimum allocation
107 : : // at 256 to prefer running a single iteration for
108 : : // small strings with supplemental plane characters.
109 : : //
110 : : // When converting Chinese characters, for example,
111 : : // some dialectal characters are in the supplemental plane
112 : : // Adding a padding of 12 prevents a few dialectal characters
113 : : // from requiring a reallocation.
114 : 174 : size_t buffer_size = std::max(bytes_len * 2 + 12, static_cast<size_t>(256u));
115 : :
116 : : // Cast data to correct input types
117 : 174 : const char* input = reinterpret_cast<const char*>(bytes);
118 : 174 : size_t input_len = bytes_len;
119 : :
120 : : // The base string that we'll append to.
121 : 348 : std::u16string output_str = u"";
122 : :
123 : : do {
124 : 2375 : GjsAutoError local_error;
125 : :
126 : : // Create a buffer to convert into.
127 : 2375 : std::unique_ptr<char[]> buffer = std::make_unique<char[]>(buffer_size);
128 : 2375 : size_t bytes_written = 0, bytes_read = 0;
129 : :
130 : 4750 : g_converter_convert(G_CONVERTER(converter.get()), input, input_len,
131 : 2375 : buffer.get(), buffer_size, G_CONVERTER_INPUT_AT_END,
132 : : &bytes_read, &bytes_written, &local_error);
133 : :
134 : : // If bytes were read, adjust input.
135 [ + + ]: 2375 : if (bytes_read > 0) {
136 : 635 : input += bytes_read;
137 : 635 : input_len -= bytes_read;
138 : : }
139 : :
140 : : // If bytes were written append the buffer contents to our string
141 : : // accumulator
142 [ + + ]: 2375 : if (bytes_written > 0) {
143 : 635 : char16_t* utf16_buffer = reinterpret_cast<char16_t*>(buffer.get());
144 : : // std::u16string uses exactly 2 bytes for every character.
145 : 635 : output_str.append(utf16_buffer, bytes_written / 2);
146 [ + - ]: 1740 : } else if (local_error) {
147 : : // A PARTIAL_INPUT error can only occur if the user does not provide
148 : : // the full sequence for a multi-byte character, we skip over the
149 : : // next character and insert a unicode fallback.
150 : :
151 : : // An INVALID_DATA error occurs when there is no way to decode a
152 : : // given byte into UTF-16 or the given byte does not exist in the
153 : : // source encoding.
154 : 1740 : if (g_error_matches(local_error, G_IO_ERROR,
155 [ + + + - : 1742 : G_IO_ERROR_INVALID_DATA) ||
+ - ]
156 : 2 : g_error_matches(local_error, G_IO_ERROR,
157 : : G_IO_ERROR_PARTIAL_INPUT)) {
158 : : // If we're already at the end of the string, don't insert a
159 : : // fallback.
160 [ + - ]: 1740 : if (input_len > 0) {
161 : : // Skip the next byte and reduce length by one.
162 : 1740 : input += 1;
163 : 1740 : input_len -= 1;
164 : :
165 : : // Append the unicode fallback character to the output
166 : 1740 : output_str.append(u"\ufffd", 1);
167 : : }
168 [ # # ]: 0 : } else if (g_error_matches(local_error, G_IO_ERROR,
169 : : G_IO_ERROR_NO_SPACE)) {
170 : : // If the buffer was full increase the buffer
171 : : // size and re-try the conversion.
172 : : //
173 : : // This logic allocates bytes_len * 3 first,
174 : : // then bytes_len * 4 (the worst case scenario
175 : : // is nearly impossible) and then continues appending
176 : : // arbitrary padding because we'll trust Gio and give
177 : : // it additional space.
178 [ # # ]: 0 : if (buffer_size > bytes_len * 4) {
179 : 0 : buffer_size += 256;
180 : : } else {
181 : 0 : buffer_size += bytes_len;
182 : : }
183 : : } else {
184 : : // Stop decoding if an unknown error occurs.
185 : 0 : return gjs_throw_type_error_from_gerror(cx, local_error);
186 : : }
187 : : }
188 [ + - + - : 4750 : } while (input_len > 0);
+ + ]
189 : :
190 : : // Copy the accumulator's data into a JSString of Unicode (UTF-16) chars.
191 : 174 : return JS_NewUCStringCopyN(cx, output_str.c_str(), output_str.size());
192 : 174 : }
193 : :
194 : : GJS_JSAPI_RETURN_CONVENTION
195 : 176 : static JSString* gjs_decode_from_uint8array_slow(JSContext* cx,
196 : : const uint8_t* input,
197 : : size_t input_len,
198 : : const char* encoding,
199 : : bool fatal) {
200 : : // If the decoding is not fatal we use the lossy decoder.
201 [ + + ]: 176 : if (!fatal)
202 : 174 : return gjs_lossy_decode_from_uint8array_slow(cx, input, input_len,
203 : 174 : encoding);
204 : :
205 : : // g_convert only handles up to SSIZE_MAX bytes, but we may have SIZE_MAX
206 [ - + ]: 2 : if (G_UNLIKELY(input_len > SSIZE_MAX)) {
207 : 0 : gjs_throw(cx, "Array too big to decode: %zu bytes", input_len);
208 : 0 : return nullptr;
209 : : }
210 : :
211 : : size_t bytes_written, bytes_read;
212 : 2 : GjsAutoError error;
213 : :
214 : : GjsAutoChar bytes =
215 : : g_convert(reinterpret_cast<const char*>(input), input_len,
216 : 2 : UTF16_CODESET, encoding, &bytes_read, &bytes_written, &error);
217 : :
218 [ + + ]: 2 : if (error)
219 : 1 : return gjs_throw_type_error_from_gerror(cx, error);
220 : :
221 : : // bytes_written should be bytes in a UTF-16 string so should be a
222 : : // multiple of 2
223 : 1 : g_assert((bytes_written % 2) == 0);
224 : :
225 : : // Cast g_convert's output to char16_t and copy the data.
226 : 1 : const char16_t* unicode_bytes = reinterpret_cast<char16_t*>(bytes.get());
227 : 1 : return JS_NewUCStringCopyN(cx, unicode_bytes, bytes_written / 2);
228 : 2 : }
229 : :
230 : 380 : [[nodiscard]] static bool is_utf8_label(const char* encoding) {
231 : : // We could be smarter about utf8 synonyms here.
232 : : // For now, we handle any casing and trailing/leading
233 : : // whitespace.
234 : : //
235 : : // is_utf8_label is only an optimization, so if a label
236 : : // doesn't match we just use the slower path.
237 [ + + - + : 558 : if (g_ascii_strcasecmp(encoding, "utf-8") == 0 ||
+ + ]
238 : 178 : g_ascii_strcasecmp(encoding, "utf8") == 0)
239 : 202 : return true;
240 : :
241 : 178 : GjsAutoChar stripped(g_strdup(encoding));
242 : 178 : g_strstrip(stripped); // modifies in place
243 [ + - - + ]: 356 : return g_ascii_strcasecmp(stripped, "utf-8") == 0 ||
244 : 178 : g_ascii_strcasecmp(stripped, "utf8") == 0;
245 : 178 : }
246 : :
247 : : // Finds the length of a given data array, stopping at the first 0 byte.
248 : : template <class T>
249 : 11 : [[nodiscard]] static size_t zero_terminated_length(const T* data, size_t len) {
250 [ + - + + ]: 11 : if (!data || len == 0)
251 : 1 : return 0;
252 : :
253 : 10 : const T* start = data;
254 : 10 : auto* found = static_cast<const T*>(std::memchr(start, '\0', len));
255 : :
256 : : // If a null byte was not found, return the passed length.
257 [ + + ]: 10 : if (!found)
258 : 5 : return len;
259 : :
260 : 5 : return std::distance(start, found);
261 : : }
262 : :
263 : : // decode() function implementation
264 : 350 : JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array,
265 : : const char* encoding,
266 : : GjsStringTermination string_termination,
267 : : bool fatal) {
268 : 350 : g_assert(encoding && "encoding must be non-null");
269 : :
270 [ + + ]: 350 : if (!JS_IsUint8Array(byte_array)) {
271 : 1 : gjs_throw(cx, "Argument to decode() must be a Uint8Array");
272 : 1 : return nullptr;
273 : : }
274 : :
275 : : uint8_t* data;
276 : : size_t len;
277 : : bool is_shared_memory;
278 : 349 : js::GetUint8ArrayLengthAndData(byte_array, &len, &is_shared_memory, &data);
279 : :
280 : : // If the desired behavior is zero-terminated, calculate the
281 : : // zero-terminated length of the given data.
282 [ + + + + ]: 349 : if (len && string_termination == GjsStringTermination::ZERO_TERMINATED)
283 : 9 : len = zero_terminated_length(data, len);
284 : :
285 : : // If the calculated length is 0 we can just return an empty string.
286 [ + + ]: 349 : if (len == 0)
287 : 3 : return JS_GetEmptyString(cx);
288 : :
289 : : // Optimization, only use glib's iconv-based converters if we're dealing
290 : : // with a non-UTF8 encoding. SpiderMonkey has highly optimized UTF-8 decoder
291 : : // and encoders.
292 : 346 : bool encoding_is_utf8 = is_utf8_label(encoding);
293 [ + + ]: 346 : if (!encoding_is_utf8)
294 : 176 : return gjs_decode_from_uint8array_slow(cx, data, len, encoding, fatal);
295 : :
296 : 170 : JS::RootedString decoded(cx);
297 [ + + ]: 170 : if (!fatal) {
298 : 162 : decoded.set(gjs_lossy_string_from_utf8_n(
299 : : cx, reinterpret_cast<char*>(data), len));
300 : : } else {
301 : 8 : JS::UTF8Chars chars(reinterpret_cast<char*>(data), len);
302 : 8 : JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
303 : :
304 : : // If an exception occurred, we need to check if the
305 : : // exception was an InternalError. Unfortunately,
306 : : // SpiderMonkey's decoder can throw InternalError for some
307 : : // invalid UTF-8 sources, we have to convert this into a
308 : : // TypeError to match the Encoding specification.
309 [ + + ]: 8 : if (str) {
310 : 7 : decoded.set(str);
311 : : } else {
312 : 1 : JS::RootedValue exc(cx);
313 [ + - - + : 1 : if (!JS_GetPendingException(cx, &exc) || !exc.isObject())
- + ]
314 : 0 : return nullptr;
315 : :
316 : 1 : JS::RootedObject exc_obj(cx, &exc.toObject());
317 : : const JSClass* internal_error =
318 : 1 : js::ProtoKeyToClass(JSProto_InternalError);
319 [ - + ]: 1 : if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) {
320 : : // Clear the existing exception.
321 : 0 : JS_ClearPendingException(cx);
322 : 0 : gjs_throw_custom(
323 : : cx, JSEXN_TYPEERR, nullptr,
324 : : "The provided encoded data was not valid UTF-8");
325 : : }
326 : :
327 : 1 : return nullptr;
328 : 1 : }
329 [ + + ]: 8 : }
330 : :
331 : : uint8_t* current_data;
332 : : size_t current_len;
333 : : bool ignore_val;
334 : :
335 : : // If a garbage collection occurs between when we call
336 : : // js::GetUint8ArrayLengthAndData and return from
337 : : // gjs_decode_from_uint8array, a use-after-free corruption can occur if the
338 : : // garbage collector shifts the location of the Uint8Array's private data.
339 : : // To mitigate this we call js::GetUint8ArrayLengthAndData again and then
340 : : // compare if the length and pointer are still the same. If the pointers
341 : : // differ, we use the slow path to ensure no data corruption occurred. The
342 : : // shared-ness of an array cannot change between calls, so we ignore it.
343 : 169 : js::GetUint8ArrayLengthAndData(byte_array, ¤t_len, &ignore_val,
344 : : ¤t_data);
345 : :
346 : : // Ensure the private data hasn't changed
347 [ + - ]: 169 : if (current_data == data)
348 : 169 : return decoded;
349 : :
350 : 0 : g_assert(current_len == len &&
351 : : "Garbage collection should not affect data length.");
352 : :
353 : : // This was the UTF-8 optimized path, so we explicitly pass the encoding
354 : 0 : return gjs_decode_from_uint8array_slow(cx, current_data, current_len,
355 : 0 : "utf-8", fatal);
356 : 170 : }
357 : :
358 : : GJS_JSAPI_RETURN_CONVENTION
359 : 339 : static bool gjs_decode(JSContext* cx, unsigned argc, JS::Value* vp) {
360 : 339 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
361 : :
362 : 339 : JS::RootedObject byte_array(cx);
363 : 339 : JS::UniqueChars encoding;
364 : 339 : bool fatal = false;
365 [ - + ]: 339 : if (!gjs_parse_call_args(cx, "decode", args, "os|b", "byteArray",
366 : : &byte_array, "encoding", &encoding, "fatal",
367 : : &fatal))
368 : 0 : return false;
369 : :
370 : : JS::RootedString decoded(
371 : 339 : cx, gjs_decode_from_uint8array(cx, byte_array, encoding.get(),
372 : : GjsStringTermination::EXPLICIT_LENGTH,
373 : 339 : fatal));
374 [ + + ]: 339 : if (!decoded)
375 : 2 : return false;
376 : :
377 : 337 : args.rval().setString(decoded);
378 : 337 : return true;
379 : 339 : }
380 : :
381 : : // encode() function implementation
382 : 34 : JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
383 : : const char* encoding,
384 : : GjsStringTermination string_termination) {
385 : 34 : JS::RootedObject array_buffer(cx);
386 : :
387 : 34 : bool encoding_is_utf8 = is_utf8_label(encoding);
388 [ + + ]: 34 : if (encoding_is_utf8) {
389 : 32 : JS::UniqueChars utf8;
390 : : size_t utf8_len;
391 : :
392 [ - + ]: 32 : if (!gjs_string_to_utf8_n(cx, str, &utf8, &utf8_len))
393 : 0 : return nullptr;
394 : :
395 [ + + ]: 32 : if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
396 : : // strlen is safe because gjs_string_to_utf8_n returns
397 : : // a null-terminated string.
398 : 19 : utf8_len = strlen(utf8.get());
399 : : }
400 : :
401 : 32 : array_buffer = JS::NewArrayBufferWithContents(cx, utf8_len, utf8.get());
402 : :
403 : : // array_buffer only assumes ownership if the call succeeded,
404 : : // if array_buffer assumes ownership we must release our ownership
405 : : // without freeing the data.
406 [ + - ]: 32 : if (array_buffer)
407 : 32 : mozilla::Unused << utf8.release();
408 [ + - ]: 32 : } else {
409 : 2 : GjsAutoError error;
410 : 2 : GjsAutoChar encoded = nullptr;
411 : : size_t bytes_written;
412 : :
413 : : /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
414 : : * while we are using the string's chars */
415 : : {
416 : 2 : JS::AutoCheckCannotGC nogc;
417 : : size_t len;
418 : :
419 [ + - ]: 2 : if (JS::StringHasLatin1Chars(str)) {
420 : : const JS::Latin1Char* chars =
421 : 2 : JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len);
422 [ - + ]: 2 : if (!chars)
423 : 0 : return nullptr;
424 : :
425 : : encoded = g_convert(reinterpret_cast<const char*>(chars), len,
426 : : encoding, // to_encoding
427 : : "LATIN1", // from_encoding
428 : : nullptr, // bytes_read
429 : 2 : &bytes_written, &error);
430 : : } else {
431 : : const char16_t* chars =
432 : 0 : JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
433 [ # # ]: 0 : if (!chars)
434 : 0 : return nullptr;
435 : :
436 : : encoded =
437 : 0 : g_convert(reinterpret_cast<const char*>(chars), len * 2,
438 : : encoding, // to_encoding
439 : : "UTF-16", // from_encoding
440 : : nullptr, // bytes_read
441 : 0 : &bytes_written, &error);
442 : : }
443 [ + - ]: 2 : }
444 : :
445 [ - + ]: 2 : if (!encoded)
446 : 0 : return gjs_throw_type_error_from_gerror(cx, error); // frees GError
447 : :
448 [ + - ]: 2 : if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
449 : 2 : bytes_written =
450 : 2 : zero_terminated_length(encoded.get(), bytes_written);
451 : : }
452 : :
453 [ + + ]: 2 : if (bytes_written == 0)
454 : 1 : return JS_NewUint8Array(cx, 0);
455 : :
456 : : array_buffer =
457 : 1 : JS::NewExternalArrayBuffer(cx, bytes_written, encoded.release(),
458 : 1 : gfree_arraybuffer_contents, nullptr);
459 [ + + + + ]: 3 : }
460 : :
461 [ - + ]: 33 : if (!array_buffer)
462 : 0 : return nullptr;
463 : :
464 : 33 : return JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1);
465 : 34 : }
466 : :
467 : : GJS_JSAPI_RETURN_CONVENTION
468 : 3 : static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str,
469 : : JS::HandleObject uint8array,
470 : : JS::MutableHandleValue rval) {
471 [ - + ]: 3 : if (!JS_IsUint8Array(uint8array)) {
472 : 0 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
473 : : "Argument to encodeInto() must be a Uint8Array");
474 : 0 : return false;
475 : : }
476 : :
477 : 3 : uint32_t len = JS_GetTypedArrayByteLength(uint8array);
478 : 3 : bool shared = JS_GetTypedArraySharedness(uint8array);
479 : :
480 [ - + ]: 3 : if (shared) {
481 : 0 : gjs_throw(cx, "Cannot encode data into shared memory.");
482 : 0 : return false;
483 : : }
484 : :
485 : 3 : mozilla::Maybe<std::tuple<size_t, size_t>> results;
486 : :
487 : : {
488 : 3 : JS::AutoCheckCannotGC nogc(cx);
489 : 3 : uint8_t* data = JS_GetUint8ArrayData(uint8array, &shared, nogc);
490 : :
491 : : // We already checked for sharedness with JS_GetTypedArraySharedness
492 : 3 : g_assert(!shared);
493 : :
494 : 3 : results = JS_EncodeStringToUTF8BufferPartial(
495 : 3 : cx, str, mozilla::AsWritableChars(mozilla::Span(data, len)));
496 : 3 : }
497 : :
498 [ - + ]: 3 : if (!results) {
499 : 0 : JS_ReportOutOfMemory(cx);
500 : 0 : return false;
501 : : }
502 : :
503 : : size_t read, written;
504 : 3 : std::tie(read, written) = *results;
505 : :
506 : 3 : g_assert(written <= len);
507 : :
508 : 3 : JS::RootedObject result(cx, JS_NewPlainObject(cx));
509 [ - + ]: 3 : if (!result)
510 : 0 : return false;
511 : :
512 : 3 : JS::RootedValue v_read(cx, JS::NumberValue(read)),
513 : 3 : v_written(cx, JS::NumberValue(written));
514 : :
515 [ + - ]: 6 : if (!JS_SetProperty(cx, result, "read", v_read) ||
516 [ - + - + ]: 6 : !JS_SetProperty(cx, result, "written", v_written))
517 : 0 : return false;
518 : :
519 : 3 : rval.setObject(*result);
520 : 3 : return true;
521 : 3 : }
522 : :
523 : : GJS_JSAPI_RETURN_CONVENTION
524 : 13 : static bool gjs_encode(JSContext* cx, unsigned argc, JS::Value* vp) {
525 : 13 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
526 : 13 : JS::RootedString str(cx);
527 : 13 : JS::UniqueChars encoding;
528 [ - + ]: 13 : if (!gjs_parse_call_args(cx, "encode", args, "Ss", "string", &str,
529 : : "encoding", &encoding))
530 : 0 : return false;
531 : :
532 : : JS::RootedObject uint8array(
533 : 13 : cx, gjs_encode_to_uint8array(cx, str, encoding.get(),
534 : 13 : GjsStringTermination::EXPLICIT_LENGTH));
535 [ - + ]: 13 : if (!uint8array)
536 : 0 : return false;
537 : :
538 : 13 : args.rval().setObject(*uint8array);
539 : 13 : return true;
540 : 13 : }
541 : :
542 : : GJS_JSAPI_RETURN_CONVENTION
543 : 3 : static bool gjs_encode_into(JSContext* cx, unsigned argc, JS::Value* vp) {
544 : 3 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
545 : 3 : JS::RootedString str(cx);
546 : 3 : JS::RootedObject uint8array(cx);
547 [ - + ]: 3 : if (!gjs_parse_call_args(cx, "encodeInto", args, "So", "string", &str,
548 : : "byteArray", &uint8array))
549 : 0 : return false;
550 : :
551 : 3 : return gjs_encode_into_uint8array(cx, str, uint8array, args.rval());
552 : 3 : }
553 : :
554 : : static JSFunctionSpec gjs_text_encoding_module_funcs[] = {
555 : : JS_FN("decode", gjs_decode, 3, 0),
556 : : JS_FN("encodeInto", gjs_encode_into, 2, 0),
557 : : JS_FN("encode", gjs_encode, 2, 0), JS_FS_END};
558 : :
559 : 6 : bool gjs_define_text_encoding_stuff(JSContext* cx,
560 : : JS::MutableHandleObject module) {
561 : 6 : JSObject* new_obj = JS_NewPlainObject(cx);
562 [ - + ]: 6 : if (!new_obj)
563 : 0 : return false;
564 : 6 : module.set(new_obj);
565 : :
566 : 6 : return JS_DefineFunctions(cx, module, gjs_text_encoding_module_funcs);
567 : : }
|