diff options
-rw-r--r-- | jstests/core/connection_string_validation.js | 79 | ||||
-rw-r--r-- | jstests/noPassthroughWithMongod/host_connection_string_validation.js | 125 | ||||
-rw-r--r-- | jstests/noPassthroughWithMongod/ipv6_connection_string_validation.js | 75 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri.cpp | 2 | ||||
-rw-r--r-- | src/mongo/scripting/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 10 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.h | 13 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/internedstring.defs | 8 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongo.cpp | 8 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/objectwrapper.cpp | 7 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/objectwrapper.h | 1 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/uri.cpp | 105 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/uri.h | 52 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/valuereader.cpp | 34 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/valuereader.h | 1 | ||||
-rw-r--r-- | src/mongo/shell/dbshell.cpp | 61 | ||||
-rw-r--r-- | src/mongo/shell/mongo.js | 49 | ||||
-rw-r--r-- | src/mongo/util/net/hostandport.cpp | 8 |
18 files changed, 504 insertions, 139 deletions
diff --git a/jstests/core/connection_string_validation.js b/jstests/core/connection_string_validation.js index 232650f230b..4f878593e6e 100644 --- a/jstests/core/connection_string_validation.js +++ b/jstests/core/connection_string_validation.js @@ -8,30 +8,42 @@ if (db.getMongo().host.indexOf(":") >= 0) { port = db.getMongo().host.substring(idx + 1); } -var goodStrings = ["localhost:" + port + "/test", "127.0.0.1:" + port + "/test"]; +var goodStrings = [ + "localhost:" + port + "/test", + "127.0.0.1:" + port + "/test", + "127.0.0.1:" + port + "/", +]; +var missingConnString = /^Missing connection string$/; +var incorrectType = /^Incorrect type/; +var emptyConnString = /^Empty connection string$/; +var badHost = /^Failed to parse mongodb/; +var emptyHost = /^Empty host component/; +var noPort = /^No digits/; +var badPort = /^Bad digit/; +var invalidPort = /^Port number \d+ out of range/; +var multipleColon = /^More than one ':' detected./; var badStrings = [ - {s: undefined, r: /^Missing connection string$/}, - {s: 7, r: /^Incorrect type/}, - {s: null, r: /^Incorrect type/}, - {s: "", r: /^Empty connection string$/}, - {s: " ", r: /^Empty connection string$/}, - {s: ":", r: /^Missing host name/}, - {s: "/", r: /^Missing host name/}, - {s: ":/", r: /^Missing host name/}, - {s: ":/test", r: /^Missing host name/}, - {s: ":" + port + "/", r: /^Missing host name/}, - {s: ":" + port + "/test", r: /^Missing host name/}, - {s: "/test", r: /^Missing host name/}, - {s: "localhost:/test", r: /^Missing port number/}, - {s: "127.0.0.1:/test", r: /^Missing port number/}, - {s: "127.0.0.1:cat/test", r: /^Invalid port number/}, - {s: "127.0.0.1:1cat/test", r: /^Invalid port number/}, - {s: "127.0.0.1:123456/test", r: /^Invalid port number/}, - {s: "127.0.0.1:65536/test", r: /^Invalid port number/}, - {s: "::1:65536/test", r: /^Invalid port number/}, - {s: "127.0.0.1:" + port + "/", r: /^Missing database name/}, - {s: "::1:" + port + "/", r: /^Missing database name/} + {s: undefined, r: missingConnString}, + {s: 7, r: incorrectType}, + {s: null, r: incorrectType}, + {s: "", r: emptyConnString}, + {s: " ", r: emptyConnString}, + {s: ":", r: emptyHost}, + {s: "/", r: badHost}, + {s: "/test", r: badHost}, + {s: ":/", r: emptyHost}, + {s: ":/test", r: emptyHost}, + {s: ":" + port + "/", r: emptyHost}, + {s: ":" + port + "/test", r: emptyHost}, + {s: "localhost:/test", r: noPort}, + {s: "127.0.0.1:/test", r: noPort}, + {s: "127.0.0.1:cat/test", r: badPort}, + {s: "127.0.0.1:1cat/test", r: badPort}, + {s: "127.0.0.1:123456/test", r: invalidPort}, + {s: "127.0.0.1:65536/test", r: invalidPort}, + {s: "::1:65536/test", r: multipleColon}, + {s: "::1:" + port + "/", r: multipleColon} ]; function testGood(i, connectionString) { @@ -55,6 +67,28 @@ function testGood(i, connectionString) { doassert(message); } +function testGoodAsURI(i, uri) { + uri = "mongodb://" + uri; + print("\nTesting good uri " + i + " (\"" + uri + "\") ..."); + var gotException = false; + var exception; + try { + var m_uri = MongoURI(uri); + var connectDB = connect(uri); + connectDB = null; + } catch (e) { + gotException = true; + exception = e; + } + if (!gotException) { + print("Good uri " + i + " (\"" + uri + "\") correctly validated"); + return; + } + var message = "FAILED to correctly validate goodString " + i + " (\"" + uri + + "\"): exception was \"" + tojson(exception) + "\""; + doassert(message); +} + function testBad(i, connectionString, errorRegex) { print("\nTesting bad connection string " + i + " (\"" + connectionString + "\") ..."); var gotException = false; @@ -90,6 +124,7 @@ var i; jsTest.log("TESTING " + goodStrings.length + " good connection strings"); for (i = 0; i < goodStrings.length; ++i) { testGood(i, goodStrings[i]); + testGoodAsURI(i, goodStrings[i]); } jsTest.log("TESTING " + badStrings.length + " bad connection strings"); diff --git a/jstests/noPassthroughWithMongod/host_connection_string_validation.js b/jstests/noPassthroughWithMongod/host_connection_string_validation.js new file mode 100644 index 00000000000..89d77e707f1 --- /dev/null +++ b/jstests/noPassthroughWithMongod/host_connection_string_validation.js @@ -0,0 +1,125 @@ +// Test --host. + +// This "inner_mode" method of spawning a mongod and re-running was copied from +// ipv6_connection_string_validation.js +if ("undefined" == typeof inner_mode) { + // Start a mongod with --ipv6 + jsTest.log("Outer mode test starting mongod with --ipv6"); + // NOTE: bind_ip arg is present to test if it can parse ipv6 addresses (::1 in this case). + // Unfortunately, having bind_ip = ::1 won't work in the test framework (But does work when + // tested manually), so 127.0.0.1 is also present so the test mongo shell can connect + // with that address. + var mongod = MongoRunner.runMongod({ipv6: "", bind_ip: "::1,127.0.0.1"}); + var args = [ + "mongo", + "--nodb", + "--ipv6", + "--host", + "::1", + "--port", + mongod.port, + "--eval", + "inner_mode=true;port=" + mongod.port + ";", + "jstests/noPassthroughWithMongod/host_connection_string_validation.js" + ]; + var exitCode = _runMongoProgram.apply(null, args); + jsTest.log("Inner mode test finished, exit code was " + exitCode); + + // Stop the server we started + jsTest.log("Outer mode test stopping server"); + MongoRunner.stopMongod(mongod.port, 15); + + // Pass the inner test's exit code back as the outer test's exit code + quit(exitCode); +} + +var testHost = function(host, shouldSucceed) { + var exitCode = runMongoProgram('mongo', '--ipv6', '--eval', ';', '--host', host); + if (shouldSucceed) { + if (exitCode !== 0) { + doassert("failed to connect with `--host " + host + + "`, but expected success. Exit code: " + exitCode); + } + } else { + if (exitCode === 0) { + doassert("successfully connected with `--host " + host + "`, but expected to fail."); + } + } +}; + +var goodStrings = [ + "[::1]:27999", + "[::1]:27999/test", + "localhost:27999", + "localhost:27999/test", + "127.0.0.1:27999", + "127.0.0.1:27999/test", + "[0:0:0:0:0:0:0:1]:27999", + "[0:0:0:0:0:0:0:1]:27999/test", + "[0000:0000:0000:0000:0000:0000:0000:0001]:27999", + "[0000:0000:0000:0000:0000:0000:0000:0001]:27999/test", +]; + +var goodSocketStrings = [ + "/tmp/mongodb-27999.sock", + "/tmp/mongodb-27999.sock/test", +]; + +var badStrings = [ + "/", + ":", + ":/", + "/test", + ":/test", + ":27999/", + ":27999/test", + "::1]:27999/", + "[::1:27999/", + "[::1]:/test", + "[::1:]27999/", + "a[::1:]27999/", + "::1:27999/test", + "::1:65536/test", + "[::1]:cat/test", + "[::1]:1cat/test", + "localhost:/test", + "127.0.0.1:/test", + "[::1]:65536/test", + "[::1]:123456/test", + "127.0.0.1:cat/test", + "a[127.0.0.1]:27999/", + "127.0.0.1:1cat/test", + "127.0.0.1:65536/test", + "0:0::0:0:1:27999/test", + "127.0.0.1:123456/test", + "0000:0000:0000:0000:0000:0000:0000:0001:27999/test", +]; + +function runUriTestFor(i, connectionString, isGood) { + connectionString = connectionString.replace("27999", "" + port); + print("Testing " + (isGood ? "good" : "bad") + " connection string " + i + "..."); + print(" * testing " + connectionString); + testHost(connectionString, isGood); + print(" * testing mongodb://" + connectionString); + testHost("mongodb://" + connectionString, isGood); +} + +var i; +jsTest.log("TESTING " + goodStrings.length + " good uri strings"); +for (i = 0; i < goodStrings.length; ++i) { + runUriTestFor(i, goodStrings[i], true); +} + +if (!_isWindows()) { + jsTest.log("TESTING " + goodSocketStrings.length + " good uri socket strings"); + for (i = 0; i < goodSocketStrings.length; ++i) { + runUriTestFor(i, goodSocketStrings[i], true); + } +} + +jsTest.log("TESTING " + badStrings.length + " bad uri strings"); +for (i = 0; i < badStrings.length; ++i) { + runUriTestFor(i, badStrings[i], false); +} + +jsTest.log("SUCCESSFUL test completion"); diff --git a/jstests/noPassthroughWithMongod/ipv6_connection_string_validation.js b/jstests/noPassthroughWithMongod/ipv6_connection_string_validation.js index e0a83397ee9..881c77c96d9 100644 --- a/jstests/noPassthroughWithMongod/ipv6_connection_string_validation.js +++ b/jstests/noPassthroughWithMongod/ipv6_connection_string_validation.js @@ -40,39 +40,54 @@ var goodStrings = [ "localhost:27999/test", "[::1]:27999/test", "[0:0:0:0:0:0:0:1]:27999/test", - "[0000:0000:0000:0000:0000:0000:0000:0001]:27999/test" + "[0000:0000:0000:0000:0000:0000:0000:0001]:27999/test", + "localhost:27999", + "[::1]:27999", + "[0:0:0:0:0:0:0:1]:27999", + "[0000:0000:0000:0000:0000:0000:0000:0001]:27999", ]; +var missingConnString = /^Missing connection string$/; +var incorrectType = /^Incorrect type/; +var emptyConnString = /^Empty connection string$/; +var badHost = /^Failed to parse mongodb/; +var emptyHost = /^Empty host component/; +var noPort = /^No digits/; +var badPort = /^Bad digit/; +var invalidPort = /^Port number \d+ out of range/; +var moreThanOneColon = /^More than one ':' detected/; +var charBeforeSquareBracket = /^'\[' present, but not first character/; +var noCloseBracket = /^ipv6 address is missing closing '\]'/; +var noOpenBracket = /^'\]' present without '\['/; +var noColonPrePort = /^missing colon after '\]' before the port/; var badStrings = [ - {s: undefined, r: /^Missing connection string$/}, - {s: 7, r: /^Incorrect type/}, - {s: null, r: /^Incorrect type/}, - {s: "", r: /^Empty connection string$/}, - {s: " ", r: /^Empty connection string$/}, - {s: ":", r: /^Missing host name/}, - {s: "/", r: /^Missing host name/}, - {s: ":/", r: /^Missing host name/}, - {s: ":/test", r: /^Missing host name/}, - {s: ":27999/", r: /^Missing host name/}, - {s: ":27999/test", r: /^Missing host name/}, - {s: "/test", r: /^Missing host name/}, - {s: "localhost:/test", r: /^Missing port number/}, - {s: "::1:/test", r: /^Missing port number/}, - {s: "::1:cat/test", r: /^Invalid port number/}, - {s: "::1:1cat/test", r: /^Invalid port number/}, - {s: "::1:123456/test", r: /^Invalid port number/}, - {s: "::1:65536/test", r: /^Invalid port number/}, - {s: "127.0.0.1:65536/test", r: /^Invalid port number/}, - {s: "::1:27999/", r: /^Missing database name/}, - {s: "127.0.0.1:27999/", r: /^Missing database name/}, - {s: "::1:27999/test", r: /^More than one ':'/}, - {s: "0:0::0:0:1:27999/test", r: /^More than one ':'/}, - {s: "0000:0000:0000:0000:0000:0000:0000:0001:27999/test", r: /^More than one ':'/}, - {s: "a[127.0.0.1]:27999/", r: /^Missing database name/}, - {s: "a[::1:]27999/", r: /^Invalid port number/}, - {s: "[::1:27999/", r: /^Missing database name/}, - {s: "[::1:]27999/", r: /^Invalid port number/}, - {s: "::1]:27999/", r: /^Missing database name/} + {s: undefined, r: missingConnString}, + {s: 7, r: incorrectType}, + {s: null, r: incorrectType}, + {s: "", r: emptyConnString}, + {s: " ", r: emptyConnString}, + {s: ":", r: emptyHost}, + {s: "/", r: badHost}, + {s: ":/", r: emptyHost}, + {s: ":/test", r: emptyHost}, + {s: ":27999/", r: emptyHost}, + {s: ":27999/test", r: emptyHost}, + {s: "/test", r: badHost}, + {s: "localhost:/test", r: noPort}, + {s: "[::1]:/test", r: noPort}, + {s: "[::1]:cat/test", r: badPort}, + {s: "[::1]:1cat/test", r: badPort}, + {s: "[::1]:123456/test", r: invalidPort}, + {s: "[::1]:65536/test", r: invalidPort}, + {s: "127.0.0.1:65536/test", r: invalidPort}, + {s: "::1:27999/test", r: moreThanOneColon}, + {s: "0:0::0:0:1:27999/test", r: moreThanOneColon}, + {s: "0000:0000:0000:0000:0000:0000:0000:0001:27999/test", r: moreThanOneColon}, + {s: "a[127.0.0.1]:27999/", r: charBeforeSquareBracket}, + {s: "a[::1:]27999/", r: charBeforeSquareBracket}, + {s: "[::1:27999/", r: noCloseBracket}, + {s: "[::1:]27999/", r: noColonPrePort}, + {s: "::1]:27999/", r: noOpenBracket}, ]; var substitutePort = function(connectionString) { diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp index ab16452b56b..e737479d05b 100644 --- a/src/mongo/client/mongo_uri.cpp +++ b/src/mongo/client/mongo_uri.cpp @@ -56,7 +56,7 @@ const char kMongoDBURL[] = "(?:([^:]+)(?::([^@]+))?@)?" // servers: grabs all host:port or UNIX socket names - "((?:[^\\/]+|/.+\\.sock)(?:(?:[^\\/]+|/.+\\.sock),)*)" + "((?:[^\\/]+|/.+\\.sock)(?:,(?:[^\\/]+|/.+\\.sock))*)" // database and options are grouped together "(?:/" diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index fc32d46d01a..8c2604c76b6 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -107,8 +107,8 @@ if usemozjs: 'mozjs/countdownlatch.cpp', 'mozjs/cursor.cpp', 'mozjs/cursor_handle.cpp', - 'mozjs/dbcollection.cpp', 'mozjs/db.cpp', + 'mozjs/dbcollection.cpp', 'mozjs/dbpointer.cpp', 'mozjs/dbquery.cpp', 'mozjs/dbref.cpp', @@ -128,9 +128,9 @@ if usemozjs: 'mozjs/mongohelpers.cpp', 'mozjs/mongohelpers_js.cpp', 'mozjs/nativefunction.cpp', + 'mozjs/numberdecimal.cpp', 'mozjs/numberint.cpp', 'mozjs/numberlong.cpp', - 'mozjs/numberdecimal.cpp', 'mozjs/object.cpp', 'mozjs/objectwrapper.cpp', 'mozjs/oid.cpp', @@ -138,6 +138,7 @@ if usemozjs: 'mozjs/proxyscope.cpp', 'mozjs/regexp.cpp', 'mozjs/timestamp.cpp', + 'mozjs/uri.cpp', 'mozjs/valuereader.cpp', 'mozjs/valuewriter.cpp', ], diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index 2b01e99454b..fff5f233ac2 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -336,12 +336,12 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _bsonProto(_context), _codeProto(_context), _countDownLatchProto(_context), - _cursorProto(_context), _cursorHandleProto(_context), + _cursorProto(_context), _dbCollectionProto(_context), + _dbProto(_context), _dbPointerProto(_context), _dbQueryProto(_context), - _dbProto(_context), _dbRefProto(_context), _errorProto(_context), _jsThreadProto(_context), @@ -351,13 +351,14 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _mongoHelpersProto(_context), _mongoLocalProto(_context), _nativeFunctionProto(_context), + _numberDecimalProto(_context), _numberIntProto(_context), _numberLongProto(_context), - _numberDecimalProto(_context), _objectProto(_context), _oidProto(_context), _regExpProto(_context), - _timestampProto(_context) { + _timestampProto(_context), + _uriProto(_context) { kCurrentScope = this; // The default is quite low and doesn't seem to directly correlate with @@ -790,6 +791,7 @@ void MozJSImplScope::installBSONTypes() { _oidProto.install(_global); _regExpProto.install(_global); _timestampProto.install(_global); + _uriProto.install(_global); // This builtin map is a javascript 6 thing. We want our version. so // take theirs out diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index d8225bffdde..080716428ac 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -60,6 +60,7 @@ #include "mongo/scripting/mozjs/oid.h" #include "mongo/scripting/mozjs/regexp.h" #include "mongo/scripting/mozjs/timestamp.h" +#include "mongo/scripting/mozjs/uri.h" namespace mongo { namespace mozjs { @@ -289,6 +290,11 @@ public: return _timestampProto; } + template <typename T> + typename std::enable_if<std::is_same<T, URIInfo>::value, WrapType<T>&>::type getProto() { + return _uriProto; + } + void setQuickExit(int exitCode); bool getQuickExit(int* exitCode); @@ -380,12 +386,12 @@ private: WrapType<BSONInfo> _bsonProto; WrapType<CodeInfo> _codeProto; WrapType<CountDownLatchInfo> _countDownLatchProto; - WrapType<CursorInfo> _cursorProto; WrapType<CursorHandleInfo> _cursorHandleProto; + WrapType<CursorInfo> _cursorProto; WrapType<DBCollectionInfo> _dbCollectionProto; + WrapType<DBInfo> _dbProto; WrapType<DBPointerInfo> _dbPointerProto; WrapType<DBQueryInfo> _dbQueryProto; - WrapType<DBInfo> _dbProto; WrapType<DBRefInfo> _dbRefProto; WrapType<ErrorInfo> _errorProto; WrapType<JSThreadInfo> _jsThreadProto; @@ -395,13 +401,14 @@ private: WrapType<MongoHelpersInfo> _mongoHelpersProto; WrapType<MongoLocalInfo> _mongoLocalProto; WrapType<NativeFunctionInfo> _nativeFunctionProto; + WrapType<NumberDecimalInfo> _numberDecimalProto; WrapType<NumberIntInfo> _numberIntProto; WrapType<NumberLongInfo> _numberLongProto; - WrapType<NumberDecimalInfo> _numberDecimalProto; WrapType<ObjectInfo> _objectProto; WrapType<OIDInfo> _oidProto; WrapType<RegExpInfo> _regExpProto; WrapType<TimestampInfo> _timestampProto; + WrapType<URIInfo> _uriProto; }; inline MozJSImplScope* getScope(JSContext* cx) { diff --git a/src/mongo/scripting/mozjs/internedstring.defs b/src/mongo/scripting/mozjs/internedstring.defs index 792ec0bbcb5..fe2394edb27 100644 --- a/src/mongo/scripting/mozjs/internedstring.defs +++ b/src/mongo/scripting/mozjs/internedstring.defs @@ -53,3 +53,11 @@ MONGO_MOZJS_INTERNED_STRING(str, "str") MONGO_MOZJS_INTERNED_STRING(top, "top") MONGO_MOZJS_INTERNED_STRING(t, "t") MONGO_MOZJS_INTERNED_STRING(type, "type") +MONGO_MOZJS_INTERNED_STRING(uri, "uri") +MONGO_MOZJS_INTERNED_STRING(user, "user") +MONGO_MOZJS_INTERNED_STRING(password, "password") +MONGO_MOZJS_INTERNED_STRING(options, "options") +MONGO_MOZJS_INTERNED_STRING(database, "database") +MONGO_MOZJS_INTERNED_STRING(isValid, "isValid") +MONGO_MOZJS_INTERNED_STRING(setName, "setName") +MONGO_MOZJS_INTERNED_STRING(servers, "servers") diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index b7d9677fd0a..da62b092f34 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -638,8 +638,7 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { } auto statusWithHost = MongoURI::parse(host); - uassertStatusOK(statusWithHost); - auto cs = statusWithHost.getValue(); + auto cs = uassertStatusOK(statusWithHost); std::string errmsg; std::unique_ptr<DBClientBase> conn(cs.connect("MongoDB Shell", errmsg)); @@ -657,8 +656,9 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { JS_SetPrivate(thisv, new std::shared_ptr<DBClientBase>(conn.release())); o.setBoolean(InternedString::slaveOk, false); - o.setString(InternedString::host, host); - o.setString(InternedString::defaultDB, cs.getDatabase()); + o.setString(InternedString::host, cs.toString()); + auto defaultDB = cs.getDatabase() == "" ? "test" : cs.getDatabase(); + o.setString(InternedString::defaultDB, defaultDB); args.rval().setObjectOrNull(thisv); } diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp index 5c921c1ebec..98f8094dfcc 100644 --- a/src/mongo/scripting/mozjs/objectwrapper.cpp +++ b/src/mongo/scripting/mozjs/objectwrapper.cpp @@ -361,6 +361,13 @@ void ObjectWrapper::setBSON(Key key, const BSONObj& obj, bool readOnly) { setValue(key, value); } +void ObjectWrapper::setBSONArray(Key key, const BSONObj& obj, bool readOnly) { + JS::RootedValue value(_context); + ValueReader(_context, &value).fromBSONArray(obj, nullptr, readOnly); + + setValue(key, value); +} + void ObjectWrapper::setValue(Key key, JS::HandleValue val) { key.set(_context, _object, val); } diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h index 8c85a5b42d1..cf6c61d39e1 100644 --- a/src/mongo/scripting/mozjs/objectwrapper.h +++ b/src/mongo/scripting/mozjs/objectwrapper.h @@ -115,6 +115,7 @@ public: void setBoolean(Key key, bool val); void setBSONElement(Key key, const BSONElement& elem, const BSONObj& obj, bool readOnly); void setBSON(Key key, const BSONObj& obj, bool readOnly); + void setBSONArray(Key key, const BSONObj& obj, bool readOnly); void setValue(Key key, JS::HandleValue value); void setObject(Key key, JS::HandleObject value); diff --git a/src/mongo/scripting/mozjs/uri.cpp b/src/mongo/scripting/mozjs/uri.cpp new file mode 100644 index 00000000000..f943bb8a171 --- /dev/null +++ b/src/mongo/scripting/mozjs/uri.cpp @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2016 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/uri.h" + +#include <algorithm> +#include <iterator> + +#include "mongo/client/mongo_uri.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/scripting/mozjs/wrapconstrainedmethod.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec URIInfo::methods[2] = { + MONGO_ATTACH_JS_CONSTRAINED_METHOD(toString, URIInfo), JS_FS_END, +}; + +const char* const URIInfo::className = "MongoURI"; + +void URIInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) { + ObjectWrapper o(cx, args.thisv()); + ValueReader(cx, args.rval()).fromStringData(o.getString(InternedString::uri)); +} + +void URIInfo::construct(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::BadValue, "MongoURI needs 1 argument", args.length() == 1); + + JS::HandleValue uriArg = args.get(0); + if (!uriArg.isString()) + uasserted(ErrorCodes::BadValue, "uri must be a string"); + + std::string uri = ValueWriter(cx, args.get(0)).toString(); + + auto sw = MongoURI::parse(uri); + auto parsed = uassertStatusOK(sw); + + BSONArrayBuilder serversBuilder; + for (const auto hp : parsed.getServers()) { + BSONObjBuilder b; + b.append("server", hp.toString()); + b.append("host", hp.host()); + if (hp.hasPort()) { + b.append("port", hp.port()); + } + serversBuilder.append(b.obj()); + } + + BSONObjBuilder optsBuilder; + for (const auto& kvpair : parsed.getOptions()) { + optsBuilder.append(kvpair.first, kvpair.second); + } + + JS::RootedObject thisv(cx); + auto scope = getScope(cx); + scope->getProto<URIInfo>().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue(InternedString::uri, uriArg); + o.setString(InternedString::user, parsed.getUser()); + o.setString(InternedString::password, parsed.getPassword()); + o.setBSON(InternedString::options, optsBuilder.obj(), true); + o.setString(InternedString::database, parsed.getDatabase()); + o.setBoolean(InternedString::isValid, parsed.isValid()); + o.setString(InternedString::setName, parsed.getSetName()); + o.setBSONArray(InternedString::servers, serversBuilder.arr(), true); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/uri.h b/src/mongo/scripting/mozjs/uri.h new file mode 100644 index 00000000000..d1b487ce2ab --- /dev/null +++ b/src/mongo/scripting/mozjs/uri.h @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * A MongoURI object. + */ +struct URIInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DECLARE_JS_FUNCTION(toString); + }; + + static const JSFunctionSpec methods[2]; + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/valuereader.cpp b/src/mongo/scripting/mozjs/valuereader.cpp index af26238763d..1eedef58e86 100644 --- a/src/mongo/scripting/mozjs/valuereader.cpp +++ b/src/mongo/scripting/mozjs/valuereader.cpp @@ -97,21 +97,7 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent _value.setInt32(elem.Int()); return; case mongo::Array: { - JS::AutoValueVector avv(_context); - - BSONForEach(subElem, elem.embeddedObject()) { - JS::RootedValue member(_context); - - ValueReader(_context, &member).fromBSONElement(subElem, parent, readOnly); - if (!avv.append(member)) { - uasserted(ErrorCodes::JSInterpreterFailure, "Failed to append to JS array"); - } - } - JS::RootedObject array(_context, JS_NewArrayObject(_context, avv)); - if (!array) { - uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewArrayObject"); - } - _value.setObjectOrNull(array); + fromBSONArray(elem.embeddedObject(), &parent, readOnly); return; } case mongo::Object: @@ -254,6 +240,24 @@ void ValueReader::fromBSON(const BSONObj& obj, const BSONObj* parent, bool readO _value.setObjectOrNull(child); } +void ValueReader::fromBSONArray(const BSONObj& obj, const BSONObj* parent, bool readOnly) { + JS::AutoValueVector avv(_context); + + BSONForEach(elem, obj) { + JS::RootedValue member(_context); + + ValueReader(_context, &member).fromBSONElement(elem, parent ? *parent : obj, readOnly); + if (!avv.append(member)) { + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to append to JS array"); + } + } + JS::RootedObject array(_context, JS_NewArrayObject(_context, avv)); + if (!array) { + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewArrayObject"); + } + _value.setObjectOrNull(array); +} + /** * SpiderMonkey doesn't have a direct entry point to create a jsstring from * utf8, so we have to flow through some slightly less public interfaces. diff --git a/src/mongo/scripting/mozjs/valuereader.h b/src/mongo/scripting/mozjs/valuereader.h index 6b03d30086c..58abed86c45 100644 --- a/src/mongo/scripting/mozjs/valuereader.h +++ b/src/mongo/scripting/mozjs/valuereader.h @@ -49,6 +49,7 @@ public: void fromBSONElement(const BSONElement& elem, const BSONObj& parent, bool readOnly); void fromBSON(const BSONObj& obj, const BSONObj* parent, bool readOnly); + void fromBSONArray(const BSONObj& obj, const BSONObj* parent, bool readOnly); void fromDouble(double d); void fromStringData(StringData sd); void fromDecimal128(Decimal128 decimal); diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp index 243bdd187ef..9fc6b6de5df 100644 --- a/src/mongo/shell/dbshell.cpp +++ b/src/mongo/shell/dbshell.cpp @@ -85,6 +85,10 @@ bool gotInterrupted = false; bool inMultiLine = false; static volatile bool atPrompt = false; // can eval before getting to prompt +namespace { +const auto kDefaultMongoURL = "mongodb://127.0.0.1:27017"_sd; +} + namespace mongo { Scope* shellMainScope; @@ -200,35 +204,49 @@ void setupSignals() { signal(SIGINT, quitNicely); } -string fixHost(const std::string& url, const std::string& host, const std::string& port) { +string getURIFromArgs(const std::string& url, const std::string& host, const std::string& port) { if (host.size() == 0 && port.size() == 0) { - if (url.find("/") == string::npos) { - // check for ips - if (url.find(".") != string::npos) - return url + "/test"; - - if (url.rfind(":") != string::npos && isdigit(url[url.rfind(":") + 1])) - return url + "/test"; - } - return url; + return url.size() == 0 ? kDefaultMongoURL.toString() : url; } + // The name URL is misleading; really it's just a positional argument that wasn't a file. The + // check for "/" means "this 'URL' is probably a real URL and not the db name (e.g.)". if (url.find("/") != string::npos) { - cerr << "url can't have host or port if you specify them individually" << endl; + cerr << "if a full URI is provided, you cannot also specify host or port" << endl; quickExit(-1); } - string newurl((host.size() == 0) ? "127.0.0.1" : host); - if (port.size() > 0) - newurl += ":" + port; - else if (host.find(':') == string::npos) { - // need to add port with IPv6 addresses - newurl += ":27017"; + bool hostEndsInSock = str::endsWith(host, ".sock"); + + // If host looks like a full URI (i.e. has a slash and isn't a unix socket) and the other fields + // are empty, then just return host. + if (url.size() == 0 && port.size() == 0 && + (!hostEndsInSock && host.find("/") != string::npos)) { + return host; } - newurl += "/" + url; + stringstream ss; + if (host.size() == 0) { + ss << "mongodb://127.0.0.1"; + } else { + if (!str::startsWith(host, "mongodb://")) { + ss << "mongodb://"; + } + ss << host; + } + + if (!hostEndsInSock) { + if (port.size() > 0) { + ss << ":" << port; + } else if (host.find(':') == string::npos || str::endsWith(host, "]")) { + // Default the port to 27017 if the host did not provide one (i.e. the host has no + // colons or ends in ']' like an IPv6 address). + ss << ":27017"; + } + } - return newurl; + ss << "/" << url; + return ss.str(); } static string OpSymbols = "~!%^&*-+=|:,<>/?."; @@ -585,8 +603,6 @@ int _main(int argc, char* argv[], char** envp) { mongo::shell_utils::RecordMyLocation(argv[0]); - shellGlobalParams.url = "test"; - mongo::runGlobalInitializersOrDie(argc, argv, envp); // hide password from ps output @@ -615,7 +631,8 @@ int _main(int argc, char* argv[], char** envp) { if (mongo::serverGlobalParams.quiet) ss << "__quiet = true;"; ss << "db = connect( \"" - << fixHost(shellGlobalParams.url, shellGlobalParams.dbhost, shellGlobalParams.port) + << getURIFromArgs( + shellGlobalParams.url, shellGlobalParams.dbhost, shellGlobalParams.port) << "\")"; mongo::shell_utils::_dbConnect = ss.str(); diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js index c7ba018358a..2cadfcb77ed 100644 --- a/src/mongo/shell/mongo.js +++ b/src/mongo/shell/mongo.js @@ -194,6 +194,11 @@ Mongo.prototype.getReadConcern = function() { }; connect = function(url, user, pass) { + if (url instanceof MongoURI) { + user = url.user; + pass = url.password; + url = url.uri; + } if (user && !pass) throw Error("you specified a user and not a password. " + "either you need a password, or you're using the old connect api"); @@ -201,8 +206,7 @@ connect = function(url, user, pass) { // Validate connection string "url" as "hostName:portNumber/databaseName" // or "hostName/databaseName" // or "databaseName" - // hostName may be an IPv6 address (with colons), in which case ":portNumber" is required - // + // or full mongo uri. var urlType = typeof url; if (urlType == "undefined") { throw Error("Missing connection string"); @@ -215,45 +219,20 @@ connect = function(url, user, pass) { if (0 == url.length) { throw Error("Empty connection string"); } + if (!url.startsWith("mongodb://")) { var colon = url.lastIndexOf(":"); var slash = url.lastIndexOf("/"); - if (0 == colon || 0 == slash) { - throw Error("Missing host name in connection string \"" + url + "\""); - } - if (colon == slash - 1 || colon == url.length - 1) { - throw Error("Missing port number in connection string \"" + url + "\""); - } - if (colon != -1 && colon < slash) { - var portNumber = url.substring(colon + 1, slash); - if (portNumber.length > 5 || !/^\d*$/.test(portNumber) || - parseInt(portNumber) > 65535) { - throw Error("Invalid port number \"" + portNumber + "\" in connection string \"" + - url + "\""); - } - } - if (slash == url.length - 1) { - throw Error("Missing database name in connection string \"" + url + "\""); + if (slash == -1 && colon == -1) { + url = "mongodb://127.0.0.1:27017/" + url; + } else { + url = "mongodb://" + url; } } - var db; - if (url.startsWith("mongodb://")) { - chatty("connecting to: " + url); - db = new Mongo(url); - if (db.defaultDB.length == 0) { - db.defaultDB = "test"; - } - db = db.getDB(db.defaultDB); - } else if (slash == -1) { - chatty("connecting to: 127.0.0.1:27017/" + url); - db = new Mongo().getDB(url); - } else { - var hostPart = url.substring(0, slash); - var dbPart = url.substring(slash + 1); - chatty("connecting to: " + hostPart + "/" + dbPart); - db = new Mongo(hostPart).getDB(dbPart); - } + chatty("connecting to: " + url); + var m = new Mongo(url); + db = m.getDB(m.defaultDB); if (user && pass) { if (!db.auth(user, pass)) { diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp index 36106ac83fa..04be4a97414 100644 --- a/src/mongo/util/net/hostandport.cpp +++ b/src/mongo/util/net/hostandport.cpp @@ -123,6 +123,12 @@ Status HostAndPort::initialize(StringData s) { hostPart = s.substr(openBracketPos + 1, closeBracketPos - openBracketPos - 1); // prevent accidental assignment of port to the value of the final portion of hostPart if (colonPos < closeBracketPos) { + // If the last colon is inside the brackets, then there must not be a port. + if (s.size() != closeBracketPos + 1) { + return Status(ErrorCodes::FailedToParse, + str::stream() << "missing colon after ']' before the port in " + << s.toString()); + } colonPos = std::string::npos; } else if (colonPos != closeBracketPos + 1) { return Status(ErrorCodes::FailedToParse, @@ -154,7 +160,7 @@ Status HostAndPort::initialize(StringData s) { if (!status.isOK()) { return status; } - if (port <= 0) { + if (port <= 0 || port > 65535) { return Status(ErrorCodes::FailedToParse, str::stream() << "Port number " << port << " out of range parsing HostAndPort from \"" |