mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	SF bug 874842 and patch 997626: httplib bugs
Hack httplib to work with broken Akamai proxies. Make sure that httplib doesn't add extract Accept-Encoding or Content-Length headers if the client has already set them.
This commit is contained in:
		
							parent
							
								
									dc54f2be3f
								
							
						
					
					
						commit
						2c178253bd
					
				
					 2 changed files with 64 additions and 11 deletions
				
			
		|  | @ -344,6 +344,7 @@ def begin(self): | ||||||
|             self.will_close = 1 |             self.will_close = 1 | ||||||
| 
 | 
 | ||||||
|     def _check_close(self): |     def _check_close(self): | ||||||
|  |         conn = self.msg.getheader('connection') | ||||||
|         if self.version == 11: |         if self.version == 11: | ||||||
|             # An HTTP/1.1 proxy is assumed to stay open unless |             # An HTTP/1.1 proxy is assumed to stay open unless | ||||||
|             # explicitly closed. |             # explicitly closed. | ||||||
|  | @ -352,13 +353,18 @@ def _check_close(self): | ||||||
|                 return True |                 return True | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         # An HTTP/1.0 response with a Connection header is probably |         # Some HTTP/1.0 implementations have support for persistent | ||||||
|         # the result of a confused proxy.  Ignore it. |         # connections, using rules different than HTTP/1.1. | ||||||
| 
 | 
 | ||||||
|         # For older HTTP, Keep-Alive indiciates persistent connection. |         # For older HTTP, Keep-Alive indiciates persistent connection. | ||||||
|         if self.msg.getheader('keep-alive'): |         if self.msg.getheader('keep-alive'): | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|  |         # At least Akamai returns a "Connection: Keep-Alive" header, | ||||||
|  |         # which was supposed to be sent by the client. | ||||||
|  |         if conn and "keep-alive" in conn.lower(): | ||||||
|  |             return False | ||||||
|  | 
 | ||||||
|         # Proxy-Connection is a netscape hack. |         # Proxy-Connection is a netscape hack. | ||||||
|         pconn = self.msg.getheader('proxy-connection') |         pconn = self.msg.getheader('proxy-connection') | ||||||
|         if pconn and "keep-alive" in pconn.lower(): |         if pconn and "keep-alive" in pconn.lower(): | ||||||
|  | @ -381,6 +387,8 @@ def isclosed(self): | ||||||
|         #          called, meaning self.isclosed() is meaningful. |         #          called, meaning self.isclosed() is meaningful. | ||||||
|         return self.fp is None |         return self.fp is None | ||||||
| 
 | 
 | ||||||
|  |     # XXX It would be nice to have readline and __iter__ for this, too. | ||||||
|  | 
 | ||||||
|     def read(self, amt=None): |     def read(self, amt=None): | ||||||
|         if self.fp is None: |         if self.fp is None: | ||||||
|             return '' |             return '' | ||||||
|  | @ -728,15 +736,17 @@ def request(self, method, url, body=None, headers={}): | ||||||
|             self._send_request(method, url, body, headers) |             self._send_request(method, url, body, headers) | ||||||
| 
 | 
 | ||||||
|     def _send_request(self, method, url, body, headers): |     def _send_request(self, method, url, body, headers): | ||||||
|         # If headers already contains a host header, then define the |         # honour explicitly requested Host: and Accept-Encoding headers | ||||||
|         # optional skip_host argument to putrequest().  The check is |         header_names = dict.fromkeys([k.lower() for k in headers]) | ||||||
|         # harder because field names are case insensitive. |         skips = {} | ||||||
|         if 'host' in [k.lower() for k in headers]: |         if 'host' in header_names: | ||||||
|             self.putrequest(method, url, skip_host=1) |             skips['skip_host'] = 1 | ||||||
|         else: |         if 'accept-encoding' in header_names: | ||||||
|             self.putrequest(method, url) |             skips['skip_accept_encoding'] = 1 | ||||||
| 
 | 
 | ||||||
|         if body: |         self.putrequest(method, url, **skips) | ||||||
|  | 
 | ||||||
