From bcd833f30f77160e321056fa548d76e2abe26701 Mon Sep 17 00:00:00 2001 From: Senthil Kumaran Date: Wed, 11 Jan 2012 00:09:24 +0800 Subject: [PATCH] =?UTF-8?q?-=20Issue=20#13642:=20Unquote=20before=20b64enc?= =?UTF-8?q?oding=20user:password=20during=20Basic=20=20=20Authentication.?= =?UTF-8?q?=20Patch=20contributed=20by=20Joonas=20Kuorilehto=20and=20Miche?= =?UTF-8?q?le=20Orr=C3=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_urllib.py | 54 ++++++++++++++++++++++++++++++++++++++--- Lib/urllib.py | 11 +++++---- Misc/NEWS | 3 +++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index db908399f43..085eecf0f60 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -3,13 +3,16 @@ import urllib import httplib import unittest -from test import test_support import os import sys import mimetools import tempfile import StringIO +from test import test_support +from base64 import b64encode + + def hexescape(char): """Escape char as RFC 2396 specifies""" hex_repr = hex(ord(char))[2:].upper() @@ -22,8 +25,9 @@ class FakeHTTPMixin(object): def fakehttp(self, fakedata): class FakeSocket(StringIO.StringIO): - def sendall(self, str): - pass + def sendall(self, data): + FakeHTTPConnection.buf = data + def makefile(self, *args, **kwds): return self @@ -38,9 +42,15 @@ class FakeHTTPMixin(object): return StringIO.StringIO.readline(self, length) class FakeHTTPConnection(httplib.HTTPConnection): + + # buffer to store data for verification in urlopen tests. + buf = "" + def connect(self): self.sock = FakeSocket(fakedata) + assert httplib.HTTP._connection_class == httplib.HTTPConnection + httplib.HTTP._connection_class = FakeHTTPConnection def unfakehttp(self): @@ -209,6 +219,41 @@ Content-Type: text/html; charset=iso-8859-1 finally: self.unfakehttp() + def test_userpass_inurl(self): + self.fakehttp('Hello!') + try: + fakehttp_wrapper = httplib.HTTP._connection_class + fp = urllib.urlopen("http://user:pass@python.org/") + authorization = ("Authorization: Basic %s\r\n" % + b64encode('user:pass')) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + self.assertEqual(fp.geturl(), 'http://user:pass@python.org/') + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + def test_userpass_with_spaces_inurl(self): + self.fakehttp('Hello!') + try: + url = "http://a b:c d@python.org/" + fakehttp_wrapper = httplib.HTTP._connection_class + authorization = ("Authorization: Basic %s\r\n" % + b64encode('a b:c d')) + fp = urllib.urlopen(url) + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf) + self.assertEqual(fp.readline(), "Hello!") + self.assertEqual(fp.readline(), "") + # the spaces are quoted in URL so no match + self.assertNotEqual(fp.geturl(), url) + self.assertEqual(fp.getcode(), 200) + finally: + self.unfakehttp() + + class urlretrieve_FileTests(unittest.TestCase): """Test urllib.urlretrieve() on local files""" @@ -716,6 +761,9 @@ class Utility_Tests(unittest.TestCase): self.assertEqual(('user', 'a\fb'),urllib.splitpasswd('user:a\fb')) self.assertEqual(('user', 'a\vb'),urllib.splitpasswd('user:a\vb')) self.assertEqual(('user', 'a:b'),urllib.splitpasswd('user:a:b')) + self.assertEqual(('user', 'a b'),urllib.splitpasswd('user:a b')) + self.assertEqual(('user 2', 'ab'),urllib.splitpasswd('user 2:ab')) + self.assertEqual(('user+1', 'a+b'),urllib.splitpasswd('user+1:a+b')) class URLopener_Tests(unittest.TestCase): diff --git a/Lib/urllib.py b/Lib/urllib.py index 7b0a81cade7..c7af8ec4fd9 100644 --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -27,6 +27,8 @@ import socket import os import time import sys +import base64 + from urlparse import urljoin as basejoin __all__ = ["urlopen", "URLopener", "FancyURLopener", "urlretrieve", @@ -318,13 +320,13 @@ class URLopener: if not host: raise IOError, ('http error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -408,12 +410,12 @@ class URLopener: #print "proxy via https:", host, selector if not host: raise IOError, ('https error', 'no host given') if proxy_passwd: - import base64 + proxy_passwd = unquote(proxy_passwd) proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: - import base64 + user_passwd = unquote(user_passwd) auth = base64.b64encode(user_passwd).strip() else: auth = None @@ -589,7 +591,6 @@ class URLopener: time.gmtime(time.time()))) msg.append('Content-type: %s' % type) if encoding == 'base64': - import base64 data = base64.decodestring(data) else: data = unquote(data) diff --git a/Misc/NEWS b/Misc/NEWS index 8bd308bd269..aa32dd75872 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -89,6 +89,9 @@ Core and Builtins Library ------- +- Issue #13642: Unquote before b64encoding user:password during Basic + Authentication. Patch contributed by Joonas Kuorilehto and Michele OrrĂ¹. + - Issue #13636: Weak ciphers are now disabled by default in the ssl module (except when SSLv2 is explicitly asked for).