[Jython-checkins] jython: Now with the right httplib.py
darjus.loktevic
jython-checkins at python.org
Thu Dec 10 21:40:23 EST 2015
https://hg.python.org/jython/rev/e1384410dd8c
changeset: 7832:e1384410dd8c
user: Darjus Loktevic <darjus at gmail.com>
date: Fri Dec 11 13:40:15 2015 +1100
summary:
Now with the right httplib.py
files:
lib-python/2.7/httplib.py | 167 ++++++++++++++++++-------
1 files changed, 121 insertions(+), 46 deletions(-)
diff --git a/lib-python/2.7/httplib.py b/lib-python/2.7/httplib.py
--- a/lib-python/2.7/httplib.py
+++ b/lib-python/2.7/httplib.py
@@ -68,6 +68,7 @@
from array import array
import os
+import re
import socket
from sys import py3kwarning
from urlparse import urlsplit
@@ -218,6 +219,38 @@
# maximum amount of headers accepted
_MAXHEADERS = 100
+# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2)
+#
+# VCHAR = %x21-7E
+# obs-text = %x80-FF
+# header-field = field-name ":" OWS field-value OWS
+# field-name = token
+# field-value = *( field-content / obs-fold )
+# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+# field-vchar = VCHAR / obs-text
+#
+# obs-fold = CRLF 1*( SP / HTAB )
+# ; obsolete line folding
+# ; see Section 3.2.4
+
+# token = 1*tchar
+#
+# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
+# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+# / DIGIT / ALPHA
+# ; any VCHAR, except delimiters
+#
+# VCHAR defined in http://tools.ietf.org/html/rfc5234#appendix-B.1
+
+# the patterns for both name and value are more leniant than RFC
+# definitions to allow for backwards compatibility
+_is_legal_header_name = re.compile(r'\A[^:\s][^:\r\n]*\Z').match
+_is_illegal_header_value = re.compile(r'\n(?![ \t])|\r(?![ \t\n])').search
+
+# We always set the Content-Length header for these methods because some
+# servers will otherwise respond with a 411
+_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'}
+
class HTTPMessage(mimetools.Message):
@@ -313,6 +346,11 @@
hlist.append(line)
self.addheader(headerseen, line[len(headerseen)+1:].strip())
continue
+ elif headerseen is not None:
+ # An empty header name. These aren't allowed in HTTP, but it's
+ # probably a benign mistake. Don't add the header, just keep
+ # going.
+ continue
else:
# It's not a header line; throw it back and stop here.
if not self.dict:
@@ -522,9 +560,10 @@
return True
def close(self):
- if self.fp:
- self.fp.close()
+ fp = self.fp
+ if fp:
self.fp = None
+ fp.close()
def isclosed(self):
# NOTE: it is possible that we will not ever call self.close(). This
@@ -723,7 +762,7 @@
endpoint passed to set_tunnel. This is done by sending a HTTP CONNECT
request to the proxy server when the connection is established.
- This method must be called before the HTML connection has been
+ This method must be called before the HTTP connection has been
established.
The headers argument should be a mapping of extra HTTP headers
@@ -733,8 +772,7 @@
if self.sock:
raise RuntimeError("Can't setup tunnel for established connection.")
- self._tunnel_host = host
- self._tunnel_port = port
+ self._tunnel_host, self._tunnel_port = self._get_hostport(host, port)
if headers:
self._tunnel_headers = headers
else:
@@ -763,8 +801,8 @@
self.debuglevel = level
def _tunnel(self):
- (host, port) = self._get_hostport(self._tunnel_host, self._tunnel_port)
- self.send("CONNECT %s:%d HTTP/1.0\r\n" % (host, port))
+ self.send("CONNECT %s:%d HTTP/1.0\r\n" % (self._tunnel_host,
+ self._tunnel_port))
for header, value in self._tunnel_headers.iteritems():
self.send("%s: %s\r\n" % (header, value))
self.send("\r\n")
@@ -772,6 +810,11 @@
method = self._method)
(version, code, message) = response._read_status()
+ if version == "HTTP/0.9":
+ # HTTP/0.9 doesn't support the CONNECT verb, so if httplib has
+ # concluded HTTP/0.9 is being used something has gone wrong.
+ self.close()
+ raise socket.error("Invalid response from tunnel request")
if code != 200:
self.close()
raise socket.error("Tunnel connection failed: %d %s" % (code,
@@ -797,13 +840,17 @@
def close(self):
"""Close the connection to the HTTP server."""
- if self.sock:
- self.sock.close() # close it manually... there may be other refs
- self.sock = None
- if self.__response:
- self.__response.close()
- self.__response = None
self.__state = _CS_IDLE
+ try:
+ sock = self.sock
+ if sock:
+ self.sock = None
+ sock.close() # close it manually... there may be other refs
+ finally:
+ response = self.__response
+ if response:
+ self.__response = None
+ response.close()
def send(self, data):
"""Send `data' to the server."""
@@ -978,7 +1025,16 @@
if self.__state != _CS_REQ_STARTED:
raise CannotSendHeader()
- hdr = '%s: %s' % (header, '\r\n\t'.join([str(v) for v in values]))
+ header = '%s' % header
+ if not _is_legal_header_name(header):
+ raise ValueError('Invalid header name %r' % (header,))
+
+ values = [str(v) for v in values]
+ for one_value in values:
+ if _is_illegal_header_value(one_value):
+ raise ValueError('Invalid header value %r' % (one_value,))
+
+ hdr = '%s: %s' % (header, '\r\n\t'.join(values))
self._output(hdr)
def endheaders(self, message_body=None):
@@ -1000,19 +1056,25 @@
"""Send a complete request to the server."""
self._send_request(method, url, body, headers)
- def _set_content_length(self, body):
- # Set the content-length based on the body.
+ def _set_content_length(self, body, method):
+ # Set the content-length based on the body. If the body is "empty", we
+ # set Content-Length: 0 for methods that expect a body (RFC 7230,
+ # Section 3.3.2). If the body is set for other methods, we set the
+ # header provided we can figure out what the length is.
thelen = None
- try:
- thelen = str(len(body))
- except TypeError, te:
- # If this is a file-like object, try to
- # fstat its file descriptor
+ if body is None and method.upper() in _METHODS_EXPECTING_BODY:
+ thelen = '0'
+ elif body is not None:
try:
- thelen = str(os.fstat(body.fileno()).st_size)
- except (AttributeError, OSError):
- # Don't send a length if this failed
- if self.debuglevel > 0: print "Cannot stat!!"
+ thelen = str(len(body))
+ except (TypeError, AttributeError):
+ # If this is a file-like object, try to
+ # fstat its file descriptor
+ try:
+ thelen = str(os.fstat(body.fileno()).st_size)
+ except (AttributeError, OSError):
+ # Don't send a length if this failed
+ if self.debuglevel > 0: print "Cannot stat!!"
if thelen is not None:
self.putheader('Content-Length', thelen)
@@ -1028,8 +1090,8 @@
self.putrequest(method, url, **skips)
- if body is not None and 'content-length' not in header_names:
- self._set_content_length(body)
+ if 'content-length' not in header_names:
+ self._set_content_length(body, method)
for hdr, value in headers.iteritems():
self.putheader(hdr, value)
self.endheaders(body)
@@ -1070,18 +1132,22 @@
kwds["buffering"] = True;
response = self.response_class(*args, **kwds)
- response.begin()
- assert response.will_close != _UNKNOWN
- self.__state = _CS_IDLE
+ try:
+ response.begin()
+ assert response.will_close != _UNKNOWN
+ self.__state = _CS_IDLE
- if response.will_close:
- # this effectively passes the connection to the response
- self.close()
- else:
- # remember this, so we can tell when it is complete
- self.__response = response
+ if response.will_close:
+ # this effectively passes the connection to the response
+ self.close()
+ else:
+ # remember this, so we can tell when it is complete
+ self.__response = response
- return response
+ return response
+ except:
+ response.close()
+ raise
class HTTP:
@@ -1125,7 +1191,7 @@
"Accept arguments to set the host/port, since the superclass doesn't."
if host is not None:
- self._conn._set_hostport(host, port)
+ (self._conn.host, self._conn.port) = self._conn._get_hostport(host, port)
self._conn.connect()
def getfile(self):
@@ -1187,21 +1253,29 @@
def __init__(self, host, port=None, key_file=None, cert_file=None,
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None):
+ source_address=None, context=None):
HTTPConnection.__init__(self, host, port, strict, timeout,
source_address)
self.key_file = key_file
self.cert_file = cert_file
+ if context is None:
+ context = ssl._create_default_https_context()
+ if key_file or cert_file:
+ context.load_cert_chain(cert_file, key_file)
+ self._context = context
def connect(self):
"Connect to a host on a given (SSL) port."
- sock = self._create_connection((self.host, self.port),
- self.timeout, self.source_address)
+ HTTPConnection.connect(self)
+
if self._tunnel_host:
- self.sock = sock
- self._tunnel()
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
+ server_hostname = self._tunnel_host
+ else:
+ server_hostname = self.host
+
+ self.sock = self._context.wrap_socket(self.sock,
+ server_hostname=server_hostname)
__all__.append("HTTPSConnection")
@@ -1216,14 +1290,15 @@
_connection_class = HTTPSConnection
def __init__(self, host='', port=None, key_file=None, cert_file=None,
- strict=None):
+ strict=None, context=None):
# provide a default host, pass the X509 cert info
# urf. compensate for bad input.
if port == 0:
port = None
self._setup(self._connection_class(host, port, key_file,
- cert_file, strict))
+ cert_file, strict,
+ context=context))
# we never actually use these for anything, but we keep them
# here for compatibility with post-1.5.2 CVS.
--
Repository URL: https://hg.python.org/jython
More information about the Jython-checkins
mailing list