blob: 1cc020f63539decafddf10df24ccab0715cad4f7 [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):
Ethan Furman1b4addf2021-06-18 14:25:42 -0700262 self.con.request('SEND_ERROR', '/{}'.format(code))
Martin Pantere42e1292016-06-08 08:29:13 +0000263 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):
Ethan Furman1b4addf2021-06-18 14:25:42 -0700279 self.con.request('HEAD', '/{}'.format(code))
Martin Pantere42e1292016-06-08 08:29:13 +0000280 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)
Miss Islington (bot)058f9b22021-05-06 12:48:36 -0700431 self.assertEqual(response.getheader("Content-Length"), "0")
Martin Panterfc475a92016-04-09 04:56:10 +0000432 response = self.request(self.base_url + '/?hi=2')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200433 self.check_status_and_reason(response, HTTPStatus.OK)
Martin Panterfc475a92016-04-09 04:56:10 +0000434 response = self.request(self.base_url + '?hi=1')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200435 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600436 self.assertEqual(response.getheader("Location"),
Martin Panterfc475a92016-04-09 04:56:10 +0000437 self.base_url + "/?hi=1")
Georg Brandlb533e262008-05-25 18:19:30 +0000438 response = self.request('/ThisDoesNotExist')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200439 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000440 response = self.request('/' + 'ThisDoesNotExist' + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200441 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300442
443 data = b"Dummy index file\r\n"
444 with open(os.path.join(self.tempdir_name, 'index.html'), 'wb') as f:
445 f.write(data)
Martin Panterfc475a92016-04-09 04:56:10 +0000446 response = self.request(self.base_url + '/')
Berker Peksagb5754322015-07-22 19:25:37 +0300447 self.check_status_and_reason(response, HTTPStatus.OK, data)
448
449 # chmod() doesn't work as expected on Windows, and filesystem
450 # permissions are ignored by root on Unix.
451 if os.name == 'posix' and os.geteuid() != 0:
452 os.chmod(self.tempdir, 0)
453 try:
Martin Panterfc475a92016-04-09 04:56:10 +0000454 response = self.request(self.base_url + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200455 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300456 finally:
Brett Cannon105df5d2010-10-29 23:43:42 +0000457 os.chmod(self.tempdir, 0o755)
Georg Brandlb533e262008-05-25 18:19:30 +0000458
459 def test_head(self):
460 response = self.request(
Martin Panterfc475a92016-04-09 04:56:10 +0000461 self.base_url + '/test', method='HEAD')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200462 self.check_status_and_reason(response, HTTPStatus.OK)
Georg Brandlb533e262008-05-25 18:19:30 +0000463 self.assertEqual(response.getheader('content-length'),
464 str(len(self.data)))
465 self.assertEqual(response.getheader('content-type'),
466 'application/octet-stream')
467
Pierre Quentel351adda2017-04-02 12:26:12 +0200468 def test_browser_cache(self):
469 """Check that when a request to /test is sent with the request header
470 If-Modified-Since set to date of last modification, the server returns
471 status code 304, not 200
472 """
473 headers = email.message.Message()
474 headers['If-Modified-Since'] = self.last_modif_header
475 response = self.request(self.base_url + '/test', headers=headers)
476 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
477
478 # one hour after last modification : must return 304
479 new_dt = self.last_modif_datetime + datetime.timedelta(hours=1)
480 headers = email.message.Message()
481 headers['If-Modified-Since'] = email.utils.format_datetime(new_dt,
482 usegmt=True)
483 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200484 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
Pierre Quentel351adda2017-04-02 12:26:12 +0200485
486 def test_browser_cache_file_changed(self):
487 # with If-Modified-Since earlier than Last-Modified, must return 200
488 dt = self.last_modif_datetime
489 # build datetime object : 365 days before last modification
490 old_dt = dt - datetime.timedelta(days=365)
491 headers = email.message.Message()
492 headers['If-Modified-Since'] = email.utils.format_datetime(old_dt,
493 usegmt=True)
494 response = self.request(self.base_url + '/test', headers=headers)
495 self.check_status_and_reason(response, HTTPStatus.OK)
496
497 def test_browser_cache_with_If_None_Match_header(self):
498 # if If-None-Match header is present, ignore If-Modified-Since
499
500 headers = email.message.Message()
501 headers['If-Modified-Since'] = self.last_modif_header
502 headers['If-None-Match'] = "*"
503 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200504 self.check_status_and_reason(response, HTTPStatus.OK)
Pierre Quentel351adda2017-04-02 12:26:12 +0200505
Georg Brandlb533e262008-05-25 18:19:30 +0000506 def test_invalid_requests(self):
507 response = self.request('/', method='FOO')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200508 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000509 # requests must be case sensitive,so this should fail too
Terry Jan Reedydd09efd2014-10-18 17:10:09 -0400510 response = self.request('/', method='custom')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200511 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000512 response = self.request('/', method='GETs')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200513 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000514
Pierre Quentel351adda2017-04-02 12:26:12 +0200515 def test_last_modified(self):
516 """Checks that the datetime returned in Last-Modified response header
517 is the actual datetime of last modification, rounded to the second
518 """
519 response = self.request(self.base_url + '/test')
520 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
521 last_modif_header = response.headers['Last-modified']
522 self.assertEqual(last_modif_header, self.last_modif_header)
523
Martin Panterfc475a92016-04-09 04:56:10 +0000524 def test_path_without_leading_slash(self):
525 response = self.request(self.tempdir_name + '/test')
526 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
527 response = self.request(self.tempdir_name + '/test/')
528 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
529 response = self.request(self.tempdir_name + '/')
530 self.check_status_and_reason(response, HTTPStatus.OK)
531 response = self.request(self.tempdir_name)
532 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
533 response = self.request(self.tempdir_name + '/?hi=2')
534 self.check_status_and_reason(response, HTTPStatus.OK)
535 response = self.request(self.tempdir_name + '?hi=1')
536 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
537 self.assertEqual(response.getheader("Location"),
538 self.tempdir_name + "/?hi=1")
539
Martin Panterda3bb382016-04-11 00:40:08 +0000540 def test_html_escape_filename(self):
541 filename = '<test&>.txt'
542 fullpath = os.path.join(self.tempdir, filename)
543
544 try:
Inada Naokifa51c0c2021-04-29 11:34:56 +0900545 open(fullpath, 'wb').close()
Martin Panterda3bb382016-04-11 00:40:08 +0000546 except OSError:
547 raise unittest.SkipTest('Can not create file %s on current file '
548 'system' % filename)
549
550 try:
551 response = self.request(self.base_url + '/')
552 body = self.check_status_and_reason(response, HTTPStatus.OK)
553 enc = response.headers.get_content_charset()
554 finally:
555 os.unlink(fullpath) # avoid affecting test_undecodable_filename
556
557 self.assertIsNotNone(enc)
558 html_text = '>%s<' % html.escape(filename, quote=False)
559 self.assertIn(html_text.encode(enc), body)
560
Georg Brandlb533e262008-05-25 18:19:30 +0000561
562cgi_file1 = """\
563#!%s
564
565print("Content-type: text/html")
566print()
567print("Hello World")
568"""
569
570cgi_file2 = """\
571#!%s
572import cgi
573
574print("Content-type: text/html")
575print()
576
577form = cgi.FieldStorage()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000578print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
579 form.getfirst("bacon")))
Georg Brandlb533e262008-05-25 18:19:30 +0000580"""
581
Martin Pantera02e18a2015-10-03 05:38:07 +0000582cgi_file4 = """\
583#!%s
584import os
585
586print("Content-type: text/html")
587print()
588
589print(os.environ["%s"])
590"""
591
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800592cgi_file6 = """\
593#!%s
594import os
595
Miss Islington (bot)356bdff2021-07-15 13:09:26 -0700596print("X-ambv: was here")
597print("Content-type: text/html")
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800598print()
Miss Islington (bot)356bdff2021-07-15 13:09:26 -0700599print("<pre>")
600for k, v in os.environ.items():
601 try:
602 k.encode('ascii')
603 v.encode('ascii')
604 except UnicodeEncodeError:
605 continue # see: BPO-44647
606 print(f"{k}={v}")
607print("</pre>")
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800608"""
609
Charles-François Natalif7ed9fc2011-11-02 19:35:14 +0100610
611@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
612 "This test can't be run reliably as root (issue #13308).")
Georg Brandlb533e262008-05-25 18:19:30 +0000613class CGIHTTPServerTestCase(BaseTestCase):
614 class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
615 pass
616
Antoine Pitroue768c392012-08-05 14:52:45 +0200617 linesep = os.linesep.encode('ascii')
618
Georg Brandlb533e262008-05-25 18:19:30 +0000619 def setUp(self):
620 BaseTestCase.setUp(self)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000621 self.cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +0000622 self.parent_dir = tempfile.mkdtemp()
623 self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
Ned Deily915a30f2014-07-12 22:06:26 -0700624 self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
Siwon Kang91daa9d2019-11-22 18:13:05 +0900625 self.sub_dir_1 = os.path.join(self.parent_dir, 'sub')
626 self.sub_dir_2 = os.path.join(self.sub_dir_1, 'dir')
627 self.cgi_dir_in_sub_dir = os.path.join(self.sub_dir_2, 'cgi-bin')
Georg Brandlb533e262008-05-25 18:19:30 +0000628 os.mkdir(self.cgi_dir)
Ned Deily915a30f2014-07-12 22:06:26 -0700629 os.mkdir(self.cgi_child_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900630 os.mkdir(self.sub_dir_1)
631 os.mkdir(self.sub_dir_2)
632 os.mkdir(self.cgi_dir_in_sub_dir)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400633 self.nocgi_path = None
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000634 self.file1_path = None
635 self.file2_path = None
Ned Deily915a30f2014-07-12 22:06:26 -0700636 self.file3_path = None
Martin Pantera02e18a2015-10-03 05:38:07 +0000637 self.file4_path = None
Siwon Kang91daa9d2019-11-22 18:13:05 +0900638 self.file5_path = None
Georg Brandlb533e262008-05-25 18:19:30 +0000639
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000640 # The shebang line should be pure ASCII: use symlink if possible.
641 # See issue #7668.
Steve Dower9048c492019-06-29 10:34:11 -0700642 self._pythonexe_symlink = None
Hai Shi79bb2c92020-08-06 19:51:29 +0800643 if os_helper.can_symlink():
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000644 self.pythonexe = os.path.join(self.parent_dir, 'python')
Steve Dower9048c492019-06-29 10:34:11 -0700645 self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000646 else:
647 self.pythonexe = sys.executable
648
Victor Stinner3218c312010-10-17 20:13:36 +0000649 try:
650 # The python executable path is written as the first line of the
651 # CGI Python script. The encoding cookie cannot be used, and so the
652 # path should be encodable to the default script encoding (utf-8)
653 self.pythonexe.encode('utf-8')
654 except UnicodeEncodeError:
655 self.tearDown()
Serhiy Storchaka0b4591e2013-02-04 15:45:00 +0200656 self.skipTest("Python executable path is not encodable to utf-8")
Victor Stinner3218c312010-10-17 20:13:36 +0000657
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400658 self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
Inada Naokifa51c0c2021-04-29 11:34:56 +0900659 with open(self.nocgi_path, 'w', encoding='utf-8') as fp:
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400660 fp.write(cgi_file1 % self.pythonexe)
661 os.chmod(self.nocgi_path, 0o777)
662
Georg Brandlb533e262008-05-25 18:19:30 +0000663 self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000664 with open(self.file1_path, 'w', encoding='utf-8') as file1:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000665 file1.write(cgi_file1 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000666 os.chmod(self.file1_path, 0o777)
667
668 self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000669 with open(self.file2_path, 'w', encoding='utf-8') as file2:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000670 file2.write(cgi_file2 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000671 os.chmod(self.file2_path, 0o777)
672
Ned Deily915a30f2014-07-12 22:06:26 -0700673 self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
674 with open(self.file3_path, 'w', encoding='utf-8') as file3:
675 file3.write(cgi_file1 % self.pythonexe)
676 os.chmod(self.file3_path, 0o777)
677
Martin Pantera02e18a2015-10-03 05:38:07 +0000678 self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
679 with open(self.file4_path, 'w', encoding='utf-8') as file4:
680 file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
681 os.chmod(self.file4_path, 0o777)
682
Siwon Kang91daa9d2019-11-22 18:13:05 +0900683 self.file5_path = os.path.join(self.cgi_dir_in_sub_dir, 'file5.py')
684 with open(self.file5_path, 'w', encoding='utf-8') as file5:
685 file5.write(cgi_file1 % self.pythonexe)
686 os.chmod(self.file5_path, 0o777)
687
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800688 self.file6_path = os.path.join(self.cgi_dir, 'file6.py')
689 with open(self.file6_path, 'w', encoding='utf-8') as file6:
690 file6.write(cgi_file6 % self.pythonexe)
691 os.chmod(self.file6_path, 0o777)
692
Georg Brandlb533e262008-05-25 18:19:30 +0000693 os.chdir(self.parent_dir)
694
695 def tearDown(self):
696 try:
697 os.chdir(self.cwd)
Steve Dower9048c492019-06-29 10:34:11 -0700698 if self._pythonexe_symlink:
699 self._pythonexe_symlink.__exit__(None, None, None)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400700 if self.nocgi_path:
701 os.remove(self.nocgi_path)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000702 if self.file1_path:
703 os.remove(self.file1_path)
704 if self.file2_path:
705 os.remove(self.file2_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700706 if self.file3_path:
707 os.remove(self.file3_path)
Martin Pantera02e18a2015-10-03 05:38:07 +0000708 if self.file4_path:
709 os.remove(self.file4_path)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900710 if self.file5_path:
711 os.remove(self.file5_path)
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800712 if self.file6_path:
713 os.remove(self.file6_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700714 os.rmdir(self.cgi_child_dir)
Georg Brandlb533e262008-05-25 18:19:30 +0000715 os.rmdir(self.cgi_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900716 os.rmdir(self.cgi_dir_in_sub_dir)
717 os.rmdir(self.sub_dir_2)
718 os.rmdir(self.sub_dir_1)
Georg Brandlb533e262008-05-25 18:19:30 +0000719 os.rmdir(self.parent_dir)
720 finally:
721 BaseTestCase.tearDown(self)
722
Senthil Kumarand70846b2012-04-12 02:34:32 +0800723 def test_url_collapse_path(self):
724 # verify tail is the last portion and head is the rest on proper urls
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000725 test_vectors = {
Senthil Kumarand70846b2012-04-12 02:34:32 +0800726 '': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000727 '..': IndexError,
728 '/.//..': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800729 '/': '//',
730 '//': '//',
731 '/\\': '//\\',
732 '/.//': '//',
733 'cgi-bin/file1.py': '/cgi-bin/file1.py',
734 '/cgi-bin/file1.py': '/cgi-bin/file1.py',
735 'a': '//a',
736 '/a': '//a',
737 '//a': '//a',
738 './a': '//a',
739 './C:/': '/C:/',
740 '/a/b': '/a/b',
741 '/a/b/': '/a/b/',
742 '/a/b/.': '/a/b/',
743 '/a/b/c/..': '/a/b/',
744 '/a/b/c/../d': '/a/b/d',
745 '/a/b/c/../d/e/../f': '/a/b/d/f',
746 '/a/b/c/../d/e/../../f': '/a/b/f',
747 '/a/b/c/../d/e/.././././..//f': '/a/b/f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000748 '../a/b/c/../d/e/.././././..//f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800749 '/a/b/c/../d/e/../../../f': '/a/f',
750 '/a/b/c/../d/e/../../../../f': '//f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000751 '/a/b/c/../d/e/../../../../../f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800752 '/a/b/c/../d/e/../../../../f/..': '//',
753 '/a/b/c/../d/e/../../../../f/../.': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000754 }
755 for path, expected in test_vectors.items():
756 if isinstance(expected, type) and issubclass(expected, Exception):
757 self.assertRaises(expected,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800758 server._url_collapse_path, path)
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000759 else:
Senthil Kumarand70846b2012-04-12 02:34:32 +0800760 actual = server._url_collapse_path(path)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000761 self.assertEqual(expected, actual,
762 msg='path = %r\nGot: %r\nWanted: %r' %
763 (path, actual, expected))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000764
Georg Brandlb533e262008-05-25 18:19:30 +0000765 def test_headers_and_content(self):
766 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200767 self.assertEqual(
768 (res.read(), res.getheader('Content-type'), res.status),
769 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK))
Georg Brandlb533e262008-05-25 18:19:30 +0000770
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400771 def test_issue19435(self):
772 res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200773 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400774
Georg Brandlb533e262008-05-25 18:19:30 +0000775 def test_post(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000776 params = urllib.parse.urlencode(
777 {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
Georg Brandlb533e262008-05-25 18:19:30 +0000778 headers = {'Content-type' : 'application/x-www-form-urlencoded'}
779 res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
780
Antoine Pitroue768c392012-08-05 14:52:45 +0200781 self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
Georg Brandlb533e262008-05-25 18:19:30 +0000782
783 def test_invaliduri(self):
784 res = self.request('/cgi-bin/invalid')
785 res.read()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200786 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000787
788 def test_authorization(self):
789 headers = {b'Authorization' : b'Basic ' +
790 base64.b64encode(b'username:pass')}
791 res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200792 self.assertEqual(
793 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
794 (res.read(), res.getheader('Content-type'), res.status))
Georg Brandlb533e262008-05-25 18:19:30 +0000795
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000796 def test_no_leading_slash(self):
797 # http://bugs.python.org/issue2254
798 res = self.request('cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200799 self.assertEqual(
800 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
801 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000802
Senthil Kumaran42713722010-10-03 17:55:45 +0000803 def test_os_environ_is_not_altered(self):
804 signature = "Test CGI Server"
805 os.environ['SERVER_SOFTWARE'] = signature
806 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200807 self.assertEqual(
808 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
809 (res.read(), res.getheader('Content-type'), res.status))
Senthil Kumaran42713722010-10-03 17:55:45 +0000810 self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
811
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700812 def test_urlquote_decoding_in_cgi_check(self):
813 res = self.request('/cgi-bin%2ffile1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200814 self.assertEqual(
815 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
816 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700817
Ned Deily915a30f2014-07-12 22:06:26 -0700818 def test_nested_cgi_path_issue21323(self):
819 res = self.request('/cgi-bin/child-dir/file3.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200820 self.assertEqual(
821 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
822 (res.read(), res.getheader('Content-type'), res.status))
Ned Deily915a30f2014-07-12 22:06:26 -0700823
Martin Pantera02e18a2015-10-03 05:38:07 +0000824 def test_query_with_multiple_question_mark(self):
825 res = self.request('/cgi-bin/file4.py?a=b?c=d')
826 self.assertEqual(
Martin Pantereb1fee92015-10-03 06:07:22 +0000827 (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
Martin Pantera02e18a2015-10-03 05:38:07 +0000828 (res.read(), res.getheader('Content-type'), res.status))
829
Martin Pantercb29e8c2015-10-03 05:55:46 +0000830 def test_query_with_continuous_slashes(self):
831 res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
832 self.assertEqual(
833 (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
Martin Pantereb1fee92015-10-03 06:07:22 +0000834 'text/html', HTTPStatus.OK),
Martin Pantercb29e8c2015-10-03 05:55:46 +0000835 (res.read(), res.getheader('Content-type'), res.status))
836
Siwon Kang91daa9d2019-11-22 18:13:05 +0900837 def test_cgi_path_in_sub_directories(self):
Pablo Galindo24f5cac2019-12-04 09:29:10 +0000838 try:
839 CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
840 res = self.request('/sub/dir/cgi-bin/file5.py')
841 self.assertEqual(
842 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
843 (res.read(), res.getheader('Content-type'), res.status))
844 finally:
845 CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
846
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800847 def test_accept(self):
848 browser_accept = \
849 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
850 tests = (
851 ((('Accept', browser_accept),), browser_accept),
852 ((), ''),
853 # Hack case to get two values for the one header
854 ((('Accept', 'text/html'), ('ACCEPT', 'text/plain')),
855 'text/html,text/plain'),
856 )
857 for headers, expected in tests:
858 headers = OrderedDict(headers)
859 with self.subTest(headers):
860 res = self.request('/cgi-bin/file6.py', 'GET', headers=headers)
861 self.assertEqual(http.HTTPStatus.OK, res.status)
Miss Islington (bot)356bdff2021-07-15 13:09:26 -0700862 expected = f"HTTP_ACCEPT={expected}".encode('ascii')
863 self.assertIn(expected, res.read())
Siwon Kang91daa9d2019-11-22 18:13:05 +0900864
Georg Brandlb533e262008-05-25 18:19:30 +0000865
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000866class SocketlessRequestHandler(SimpleHTTPRequestHandler):
Géry Ogam781266e2019-09-11 15:03:46 +0200867 def __init__(self, directory=None):
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200868 request = mock.Mock()
869 request.makefile.return_value = BytesIO()
Géry Ogam781266e2019-09-11 15:03:46 +0200870 super().__init__(request, None, None, directory=directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200871
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000872 self.get_called = False
873 self.protocol_version = "HTTP/1.1"
874
875 def do_GET(self):
876 self.get_called = True
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200877 self.send_response(HTTPStatus.OK)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000878 self.send_header('Content-Type', 'text/html')
879 self.end_headers()
880 self.wfile.write(b'<html><body>Data</body></html>\r\n')
881
882 def log_message(self, format, *args):
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000883 pass
884
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000885class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
886 def handle_expect_100(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200887 self.send_error(HTTPStatus.EXPECTATION_FAILED)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000888 return False
889
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800890
891class AuditableBytesIO:
892
893 def __init__(self):
894 self.datas = []
895
896 def write(self, data):
897 self.datas.append(data)
898
899 def getData(self):
900 return b''.join(self.datas)
901
902 @property
903 def numWrites(self):
904 return len(self.datas)
905
906
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000907class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
Ezio Melotti3b3499b2011-03-16 11:35:38 +0200908 """Test the functionality of the BaseHTTPServer.
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000909
910 Test the support for the Expect 100-continue header.
911 """
912
913 HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
914
915 def setUp (self):
916 self.handler = SocketlessRequestHandler()
917
918 def send_typical_request(self, message):
919 input = BytesIO(message)
920 output = BytesIO()
921 self.handler.rfile = input
922 self.handler.wfile = output
923 self.handler.handle_one_request()
924 output.seek(0)
925 return output.readlines()
926
927 def verify_get_called(self):
928 self.assertTrue(self.handler.get_called)
929
930 def verify_expected_headers(self, headers):
931 for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
932 self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
933
934 def verify_http_server_response(self, response):
935 match = self.HTTPResponseMatch.search(response)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200936 self.assertIsNotNone(match)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000937
938 def test_http_1_1(self):
939 result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
940 self.verify_http_server_response(result[0])
941 self.verify_expected_headers(result[1:-1])
942 self.verify_get_called()
943 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500944 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
945 self.assertEqual(self.handler.command, 'GET')
946 self.assertEqual(self.handler.path, '/')
947 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
948 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000949
950 def test_http_1_0(self):
951 result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
952 self.verify_http_server_response(result[0])
953 self.verify_expected_headers(result[1:-1])
954 self.verify_get_called()
955 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500956 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
957 self.assertEqual(self.handler.command, 'GET')
958 self.assertEqual(self.handler.path, '/')
959 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
960 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000961
962 def test_http_0_9(self):
963 result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
964 self.assertEqual(len(result), 1)
965 self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
966 self.verify_get_called()
967
Martin Pantere82338d2016-11-19 01:06:37 +0000968 def test_extra_space(self):
969 result = self.send_typical_request(
970 b'GET /spaced out HTTP/1.1\r\n'
971 b'Host: dummy\r\n'
972 b'\r\n'
973 )
974 self.assertTrue(result[0].startswith(b'HTTP/1.1 400 '))
975 self.verify_expected_headers(result[1:result.index(b'\r\n')])
976 self.assertFalse(self.handler.get_called)
977
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000978 def test_with_continue_1_0(self):
979 result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
980 self.verify_http_server_response(result[0])
981 self.verify_expected_headers(result[1:-1])
982 self.verify_get_called()
983 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500984 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
985 self.assertEqual(self.handler.command, 'GET')
986 self.assertEqual(self.handler.path, '/')
987 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
988 headers = (("Expect", "100-continue"),)
989 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000990
991 def test_with_continue_1_1(self):
992 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
993 self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
Benjamin Peterson04424232014-01-18 21:50:18 -0500994 self.assertEqual(result[1], b'\r\n')
995 self.assertEqual(result[2], b'HTTP/1.1 200 OK\r\n')
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000996 self.verify_expected_headers(result[2:-1])
997 self.verify_get_called()
998 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500999 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1000 self.assertEqual(self.handler.command, 'GET')
1001 self.assertEqual(self.handler.path, '/')
1002 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
1003 headers = (("Expect", "100-continue"),)
1004 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001005
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001006 def test_header_buffering_of_send_error(self):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001007
1008 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001009 output = AuditableBytesIO()
1010 handler = SocketlessRequestHandler()
1011 handler.rfile = input
1012 handler.wfile = output
1013 handler.request_version = 'HTTP/1.1'
1014 handler.requestline = ''
1015 handler.command = None
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001016
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001017 handler.send_error(418)
1018 self.assertEqual(output.numWrites, 2)
1019
1020 def test_header_buffering_of_send_response_only(self):
1021
1022 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1023 output = AuditableBytesIO()
1024 handler = SocketlessRequestHandler()
1025 handler.rfile = input
1026 handler.wfile = output
1027 handler.request_version = 'HTTP/1.1'
1028
1029 handler.send_response_only(418)
1030 self.assertEqual(output.numWrites, 0)
1031 handler.end_headers()
1032 self.assertEqual(output.numWrites, 1)
1033
1034 def test_header_buffering_of_send_header(self):
1035
1036 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1037 output = AuditableBytesIO()
1038 handler = SocketlessRequestHandler()
1039 handler.rfile = input
1040 handler.wfile = output
1041 handler.request_version = 'HTTP/1.1'
1042
1043 handler.send_header('Foo', 'foo')
1044 handler.send_header('bar', 'bar')
1045 self.assertEqual(output.numWrites, 0)
1046 handler.end_headers()
1047 self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
1048 self.assertEqual(output.numWrites, 1)
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001049
1050 def test_header_unbuffered_when_continue(self):
1051
1052 def _readAndReseek(f):
1053 pos = f.tell()
1054 f.seek(0)
1055 data = f.read()
1056 f.seek(pos)
1057 return data
1058
1059 input = BytesIO(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1060 output = BytesIO()
1061 self.handler.rfile = input
1062 self.handler.wfile = output
1063 self.handler.request_version = 'HTTP/1.1'
1064
1065 self.handler.handle_one_request()
1066 self.assertNotEqual(_readAndReseek(output), b'')
1067 result = _readAndReseek(output).split(b'\r\n')
1068 self.assertEqual(result[0], b'HTTP/1.1 100 Continue')
Benjamin Peterson04424232014-01-18 21:50:18 -05001069 self.assertEqual(result[1], b'')
1070 self.assertEqual(result[2], b'HTTP/1.1 200 OK')
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001071
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001072 def test_with_continue_rejected(self):
1073 usual_handler = self.handler # Save to avoid breaking any subsequent tests.
1074 self.handler = RejectingSocketlessRequestHandler()
1075 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1076 self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
1077 self.verify_expected_headers(result[1:-1])
1078 # The expect handler should short circuit the usual get method by
1079 # returning false here, so get_called should be false
1080 self.assertFalse(self.handler.get_called)
1081 self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
1082 self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
1083
Antoine Pitrouc4924372010-12-16 16:48:36 +00001084 def test_request_length(self):
1085 # Issue #10714: huge request lines are discarded, to avoid Denial
1086 # of Service attacks.
1087 result = self.send_typical_request(b'GET ' + b'x' * 65537)
1088 self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
1089 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001090 self.assertIsInstance(self.handler.requestline, str)
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001091
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001092 def test_header_length(self):
1093 # Issue #6791: same for headers
1094 result = self.send_typical_request(
1095 b'GET / HTTP/1.1\r\nX-Foo: bar' + b'r' * 65537 + b'\r\n\r\n')
Martin Panter50badad2016-04-03 01:28:53 +00001096 self.assertEqual(result[0], b'HTTP/1.1 431 Line too long\r\n')
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001097 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001098 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1099
Martin Panteracc03192016-04-03 00:45:46 +00001100 def test_too_many_headers(self):
1101 result = self.send_typical_request(
1102 b'GET / HTTP/1.1\r\n' + b'X-Foo: bar\r\n' * 101 + b'\r\n')
1103 self.assertEqual(result[0], b'HTTP/1.1 431 Too many headers\r\n')
1104 self.assertFalse(self.handler.get_called)
1105 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1106
Martin Panterda3bb382016-04-11 00:40:08 +00001107 def test_html_escape_on_error(self):
1108 result = self.send_typical_request(
1109 b'<script>alert("hello")</script> / HTTP/1.1')
1110 result = b''.join(result)
1111 text = '<script>alert("hello")</script>'
1112 self.assertIn(html.escape(text, quote=False).encode('ascii'), result)
1113
Benjamin Peterson70e28472015-02-17 21:11:10 -05001114 def test_close_connection(self):
1115 # handle_one_request() should be repeatedly called until
1116 # it sets close_connection
1117 def handle_one_request():
1118 self.handler.close_connection = next(close_values)
1119 self.handler.handle_one_request = handle_one_request
1120
1121 close_values = iter((True,))
1122 self.handler.handle()
1123 self.assertRaises(StopIteration, next, close_values)
1124
1125 close_values = iter((False, False, True))
1126 self.handler.handle()
1127 self.assertRaises(StopIteration, next, close_values)
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001128
Berker Peksag04bc5b92016-03-14 06:06:03 +02001129 def test_date_time_string(self):
1130 now = time.time()
1131 # this is the old code that formats the timestamp
1132 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
1133 expected = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
1134 self.handler.weekdayname[wd],
1135 day,
1136 self.handler.monthname[month],
1137 year, hh, mm, ss
1138 )
1139 self.assertEqual(self.handler.date_time_string(timestamp=now), expected)
1140
1141
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001142class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
1143 """ Test url parsing """
1144 def setUp(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001145 self.translated_1 = os.path.join(os.getcwd(), 'filename')
1146 self.translated_2 = os.path.join('foo', 'filename')
1147 self.translated_3 = os.path.join('bar', 'filename')
1148 self.handler_1 = SocketlessRequestHandler()
1149 self.handler_2 = SocketlessRequestHandler(directory='foo')
1150 self.handler_3 = SocketlessRequestHandler(directory=pathlib.PurePath('bar'))
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001151
1152 def test_query_arguments(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001153 path = self.handler_1.translate_path('/filename')
1154 self.assertEqual(path, self.translated_1)
1155 path = self.handler_2.translate_path('/filename')
1156 self.assertEqual(path, self.translated_2)
1157 path = self.handler_3.translate_path('/filename')
1158 self.assertEqual(path, self.translated_3)
1159
1160 path = self.handler_1.translate_path('/filename?foo=bar')
1161 self.assertEqual(path, self.translated_1)
1162 path = self.handler_2.translate_path('/filename?foo=bar')
1163 self.assertEqual(path, self.translated_2)
1164 path = self.handler_3.translate_path('/filename?foo=bar')
1165 self.assertEqual(path, self.translated_3)
1166
1167 path = self.handler_1.translate_path('/filename?a=b&spam=eggs#zot')
1168 self.assertEqual(path, self.translated_1)
1169 path = self.handler_2.translate_path('/filename?a=b&spam=eggs#zot')
1170 self.assertEqual(path, self.translated_2)
1171 path = self.handler_3.translate_path('/filename?a=b&spam=eggs#zot')
1172 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001173
1174 def test_start_with_double_slash(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001175 path = self.handler_1.translate_path('//filename')
1176 self.assertEqual(path, self.translated_1)
1177 path = self.handler_2.translate_path('//filename')
1178 self.assertEqual(path, self.translated_2)
1179 path = self.handler_3.translate_path('//filename')
1180 self.assertEqual(path, self.translated_3)
1181
1182 path = self.handler_1.translate_path('//filename?foo=bar')
1183 self.assertEqual(path, self.translated_1)
1184 path = self.handler_2.translate_path('//filename?foo=bar')
1185 self.assertEqual(path, self.translated_2)
1186 path = self.handler_3.translate_path('//filename?foo=bar')
1187 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001188
Martin Panterd274b3f2016-04-18 03:45:18 +00001189 def test_windows_colon(self):
1190 with support.swap_attr(server.os, 'path', ntpath):
Géry Ogam781266e2019-09-11 15:03:46 +02001191 path = self.handler_1.translate_path('c:c:c:foo/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:c:c:foo/filename')
1195 path = path.replace(ntpath.sep, os.sep)
1196 self.assertEqual(path, self.translated_2)
1197 path = self.handler_3.translate_path('c:c:c:foo/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:../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:../filename')
1205 path = path.replace(ntpath.sep, os.sep)
1206 self.assertEqual(path, self.translated_2)
1207 path = self.handler_3.translate_path('\\c:../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/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/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/filename')
1218 path = path.replace(ntpath.sep, os.sep)
1219 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001220
Géry Ogam781266e2019-09-11 15:03:46 +02001221 path = self.handler_1.translate_path('c:c:foo\\c:c:bar/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001222 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001223 self.assertEqual(path, self.translated_1)
1224 path = self.handler_2.translate_path('c:c:foo\\c:c:bar/filename')
1225 path = path.replace(ntpath.sep, os.sep)
1226 self.assertEqual(path, self.translated_2)
1227 path = self.handler_3.translate_path('c:c:foo\\c:c:bar/filename')
1228 path = path.replace(ntpath.sep, os.sep)
1229 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001230
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001231
Berker Peksag366c5702015-02-13 20:48:15 +02001232class MiscTestCase(unittest.TestCase):
1233 def test_all(self):
1234 expected = []
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001235 denylist = {'executable', 'nobody_uid', 'test'}
Berker Peksag366c5702015-02-13 20:48:15 +02001236 for name in dir(server):
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001237 if name.startswith('_') or name in denylist:
Berker Peksag366c5702015-02-13 20:48:15 +02001238 continue
1239 module_object = getattr(server, name)
1240 if getattr(module_object, '__module__', None) == 'http.server':
1241 expected.append(name)
1242 self.assertCountEqual(server.__all__, expected)
1243
1244
Lisa Roach433433f2018-11-26 10:43:38 -08001245class ScriptTestCase(unittest.TestCase):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001246
1247 def mock_server_class(self):
1248 return mock.MagicMock(
1249 return_value=mock.MagicMock(
1250 __enter__=mock.MagicMock(
1251 return_value=mock.MagicMock(
1252 socket=mock.MagicMock(
1253 getsockname=lambda: ('', 0),
1254 ),
1255 ),
1256 ),
1257 ),
1258 )
1259
1260 @mock.patch('builtins.print')
1261 def test_server_test_unspec(self, _):
1262 mock_server = self.mock_server_class()
1263 server.test(ServerClass=mock_server, bind=None)
1264 self.assertIn(
1265 mock_server.address_family,
1266 (socket.AF_INET6, socket.AF_INET),
1267 )
1268
1269 @mock.patch('builtins.print')
1270 def test_server_test_localhost(self, _):
1271 mock_server = self.mock_server_class()
1272 server.test(ServerClass=mock_server, bind="localhost")
1273 self.assertIn(
1274 mock_server.address_family,
1275 (socket.AF_INET6, socket.AF_INET),
1276 )
1277
1278 ipv6_addrs = (
1279 "::",
1280 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
1281 "::1",
1282 )
1283
1284 ipv4_addrs = (
1285 "0.0.0.0",
1286 "8.8.8.8",
1287 "127.0.0.1",
1288 )
1289
Lisa Roach433433f2018-11-26 10:43:38 -08001290 @mock.patch('builtins.print')
1291 def test_server_test_ipv6(self, _):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001292 for bind in self.ipv6_addrs:
1293 mock_server = self.mock_server_class()
1294 server.test(ServerClass=mock_server, bind=bind)
1295 self.assertEqual(mock_server.address_family, socket.AF_INET6)
Lisa Roach433433f2018-11-26 10:43:38 -08001296
Jason R. Coombsf2890842019-02-07 08:22:45 -05001297 @mock.patch('builtins.print')
1298 def test_server_test_ipv4(self, _):
1299 for bind in self.ipv4_addrs:
1300 mock_server = self.mock_server_class()
1301 server.test(ServerClass=mock_server, bind=bind)
1302 self.assertEqual(mock_server.address_family, socket.AF_INET)
Lisa Roach433433f2018-11-26 10:43:38 -08001303
1304
Serhiy Storchakabedce352021-09-19 22:36:03 +03001305def setUpModule():
1306 unittest.addModuleCleanup(os.chdir, os.getcwd())
1307
Georg Brandlb533e262008-05-25 18:19:30 +00001308
1309if __name__ == '__main__':
Serhiy Storchakabedce352021-09-19 22:36:03 +03001310 unittest.main()