[gjs/ewlsh/text-encoding: 1/5] modules: Support zero-terminated and fixed length encoding.




commit e297f19e5d0e5fa44ea98094a3e0cc48735b63fc
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Jul 4 22:04:39 2021 -0700

    modules: Support zero-terminated and fixed length encoding.

 gjs/byteArray.cpp         | 127 ++++-------------
 gjs/jsapi-util-string.cpp |  37 ++++-
 gjs/jsapi-util.h          |   3 +
 gjs/text-encoding.cpp     | 344 ++++++++++++++++++++++++++++++++++++++--------
 gjs/text-encoding.h       |  15 +-
 5 files changed, 364 insertions(+), 162 deletions(-)
---
diff --git a/gjs/byteArray.cpp b/gjs/byteArray.cpp
index 63a2f17d..b210e77b 100644
--- a/gjs/byteArray.cpp
+++ b/gjs/byteArray.cpp
@@ -5,7 +5,6 @@
 #include <config.h>
 
 #include <stdint.h>
-#include <string.h>  // for strcmp, memchr, strlen
 
 #include <girepository.h>
 #include <glib-object.h>
@@ -13,7 +12,6 @@
 
 #include <js/ArrayBuffer.h>
 #include <js/CallArgs.h>
-#include <js/GCAPI.h>  // for AutoCheckCannotGC
 #include <js/PropertySpec.h>
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
@@ -31,11 +29,7 @@
 #include "gjs/text-encoding.h"
 #include "util/misc.h"  // for _gjs_memdup2
 
-/* Callbacks to use with JS::NewExternalArrayBuffer() */
-
-static void gfree_arraybuffer_contents(void* contents, void*) {
-    g_free(contents);
-}
+// Callback to use with JS::NewExternalArrayBuffer()
 
 static void bytes_unref_arraybuffer(void* contents [[maybe_unused]],
                                     void* user_data) {
@@ -53,7 +47,15 @@ static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
                              &byte_array, "encoding", &encoding))
         return false;
 
-    return bytearray_to_string(cx, byte_array, encoding.get(), args.rval());
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedString str(cx, gjs_decode_from_uint8array(
+                                 cx, byte_array, actual_encoding,
+                                 GjsStringTermination::ZERO_TERMINATED, true));
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
+    return true;
 }
 
 /* Workaround to keep existing code compatible. This function is tacked onto
@@ -71,7 +73,15 @@ static bool instance_to_string_func(JSContext* cx, unsigned argc,
     if (!gjs_parse_call_args(cx, "toString", args, "|s", "encoding", &encoding))
         return false;
 
-    return bytearray_to_string(cx, this_obj, encoding.get(), args.rval());
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedString str(cx, gjs_decode_from_uint8array(
+                                 cx, this_obj, actual_encoding,
+                                 GjsStringTermination::ZERO_TERMINATED, true));
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
+    return true;
 }
 
 GJS_JSAPI_RETURN_CONVENTION
@@ -83,101 +93,22 @@ static bool define_legacy_tostring(JSContext* cx, JS::HandleObject array) {
 
 /* fromString() function implementation */
 GJS_JSAPI_RETURN_CONVENTION
