1414
1515
1616"""Tools to parse and validate a MongoDB URI."""
17+ import re
1718import warnings
1819
1920from bson .py3compat import PY3 , iteritems , string_type
@@ -68,7 +69,7 @@ def _rpartition(entity, sep):
6869def parse_userinfo (userinfo ):
6970 """Validates the format of user information in a MongoDB URI.
7071 Reserved characters like ':', '/', '+' and '@' must be escaped
71- following RFC 2396 .
72+ following RFC 3986 .
7273
7374 Returns a 2-tuple containing the unescaped username followed
7475 by the unescaped password.
@@ -80,16 +81,13 @@ def parse_userinfo(userinfo):
8081 Now uses `urllib.unquote_plus` so `+` characters must be escaped.
8182 """
8283 if '@' in userinfo or userinfo .count (':' ) > 1 :
83- raise InvalidURI ("':' or '@' characters in a username or password "
84- "must be escaped according to RFC 2396 ." )
84+ raise InvalidURI ("Username and password must be escaped according to "
85+ "RFC 3986, use urllib.quote_plus() ." )
8586 user , _ , passwd = _partition (userinfo , ":" )
8687 # No password is expected with GSSAPI authentication.
8788 if not user :
8889 raise InvalidURI ("The empty string is not valid username." )
89- user = unquote_plus (user )
90- passwd = unquote_plus (passwd )
91-
92- return user , passwd
90+ return unquote_plus (user ), unquote_plus (passwd )
9391
9492
9593def parse_ipv6_literal_host (entity , default_port ):
@@ -251,6 +249,11 @@ def split_hosts(hosts, default_port=DEFAULT_PORT):
251249 return nodes
252250
253251
252+ # Prohibited characters in database name. DB names also can't have ".", but for
253+ # backward-compat we allow "db.collection" in URI.
254+ _BAD_DB_CHARS = re .compile ('[' + re .escape (r'/ "$' ) + ']' )
255+
256+
254257def parse_uri (uri , default_port = DEFAULT_PORT , validate = True , warn = False ):
255258 """Parse and validate a MongoDB URI.
256259
@@ -294,19 +297,10 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
294297 collection = None
295298 options = {}
296299
297- # Check for unix domain sockets in the uri
298- if '.sock' in scheme_free :
299- host_part , _ , path_part = _rpartition (scheme_free , '/' )
300- if not host_part :
301- host_part = path_part
302- path_part = ""
303- if '/' in host_part :
304- raise InvalidURI ("Any '/' in a unix domain socket must be"
305- " URL encoded: %s" % host_part )
306- host_part = unquote_plus (host_part )
307- path_part = unquote_plus (path_part )
308- else :
309- host_part , _ , path_part = _partition (scheme_free , '/' )
300+ host_part , _ , path_part = _partition (scheme_free , '/' )
301+ if not host_part :
302+ host_part = path_part
303+ path_part = ""
310304
311305 if not path_part and '?' in host_part :
312306 raise InvalidURI ("A '/' is required between "
@@ -318,17 +312,24 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
318312 else :
319313 hosts = host_part
320314
315+ if '/' in hosts :
316+ raise InvalidURI ("Any '/' in a unix domain socket must be"
317+ " URL encoded: %s" % host_part )
318+
319+ hosts = unquote_plus (hosts )
321320 nodes = split_hosts (hosts , default_port = default_port )
322321
323322 if path_part :
324-
325323 if path_part [0 ] == '?' :
326- opts = path_part [1 :]
324+ opts = unquote_plus ( path_part [1 :])
327325 else :
328- dbase , _ , opts = _partition (path_part , '?' )
326+ dbase , _ , opts = map ( unquote_plus , _partition (path_part , '?' ) )
329327 if '.' in dbase :
330328 dbase , collection = dbase .split ('.' , 1 )
331329
330+ if _BAD_DB_CHARS .search (dbase ):
331+ raise InvalidURI ('Bad database name "%s"' % dbase )
332+
332333 if opts :
333334 options = split_options (opts , validate , warn )
334335
0 commit comments