summaryrefslogtreecommitdiff
path: root/src/mongo/shell/dbshell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/shell/dbshell.cpp')
-rw-r--r--src/mongo/shell/dbshell.cpp169
1 files changed, 132 insertions, 37 deletions
diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp
index 801130d07c5..90c59f0ee55 100644
--- a/src/mongo/shell/dbshell.cpp
+++ b/src/mongo/shell/dbshell.cpp
@@ -43,6 +43,7 @@
#include "mongo/base/initializer.h"
#include "mongo/base/status.h"
#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/mongo_uri.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/client.h"
#include "mongo/db/log_process_details.h"
@@ -215,57 +216,151 @@ void setupSignals() {
signal(SIGINT, quitNicely);
}
-string getURIFromArgs(const std::string& url, const std::string& host, const std::string& port) {
- if (host.size() == 0 && port.size() == 0) {
- return url.size() == 0 ? kDefaultMongoURL.toString() : url;
+string getURIFromArgs(const std::string& arg, const std::string& host, const std::string& port) {
+ if (host.empty() && arg.empty() && port.empty()) {
+ // Nothing provided, just play the default.
+ return kDefaultMongoURL.toString();
}
- // 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 << "if a full URI is provided, you cannot also specify host or port" << endl;
+ if (str::startsWith(arg, "mongodb://") && host.empty() && port.empty()) {
+ // mongo mongodb://blah
+ return arg;
+ }
+ if (str::startsWith(host, "mongodb://") && arg.empty() && port.empty()) {
+ // mongo --host mongodb://blah
+ return host;
+ }
+
+ // We expect a positional arg to be a plain dbname or plain hostname at this point
+ // since we have separate host/port args.
+ if ((arg.find('/') != string::npos) && (host.size() || port.size())) {
+ cerr << "If a full URI is provided, you cannot also specify --host or --port" << endl;
quickExit(-1);
}
- bool hostEndsInSock = str::endsWith(host, ".sock");
+ const auto parseDbHost = [port](const std::string& db, const std::string& host) -> std::string {
+ // Parse --host as a connection string.
+ // e.g. rs0/host0:27000,host1:27001
+ const auto slashPos = host.find('/');
+ const auto hasReplSet = (slashPos > 0) && (slashPos != std::string::npos);
+
+ std::ostringstream ss;
+ ss << "mongodb://";
+
+ // Handle each sub-element of the connection string individually.
+ // Comma separated list of host elements.
+ // Each host element may be:
+ // * /unix/domain.sock
+ // * hostname
+ // * hostname:port
+ // If --port is specified and port is included in connection string,
+ // then they must match exactly.
+ auto start = hasReplSet ? slashPos + 1 : 0;
+ while (start < host.size()) {
+ // Encode each host component.
+ auto end = host.find(',', start);
+ if (end == std::string::npos) {
+ end = host.size();
+ }
+ if ((end - start) == 0) {
+ // Ignore empty components.
+ start = end + 1;
+ continue;
+ }
+
+ const auto hostElem = host.substr(start, end - start);
+ if ((hostElem.find('/') != std::string::npos) && str::endsWith(hostElem, ".sock")) {
+ // Unix domain socket, ignore --port.
+ ss << uriEncode(hostElem);
+
+ } else {
+ auto colon = hostElem.find(':');
+ if ((colon != std::string::npos) &&
+ (hostElem.find(':', colon + 1) != std::string::npos)) {
+ // Looks like an IPv6 numeric address.
+ const auto close = hostElem.find(']');
+ if ((hostElem[0] == '[') && (close != std::string::npos)) {
+ // Encapsulated already.
+ ss << '[' << uriEncode(hostElem.substr(1, close - 1), ":") << ']';
+ colon = hostElem.find(':', close + 1);
+ } else {
+ // Not encapsulated yet.
+ ss << '[' << uriEncode(hostElem, ":") << ']';
+ colon = std::string::npos;
+ }
+ } else if (colon != std::string::npos) {
+ // Not IPv6 numeric, but does have a port.
+ ss << uriEncode(hostElem.substr(0, colon));
+ } else {
+ // Raw hostname/IPv4 without port.
+ ss << uriEncode(hostElem);
+ }
+
+ if (colon != std::string::npos) {
+ // Have a port in our host element, verify it.
+ const auto myport = hostElem.substr(colon + 1);
+ if (port.size() && (port != myport)) {
+ cerr << "connection string bears different port than provided by --port"
+ << endl;
+ quickExit(-1);
+ }
+ ss << ':' << uriEncode(myport);
+ } else if (port.size()) {
+ ss << ':' << uriEncode(port);
+ } else {
+ ss << ":27017";
+ }
+ }
+ start = end + 1;
+ if (start < host.size()) {
+ ss << ',';
+ }
+ }
+
+ ss << '/' << uriEncode(db);
- // 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.
- std::string::size_type slashPos;
- if (url.size() == 0 && port.size() == 0 &&
- (!hostEndsInSock && ((slashPos = host.find("/")) != string::npos))) {
- if (str::startsWith(host, "mongodb://")) {
- return host;
+ if (hasReplSet) {
+ // Remap included replica set name to URI option
+ ss << "?replicaSet=" << uriEncode(host.substr(0, slashPos));
}
- // If there's a slash in the host field, then it's the replica set name, not a database name
- stringstream ss;
- ss << "mongodb://" << host.substr(slashPos + 1)
- << "/?replicaSet=" << host.substr(0, slashPos);
+
return ss.str();
- }
+ };
+
+ if (host.size()) {
+ // --host provided, treat it as the connect string and get db from positional arg.
+ return parseDbHost(arg, host);
+ } else if (arg.size()) {
+ // --host missing, but we have a potential host/db positional arg.
+ const auto slashPos = arg.find('/');
+ if (slashPos != std::string::npos) {
+ // host/db pair.
+ return parseDbHost(arg.substr(slashPos + 1), arg.substr(0, slashPos));
+ }
- stringstream ss;
- if (host.size() == 0) {
- ss << "mongodb://127.0.0.1";
- } else {
- if (!str::startsWith(host, "mongodb://")) {
- ss << "mongodb://";
+ // Compatability formats.
+ // * Any arg with a dot is assumed to be a hostname or IPv4 numeric address.
+ // * Any arg with a colon followed by a digit assumed to be host or IP followed by port.
+ // * Anything else is assumed to be a db.
+
+ if (arg.find('.') != std::string::npos) {
+ // Assume IPv4 or hostnameish.
+ return parseDbHost("test", arg);
}
- 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";
+ const auto colonPos = arg.find(':');
+ if ((colonPos != std::string::npos) && ((colonPos + 1) < arg.size()) &&
+ isdigit(arg[colonPos + 1])) {
+ // Assume IPv4 or hostname with port.
+ return parseDbHost("test", arg);
}
+
+ // db, assume localhost.
+ return parseDbHost(arg, "127.0.0.1");
}
- ss << "/" << url;
- return ss.str();
+ // --host empty, position arg empty, fallback on localhost without a dbname.
+ return parseDbHost("", "127.0.0.1");
}
static string OpSymbols = "~!%^&*-+=|:,<>/?.";