// v8_db.cpp
/* Copyright 2014 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 .
*
* 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/scripting/v8-3.25_db.h"
#include
#include
#include
#include
#include "mongo/base/init.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/client/syncclusterconnection.h"
#include "mongo/db/namespace_string.h"
#include "mongo/s/d_state.h"
#include "mongo/scripting/engine_v8-3.25.h"
#include "mongo/scripting/v8-3.25_utils.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/base64.h"
#include "mongo/util/hex.h"
#include "mongo/util/text.h"
using namespace std;
using boost::scoped_array;
using boost::shared_ptr;
namespace mongo {
namespace {
std::vector _mongoPrototypeManipulators;
bool _mongoPrototypeManipulatorsFrozen = false;
MONGO_INITIALIZER(V8MongoPrototypeManipulatorRegistry)(InitializerContext* context) {
return Status::OK();
}
MONGO_INITIALIZER_WITH_PREREQUISITES(V8MongoPrototypeManipulatorRegistrationDone,
("V8MongoPrototypeManipulatorRegistry"))
(InitializerContext* context) {
_mongoPrototypeManipulatorsFrozen = true;
return Status::OK();
}
} // namespace
void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator) {
fassert(16467, !_mongoPrototypeManipulatorsFrozen);
_mongoPrototypeManipulators.push_back(manipulator);
}
static v8::Local newInstance(v8::Local f,
const v8::FunctionCallbackInfo& args) {
// need to translate arguments into an array
v8::EscapableHandleScope handle_scope(args.GetIsolate());
const int argc = args.Length();
static const int MAX_ARGC = 24;
uassert(16858, "Too many arguments. Max is 24", argc <= MAX_ARGC);
// TODO SERVER-8016: properly allocate handles on the stack
v8::Local argv[MAX_ARGC];
for (int i = 0; i < argc; ++i) {
argv[i] = args[i];
}
return handle_scope.Escape(f->NewInstance(argc, argv));
}
v8::Local getInternalCursorFunctionTemplate(V8Scope* scope) {
v8::Local ic = scope->createV8Function(internalCursorCons);
ic->InstanceTemplate()->SetInternalFieldCount(1);
v8::Local icproto = ic->PrototypeTemplate();
scope->injectV8Method("next", internalCursorNext, icproto);
scope->injectV8Method("hasNext", internalCursorHasNext, icproto);
scope->injectV8Method("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto);
scope->injectV8Method("readOnly", internalCursorReadOnly, icproto);
return ic;
}
v8::Local getMongoFunctionTemplate(V8Scope* scope, bool local) {
v8::Local mongo;
if (local)
mongo = scope->createV8Function(mongoConsLocal);
else
mongo = scope->createV8Function(mongoConsExternal);
mongo->InstanceTemplate()->SetInternalFieldCount(1);
v8::Local proto = mongo->PrototypeTemplate();
scope->injectV8Method("find", mongoFind, proto);
scope->injectV8Method("insert", mongoInsert, proto);
scope->injectV8Method("remove", mongoRemove, proto);
scope->injectV8Method("update", mongoUpdate, proto);
scope->injectV8Method("auth", mongoAuth, proto);
scope->injectV8Method("logout", mongoLogout, proto);
scope->injectV8Method("cursorFromId", mongoCursorFromId, proto);
fassert(16468, _mongoPrototypeManipulatorsFrozen);
for (size_t i = 0; i < _mongoPrototypeManipulators.size(); ++i)
_mongoPrototypeManipulators[i](scope, mongo);
return mongo;
}
v8::Local mongoConsExternal(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
string host = "127.0.0.1";
if (args.Length() > 0 && args[0]->IsString()) {
v8::String::Utf8Value utf(args[0]);
host = string(*utf);
}
// only allow function template to be used by a constructor
uassert(16859, "Mongo function is only usable as a constructor", args.IsConstructCall());
verify(scope->MongoFT()->HasInstance(args.This()));
string errmsg;
ConnectionString cs = ConnectionString::parse(host, errmsg);
if (!cs.isValid()) {
return v8AssertionException(errmsg);
}
DBClientBase* conn;
conn = cs.connect(errmsg);
if (!conn) {
return v8AssertionException(errmsg);
}
v8::Local connHandle =
scope->dbClientBaseTracker.track(scope->getIsolate(), args.This(), conn);
ScriptEngine::runConnectCallback(*conn);
args.This()->SetInternalField(0, connHandle);
args.This()->ForceSet(scope->v8StringData("slaveOk"),
v8::Boolean::New(scope->getIsolate(), false));
args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData(host));
return v8::Undefined(scope->getIsolate());
}
v8::Local mongoConsLocal(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 0, "local Mongo constructor takes no args")
// only allow function template to be used by a constructor
uassert(16860, "Mongo function is only usable as a constructor", args.IsConstructCall());
verify(scope->MongoFT()->HasInstance(args.This()));
DBClientBase* conn = createDirectClient(scope->getOpContext());
v8::Local connHandle =
scope->dbClientBaseTracker.track(scope->getIsolate(), args.This(), conn);
args.This()->SetInternalField(0, connHandle);
args.This()->ForceSet(scope->v8StringData("slaveOk"),
v8::Boolean::New(scope->getIsolate(), false));
args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData("EMBEDDED"));
return v8::Undefined(scope->getIsolate());
}
boost::shared_ptr getConnection(
V8Scope* scope, const v8::FunctionCallbackInfo& args) {
verify(scope->MongoFT()->HasInstance(args.This()));
verify(args.This()->InternalFieldCount() == 1);
v8::Local c = v8::Local::Cast(args.This()->GetInternalField(0));
boost::shared_ptr* conn =
static_cast*>(c->Value());
massert(16667, "Unable to get db client connection", conn && conn->get());
return *conn;
}
/**
* JavaScript binding for Mongo.prototype.find(namespace, query, fields, limit, skip)
*/
v8::Local mongoFind(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 7, "find needs 7 args")
argumentCheck(args[1]->IsObject(), "needs to be an object")
boost::shared_ptr conn = getConnection(scope, args);
const string ns = toSTLString(args[0]);
BSONObj fields;
BSONObj q = scope->v8ToMongo(args[1]->ToObject());
bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0;
if (haveFields)
fields = scope->v8ToMongo(args[2]->ToObject());
boost::shared_ptr cursor;
int nToReturn = args[3]->Int32Value();
int nToSkip = args[4]->Int32Value();
int batchSize = args[5]->Int32Value();
int options = args[6]->Int32Value();
cursor =
conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize);
if (!cursor.get()) {
return v8AssertionException("error doing query: failed");
}
v8::Local cons = scope->InternalCursorFT()->GetFunction();
v8::Local c = cons->NewInstance();
c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get()));
scope->dbConnectionAndCursor.track(
scope->getIsolate(), c, new V8Scope::DBConnectionAndCursor(conn, cursor));
return c;
}
v8::Local mongoCursorFromId(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 2 || args.Length() == 3, "cursorFromId needs 2 or 3 args")
argumentCheck(scope->NumberLongFT()->HasInstance(args[1]), "2nd arg must be a NumberLong")
argumentCheck(args[2]->IsUndefined() || args[2]->IsNumber(),
"3rd arg must be a js Number")
boost::shared_ptr conn = getConnection(scope, args);
const string ns = toSTLString(args[0]);
long long cursorId = numberLongVal(scope, args[1]->ToObject());
boost::shared_ptr cursor(
new DBClientCursor(conn.get(), ns, cursorId, 0, 0));
if (!args[2]->IsUndefined())
cursor->setBatchSize(args[2]->Int32Value());
v8::Local cons = scope->InternalCursorFT()->GetFunction();
v8::Local c = cons->NewInstance();
c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get()));
scope->dbConnectionAndCursor.track(
scope->getIsolate(), c, new V8Scope::DBConnectionAndCursor(conn, cursor));
return c;
}
v8::Local mongoInsert(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 3, "insert needs 3 args")
argumentCheck(args[1]->IsObject(), "attempted to insert a non-object")
verify(scope->MongoFT()->HasInstance(args.This()));
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
boost::shared_ptr conn = getConnection(scope, args);
const string ns = toSTLString(args[0]);
v8::Local flags = args[2]->ToInteger();
if (args[1]->IsArray()) {
v8::Local arr = v8::Local::Cast(args[1]);
vector bos;
uint32_t len = arr->Length();
argumentCheck(len > 0, "attempted to insert an empty array")
for (uint32_t i = 0; i < len; i++) {
v8::Local el = arr->CloneElementAt(i);
argumentCheck(!el.IsEmpty(), "attempted to insert an array of non-object types")
// Set ID on the element if necessary
if (!el->Has(scope->v8StringData("_id"))) {
v8::Local argv[1];
el->ForceSet(scope->v8StringData("_id"),
scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv));
}
bos.push_back(scope->v8ToMongo(el));
}
conn->insert(ns, bos, flags->Int32Value());
} else {
v8::Local in = args[1]->ToObject();
if (!in->Has(scope->v8StringData("_id"))) {
v8::Local argv[1];
in->ForceSet(scope->v8StringData("_id"),
scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv));
}
BSONObj o = scope->v8ToMongo(in);
conn->insert(ns, o);
}
return v8::Undefined(scope->getIsolate());
}
v8::Local mongoRemove(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args")
argumentCheck(args[1]->IsObject(), "attempted to remove a non-object")
verify(scope->MongoFT()->HasInstance(args.This()));
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
boost::shared_ptr conn = getConnection(scope, args);
const string ns = toSTLString(args[0]);
v8::Local in = args[1]->ToObject();
BSONObj o = scope->v8ToMongo(in);
bool justOne = false;
if (args.Length() > 2) {
justOne = args[2]->BooleanValue();
}
conn->remove(ns, o, justOne);
return v8::Undefined(scope->getIsolate());
}
v8::Local mongoUpdate(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() >= 3, "update needs at least 3 args")
argumentCheck(args[1]->IsObject(), "1st param to update has to be an object")
argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object")
verify(scope->MongoFT()->HasInstance(args.This()));
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
boost::shared_ptr conn = getConnection(scope, args);
const string ns = toSTLString(args[0]);
v8::Local q = args[1]->ToObject();
v8::Local o = args[2]->ToObject();
bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value();
bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value();
BSONObj q1 = scope->v8ToMongo(q);
BSONObj o1 = scope->v8ToMongo(o);
conn->update(ns, q1, o1, upsert, multi);
return v8::Undefined(scope->getIsolate());
}
v8::Local mongoAuth(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
boost::shared_ptr conn = getConnection(scope, args);
if (NULL == conn)
return v8AssertionException("no connection");
BSONObj params;
switch (args.Length()) {
case 1:
params = scope->v8ToMongo(args[0]->ToObject());
break;
case 3:
params = BSON(saslCommandMechanismFieldName
<< "MONGODB-CR" << saslCommandUserDBFieldName << toSTLString(args[0])
<< saslCommandUserFieldName << toSTLString(args[1])
<< saslCommandPasswordFieldName << toSTLString(args[2]));
break;
default:
return v8AssertionException("mongoAuth takes 1 object or 3 string arguments");
}
try {
conn->auth(params);
} catch (const DBException& ex) {
return v8AssertionException(ex.toString());
}
return v8::Boolean::New(scope->getIsolate(), true);
}
v8::Local mongoLogout(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 1, "logout needs 1 arg") boost::shared_ptr conn =
getConnection(scope, args);
const string db = toSTLString(args[0]);
BSONObj ret;
conn->logout(db, ret);
return scope->mongoToLZV8(ret, false);
}
/**
* get cursor from v8 argument
*/
mongo::DBClientCursor* getCursor(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
verify(scope->InternalCursorFT()->HasInstance(args.This()));
verify(args.This()->InternalFieldCount() == 1);
v8::Local c = v8::Local::Cast(args.This()->GetInternalField(0));
mongo::DBClientCursor* cursor = static_cast(c->Value());
return cursor;
}
v8::Local internalCursorCons(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
return v8::Undefined(scope->getIsolate());
}
/**
* cursor.next()
*/
v8::Local internalCursorNext(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
mongo::DBClientCursor* cursor = getCursor(scope, args);
if (!cursor)
return v8::Undefined(scope->getIsolate());
BSONObj o = cursor->next();
bool ro = false;
if (args.This()->Has(v8::String::NewFromUtf8(scope->getIsolate(), "_ro")))
ro = args.This()->Get(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"))->BooleanValue();
return scope->mongoToLZV8(o, ro);
}
/**
* cursor.hasNext()
*/
v8::Local internalCursorHasNext(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
mongo::DBClientCursor* cursor = getCursor(scope, args);
if (!cursor)
return v8::Boolean::New(scope->getIsolate(), false);
return v8::Boolean::New(scope->getIsolate(), cursor->more());
}
/**
* cursor.objsLeftInBatch()
*/
v8::Local internalCursorObjsLeftInBatch(
V8Scope* scope, const v8::FunctionCallbackInfo& args) {
mongo::DBClientCursor* cursor = getCursor(scope, args);
if (!cursor)
return v8::Number::New(scope->getIsolate(), 0.0);
return v8::Number::New(scope->getIsolate(), static_cast(cursor->objsLeftInBatch()));
}
/**
* cursor.readOnly()
*/
v8::Local internalCursorReadOnly(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
verify(scope->InternalCursorFT()->HasInstance(args.This()));
v8::Local cursor = args.This();
cursor->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"),
v8::Boolean::New(scope->getIsolate(), true));
return cursor;
}
v8::Local dbInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->DBFT()->GetFunction();
return newInstance(f, args);
}
verify(scope->DBFT()->HasInstance(args.This()));
argumentCheck(args.Length() == 2, "db constructor requires 2 arguments")
args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]);
args.This()->ForceSet(scope->v8StringData("_name"), args[1]);
for (int i = 0; i < args.Length(); i++) {
argumentCheck(!args[i]->IsUndefined(), "db initializer called with undefined argument")
}
string dbName = toSTLString(args[1]);
if (!NamespaceString::validDBName(dbName)) {
return v8AssertionException(str::stream() << "[" << dbName
<< "] is not a valid database name");
}
return v8::Undefined(scope->getIsolate());
}
v8::Local collectionInit(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->DBCollectionFT()->GetFunction();
return newInstance(f, args);
}
verify(scope->DBCollectionFT()->HasInstance(args.This()));
argumentCheck(args.Length() == 4, "collection constructor requires 4 arguments")
for (int i = 0; i < args.Length(); i++) {
argumentCheck(!args[i]->IsUndefined(),
"collection constructor called with undefined argument")
}
args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]);
args.This()->ForceSet(scope->v8StringData("_db"), args[1]);
args.This()->ForceSet(scope->v8StringData("_shortName"), args[2]);
args.This()->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"), args[3]);
if (haveLocalShardingInfo(toSTLString(args[3]))) {
return v8AssertionException("can't use sharded collection from db.eval");
}
return v8::Undefined(scope->getIsolate());
}
v8::Local dbQueryInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->DBQueryFT()->GetFunction();
return newInstance(f, args);
}
verify(scope->DBQueryFT()->HasInstance(args.This()));
argumentCheck(args.Length() >= 4, "dbQuery constructor requires at least 4 arguments")
v8::Local t = args.This();
t->ForceSet(scope->v8StringData("_mongo"), args[0]);
t->ForceSet(scope->v8StringData("_db"), args[1]);
t->ForceSet(scope->v8StringData("_collection"), args[2]);
t->ForceSet(scope->v8StringData("_ns"), args[3]);
if (args.Length() > 4 && args[4]->IsObject())
t->ForceSet(scope->v8StringData("_query"), args[4]);
else
t->ForceSet(scope->v8StringData("_query"), v8::Object::New(scope->getIsolate()));
if (args.Length() > 5 && args[5]->IsObject())
t->ForceSet(scope->v8StringData("_fields"), args[5]);
else
t->ForceSet(scope->v8StringData("_fields"), v8::Null(scope->getIsolate()));
if (args.Length() > 6 && args[6]->IsNumber())
t->ForceSet(scope->v8StringData("_limit"), args[6]);
else
t->ForceSet(scope->v8StringData("_limit"), v8::Number::New(scope->getIsolate(), 0));
if (args.Length() > 7 && args[7]->IsNumber())
t->ForceSet(scope->v8StringData("_skip"), args[7]);
else
t->ForceSet(scope->v8StringData("_skip"), v8::Number::New(scope->getIsolate(), 0));
if (args.Length() > 8 && args[8]->IsNumber())
t->ForceSet(scope->v8StringData("_batchSize"), args[8]);
else
t->ForceSet(scope->v8StringData("_batchSize"), v8::Number::New(scope->getIsolate(), 0));
if (args.Length() > 9 && args[9]->IsNumber())
t->ForceSet(scope->v8StringData("_options"), args[9]);
else
t->ForceSet(scope->v8StringData("_options"), v8::Number::New(scope->getIsolate(), 0));
t->ForceSet(scope->v8StringData("_cursor"), v8::Null(scope->getIsolate()));
t->ForceSet(scope->v8StringData("_numReturned"), v8::Number::New(scope->getIsolate(), 0));
t->ForceSet(scope->v8StringData("_special"), v8::Boolean::New(scope->getIsolate(), false));
return v8::Undefined(scope->getIsolate());
}
void collectionSetter(v8::Local name,
v8::Local value,
const v8::PropertyCallbackInfo& info) {
v8::Local val;
v8::ReturnValue result = info.GetReturnValue();
result.Set(val);
try {
V8Scope* scope = getScope(info.GetIsolate());
// Both DB and Collection objects use this setter
verify(scope->DBCollectionFT()->HasInstance(info.This()) ||
scope->DBFT()->HasInstance(info.This()));
// a collection name cannot be overwritten by a variable
string sname = toSTLString(name);
if (sname.length() == 0 || sname[0] == '_') {
// if starts with '_' we allow overwrite
return;
}
// dont set
result.Set(value);
} catch (const DBException& dbEx) {
result.Set(v8AssertionException(dbEx.toString()));
} catch (...) {
result.Set(v8AssertionException("unknown error in collationSetter"));
}
}
void collectionGetter(v8::Local name, const v8::PropertyCallbackInfo& info) {
v8::Local val;
v8::ReturnValue result = info.GetReturnValue();
result.Set(val);
try {
V8Scope* scope = getScope(info.GetIsolate());
// Both DB and Collection objects use this getter
verify(scope->DBCollectionFT()->HasInstance(info.This()) ||
scope->DBFT()->HasInstance(info.This()));
v8::TryCatch tryCatch;
// first look in prototype, may be a function
v8::Local real = info.This()->GetPrototype()->ToObject()->Get(name);
if (!real->IsUndefined()) {
result.Set(real);
return;
}
// 2nd look into real values, may be cached collection object
string sname = toSTLString(name);
if (info.This()->HasRealNamedProperty(name)) {
v8::Local prop = info.This()->GetRealNamedProperty(name);
if (prop->IsObject() &&
prop->ToObject()->HasRealNamedProperty(
v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))) {
// need to check every time that the collection did not get sharded
if (haveLocalShardingInfo(toSTLString(prop->ToObject()->GetRealNamedProperty(
v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))))) {
result.Set(v8AssertionException("can't use sharded collection from db.eval"));
return;
}
}
result.Set(prop);
return;
} else if (sname.length() == 0 || sname[0] == '_') {
// if starts with '_' we dont return collection, one must use getCollection()
return;
}
// no hit, create new collection
v8::Local getCollection = info.This()->GetPrototype()->ToObject()->Get(
v8::String::NewFromUtf8(scope->getIsolate(), "getCollection"));
if (!getCollection->IsFunction()) {
result.Set(v8AssertionException("getCollection is not a function"));
return;
}
v8::Local f = getCollection.As();
v8::Local argv[1];
argv[0] = name;
v8::Local coll = f->Call(info.This(), 1, argv);
if (coll.IsEmpty()) {
result.Set(tryCatch.ReThrow());
return;
}
uassert(16861,
"getCollection returned something other than a collection",
scope->DBCollectionFT()->HasInstance(coll));
// cache collection for reuse, don't enumerate
info.This()->ForceSet(name, coll, v8::DontEnum);
result.Set(coll);
} catch (const DBException& dbEx) {
result.Set(v8AssertionException(dbEx.toString()));
} catch (...) {
result.Set(v8AssertionException("unknown error in collectionGetter"));
}
}
void dbQueryIndexAccess(unsigned int index, const v8::PropertyCallbackInfo& info) {
v8::Local val;
v8::ReturnValue result = info.GetReturnValue();
result.Set(val);
try {
V8Scope* scope = getScope(info.GetIsolate());
verify(scope->DBQueryFT()->HasInstance(info.This()));
v8::Local arrayAccess = info.This()->GetPrototype()->ToObject()->Get(
v8::String::NewFromUtf8(scope->getIsolate(), "arrayAccess"));
massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction());
v8::Local f = arrayAccess.As();
v8::Local argv[1];
argv[0] = v8::Number::New(scope->getIsolate(), index);
result.Set(f->Call(info.This(), 1, argv));
} catch (const DBException& dbEx) {
result.Set(v8AssertionException(dbEx.toString()));
} catch (...) {
result.Set(v8AssertionException("unknown error in dbQueryIndexAccess"));
}
}
v8::Local objectIdInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->ObjectIdFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->ObjectIdFT()->HasInstance(it));
OID oid;
if (args.Length() == 0) {
oid.init();
} else {
string s = toSTLString(args[0]);
try {
Scope::validateObjectIdString(s);
} catch (const MsgAssertionException& m) {
return v8AssertionException(m.toString());
}
oid.init(s);
}
it->ForceSet(scope->v8StringData("str"),
v8::String::NewFromUtf8(scope->getIsolate(), oid.toString().c_str()));
return it;
}
v8::Local dbRefInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->DBRefFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->DBRefFT()->HasInstance(it));
argumentCheck(args.Length() >= 2 && args.Length() <= 3, "DBRef needs 2 or 3 arguments")
argumentCheck(args[0]->IsString(), "DBRef 1st parameter must be a string")
it->ForceSet(scope->v8StringData("$ref"), args[0]);
it->ForceSet(scope->v8StringData("$id"), args[1]);
if (args.Length() == 3) {
argumentCheck(args[2]->IsString(), "DBRef 3rd parameter must be a string")
it->ForceSet(scope->v8StringData("$db"), args[2]);
}
return it;
}
v8::Local dbPointerInit(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->DBPointerFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->DBPointerFT()->HasInstance(it));
argumentCheck(args.Length() == 2, "DBPointer needs 2 arguments")
argumentCheck(args[0]->IsString(), "DBPointer 1st parameter must be a string")
argumentCheck(scope->ObjectIdFT()->HasInstance(args[1]),
"DBPointer 2nd parameter must be an ObjectId")
it->ForceSet(scope->v8StringData("ns"), args[0]);
it->ForceSet(scope->v8StringData("id"), args[1]);
return it;
}
v8::Local dbTimestampInit(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->TimestampFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->TimestampFT()->HasInstance(it));
if (args.Length() == 0) {
it->ForceSet(scope->v8StringData("t"), v8::Number::New(scope->getIsolate(), 0));
it->ForceSet(scope->v8StringData("i"), v8::Number::New(scope->getIsolate(), 0));
} else if (args.Length() == 2) {
if (!args[0]->IsNumber()) {
return v8AssertionException("Timestamp time must be a number");
}
if (!args[1]->IsNumber()) {
return v8AssertionException("Timestamp increment must be a number");
}
int64_t t = args[0]->IntegerValue();
int64_t largestVal = int64_t(OpTime::max().getSecs());
if (t > largestVal)
return v8AssertionException(str::stream() << "The first argument must be in seconds; "
<< t << " is too large (max " << largestVal
<< ")");
it->ForceSet(scope->v8StringData("t"), args[0]);
it->ForceSet(scope->v8StringData("i"), args[1]);
} else {
return v8AssertionException("Timestamp needs 0 or 2 arguments");
}
return it;
}
v8::Local binDataInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->BinDataFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->BinDataFT()->HasInstance(it));
argumentCheck(args.Length() == 2, "BinData takes 2 arguments -- BinData(subtype,data)");
// 2 args: type, base64 string
v8::Local type = args[0];
if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) {
return v8AssertionException(
"BinData subtype must be a Number between 0 and 255 inclusive)");
}
v8::String::Utf8Value utf(args[1]);
// uassert if invalid base64 string
string tmpBase64 = base64::decode(*utf);
// length property stores the decoded length
it->ForceSet(scope->v8StringData("len"),
v8::Number::New(scope->getIsolate(), tmpBase64.length()),
v8::ReadOnly);
it->ForceSet(scope->v8StringData("type"), type, v8::ReadOnly);
it->SetInternalField(0, args[1]);
return it;
}
v8::Local binDataToString(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
verify(scope->BinDataFT()->HasInstance(it));
int type = it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "type"))->Int32Value();
stringstream ss;
verify(it->InternalFieldCount() == 1);
ss << "BinData(" << type << ",\"" << toSTLString(it->GetInternalField(0)) << "\")";
return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str());
}
v8::Local binDataToBase64(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
verify(scope->BinDataFT()->HasInstance(it));
verify(it->InternalFieldCount() == 1);
return it->GetInternalField(0);
}
v8::Local binDataToHex(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
verify(scope->BinDataFT()->HasInstance(it));
verify(it->InternalFieldCount() == 1);
string data = base64::decode(toSTLString(it->GetInternalField(0)));
stringstream ss;
ss.setf(ios_base::hex, ios_base::basefield);
ss.fill('0');
ss.setf(ios_base::right, ios_base::adjustfield);
for (std::string::iterator it = data.begin(); it != data.end(); ++it) {
unsigned v = (unsigned char)*it;
ss << setw(2) << v;
}
return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str());
}
static v8::Local hexToBinData(V8Scope* scope, int type, string hexstr) {
// SERVER-9686: This function does not correctly check to make sure hexstr is actually made
// up of valid hex digits, and fails in the hex utility functions
int len = hexstr.length() / 2;
scoped_array data(new char[len]);
const char* src = hexstr.c_str();
for (int i = 0; i < len; i++) {
data[i] = fromHex(src + i * 2);
}
string encoded = base64::encode(data.get(), len);
v8::Local argv[2];
argv[0] = v8::Number::New(scope->getIsolate(), type);
argv[1] = v8::String::NewFromUtf8(scope->getIsolate(), encoded.c_str());
return scope->BinDataFT()->GetFunction()->NewInstance(2, argv);
}
v8::Local uuidInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 1, "UUID needs 1 argument") v8::String::Utf8Value utf(args[0]);
argumentCheck(utf.length() == 32,
"UUID string must have 32 characters") return hexToBinData(scope, bdtUUID, *utf);
}
v8::Local md5Init(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 1, "MD5 needs 1 argument") v8::String::Utf8Value utf(args[0]);
argumentCheck(utf.length() == 32,
"MD5 string must have 32 characters") return hexToBinData(scope, MD5Type, *utf);
}
v8::Local hexDataInit(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 2, "HexData needs 2 arguments") v8::Local type =
args[0];
if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) {
return v8AssertionException("HexData subtype must be a Number between 0 and 255 inclusive");
}
v8::String::Utf8Value utf(args[1]);
return hexToBinData(scope, type->Int32Value(), *utf);
}
v8::Local numberLongInit(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->NumberLongFT()->GetFunction();
return newInstance(f, args);
}
argumentCheck(args.Length() == 0 || args.Length() == 1 || args.Length() == 3,
"NumberLong needs 0, 1 or 3 arguments")
v8::Local it = args.This();
verify(scope->NumberLongFT()->HasInstance(it));
if (args.Length() == 0) {
it->ForceSet(scope->v8StringData("floatApprox"), v8::Number::New(scope->getIsolate(), 0));
} else if (args.Length() == 1) {
if (args[0]->IsNumber()) {
it->ForceSet(scope->v8StringData("floatApprox"), args[0]);
} else {
v8::String::Utf8Value data(args[0]);
string num = *data;
const char* numStr = num.c_str();
long long n;
try {
n = parseLL(numStr);
} catch (const AssertionException&) {
return v8AssertionException(string("could not convert \"") + num +
"\" to NumberLong");
}
unsigned long long val = n;
// values above 2^53 are not accurately represented in JS
if ((long long)val == (long long)(double)(long long)(val) &&
val < 9007199254740992ULL) {
it->ForceSet(scope->v8StringData("floatApprox"),
v8::Number::New(scope->getIsolate(), (double)(long long)(val)));
} else {
it->ForceSet(scope->v8StringData("floatApprox"),
v8::Number::New(scope->getIsolate(), (double)(long long)(val)));
it->ForceSet(scope->v8StringData("top"),
v8::Integer::New(scope->getIsolate(), val >> 32));
it->ForceSet(scope->v8StringData("bottom"),
v8::Integer::New(scope->getIsolate(),
(unsigned long)(val & 0x00000000ffffffff)));
}
}
} else {
it->ForceSet(scope->v8StringData("floatApprox"), args[0]->ToNumber());
it->ForceSet(scope->v8StringData("top"), args[1]->ToUint32());
it->ForceSet(scope->v8StringData("bottom"), args[2]->ToUint32());
}
return it;
}
long long numberLongVal(V8Scope* scope, const v8::Local& it) {
verify(scope->NumberLongFT()->HasInstance(it));
if (!it->Has(v8::String::NewFromUtf8(scope->getIsolate(), "top")))
return (long long)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "floatApprox"))
->NumberValue());
return (long long)((unsigned long long)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(),
"top"))
->ToInt32()
->Value())
<< 32) +
(unsigned)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "bottom"))
->ToInt32()
->Value());
}
v8::Local numberLongValueOf(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
long long val = numberLongVal(scope, it);
return v8::Number::New(scope->getIsolate(), double(val));
}
v8::Local numberLongToNumber(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
return numberLongValueOf(scope, args);
}
v8::Local numberLongToString(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
stringstream ss;
long long val = numberLongVal(scope, it);
const long long limit = 2LL << 30;
if (val <= -limit || limit <= val)
ss << "NumberLong(\"" << val << "\")";
else
ss << "NumberLong(" << val << ")";
string ret = ss.str();
return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str());
}
v8::Local numberIntInit(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
if (!args.IsConstructCall()) {
v8::Local f = scope->NumberIntFT()->GetFunction();
return newInstance(f, args);
}
v8::Local it = args.This();
verify(scope->NumberIntFT()->HasInstance(it));
argumentCheck(args.Length() == 0 || args.Length() == 1,
"NumberInt needs 0 or 1 arguments") if (args.Length() == 0) {
it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"),
v8::Number::New(scope->getIsolate(), 0));
}
else if (args.Length() == 1) {
it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"),
args[0]->ToInt32());
}
return it;
}
int numberIntVal(V8Scope* scope, const v8::Local& it) {
verify(scope->NumberIntFT()->HasInstance(it));
v8::Local value =
it->GetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"));
verify(!value.IsEmpty());
return value->Int32Value();
}
v8::Local numberIntValueOf(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
return v8::Integer::New(scope->getIsolate(), numberIntVal(scope, it));
}
v8::Local numberIntToNumber(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
return numberIntValueOf(scope, args);
}
v8::Local numberIntToString(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
v8::Local it = args.This();
int val = numberIntVal(scope, it);
string ret = str::stream() << "NumberInt(" << val << ")";
return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str());
}
v8::Local v8ObjectInvalidForStorage(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 1, "invalidForStorage needs 1 argument") if (args[0]->IsNull()) {
return v8::Null(scope->getIsolate());
}
argumentCheck(args[0]->IsObject(), "argument to invalidForStorage has to be an object")
Status validForStorage = scope->v8ToMongo(args[0]->ToObject()).storageValid(true);
if (validForStorage.isOK()) {
return v8::Null(scope->getIsolate());
}
std::string errmsg = str::stream() << validForStorage.codeString() << ": "
<< validForStorage.reason();
return v8::String::NewFromUtf8(scope->getIsolate(), errmsg.c_str());
}
v8::Local bsonsize(V8Scope* scope, const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 1, "bsonsize needs 1 argument") if (args[0]->IsNull()) {
return v8::Number::New(scope->getIsolate(), 0);
}
argumentCheck(args[0]->IsObject(), "argument to bsonsize has to be an object") return v8::
Number::New(scope->getIsolate(), scope->v8ToMongo(args[0]->ToObject()).objsize());
}
v8::Local bsonWoCompare(V8Scope* scope,
const v8::FunctionCallbackInfo& args) {
argumentCheck(args.Length() == 2, "bsonWoCompare needs 2 argument");
argumentCheck(args[0]->IsObject(), "first argument to bsonWoCompare has to be an object");
argumentCheck(args[1]->IsObject(), "second argument to bsonWoCompare has to be an object");
BSONObj firstObject(scope->v8ToMongo(args[0]->ToObject()));
BSONObj secondObject(scope->v8ToMongo(args[1]->ToObject()));
return v8::Number::New(scope->getIsolate(), firstObject.woCompare(secondObject));
}
}