#23539: Set Content-Length to 0 for PUT, POST, and PATCH if body is None.
Some http servers will reject PUT, POST, and PATCH requests if they do not have a Content-Length header. Patch by James Rutherford.
This commit is contained in:
parent
29368584d2
commit
b4b000f002
|
@ -433,9 +433,16 @@ HTTPConnection Objects
|
|||
and the selector *url*. If the *body* argument is present, it should be a
|
||||
string of data to send after the headers are finished. Alternatively, it may
|
||||
be an open file object, in which case the contents of the file is sent; this
|
||||
file object should support ``fileno()`` and ``read()`` methods. The header
|
||||
Content-Length is automatically set to the correct value. The *headers*
|
||||
argument should be a mapping of extra HTTP headers to send with the request.
|
||||
file object should support ``fileno()`` and ``read()`` methods. The
|
||||
*headers* argument should be a mapping of extra HTTP headers to send with
|
||||
the request.
|
||||
|
||||
If one is not provided in *headers*, a ``Content-Length`` header is added
|
||||
automatically for all methods if the length of the body can be determined,
|
||||
either from the length of the ``str`` representation, or from the reported
|
||||
size of the file on disk. If *body* is ``None`` the header is not set except
|
||||
for methods that expect a body (``PUT``, ``POST``, and ``PATCH``) in which
|
||||
case it is set to ``0``.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
*body* can be a file object.
|
||||
|
|
|
@ -247,6 +247,10 @@ _MAXHEADERS = 100
|
|||
_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):
|
||||
|
||||
|
@ -1043,19 +1047,25 @@ class HTTPConnection:
|
|||
"""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:
|
||||
# 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)
|
||||
|
@ -1071,8 +1081,8 @@ class HTTPConnection:
|
|||
|
||||
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)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import httplib
|
||||
import itertools
|
||||
import array
|
||||
import StringIO
|
||||
import socket
|
||||
|
@ -122,21 +123,59 @@ class HeaderTests(TestCase):
|
|||
self.content_length = kv[1].strip()
|
||||
list.append(self, item)
|
||||
|
||||
# POST with empty body
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request('POST', '/', '')
|
||||
self.assertEqual(conn._buffer.content_length, '0',
|
||||
'Header Content-Length not set')
|
||||
# Here, we're testing that methods expecting a body get a
|
||||
# content-length set to zero if the body is empty (either None or '')
|
||||
bodies = (None, '')
|
||||
methods_with_body = ('PUT', 'POST', 'PATCH')
|
||||
for method, body in itertools.product(methods_with_body, bodies):
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request(method, '/', body)
|
||||
self.assertEqual(
|
||||
conn._buffer.content_length, '0',
|
||||
'Header Content-Length incorrect on {}'.format(method)
|
||||
)
|
||||
|
||||
# PUT request with empty body
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request('PUT', '/', '')
|
||||
self.assertEqual(conn._buffer.content_length, '0',
|
||||
'Header Content-Length not set')
|
||||
# For these methods, we make sure that content-length is not set when
|
||||
# the body is None because it might cause unexpected behaviour on the
|
||||
# server.
|
||||
methods_without_body = (
|
||||
'GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE',
|
||||
)
|
||||
for method in methods_without_body:
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request(method, '/', None)
|
||||
self.assertEqual(
|
||||
conn._buffer.content_length, None,
|
||||
'Header Content-Length set for empty body on {}'.format(method)
|
||||
)
|
||||
|
||||
# If the body is set to '', that's considered to be "present but
|
||||
# empty" rather than "missing", so content length would be set, even
|
||||
# for methods that don't expect a body.
|
||||
for method in methods_without_body:
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request(method, '/', '')
|
||||
self.assertEqual(
|
||||
conn._buffer.content_length, '0',
|
||||
'Header Content-Length incorrect on {}'.format(method)
|
||||
)
|
||||
|
||||
# If the body is set, make sure Content-Length is set.
|
||||
for method in itertools.chain(methods_without_body, methods_with_body):
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
conn.sock = FakeSocket(None)
|
||||
conn._buffer = ContentLengthChecker()
|
||||
conn.request(method, '/', ' ')
|
||||
self.assertEqual(
|
||||
conn._buffer.content_length, '1',
|
||||
'Header Content-Length incorrect on {}'.format(method)
|
||||
)
|
||||
|
||||
def test_putheader(self):
|
||||
conn = httplib.HTTPConnection('example.com')
|
||||
|
|
|
@ -1171,6 +1171,7 @@ Sam Rushing
|
|||
Mark Russell
|
||||
Rusty Russell
|
||||
Nick Russo
|
||||
James Rutherford
|
||||
Chris Ryland
|
||||
Constantina S.
|
||||
Patrick Sabin
|
||||
|
|
|
@ -21,6 +21,10 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #23539: If body is None, http.client.HTTPConnection.request now sets
|
||||
Content-Length to 0 for PUT, POST, and PATCH headers to avoid 411 errors from
|
||||
some web servers.
|
||||
|
||||
- Issue #23136: _strptime now uniformly handles all days in week 0, including
|
||||
Dec 30 of previous year. Based on patch by Jim Carroll.
|
||||
|
||||
|
|
Loading…
Reference in New Issue