blob: c3d7c8feb1ec0df46a06336f302744d409078212 [file] [log] [blame]
Georg Brandlb533e262008-05-25 18:19:30 +00001"""Unittests for the various HTTPServer modules.
2
3Written by Cody A.W. Somerville <cody-somerville@ubuntu.com>,
4Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
5"""
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -08006from collections import OrderedDict
Georg Brandl24420152008-05-26 16:32:26 +00007from http.server import BaseHTTPRequestHandler, HTTPServer, \
8 SimpleHTTPRequestHandler, CGIHTTPRequestHandler
Serhiy Storchakac0a23e62015-03-07 11:51:37 +02009from http import server, HTTPStatus
Georg Brandlb533e262008-05-25 18:19:30 +000010
11import os
Lisa Roach433433f2018-11-26 10:43:38 -080012import socket
Georg Brandlb533e262008-05-25 18:19:30 +000013import sys
Senthil Kumaran0f476d42010-09-30 06:09:18 +000014import re
Georg Brandlb533e262008-05-25 18:19:30 +000015import base64
Martin Panterd274b3f2016-04-18 03:45:18 +000016import ntpath
Géry Ogam781266e2019-09-11 15:03:46 +020017import pathlib
Georg Brandlb533e262008-05-25 18:19:30 +000018import shutil
Pierre Quentel351adda2017-04-02 12:26:12 +020019import email.message
20import email.utils
Serhiy Storchakacb5bc402014-08-17 08:22:11 +030021import html
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -080022import http, http.client
Pierre Quentel351adda2017-04-02 12:26:12 +020023import urllib.parse
Georg Brandlb533e262008-05-25 18:19:30 +000024import tempfile
Berker Peksag04bc5b92016-03-14 06:06:03 +020025import time
Pierre Quentel351adda2017-04-02 12:26:12 +020026import datetime
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020027import threading
Stéphane Wirtela17a2f52017-05-24 09:29:06 +020028from unittest import mock
Senthil Kumaran0f476d42010-09-30 06:09:18 +000029from io import BytesIO
Georg Brandlb533e262008-05-25 18:19:30 +000030
31import unittest
32from test import support
Hai Shia089d212020-07-06 17:15:08 +080033from test.support import os_helper
Hai Shie80697d2020-05-28 06:10:27 +080034from test.support import threading_helper
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020035
Georg Brandlb533e262008-05-25 18:19:30 +000036
Georg Brandlb533e262008-05-25 18:19:30 +000037class NoLogRequestHandler:
38 def log_message(self, *args):
39 # don't write log messages to stderr
40 pass
41
Barry Warsaw820c1202008-06-12 04:06:45 +000042 def read(self, n=None):
43 return ''
44
Georg Brandlb533e262008-05-25 18:19:30 +000045
46class TestServerThread(threading.Thread):
47 def __init__(self, test_object, request_handler):
48 threading.Thread.__init__(self)
49 self.request_handler = request_handler
50 self.test_object = test_object
Georg Brandlb533e262008-05-25 18:19:30 +000051
52 def run(self):
Antoine Pitroucb342182011-03-21 00:26:51 +010053 self.server = HTTPServer(('localhost', 0), self.request_handler)
54 self.test_object.HOST, self.test_object.PORT = self.server.socket.getsockname()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000055 self.test_object.server_started.set()
56 self.test_object = None
Georg Brandlb533e262008-05-25 18:19:30 +000057 try:
Antoine Pitrou08911bd2010-04-25 22:19:43 +000058 self.server.serve_forever(0.05)
Georg Brandlb533e262008-05-25 18:19:30 +000059 finally:
60 self.server.server_close()
61
62 def stop(self):
63 self.server.shutdown()
Victor Stinner830d7d22017-08-22 18:05:07 +020064 self.join()
Georg Brandlb533e262008-05-25 18:19:30 +000065
66
67class BaseTestCase(unittest.TestCase):
68 def setUp(self):
Hai Shie80697d2020-05-28 06:10:27 +080069 self._threads = threading_helper.threading_setup()
Hai Shi79bb2c92020-08-06 19:51:29 +080070 os.environ = os_helper.EnvironmentVarGuard()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000071 self.server_started = threading.Event()
Georg Brandlb533e262008-05-25 18:19:30 +000072 self.thread = TestServerThread(self, self.request_handler)
73 self.thread.start()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000074 self.server_started.wait()
Georg Brandlb533e262008-05-25 18:19:30 +000075
76 def tearDown(self):
Georg Brandlb533e262008-05-25 18:19:30 +000077 self.thread.stop()
Antoine Pitrouf7270822012-09-30 01:05:30 +020078 self.thread = None
Nick Coghlan6ead5522009-10-18 13:19:33 +000079 os.environ.__exit__()
Hai Shie80697d2020-05-28 06:10:27 +080080 threading_helper.threading_cleanup(*self._threads)
Georg Brandlb533e262008-05-25 18:19:30 +000081
82 def request(self, uri, method='GET', body=None, headers={}):
Antoine Pitroucb342182011-03-21 00:26:51 +010083 self.connection = http.client.HTTPConnection(self.HOST, self.PORT)
Georg Brandlb533e262008-05-25 18:19:30 +000084 self.connection.request(method, uri, body, headers)
85 return self.connection.getresponse()
86
87
88class BaseHTTPServerTestCase(BaseTestCase):
89 class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
90 protocol_version = 'HTTP/1.1'
91 default_request_version = 'HTTP/1.1'
92
93 def do_TEST(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +020094 self.send_response(HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +000095 self.send_header('Content-Type', 'text/html')
96 self.send_header('Connection', 'close')
97 self.end_headers()
98
99 def do_KEEP(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200100 self.send_response(HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +0000101 self.send_header('Content-Type', 'text/html')
102 self.send_header('Connection', 'keep-alive')
103 self.end_headers()
104
105 def do_KEYERROR(self):
106 self.send_error(999)
107
Senthil Kumaran52d27202012-10-10 23:16:21 -0700108 def do_NOTFOUND(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200109 self.send_error(HTTPStatus.NOT_FOUND)
Senthil Kumaran52d27202012-10-10 23:16:21 -0700110
Senthil Kumaran26886442013-03-15 07:53:21 -0700111 def do_EXPLAINERROR(self):
112 self.send_error(999, "Short Message",
Martin Panter46f50722016-05-26 05:35:26 +0000113 "This is a long \n explanation")
Senthil Kumaran26886442013-03-15 07:53:21 -0700114
Georg Brandlb533e262008-05-25 18:19:30 +0000115 def do_CUSTOM(self):
116 self.send_response(999)
117 self.send_header('Content-Type', 'text/html')
118 self.send_header('Connection', 'close')
119 self.end_headers()
120
Armin Ronacher8d96d772011-01-22 13:13:05 +0000121 def do_LATINONEHEADER(self):
122 self.send_response(999)
123 self.send_header('X-Special', 'Dängerous Mind')
Armin Ronacher59531282011-01-22 13:44:22 +0000124 self.send_header('Connection', 'close')
Armin Ronacher8d96d772011-01-22 13:13:05 +0000125 self.end_headers()
Armin Ronacher59531282011-01-22 13:44:22 +0000126 body = self.headers['x-special-incoming'].encode('utf-8')
127 self.wfile.write(body)
Armin Ronacher8d96d772011-01-22 13:13:05 +0000128
Martin Pantere42e1292016-06-08 08:29:13 +0000129 def do_SEND_ERROR(self):
130 self.send_error(int(self.path[1:]))
131
132 def do_HEAD(self):
133 self.send_error(int(self.path[1:]))
134
Georg Brandlb533e262008-05-25 18:19:30 +0000135 def setUp(self):
136 BaseTestCase.setUp(self)
Antoine Pitroucb342182011-03-21 00:26:51 +0100137 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
Georg Brandlb533e262008-05-25 18:19:30 +0000138 self.con.connect()
139
140 def test_command(self):
141 self.con.request('GET', '/')
142 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200143 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000144
145 def test_request_line_trimming(self):
146 self.con._http_vsn_str = 'HTTP/1.1\n'
R David Murray14199f92014-06-24 16:39:49 -0400147 self.con.putrequest('XYZBOGUS', '/')
Georg Brandlb533e262008-05-25 18:19:30 +0000148 self.con.endheaders()
149 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200150 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000151
152 def test_version_bogus(self):
153 self.con._http_vsn_str = 'FUBAR'
154 self.con.putrequest('GET', '/')
155 self.con.endheaders()
156 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200157 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000158
159 def test_version_digits(self):
160 self.con._http_vsn_str = 'HTTP/9.9.9'
161 self.con.putrequest('GET', '/')
162 self.con.endheaders()
163 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200164 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000165
166 def test_version_none_get(self):
167 self.con._http_vsn_str = ''
168 self.con.putrequest('GET', '/')
169 self.con.endheaders()
170 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200171 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000172
173 def test_version_none(self):
R David Murray14199f92014-06-24 16:39:49 -0400174 # Test that a valid method is rejected when not HTTP/1.x
Georg Brandlb533e262008-05-25 18:19:30 +0000175 self.con._http_vsn_str = ''
R David Murray14199f92014-06-24 16:39:49 -0400176 self.con.putrequest('CUSTOM', '/')
Georg Brandlb533e262008-05-25 18:19:30 +0000177 self.con.endheaders()
178 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200179 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000180
181 def test_version_invalid(self):
182 self.con._http_vsn = 99
183 self.con._http_vsn_str = 'HTTP/9.9'
184 self.con.putrequest('GET', '/')
185 self.con.endheaders()
186 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200187 self.assertEqual(res.status, HTTPStatus.HTTP_VERSION_NOT_SUPPORTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000188
189 def test_send_blank(self):
190 self.con._http_vsn_str = ''
191 self.con.putrequest('', '')
192 self.con.endheaders()
193 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200194 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000195
196 def test_header_close(self):
197 self.con.putrequest('GET', '/')
198 self.con.putheader('Connection', 'close')
199 self.con.endheaders()
200 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200201 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000202
Berker Peksag20853612016-08-25 01:13:34 +0300203 def test_header_keep_alive(self):
Georg Brandlb533e262008-05-25 18:19:30 +0000204 self.con._http_vsn_str = 'HTTP/1.1'
205 self.con.putrequest('GET', '/')
206 self.con.putheader('Connection', 'keep-alive')
207 self.con.endheaders()
208 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200209 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000210
211 def test_handler(self):
212 self.con.request('TEST', '/')
213 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200214 self.assertEqual(res.status, HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +0000215
216 def test_return_header_keep_alive(self):
217 self.con.request('KEEP', '/')
218 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000219 self.assertEqual(res.getheader('Connection'), 'keep-alive')
Georg Brandlb533e262008-05-25 18:19:30 +0000220 self.con.request('TEST', '/')
Brian Curtin61d0d602010-10-31 00:34:23 +0000221 self.addCleanup(self.con.close)
Georg Brandlb533e262008-05-25 18:19:30 +0000222
223 def test_internal_key_error(self):
224 self.con.request('KEYERROR', '/')
225 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000226 self.assertEqual(res.status, 999)
Georg Brandlb533e262008-05-25 18:19:30 +0000227
228 def test_return_custom_status(self):
229 self.con.request('CUSTOM', '/')
230 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000231 self.assertEqual(res.status, 999)
Georg Brandlb533e262008-05-25 18:19:30 +0000232
Senthil Kumaran26886442013-03-15 07:53:21 -0700233 def test_return_explain_error(self):
234 self.con.request('EXPLAINERROR', '/')
235 res = self.con.getresponse()
236 self.assertEqual(res.status, 999)
237 self.assertTrue(int(res.getheader('Content-Length')))
238
Armin Ronacher8d96d772011-01-22 13:13:05 +0000239 def test_latin1_header(self):
Armin Ronacher59531282011-01-22 13:44:22 +0000240 self.con.request('LATINONEHEADER', '/', headers={
241 'X-Special-Incoming': 'Ärger mit Unicode'
242 })
Armin Ronacher8d96d772011-01-22 13:13:05 +0000243 res = self.con.getresponse()
244 self.assertEqual(res.getheader('X-Special'), 'Dängerous Mind')
Armin Ronacher59531282011-01-22 13:44:22 +0000245 self.assertEqual(res.read(), 'Ärger mit Unicode'.encode('utf-8'))
Armin Ronacher8d96d772011-01-22 13:13:05 +0000246
Senthil Kumaran52d27202012-10-10 23:16:21 -0700247 def test_error_content_length(self):
248 # Issue #16088: standard error responses should have a content-length
249 self.con.request('NOTFOUND', '/')
250 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200251 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
252
Senthil Kumaran52d27202012-10-10 23:16:21 -0700253 data = res.read()
Senthil Kumaran52d27202012-10-10 23:16:21 -0700254 self.assertEqual(int(res.getheader('Content-Length')), len(data))
255
Martin Pantere42e1292016-06-08 08:29:13 +0000256 def test_send_error(self):
257 allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
258 HTTPStatus.RESET_CONTENT)
259 for code in (HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED,
260 HTTPStatus.PROCESSING, HTTPStatus.RESET_CONTENT,
261 HTTPStatus.SWITCHING_PROTOCOLS):
262 self.con.request('SEND_ERROR', '/{}'.format(code))
263 res = self.con.getresponse()
264 self.assertEqual(code, res.status)
265 self.assertEqual(None, res.getheader('Content-Length'))
266 self.assertEqual(None, res.getheader('Content-Type'))
267 if code not in allow_transfer_encoding_codes:
268 self.assertEqual(None, res.getheader('Transfer-Encoding'))
269
270 data = res.read()
271 self.assertEqual(b'', data)
272
273 def test_head_via_send_error(self):
274 allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
275 HTTPStatus.RESET_CONTENT)
276 for code in (HTTPStatus.OK, HTTPStatus.NO_CONTENT,
277 HTTPStatus.NOT_MODIFIED, HTTPStatus.RESET_CONTENT,
278 HTTPStatus.SWITCHING_PROTOCOLS):
279 self.con.request('HEAD', '/{}'.format(code))
280 res = self.con.getresponse()
281 self.assertEqual(code, res.status)
282 if code == HTTPStatus.OK:
283 self.assertTrue(int(res.getheader('Content-Length')) > 0)
284 self.assertIn('text/html', res.getheader('Content-Type'))
285 else:
286 self.assertEqual(None, res.getheader('Content-Length'))
287 self.assertEqual(None, res.getheader('Content-Type'))
288 if code not in allow_transfer_encoding_codes:
289 self.assertEqual(None, res.getheader('Transfer-Encoding'))
290
291 data = res.read()
292 self.assertEqual(b'', data)
293
Georg Brandlb533e262008-05-25 18:19:30 +0000294
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200295class RequestHandlerLoggingTestCase(BaseTestCase):
296 class request_handler(BaseHTTPRequestHandler):
297 protocol_version = 'HTTP/1.1'
298 default_request_version = 'HTTP/1.1'
299
300 def do_GET(self):
301 self.send_response(HTTPStatus.OK)
302 self.end_headers()
303
304 def do_ERROR(self):
305 self.send_error(HTTPStatus.NOT_FOUND, 'File not found')
306
307 def test_get(self):
308 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
309 self.con.connect()
310
311 with support.captured_stderr() as err:
312 self.con.request('GET', '/')
313 self.con.getresponse()
314
315 self.assertTrue(
316 err.getvalue().endswith('"GET / HTTP/1.1" 200 -\n'))
317
318 def test_err(self):
319 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
320 self.con.connect()
321
322 with support.captured_stderr() as err:
323 self.con.request('ERROR', '/')
324 self.con.getresponse()
325
326 lines = err.getvalue().split('\n')
327 self.assertTrue(lines[0].endswith('code 404, message File not found'))
328 self.assertTrue(lines[1].endswith('"ERROR / HTTP/1.1" 404 -'))
329
330
Georg Brandlb533e262008-05-25 18:19:30 +0000331class SimpleHTTPServerTestCase(BaseTestCase):
332 class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
333 pass
334
335 def setUp(self):
336 BaseTestCase.setUp(self)
337 self.cwd = os.getcwd()
338 basetempdir = tempfile.gettempdir()
339 os.chdir(basetempdir)
340 self.data = b'We are the knights who say Ni!'
341 self.tempdir = tempfile.mkdtemp(dir=basetempdir)
342 self.tempdir_name = os.path.basename(self.tempdir)
Martin Panterfc475a92016-04-09 04:56:10 +0000343 self.base_url = '/' + self.tempdir_name
Victor Stinner28ce07a2017-07-28 18:15:02 +0200344 tempname = os.path.join(self.tempdir, 'test')
345 with open(tempname, 'wb') as temp:
Brett Cannon105df5d2010-10-29 23:43:42 +0000346 temp.write(self.data)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200347 temp.flush()
348 mtime = os.stat(tempname).st_mtime
Pierre Quentel351adda2017-04-02 12:26:12 +0200349 # compute last modification datetime for browser cache tests
350 last_modif = datetime.datetime.fromtimestamp(mtime,
351 datetime.timezone.utc)
352 self.last_modif_datetime = last_modif.replace(microsecond=0)
353 self.last_modif_header = email.utils.formatdate(
354 last_modif.timestamp(), usegmt=True)
Georg Brandlb533e262008-05-25 18:19:30 +0000355
356 def tearDown(self):
357 try:
358 os.chdir(self.cwd)
359 try:
360 shutil.rmtree(self.tempdir)
361 except:
362 pass
363 finally:
364 BaseTestCase.tearDown(self)
365
366 def check_status_and_reason(self, response, status, data=None):
Berker Peksagb5754322015-07-22 19:25:37 +0300367 def close_conn():
368 """Don't close reader yet so we can check if there was leftover
369 buffered input"""
370 nonlocal reader
371 reader = response.fp
372 response.fp = None
373 reader = None
374 response._close_conn = close_conn
375
Georg Brandlb533e262008-05-25 18:19:30 +0000376 body = response.read()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000377 self.assertTrue(response)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000378 self.assertEqual(response.status, status)
379 self.assertIsNotNone(response.reason)
Georg Brandlb533e262008-05-25 18:19:30 +0000380 if data:
381 self.assertEqual(data, body)
Berker Peksagb5754322015-07-22 19:25:37 +0300382 # Ensure the server has not set up a persistent connection, and has
383 # not sent any extra data
384 self.assertEqual(response.version, 10)
385 self.assertEqual(response.msg.get("Connection", "close"), "close")
386 self.assertEqual(reader.read(30), b'', 'Connection should be closed')
387
388 reader.close()
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300389 return body
390
Ned Deilyb3edde82017-12-04 23:42:02 -0500391 @unittest.skipIf(sys.platform == 'darwin',
392 'undecodable name cannot always be decoded on macOS')
Steve Dowere58571b2016-09-08 11:11:13 -0700393 @unittest.skipIf(sys.platform == 'win32',
394 'undecodable name cannot be decoded on win32')
Hai Shia089d212020-07-06 17:15:08 +0800395 @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
396 'need os_helper.TESTFN_UNDECODABLE')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300397 def test_undecodable_filename(self):
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300398 enc = sys.getfilesystemencoding()
Hai Shia089d212020-07-06 17:15:08 +0800399 filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt'
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300400 with open(os.path.join(self.tempdir, filename), 'wb') as f:
Hai Shia089d212020-07-06 17:15:08 +0800401 f.write(os_helper.TESTFN_UNDECODABLE)
Martin Panterfc475a92016-04-09 04:56:10 +0000402 response = self.request(self.base_url + '/')
Serhiy Storchakad9e95282014-08-17 16:57:39 +0300403 if sys.platform == 'darwin':
404 # On Mac OS the HFS+ filesystem replaces bytes that aren't valid
405 # UTF-8 into a percent-encoded value.
406 for name in os.listdir(self.tempdir):
407 if name != 'test': # Ignore a filename created in setUp().
408 filename = name
409 break
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200410 body = self.check_status_and_reason(response, HTTPStatus.OK)
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300411 quotedname = urllib.parse.quote(filename, errors='surrogatepass')
412 self.assertIn(('href="%s"' % quotedname)
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300413 .encode(enc, 'surrogateescape'), body)
Martin Panterda3bb382016-04-11 00:40:08 +0000414 self.assertIn(('>%s<' % html.escape(filename, quote=False))
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300415 .encode(enc, 'surrogateescape'), body)
Martin Panterfc475a92016-04-09 04:56:10 +0000416 response = self.request(self.base_url + '/' + quotedname)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200417 self.check_status_and_reason(response, HTTPStatus.OK,
Hai Shia089d212020-07-06 17:15:08 +0800418 data=os_helper.TESTFN_UNDECODABLE)
Georg Brandlb533e262008-05-25 18:19:30 +0000419
420 def test_get(self):
421 #constructs the path relative to the root directory of the HTTPServer
Martin Panterfc475a92016-04-09 04:56:10 +0000422 response = self.request(self.base_url + '/test')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200423 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700424 # check for trailing "/" which should return 404. See Issue17324
Martin Panterfc475a92016-04-09 04:56:10 +0000425 response = self.request(self.base_url + '/test/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200426 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Martin Panterfc475a92016-04-09 04:56:10 +0000427 response = self.request(self.base_url + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200428 self.check_status_and_reason(response, HTTPStatus.OK)
Martin Panterfc475a92016-04-09 04:56:10 +0000429 response = self.request(self.base_url)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200430 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
Martin Panterfc475a92016-04-09 04:56:10 +0000431 response = self.request(self.base_url + '/?hi=2')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200432 self.check_status_and_reason(response, HTTPStatus.OK)
Martin Panterfc475a92016-04-09 04:56:10 +0000433 response = self.request(self.base_url + '?hi=1')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200434 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600435 self.assertEqual(response.getheader("Location"),
Martin Panterfc475a92016-04-09 04:56:10 +0000436 self.base_url + "/?hi=1")
Georg Brandlb533e262008-05-25 18:19:30 +0000437 response = self.request('/ThisDoesNotExist')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200438 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000439 response = self.request('/' + 'ThisDoesNotExist' + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200440 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300441
442 data = b"Dummy index file\r\n"
443 with open(os.path.join(self.tempdir_name, 'index.html'), 'wb') as f:
444 f.write(data)
Martin Panterfc475a92016-04-09 04:56:10 +0000445 response = self.request(self.base_url + '/')
Berker Peksagb5754322015-07-22 19:25:37 +0300446 self.check_status_and_reason(response, HTTPStatus.OK, data)
447
448 # chmod() doesn't work as expected on Windows, and filesystem
449 # permissions are ignored by root on Unix.
450 if os.name == 'posix' and os.geteuid() != 0:
451 os.chmod(self.tempdir, 0)
452 try:
Martin Panterfc475a92016-04-09 04:56:10 +0000453 response = self.request(self.base_url + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200454 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300455 finally:
Brett Cannon105df5d2010-10-29 23:43:42 +0000456 os.chmod(self.tempdir, 0o755)
Georg Brandlb533e262008-05-25 18:19:30 +0000457
458 def test_head(self):
459 response = self.request(
Martin Panterfc475a92016-04-09 04:56:10 +0000460 self.base_url + '/test', method='HEAD')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200461 self.check_status_and_reason(response, HTTPStatus.OK)
Georg Brandlb533e262008-05-25 18:19:30 +0000462 self.assertEqual(response.getheader('content-length'),
463 str(len(self.data)))
464 self.assertEqual(response.getheader('content-type'),
465 'application/octet-stream')
466
Pierre Quentel351adda2017-04-02 12:26:12 +0200467 def test_browser_cache(self):
468 """Check that when a request to /test is sent with the request header
469 If-Modified-Since set to date of last modification, the server returns
470 status code 304, not 200
471 """
472 headers = email.message.Message()
473 headers['If-Modified-Since'] = self.last_modif_header
474 response = self.request(self.base_url + '/test', headers=headers)
475 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
476
477 # one hour after last modification : must return 304
478 new_dt = self.last_modif_datetime + datetime.timedelta(hours=1)
479 headers = email.message.Message()
480 headers['If-Modified-Since'] = email.utils.format_datetime(new_dt,
481 usegmt=True)
482 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200483 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
Pierre Quentel351adda2017-04-02 12:26:12 +0200484
485 def test_browser_cache_file_changed(self):
486 # with If-Modified-Since earlier than Last-Modified, must return 200
487 dt = self.last_modif_datetime
488 # build datetime object : 365 days before last modification
489 old_dt = dt - datetime.timedelta(days=365)
490 headers = email.message.Message()
491 headers['If-Modified-Since'] = email.utils.format_datetime(old_dt,
492 usegmt=True)
493 response = self.request(self.base_url + '/test', headers=headers)
494 self.check_status_and_reason(response, HTTPStatus.OK)
495
496 def test_browser_cache_with_If_None_Match_header(self):
497 # if If-None-Match header is present, ignore If-Modified-Since
498
499 headers = email.message.Message()
500 headers['If-Modified-Since'] = self.last_modif_header
501 headers['If-None-Match'] = "*"
502 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200503 self.check_status_and_reason(response, HTTPStatus.OK)
Pierre Quentel351adda2017-04-02 12:26:12 +0200504
Georg Brandlb533e262008-05-25 18:19:30 +0000505 def test_invalid_requests(self):
506 response = self.request('/', method='FOO')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200507 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000508 # requests must be case sensitive,so this should fail too
Terry Jan Reedydd09efd2014-10-18 17:10:09 -0400509 response = self.request('/', method='custom')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200510 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000511 response = self.request('/', method='GETs')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200512 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000513
Pierre Quentel351adda2017-04-02 12:26:12 +0200514 def test_last_modified(self):
515 """Checks that the datetime returned in Last-Modified response header
516 is the actual datetime of last modification, rounded to the second
517 """
518 response = self.request(self.base_url + '/test')
519 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
520 last_modif_header = response.headers['Last-modified']
521 self.assertEqual(last_modif_header, self.last_modif_header)
522
Martin Panterfc475a92016-04-09 04:56:10 +0000523 def test_path_without_leading_slash(self):
524 response = self.request(self.tempdir_name + '/test')
525 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
526 response = self.request(self.tempdir_name + '/test/')
527 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
528 response = self.request(self.tempdir_name + '/')
529 self.check_status_and_reason(response, HTTPStatus.OK)
530 response = self.request(self.tempdir_name)
531 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
532 response = self.request(self.tempdir_name + '/?hi=2')
533 self.check_status_and_reason(response, HTTPStatus.OK)
534 response = self.request(self.tempdir_name + '?hi=1')
535 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
536 self.assertEqual(response.getheader("Location"),
537 self.tempdir_name + "/?hi=1")
538
Martin Panterda3bb382016-04-11 00:40:08 +0000539 def test_html_escape_filename(self):
540 filename = '<test&>.txt'
541 fullpath = os.path.join(self.tempdir, filename)
542
543 try:
544 open(fullpath, 'w').close()
545 except OSError:
546 raise unittest.SkipTest('Can not create file %s on current file '
547 'system' % filename)
548
549 try:
550 response = self.request(self.base_url + '/')
551 body = self.check_status_and_reason(response, HTTPStatus.OK)
552 enc = response.headers.get_content_charset()
553 finally:
554 os.unlink(fullpath) # avoid affecting test_undecodable_filename
555
556 self.assertIsNotNone(enc)
557 html_text = '>%s<' % html.escape(filename, quote=False)
558 self.assertIn(html_text.encode(enc), body)
559
Georg Brandlb533e262008-05-25 18:19:30 +0000560
561cgi_file1 = """\
562#!%s
563
564print("Content-type: text/html")
565print()
566print("Hello World")
567"""
568
569cgi_file2 = """\
570#!%s
571import cgi
572
573print("Content-type: text/html")
574print()
575
576form = cgi.FieldStorage()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000577print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
578 form.getfirst("bacon")))
Georg Brandlb533e262008-05-25 18:19:30 +0000579"""
580
Martin Pantera02e18a2015-10-03 05:38:07 +0000581cgi_file4 = """\
582#!%s
583import os
584
585print("Content-type: text/html")
586print()
587
588print(os.environ["%s"])
589"""
590
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800591cgi_file6 = """\
592#!%s
593import os
594
595print("Content-type: text/plain")
596print()
597print(repr(os.environ))
598"""
599
Charles-François Natalif7ed9fc2011-11-02 19:35:14 +0100600
601@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
602 "This test can't be run reliably as root (issue #13308).")
Georg Brandlb533e262008-05-25 18:19:30 +0000603class CGIHTTPServerTestCase(BaseTestCase):
604 class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
605 pass
606
Antoine Pitroue768c392012-08-05 14:52:45 +0200607 linesep = os.linesep.encode('ascii')
608
Georg Brandlb533e262008-05-25 18:19:30 +0000609 def setUp(self):
610 BaseTestCase.setUp(self)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000611 self.cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +0000612 self.parent_dir = tempfile.mkdtemp()
613 self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
Ned Deily915a30f2014-07-12 22:06:26 -0700614 self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
Siwon Kang91daa9d2019-11-22 18:13:05 +0900615 self.sub_dir_1 = os.path.join(self.parent_dir, 'sub')
616 self.sub_dir_2 = os.path.join(self.sub_dir_1, 'dir')
617 self.cgi_dir_in_sub_dir = os.path.join(self.sub_dir_2, 'cgi-bin')
Georg Brandlb533e262008-05-25 18:19:30 +0000618 os.mkdir(self.cgi_dir)
Ned Deily915a30f2014-07-12 22:06:26 -0700619 os.mkdir(self.cgi_child_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900620 os.mkdir(self.sub_dir_1)
621 os.mkdir(self.sub_dir_2)
622 os.mkdir(self.cgi_dir_in_sub_dir)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400623 self.nocgi_path = None
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000624 self.file1_path = None
625 self.file2_path = None
Ned Deily915a30f2014-07-12 22:06:26 -0700626 self.file3_path = None
Martin Pantera02e18a2015-10-03 05:38:07 +0000627 self.file4_path = None
Siwon Kang91daa9d2019-11-22 18:13:05 +0900628 self.file5_path = None
Georg Brandlb533e262008-05-25 18:19:30 +0000629
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000630 # The shebang line should be pure ASCII: use symlink if possible.
631 # See issue #7668.
Steve Dower9048c492019-06-29 10:34:11 -0700632 self._pythonexe_symlink = None
Hai Shi79bb2c92020-08-06 19:51:29 +0800633 if os_helper.can_symlink():
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000634 self.pythonexe = os.path.join(self.parent_dir, 'python')
Steve Dower9048c492019-06-29 10:34:11 -0700635 self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000636 else:
637 self.pythonexe = sys.executable
638
Victor Stinner3218c312010-10-17 20:13:36 +0000639 try:
640 # The python executable path is written as the first line of the
641 # CGI Python script. The encoding cookie cannot be used, and so the
642 # path should be encodable to the default script encoding (utf-8)
643 self.pythonexe.encode('utf-8')
644 except UnicodeEncodeError:
645 self.tearDown()
Serhiy Storchaka0b4591e2013-02-04 15:45:00 +0200646 self.skipTest("Python executable path is not encodable to utf-8")
Victor Stinner3218c312010-10-17 20:13:36 +0000647
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400648 self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
649 with open(self.nocgi_path, 'w') as fp:
650 fp.write(cgi_file1 % self.pythonexe)
651 os.chmod(self.nocgi_path, 0o777)
652
Georg Brandlb533e262008-05-25 18:19:30 +0000653 self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000654 with open(self.file1_path, 'w', encoding='utf-8') as file1:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000655 file1.write(cgi_file1 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000656 os.chmod(self.file1_path, 0o777)
657
658 self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000659 with open(self.file2_path, 'w', encoding='utf-8') as file2:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000660 file2.write(cgi_file2 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000661 os.chmod(self.file2_path, 0o777)
662
Ned Deily915a30f2014-07-12 22:06:26 -0700663 self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
664 with open(self.file3_path, 'w', encoding='utf-8') as file3:
665 file3.write(cgi_file1 % self.pythonexe)
666 os.chmod(self.file3_path, 0o777)
667
Martin Pantera02e18a2015-10-03 05:38:07 +0000668 self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
669 with open(self.file4_path, 'w', encoding='utf-8') as file4:
670 file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
671 os.chmod(self.file4_path, 0o777)
672
Siwon Kang91daa9d2019-11-22 18:13:05 +0900673 self.file5_path = os.path.join(self.cgi_dir_in_sub_dir, 'file5.py')
674 with open(self.file5_path, 'w', encoding='utf-8') as file5:
675 file5.write(cgi_file1 % self.pythonexe)
676 os.chmod(self.file5_path, 0o777)
677
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800678 self.file6_path = os.path.join(self.cgi_dir, 'file6.py')
679 with open(self.file6_path, 'w', encoding='utf-8') as file6:
680 file6.write(cgi_file6 % self.pythonexe)
681 os.chmod(self.file6_path, 0o777)
682
Georg Brandlb533e262008-05-25 18:19:30 +0000683 os.chdir(self.parent_dir)
684
685 def tearDown(self):
686 try:
687 os.chdir(self.cwd)
Steve Dower9048c492019-06-29 10:34:11 -0700688 if self._pythonexe_symlink:
689 self._pythonexe_symlink.__exit__(None, None, None)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400690 if self.nocgi_path:
691 os.remove(self.nocgi_path)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000692 if self.file1_path:
693 os.remove(self.file1_path)
694 if self.file2_path:
695 os.remove(self.file2_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700696 if self.file3_path:
697 os.remove(self.file3_path)
Martin Pantera02e18a2015-10-03 05:38:07 +0000698 if self.file4_path:
699 os.remove(self.file4_path)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900700 if self.file5_path:
701 os.remove(self.file5_path)
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800702 if self.file6_path:
703 os.remove(self.file6_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700704 os.rmdir(self.cgi_child_dir)
Georg Brandlb533e262008-05-25 18:19:30 +0000705 os.rmdir(self.cgi_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900706 os.rmdir(self.cgi_dir_in_sub_dir)
707 os.rmdir(self.sub_dir_2)
708 os.rmdir(self.sub_dir_1)
Georg Brandlb533e262008-05-25 18:19:30 +0000709 os.rmdir(self.parent_dir)
710 finally:
711 BaseTestCase.tearDown(self)
712
Senthil Kumarand70846b2012-04-12 02:34:32 +0800713 def test_url_collapse_path(self):
714 # verify tail is the last portion and head is the rest on proper urls
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000715 test_vectors = {
Senthil Kumarand70846b2012-04-12 02:34:32 +0800716 '': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000717 '..': IndexError,
718 '/.//..': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800719 '/': '//',
720 '//': '//',
721 '/\\': '//\\',
722 '/.//': '//',
723 'cgi-bin/file1.py': '/cgi-bin/file1.py',
724 '/cgi-bin/file1.py': '/cgi-bin/file1.py',
725 'a': '//a',
726 '/a': '//a',
727 '//a': '//a',
728 './a': '//a',
729 './C:/': '/C:/',
730 '/a/b': '/a/b',
731 '/a/b/': '/a/b/',
732 '/a/b/.': '/a/b/',
733 '/a/b/c/..': '/a/b/',
734 '/a/b/c/../d': '/a/b/d',
735 '/a/b/c/../d/e/../f': '/a/b/d/f',
736 '/a/b/c/../d/e/../../f': '/a/b/f',
737 '/a/b/c/../d/e/.././././..//f': '/a/b/f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000738 '../a/b/c/../d/e/.././././..//f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800739 '/a/b/c/../d/e/../../../f': '/a/f',
740 '/a/b/c/../d/e/../../../../f': '//f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000741 '/a/b/c/../d/e/../../../../../f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800742 '/a/b/c/../d/e/../../../../f/..': '//',
743 '/a/b/c/../d/e/../../../../f/../.': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000744 }
745 for path, expected in test_vectors.items():
746 if isinstance(expected, type) and issubclass(expected, Exception):
747 self.assertRaises(expected,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800748 server._url_collapse_path, path)
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000749 else:
Senthil Kumarand70846b2012-04-12 02:34:32 +0800750 actual = server._url_collapse_path(path)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000751 self.assertEqual(expected, actual,
752 msg='path = %r\nGot: %r\nWanted: %r' %
753 (path, actual, expected))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000754
Georg Brandlb533e262008-05-25 18:19:30 +0000755 def test_headers_and_content(self):
756 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200757 self.assertEqual(
758 (res.read(), res.getheader('Content-type'), res.status),
759 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK))
Georg Brandlb533e262008-05-25 18:19:30 +0000760
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400761 def test_issue19435(self):
762 res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200763 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400764
Georg Brandlb533e262008-05-25 18:19:30 +0000765 def test_post(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000766 params = urllib.parse.urlencode(
767 {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
Georg Brandlb533e262008-05-25 18:19:30 +0000768 headers = {'Content-type' : 'application/x-www-form-urlencoded'}
769 res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
770
Antoine Pitroue768c392012-08-05 14:52:45 +0200771 self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
Georg Brandlb533e262008-05-25 18:19:30 +0000772
773 def test_invaliduri(self):
774 res = self.request('/cgi-bin/invalid')
775 res.read()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200776 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000777
778 def test_authorization(self):
779 headers = {b'Authorization' : b'Basic ' +
780 base64.b64encode(b'username:pass')}
781 res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200782 self.assertEqual(
783 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
784 (res.read(), res.getheader('Content-type'), res.status))
Georg Brandlb533e262008-05-25 18:19:30 +0000785
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000786 def test_no_leading_slash(self):
787 # http://bugs.python.org/issue2254
788 res = self.request('cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200789 self.assertEqual(
790 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
791 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000792
Senthil Kumaran42713722010-10-03 17:55:45 +0000793 def test_os_environ_is_not_altered(self):
794 signature = "Test CGI Server"
795 os.environ['SERVER_SOFTWARE'] = signature
796 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200797 self.assertEqual(
798 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
799 (res.read(), res.getheader('Content-type'), res.status))
Senthil Kumaran42713722010-10-03 17:55:45 +0000800 self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
801
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700802 def test_urlquote_decoding_in_cgi_check(self):
803 res = self.request('/cgi-bin%2ffile1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200804 self.assertEqual(
805 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
806 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700807
Ned Deily915a30f2014-07-12 22:06:26 -0700808 def test_nested_cgi_path_issue21323(self):
809 res = self.request('/cgi-bin/child-dir/file3.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200810 self.assertEqual(
811 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
812 (res.read(), res.getheader('Content-type'), res.status))
Ned Deily915a30f2014-07-12 22:06:26 -0700813
Martin Pantera02e18a2015-10-03 05:38:07 +0000814 def test_query_with_multiple_question_mark(self):
815 res = self.request('/cgi-bin/file4.py?a=b?c=d')
816 self.assertEqual(
Martin Pantereb1fee92015-10-03 06:07:22 +0000817 (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
Martin Pantera02e18a2015-10-03 05:38:07 +0000818 (res.read(), res.getheader('Content-type'), res.status))
819
Martin Pantercb29e8c2015-10-03 05:55:46 +0000820 def test_query_with_continuous_slashes(self):
821 res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
822 self.assertEqual(
823 (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
Martin Pantereb1fee92015-10-03 06:07:22 +0000824 'text/html', HTTPStatus.OK),
Martin Pantercb29e8c2015-10-03 05:55:46 +0000825 (res.read(), res.getheader('Content-type'), res.status))
826
Siwon Kang91daa9d2019-11-22 18:13:05 +0900827 def test_cgi_path_in_sub_directories(self):
Pablo Galindo24f5cac2019-12-04 09:29:10 +0000828 try:
829 CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
830 res = self.request('/sub/dir/cgi-bin/file5.py')
831 self.assertEqual(
832 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
833 (res.read(), res.getheader('Content-type'), res.status))
834 finally:
835 CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
836
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800837 def test_accept(self):
838 browser_accept = \
839 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
840 tests = (
841 ((('Accept', browser_accept),), browser_accept),
842 ((), ''),
843 # Hack case to get two values for the one header
844 ((('Accept', 'text/html'), ('ACCEPT', 'text/plain')),
845 'text/html,text/plain'),
846 )
847 for headers, expected in tests:
848 headers = OrderedDict(headers)
849 with self.subTest(headers):
850 res = self.request('/cgi-bin/file6.py', 'GET', headers=headers)
851 self.assertEqual(http.HTTPStatus.OK, res.status)
852 expected = f"'HTTP_ACCEPT': {expected!r}"
853 self.assertIn(expected.encode('ascii'), res.read())
Siwon Kang91daa9d2019-11-22 18:13:05 +0900854
Georg Brandlb533e262008-05-25 18:19:30 +0000855
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000856class SocketlessRequestHandler(SimpleHTTPRequestHandler):
Géry Ogam781266e2019-09-11 15:03:46 +0200857 def __init__(self, directory=None):
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200858 request = mock.Mock()
859 request.makefile.return_value = BytesIO()
Géry Ogam781266e2019-09-11 15:03:46 +0200860 super().__init__(request, None, None, directory=directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200861
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000862 self.get_called = False
863 self.protocol_version = "HTTP/1.1"
864
865 def do_GET(self):
866 self.get_called = True
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200867 self.send_response(HTTPStatus.OK)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000868 self.send_header('Content-Type', 'text/html')
869 self.end_headers()
870 self.wfile.write(b'<html><body>Data</body></html>\r\n')
871
872 def log_message(self, format, *args):
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000873 pass
874
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000875class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
876 def handle_expect_100(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200877 self.send_error(HTTPStatus.EXPECTATION_FAILED)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000878 return False
879
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800880
881class AuditableBytesIO:
882
883 def __init__(self):
884 self.datas = []
885
886 def write(self, data):
887 self.datas.append(data)
888
889 def getData(self):
890 return b''.join(self.datas)
891
892 @property
893 def numWrites(self):
894 return len(self.datas)
895
896
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000897class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
Ezio Melotti3b3499b2011-03-16 11:35:38 +0200898 """Test the functionality of the BaseHTTPServer.
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000899
900 Test the support for the Expect 100-continue header.
901 """
902
903 HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
904
905 def setUp (self):
906 self.handler = SocketlessRequestHandler()
907
908 def send_typical_request(self, message):
909 input = BytesIO(message)
910 output = BytesIO()
911 self.handler.rfile = input
912 self.handler.wfile = output
913 self.handler.handle_one_request()
914 output.seek(0)
915 return output.readlines()
916
917 def verify_get_called(self):
918 self.assertTrue(self.handler.get_called)
919
920 def verify_expected_headers(self, headers):
921 for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
922 self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
923
924 def verify_http_server_response(self, response):
925 match = self.HTTPResponseMatch.search(response)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200926 self.assertIsNotNone(match)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000927
928 def test_http_1_1(self):
929 result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
930 self.verify_http_server_response(result[0])
931 self.verify_expected_headers(result[1:-1])
932 self.verify_get_called()
933 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500934 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
935 self.assertEqual(self.handler.command, 'GET')
936 self.assertEqual(self.handler.path, '/')
937 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
938 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000939
940 def test_http_1_0(self):
941 result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
942 self.verify_http_server_response(result[0])
943 self.verify_expected_headers(result[1:-1])
944 self.verify_get_called()
945 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500946 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
947 self.assertEqual(self.handler.command, 'GET')
948 self.assertEqual(self.handler.path, '/')
949 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
950 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000951
952 def test_http_0_9(self):
953 result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
954 self.assertEqual(len(result), 1)
955 self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
956 self.verify_get_called()
957
Martin Pantere82338d2016-11-19 01:06:37 +0000958 def test_extra_space(self):
959 result = self.send_typical_request(
960 b'GET /spaced out HTTP/1.1\r\n'
961 b'Host: dummy\r\n'
962 b'\r\n'
963 )
964 self.assertTrue(result[0].startswith(b'HTTP/1.1 400 '))
965 self.verify_expected_headers(result[1:result.index(b'\r\n')])
966 self.assertFalse(self.handler.get_called)
967
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000968 def test_with_continue_1_0(self):
969 result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
970 self.verify_http_server_response(result[0])
971 self.verify_expected_headers(result[1:-1])
972 self.verify_get_called()
973 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500974 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
975 self.assertEqual(self.handler.command, 'GET')
976 self.assertEqual(self.handler.path, '/')
977 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
978 headers = (("Expect", "100-continue"),)
979 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000980
981 def test_with_continue_1_1(self):
982 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
983 self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
Benjamin Peterson04424232014-01-18 21:50:18 -0500984 self.assertEqual(result[1], b'\r\n')
985 self.assertEqual(result[2], b'HTTP/1.1 200 OK\r\n')
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000986 self.verify_expected_headers(result[2:-1])
987 self.verify_get_called()
988 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500989 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
990 self.assertEqual(self.handler.command, 'GET')
991 self.assertEqual(self.handler.path, '/')
992 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
993 headers = (("Expect", "100-continue"),)
994 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000995
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800996 def test_header_buffering_of_send_error(self):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000997
998 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800999 output = AuditableBytesIO()
1000 handler = SocketlessRequestHandler()
1001 handler.rfile = input
1002 handler.wfile = output
1003 handler.request_version = 'HTTP/1.1'
1004 handler.requestline = ''
1005 handler.command = None
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001006
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001007 handler.send_error(418)
1008 self.assertEqual(output.numWrites, 2)
1009
1010 def test_header_buffering_of_send_response_only(self):
1011
1012 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1013 output = AuditableBytesIO()
1014 handler = SocketlessRequestHandler()
1015 handler.rfile = input
1016 handler.wfile = output
1017 handler.request_version = 'HTTP/1.1'
1018
1019 handler.send_response_only(418)
1020 self.assertEqual(output.numWrites, 0)
1021 handler.end_headers()
1022 self.assertEqual(output.numWrites, 1)
1023
1024 def test_header_buffering_of_send_header(self):
1025
1026 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1027 output = AuditableBytesIO()
1028 handler = SocketlessRequestHandler()
1029 handler.rfile = input
1030 handler.wfile = output
1031 handler.request_version = 'HTTP/1.1'
1032
1033 handler.send_header('Foo', 'foo')
1034 handler.send_header('bar', 'bar')
1035 self.assertEqual(output.numWrites, 0)
1036 handler.end_headers()
1037 self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
1038 self.assertEqual(output.numWrites, 1)
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001039
1040 def test_header_unbuffered_when_continue(self):
1041
1042 def _readAndReseek(f):
1043 pos = f.tell()
1044 f.seek(0)
1045 data = f.read()
1046 f.seek(pos)
1047 return data
1048
1049 input = BytesIO(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1050 output = BytesIO()
1051 self.handler.rfile = input
1052 self.handler.wfile = output
1053 self.handler.request_version = 'HTTP/1.1'
1054
1055 self.handler.handle_one_request()
1056 self.assertNotEqual(_readAndReseek(output), b'')
1057 result = _readAndReseek(output).split(b'\r\n')
1058 self.assertEqual(result[0], b'HTTP/1.1 100 Continue')
Benjamin Peterson04424232014-01-18 21:50:18 -05001059 self.assertEqual(result[1], b'')
1060 self.assertEqual(result[2], b'HTTP/1.1 200 OK')
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001061
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001062 def test_with_continue_rejected(self):
1063 usual_handler = self.handler # Save to avoid breaking any subsequent tests.
1064 self.handler = RejectingSocketlessRequestHandler()
1065 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1066 self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
1067 self.verify_expected_headers(result[1:-1])
1068 # The expect handler should short circuit the usual get method by
1069 # returning false here, so get_called should be false
1070 self.assertFalse(self.handler.get_called)
1071 self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
1072 self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
1073
Antoine Pitrouc4924372010-12-16 16:48:36 +00001074 def test_request_length(self):
1075 # Issue #10714: huge request lines are discarded, to avoid Denial
1076 # of Service attacks.
1077 result = self.send_typical_request(b'GET ' + b'x' * 65537)
1078 self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
1079 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001080 self.assertIsInstance(self.handler.requestline, str)
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001081
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001082 def test_header_length(self):
1083 # Issue #6791: same for headers
1084 result = self.send_typical_request(
1085 b'GET / HTTP/1.1\r\nX-Foo: bar' + b'r' * 65537 + b'\r\n\r\n')
Martin Panter50badad2016-04-03 01:28:53 +00001086 self.assertEqual(result[0], b'HTTP/1.1 431 Line too long\r\n')
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001087 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001088 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1089
Martin Panteracc03192016-04-03 00:45:46 +00001090 def test_too_many_headers(self):
1091 result = self.send_typical_request(
1092 b'GET / HTTP/1.1\r\n' + b'X-Foo: bar\r\n' * 101 + b'\r\n')
1093 self.assertEqual(result[0], b'HTTP/1.1 431 Too many headers\r\n')
1094 self.assertFalse(self.handler.get_called)
1095 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1096
Martin Panterda3bb382016-04-11 00:40:08 +00001097 def test_html_escape_on_error(self):
1098 result = self.send_typical_request(
1099 b'<script>alert("hello")</script> / HTTP/1.1')
1100 result = b''.join(result)
1101 text = '<script>alert("hello")</script>'
1102 self.assertIn(html.escape(text, quote=False).encode('ascii'), result)
1103
Benjamin Peterson70e28472015-02-17 21:11:10 -05001104 def test_close_connection(self):
1105 # handle_one_request() should be repeatedly called until
1106 # it sets close_connection
1107 def handle_one_request():
1108 self.handler.close_connection = next(close_values)
1109 self.handler.handle_one_request = handle_one_request
1110
1111 close_values = iter((True,))
1112 self.handler.handle()
1113 self.assertRaises(StopIteration, next, close_values)
1114
1115 close_values = iter((False, False, True))
1116 self.handler.handle()
1117 self.assertRaises(StopIteration, next, close_values)
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001118
Berker Peksag04bc5b92016-03-14 06:06:03 +02001119 def test_date_time_string(self):
1120 now = time.time()
1121 # this is the old code that formats the timestamp
1122 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
1123 expected = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
1124 self.handler.weekdayname[wd],
1125 day,
1126 self.handler.monthname[month],
1127 year, hh, mm, ss
1128 )
1129 self.assertEqual(self.handler.date_time_string(timestamp=now), expected)
1130
1131
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001132class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
1133 """ Test url parsing """
1134 def setUp(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001135 self.translated_1 = os.path.join(os.getcwd(), 'filename')
1136 self.translated_2 = os.path.join('foo', 'filename')
1137 self.translated_3 = os.path.join('bar', 'filename')
1138 self.handler_1 = SocketlessRequestHandler()
1139 self.handler_2 = SocketlessRequestHandler(directory='foo')
1140 self.handler_3 = SocketlessRequestHandler(directory=pathlib.PurePath('bar'))
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001141
1142 def test_query_arguments(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001143 path = self.handler_1.translate_path('/filename')
1144 self.assertEqual(path, self.translated_1)
1145 path = self.handler_2.translate_path('/filename')
1146 self.assertEqual(path, self.translated_2)
1147 path = self.handler_3.translate_path('/filename')
1148 self.assertEqual(path, self.translated_3)
1149
1150 path = self.handler_1.translate_path('/filename?foo=bar')
1151 self.assertEqual(path, self.translated_1)
1152 path = self.handler_2.translate_path('/filename?foo=bar')
1153 self.assertEqual(path, self.translated_2)
1154 path = self.handler_3.translate_path('/filename?foo=bar')
1155 self.assertEqual(path, self.translated_3)
1156
1157 path = self.handler_1.translate_path('/filename?a=b&spam=eggs#zot')
1158 self.assertEqual(path, self.translated_1)
1159 path = self.handler_2.translate_path('/filename?a=b&spam=eggs#zot')
1160 self.assertEqual(path, self.translated_2)
1161 path = self.handler_3.translate_path('/filename?a=b&spam=eggs#zot')
1162 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001163
1164 def test_start_with_double_slash(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001165 path = self.handler_1.translate_path('//filename')
1166 self.assertEqual(path, self.translated_1)
1167 path = self.handler_2.translate_path('//filename')
1168 self.assertEqual(path, self.translated_2)
1169 path = self.handler_3.translate_path('//filename')
1170 self.assertEqual(path, self.translated_3)
1171
1172 path = self.handler_1.translate_path('//filename?foo=bar')
1173 self.assertEqual(path, self.translated_1)
1174 path = self.handler_2.translate_path('//filename?foo=bar')
1175 self.assertEqual(path, self.translated_2)
1176 path = self.handler_3.translate_path('//filename?foo=bar')
1177 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001178
Martin Panterd274b3f2016-04-18 03:45:18 +00001179 def test_windows_colon(self):
1180 with support.swap_attr(server.os, 'path', ntpath):
Géry Ogam781266e2019-09-11 15:03:46 +02001181 path = self.handler_1.translate_path('c:c:c:foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001182 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001183 self.assertEqual(path, self.translated_1)
1184 path = self.handler_2.translate_path('c:c:c:foo/filename')
1185 path = path.replace(ntpath.sep, os.sep)
1186 self.assertEqual(path, self.translated_2)
1187 path = self.handler_3.translate_path('c:c:c:foo/filename')
1188 path = path.replace(ntpath.sep, os.sep)
1189 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001190
Géry Ogam781266e2019-09-11 15:03:46 +02001191 path = self.handler_1.translate_path('\\c:../filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001192 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001193 self.assertEqual(path, self.translated_1)
1194 path = self.handler_2.translate_path('\\c:../filename')
1195 path = path.replace(ntpath.sep, os.sep)
1196 self.assertEqual(path, self.translated_2)
1197 path = self.handler_3.translate_path('\\c:../filename')
1198 path = path.replace(ntpath.sep, os.sep)
1199 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001200
Géry Ogam781266e2019-09-11 15:03:46 +02001201 path = self.handler_1.translate_path('c:\\c:..\\foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001202 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001203 self.assertEqual(path, self.translated_1)
1204 path = self.handler_2.translate_path('c:\\c:..\\foo/filename')
1205 path = path.replace(ntpath.sep, os.sep)
1206 self.assertEqual(path, self.translated_2)
1207 path = self.handler_3.translate_path('c:\\c:..\\foo/filename')
1208 path = path.replace(ntpath.sep, os.sep)
1209 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001210
Géry Ogam781266e2019-09-11 15:03:46 +02001211 path = self.handler_1.translate_path('c:c:foo\\c:c:bar/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001212 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001213 self.assertEqual(path, self.translated_1)
1214 path = self.handler_2.translate_path('c:c:foo\\c:c:bar/filename')
1215 path = path.replace(ntpath.sep, os.sep)
1216 self.assertEqual(path, self.translated_2)
1217 path = self.handler_3.translate_path('c:c:foo\\c:c:bar/filename')
1218 path = path.replace(ntpath.sep, os.sep)
1219 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001220
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001221
Berker Peksag366c5702015-02-13 20:48:15 +02001222class MiscTestCase(unittest.TestCase):
1223 def test_all(self):
1224 expected = []
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001225 denylist = {'executable', 'nobody_uid', 'test'}
Berker Peksag366c5702015-02-13 20:48:15 +02001226 for name in dir(server):
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001227 if name.startswith('_') or name in denylist:
Berker Peksag366c5702015-02-13 20:48:15 +02001228 continue
1229 module_object = getattr(server, name)
1230 if getattr(module_object, '__module__', None) == 'http.server':
1231 expected.append(name)
1232 self.assertCountEqual(server.__all__, expected)
1233
1234
Lisa Roach433433f2018-11-26 10:43:38 -08001235class ScriptTestCase(unittest.TestCase):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001236
1237 def mock_server_class(self):
1238 return mock.MagicMock(
1239 return_value=mock.MagicMock(
1240 __enter__=mock.MagicMock(
1241 return_value=mock.MagicMock(
1242 socket=mock.MagicMock(
1243 getsockname=lambda: ('', 0),
1244 ),
1245 ),
1246 ),
1247 ),
1248 )
1249
1250 @mock.patch('builtins.print')
1251 def test_server_test_unspec(self, _):
1252 mock_server = self.mock_server_class()
1253 server.test(ServerClass=mock_server, bind=None)
1254 self.assertIn(
1255 mock_server.address_family,
1256 (socket.AF_INET6, socket.AF_INET),
1257 )
1258
1259 @mock.patch('builtins.print')
1260 def test_server_test_localhost(self, _):
1261 mock_server = self.mock_server_class()
1262 server.test(ServerClass=mock_server, bind="localhost")
1263 self.assertIn(
1264 mock_server.address_family,
1265 (socket.AF_INET6, socket.AF_INET),
1266 )
1267
1268 ipv6_addrs = (
1269 "::",
1270 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
1271 "::1",
1272 )
1273
1274 ipv4_addrs = (
1275 "0.0.0.0",
1276 "8.8.8.8",
1277 "127.0.0.1",
1278 )
1279
Lisa Roach433433f2018-11-26 10:43:38 -08001280 @mock.patch('builtins.print')
1281 def test_server_test_ipv6(self, _):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001282 for bind in self.ipv6_addrs:
1283 mock_server = self.mock_server_class()
1284 server.test(ServerClass=mock_server, bind=bind)
1285 self.assertEqual(mock_server.address_family, socket.AF_INET6)
Lisa Roach433433f2018-11-26 10:43:38 -08001286
Jason R. Coombsf2890842019-02-07 08:22:45 -05001287 @mock.patch('builtins.print')
1288 def test_server_test_ipv4(self, _):
1289 for bind in self.ipv4_addrs:
1290 mock_server = self.mock_server_class()
1291 server.test(ServerClass=mock_server, bind=bind)
1292 self.assertEqual(mock_server.address_family, socket.AF_INET)
Lisa Roach433433f2018-11-26 10:43:38 -08001293
1294
Georg Brandlb533e262008-05-25 18:19:30 +00001295def test_main(verbose=None):
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001296 cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +00001297 try:
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001298 support.run_unittest(
Serhiy Storchakac0a23e62015-03-07 11:51:37 +02001299 RequestHandlerLoggingTestCase,
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001300 BaseHTTPRequestHandlerTestCase,
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001301 BaseHTTPServerTestCase,
1302 SimpleHTTPServerTestCase,
1303 CGIHTTPServerTestCase,
1304 SimpleHTTPRequestHandlerTestCase,
Berker Peksag366c5702015-02-13 20:48:15 +02001305 MiscTestCase,
Lisa Roach433433f2018-11-26 10:43:38 -08001306 ScriptTestCase
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001307 )
Georg Brandlb533e262008-05-25 18:19:30 +00001308 finally:
1309 os.chdir(cwd)
1310
1311if __name__ == '__main__':
1312 test_main()