|  |         if body and ('content-length' not in header_names): | ||||||
|             self.putheader('Content-Length', str(len(body))) |             self.putheader('Content-Length', str(len(body))) | ||||||
|         for hdr, value in headers.iteritems(): |         for hdr, value in headers.iteritems(): | ||||||
|             self.putheader(hdr, value) |             self.putheader(hdr, value) | ||||||
|  |  | ||||||
|  | @ -2,13 +2,18 @@ | ||||||
| import StringIO | import StringIO | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| from test.test_support import verify,verbose | from unittest import TestCase | ||||||
|  | 
 | ||||||
|  | from test import test_support | ||||||
| 
 | 
 | ||||||
| class FakeSocket: | class FakeSocket: | ||||||
|     def __init__(self, text, fileclass=StringIO.StringIO): |     def __init__(self, text, fileclass=StringIO.StringIO): | ||||||
|         self.text = text |         self.text = text | ||||||
|         self.fileclass = fileclass |         self.fileclass = fileclass | ||||||
| 
 | 
 | ||||||
|  |     def sendall(self, data): | ||||||
|  |         self.data = data | ||||||
|  | 
 | ||||||
|     def makefile(self, mode, bufsize=None): |     def makefile(self, mode, bufsize=None): | ||||||
|         if mode != 'r' and mode != 'rb': |         if mode != 'r' and mode != 'rb': | ||||||
|             raise httplib.UnimplementedFileMode() |             raise httplib.UnimplementedFileMode() | ||||||
|  | @ -32,6 +37,39 @@ def readline(self, length=None): | ||||||
|             raise AssertionError('caller tried to read past EOF') |             raise AssertionError('caller tried to read past EOF') | ||||||
|         return data |         return data | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class HeaderTests(TestCase): | ||||||
|  |     def test_auto_headers(self): | ||||||
|  |         # Some headers are added automatically, but should not be added by | ||||||
|  |         # .request() if they are explicitly set. | ||||||
|  | 
 | ||||||
|  |         import httplib | ||||||
|  | 
 | ||||||
|  |         class HeaderCountingBuffer(list): | ||||||
|  |             def __init__(self): | ||||||
|  |                 self.count = {} | ||||||
|  |             def append(self, item): | ||||||
|  |                 kv = item.split(':') | ||||||
|  |                 if len(kv) > 1: | ||||||
|  |                     # item is a 'Key: Value' header string | ||||||
|  |                     lcKey = kv[0].lower() | ||||||
|  |                     self.count.setdefault(lcKey, 0) | ||||||
|  |                     self.count[lcKey] += 1 | ||||||
|  |                 list.append(self, item) | ||||||
|  | 
 | ||||||
|  |         for explicit_header in True, False: | ||||||
|  |             for header in 'Content-length', 'Host', 'Accept-encoding': | ||||||
|  |                 conn = httplib.HTTPConnection('example.com') | ||||||
|  |                 conn.sock = FakeSocket('blahblahblah') | ||||||
|  |                 conn._buffer = HeaderCountingBuffer() | ||||||
|  | 
 | ||||||
|  |                 body = 'spamspamspam' | ||||||
|  |                 headers = {} | ||||||
|  |                 if explicit_header: | ||||||
|  |                     headers[header] = str(len(body)) | ||||||
|  |                 conn.request('POST', '/', body, headers) | ||||||
|  |                 self.assertEqual(conn._buffer.count[header.lower()], 1) | ||||||
|  | 
 | ||||||
| # Collect output to a buffer so that we don't have to cope with line-ending | # Collect output to a buffer so that we don't have to cope with line-ending | ||||||
| # issues across platforms.  Specifically, the headers will have \r\n pairs | # issues across platforms.  Specifically, the headers will have \r\n pairs | ||||||
| # and some platforms will strip them from the output file. | # and some platforms will strip them from the output file. | ||||||
|  | @ -110,4 +148,9 @@ def _test(): | ||||||
|         raise AssertionError, "Did not expect response from HEAD request" |         raise AssertionError, "Did not expect response from HEAD request" | ||||||
|     resp.close() |     resp.close() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def test_main(verbose=None): | ||||||
|  |     tests = [HeaderTests,] | ||||||
|  |     test_support.run_unittest(*tests) | ||||||
|  | 
 | ||||||
| test() | test() | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jeremy Hylton
						Jeremy Hylton