-static bool
-from_string_func(JSContext *context,
-                 unsigned   argc,
-                 JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
+static bool from_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedString str(cx);
     JS::UniqueChars encoding;
-    JS::UniqueChars utf8;
-    bool encoding_is_utf8;
-    JS::RootedObject obj(context), array_buffer(context);
-
-    if (!gjs_parse_call_args(context, "fromString", argv, "s|s",
-                             "string", &utf8,
+    if (!gjs_parse_call_args(cx, "fromString", args, "S|s", "string", &str,
                              "encoding", &encoding))
         return false;
 
-    if (argc > 1) {
-        /* maybe we should be smarter about utf8 synonyms here.
-         * doesn't matter much though. encoding_is_utf8 is
-         * just an optimization anyway.
-         */
-        encoding_is_utf8 = (strcmp(encoding.get(), "UTF-8") == 0);
-    } else {
-        encoding_is_utf8 = true;
-    }
-
-    if (encoding_is_utf8) {
-        /* optimization? avoids iconv overhead and runs
-         * libmozjs hardwired utf16-to-utf8.
-         */
-        size_t len = strlen(utf8.get());
-        array_buffer =
-            JS::NewArrayBufferWithContents(context, len, utf8.release());
-    } else {
-        JSString *str = argv[0].toString();  /* Rooted by argv */
-        GError *error = NULL;
-        char *encoded = NULL;
-        gsize bytes_written;
-
-        /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
-         * while we are using the string's chars */
-        {
-            JS::AutoCheckCannotGC nogc;
-            size_t len;
-
-            if (JS_StringHasLatin1Chars(str)) {
-                const JS::Latin1Char *chars =
-                    JS_GetLatin1StringCharsAndLength(context, nogc, str, &len);
-                if (chars == NULL)
-                    return false;
-
-                encoded = g_convert((char *) chars, len,
-                                    encoding.get(),  // to_encoding
-                                    "LATIN1",  /* from_encoding */
-                                    NULL,  /* bytes read */
-                                    &bytes_written, &error);
-            } else {
-                const char16_t *chars =
-                    JS_GetTwoByteStringCharsAndLength(context, nogc, str, &len);
-                if (chars == NULL)
-                    return false;
-
-                encoded = g_convert((char *) chars, len * 2,
-                                    encoding.get(),  // to_encoding
-                                    "UTF-16",  /* from_encoding */
-                                    NULL,  /* bytes read */
-                                    &bytes_written, &error);
-            }
-        }
-
-        if (!encoded)
-            return gjs_throw_gerror_message(context, error);  // frees GError
-
-        if (bytes_written == 0) {
-            g_free(encoded);
-            JS::RootedObject empty_array(context, JS_NewUint8Array(context, 0));
-            if (!empty_array || !define_legacy_tostring(context, empty_array))
-                return false;
-
-            argv.rval().setObject(*empty_array);
-            return true;
-        }
-
-        array_buffer =
-            JS::NewExternalArrayBuffer(context, bytes_written, encoded,
-                                       gfree_arraybuffer_contents, nullptr);
-    }
-
-    if (!array_buffer)
-        return false;
-    obj = JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1);
-    if (!obj || !define_legacy_tostring(context, obj))
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedObject uint8array(
+        cx, gjs_encode_to_uint8array(cx, str, actual_encoding,
+                                     GjsStringTermination::ZERO_TERMINATED));
+    if (!uint8array || !define_legacy_tostring(cx, uint8array))
         return false;
 
-    argv.rval().setObject(*obj);
+    args.rval().setObject(*uint8array);
     return true;
 }
 
diff --git a/gjs/jsapi-util-string.cpp b/gjs/jsapi-util-string.cpp
index e5de20c2..49226d53 100644
--- a/gjs/jsapi-util-string.cpp
+++ b/gjs/jsapi-util-string.cpp
@@ -19,7 +19,6 @@
 #include <js/BigInt.h>
 #include <js/CharacterEncoding.h>
 #include <js/Class.h>
-#include <js/ComparisonOperators.h>
 #include <js/GCAPI.h>  // for AutoCheckCannotGC
 #include <js/Id.h>     // for JSID_IS_STRING...
 #include <js/Promise.h>
@@ -92,6 +91,42 @@ JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) {
     return JS_EncodeStringToUTF8(cx, str);
 }
 
