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