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