Skip to content

bpo-5038: Use iterable objects in case of mmap file source #11904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion Lib/test/test_urllib2_localnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import threading
import unittest
import hashlib
import mmap

from test import support

Expand Down Expand Up @@ -153,6 +154,25 @@ def _return_auth_challenge(self, request_handler):
request_handler.wfile.write(b"Proxy Authentication Required.")
return False

def _read_post_body(self, request_handler):
encoding = request_handler.headers.get("Transfer-Encoding")
if encoding is None:
length = int(request_handler.headers.get("Content-Length"))
if encoding == "chunked":
request = b""
while True:
line = request_handler.rfile.readline()
length = int(line, 16)
if length == 0:
request_handler.rfile.readline()
break
request = request + request_handler.rfile.read(length)
request_handler.rfile.read(2)
else:
request = self.rfile.read(length)

return request

def handle_request(self, request_handler):
"""Performs digest authentication on the given HTTP request
handler. Returns True if authentication was successful, False
Expand All @@ -162,6 +182,9 @@ def handle_request(self, request_handler):
disabled and this method will always return True.
"""

if request_handler.is_POST == True:
request_handler.post_body = self._read_post_body(request_handler)

if len(self._users) == 0:
return True

Expand Down Expand Up @@ -238,7 +261,39 @@ def do_GET(self):
# Request Unauthorized
self.do_AUTHHEAD()


def do_POST(self):
encoding = self.headers.get("Transfer-Encoding")
if encoding is None:
length = int(self.headers.get("Content-Length"))
if encoding == "chunked":
request = b""
while True:
line = self.rfile.readline()
length = int(line, 16)
if length == 0:
self.rfile.readline()
break
request = request + self.rfile.read(length)
self.rfile.read(2)
length = len(request)
else:
request = self.rfile.read(length)
if length > 0:
if not self.headers.get("Authorization", ""):
self.do_AUTHHEAD()
self.wfile.write(b"No Auth header received")
return
elif self.headers.get(
"Authorization", "") == "Basic " + self.ENCODED_AUTH:
self.send_response(200, "OK")
self.end_headers()
self.wfile.write(request)
else:
self.do_AUTHHEAD()
return
else:
self.send_response(400, "Empty request data")
self.end_headers()

# Proxy test infrastructure

Expand All @@ -264,6 +319,7 @@ def do_GET(self):
(scm, netloc, path, params, query, fragment) = urllib.parse.urlparse(
self.path, "http")
self.short_path = path
self.is_POST = False
if self.digest_auth_handler.handle_request(self):
self.send_response(200, "OK")
self.send_header("Content-Type", "text/html")
Expand All @@ -273,6 +329,22 @@ def do_GET(self):
self.wfile.write(b"Our apologies, but our server is down due to "
b"a sudden zombie invasion.")

def do_POST(self):
(scm, netloc, path, params, query, fragment) = urllib.parse.urlparse(
self.path, "http")
self.short_path = path
self.is_POST = True
if self.digest_auth_handler.handle_request(self):
request = self.post_body
if len(request) > 0:
self.send_response(200, "OK")
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(request)
else:
self.send_response(400, "Empty request data")
self.end_headers()

# Test cases

class BasicAuthTests(unittest.TestCase):
Expand Down Expand Up @@ -314,6 +386,21 @@ def test_basic_auth_httperror(self):
urllib.request.install_opener(urllib.request.build_opener(ah))
self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, self.server_url)

def test_basic_auth_mmap_post(self):
data = "field=value".encode("ascii")
mem = mmap.mmap(-1, len(data))
mem[:] = data
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm=self.REALM, uri=self.server_url,
user=self.USER, passwd=self.PASSWD)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
try:
response = urllib.request.urlopen(self.server_url, mem)
except urllib.error.HTTPError:
self.fail("Basic auth failed for the url: %s" % self.server_url)
response_data = response.read()
self.assertEqual(response_data, data)

class ProxyAuthTests(unittest.TestCase):
URL = "http://localhost"
Expand Down Expand Up @@ -392,6 +479,15 @@ def test_proxy_qop_auth_int_works_or_throws_urlerror(self):
pass
result.close()

def test_proxy_auth_mmap_post(self):
data = "field=value".encode("ascii")
mem = mmap.mmap(-1, len(data))
mem[:] = data
self.proxy_digest_handler.add_password(self.REALM, self.URL,
self.USER, self.PASSWD)
response = self.opener.open(self.URL, mem)
self.assertEqual(response.read(), data)


def GetRequestHandler(responses):

Expand Down
27 changes: 27 additions & 0 deletions Lib/urllib/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,19 @@ def is_authenticated(self, authuri):
if self.is_suburi(uri, reduced_authuri):
return self.authenticated[uri]

class _AuthHandlerFileReiterator:
def __init__(self, file, startPosition):
self.file = file
self.startPosition = startPosition
self.chunksize = 8196

def __iter__(self):
self.file.seek(self.startPosition)
while True:
chunk = self.file.read(self.chunksize)
yield chunk
if len(chunk) < self.chunksize:
break

class AbstractBasicAuthHandler:

Expand All @@ -956,6 +969,7 @@ def __init__(self, password_mgr=None):
password_mgr = HTTPPasswordMgr()
self.passwd = password_mgr
self.add_password = self.passwd.add_password
self.file_start_position = 0

def http_error_auth_reqed(self, authreq, host, req, headers):
# host may be an authority (without userinfo) or a URL with an
Expand Down Expand Up @@ -987,11 +1001,15 @@ def retry_http_basic_auth(self, host, req, realm):
if req.get_header(self.auth_header, None) == auth:
return None
req.add_unredirected_header(self.auth_header, auth)
if hasattr(req._data, "read"):
req._data = _AuthHandlerFileReiterator(req._data, self.file_start_position)
return self.parent.open(req, timeout=req.timeout)
else:
return None

def http_request(self, req):
if hasattr(req._data, "read"):
self.file_start_position = req._data.tell()
if (not hasattr(self.passwd, 'is_authenticated') or
not self.passwd.is_authenticated(req.full_url)):
return req
Expand Down Expand Up @@ -1066,6 +1084,7 @@ def __init__(self, passwd=None):
self.retried = 0
self.nonce_count = 0
self.last_nonce = None
self.file_start_position = 0

def reset_retry_count(self):
self.retried = 0
Expand Down Expand Up @@ -1099,6 +1118,8 @@ def retry_http_digest_auth(self, req, auth):
if req.headers.get(self.auth_header, None) == auth_val:
return None
req.add_unredirected_header(self.auth_header, auth_val)
if hasattr(req._data, "read"):
req._data = _AuthHandlerFileReiterator(req._data, self.file_start_position)
resp = self.parent.open(req, timeout=req.timeout)
return resp

Expand Down Expand Up @@ -1190,6 +1211,12 @@ def get_entity_digest(self, data, chal):
# XXX not implemented yet
return None

def http_request(self, req):
if hasattr(req._data, "read"):
self.file_start_position = req._data.tell()
return req

https_request = http_request

class HTTPDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
"""An authentication protocol defined by RFC 2069
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
urllib now allows mmap'ed files to be resent when the http server responds with 401 unauthorized for basic and digest authentication.
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy