diff -ru tmp/Python-2.7.3/Doc/library/httplib.rst Python-2.7.3/Doc/library/httplib.rst --- tmp/Python-2.7.3/Doc/library/httplib.rst 2012-04-10 00:07:29.000000000 +0100 +++ Python-2.7.3/Doc/library/httplib.rst 2012-07-11 11:37:25.000000000 +0100 @@ -115,6 +115,21 @@ .. versionadded:: 2.0 +.. exception:: ExpectationFailed + + A subclass of :exc:`HTTPException`, raised if a server returns a 417 + (Expectation Failed) after sending an Expect: 100-continue header + + .. versionadded:: 2.7.4 + +.. exception:: ContinueExpected + + A subclass of :exc:`HTTPException`, raised if a server returns + something different than a 100 (Continue) or a 417 + (Expectation Failed) after sending an Expect: 100-continue header + + .. versionadded:: 2.7.4 + .. exception:: NotConnected A subclass of :exc:`HTTPException`. @@ -611,4 +626,3 @@ >>> data 'Redirecting to http://bugs.python.org/issue12524' >>> conn.close() - diff -ru tmp/Python-2.7.3/Lib/httplib.py Python-2.7.3/Lib/httplib.py --- tmp/Python-2.7.3/Lib/httplib.py 2012-04-10 00:07:30.000000000 +0100 +++ Python-2.7.3/Lib/httplib.py 2012-07-11 12:26:27.000000000 +0100 @@ -402,22 +402,7 @@ # we've already started reading the response return - # read until we get a non-100 response - while True: - version, status, reason = self._read_status() - if status != CONTINUE: - break - # skip the header from the 100 response - while True: - skip = self.fp.readline(_MAXLINE + 1) - if len(skip) > _MAXLINE: - raise LineTooLong("header line") - skip = skip.strip() - if not skip: - break - if self.debuglevel > 0: - print "header:", skip - + version, status, reason = self._read_status() self.status = status self.reason = reason.strip() if version == 'HTTP/1.0': @@ -983,13 +968,36 @@ if 'accept-encoding' in header_names: skips['skip_accept_encoding'] = 1 + expect_continue = False + for hdr, val in headers.iteritems(): + if 'expect' == hdr.lower() and '100-continue' in val.lower(): + expect_continue = True + self.putrequest(method, url, **skips) if body and ('content-length' not in header_names): self._set_content_length(body) for hdr, value in headers.iteritems(): self.putheader(hdr, value) - self.endheaders(body) + + # If an Expect: 100-continue was sent, we need to check for a 417 + # Expectation Failed to avoid unecessarily sending the body + # See RFC 2616 8.2.3 + if expect_continue: + if not body: + raise HTTPException("A body is required when expecting " + "100-continue") + self.endheaders() + resp = self.getresponse() + resp.read() + self.__state = _CS_REQ_SENT + if resp.status == EXPECTATION_FAILED: + raise ExpectationFailed() + elif resp.status != CONTINUE: + raise ContinueExpected(resp.status) + self.send(body) + else: + self.endheaders(body) def getresponse(self, buffering=False): "Get the response from the server." @@ -1200,6 +1208,12 @@ # or define self.args. Otherwise, str() will fail. pass +class ExpectationFailed(HTTPException): + pass + +class ContinueExpected(HTTPException): + pass + class NotConnected(HTTPException): pass diff -ru tmp/Python-2.7.3/Lib/test/test_httplib.py Python-2.7.3/Lib/test/test_httplib.py --- tmp/Python-2.7.3/Lib/test/test_httplib.py 2012-04-10 00:07:31.000000000 +0100 +++ Python-2.7.3/Lib/test/test_httplib.py 2012-07-11 12:14:25.000000000 +0100 @@ -1,6 +1,5 @@ import httplib import array -import httplib import StringIO import socket import errno @@ -26,6 +25,9 @@ raise httplib.UnimplementedFileMode() return self.fileclass(self.text) + def close(self): + pass + class EPipeSocket(FakeSocket): def __init__(self, text, pipe_trigger): @@ -117,6 +119,43 @@ self.assertTrue(sock.data.startswith(expected)) + +class OneHundredContinueTests(TestCase): + def test_continue_sent(self): + body = "HTTP/1.1 100 Continue\r\n\r\n" + conn = httplib.HTTPConnection('example.com') + sock = FakeSocket(body) + conn.sock = sock + + body = 'testdata' + headers = {'Expect': '100-continue'} + conn.request(method='POST', url='/foo', body=body, headers=headers) + self.assertTrue(body in sock.data) + + def test_expectation_failed(self): + body = "HTTP/1.1 417 Expectation Failed\r\n\r\n" + conn = httplib.HTTPConnection('example.com') + sock = FakeSocket(body) + conn.sock = sock + + body = 'testdata' + headers = {'Expect': '100-continue'} + self.assertRaises(httplib.ExpectationFailed, conn.request, + method='POST', url='/foo', body=body, headers=headers) + self.assertTrue(body not in sock.data) + + def test_continue_expected(self): + body = "HTTP/1.1 401 Unauthorized\r\n\r\n" + conn = httplib.HTTPConnection('example.com') + sock = FakeSocket(body) + conn.sock = sock + + body = 'testdata' + headers = {'Expect': '100-continue'} + self.assertRaises(httplib.ContinueExpected, conn.request, + method='POST', url='/foo', body=body, headers=headers) + self.assertTrue(body not in sock.data) + class BasicTest(TestCase): def test_status_lines(self): # Test HTTP status lines @@ -138,7 +177,7 @@ self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''') def test_partial_reads(self): - # if we have a lenght, the system knows when to close itself + # if we have a length, the system knows when to close itself # same behaviour than when we read the whole thing with read() body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText" sock = FakeSocket(body) @@ -465,7 +504,8 @@ def test_main(verbose=None): test_support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest, - HTTPSTimeoutTest, SourceAddressTest) + HTTPSTimeoutTest, SourceAddressTest, + OneHundredContinueTests) if __name__ == '__main__': test_main() diff -ru tmp/Python-2.7.3/Misc/NEWS Python-2.7.3/Misc/NEWS --- tmp/Python-2.7.3/Misc/NEWS 2012-04-10 00:07:33.000000000 +0100 +++ Python-2.7.3/Misc/NEWS 2012-07-11 10:50:48.000000000 +0100 @@ -5204,6 +5204,8 @@ - Issue #2338: Create imp.reload() to help with transitioning to Python 3.0 as the reload() built-in has been removed. +- Issue#1346874: Added 100-continue support to httplib + - Changed code in the following modules/packages to remove warnings raised while running under the ``-3`` flag: aifc, asynchat, asyncore, bdb, bsddb, ConfigParser, cookielib, csv, difflib, distutils, DocXMLRPCServer, email,