+/**
+ * gjs_string_to_utf8:
+ * @param cx: the current #JSContext
+ * @param str: a handle to a JSString
+ * @param output a pointer for the output char array
+ * @param output_len a pointer for the length of output
+ *
+ * @brief Converts a JSString to UTF-8 and puts the char array in #output and
+ * its length in #output_len.
+ *
+ * This function is a wrapper around the JSAPI for deflating UTF-8, it should
+ * be used instead of using JS::DeflateStringToUTF8Buffer directly.
+ */
+bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, char** output,
+                          size_t* output_len) {
+    JSLinearString* linear = JS_EnsureLinearString(cx, str);
+    if (!linear)
+        return false;
+
+    size_t length = JS::GetDeflatedUTF8StringLength(linear);
+    char* bytes = static_cast<char*>(g_malloc(length + 1));
+    if (!bytes)
+        return false;
+
+    // Append a zero-terminator to the string.
+    bytes[length] = '\0';
+
+    size_t deflated_length [[maybe_unused]] =
+        JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length));
+    g_assert(deflated_length == length);
+
+    *output_len = length;
+    *output = bytes;
+    return true;
+}
+
 bool
 gjs_string_from_utf8(JSContext             *context,
                      const char            *utf8_string,
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index e1f41e5a..c43fd860 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -451,6 +451,9 @@ void gjs_warning_reporter(JSContext*, JSErrorReport* report);
 GJS_JSAPI_RETURN_CONVENTION
 JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value string_val);
 GJS_JSAPI_RETURN_CONVENTION
+bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, char** output,
+                          size_t* output_len);
+GJS_JSAPI_RETURN_CONVENTION
 bool gjs_string_from_utf8(JSContext             *context,
                           const char            *utf8_string,
                           JS::MutableHandleValue value_p);
diff --git a/gjs/text-encoding.cpp b/gjs/text-encoding.cpp
index a3559fc8..daa43da2 100644
--- a/gjs/text-encoding.cpp
+++ b/gjs/text-encoding.cpp
@@ -35,6 +35,20 @@
 #include "gjs/jsapi-util.h"
 #include "gjs/text-encoding.h"
 
+// Callback to use with JS::NewExternalArrayBuffer()
+
+static void gfree_arraybuffer_contents(void* contents, void*) {
+    g_free(contents);
+}
+
+static std::nullptr_t gjs_throw_type_error_from_gerror(JSContext* cx,
+                                                       GError* error) {
+    g_return_val_if_fail(error, nullptr);
+    gjs_throw_custom(cx, JSProto_TypeError, nullptr, "%s", error->message);
+    g_error_free(error);
+    return nullptr;
+}
+
 // UTF16_CODESET is used to encode and decode UTF-16 buffers with
 // iconv. To ensure the output of iconv is laid out in memory correctly
 // we have to use UTF-16LE on little endian systems and UTF-16BE on big
