mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
Issue1491 - BaseHTTPServer incorrectly implements response code 100
This commit is contained in:
parent
748cacee46
commit
0f476d49f8
4 changed files with 151 additions and 3 deletions
|
@ -155,6 +155,17 @@ of which this module provides three different variants:
|
||||||
This method will parse and dispatch the request to the appropriate
|
This method will parse and dispatch the request to the appropriate
|
||||||
:meth:`do_\*` method. You should never need to override it.
|
:meth:`do_\*` method. You should never need to override it.
|
||||||
|
|
||||||
|
.. method:: handle_expect_100()
|
||||||
|
|
||||||
|
When a HTTP/1.1 compliant server receives a ``Expect: 100-continue``
|
||||||
|
request header it responds back with a ``100 Continue`` followed by ``200
|
||||||
|
OK`` headers.
|
||||||
|
This method can be overridden to raise an error if the server does not
|
||||||
|
want the client to continue. For e.g. server can chose to send ``417
|
||||||
|
Expectation Failed`` as a response header and ``return False``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
.. method:: send_error(code, message=None)
|
.. method:: send_error(code, message=None)
|
||||||
|
|
||||||
Sends and logs a complete error reply to the client. The numeric *code*
|
Sends and logs a complete error reply to the client. The numeric *code*
|
||||||
|
@ -174,6 +185,15 @@ of which this module provides three different variants:
|
||||||
Writes a specific HTTP header to the output stream. *keyword* should
|
Writes a specific HTTP header to the output stream. *keyword* should
|
||||||
specify the header keyword, with *value* specifying its value.
|
specify the header keyword, with *value* specifying its value.
|
||||||
|
|
||||||
|
.. method:: send_response_only(code, message=None)
|
||||||
|
|
||||||
|
Sends the reponse header only, used for the purposes when ``100
|
||||||
|
Continue`` response is sent by the server to the client. If the *message*
|
||||||
|
is not specified, the HTTP message corresponding the response *code* is
|
||||||
|
sent.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
.. method:: end_headers()
|
.. method:: end_headers()
|
||||||
|
|
||||||
Sends a blank line, indicating the end of the HTTP headers in the
|
Sends a blank line, indicating the end of the HTTP headers in the
|
||||||
|
|
|
@ -322,6 +322,30 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
elif (conntype.lower() == 'keep-alive' and
|
elif (conntype.lower() == 'keep-alive' and
|
||||||
self.protocol_version >= "HTTP/1.1"):
|
self.protocol_version >= "HTTP/1.1"):
|
||||||
self.close_connection = 0
|
self.close_connection = 0
|
||||||
|
# Examine the headers and look for an Expect directive
|
||||||
|
expect = self.headers.get('Expect', "")
|
||||||
|
if (expect.lower() == "100-continue" and
|
||||||
|
self.protocol_version >= "HTTP/1.1" and
|
||||||
|
self.request_version >= "HTTP/1.1"):
|
||||||
|
if not self.handle_expect_100():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handle_expect_100(self):
|
||||||
|
"""Decide what to do with an "Expect: 100-continue" header.
|
||||||
|
|
||||||
|
If the client is expecting a 100 Continue response, we must
|
||||||
|
respond with either a 100 Continue or a final response before
|
||||||
|
waiting for the request body. The default is to always respond
|
||||||
|
with a 100 Continue. You can behave differently (for example,
|
||||||
|
reject unauthorized requests) by overriding this method.
|
||||||
|
|
||||||
|
This method should either return True (possibly after sending
|
||||||
|
a 100 Continue response) or send an error response and return
|
||||||
|
False.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_response_only(100)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handle_one_request(self):
|
def handle_one_request(self):
|
||||||
|
@ -400,6 +424,12 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.log_request(code)
|
self.log_request(code)
|
||||||
|
self.send_response_only(code, message)
|
||||||
|
self.send_header('Server', self.version_string())
|
||||||
|
self.send_header('Date', self.date_time_string())
|
||||||
|
|
||||||
|
def send_response_only(self, code, message=None):
|
||||||
|
"""Send the response header only."""
|
||||||
if message is None:
|
if message is None:
|
||||||
if code in self.responses:
|
if code in self.responses:
|
||||||
message = self.responses[code][0]
|
message = self.responses[code][0]
|
||||||
|
@ -408,9 +438,6 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
if self.request_version != 'HTTP/0.9':
|
if self.request_version != 'HTTP/0.9':
|
||||||
self.wfile.write(("%s %d %s\r\n" %
|
self.wfile.write(("%s %d %s\r\n" %
|
||||||
(self.protocol_version, code, message)).encode('ASCII', 'strict'))
|
(self.protocol_version, code, message)).encode('ASCII', 'strict'))
|
||||||
# print (self.protocol_version, code, message)
|
|
||||||
self.send_header('Server', self.version_string())
|
|
||||||
self.send_header('Date', self.date_time_string())
|
|
||||||
|
|
||||||
def send_header(self, keyword, value):
|
def send_header(self, keyword, value):
|
||||||
"""Send a MIME header."""
|
"""Send a MIME header."""
|
||||||
|
|
|
@ -10,11 +10,13 @@ from http import server
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
import base64
|
import base64
|
||||||
import shutil
|
import shutil
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import http.client
|
import http.client
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -403,8 +405,103 @@ class CGIHTTPServerTestCase(BaseTestCase):
|
||||||
|
|
||||||
class SocketlessRequestHandler(SimpleHTTPRequestHandler):
|
class SocketlessRequestHandler(SimpleHTTPRequestHandler):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.get_called = False
|
||||||
|
self.protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.get_called = True
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'<html><body>Data</body></html>\r\n')
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
|
||||||
|
def handle_expect_100(self):
|
||||||
|
self.send_error(417)
|
||||||
|
return False
|
||||||
|
|
||||||
|
class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
|
||||||
|
"""Test the functionaility of the BaseHTTPServer.
|
||||||
|
|
||||||
|
Test the support for the Expect 100-continue header.
|
||||||
|
"""
|
||||||
|
|
||||||
|
HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
|
||||||
|
|
||||||
|
def setUp (self):
|
||||||
|
self.handler = SocketlessRequestHandler()
|
||||||
|
|
||||||
|
def send_typical_request(self, message):
|
||||||
|
input = BytesIO(message)
|
||||||
|
output = BytesIO()
|
||||||
|
self.handler.rfile = input
|
||||||
|
self.handler.wfile = output
|
||||||
|
self.handler.handle_one_request()
|
||||||
|
output.seek(0)
|
||||||
|
return output.readlines()
|
||||||
|
|
||||||
|
def verify_get_called(self):
|
||||||
|
self.assertTrue(self.handler.get_called)
|
||||||
|
|
||||||
|
def verify_expected_headers(self, headers):
|
||||||
|
for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
|
||||||
|
self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
|
||||||
|
|
||||||
|
def verify_http_server_response(self, response):
|
||||||
|
match = self.HTTPResponseMatch.search(response)
|
||||||
|
self.assertTrue(match is not None)
|
||||||
|
|
||||||
|
def test_http_1_1(self):
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
|
||||||
|
self.verify_http_server_response(result[0])
|
||||||
|
self.verify_expected_headers(result[1:-1])
|
||||||
|
self.verify_get_called()
|
||||||
|
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
|
||||||
|
|
||||||
|
def test_http_1_0(self):
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
|
||||||
|
self.verify_http_server_response(result[0])
|
||||||
|
self.verify_expected_headers(result[1:-1])
|
||||||
|
self.verify_get_called()
|
||||||
|
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
|
||||||
|
|
||||||
|
def test_http_0_9(self):
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
|
||||||
|
self.assertEqual(len(result), 1)
|
||||||
|
self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
|
||||||
|
self.verify_get_called()
|
||||||
|
|
||||||
|
def test_with_continue_1_0(self):
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
|
||||||
|
self.verify_http_server_response(result[0])
|
||||||
|
self.verify_expected_headers(result[1:-1])
|
||||||
|
self.verify_get_called()
|
||||||
|
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
|
||||||
|
|
||||||
|
def test_with_continue_1_1(self):
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
|
||||||
|
self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
|
||||||
|
self.assertEqual(result[1], b'HTTP/1.1 200 OK\r\n')
|
||||||
|
self.verify_expected_headers(result[2:-1])
|
||||||
|
self.verify_get_called()
|
||||||
|
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
|
||||||
|
|
||||||
|
def test_with_continue_rejected(self):
|
||||||
|
usual_handler = self.handler # Save to avoid breaking any subsequent tests.
|
||||||
|
self.handler = RejectingSocketlessRequestHandler()
|
||||||
|
result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
|
||||||
|
self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
|
||||||
|
self.verify_expected_headers(result[1:-1])
|
||||||
|
# The expect handler should short circuit the usual get method by
|
||||||
|
# returning false here, so get_called should be false
|
||||||
|
self.assertFalse(self.handler.get_called)
|
||||||
|
self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
|
||||||
|
self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
|
||||||
|
|
||||||
|
|
||||||
class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
|
class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
|
||||||
""" Test url parsing """
|
""" Test url parsing """
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -431,6 +528,7 @@ def test_main(verbose=None):
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
try:
|
try:
|
||||||
support.run_unittest(
|
support.run_unittest(
|
||||||
|
BaseHTTPRequestHandlerTestCase,
|
||||||
BaseHTTPServerTestCase,
|
BaseHTTPServerTestCase,
|
||||||
SimpleHTTPServerTestCase,
|
SimpleHTTPServerTestCase,
|
||||||
CGIHTTPServerTestCase,
|
CGIHTTPServerTestCase,
|
||||||
|
|
|
@ -76,6 +76,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #1491: BaseHTTPServer nows send a 100 Continue response before sending
|
||||||
|
a 200 OK for the Expect: 100-continue request header.
|
||||||
|
|
||||||
- Issue #9360: Cleanup and improvements to the nntplib module. The API
|
- Issue #9360: Cleanup and improvements to the nntplib module. The API
|
||||||
now conforms to the philosophy of bytes and unicode separation in Python 3.
|
now conforms to the philosophy of bytes and unicode separation in Python 3.
|
||||||
A test suite has also been added.
|
A test suite has also been added.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue