blob: aeea020d2416d2c4ab19ae878ca5c239638507a5 [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
596print("Content-type: text/plain")
597print()
598print(repr(os.environ))
599"""
600
Charles-François Natalif7ed9fc2011-11-02 19:35:14 +0100601
602@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
603 "This test can't be run reliably as root (issue #13308).")
Georg Brandlb533e262008-05-25 18:19:30 +0000604class CGIHTTPServerTestCase(BaseTestCase):
605 class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
606 pass
607
Antoine Pitroue768c392012-08-05 14:52:45 +0200608 linesep = os.linesep.encode('ascii')
609
Georg Brandlb533e262008-05-25 18:19:30 +0000610 def setUp(self):
611 BaseTestCase.setUp(self)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000612 self.cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +0000613 self.parent_dir = tempfile.mkdtemp()
614 self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
Ned Deily915a30f2014-07-12 22:06:26 -0700615 self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
Siwon Kang91daa9d2019-11-22 18:13:05 +0900616 self.sub_dir_1 = os.path.join(self.parent_dir, 'sub')
617 self.sub_dir_2 = os.path.join(self.sub_dir_1, 'dir')
618 self.cgi_dir_in_sub_dir = os.path.join(self.sub_dir_2, 'cgi-bin')
Georg Brandlb533e262008-05-25 18:19:30 +0000619 os.mkdir(self.cgi_dir)
Ned Deily915a30f2014-07-12 22:06:26 -0700620 os.mkdir(self.cgi_child_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900621 os.mkdir(self.sub_dir_1)
622 os.mkdir(self.sub_dir_2)
623 os.mkdir(self.cgi_dir_in_sub_dir)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400624 self.nocgi_path = None
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000625 self.file1_path = None
626 self.file2_path = None
Ned Deily915a30f2014-07-12 22:06:26 -0700627 self.file3_path = None
Martin Pantera02e18a2015-10-03 05:38:07 +0000628 self.file4_path = None
Siwon Kang91daa9d2019-11-22 18:13:05 +0900629 self.file5_path = None
Georg Brandlb533e262008-05-25 18:19:30 +0000630
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000631 # The shebang line should be pure ASCII: use symlink if possible.
632 # See issue #7668.
Steve Dower9048c492019-06-29 10:34:11 -0700633 self._pythonexe_symlink = None
Hai Shi79bb2c92020-08-06 19:51:29 +0800634 if os_helper.can_symlink():
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000635 self.pythonexe = os.path.join(self.parent_dir, 'python')
Steve Dower9048c492019-06-29 10:34:11 -0700636 self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000637 else:
638 self.pythonexe = sys.executable
639
Victor Stinner3218c312010-10-17 20:13:36 +0000640 try:
641 # The python executable path is written as the first line of the
642 # CGI Python script. The encoding cookie cannot be used, and so the
643 # path should be encodable to the default script encoding (utf-8)
644 self.pythonexe.encode('utf-8')
645 except UnicodeEncodeError:
646 self.tearDown()
Serhiy Storchaka0b4591e2013-02-04 15:45:00 +0200647 self.skipTest("Python executable path is not encodable to utf-8")
Victor Stinner3218c312010-10-17 20:13:36 +0000648
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400649 self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
Inada Naokifa51c0c2021-04-29 11:34:56 +0900650 with open(self.nocgi_path, 'w', encoding='utf-8') as fp:
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400651 fp.write(cgi_file1 % self.pythonexe)
652 os.chmod(self.nocgi_path, 0o777)
653
Georg Brandlb533e262008-05-25 18:19:30 +0000654 self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000655 with open(self.file1_path, 'w', encoding='utf-8') as file1:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000656 file1.write(cgi_file1 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000657 os.chmod(self.file1_path, 0o777)
658
659 self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000660 with open(self.file2_path, 'w', encoding='utf-8') as file2:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000661 file2.write(cgi_file2 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000662 os.chmod(self.file2_path, 0o777)
663
Ned Deily915a30f2014-07-12 22:06:26 -0700664 self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
665 with open(self.file3_path, 'w', encoding='utf-8') as file3:
666 file3.write(cgi_file1 % self.pythonexe)
667 os.chmod(self.file3_path, 0o777)
668
Martin Pantera02e18a2015-10-03 05:38:07 +0000669 self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
670 with open(self.file4_path, 'w', encoding='utf-8') as file4:
671 file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
672 os.chmod(self.file4_path, 0o777)
673
Siwon Kang91daa9d2019-11-22 18:13:05 +0900674 self.file5_path = os.path.join(self.cgi_dir_in_sub_dir, 'file5.py')
675 with open(self.file5_path, 'w', encoding='utf-8') as file5:
676 file5.write(cgi_file1 % self.pythonexe)
677 os.chmod(self.file5_path, 0o777)
678
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800679 self.file6_path = os.path.join(self.cgi_dir, 'file6.py')
680 with open(self.file6_path, 'w', encoding='utf-8') as file6:
681 file6.write(cgi_file6 % self.pythonexe)
682 os.chmod(self.file6_path, 0o777)
683
Georg Brandlb533e262008-05-25 18:19:30 +0000684 os.chdir(self.parent_dir)
685
686 def tearDown(self):
687 try:
688 os.chdir(self.cwd)
Steve Dower9048c492019-06-29 10:34:11 -0700689 if self._pythonexe_symlink:
690 self._pythonexe_symlink.__exit__(None, None, None)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400691 if self.nocgi_path:
692 os.remove(self.nocgi_path)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000693 if self.file1_path:
694 os.remove(self.file1_path)
695 if self.file2_path:
696 os.remove(self.file2_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700697 if self.file3_path:
698 os.remove(self.file3_path)
Martin Pantera02e18a2015-10-03 05:38:07 +0000699 if self.file4_path:
700 os.remove(self.file4_path)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900701 if self.file5_path:
702 os.remove(self.file5_path)
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800703 if self.file6_path:
704 os.remove(self.file6_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700705 os.rmdir(self.cgi_child_dir)
Georg Brandlb533e262008-05-25 18:19:30 +0000706 os.rmdir(self.cgi_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900707 os.rmdir(self.cgi_dir_in_sub_dir)
708 os.rmdir(self.sub_dir_2)
709 os.rmdir(self.sub_dir_1)
Georg Brandlb533e262008-05-25 18:19:30 +0000710 os.rmdir(self.parent_dir)
711 finally:
712 BaseTestCase.tearDown(self)
713
Senthil Kumarand70846b2012-04-12 02:34:32 +0800714 def test_url_collapse_path(self):
715 # verify tail is the last portion and head is the rest on proper urls
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000716 test_vectors = {
Senthil Kumarand70846b2012-04-12 02:34:32 +0800717 '': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000718 '..': IndexError,
719 '/.//..': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800720 '/': '//',
721 '//': '//',
722 '/\\': '//\\',
723 '/.//': '//',
724 'cgi-bin/file1.py': '/cgi-bin/file1.py',
725 '/cgi-bin/file1.py': '/cgi-bin/file1.py',
726 'a': '//a',
727 '/a': '//a',
728 '//a': '//a',
729 './a': '//a',
730 './C:/': '/C:/',
731 '/a/b': '/a/b',
732 '/a/b/': '/a/b/',
733 '/a/b/.': '/a/b/',
734 '/a/b/c/..': '/a/b/',
735 '/a/b/c/../d': '/a/b/d',
736 '/a/b/c/../d/e/../f': '/a/b/d/f',
737 '/a/b/c/../d/e/../../f': '/a/b/f',
738 '/a/b/c/../d/e/.././././..//f': '/a/b/f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000739 '../a/b/c/../d/e/.././././..//f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800740 '/a/b/c/../d/e/../../../f': '/a/f',
741 '/a/b/c/../d/e/../../../../f': '//f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000742 '/a/b/c/../d/e/../../../../../f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800743 '/a/b/c/../d/e/../../../../f/..': '//',
744 '/a/b/c/../d/e/../../../../f/../.': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000745 }
746 for path, expected in test_vectors.items():
747 if isinstance(expected, type) and issubclass(expected, Exception):
748 self.assertRaises(expected,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800749 server._url_collapse_path, path)
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000750 else:
Senthil Kumarand70846b2012-04-12 02:34:32 +0800751 actual = server._url_collapse_path(path)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000752 self.assertEqual(expected, actual,
753 msg='path = %r\nGot: %r\nWanted: %r' %
754 (path, actual, expected))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000755
Georg Brandlb533e262008-05-25 18:19:30 +0000756 def test_headers_and_content(self):
757 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200758 self.assertEqual(
759 (res.read(), res.getheader('Content-type'), res.status),
760 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK))
Georg Brandlb533e262008-05-25 18:19:30 +0000761
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400762 def test_issue19435(self):
763 res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200764 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400765
Georg Brandlb533e262008-05-25 18:19:30 +0000766 def test_post(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000767 params = urllib.parse.urlencode(
768 {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
Georg Brandlb533e262008-05-25 18:19:30 +0000769 headers = {'Content-type' : 'application/x-www-form-urlencoded'}
770 res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
771
Antoine Pitroue768c392012-08-05 14:52:45 +0200772 self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
Georg Brandlb533e262008-05-25 18:19:30 +0000773
774 def test_invaliduri(self):
775 res = self.request('/cgi-bin/invalid')
776 res.read()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200777 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000778
779 def test_authorization(self):
780 headers = {b'Authorization' : b'Basic ' +
781 base64.b64encode(b'username:pass')}
782 res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200783 self.assertEqual(
784 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
785 (res.read(), res.getheader('Content-type'), res.status))
Georg Brandlb533e262008-05-25 18:19:30 +0000786
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000787 def test_no_leading_slash(self):
788 # http://bugs.python.org/issue2254
789 res = self.request('cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200790 self.assertEqual(
791 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
792 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000793
Senthil Kumaran42713722010-10-03 17:55:45 +0000794 def test_os_environ_is_not_altered(self):
795 signature = "Test CGI Server"
796 os.environ['SERVER_SOFTWARE'] = signature
797 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200798 self.assertEqual(
799 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
800 (res.read(), res.getheader('Content-type'), res.status))
Senthil Kumaran42713722010-10-03 17:55:45 +0000801 self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
802
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700803 def test_urlquote_decoding_in_cgi_check(self):
804 res = self.request('/cgi-bin%2ffile1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200805 self.assertEqual(
806 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
807 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700808
Ned Deily915a30f2014-07-12 22:06:26 -0700809 def test_nested_cgi_path_issue21323(self):
810 res = self.request('/cgi-bin/child-dir/file3.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200811 self.assertEqual(
812 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
813 (res.read(), res.getheader('Content-type'), res.status))
Ned Deily915a30f2014-07-12 22:06:26 -0700814
Martin Pantera02e18a2015-10-03 05:38:07 +0000815 def test_query_with_multiple_question_mark(self):
816 res = self.request('/cgi-bin/file4.py?a=b?c=d')
817 self.assertEqual(
Martin Pantereb1fee92015-10-03 06:07:22 +0000818 (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
Martin Pantera02e18a2015-10-03 05:38:07 +0000819 (res.read(), res.getheader('Content-type'), res.status))
820
Martin Pantercb29e8c2015-10-03 05:55:46 +0000821 def test_query_with_continuous_slashes(self):
822 res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
823 self.assertEqual(
824 (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
Martin Pantereb1fee92015-10-03 06:07:22 +0000825 'text/html', HTTPStatus.OK),
Martin Pantercb29e8c2015-10-03 05:55:46 +0000826 (res.read(), res.getheader('Content-type'), res.status))
827
Siwon Kang91daa9d2019-11-22 18:13:05 +0900828 def test_cgi_path_in_sub_directories(self):
Pablo Galindo24f5cac2019-12-04 09:29:10 +0000829 try:
830 CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
831 res = self.request('/sub/dir/cgi-bin/file5.py')
832 self.assertEqual(
833 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
834 (res.read(), res.getheader('Content-type'), res.status))
835 finally:
836 CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
837
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -0800838 def test_accept(self):
839 browser_accept = \
840 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
841 tests = (
842 ((('Accept', browser_accept),), browser_accept),
843 ((), ''),
844 # Hack case to get two values for the one header
845 ((('Accept', 'text/html'), ('ACCEPT', 'text/plain')),
846 'text/html,text/plain'),
847 )
848 for headers, expected in tests:
849 headers = OrderedDict(headers)
850 with self.subTest(headers):
851 res = self.request('/cgi-bin/file6.py', 'GET', headers=headers)
852 self.assertEqual(http.HTTPStatus.OK, res.status)
853 expected = f"'HTTP_ACCEPT': {expected!r}"
854 self.assertIn(expected.encode('ascii'), res.read())
Siwon Kang91daa9d2019-11-22 18:13:05 +0900855
Georg Brandlb533e262008-05-25 18:19:30 +0000856
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000857class SocketlessRequestHandler(SimpleHTTPRequestHandler):
Géry Ogam781266e2019-09-11 15:03:46 +0200858 def __init__(self, directory=None):
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200859 request = mock.Mock()
860 request.makefile.return_value = BytesIO()
Géry Ogam781266e2019-09-11 15:03:46 +0200861 super().__init__(request, None, None, directory=directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200862
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000863 self.get_called = False
864 self.protocol_version = "HTTP/1.1"
865
866 def do_GET(self):
867 self.get_called = True
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200868 self.send_response(HTTPStatus.OK)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000869 self.send_header('Content-Type', 'text/html')
870 self.end_headers()
871 self.wfile.write(b'<html><body>Data</body></html>\r\n')
872
873 def log_message(self, format, *args):
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000874 pass
875
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000876class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
877 def handle_expect_100(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200878 self.send_error(HTTPStatus.EXPECTATION_FAILED)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000879 return False
880
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800881
882class AuditableBytesIO:
883
884 def __init__(self):
885 self.datas = []
886
887 def write(self, data):
888 self.datas.append(data)
889
890 def getData(self):
891 return b''.join(self.datas)
892
893 @property
894 def numWrites(self):
895 return len(self.datas)
896
897
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000898class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
Ezio Melotti3b3499b2011-03-16 11:35:38 +0200899 """Test the functionality of the BaseHTTPServer.
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000900
901 Test the support for the Expect 100-continue header.
902 """
903
904 HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
905
906 def setUp (self):
907 self.handler = SocketlessRequestHandler()
908
909 def send_typical_request(self, message):
910 input = BytesIO(message)
911 output = BytesIO()
912 self.handler.rfile = input
913 self.handler.wfile = output
914 self.handler.handle_one_request()
915 output.seek(0)
916 return output.readlines()
917
918 def verify_get_called(self):
919 self.assertTrue(self.handler.get_called)
920
921 def verify_expected_headers(self, headers):
922 for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
923 self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
924
925 def verify_http_server_response(self, response):
926 match = self.HTTPResponseMatch.search(response)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200927 self.assertIsNotNone(match)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000928
929 def test_http_1_1(self):
930 result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
931 self.verify_http_server_response(result[0])
932 self.verify_expected_headers(result[1:-1])
933 self.verify_get_called()
934 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500935 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
936 self.assertEqual(self.handler.command, 'GET')
937 self.assertEqual(self.handler.path, '/')
938 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
939 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000940
941 def test_http_1_0(self):
942 result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
943 self.verify_http_server_response(result[0])
944 self.verify_expected_headers(result[1:-1])
945 self.verify_get_called()
946 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500947 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
948 self.assertEqual(self.handler.command, 'GET')
949 self.assertEqual(self.handler.path, '/')
950 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
951 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000952
953 def test_http_0_9(self):
954 result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
955 self.assertEqual(len(result), 1)
956 self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
957 self.verify_get_called()
958
Martin Pantere82338d2016-11-19 01:06:37 +0000959 def test_extra_space(self):
960 result = self.send_typical_request(
961 b'GET /spaced out HTTP/1.1\r\n'
962 b'Host: dummy\r\n'
963 b'\r\n'
964 )
965 self.assertTrue(result[0].startswith(b'HTTP/1.1 400 '))
966 self.verify_expected_headers(result[1:result.index(b'\r\n')])
967 self.assertFalse(self.handler.get_called)
968
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000969 def test_with_continue_1_0(self):
970 result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
971 self.verify_http_server_response(result[0])
972 self.verify_expected_headers(result[1:-1])
973 self.verify_get_called()
974 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500975 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
976 self.assertEqual(self.handler.command, 'GET')
977 self.assertEqual(self.handler.path, '/')
978 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
979 headers = (("Expect", "100-continue"),)
980 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000981
982 def test_with_continue_1_1(self):
983 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
984 self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
Benjamin Peterson04424232014-01-18 21:50:18 -0500985 self.assertEqual(result[1], b'\r\n')
986 self.assertEqual(result[2], b'HTTP/1.1 200 OK\r\n')
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000987 self.verify_expected_headers(result[2:-1])
988 self.verify_get_called()
989 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500990 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
991 self.assertEqual(self.handler.command, 'GET')
992 self.assertEqual(self.handler.path, '/')
993 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
994 headers = (("Expect", "100-continue"),)
995 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000996
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800997 def test_header_buffering_of_send_error(self):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000998
999 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001000 output = AuditableBytesIO()
1001 handler = SocketlessRequestHandler()
1002 handler.rfile = input
1003 handler.wfile = output
1004 handler.request_version = 'HTTP/1.1'
1005 handler.requestline = ''
1006 handler.command = None
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001007
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001008 handler.send_error(418)
1009 self.assertEqual(output.numWrites, 2)
1010
1011 def test_header_buffering_of_send_response_only(self):
1012
1013 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1014 output = AuditableBytesIO()
1015 handler = SocketlessRequestHandler()
1016 handler.rfile = input
1017 handler.wfile = output
1018 handler.request_version = 'HTTP/1.1'
1019
1020 handler.send_response_only(418)
1021 self.assertEqual(output.numWrites, 0)
1022 handler.end_headers()
1023 self.assertEqual(output.numWrites, 1)
1024
1025 def test_header_buffering_of_send_header(self):
1026
1027 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
1028 output = AuditableBytesIO()
1029 handler = SocketlessRequestHandler()
1030 handler.rfile = input
1031 handler.wfile = output
1032 handler.request_version = 'HTTP/1.1'
1033
1034 handler.send_header('Foo', 'foo')
1035 handler.send_header('bar', 'bar')
1036 self.assertEqual(output.numWrites, 0)
1037 handler.end_headers()
1038 self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
1039 self.assertEqual(output.numWrites, 1)
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001040
1041 def test_header_unbuffered_when_continue(self):
1042
1043 def _readAndReseek(f):
1044 pos = f.tell()
1045 f.seek(0)
1046 data = f.read()
1047 f.seek(pos)
1048 return data
1049
1050 input = BytesIO(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1051 output = BytesIO()
1052 self.handler.rfile = input
1053 self.handler.wfile = output
1054 self.handler.request_version = 'HTTP/1.1'
1055
1056 self.handler.handle_one_request()
1057 self.assertNotEqual(_readAndReseek(output), b'')
1058 result = _readAndReseek(output).split(b'\r\n')
1059 self.assertEqual(result[0], b'HTTP/1.1 100 Continue')
Benjamin Peterson04424232014-01-18 21:50:18 -05001060 self.assertEqual(result[1], b'')
1061 self.assertEqual(result[2], b'HTTP/1.1 200 OK')
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001062
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001063 def test_with_continue_rejected(self):
1064 usual_handler = self.handler # Save to avoid breaking any subsequent tests.
1065 self.handler = RejectingSocketlessRequestHandler()
1066 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1067 self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
1068 self.verify_expected_headers(result[1:-1])
1069 # The expect handler should short circuit the usual get method by
1070 # returning false here, so get_called should be false
1071 self.assertFalse(self.handler.get_called)
1072 self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
1073 self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
1074
Antoine Pitrouc4924372010-12-16 16:48:36 +00001075 def test_request_length(self):
1076 # Issue #10714: huge request lines are discarded, to avoid Denial
1077 # of Service attacks.
1078 result = self.send_typical_request(b'GET ' + b'x' * 65537)
1079 self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
1080 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001081 self.assertIsInstance(self.handler.requestline, str)
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001082
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001083 def test_header_length(self):
1084 # Issue #6791: same for headers
1085 result = self.send_typical_request(
1086 b'GET / HTTP/1.1\r\nX-Foo: bar' + b'r' * 65537 + b'\r\n\r\n')
Martin Panter50badad2016-04-03 01:28:53 +00001087 self.assertEqual(result[0], b'HTTP/1.1 431 Line too long\r\n')
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001088 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001089 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1090
Martin Panteracc03192016-04-03 00:45:46 +00001091 def test_too_many_headers(self):
1092 result = self.send_typical_request(
1093 b'GET / HTTP/1.1\r\n' + b'X-Foo: bar\r\n' * 101 + b'\r\n')
1094 self.assertEqual(result[0], b'HTTP/1.1 431 Too many headers\r\n')
1095 self.assertFalse(self.handler.get_called)
1096 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1097
Martin Panterda3bb382016-04-11 00:40:08 +00001098 def test_html_escape_on_error(self):
1099 result = self.send_typical_request(
1100 b'<script>alert("hello")</script> / HTTP/1.1')
1101 result = b''.join(result)
1102 text = '<script>alert("hello")</script>'
1103 self.assertIn(html.escape(text, quote=False).encode('ascii'), result)
1104
Benjamin Peterson70e28472015-02-17 21:11:10 -05001105 def test_close_connection(self):
1106 # handle_one_request() should be repeatedly called until
1107 # it sets close_connection
1108 def handle_one_request():
1109 self.handler.close_connection = next(close_values)
1110 self.handler.handle_one_request = handle_one_request
1111
1112 close_values = iter((True,))
1113 self.handler.handle()
1114 self.assertRaises(StopIteration, next, close_values)
1115
1116 close_values = iter((False, False, True))
1117 self.handler.handle()
1118 self.assertRaises(StopIteration, next, close_values)
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001119
Berker Peksag04bc5b92016-03-14 06:06:03 +02001120 def test_date_time_string(self):
1121 now = time.time()
1122 # this is the old code that formats the timestamp
1123 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
1124 expected = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
1125 self.handler.weekdayname[wd],
1126 day,
1127 self.handler.monthname[month],
1128 year, hh, mm, ss
1129 )
1130 self.assertEqual(self.handler.date_time_string(timestamp=now), expected)
1131
1132
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001133class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
1134 """ Test url parsing """
1135 def setUp(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001136 self.translated_1 = os.path.join(os.getcwd(), 'filename')
1137 self.translated_2 = os.path.join('foo', 'filename')
1138 self.translated_3 = os.path.join('bar', 'filename')
1139 self.handler_1 = SocketlessRequestHandler()
1140 self.handler_2 = SocketlessRequestHandler(directory='foo')
1141 self.handler_3 = SocketlessRequestHandler(directory=pathlib.PurePath('bar'))
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001142
1143 def test_query_arguments(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001144 path = self.handler_1.translate_path('/filename')
1145 self.assertEqual(path, self.translated_1)
1146 path = self.handler_2.translate_path('/filename')
1147 self.assertEqual(path, self.translated_2)
1148 path = self.handler_3.translate_path('/filename')
1149 self.assertEqual(path, self.translated_3)
1150
1151 path = self.handler_1.translate_path('/filename?foo=bar')
1152 self.assertEqual(path, self.translated_1)
1153 path = self.handler_2.translate_path('/filename?foo=bar')
1154 self.assertEqual(path, self.translated_2)
1155 path = self.handler_3.translate_path('/filename?foo=bar')
1156 self.assertEqual(path, self.translated_3)
1157
1158 path = self.handler_1.translate_path('/filename?a=b&spam=eggs#zot')
1159 self.assertEqual(path, self.translated_1)
1160 path = self.handler_2.translate_path('/filename?a=b&spam=eggs#zot')
1161 self.assertEqual(path, self.translated_2)
1162 path = self.handler_3.translate_path('/filename?a=b&spam=eggs#zot')
1163 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001164
1165 def test_start_with_double_slash(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001166 path = self.handler_1.translate_path('//filename')
1167 self.assertEqual(path, self.translated_1)
1168 path = self.handler_2.translate_path('//filename')
1169 self.assertEqual(path, self.translated_2)
1170 path = self.handler_3.translate_path('//filename')
1171 self.assertEqual(path, self.translated_3)
1172
1173 path = self.handler_1.translate_path('//filename?foo=bar')
1174 self.assertEqual(path, self.translated_1)
1175 path = self.handler_2.translate_path('//filename?foo=bar')
1176 self.assertEqual(path, self.translated_2)
1177 path = self.handler_3.translate_path('//filename?foo=bar')
1178 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001179
Martin Panterd274b3f2016-04-18 03:45:18 +00001180 def test_windows_colon(self):
1181 with support.swap_attr(server.os, 'path', ntpath):
Géry Ogam781266e2019-09-11 15:03:46 +02001182 path = self.handler_1.translate_path('c:c:c:foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001183 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001184 self.assertEqual(path, self.translated_1)
1185 path = self.handler_2.translate_path('c:c:c:foo/filename')
1186 path = path.replace(ntpath.sep, os.sep)
1187 self.assertEqual(path, self.translated_2)
1188 path = self.handler_3.translate_path('c:c:c:foo/filename')
1189 path = path.replace(ntpath.sep, os.sep)
1190 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001191
Géry Ogam781266e2019-09-11 15:03:46 +02001192 path = self.handler_1.translate_path('\\c:../filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001193 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001194 self.assertEqual(path, self.translated_1)
1195 path = self.handler_2.translate_path('\\c:../filename')
1196 path = path.replace(ntpath.sep, os.sep)
1197 self.assertEqual(path, self.translated_2)
1198 path = self.handler_3.translate_path('\\c:../filename')
1199 path = path.replace(ntpath.sep, os.sep)
1200 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001201
Géry Ogam781266e2019-09-11 15:03:46 +02001202 path = self.handler_1.translate_path('c:\\c:..\\foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001203 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001204 self.assertEqual(path, self.translated_1)
1205 path = self.handler_2.translate_path('c:\\c:..\\foo/filename')
1206 path = path.replace(ntpath.sep, os.sep)
1207 self.assertEqual(path, self.translated_2)
1208 path = self.handler_3.translate_path('c:\\c:..\\foo/filename')
1209 path = path.replace(ntpath.sep, os.sep)
1210 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001211
Géry Ogam781266e2019-09-11 15:03:46 +02001212 path = self.handler_1.translate_path('c:c:foo\\c:c:bar/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001213 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001214 self.assertEqual(path, self.translated_1)
1215 path = self.handler_2.translate_path('c:c:foo\\c:c:bar/filename')
1216 path = path.replace(ntpath.sep, os.sep)
1217 self.assertEqual(path, self.translated_2)
1218 path = self.handler_3.translate_path('c:c:foo\\c:c:bar/filename')
1219 path = path.replace(ntpath.sep, os.sep)
1220 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001221
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001222
Berker Peksag366c5702015-02-13 20:48:15 +02001223class MiscTestCase(unittest.TestCase):
1224 def test_all(self):
1225 expected = []
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001226 denylist = {'executable', 'nobody_uid', 'test'}
Berker Peksag366c5702015-02-13 20:48:15 +02001227 for name in dir(server):
Victor Stinnerfabd7bb2020-08-11 15:26:59 +02001228 if name.startswith('_') or name in denylist:
Berker Peksag366c5702015-02-13 20:48:15 +02001229 continue
1230 module_object = getattr(server, name)
1231 if getattr(module_object, '__module__', None) == 'http.server':
1232 expected.append(name)
1233 self.assertCountEqual(server.__all__, expected)
1234
1235
Lisa Roach433433f2018-11-26 10:43:38 -08001236class ScriptTestCase(unittest.TestCase):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001237
1238 def mock_server_class(self):
1239 return mock.MagicMock(
1240 return_value=mock.MagicMock(
1241 __enter__=mock.MagicMock(
1242 return_value=mock.MagicMock(
1243 socket=mock.MagicMock(
1244 getsockname=lambda: ('', 0),
1245 ),
1246 ),
1247 ),
1248 ),
1249 )
1250
1251 @mock.patch('builtins.print')
1252 def test_server_test_unspec(self, _):
1253 mock_server = self.mock_server_class()
1254 server.test(ServerClass=mock_server, bind=None)
1255 self.assertIn(
1256 mock_server.address_family,
1257 (socket.AF_INET6, socket.AF_INET),
1258 )
1259
1260 @mock.patch('builtins.print')
1261 def test_server_test_localhost(self, _):
1262 mock_server = self.mock_server_class()
1263 server.test(ServerClass=mock_server, bind="localhost")
1264 self.assertIn(
1265 mock_server.address_family,
1266 (socket.AF_INET6, socket.AF_INET),
1267 )
1268
1269 ipv6_addrs = (
1270 "::",
1271 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
1272 "::1",
1273 )
1274
1275 ipv4_addrs = (
1276 "0.0.0.0",
1277 "8.8.8.8",
1278 "127.0.0.1",
1279 )
1280
Lisa Roach433433f2018-11-26 10:43:38 -08001281 @mock.patch('builtins.print')
1282 def test_server_test_ipv6(self, _):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001283 for bind in self.ipv6_addrs:
1284 mock_server = self.mock_server_class()
1285 server.test(ServerClass=mock_server, bind=bind)
1286 self.assertEqual(mock_server.address_family, socket.AF_INET6)
Lisa Roach433433f2018-11-26 10:43:38 -08001287
Jason R. Coombsf2890842019-02-07 08:22:45 -05001288 @mock.patch('builtins.print')
1289 def test_server_test_ipv4(self, _):
1290 for bind in self.ipv4_addrs:
1291 mock_server = self.mock_server_class()
1292 server.test(ServerClass=mock_server, bind=bind)
1293 self.assertEqual(mock_server.address_family, socket.AF_INET)
Lisa Roach433433f2018-11-26 10:43:38 -08001294
1295
Georg Brandlb533e262008-05-25 18:19:30 +00001296def test_main(verbose=None):
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001297 cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +00001298 try:
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001299 support.run_unittest(
Serhiy Storchakac0a23e62015-03-07 11:51:37 +02001300 RequestHandlerLoggingTestCase,
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001301 BaseHTTPRequestHandlerTestCase,
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001302 BaseHTTPServerTestCase,
1303 SimpleHTTPServerTestCase,
1304 CGIHTTPServerTestCase,
1305 SimpleHTTPRequestHandlerTestCase,
Berker Peksag366c5702015-02-13 20:48:15 +02001306 MiscTestCase,
Lisa Roach433433f2018-11-26 10:43:38 -08001307 ScriptTestCase
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001308 )
Georg Brandlb533e262008-05-25 18:19:30 +00001309 finally:
1310 os.chdir(cwd)
1311
1312if __name__ == '__main__':
1313 test_main()