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 : :
5 : : #include <config.h>
6 : :
7 : : #include <stdint.h>
8 : :
9 : : #include <algorithm> // for copy_n
10 : :
11 : : #include <glib-object.h>
12 : : #include <glib.h>
13 : :
14 : : #include <js/ArrayBuffer.h>
15 : : #include <js/CallArgs.h>
16 : : #include <js/GCAPI.h>
17 : : #include <js/PropertyAndElement.h>
18 : : #include <js/PropertySpec.h>
19 : : #include <js/RootingAPI.h>
20 : : #include <js/TypeDecls.h>
21 : : #include <js/Utility.h> // for UniqueChars
22 : : #include <js/experimental/TypedData.h>
23 : : #include <jsapi.h> // for JS_NewPlainObject
24 : :
25 : : #include "gi/boxed.h"
26 : : #include "gjs/atoms.h"
27 : : #include "gjs/byteArray.h"
28 : : #include "gjs/context-private.h"
29 : : #include "gjs/deprecation.h"
30 : : #include "gjs/jsapi-util-args.h"
31 : : #include "gjs/jsapi-util.h"
32 : : #include "gjs/macros.h"
33 : : #include "gjs/text-encoding.h"
34 : :
35 : : GJS_JSAPI_RETURN_CONVENTION
36 : 7 : static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
37 : 7 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
38 : 7 : JS::UniqueChars encoding;
39 : 7 : JS::RootedObject byte_array(cx);
40 : :
41 [ - + ]: 7 : if (!gjs_parse_call_args(cx, "toString", args, "o|s", "byteArray",
42 : : &byte_array, "encoding", &encoding))
43 : 0 : return false;
44 : :
45 [ + + ]: 7 : const char* actual_encoding = encoding ? encoding.get() : "utf-8";
46 : : JS::RootedString str(
47 : 7 : cx, gjs_decode_from_uint8array(cx, byte_array, actual_encoding,
48 : 7 : GjsStringTermination::ZERO_TERMINATED, true));
49 [ + + ]: 7 : if (!str)
50 : 1 : return false;
51 : :
52 : 6 : args.rval().setString(str);
53 : 6 : return true;
54 : 7 : }
55 : :
56 : : /* Workaround to keep existing code compatible. This function is tacked onto
57 : : * any Uint8Array instances created in situations where previously a ByteArray
58 : : * would have been created. It logs a compatibility warning. */
59 : : GJS_JSAPI_RETURN_CONVENTION
60 : 2 : static bool instance_to_string_func(JSContext* cx, unsigned argc,
61 : : JS::Value* vp) {
62 [ - + ]: 2 : GJS_GET_THIS(cx, argc, vp, args, this_obj);
63 : 2 : JS::UniqueChars encoding;
64 : :
65 : 2 : _gjs_warn_deprecated_once_per_callsite(
66 : : cx, GjsDeprecationMessageId::ByteArrayInstanceToString);
67 : :
68 [ - + ]: 2 : if (!gjs_parse_call_args(cx, "toString", args, "|s", "encoding", &encoding))
69 : 0 : return false;
70 : :
71 [ - + ]: 2 : const char* actual_encoding = encoding ? encoding.get() : "utf-8";
72 : : JS::RootedString str(
73 : 2 : cx, gjs_decode_from_uint8array(cx, this_obj, actual_encoding,
74 : 2 : GjsStringTermination::ZERO_TERMINATED, true));
75 [ - + ]: 2 : if (!str)
76 : 0 : return false;
77 : :
78 : 2 : args.rval().setString(str);
79 : 2 : return true;
80 : 2 : }
81 : :
82 : : GJS_JSAPI_RETURN_CONVENTION
83 : 363 : static bool define_legacy_tostring(JSContext* cx, JS::HandleObject array) {
84 : 363 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
85 : 363 : return JS_DefineFunctionById(cx, array, atoms.to_string(),
86 : 363 : instance_to_string_func, 1, 0);
87 : : }
88 : :
89 : : /* fromString() function implementation */
90 : : GJS_JSAPI_RETURN_CONVENTION
91 : 8 : static bool from_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
92 : 8 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
93 : 8 : JS::RootedString str(cx);
94 : 8 : JS::UniqueChars encoding;
95 [ - + ]: 8 : if (!gjs_parse_call_args(cx, "fromString", args, "S|s", "string", &str,
96 : : "encoding", &encoding))
97 : 0 : return false;
98 : :
99 [ + + ]: 8 : const char* actual_encoding = encoding ? encoding.get() : "utf-8";
100 : : JS::RootedObject uint8array(
101 : 8 : cx, gjs_encode_to_uint8array(cx, str, actual_encoding,
102 : 8 : GjsStringTermination::ZERO_TERMINATED));
103 [ + - - + : 8 : if (!uint8array || !define_legacy_tostring(cx, uint8array))
- + ]
104 : 0 : return false;
105 : :
106 : 8 : args.rval().setObject(*uint8array);
107 : 8 : return true;
108 : 8 : }
109 : :
110 : : GJS_JSAPI_RETURN_CONVENTION
111 : : static bool
112 : 355 : from_gbytes_func(JSContext *context,
113 : : unsigned argc,
114 : : JS::Value *vp)
115 : : {
116 : 355 : JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
117 : 355 : JS::RootedObject bytes_obj(context);
118 : : GBytes *gbytes;
119 : :
120 [ - + ]: 355 : if (!gjs_parse_call_args(context, "fromGBytes", argv, "o",
121 : : "bytes", &bytes_obj))
122 : 0 : return false;
123 : :
124 [ - + ]: 355 : if (!BoxedBase::typecheck(context, bytes_obj, nullptr, G_TYPE_BYTES))
125 : 0 : return false;
126 : :
127 : 355 : gbytes = BoxedBase::to_c_ptr<GBytes>(context, bytes_obj);
128 [ - + ]: 355 : if (!gbytes)
129 : 0 : return false;
130 : :
131 : : size_t len;
132 : 355 : const void* data = g_bytes_get_data(gbytes, &len);
133 [ + + ]: 355 : if (len == 0) {
134 : 1 : JS::RootedObject empty_array(context, JS_NewUint8Array(context, 0));
135 [ + - - + : 1 : if (!empty_array || !define_legacy_tostring(context, empty_array))
- + ]
136 : 0 : return false;
137 : :
138 : 1 : argv.rval().setObject(*empty_array);
139 : 1 : return true;
140 : 1 : }
141 : :
142 : 354 : JS::RootedObject array_buffer{context, JS::NewArrayBuffer(context, len)};
143 [ - + ]: 354 : if (!array_buffer)
144 : 0 : return false;
145 : :
146 : : // Copy the data into the ArrayBuffer so that the copy is aligned, and
147 : : // because the GBytes data pointer may point into immutable memory.
148 : : {
149 : 354 : JS::AutoCheckCannotGC nogc;
150 : : bool unused;
151 : 354 : uint8_t* storage = JS::GetArrayBufferData(array_buffer, &unused, nogc);
152 : 354 : std::copy_n(static_cast<const uint8_t*>(data), len, storage);
153 : 354 : }
154 : :
155 : : JS::RootedObject obj(
156 : 354 : context, JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1));
157 [ + - - + : 354 : if (!obj || !define_legacy_tostring(context, obj))
- + ]
158 : 0 : return false;
159 : :
160 : 354 : argv.rval().setObject(*obj);
161 : 354 : return true;
162 : 355 : }
163 : :
164 : 15 : JSObject* gjs_byte_array_from_data_copy(JSContext* cx, size_t nbytes,
165 : : void* data) {
166 : 15 : JS::RootedObject array_buffer(cx);
167 : : // a null data pointer takes precedence over whatever `nbytes` says
168 [ + - ]: 15 : if (data) {
169 : 15 : array_buffer = JS::NewArrayBuffer(cx, nbytes);
170 : :
171 : 15 : JS::AutoCheckCannotGC nogc{};
172 : : bool unused;
173 : 15 : uint8_t* storage = JS::GetArrayBufferData(array_buffer, &unused, nogc);
174 : 15 : std::copy_n(static_cast<uint8_t*>(data), nbytes, storage);
175 : 15 : } else {
176 : 0 : array_buffer = JS::NewArrayBuffer(cx, 0);
177 : : }
178 [ - + ]: 15 : if (!array_buffer)
179 : 0 : return nullptr;
180 : :
181 : : JS::RootedObject array(cx,
182 : 15 : JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1));
183 : :
184 : 15 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
185 [ - + ]: 15 : if (!JS_DefineFunctionById(cx, array, atoms.to_string(),
186 : : instance_to_string_func, 1, 0))
187 : 0 : return nullptr;
188 : 15 : return array;
189 : 15 : }
190 : :
191 : 11 : JSObject* gjs_byte_array_from_byte_array(JSContext* cx, GByteArray* array) {
192 : 11 : return gjs_byte_array_from_data_copy(cx, array->len, array->data);
193 : : }
194 : :
195 : 46 : GBytes* gjs_byte_array_get_bytes(JSObject* obj) {
196 : : bool is_shared_memory;
197 : : size_t len;
198 : : uint8_t* data;
199 : :
200 : 46 : js::GetUint8ArrayLengthAndData(obj, &len, &is_shared_memory, &data);
201 : 46 : return g_bytes_new(data, len);
202 : : }
203 : :
204 : 10 : GByteArray* gjs_byte_array_get_byte_array(JSObject* obj) {
205 : 10 : return g_bytes_unref_to_array(gjs_byte_array_get_bytes(obj));
206 : : }
207 : :
208 : : static JSFunctionSpec gjs_byte_array_module_funcs[] = {
209 : : JS_FN("fromString", from_string_func, 2, 0),
210 : : JS_FN("fromGBytes", from_gbytes_func, 1, 0),
211 : : JS_FN("toString", to_string_func, 2, 0),
212 : : JS_FS_END};
213 : :
214 : : bool
215 : 9 : gjs_define_byte_array_stuff(JSContext *cx,
216 : : JS::MutableHandleObject module)
217 : : {
218 : 9 : module.set(JS_NewPlainObject(cx));
219 : 9 : return JS_DefineFunctions(cx, module, gjs_byte_array_module_funcs);
220 : : }
|