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 #include "mongo/platform/basic.h"
32 
33 #include "mongo/scripting/mozjs/bson.h"
34 
35 #include <boost/optional.hpp>
36 #include <set>
37 
38 #include "mongo/scripting/mozjs/idwrapper.h"
39 #include "mongo/scripting/mozjs/implscope.h"
40 #include "mongo/scripting/mozjs/internedstring.h"
41 #include "mongo/scripting/mozjs/objectwrapper.h"
42 #include "mongo/scripting/mozjs/valuereader.h"
43 #include "mongo/scripting/mozjs/valuewriter.h"
44 #include "mongo/util/string_map.h"
45 
46 namespace mongo {
47 namespace mozjs {
48 
49 const char* const BSONInfo::className = "BSON";
50 
51 const JSFunctionSpec BSONInfo::freeFunctions[3] = {
52     MONGO_ATTACH_JS_FUNCTION(bsonWoCompare), MONGO_ATTACH_JS_FUNCTION(bsonBinaryEqual), JS_FS_END,
53 };
54 
55 namespace {
56 
57 /**
58  * Holder for bson objects which tracks state for the js wrapper
59  *
60  * Basically, we have read only and read/write variants, and a need to manage
61  * the appearance of mutable state on the read/write versions.
62  */
63 struct BSONHolder {
BSONHoldermongo::mozjs::__anon41853c1f0111::BSONHolder64     BSONHolder(const BSONObj& obj, const BSONObj* parent, const MozJSImplScope* scope, bool ro)
65         : _obj(obj),
66           _generation(scope->getGeneration()),
67           _isOwned(obj.isOwned() || (parent && parent->isOwned())),
68           _resolved(false),
69           _readOnly(ro),
70           _altered(false) {
71         uassert(
72             ErrorCodes::BadValue,
73             "Attempt to bind an unowned BSON Object to a JS scope marked as requiring ownership",
74             _isOwned || (!scope->requiresOwnedObjects()));
75         if (parent) {
76             _parent.emplace(*parent);
77         }
78     }
79 
getOwnermongo::mozjs::__anon41853c1f0111::BSONHolder80     const BSONObj& getOwner() const {
81         return _parent ? *(_parent) : _obj;
82     }
83 
uassertValidmongo::mozjs::__anon41853c1f0111::BSONHolder84     void uassertValid(JSContext* cx) const {
85         if (!_isOwned && getScope(cx)->getGeneration() != _generation)
86             uasserted(ErrorCodes::BadValue,
87                       "Attempt to access an invalidated BSON Object in JS scope");
88     }
89 
90     BSONObj _obj;
91     boost::optional<BSONObj> _parent;
92     std::size_t _generation;
93     bool _isOwned;
94     bool _resolved;
95     bool _readOnly;
96     bool _altered;
97     StringMap<bool> _removed;
98 };
99 
getValidHolder(JSContext * cx,JSObject * obj)100 BSONHolder* getValidHolder(JSContext* cx, JSObject* obj) {
101     auto holder = static_cast<BSONHolder*>(JS_GetPrivate(obj));
102 
103     if (holder)
104         holder->uassertValid(cx);
105 
106     return holder;
107 }
108 
109 }  // namespace
110 
make(JSContext * cx,JS::MutableHandleObject obj,BSONObj bson,const BSONObj * parent,bool ro)111 void BSONInfo::make(
112     JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, const BSONObj* parent, bool ro) {
113     auto scope = getScope(cx);
114 
115     scope->getProto<BSONInfo>().newObject(obj);
116     JS_SetPrivate(obj, scope->trackedNew<BSONHolder>(bson, parent, scope, ro));
117 }
118 
finalize(JSFreeOp * fop,JSObject * obj)119 void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) {
120     auto holder = static_cast<BSONHolder*>(JS_GetPrivate(obj));
121 
122     if (!holder)
123         return;
124 
125     getScope(fop)->trackedDelete(holder);
126 }
127 
enumerate(JSContext * cx,JS::HandleObject obj,JS::AutoIdVector & properties,bool enumerableOnly)128 void BSONInfo::enumerate(JSContext* cx,
129                          JS::HandleObject obj,
130                          JS::AutoIdVector& properties,
131                          bool enumerableOnly) {
132     auto holder = getValidHolder(cx, obj);
133 
134     if (!holder)
135         return;
136 
137     BSONObjIterator i(holder->_obj);
138 
139     ObjectWrapper o(cx, obj);
140     JS::RootedValue val(cx);
141     JS::RootedId id(cx);
142 
143     while (i.more()) {
144         BSONElement e = i.next();
145 
146         // TODO: when we get heterogenous set lookup, switch to StringData
147         // rather than involving the temporary string
148         if (holder->_removed.find(e.fieldName()) != holder->_removed.end())
149             continue;
150 
151         ValueReader(cx, &val).fromStringData(e.fieldNameStringData());
152 
153         if (!JS_ValueToId(cx, val, &id))
154             uasserted(ErrorCodes::JSInterpreterFailure, "Failed to invoke JS_ValueToId");
155 
156         properties.append(id);
157     }
158 }
159 
setProperty(JSContext * cx,JS::HandleObject obj,JS::HandleId id,JS::MutableHandleValue vp,JS::ObjectOpResult & result)160 void BSONInfo::setProperty(JSContext* cx,
161                            JS::HandleObject obj,
162                            JS::HandleId id,
163                            JS::MutableHandleValue vp,
164                            JS::ObjectOpResult& result) {
165     auto holder = getValidHolder(cx, obj);
166 
167     if (holder) {
168         if (holder->_readOnly) {
169             uasserted(ErrorCodes::BadValue, "Read only object");
170             return;
171         }
172 
173         auto iter = holder->_removed.find(IdWrapper(cx, id).toString());
174 
175         if (iter != holder->_removed.end()) {
176             holder->_removed.erase(iter);
177         }
178 
179         holder->_altered = true;
180     }
181 
182     ObjectWrapper(cx, obj).defineProperty(id, vp, JSPROP_ENUMERATE);
183     result.succeed();
184 }
185 
delProperty(JSContext * cx,JS::HandleObject obj,JS::HandleId id,JS::ObjectOpResult & result)186 void BSONInfo::delProperty(JSContext* cx,
187                            JS::HandleObject obj,
188                            JS::HandleId id,
189                            JS::ObjectOpResult& result) {
190     auto holder = getValidHolder(cx, obj);
191 
192     if (holder) {
193         if (holder->_readOnly) {
194             uasserted(ErrorCodes::BadValue, "Read only object");
195             return;
196         }
197 
198         holder->_altered = true;
199 
200         JSStringWrapper jsstr;
201         holder->_removed[IdWrapper(cx, id).toStringData(&jsstr)] = true;
202     }
203 
204     result.succeed();
205 }
206 
resolve(JSContext * cx,JS::HandleObject obj,JS::HandleId id,bool * resolvedp)207 void BSONInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
208     auto holder = getValidHolder(cx, obj);
209 
210     *resolvedp = false;
211 
212     if (!holder) {
213         return;
214     }
215 
216     IdWrapper idw(cx, id);
217     JSStringWrapper jsstr;
218 
219     auto sname = idw.toStringData(&jsstr);
220 
221     if (!holder->_readOnly && holder->_removed.find(sname.toString()) != holder->_removed.end()) {
222         return;
223     }
224 
225     ObjectWrapper o(cx, obj);
226 
227     if (holder->_obj.hasField(sname)) {
228         auto elem = holder->_obj[sname];
229 
230         JS::RootedValue vp(cx);
231 
232         ValueReader(cx, &vp).fromBSONElement(elem, holder->getOwner(), holder->_readOnly);
233 
234         o.defineProperty(id, vp, JSPROP_ENUMERATE);
235 
236         if (!holder->_readOnly && (elem.type() == mongo::Object || elem.type() == mongo::Array)) {
237             // if accessing a subobject, we have no way to know if
238             // modifications are being made on writable objects
239 
240             holder->_altered = true;
241         }
242 
243         *resolvedp = true;
244     }
245 }
246 
originalBSON(JSContext * cx,JS::HandleObject obj)247 std::tuple<BSONObj*, bool> BSONInfo::originalBSON(JSContext* cx, JS::HandleObject obj) {
248     std::tuple<BSONObj*, bool> out(nullptr, false);
249 
250     if (auto holder = getValidHolder(cx, obj))
251         out = std::make_tuple(&holder->_obj, holder->_altered);
252 
253     return out;
254 }
255 
call(JSContext * cx,JS::CallArgs args)256 void BSONInfo::Functions::bsonWoCompare::call(JSContext* cx, JS::CallArgs args) {
257     if (args.length() != 2)
258         uasserted(ErrorCodes::BadValue, "bsonWoCompare needs 2 arguments");
259 
260     if (!args.get(0).isObject())
261         uasserted(ErrorCodes::BadValue, "first argument to bsonWoCompare must be an object");
262 
263     if (!args.get(1).isObject())
264         uasserted(ErrorCodes::BadValue, "second argument to bsonWoCompare must be an object");
265 
266     BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON();
267     BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON();
268 
269     args.rval().setInt32(firstObject.woCompare(secondObject));
270 }
271 
call(JSContext * cx,JS::CallArgs args)272 void BSONInfo::Functions::bsonBinaryEqual::call(JSContext* cx, JS::CallArgs args) {
273     if (args.length() != 2)
274         uasserted(ErrorCodes::BadValue, "bsonBinaryEqual needs 2 arguments");
275 
276     if (!args.get(0).isObject())
277         uasserted(ErrorCodes::BadValue, "first argument to bsonBinaryEqual must be an object");
278 
279     if (!args.get(1).isObject())
280         uasserted(ErrorCodes::BadValue, "second argument to bsonBinaryEqual must be an object");
281 
282     BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON();
283     BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON();
284 
285     args.rval().setBoolean(firstObject.binaryEqual(secondObject));
286 }
287 
postInstall(JSContext * cx,JS::HandleObject global,JS::HandleObject proto)288 void BSONInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
289     JS::RootedValue value(cx);
290     value.setBoolean(true);
291 
292     ObjectWrapper(cx, proto).defineProperty(InternedString::_bson, value, 0);
293 }
294 
295 }  // namespace mozjs
296 }  // namespace mongo
297