1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
32
33 #include "mongo/platform/basic.h"
34
35 #include "mongo/scripting/mozjs/valuereader.h"
36
37 #include <cmath>
38 #include <cstdio>
39 #include <js/CharacterEncoding.h>
40 #include <js/Date.h>
41
42 #include "mongo/base/error_codes.h"
43 #include "mongo/platform/decimal128.h"
44 #include "mongo/scripting/mozjs/implscope.h"
45 #include "mongo/scripting/mozjs/objectwrapper.h"
46 #include "mongo/util/base64.h"
47 #include "mongo/util/log.h"
48
49 namespace mongo {
50 namespace mozjs {
51
ValueReader(JSContext * cx,JS::MutableHandleValue value)52 ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value)
53 : _context(cx), _value(value) {}
54
fromBSONElement(const BSONElement & elem,const BSONObj & parent,bool readOnly)55 void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent, bool readOnly) {
56 auto scope = getScope(_context);
57
58 switch (elem.type()) {
59 case mongo::Code:
60 // javascriptProtection prevents Code and CodeWScope BSON types from
61 // being automatically marshalled into executable functions.
62 if (scope->isJavaScriptProtectionEnabled()) {
63 JS::AutoValueArray<1> args(_context);
64 ValueReader(_context, args[0]).fromStringData(elem.valueStringData());
65
66 JS::RootedObject obj(_context);
67 scope->getProto<CodeInfo>().newInstance(args, _value);
68 } else {
69 scope->newFunction(elem.valueStringData(), _value);
70 }
71 return;
72 case mongo::CodeWScope:
73 if (scope->isJavaScriptProtectionEnabled()) {
74 JS::AutoValueArray<2> args(_context);
75
76 ValueReader(_context, args[0]).fromStringData(elem.codeWScopeCode());
77 ValueReader(_context, args[1]).fromBSON(elem.codeWScopeObject(), nullptr, readOnly);
78
79 scope->getProto<CodeInfo>().newInstance(args, _value);
80 } else {
81 if (!elem.codeWScopeObject().isEmpty())
82 warning() << "CodeWScope doesn't transfer to db.eval";
83 scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1),
84 _value);
85 }
86 return;
87 case mongo::Symbol:
88 case mongo::String:
89 fromStringData(elem.valueStringData());
90 return;
91 case mongo::jstOID: {
92 OIDInfo::make(_context, elem.OID(), _value);
93 return;
94 }
95 case mongo::NumberDouble:
96 fromDouble(elem.Number());
97 return;
98 case mongo::NumberInt:
99 _value.setInt32(elem.Int());
100 return;
101 case mongo::Array: {
102 fromBSONArray(elem.embeddedObject(), &parent, readOnly);
103 return;
104 }
105 case mongo::Object:
106 fromBSON(elem.embeddedObject(), &parent, readOnly);
107 return;
108 case mongo::Date:
109 _value.setObjectOrNull(
110 JS::NewDateObject(_context, JS::TimeClip(elem.Date().toMillisSinceEpoch())));
111 return;
112 case mongo::Bool:
113 _value.setBoolean(elem.Bool());
114 return;
115 case mongo::jstNULL:
116 _value.setNull();
117 return;
118 case mongo::EOO:
119 case mongo::Undefined:
120 _value.setUndefined();
121 return;
122 case mongo::RegEx: {
123 // TODO parse into a custom type that can support any patterns and flags SERVER-9803
124
125 JS::AutoValueArray<2> args(_context);
126
127 ValueReader(_context, args[0]).fromStringData(elem.regex());
128 ValueReader(_context, args[1]).fromStringData(elem.regexFlags());
129
130 JS::RootedObject obj(_context);
131 scope->getProto<RegExpInfo>().newInstance(args, &obj);
132
133 _value.setObjectOrNull(obj);
134
135 return;
136 }
137 case mongo::BinData: {
138 int len;
139 const char* data = elem.binData(len);
140 std::stringstream ss;
141 base64::encode(ss, data, len);
142
143 JS::AutoValueArray<2> args(_context);
144
145 args[0].setInt32(elem.binDataType());
146
147 ValueReader(_context, args[1]).fromStringData(ss.str());
148
149 scope->getProto<BinDataInfo>().newInstance(args, _value);
150 return;
151 }
152 case mongo::bsonTimestamp: {
153 JS::AutoValueArray<2> args(_context);
154
155 ValueReader(_context, args[0])
156 .fromDouble(elem.timestampTime().toMillisSinceEpoch() / 1000);
157 ValueReader(_context, args[1]).fromDouble(elem.timestampInc());
158
159 scope->getProto<TimestampInfo>().newInstance(args, _value);
160
161 return;
162 }
163 case mongo::NumberLong: {
164 JS::RootedObject thisv(_context);
165 scope->getProto<NumberLongInfo>().newObject(&thisv);
166 JS_SetPrivate(thisv, scope->trackedNew<int64_t>(elem.numberLong()));
167 _value.setObjectOrNull(thisv);
168 return;
169 }
170 case mongo::NumberDecimal: {
171 Decimal128 decimal = elem.numberDecimal();
172 JS::AutoValueArray<1> args(_context);
173 ValueReader(_context, args[0]).fromDecimal128(decimal);
174 JS::RootedObject obj(_context);
175
176 scope->getProto<NumberDecimalInfo>().newInstance(args, &obj);
177 _value.setObjectOrNull(obj);
178
179 return;
180 }
181 case mongo::MinKey:
182 scope->getProto<MinKeyInfo>().newInstance(_value);
183 return;
184 case mongo::MaxKey:
185 scope->getProto<MaxKeyInfo>().newInstance(_value);
186 return;
187 case mongo::DBRef: {
188 JS::AutoValueArray<1> oidArgs(_context);
189 ValueReader(_context, oidArgs[0]).fromStringData(elem.dbrefOID().toString());
190
191 JS::AutoValueArray<2> dbPointerArgs(_context);
192 ValueReader(_context, dbPointerArgs[0]).fromStringData(elem.dbrefNS());
193 scope->getProto<OIDInfo>().newInstance(oidArgs, dbPointerArgs[1]);
194
195 scope->getProto<DBPointerInfo>().newInstance(dbPointerArgs, _value);
196 return;
197 }
198 default:
199 massert(16661,
200 str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(),
201 false);
202 break;
203 }
204
205 _value.setUndefined();
206 }
207
fromBSON(const BSONObj & obj,const BSONObj * parent,bool readOnly)208 void ValueReader::fromBSON(const BSONObj& obj, const BSONObj* parent, bool readOnly) {
209 JS::RootedObject child(_context);
210
211 bool filledDBRef = false;
212 if (obj.firstElementType() == String && str::equals(obj.firstElementFieldName(), "$ref")) {
213 BSONObjIterator it(obj);
214 it.next();
215 const BSONElement id = it.next();
216
217 if (id.ok() && str::equals(id.fieldName(), "$id")) {
218 DBRefInfo::make(_context, &child, obj, parent, readOnly);
219 filledDBRef = true;
220 }
221 }
222
223 if (!filledDBRef) {
224 BSONInfo::make(_context, &child, obj, parent, readOnly);
225 }
226
227 _value.setObjectOrNull(child);
228 }
229
fromBSONArray(const BSONObj & obj,const BSONObj * parent,bool readOnly)230 void ValueReader::fromBSONArray(const BSONObj& obj, const BSONObj* parent, bool readOnly) {
231 JS::AutoValueVector avv(_context);
232
233 BSONForEach(elem, obj) {
234 JS::RootedValue member(_context);
235
236 ValueReader(_context, &member).fromBSONElement(elem, parent ? *parent : obj, readOnly);
237 if (!avv.append(member)) {
238 uasserted(ErrorCodes::JSInterpreterFailure, "Failed to append to JS array");
239 }
240 }
241 JS::RootedObject array(_context, JS_NewArrayObject(_context, avv));
242 if (!array) {
243 uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewArrayObject");
244 }
245 _value.setObjectOrNull(array);
246 }
247
248 /**
249 * SpiderMonkey doesn't have a direct entry point to create a jsstring from
250 * utf8, so we have to flow through some slightly less public interfaces.
251 *
252 * Basically, we have to use their routines to convert to utf16, then assign
253 * those bytes with JS_NewUCStringCopyN
254 */
fromStringData(StringData sd)255 void ValueReader::fromStringData(StringData sd) {
256 size_t utf16Len;
257
258 // TODO: we have tests that involve dropping garbage in. Do we want to
259 // throw, or to take the lossy conversion?
260 auto utf16 = JS::LossyUTF8CharsToNewTwoByteCharsZ(
261 _context, JS::UTF8Chars(sd.rawData(), sd.size()), &utf16Len);
262
263 mozilla::UniquePtr<char16_t, JS::FreePolicy> utf16Deleter(utf16.get());
264
265 uassert(ErrorCodes::JSInterpreterFailure,
266 str::stream() << "Failed to encode \"" << sd << "\" as utf16",
267 utf16);
268
269 auto jsStr = JS_NewUCStringCopyN(_context, utf16.get(), utf16Len);
270
271 uassert(ErrorCodes::JSInterpreterFailure,
272 str::stream() << "Unable to copy \"" << sd << "\" into MozJS",
273 jsStr);
274
275 _value.setString(jsStr);
276 }
277
278 /**
279 * SpiderMonkey doesn't have a direct entry point to create a Decimal128
280 *
281 * Read NumberDecimal as a string
282 * Note: This prevents shell arithmetic, which is performed for number longs
283 * by converting them to doubles, which is imprecise. Until there is a better
284 * method to handle non-double shell arithmetic, decimals will remain
285 * as a non-numeric js type.
286 */
fromDecimal128(Decimal128 decimal)287 void ValueReader::fromDecimal128(Decimal128 decimal) {
288 NumberDecimalInfo::make(_context, _value, decimal);
289 }
290
291 /**
292 * SpiderMonkey has a nasty habit of interpreting certain NaN patterns as other boxed types (it
293 * assumes that only one kind of NaN exists in JS, rather than the full ieee754 spectrum). Thus we
294 * have to flow all double setting through a wrapper which ensures that nan's are coerced to the
295 * canonical javascript NaN.
296 *
297 * See SERVER-24054 for more details.
298 */
fromDouble(double d)299 void ValueReader::fromDouble(double d) {
300 if (std::isnan(d)) {
301 _value.set(JS_GetNaNValue(_context));
302 } else {
303 _value.setDouble(d);
304 }
305 }
306
307 } // namespace mozjs
308 } // namespace mongo
309