@@ -62,71 +76,78 @@ static const char* UTF16_CODESET = "UTF-16BE";
            strcasecmp(stripped, "utf8") == 0;
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool to_string_impl_slow(JSContext* cx, uint8_t* data, uint32_t len,
-                                const char* encoding,
-                                JS::MutableHandleValue rval) {
-    size_t bytes_written;
-    GError* error = nullptr;
-    GjsAutoChar u16_str =
-        g_convert(reinterpret_cast<char*>(data), len, UTF16_CODESET, encoding,
-                  /* bytes_read = */ nullptr, &bytes_written, &error);
-    if (!u16_str)
-        return gjs_throw_gerror_message(cx, error);  // frees GError
-
-    // bytes_written should be bytes in a UTF-16 string so should be a multiple
-    // of 2
-    g_assert((bytes_written % 2) == 0);
-
-    // g_convert 0-terminates the string, although the 0 isn't included in
-    // bytes_written
-    JSString* s =
-        JS_NewUCStringCopyZ(cx, reinterpret_cast<char16_t*>(u16_str.get()));
-    if (!s)
-        return false;
+// Finds the length of a given data array, stopping at the first 0 byte.
+[[nodiscard]] static uint32_t zero_terminated_length(uint8_t* data,
+                                                     uint32_t len) {
+    uint8_t *start = data, *end = data + (len * sizeof(uint8_t));
+    uint8_t* found = std::find(start, end, 0);
+    if (found != end)
+        return std::distance(data, found) / sizeof(uint8_t);
 
-    rval.setString(s);
-    return true;
+    return len;
 }
 
-// implement ByteArray.toString() with an optional encoding arg
-bool bytearray_to_string(JSContext* context, JS::HandleObject byte_array,
-                         const char* encoding, JS::MutableHandleValue rval) {
+// decode() function implementation
+JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array,
+                                     const char* encoding,
+                                     GjsStringTermination string_termination) {
     if (!JS_IsUint8Array(byte_array)) {
-        gjs_throw(context,
-                  "Argument to ByteArray.toString() must be a Uint8Array");
-        return false;
+        gjs_throw(cx, "Argument to decode() must be a Uint8Array");
+        return nullptr;
     }
 
-    bool encoding_is_utf8 = true;
-    if (encoding)
-        encoding_is_utf8 = is_utf8_label(encoding);
-
     uint8_t* data;
     uint32_t len;
     bool is_shared_memory;
     js::GetUint8ArrayLengthAndData(byte_array, &len, &is_shared_memory, &data);
 
-    if (len == 0) {
-        rval.setString(JS_GetEmptyString(context));
-        return true;
-    }
+    // If the desired behavior is zero-terminated, calculate the
+    // zero-terminated length of the given data. If the original
+    // length, len, is smaller than the zero-terminated length,
+    // use it.
+    if (len && string_termination == GjsStringTermination::ZERO_TERMINATED)
+        len = std::min(len, zero_terminated_length(data, len));
+
+    // If the calculated length is 0 we can just return an empty string.
+    if (len == 0)
+        return JS_GetEmptyString(cx);
 
+    // Optimization, only use glib's iconv-based converters if we're dealing
+    // with a non-UTF8 encoding. SpiderMonkey has highly optimized UTF-8 decoder
+    // and encoders.
+    bool encoding_is_utf8 = is_utf8_label(encoding);
     if (!encoding_is_utf8)
-        return to_string_impl_slow(context, data, len, encoding, rval);
+        return gjs_decode_from_uint8array_slow(cx, data, len, encoding);
 
-    // optimization, avoids iconv overhead and runs libmozjs hardwired
-    // utf8-to-utf16
+    JS::RootedString decoded(cx);
+    JS::UTF8Chars chars(reinterpret_cast<char*>(data), len);
+    JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
 
-    // If there are any 0 bytes, including the terminating byte, stop at the
-    // first one
-    if (data[len - 1] == 0 || memchr(data, 0, len)) {
-        if (!gjs_string_from_utf8(context, reinterpret_cast<char*>(data), rval))
-            return false;
+    // If an exception occurred, we need to check if the
+    // exception was an InternalError. Unfortunately,
+    // SpiderMonkey's decoder can throw InternalError for some
+    // invalid UTF-8 sources, we have to convert this into a
+    // TypeError to match the Encoding specification.
+    if (str) {
+        decoded.set(str);
     } else {
-        if (!gjs_string_from_utf8_n(context, reinterpret_cast<char*>(data), len,
-                                    rval))
-            return false;
+        if (!JS_IsExceptionPending(cx))
+            return nullptr;
+        JS::RootedValue exc(cx);
+        if (!JS_GetPendingException(cx, &exc) || !exc.isObject())
+            return nullptr;
+
+        JS::RootedObject exc_obj(cx, &exc.toObject());
+        const JSClass* internal_error =
+            js::ProtoKeyToClass(JSProto_InternalError);
+        if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) {
+            // Clear the existing exception.
+            JS_ClearPendingException(cx);
+            gjs_throw_custom(cx, JSProto_TypeError, nullptr,
+                             "The provided encoded data was not valid UTF-8");
+        }
+
+        return nullptr;
     }
 
     uint8_t* current_data;
@@ -134,26 +155,227 @@ bool bytearray_to_string(JSContext* context, JS::HandleObject byte_array,
     bool ignore_val;
 
     // If a garbage collection occurs between when we call
-    // js::GetUint8ArrayLengthAndData and return from gjs_string_from_utf8, a
-    // use-after-free corruption can occur if the garbage collector shifts the
-    // location of the Uint8Array's private data. To mitigate this we call
-    // js::GetUint8ArrayLengthAndData again and then compare if the length and
-    // pointer are still the same. If the pointers differ, we use the slow path
-    // to ensure no data corruption occurred. The shared-ness of an array cannot
-    // change between calls, so we ignore it.
+    // js::GetUint8ArrayLengthAndData and return from
+    // gjs_decode_from_uint8array, a use-after-free corruption can occur if the
+    // garbage collector shifts the location of the Uint8Array's private data.
+    // To mitigate this we call js::GetUint8ArrayLengthAndData again and then
+    // compare if the length and pointer are still the same. If the pointers
+    // differ, we use the slow path to ensure no data corruption occurred. The
+    // shared-ness of an array cannot change between calls, so we ignore it.
     js::GetUint8ArrayLengthAndData(byte_array, &current_len, &ignore_val,
                                    &current_data);
 
     // Ensure the private data hasn't changed
-    if (current_len == len && current_data == data)
-        return true;
+    if (current_data == data)
+        return decoded;
+
+    // Length shouldn't change across calls but recalculate
+    // based on the moved data to be sure.
+    if (current_len &&
+        string_termination == GjsStringTermination::ZERO_TERMINATED) {
+        current_len = std::min(
+            current_len, zero_terminated_length(current_data, current_len));
+    }
 
     // This was the UTF-8 optimized path, so we explicitly pass the encoding
-    return to_string_impl_slow(context, current_data, current_len, "UTF-8",
-                               rval);
+    return gjs_decode_from_uint8array_slow(cx, current_data, current_len,
+                                           "UTF-8", fatal);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_decode(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    JS::RootedObject byte_array(cx);
+    JS::UniqueChars encoding;
+    bool fatal = false;
+    if (!gjs_parse_call_args(cx, "decode", args, "os|b", "byteArray",
+                             &byte_array, "encoding", &encoding, "fatal",
+                             &fatal))
+        return false;
+
+    JS::RootedString decoded(
+        cx, gjs_decode_from_uint8array(cx, byte_array, encoding.get(),
+                                       GjsStringTermination::EXPLICIT_LENGTH,
+                                       fatal));
+    if (!decoded)
+        return false;
+
+    args.rval().setString(decoded);
+    return true;
+}
+
+// encode() function implementation
+JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
+                                   const char* encoding,
+                                   GjsStringTermination string_termination) {
+    JS::RootedObject array_buffer(cx);
+
+    bool encoding_is_utf8 = is_utf8_label(encoding);
+    if (encoding_is_utf8) {
+        char* utf8;
+        size_t utf8_len;
+
+        if (!gjs_string_to_utf8_n(cx, str, &utf8, &utf8_len))
+            return nullptr;
+
+        if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
+            utf8_len = std::min(utf8_len, strlen(utf8));
+        }
+
+        array_buffer = JS::NewArrayBufferWithContents(cx, utf8_len, utf8);
+    } else {
+        GError* error = nullptr;
+        char* encoded = nullptr;
+        gsize bytes_written;
+
+        /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
+         * while we are using the string's chars */
+        {
+            JS::AutoCheckCannotGC nogc;
+            size_t len;
+
+            if (JS_StringHasLatin1Chars(str)) {
+                const JS::Latin1Char* chars =
+                    JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len);
+                if (!chars)
+                    return nullptr;
+
+                encoded =
+                    g_convert(reinterpret_cast<const char*>(chars), len,
+                              /* to_encoding */ encoding,
+                              /* from_encoding */ "LATIN1",
+                              /* bytes read */ nullptr, &bytes_written, &error);
+            } else {
+                const char16_t* chars =
+                    JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
+                if (!chars)
+                    return nullptr;
+
+                encoded =
+                    g_convert(reinterpret_cast<const char*>(chars), len * 2,
+                              encoding,  // to_encoding
+                              "UTF-16",  /* from_encoding */
+                              nullptr,   /* bytes read */
+                              &bytes_written, &error);
+            }
+        }
+
+        if (!encoded)
+            return gjs_throw_type_error_from_gerror(cx, error);  // frees GError
+
+        if (bytes_written == 0) {
+            g_free(encoded);
+            JS::RootedObject empty_array(cx, JS_NewUint8Array(cx, 0));
+            if (!empty_array)
+                return nullptr;
+
+            return empty_array;
+        }
+
+        array_buffer = JS::NewExternalArrayBuffer(
+            cx, bytes_written, encoded, gfree_arraybuffer_contents, nullptr);
+    }
+
+    if (!array_buffer)
+        return nullptr;
+
+    return JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str,
+                                       JS::HandleObject uint8array,
+                                       JS::MutableHandleValue rval) {
+    if (!JS_IsUint8Array(uint8array)) {
+        gjs_throw_custom(cx, JSProto_TypeError, nullptr,
+                         "Argument to encodeInto() must be a Uint8Array");
+        return false;
+    }
+
+    uint32_t len = JS_GetTypedArrayByteLength(uint8array);
+    bool shared = JS_GetTypedArraySharedness(uint8array);
+
+    if (shared) {
+        gjs_throw(cx, "Cannot encode data into shared memory.");
+        return false;
+    }
+
+    mozilla::Maybe<mozilla::Tuple<size_t, size_t>> results;
+
+    {
+        JS::AutoCheckCannotGC nogc(cx);
+        uint8_t* data = JS_GetUint8ArrayData(uint8array, &shared, nogc);
+
+        // We already checked for sharedness with JS_GetTypedArraySharedness
+        g_assert(!shared);
+
+        results = JS_EncodeStringToUTF8BufferPartial(
+            cx, str, mozilla::AsWritableChars(mozilla::Span(data, len)));
+    }
+
+    if (!results) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+    }
+
+    size_t read, written;
+
+    mozilla::Tie(read, written) = *results;
+
+    g_assert(written <= len);
+
+    JS::RootedObject result(cx, JS_NewPlainObject(cx));
+    if (!result)
+        return false;
+
+    JS::RootedValue readv(cx, JS::NumberValue(read)),
+        writtenv(cx, JS::NumberValue(written));
+
+    if (!JS_SetProperty(cx, result, "read", readv) ||
+        !JS_SetProperty(cx, result, "written", writtenv)) {
+        return false;
+    }
+
+    rval.setObject(*result);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_encode(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedString str(cx);
+    JS::UniqueChars encoding;
+    if (!gjs_parse_call_args(cx, "encode", args, "Ss", "string", &str,
+                             "encoding", &encoding))
+        return false;
+
+    JS::RootedObject uint8array(
+        cx, gjs_encode_to_uint8array(cx, str, encoding.get(),
+                                     GjsStringTermination::EXPLICIT_LENGTH));
+    if (!uint8array)
+        return false;
+
+    args.rval().setObject(*uint8array);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_encode_into(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedString str(cx);
+    JS::RootedObject uint8array(cx);
+    if (!gjs_parse_call_args(cx, "encodeInto", args, "So", "string", &str,
+                             "byteArray", &uint8array))
+        return false;
+
+    return gjs_encode_into_uint8array(cx, str, uint8array, args.rval());
 }
 
-static JSFunctionSpec gjs_text_encoding_module_funcs[] = {JS_FS_END};
+static JSFunctionSpec gjs_text_encoding_module_funcs[] = {
+    JS_FN("decode", gjs_decode, 3, 0),
+    JS_FN("encodeInto", gjs_encode_into, 2, 0),
+    JS_FN("encode", gjs_encode, 2, 0), JS_FS_END};
 
 bool gjs_define_text_encoding_stuff(JSContext* cx,
                                     JS::MutableHandleObject module) {
diff --git a/gjs/text-encoding.h b/gjs/text-encoding.h
index e9425392..eee174bb 100644
--- a/gjs/text-encoding.h
+++ b/gjs/text-encoding.h
@@ -14,9 +14,20 @@
 
 #include "gjs/macros.h"
 
+enum class GjsStringTermination {
+    ZERO_TERMINATED,
+    EXPLICIT_LENGTH,
+};
+
+GJS_JSAPI_RETURN_CONVENTION
+JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject uint8array,
+                                     const char* encoding,
+                                     GjsStringTermination string_termination);
+
 GJS_JSAPI_RETURN_CONVENTION
-bool bytearray_to_string(JSContext* cx, JS::HandleObject uint8array,
-                         const char* encoding, JS::MutableHandleValue rval);
+JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
+                                   const char* encoding,
+                                   GjsStringTermination string_termination);
 
 GJS_JSAPI_RETURN_CONVENTION
 bool gjs_define_text_encoding_stuff(JSContext* cx,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]