summaryrefslogtreecommitdiff
path: root/Lib/test/test_httpservers.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_httpservers.py')
-rw-r--r--Lib/test/test_httpservers.py146
1 files changed, 143 insertions, 3 deletions
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index be5d8de057..6e5f2db7fd 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -14,6 +14,7 @@ import re
import base64
import shutil
import urllib.parse
+import html
import http.client
import tempfile
from io import BytesIO
@@ -92,6 +93,13 @@ class BaseHTTPServerTestCase(BaseTestCase):
def do_KEYERROR(self):
self.send_error(999)
+ def do_NOTFOUND(self):
+ self.send_error(404)
+
+ def do_EXPLAINERROR(self):
+ self.send_error(999, "Short Message",
+ "This is a long \n explaination")
+
def do_CUSTOM(self):
self.send_response(999)
self.send_header('Content-Type', 'text/html')
@@ -118,7 +126,7 @@ class BaseHTTPServerTestCase(BaseTestCase):
def test_request_line_trimming(self):
self.con._http_vsn_str = 'HTTP/1.1\n'
- self.con.putrequest('GET', '/')
+ self.con.putrequest('XYZBOGUS', '/')
self.con.endheaders()
res = self.con.getresponse()
self.assertEqual(res.status, 501)
@@ -145,8 +153,9 @@ class BaseHTTPServerTestCase(BaseTestCase):
self.assertEqual(res.status, 501)
def test_version_none(self):
+ # Test that a valid method is rejected when not HTTP/1.x
self.con._http_vsn_str = ''
- self.con.putrequest('PUT', '/')
+ self.con.putrequest('CUSTOM', '/')
self.con.endheaders()
res = self.con.getresponse()
self.assertEqual(res.status, 400)
@@ -203,6 +212,12 @@ class BaseHTTPServerTestCase(BaseTestCase):
res = self.con.getresponse()
self.assertEqual(res.status, 999)
+ def test_return_explain_error(self):
+ self.con.request('EXPLAINERROR', '/')
+ res = self.con.getresponse()
+ self.assertEqual(res.status, 999)
+ self.assertTrue(int(res.getheader('Content-Length')))
+
def test_latin1_header(self):
self.con.request('LATINONEHEADER', '/', headers={
'X-Special-Incoming': 'Ärger mit Unicode'
@@ -211,6 +226,14 @@ class BaseHTTPServerTestCase(BaseTestCase):
self.assertEqual(res.getheader('X-Special'), 'Dängerous Mind')
self.assertEqual(res.read(), 'Ärger mit Unicode'.encode('utf-8'))
+ def test_error_content_length(self):
+ # Issue #16088: standard error responses should have a content-length
+ self.con.request('NOTFOUND', '/')
+ res = self.con.getresponse()
+ self.assertEqual(res.status, 404)
+ data = res.read()
+ self.assertEqual(int(res.getheader('Content-Length')), len(data))
+
class SimpleHTTPServerTestCase(BaseTestCase):
class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
@@ -244,6 +267,33 @@ class SimpleHTTPServerTestCase(BaseTestCase):
self.assertIsNotNone(response.reason)
if data:
self.assertEqual(data, body)
+ return body
+
+ @support.requires_mac_ver(10, 5)
+ @unittest.skipUnless(support.TESTFN_UNDECODABLE,
+ 'need support.TESTFN_UNDECODABLE')
+ def test_undecodable_filename(self):
+ enc = sys.getfilesystemencoding()
+ filename = os.fsdecode(support.TESTFN_UNDECODABLE) + '.txt'
+ with open(os.path.join(self.tempdir, filename), 'wb') as f:
+ f.write(support.TESTFN_UNDECODABLE)
+ response = self.request(self.tempdir_name + '/')
+ if sys.platform == 'darwin':
+ # On Mac OS the HFS+ filesystem replaces bytes that aren't valid
+ # UTF-8 into a percent-encoded value.
+ for name in os.listdir(self.tempdir):
+ if name != 'test': # Ignore a filename created in setUp().
+ filename = name
+ break
+ body = self.check_status_and_reason(response, 200)
+ quotedname = urllib.parse.quote(filename, errors='surrogatepass')
+ self.assertIn(('href="%s"' % quotedname)
+ .encode(enc, 'surrogateescape'), body)
+ self.assertIn(('>%s<' % html.escape(filename))
+ .encode(enc, 'surrogateescape'), body)
+ response = self.request(self.tempdir_name + '/' + quotedname)
+ self.check_status_and_reason(response, 200,
+ data=support.TESTFN_UNDECODABLE)
def test_get(self):
#constructs the path relative to the root directory of the HTTPServer
@@ -256,6 +306,12 @@ class SimpleHTTPServerTestCase(BaseTestCase):
self.check_status_and_reason(response, 200)
response = self.request(self.tempdir_name)
self.check_status_and_reason(response, 301)
+ response = self.request(self.tempdir_name + '/?hi=2')
+ self.check_status_and_reason(response, 200)
+ response = self.request(self.tempdir_name + '?hi=1')
+ self.check_status_and_reason(response, 301)
+ self.assertEqual(response.getheader("Location"),
+ self.tempdir_name + "/?hi=1")
response = self.request('/ThisDoesNotExist')
self.check_status_and_reason(response, 404)
response = self.request('/' + 'ThisDoesNotExist' + '/')
@@ -284,7 +340,7 @@ class SimpleHTTPServerTestCase(BaseTestCase):
response = self.request('/', method='FOO')
self.check_status_and_reason(response, 501)
# requests must be case sensitive,so this should fail too
- response = self.request('/', method='get')
+ response = self.request('/', method='custom')
self.check_status_and_reason(response, 501)
response = self.request('/', method='GETs')
self.check_status_and_reason(response, 501)
@@ -310,6 +366,16 @@ print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
form.getfirst("bacon")))
"""
+cgi_file4 = """\
+#!%s
+import os
+
+print("Content-type: text/html")
+print()
+
+print(os.environ["%s"])
+"""
+
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"This test can't be run reliably as root (issue #13308).")
@@ -331,6 +397,7 @@ class CGIHTTPServerTestCase(BaseTestCase):
self.file1_path = None
self.file2_path = None
self.file3_path = None
+ self.file4_path = None
# The shebang line should be pure ASCII: use symlink if possible.
# See issue #7668.
@@ -369,6 +436,11 @@ class CGIHTTPServerTestCase(BaseTestCase):
file3.write(cgi_file1 % self.pythonexe)
os.chmod(self.file3_path, 0o777)
+ self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
+ with open(self.file4_path, 'w', encoding='utf-8') as file4:
+ file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
+ os.chmod(self.file4_path, 0o777)
+
os.chdir(self.parent_dir)
def tearDown(self):
@@ -384,6 +456,8 @@ class CGIHTTPServerTestCase(BaseTestCase):
os.remove(self.file2_path)
if self.file3_path:
os.remove(self.file3_path)
+ if self.file4_path:
+ os.remove(self.file4_path)
os.rmdir(self.cgi_child_dir)
os.rmdir(self.cgi_dir)
os.rmdir(self.parent_dir)
@@ -485,6 +559,19 @@ class CGIHTTPServerTestCase(BaseTestCase):
self.assertEqual((b'Hello World' + self.linesep, 'text/html', 200),
(res.read(), res.getheader('Content-type'), res.status))
+ def test_query_with_multiple_question_mark(self):
+ res = self.request('/cgi-bin/file4.py?a=b?c=d')
+ self.assertEqual(
+ (b'a=b?c=d' + self.linesep, 'text/html', 200),
+ (res.read(), res.getheader('Content-type'), res.status))
+
+ def test_query_with_continuous_slashes(self):
+ res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
+ self.assertEqual(
+ (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
+ 'text/html', 200),
+ (res.read(), res.getheader('Content-type'), res.status))
+
class SocketlessRequestHandler(SimpleHTTPRequestHandler):
def __init__(self):
@@ -560,6 +647,11 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
self.verify_expected_headers(result[1:-1])
self.verify_get_called()
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+ self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
+ self.assertEqual(self.handler.command, 'GET')
+ self.assertEqual(self.handler.path, '/')
+ self.assertEqual(self.handler.request_version, 'HTTP/1.1')
+ self.assertSequenceEqual(self.handler.headers.items(), ())
def test_http_1_0(self):
result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
@@ -567,6 +659,11 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
self.verify_expected_headers(result[1:-1])
self.verify_get_called()
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+ self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
+ self.assertEqual(self.handler.command, 'GET')
+ self.assertEqual(self.handler.path, '/')
+ self.assertEqual(self.handler.request_version, 'HTTP/1.0')
+ self.assertSequenceEqual(self.handler.headers.items(), ())
def test_http_0_9(self):
result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
@@ -580,6 +677,12 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
self.verify_expected_headers(result[1:-1])
self.verify_get_called()
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+ self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
+ self.assertEqual(self.handler.command, 'GET')
+ self.assertEqual(self.handler.path, '/')
+ self.assertEqual(self.handler.request_version, 'HTTP/1.0')
+ headers = (("Expect", "100-continue"),)
+ self.assertSequenceEqual(self.handler.headers.items(), headers)
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')
@@ -589,6 +692,12 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
self.verify_expected_headers(result[2:-1])
self.verify_get_called()
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+ self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
+ self.assertEqual(self.handler.command, 'GET')
+ self.assertEqual(self.handler.path, '/')
+ self.assertEqual(self.handler.request_version, 'HTTP/1.1')
+ headers = (("Expect", "100-continue"),)
+ self.assertSequenceEqual(self.handler.headers.items(), headers)
def test_header_buffering_of_send_error(self):
@@ -674,6 +783,7 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
result = self.send_typical_request(b'GET ' + b'x' * 65537)
self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
self.assertFalse(self.handler.get_called)
+ self.assertIsInstance(self.handler.requestline, str)
def test_header_length(self):
# Issue #6791: same for headers
@@ -681,6 +791,22 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
b'GET / HTTP/1.1\r\nX-Foo: bar' + b'r' * 65537 + b'\r\n\r\n')
self.assertEqual(result[0], b'HTTP/1.1 400 Line too long\r\n')
self.assertFalse(self.handler.get_called)
+ self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
+
+ def test_close_connection(self):
+ # handle_one_request() should be repeatedly called until
+ # it sets close_connection
+ def handle_one_request():
+ self.handler.close_connection = next(close_values)
+ self.handler.handle_one_request = handle_one_request
+
+ close_values = iter((True,))
+ self.handler.handle()
+ self.assertRaises(StopIteration, next, close_values)
+
+ close_values = iter((False, False, True))
+ self.handler.handle()
+ self.assertRaises(StopIteration, next, close_values)
class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
""" Test url parsing """
@@ -704,6 +830,19 @@ class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
self.assertEqual(path, self.translated)
+class MiscTestCase(unittest.TestCase):
+ def test_all(self):
+ expected = []
+ blacklist = {'executable', 'nobody_uid', 'test'}
+ for name in dir(server):
+ if name.startswith('_') or name in blacklist:
+ continue
+ module_object = getattr(server, name)
+ if getattr(module_object, '__module__', None) == 'http.server':
+ expected.append(name)
+ self.assertCountEqual(server.__all__, expected)
+
+
def test_main(verbose=None):
cwd = os.getcwd()
try:
@@ -713,6 +852,7 @@ def test_main(verbose=None):
SimpleHTTPServerTestCase,
CGIHTTPServerTestCase,
SimpleHTTPRequestHandlerTestCase,
+ MiscTestCase,
)
finally:
os.chdir(cwd)