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