PYTHON-2973 Revert back to using quote_plus/unquote_plus (#767)

This commit is contained in:
Julius Park 2021-10-29 16:30:55 -07:00 committed by GitHub
parent 3c3a85d1bc
commit 42324c69cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 29 additions and 33 deletions

View File

@ -523,7 +523,7 @@ functions:
silent: true
script: |
cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh"
alias urlencode='${python3_binary} -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote(sys.argv[1]))"'
alias urlencode='${python3_binary} -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"'
USER=$(urlencode ${iam_auth_ecs_account})
PASS=$(urlencode ${iam_auth_ecs_secret_access_key})
MONGODB_URI="mongodb://$USER:$PASS@localhost"
@ -554,7 +554,7 @@ functions:
script: |
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh"
alias urlencode='${python3_binary} -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote(sys.argv[1]))"'
alias urlencode='${python3_binary} -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"'
alias jsonkey='${python3_binary} -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" < ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json'
USER=$(jsonkey AccessKeyId)
USER=$(urlencode $USER)

View File

@ -166,13 +166,6 @@ Breaking Changes in 4.0
:exc:`~pymongo.errors.InvalidURI` exception
when it encounters unescaped percent signs in username and password when
parsing MongoDB URIs.
- :class:`~pymongo.mongo_client.MongoClient` now uses
:py::func:`urllib.parse.unquote` rather than
:py:func:`urllib.parse.unquote_plus`,
meaning that plus signs ("+") are no longer converted to spaces (" "). This
means that if you were previously quoting your login information using
quote_plus, you must now switch to quote. Additionally, be aware that this
change only occurs when parsing login information from the URI.
Notable improvements
....................

View File

@ -15,10 +15,10 @@ Username and password must be percent-escaped with
>>> from pymongo import MongoClient
>>> import urllib.parse
>>> username = urllib.parse.quote('user')
>>> username = urllib.parse.quote_plus('user')
>>> username
'user'
>>> password = urllib.parse.quote('pass/word')
>>> password = urllib.parse.quote_plus('pass/word')
>>> password
'pass%2Fword'
>>> MongoClient('mongodb://%s:%s@127.0.0.1' % (username, password))

View File

@ -200,16 +200,6 @@ MongoClient raises exception when given unescaped percent sign in login info
:exc:`~pymongo.errors.InvalidURI` exception
when it encounters unescaped percent signs in username and password.
MongoClient uses `unquote` rather than `unquote_plus` for login info
....................................................................
:class:`~pymongo.mongo_client.MongoClient` now uses
:py:func:`urllib.parse.unquote` rather than
:py:func:`urllib.parse.unquote_plus`, meaning that space characters are no
longer converted to plus signs. This means that if you were previously
quoting your login information using :py:func:`urllib.parse.quote_plus`, you
must now switch to :py:func:`urllib.parse.quote`.
Database
--------

View File

@ -319,6 +319,9 @@ def _authenticate_gssapi(credentials, sock_info):
if password is not None:
if _USE_PRINCIPAL:
# Note that, though we use unquote_plus for unquoting URI
# options, we use quote here. Microsoft's UrlUnescape (used
# by WinKerberos) doesn't support +.
principal = ":".join((quote(username), quote(password)))
result, ctx = kerberos.authGSSClientInit(
service, principal, gssflags=kerberos.GSS_C_MUTUAL_FLAG)

View File

@ -18,7 +18,7 @@ import re
import warnings
import sys
from urllib.parse import unquote, unquote_plus
from urllib.parse import unquote_plus
from pymongo.common import (
SRV_SERVICE_NAME,
@ -47,7 +47,7 @@ def _unquoted_percent(s):
sub = s[i:i+3]
# If unquoting yields the same string this means there was an
# unquoted %.
if unquote(sub) == sub:
if unquote_plus(sub) == sub:
return True
return False
@ -65,14 +65,14 @@ def parse_userinfo(userinfo):
if ('@' in userinfo or userinfo.count(':') > 1 or
_unquoted_percent(userinfo)):
raise InvalidURI("Username and password must be escaped according to "
"RFC 3986, use urllib.parse.quote")
"RFC 3986, use urllib.parse.quote_plus")
user, _, passwd = userinfo.partition(":")
# No password is expected with GSSAPI authentication.
if not user:
raise InvalidURI("The empty string is not valid username.")
return unquote(user), unquote(passwd)
return unquote_plus(user), unquote_plus(passwd)
def parse_ipv6_literal_host(entity, default_port):
@ -430,9 +430,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
.. versionchanged:: 4.0
To better follow RFC 3986, unquoted percent signs ("%") are no longer
supported and plus signs ("+") are no longer decoded into spaces (" ")
when decoding username and password. To avoid these issues, use
:py:func:`urllib.parse.quote` when building the URI.
supported.
.. versionchanged:: 3.9
Added the ``normalize`` parameter.

View File

@ -20,7 +20,7 @@ import sys
sys.path[0:0] = [""]
from urllib.parse import quote
from urllib.parse import quote_plus
from pymongo import MongoClient, ssl_support
from pymongo.errors import (ConfigurationError,
@ -526,7 +526,7 @@ class TestSSL(IntegrationTest):
uri = ('mongodb://%s@%s:%d/?authMechanism='
'MONGODB-X509' % (
quote(MONGODB_X509_USERNAME), host, port))
quote_plus(MONGODB_X509_USERNAME), host, port))
client = MongoClient(uri,
ssl=True,
tlsAllowInvalidCertificates=True,
@ -546,7 +546,7 @@ class TestSSL(IntegrationTest):
# Auth should fail if username and certificate do not match
uri = ('mongodb://%s@%s:%d/?authMechanism='
'MONGODB-X509' % (
quote("not the username"), host, port))
quote_plus("not the username"), host, port))
bad_client = MongoClient(
uri, ssl=True, tlsAllowInvalidCertificates=True,
@ -571,7 +571,7 @@ class TestSSL(IntegrationTest):
# Invalid certificate (using CA certificate as client certificate)
uri = ('mongodb://%s@%s:%d/?authMechanism='
'MONGODB-X509' % (
quote(MONGODB_X509_USERNAME), host, port))
quote_plus(MONGODB_X509_USERNAME), host, port))
try:
connected(MongoClient(uri,
ssl=True,

View File

@ -17,6 +17,7 @@
import copy
import sys
import warnings
from urllib.parse import quote_plus
sys.path[0:0] = [""]
@ -43,7 +44,7 @@ class TestURI(unittest.TestCase):
self.assertTrue(parse_userinfo('user:password'))
self.assertEqual(('us:r', 'p@ssword'),
parse_userinfo('us%3Ar:p%40ssword'))
self.assertEqual(('us+er', 'p+ssword'),
self.assertEqual(('us er', 'p ssword'),
parse_userinfo('us+er:p+ssword'))
self.assertEqual(('us er', 'p ssword'),
parse_userinfo('us%20er:p%20ssword'))
@ -512,6 +513,14 @@ class TestURI(unittest.TestCase):
'quote_plus?'):
parse_uri(uri)
def test_special_chars(self):
user = "user@ /9+:?~!$&'()*+,;="
pwd = "pwd@ /9+:?~!$&'()*+,;="
uri = 'mongodb://%s:%s@localhost' % (quote_plus(user), quote_plus(pwd))
res = parse_uri(uri)
self.assertEqual(user, res['username'])
self.assertEqual(pwd, res['password'])
if __name__ == "__main__":
unittest.main()

View File

@ -143,6 +143,9 @@ def create_test(test, test_workdir):
options['database'] += "." + options['collection']
for elm in auth:
if auth[elm] is not None:
# We have to do this because while the spec requires
# "+"->"+", unquote_plus does "+"->" "
options[elm] = options[elm].replace(" ", "+")
self.assertEqual(auth[elm], options[elm],
"Expected %s but got %s"
% (auth[elm], options[elm]))