summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/mozjs/mongo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/scripting/mozjs/mongo.cpp')
-rw-r--r--src/mongo/scripting/mozjs/mongo.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp
new file mode 100644
index 00000000000..f1f69d0d39c
--- /dev/null
+++ b/src/mongo/scripting/mozjs/mongo.cpp
@@ -0,0 +1,565 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/mongo.h"
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/native_sasl_client_session.h"
+#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/client/sasl_client_session.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/scripting/mozjs/cursor.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec MongoBase::methods[13] = {
+ MONGO_ATTACH_JS_FUNCTION(auth),
+ MONGO_ATTACH_JS_FUNCTION(copyDatabaseWithSCRAM),
+ MONGO_ATTACH_JS_FUNCTION(cursorFromId),
+ MONGO_ATTACH_JS_FUNCTION(find),
+ MONGO_ATTACH_JS_FUNCTION(getClientRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(getServerRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(insert),
+ MONGO_ATTACH_JS_FUNCTION(logout),
+ MONGO_ATTACH_JS_FUNCTION(remove),
+ MONGO_ATTACH_JS_FUNCTION(runCommand),
+ MONGO_ATTACH_JS_FUNCTION(setClientRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(update),
+ JS_FS_END,
+};
+
+const char* const MongoBase::className = "Mongo";
+
+const JSFunctionSpec MongoExternalInfo::freeFunctions[2] = {
+ MONGO_ATTACH_JS_FUNCTION(load), JS_FS_END,
+};
+
+namespace {
+DBClientBase* getConnection(JS::CallArgs& args) {
+ return static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(args.thisv().toObjectOrNull()))
+ ->get();
+}
+
+void setCursor(JS::HandleObject target,
+ std::unique_ptr<DBClientCursor> cursor,
+ JS::CallArgs& args) {
+ auto client =
+ static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(args.thisv().toObjectOrNull()));
+
+ // Copy the client shared pointer to up the refcount
+ JS_SetPrivate(target, new CursorInfo::CursorHolder(std::move(cursor), *client));
+}
+} // namespace
+
+void MongoBase::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto conn = static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(obj));
+
+ if (conn) {
+ delete conn;
+ }
+}
+
+void MongoBase::Functions::runCommand(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 3)
+ uasserted(ErrorCodes::BadValue, "runCommand needs 3 args");
+
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "the database parameter to runCommand must be a string");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "the cmdObj parameter to runCommand must be an object");
+
+ if (!args.get(2).isNumber())
+ uasserted(ErrorCodes::BadValue, "the options parameter to runCommand must be a number");
+
+ auto conn = getConnection(args);
+
+ std::string database = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj cmdObj = ValueWriter(cx, args.get(1)).toBSON();
+
+ int queryOptions = ValueWriter(cx, args.get(2)).toInt32();
+ BSONObj cmdRes;
+ conn->runCommand(database, cmdObj, cmdRes, queryOptions);
+
+ // the returned object is not read only as some of our tests depend on modifying it.
+ ValueReader(cx, args.rval()).fromBSON(cmdRes, false /* read only */);
+}
+
+void MongoBase::Functions::find(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 7)
+ uasserted(ErrorCodes::BadValue, "find needs 7 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "needs to be an object");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj fields;
+ BSONObj q = ValueWriter(cx, args.get(1)).toBSON();
+
+ bool haveFields = false;
+
+ if (args.get(2).isObject()) {
+ size_t i = 0;
+
+ JS::RootedObject obj(cx, args.get(2).toObjectOrNull());
+
+ ObjectWrapper(cx, obj).enumerate([&i](jsid) { ++i; });
+
+ if (i > 0)
+ haveFields = true;
+ }
+
+ if (haveFields)
+ fields = ValueWriter(cx, args.get(2)).toBSON();
+
+ int nToReturn = ValueWriter(cx, args.get(3)).toInt32();
+ int nToSkip = ValueWriter(cx, args.get(4)).toInt32();
+ int batchSize = ValueWriter(cx, args.get(5)).toInt32();
+ int options = ValueWriter(cx, args.get(6)).toInt32();
+
+ std::unique_ptr<DBClientCursor> cursor(
+ conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize));
+ if (!cursor.get()) {
+ uasserted(ErrorCodes::InternalError, "error doing query: failed");
+ }
+
+ JS::RootedObject c(cx);
+ scope->getCursorProto().newInstance(&c);
+
+ setCursor(c, std::move(cursor), args);
+
+ args.rval().setObjectOrNull(c);
+}
+
+void MongoBase::Functions::insert(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 3)
+ uasserted(ErrorCodes::BadValue, "insert needs 3 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "attempted to insert a non-object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ int flags = ValueWriter(cx, args.get(2)).toInt32();
+
+ auto addId = [cx, scope](JS::HandleValue value) {
+ if (!value.isObject())
+ uasserted(ErrorCodes::BadValue, "attempted to insert a non-object type");
+
+ JS::RootedObject elementObj(cx, value.toObjectOrNull());
+
+ ObjectWrapper ele(cx, elementObj);
+
+ if (!ele.hasField("_id")) {
+ JS::RootedValue value(cx);
+ scope->getOidProto().newInstance(&value);
+ ele.setValue("_id", value);
+ }
+
+ return ValueWriter(cx, value).toBSON();
+ };
+
+ if (args.get(1).isObject() && JS_IsArrayObject(cx, args.get(1))) {
+ JS::RootedObject obj(cx, args.get(1).toObjectOrNull());
+ ObjectWrapper array(cx, obj);
+
+ std::vector<BSONObj> bos;
+
+ bool foundElement = false;
+
+ array.enumerate([&](JS::HandleId id) {
+ foundElement = true;
+
+ JS::RootedValue value(cx);
+ array.getValue(id, &value);
+
+ bos.push_back(addId(value));
+ });
+
+ if (!foundElement)
+ uasserted(ErrorCodes::BadValue, "attempted to insert an empty array");
+
+ conn->insert(ns, bos, flags);
+ } else {
+ conn->insert(ns, addId(args.get(1)));
+ }
+
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::remove(JSContext* cx, JS::CallArgs args) {
+ if (!(args.length() == 2 || args.length() == 3))
+ uasserted(ErrorCodes::BadValue, "remove needs 2 or 3 args");
+
+ if (!(args.get(1).isObject()))
+ uasserted(ErrorCodes::BadValue, "attempted to remove a non-object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj bson = ValueWriter(cx, args.get(1)).toBSON();
+
+ bool justOne = false;
+ if (args.length() > 2) {
+ justOne = args.get(2).toBoolean();
+ }
+
+ conn->remove(ns, bson, justOne);
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::update(JSContext* cx, JS::CallArgs args) {
+ if (args.length() < 3)
+ uasserted(ErrorCodes::BadValue, "update needs at least 3 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "1st param to update has to be an object");
+
+ if (!args.get(2).isObject())
+ uasserted(ErrorCodes::BadValue, "2nd param to update has to be an object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj q1 = ValueWriter(cx, args.get(1)).toBSON();
+ BSONObj o1 = ValueWriter(cx, args.get(2)).toBSON();
+
+ bool upsert = args.length() > 3 && args.get(3).isBoolean() && args.get(3).toBoolean();
+ bool multi = args.length() > 4 && args.get(4).isBoolean() && args.get(4).toBoolean();
+
+ conn->update(ns, q1, o1, upsert, multi);
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::auth(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+ if (!conn)
+ uasserted(ErrorCodes::BadValue, "no connection");
+
+ BSONObj params;
+ switch (args.length()) {
+ case 1:
+ params = ValueWriter(cx, args.get(0)).toBSON();
+ break;
+ case 3:
+ params = BSON(saslCommandMechanismFieldName
+ << "MONGODB-CR" << saslCommandUserDBFieldName
+ << ValueWriter(cx, args[0]).toString() << saslCommandUserFieldName
+ << ValueWriter(cx, args[1]).toString() << saslCommandPasswordFieldName
+ << ValueWriter(cx, args[2]).toString());
+ break;
+ default:
+ uasserted(ErrorCodes::BadValue, "mongoAuth takes 1 object or 3 string arguments");
+ }
+
+ conn->auth(params);
+
+ args.rval().setBoolean(true);
+}
+
+void MongoBase::Functions::logout(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "logout needs 1 arg");
+
+ BSONObj ret;
+
+ std::string db = ValueWriter(cx, args.get(0)).toString();
+
+ auto conn = getConnection(args);
+ if (conn) {
+ conn->logout(db, ret);
+ }
+
+ ValueReader(cx, args.rval()).fromBSON(ret, false);
+}
+
+void MongoBase::Functions::cursorFromId(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (!(args.length() == 2 || args.length() == 3))
+ uasserted(ErrorCodes::BadValue, "cursorFromId needs 2 or 3 args");
+
+ if (!scope->getNumberLongProto().instanceOf(args.get(1)))
+ uasserted(ErrorCodes::BadValue, "2nd arg must be a NumberLong");
+
+ if (!(args.get(2).isNumber() || args.get(2).isUndefined()))
+ uasserted(ErrorCodes::BadValue, "3rd arg must be a js Number");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ long long cursorId = NumberLongInfo::ToNumberLong(cx, args.get(1));
+
+ auto cursor = stdx::make_unique<DBClientCursor>(conn, ns, cursorId, 0, 0);
+
+ if (args.get(2).isNumber())
+ cursor->setBatchSize(ValueWriter(cx, args.get(2)).toInt32());
+
+ JS::RootedObject c(cx);
+ scope->getCursorProto().newInstance(&c);
+
+ setCursor(c, std::move(cursor), args);
+
+ args.rval().setObjectOrNull(c);
+}
+
+void MongoBase::Functions::copyDatabaseWithSCRAM(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (!conn)
+ uasserted(ErrorCodes::BadValue, "no connection");
+
+ if (args.length() != 5)
+ uasserted(ErrorCodes::BadValue, "copyDatabase needs 5 arg");
+
+ // copyDatabase(fromdb, todb, fromhost, username, password);
+ std::string fromDb = ValueWriter(cx, args.get(0)).toString();
+ std::string toDb = ValueWriter(cx, args.get(1)).toString();
+ std::string fromHost = ValueWriter(cx, args.get(2)).toString();
+ std::string user = ValueWriter(cx, args.get(3)).toString();
+ std::string password = ValueWriter(cx, args.get(4)).toString();
+
+ std::string hashedPwd = DBClientWithCommands::createPasswordDigest(user, password);
+
+ std::unique_ptr<SaslClientSession> session(new NativeSaslClientSession());
+
+ session->setParameter(SaslClientSession::parameterMechanism, "SCRAM-SHA-1");
+ session->setParameter(SaslClientSession::parameterUser, user);
+ session->setParameter(SaslClientSession::parameterPassword, hashedPwd);
+ session->initialize();
+
+ BSONObj saslFirstCommandPrefix =
+ BSON("copydbsaslstart" << 1 << "fromhost" << fromHost << "fromdb" << fromDb
+ << saslCommandMechanismFieldName << "SCRAM-SHA-1");
+
+ BSONObj saslFollowupCommandPrefix =
+ BSON("copydb" << 1 << "fromhost" << fromHost << "fromdb" << fromDb << "todb" << toDb);
+
+ BSONObj saslCommandPrefix = saslFirstCommandPrefix;
+ BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
+ bool isServerDone = false;
+
+ while (!session->isDone()) {
+ std::string payload;
+ BSONType type;
+
+ Status status = saslExtractPayload(inputObj, &payload, &type);
+ uassertStatusOK(status);
+
+ std::string responsePayload;
+ status = session->step(payload, &responsePayload);
+ uassertStatusOK(status);
+
+ BSONObjBuilder commandBuilder;
+
+ commandBuilder.appendElements(saslCommandPrefix);
+ commandBuilder.appendBinData(saslCommandPayloadFieldName,
+ static_cast<int>(responsePayload.size()),
+ BinDataGeneral,
+ responsePayload.c_str());
+ BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
+ if (!conversationId.eoo())
+ commandBuilder.append(conversationId);
+
+ BSONObj command = commandBuilder.obj();
+
+ bool ok = conn->runCommand("admin", command, inputObj);
+
+ ErrorCodes::Error code =
+ ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt());
+
+ if (!ok || code != ErrorCodes::OK) {
+ if (code == ErrorCodes::OK)
+ code = ErrorCodes::UnknownError;
+
+ ValueReader(cx, args.rval()).fromBSON(inputObj, true);
+ return;
+ }
+
+ isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
+ saslCommandPrefix = saslFollowupCommandPrefix;
+ }
+
+ if (!isServerDone) {
+ uasserted(ErrorCodes::InternalError, "copydb client finished before server.");
+ }
+
+ ValueReader(cx, args.rval()).fromBSON(inputObj, true);
+}
+
+void MongoBase::Functions::getClientRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "getClientRPCProtocols takes no args");
+
+ auto clientRPCProtocols = rpc::toString(conn->getClientRPCProtocols());
+ uassertStatusOK(clientRPCProtocols);
+
+ auto protoStr = clientRPCProtocols.getValue().toString();
+
+ ValueReader(cx, args.rval()).fromStringData(protoStr);
+}
+
+void MongoBase::Functions::setClientRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "setClientRPCProtocols needs 1 arg");
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "first argument to setClientRPCProtocols must be a string");
+
+ std::string rpcProtosStr = ValueWriter(cx, args.get(0)).toString();
+
+ auto clientRPCProtocols = rpc::parseProtocolSet(rpcProtosStr);
+ uassertStatusOK(clientRPCProtocols);
+
+ conn->setClientRPCProtocols(clientRPCProtocols.getValue());
+
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::getServerRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "getServerRPCProtocols takes no args");
+
+ auto serverRPCProtocols = rpc::toString(conn->getServerRPCProtocols());
+ uassertStatusOK(serverRPCProtocols);
+
+ auto protoStr = serverRPCProtocols.getValue().toString();
+
+ ValueReader(cx, args.rval()).fromStringData(protoStr);
+}
+
+void MongoLocalInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "local Mongo constructor takes no args");
+
+ std::unique_ptr<DBClientBase> conn;
+
+ conn.reset(createDirectClient(scope->getOpContext()));
+
+ JS::RootedObject thisv(cx);
+ scope->getMongoLocalProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS_SetPrivate(thisv, new std::shared_ptr<DBClientBase>(conn.release()));
+
+ o.setBoolean("slaveOk", false);
+ o.setString("host", "EMBEDDED");
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ std::string host("127.0.0.1");
+
+ if (args.length() > 0 && args.get(0).isString()) {
+ host = ValueWriter(cx, args.get(0)).toString();
+ }
+
+ auto statusWithHost = ConnectionString::parse(host);
+ uassertStatusOK(statusWithHost);
+
+ const ConnectionString cs(statusWithHost.getValue());
+
+ std::string errmsg;
+ std::unique_ptr<DBClientBase> conn(cs.connect(errmsg));
+
+ if (!conn.get()) {
+ uasserted(ErrorCodes::InternalError, errmsg);
+ }
+
+ JS::RootedObject thisv(cx);
+ scope->getMongoExternalProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS_SetPrivate(thisv, new std::shared_ptr<DBClientBase>(conn.release()));
+
+ o.setBoolean("slaveOk", false);
+ o.setString("host", host);
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+void MongoExternalInfo::Functions::load(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ for (unsigned i = 0; i < args.length(); ++i) {
+ std::string filename = ValueWriter(cx, args.get(i)).toString();
+
+ if (!scope->execFile(filename, false, true)) {
+ uasserted(ErrorCodes::BadValue, std::string("error loading js file: ") + filename);
+ }
+ }
+
+ args.rval().setBoolean(true);
+}
+
+} // namespace mozjs
+} // namespace mongo