summaryrefslogtreecommitdiff
path: root/gjs/byteArray.cpp
blob: e0b7a03b85487a88730eb153d9f3804a468117f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2010 litl, LLC

#include <config.h>

#include <stdint.h>

#include <glib-object.h>
#include <glib.h>

#include <js/ArrayBuffer.h>
#include <js/CallArgs.h>
#include <js/PropertyAndElement.h>
#include <js/PropertySpec.h>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/Utility.h>   // for UniqueChars
#include <js/experimental/TypedData.h>
#include <jsapi.h>  // for JS_NewPlainObject

#include "gi/boxed.h"
#include "gjs/atoms.h"
#include "gjs/byteArray.h"
#include "gjs/context-private.h"
#include "gjs/deprecation.h"
#include "gjs/jsapi-util-args.h"
#include "gjs/jsapi-util.h"
#include "gjs/macros.h"
#include "gjs/text-encoding.h"
#include "util/misc.h"  // for _gjs_memdup2

// Callback to use with JS::NewExternalArrayBuffer()

static void bytes_unref_arraybuffer(void* contents [[maybe_unused]],
                                    void* user_data) {
    auto* gbytes = static_cast<GBytes*>(user_data);
    g_bytes_unref(gbytes);
}

GJS_JSAPI_RETURN_CONVENTION
static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    JS::UniqueChars encoding;
    JS::RootedObject byte_array(cx);

    if (!gjs_parse_call_args(cx, "toString", args, "o|s", "byteArray",
                             &byte_array, "encoding", &encoding))
        return false;

    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
 * any Uint8Array instances created in situations where previously a ByteArray
 * would have been created. It logs a compatibility warning. */
GJS_JSAPI_RETURN_CONVENTION
static bool instance_to_string_func(JSContext* cx, unsigned argc,
                                    JS::Value* vp) {
    GJS_GET_THIS(cx, argc, vp, args, this_obj);
    JS::UniqueChars encoding;

    _gjs_warn_deprecated_once_per_callsite(
        cx, GjsDeprecationMessageId::ByteArrayInstanceToString);

    if (!gjs_parse_call_args(cx, "toString", args, "|s", "encoding", &encoding))
        return false;

    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
static bool define_legacy_tostring(JSContext* cx, JS::HandleObject array) {
    const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
    return JS_DefineFunctionById(cx, array, atoms.to_string(),
                                 instance_to_string_func, 1, 0);
}

/* fromString() function implementation */
GJS_JSAPI_RETURN_CONVENTION
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;
    if (!gjs_parse_call_args(cx, "fromString", args, "S|s", "string", &str,
                             "encoding", &encoding))
        return false;

    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;

    args.rval().setObject(*uint8array);
    return true;
}

GJS_JSAPI_RETURN_CONVENTION
static bool
from_gbytes_func(JSContext *context,
                 unsigned   argc,
                 JS::Value *vp)
{
    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
    JS::RootedObject bytes_obj(context);
    GBytes *gbytes;

    if (!gjs_parse_call_args(context, "fromGBytes", argv, "o",
                             "bytes", &bytes_obj))
        return false;

    if (!BoxedBase::typecheck(context, bytes_obj, nullptr, G_TYPE_BYTES))
        return false;

    gbytes = BoxedBase::to_c_ptr<GBytes>(context, bytes_obj);
    if (!gbytes)
        return false;

    size_t len;
    const void* data = g_bytes_get_data(gbytes, &len);
    if (len == 0) {
        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;
    }

    JS::RootedObject array_buffer(
        context,
        JS::NewExternalArrayBuffer(
            context, len,
            const_cast<void*>(data),  // the ArrayBuffer won't modify the data
            bytes_unref_arraybuffer, gbytes));
    if (!array_buffer)
        return false;
    g_bytes_ref(gbytes);  // now owned by both ArrayBuffer and BoxedBase

    JS::RootedObject obj(
        context, JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1));
    if (!obj || !define_legacy_tostring(context, obj))
        return false;

    argv.rval().setObject(*obj);
    return true;
}

JSObject* gjs_byte_array_from_data(JSContext* cx, size_t nbytes, void* data) {
    JS::RootedObject array_buffer(cx);
    // a null data pointer takes precedence over whatever `nbytes` says
    if (data)
        array_buffer = JS::NewArrayBufferWithContents(
            cx, nbytes, _gjs_memdup2(data, nbytes));
    else
        array_buffer = JS::NewArrayBuffer(cx, 0);
    if (!array_buffer)
        return nullptr;

    JS::RootedObject array(cx,
                           JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1));

    const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
    if (!JS_DefineFunctionById(cx, array, atoms.to_string(),
                               instance_to_string_func, 1, 0))
        return nullptr;
    return array;
}

JSObject* gjs_byte_array_from_byte_array(JSContext* cx, GByteArray* array) {
    return gjs_byte_array_from_data(cx, array->len, array->data);
}

GBytes* gjs_byte_array_get_bytes(JSObject* obj) {
    bool is_shared_memory;
    size_t len;
    uint8_t* data;

    js::GetUint8ArrayLengthAndData(obj, &len, &is_shared_memory, &data);
    return g_bytes_new(data, len);
}

GByteArray* gjs_byte_array_get_byte_array(JSObject* obj) {
    return g_bytes_unref_to_array(gjs_byte_array_get_bytes(obj));
}

static JSFunctionSpec gjs_byte_array_module_funcs[] = {
    JS_FN("fromString", from_string_func, 2, 0),
    JS_FN("fromGBytes", from_gbytes_func, 1, 0),
    JS_FN("toString", to_string_func, 2, 0),
    JS_FS_END};

bool
gjs_define_byte_array_stuff(JSContext              *cx,
                            JS::MutableHandleObject module)
{
    module.set(JS_NewPlainObject(cx));
    return JS_DefineFunctions(cx, module, gjs_byte_array_module_funcs);
}