diff options
Diffstat (limited to 'Lib/test/test_httpservers.py')
-rw-r--r-- | Lib/test/test_httpservers.py | 146 |
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) |