blob: a7e1719ab60ee759233642d35784187c88b73c16 [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"""
6
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
Georg Brandl24420152008-05-26 16:32:26 +000022import http.client
Pierre Quentel351adda2017-04-02 12:26:12 +020023import urllib.parse
Georg Brandlb533e262008-05-25 18:19:30 +000024import tempfile
Berker Peksag04bc5b92016-03-14 06:06:03 +020025import time
Pierre Quentel351adda2017-04-02 12:26:12 +020026import datetime
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020027import threading
Stéphane Wirtela17a2f52017-05-24 09:29:06 +020028from unittest import mock
Senthil Kumaran0f476d42010-09-30 06:09:18 +000029from io import BytesIO
Georg Brandlb533e262008-05-25 18:19:30 +000030
31import unittest
32from test import support
Hai Shia089d212020-07-06 17:15:08 +080033from test.support import os_helper
Hai Shie80697d2020-05-28 06:10:27 +080034from test.support import threading_helper
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020035
Georg Brandlb533e262008-05-25 18:19:30 +000036
Georg Brandlb533e262008-05-25 18:19:30 +000037class NoLogRequestHandler:
38 def log_message(self, *args):
39 # don't write log messages to stderr
40 pass
41
Barry Warsaw820c1202008-06-12 04:06:45 +000042 def read(self, n=None):
43 return ''
44
Georg Brandlb533e262008-05-25 18:19:30 +000045
46class TestServerThread(threading.Thread):
47 def __init__(self, test_object, request_handler):
48 threading.Thread.__init__(self)
49 self.request_handler = request_handler
50 self.test_object = test_object
Georg Brandlb533e262008-05-25 18:19:30 +000051
52 def run(self):
Antoine Pitroucb342182011-03-21 00:26:51 +010053 self.server = HTTPServer(('localhost', 0), self.request_handler)
54 self.test_object.HOST, self.test_object.PORT = self.server.socket.getsockname()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000055 self.test_object.server_started.set()
56 self.test_object = None
Georg Brandlb533e262008-05-25 18:19:30 +000057 try:
Antoine Pitrou08911bd2010-04-25 22:19:43 +000058 self.server.serve_forever(0.05)
Georg Brandlb533e262008-05-25 18:19:30 +000059 finally:
60 self.server.server_close()
61
62 def stop(self):
63 self.server.shutdown()
Victor Stinner830d7d22017-08-22 18:05:07 +020064 self.join()
Georg Brandlb533e262008-05-25 18:19:30 +000065
66
67class BaseTestCase(unittest.TestCase):
68 def setUp(self):
Hai Shie80697d2020-05-28 06:10:27 +080069 self._threads = threading_helper.threading_setup()
Hai Shi79bb2c92020-08-06 19:51:29 +080070 os.environ = os_helper.EnvironmentVarGuard()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000071 self.server_started = threading.Event()
Georg Brandlb533e262008-05-25 18:19:30 +000072 self.thread = TestServerThread(self, self.request_handler)
73 self.thread.start()
Antoine Pitrou08911bd2010-04-25 22:19:43 +000074 self.server_started.wait()
Georg Brandlb533e262008-05-25 18:19:30 +000075
76 def tearDown(self):
Georg Brandlb533e262008-05-25 18:19:30 +000077 self.thread.stop()
Antoine Pitrouf7270822012-09-30 01:05:30 +020078 self.thread = None
Nick Coghlan6ead5522009-10-18 13:19:33 +000079 os.environ.__exit__()
Hai Shie80697d2020-05-28 06:10:27 +080080 threading_helper.threading_cleanup(*self._threads)
Georg Brandlb533e262008-05-25 18:19:30 +000081
82 def request(self, uri, method='GET', body=None, headers={}):
Antoine Pitroucb342182011-03-21 00:26:51 +010083 self.connection = http.client.HTTPConnection(self.HOST, self.PORT)
Georg Brandlb533e262008-05-25 18:19:30 +000084 self.connection.request(method, uri, body, headers)
85 return self.connection.getresponse()
86
87
88class BaseHTTPServerTestCase(BaseTestCase):
89 class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
90 protocol_version = 'HTTP/1.1'
91 default_request_version = 'HTTP/1.1'
92
93 def do_TEST(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +020094 self.send_response(HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +000095 self.send_header('Content-Type', 'text/html')
96 self.send_header('Connection', 'close')
97 self.end_headers()
98
99 def do_KEEP(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200100 self.send_response(HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +0000101 self.send_header('Content-Type', 'text/html')
102 self.send_header('Connection', 'keep-alive')
103 self.end_headers()
104
105 def do_KEYERROR(self):
106 self.send_error(999)
107
Senthil Kumaran52d27202012-10-10 23:16:21 -0700108 def do_NOTFOUND(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200109 self.send_error(HTTPStatus.NOT_FOUND)
Senthil Kumaran52d27202012-10-10 23:16:21 -0700110
Senthil Kumaran26886442013-03-15 07:53:21 -0700111 def do_EXPLAINERROR(self):
112 self.send_error(999, "Short Message",
Martin Panter46f50722016-05-26 05:35:26 +0000113 "This is a long \n explanation")
Senthil Kumaran26886442013-03-15 07:53:21 -0700114
Georg Brandlb533e262008-05-25 18:19:30 +0000115 def do_CUSTOM(self):
116 self.send_response(999)
117 self.send_header('Content-Type', 'text/html')
118 self.send_header('Connection', 'close')
119 self.end_headers()
120
Armin Ronacher8d96d772011-01-22 13:13:05 +0000121 def do_LATINONEHEADER(self):
122 self.send_response(999)
123 self.send_header('X-Special', 'Dängerous Mind')
Armin Ronacher59531282011-01-22 13:44:22 +0000124 self.send_header('Connection', 'close')
Armin Ronacher8d96d772011-01-22 13:13:05 +0000125 self.end_headers()
Armin Ronacher59531282011-01-22 13:44:22 +0000126 body = self.headers['x-special-incoming'].encode('utf-8')
127 self.wfile.write(body)
Armin Ronacher8d96d772011-01-22 13:13:05 +0000128
Martin Pantere42e1292016-06-08 08:29:13 +0000129 def do_SEND_ERROR(self):
130 self.send_error(int(self.path[1:]))
131
132 def do_HEAD(self):
133 self.send_error(int(self.path[1:]))
134
Georg Brandlb533e262008-05-25 18:19:30 +0000135 def setUp(self):
136 BaseTestCase.setUp(self)
Antoine Pitroucb342182011-03-21 00:26:51 +0100137 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
Georg Brandlb533e262008-05-25 18:19:30 +0000138 self.con.connect()
139
140 def test_command(self):
141 self.con.request('GET', '/')
142 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200143 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000144
145 def test_request_line_trimming(self):
146 self.con._http_vsn_str = 'HTTP/1.1\n'
R David Murray14199f92014-06-24 16:39:49 -0400147 self.con.putrequest('XYZBOGUS', '/')
Georg Brandlb533e262008-05-25 18:19:30 +0000148 self.con.endheaders()
149 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200150 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000151
152 def test_version_bogus(self):
153 self.con._http_vsn_str = 'FUBAR'
154 self.con.putrequest('GET', '/')
155 self.con.endheaders()
156 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200157 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000158
159 def test_version_digits(self):
160 self.con._http_vsn_str = 'HTTP/9.9.9'
161 self.con.putrequest('GET', '/')
162 self.con.endheaders()
163 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200164 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000165
166 def test_version_none_get(self):
167 self.con._http_vsn_str = ''
168 self.con.putrequest('GET', '/')
169 self.con.endheaders()
170 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200171 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000172
173 def test_version_none(self):
R David Murray14199f92014-06-24 16:39:49 -0400174 # Test that a valid method is rejected when not HTTP/1.x
Georg Brandlb533e262008-05-25 18:19:30 +0000175 self.con._http_vsn_str = ''
R David Murray14199f92014-06-24 16:39:49 -0400176 self.con.putrequest('CUSTOM', '/')
Georg Brandlb533e262008-05-25 18:19:30 +0000177 self.con.endheaders()
178 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200179 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000180
181 def test_version_invalid(self):
182 self.con._http_vsn = 99
183 self.con._http_vsn_str = 'HTTP/9.9'
184 self.con.putrequest('GET', '/')
185 self.con.endheaders()
186 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200187 self.assertEqual(res.status, HTTPStatus.HTTP_VERSION_NOT_SUPPORTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000188
189 def test_send_blank(self):
190 self.con._http_vsn_str = ''
191 self.con.putrequest('', '')
192 self.con.endheaders()
193 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200194 self.assertEqual(res.status, HTTPStatus.BAD_REQUEST)
Georg Brandlb533e262008-05-25 18:19:30 +0000195
196 def test_header_close(self):
197 self.con.putrequest('GET', '/')
198 self.con.putheader('Connection', 'close')
199 self.con.endheaders()
200 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200201 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000202
Berker Peksag20853612016-08-25 01:13:34 +0300203 def test_header_keep_alive(self):
Georg Brandlb533e262008-05-25 18:19:30 +0000204 self.con._http_vsn_str = 'HTTP/1.1'
205 self.con.putrequest('GET', '/')
206 self.con.putheader('Connection', 'keep-alive')
207 self.con.endheaders()
208 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200209 self.assertEqual(res.status, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000210
211 def test_handler(self):
212 self.con.request('TEST', '/')
213 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200214 self.assertEqual(res.status, HTTPStatus.NO_CONTENT)
Georg Brandlb533e262008-05-25 18:19:30 +0000215
216 def test_return_header_keep_alive(self):
217 self.con.request('KEEP', '/')
218 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000219 self.assertEqual(res.getheader('Connection'), 'keep-alive')
Georg Brandlb533e262008-05-25 18:19:30 +0000220 self.con.request('TEST', '/')
Brian Curtin61d0d602010-10-31 00:34:23 +0000221 self.addCleanup(self.con.close)
Georg Brandlb533e262008-05-25 18:19:30 +0000222
223 def test_internal_key_error(self):
224 self.con.request('KEYERROR', '/')
225 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000226 self.assertEqual(res.status, 999)
Georg Brandlb533e262008-05-25 18:19:30 +0000227
228 def test_return_custom_status(self):
229 self.con.request('CUSTOM', '/')
230 res = self.con.getresponse()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000231 self.assertEqual(res.status, 999)
Georg Brandlb533e262008-05-25 18:19:30 +0000232
Senthil Kumaran26886442013-03-15 07:53:21 -0700233 def test_return_explain_error(self):
234 self.con.request('EXPLAINERROR', '/')
235 res = self.con.getresponse()
236 self.assertEqual(res.status, 999)
237 self.assertTrue(int(res.getheader('Content-Length')))
238
Armin Ronacher8d96d772011-01-22 13:13:05 +0000239 def test_latin1_header(self):
Armin Ronacher59531282011-01-22 13:44:22 +0000240 self.con.request('LATINONEHEADER', '/', headers={
241 'X-Special-Incoming': 'Ärger mit Unicode'
242 })
Armin Ronacher8d96d772011-01-22 13:13:05 +0000243 res = self.con.getresponse()
244 self.assertEqual(res.getheader('X-Special'), 'Dängerous Mind')
Armin Ronacher59531282011-01-22 13:44:22 +0000245 self.assertEqual(res.read(), 'Ärger mit Unicode'.encode('utf-8'))
Armin Ronacher8d96d772011-01-22 13:13:05 +0000246
Senthil Kumaran52d27202012-10-10 23:16:21 -0700247 def test_error_content_length(self):
248 # Issue #16088: standard error responses should have a content-length
249 self.con.request('NOTFOUND', '/')
250 res = self.con.getresponse()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200251 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
252
Senthil Kumaran52d27202012-10-10 23:16:21 -0700253 data = res.read()
Senthil Kumaran52d27202012-10-10 23:16:21 -0700254 self.assertEqual(int(res.getheader('Content-Length')), len(data))
255
Martin Pantere42e1292016-06-08 08:29:13 +0000256 def test_send_error(self):
257 allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
258 HTTPStatus.RESET_CONTENT)
259 for code in (HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED,
260 HTTPStatus.PROCESSING, HTTPStatus.RESET_CONTENT,
261 HTTPStatus.SWITCHING_PROTOCOLS):
262 self.con.request('SEND_ERROR', '/{}'.format(code))
263 res = self.con.getresponse()
264 self.assertEqual(code, res.status)
265 self.assertEqual(None, res.getheader('Content-Length'))
266 self.assertEqual(None, res.getheader('Content-Type'))
267 if code not in allow_transfer_encoding_codes:
268 self.assertEqual(None, res.getheader('Transfer-Encoding'))
269
270 data = res.read()
271 self.assertEqual(b'', data)
272
273 def test_head_via_send_error(self):
274 allow_transfer_encoding_codes = (HTTPStatus.NOT_MODIFIED,
275 HTTPStatus.RESET_CONTENT)
276 for code in (HTTPStatus.OK, HTTPStatus.NO_CONTENT,
277 HTTPStatus.NOT_MODIFIED, HTTPStatus.RESET_CONTENT,
278 HTTPStatus.SWITCHING_PROTOCOLS):
279 self.con.request('HEAD', '/{}'.format(code))
280 res = self.con.getresponse()
281 self.assertEqual(code, res.status)
282 if code == HTTPStatus.OK:
283 self.assertTrue(int(res.getheader('Content-Length')) > 0)
284 self.assertIn('text/html', res.getheader('Content-Type'))
285 else:
286 self.assertEqual(None, res.getheader('Content-Length'))
287 self.assertEqual(None, res.getheader('Content-Type'))
288 if code not in allow_transfer_encoding_codes:
289 self.assertEqual(None, res.getheader('Transfer-Encoding'))
290
291 data = res.read()
292 self.assertEqual(b'', data)
293
Georg Brandlb533e262008-05-25 18:19:30 +0000294
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200295class RequestHandlerLoggingTestCase(BaseTestCase):
296 class request_handler(BaseHTTPRequestHandler):
297 protocol_version = 'HTTP/1.1'
298 default_request_version = 'HTTP/1.1'
299
300 def do_GET(self):
301 self.send_response(HTTPStatus.OK)
302 self.end_headers()
303
304 def do_ERROR(self):
305 self.send_error(HTTPStatus.NOT_FOUND, 'File not found')
306
307 def test_get(self):
308 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
309 self.con.connect()
310
311 with support.captured_stderr() as err:
312 self.con.request('GET', '/')
313 self.con.getresponse()
314
315 self.assertTrue(
316 err.getvalue().endswith('"GET / HTTP/1.1" 200 -\n'))
317
318 def test_err(self):
319 self.con = http.client.HTTPConnection(self.HOST, self.PORT)
320 self.con.connect()
321
322 with support.captured_stderr() as err:
323 self.con.request('ERROR', '/')
324 self.con.getresponse()
325
326 lines = err.getvalue().split('\n')
327 self.assertTrue(lines[0].endswith('code 404, message File not found'))
328 self.assertTrue(lines[1].endswith('"ERROR / HTTP/1.1" 404 -'))
329
330
Georg Brandlb533e262008-05-25 18:19:30 +0000331class SimpleHTTPServerTestCase(BaseTestCase):
332 class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
333 pass
334
335 def setUp(self):
336 BaseTestCase.setUp(self)
337 self.cwd = os.getcwd()
338 basetempdir = tempfile.gettempdir()
339 os.chdir(basetempdir)
340 self.data = b'We are the knights who say Ni!'
341 self.tempdir = tempfile.mkdtemp(dir=basetempdir)
342 self.tempdir_name = os.path.basename(self.tempdir)
Martin Panterfc475a92016-04-09 04:56:10 +0000343 self.base_url = '/' + self.tempdir_name
Victor Stinner28ce07a2017-07-28 18:15:02 +0200344 tempname = os.path.join(self.tempdir, 'test')
345 with open(tempname, 'wb') as temp:
Brett Cannon105df5d2010-10-29 23:43:42 +0000346 temp.write(self.data)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200347 temp.flush()
348 mtime = os.stat(tempname).st_mtime
Pierre Quentel351adda2017-04-02 12:26:12 +0200349 # compute last modification datetime for browser cache tests
350 last_modif = datetime.datetime.fromtimestamp(mtime,
351 datetime.timezone.utc)
352 self.last_modif_datetime = last_modif.replace(microsecond=0)
353 self.last_modif_header = email.utils.formatdate(
354 last_modif.timestamp(), usegmt=True)
Georg Brandlb533e262008-05-25 18:19:30 +0000355
356 def tearDown(self):
357 try:
358 os.chdir(self.cwd)
359 try:
360 shutil.rmtree(self.tempdir)
361 except:
362 pass
363 finally:
364 BaseTestCase.tearDown(self)
365
366 def check_status_and_reason(self, response, status, data=None):
Berker Peksagb5754322015-07-22 19:25:37 +0300367 def close_conn():
368 """Don't close reader yet so we can check if there was leftover
369 buffered input"""
370 nonlocal reader
371 reader = response.fp
372 response.fp = None
373 reader = None
374 response._close_conn = close_conn
375
Georg Brandlb533e262008-05-25 18:19:30 +0000376 body = response.read()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000377 self.assertTrue(response)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000378 self.assertEqual(response.status, status)
379 self.assertIsNotNone(response.reason)
Georg Brandlb533e262008-05-25 18:19:30 +0000380 if data:
381 self.assertEqual(data, body)
Berker Peksagb5754322015-07-22 19:25:37 +0300382 # Ensure the server has not set up a persistent connection, and has
383 # not sent any extra data
384 self.assertEqual(response.version, 10)
385 self.assertEqual(response.msg.get("Connection", "close"), "close")
386 self.assertEqual(reader.read(30), b'', 'Connection should be closed')
387
388 reader.close()
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300389 return body
390
Ned Deilyb3edde82017-12-04 23:42:02 -0500391 @unittest.skipIf(sys.platform == 'darwin',
392 'undecodable name cannot always be decoded on macOS')
Steve Dowere58571b2016-09-08 11:11:13 -0700393 @unittest.skipIf(sys.platform == 'win32',
394 'undecodable name cannot be decoded on win32')
Hai Shia089d212020-07-06 17:15:08 +0800395 @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
396 'need os_helper.TESTFN_UNDECODABLE')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300397 def test_undecodable_filename(self):
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300398 enc = sys.getfilesystemencoding()
Hai Shia089d212020-07-06 17:15:08 +0800399 filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt'
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300400 with open(os.path.join(self.tempdir, filename), 'wb') as f:
Hai Shia089d212020-07-06 17:15:08 +0800401 f.write(os_helper.TESTFN_UNDECODABLE)
Martin Panterfc475a92016-04-09 04:56:10 +0000402 response = self.request(self.base_url + '/')
Serhiy Storchakad9e95282014-08-17 16:57:39 +0300403 if sys.platform == 'darwin':
404 # On Mac OS the HFS+ filesystem replaces bytes that aren't valid
405 # UTF-8 into a percent-encoded value.
406 for name in os.listdir(self.tempdir):
407 if name != 'test': # Ignore a filename created in setUp().
408 filename = name
409 break
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200410 body = self.check_status_and_reason(response, HTTPStatus.OK)
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300411 quotedname = urllib.parse.quote(filename, errors='surrogatepass')
412 self.assertIn(('href="%s"' % quotedname)
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300413 .encode(enc, 'surrogateescape'), body)
Martin Panterda3bb382016-04-11 00:40:08 +0000414 self.assertIn(('>%s<' % html.escape(filename, quote=False))
Serhiy Storchakaa64ce5d2014-08-17 12:20:02 +0300415 .encode(enc, 'surrogateescape'), body)
Martin Panterfc475a92016-04-09 04:56:10 +0000416 response = self.request(self.base_url + '/' + quotedname)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200417 self.check_status_and_reason(response, HTTPStatus.OK,
Hai Shia089d212020-07-06 17:15:08 +0800418 data=os_helper.TESTFN_UNDECODABLE)
Georg Brandlb533e262008-05-25 18:19:30 +0000419
420 def test_get(self):
421 #constructs the path relative to the root directory of the HTTPServer
Martin Panterfc475a92016-04-09 04:56:10 +0000422 response = self.request(self.base_url + '/test')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200423 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700424 # check for trailing "/" which should return 404. See Issue17324
Martin Panterfc475a92016-04-09 04:56:10 +0000425 response = self.request(self.base_url + '/test/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200426 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Martin Panterfc475a92016-04-09 04:56:10 +0000427 response = self.request(self.base_url + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200428 self.check_status_and_reason(response, HTTPStatus.OK)
Martin Panterfc475a92016-04-09 04:56:10 +0000429 response = self.request(self.base_url)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200430 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
Martin Panterfc475a92016-04-09 04:56:10 +0000431 response = self.request(self.base_url + '/?hi=2')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200432 self.check_status_and_reason(response, HTTPStatus.OK)
Martin Panterfc475a92016-04-09 04:56:10 +0000433 response = self.request(self.base_url + '?hi=1')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200434 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600435 self.assertEqual(response.getheader("Location"),
Martin Panterfc475a92016-04-09 04:56:10 +0000436 self.base_url + "/?hi=1")
Georg Brandlb533e262008-05-25 18:19:30 +0000437 response = self.request('/ThisDoesNotExist')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200438 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000439 response = self.request('/' + 'ThisDoesNotExist' + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200440 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300441
442 data = b"Dummy index file\r\n"
443 with open(os.path.join(self.tempdir_name, 'index.html'), 'wb') as f:
444 f.write(data)
Martin Panterfc475a92016-04-09 04:56:10 +0000445 response = self.request(self.base_url + '/')
Berker Peksagb5754322015-07-22 19:25:37 +0300446 self.check_status_and_reason(response, HTTPStatus.OK, data)
447
448 # chmod() doesn't work as expected on Windows, and filesystem
449 # permissions are ignored by root on Unix.
450 if os.name == 'posix' and os.geteuid() != 0:
451 os.chmod(self.tempdir, 0)
452 try:
Martin Panterfc475a92016-04-09 04:56:10 +0000453 response = self.request(self.base_url + '/')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200454 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
Berker Peksagb5754322015-07-22 19:25:37 +0300455 finally:
Brett Cannon105df5d2010-10-29 23:43:42 +0000456 os.chmod(self.tempdir, 0o755)
Georg Brandlb533e262008-05-25 18:19:30 +0000457
458 def test_head(self):
459 response = self.request(
Martin Panterfc475a92016-04-09 04:56:10 +0000460 self.base_url + '/test', method='HEAD')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200461 self.check_status_and_reason(response, HTTPStatus.OK)
Georg Brandlb533e262008-05-25 18:19:30 +0000462 self.assertEqual(response.getheader('content-length'),
463 str(len(self.data)))
464 self.assertEqual(response.getheader('content-type'),
465 'application/octet-stream')
466
Pierre Quentel351adda2017-04-02 12:26:12 +0200467 def test_browser_cache(self):
468 """Check that when a request to /test is sent with the request header
469 If-Modified-Since set to date of last modification, the server returns
470 status code 304, not 200
471 """
472 headers = email.message.Message()
473 headers['If-Modified-Since'] = self.last_modif_header
474 response = self.request(self.base_url + '/test', headers=headers)
475 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
476
477 # one hour after last modification : must return 304
478 new_dt = self.last_modif_datetime + datetime.timedelta(hours=1)
479 headers = email.message.Message()
480 headers['If-Modified-Since'] = email.utils.format_datetime(new_dt,
481 usegmt=True)
482 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200483 self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED)
Pierre Quentel351adda2017-04-02 12:26:12 +0200484
485 def test_browser_cache_file_changed(self):
486 # with If-Modified-Since earlier than Last-Modified, must return 200
487 dt = self.last_modif_datetime
488 # build datetime object : 365 days before last modification
489 old_dt = dt - datetime.timedelta(days=365)
490 headers = email.message.Message()
491 headers['If-Modified-Since'] = email.utils.format_datetime(old_dt,
492 usegmt=True)
493 response = self.request(self.base_url + '/test', headers=headers)
494 self.check_status_and_reason(response, HTTPStatus.OK)
495
496 def test_browser_cache_with_If_None_Match_header(self):
497 # if If-None-Match header is present, ignore If-Modified-Since
498
499 headers = email.message.Message()
500 headers['If-Modified-Since'] = self.last_modif_header
501 headers['If-None-Match'] = "*"
502 response = self.request(self.base_url + '/test', headers=headers)
Victor Stinner28ce07a2017-07-28 18:15:02 +0200503 self.check_status_and_reason(response, HTTPStatus.OK)
Pierre Quentel351adda2017-04-02 12:26:12 +0200504
Georg Brandlb533e262008-05-25 18:19:30 +0000505 def test_invalid_requests(self):
506 response = self.request('/', method='FOO')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200507 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000508 # requests must be case sensitive,so this should fail too
Terry Jan Reedydd09efd2014-10-18 17:10:09 -0400509 response = self.request('/', method='custom')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200510 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000511 response = self.request('/', method='GETs')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200512 self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED)
Georg Brandlb533e262008-05-25 18:19:30 +0000513
Pierre Quentel351adda2017-04-02 12:26:12 +0200514 def test_last_modified(self):
515 """Checks that the datetime returned in Last-Modified response header
516 is the actual datetime of last modification, rounded to the second
517 """
518 response = self.request(self.base_url + '/test')
519 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
520 last_modif_header = response.headers['Last-modified']
521 self.assertEqual(last_modif_header, self.last_modif_header)
522
Martin Panterfc475a92016-04-09 04:56:10 +0000523 def test_path_without_leading_slash(self):
524 response = self.request(self.tempdir_name + '/test')
525 self.check_status_and_reason(response, HTTPStatus.OK, data=self.data)
526 response = self.request(self.tempdir_name + '/test/')
527 self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
528 response = self.request(self.tempdir_name + '/')
529 self.check_status_and_reason(response, HTTPStatus.OK)
530 response = self.request(self.tempdir_name)
531 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
532 response = self.request(self.tempdir_name + '/?hi=2')
533 self.check_status_and_reason(response, HTTPStatus.OK)
534 response = self.request(self.tempdir_name + '?hi=1')
535 self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
536 self.assertEqual(response.getheader("Location"),
537 self.tempdir_name + "/?hi=1")
538
Martin Panterda3bb382016-04-11 00:40:08 +0000539 def test_html_escape_filename(self):
540 filename = '<test&>.txt'
541 fullpath = os.path.join(self.tempdir, filename)
542
543 try:
544 open(fullpath, 'w').close()
545 except OSError:
546 raise unittest.SkipTest('Can not create file %s on current file '
547 'system' % filename)
548
549 try:
550 response = self.request(self.base_url + '/')
551 body = self.check_status_and_reason(response, HTTPStatus.OK)
552 enc = response.headers.get_content_charset()
553 finally:
554 os.unlink(fullpath) # avoid affecting test_undecodable_filename
555
556 self.assertIsNotNone(enc)
557 html_text = '>%s<' % html.escape(filename, quote=False)
558 self.assertIn(html_text.encode(enc), body)
559
Georg Brandlb533e262008-05-25 18:19:30 +0000560
561cgi_file1 = """\
562#!%s
563
564print("Content-type: text/html")
565print()
566print("Hello World")
567"""
568
569cgi_file2 = """\
570#!%s
571import cgi
572
573print("Content-type: text/html")
574print()
575
576form = cgi.FieldStorage()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000577print("%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
578 form.getfirst("bacon")))
Georg Brandlb533e262008-05-25 18:19:30 +0000579"""
580
Martin Pantera02e18a2015-10-03 05:38:07 +0000581cgi_file4 = """\
582#!%s
583import os
584
585print("Content-type: text/html")
586print()
587
588print(os.environ["%s"])
589"""
590
Charles-François Natalif7ed9fc2011-11-02 19:35:14 +0100591
592@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
593 "This test can't be run reliably as root (issue #13308).")
Georg Brandlb533e262008-05-25 18:19:30 +0000594class CGIHTTPServerTestCase(BaseTestCase):
595 class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
596 pass
597
Antoine Pitroue768c392012-08-05 14:52:45 +0200598 linesep = os.linesep.encode('ascii')
599
Georg Brandlb533e262008-05-25 18:19:30 +0000600 def setUp(self):
601 BaseTestCase.setUp(self)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000602 self.cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +0000603 self.parent_dir = tempfile.mkdtemp()
604 self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
Ned Deily915a30f2014-07-12 22:06:26 -0700605 self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
Siwon Kang91daa9d2019-11-22 18:13:05 +0900606 self.sub_dir_1 = os.path.join(self.parent_dir, 'sub')
607 self.sub_dir_2 = os.path.join(self.sub_dir_1, 'dir')
608 self.cgi_dir_in_sub_dir = os.path.join(self.sub_dir_2, 'cgi-bin')
Georg Brandlb533e262008-05-25 18:19:30 +0000609 os.mkdir(self.cgi_dir)
Ned Deily915a30f2014-07-12 22:06:26 -0700610 os.mkdir(self.cgi_child_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900611 os.mkdir(self.sub_dir_1)
612 os.mkdir(self.sub_dir_2)
613 os.mkdir(self.cgi_dir_in_sub_dir)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400614 self.nocgi_path = None
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000615 self.file1_path = None
616 self.file2_path = None
Ned Deily915a30f2014-07-12 22:06:26 -0700617 self.file3_path = None
Martin Pantera02e18a2015-10-03 05:38:07 +0000618 self.file4_path = None
Siwon Kang91daa9d2019-11-22 18:13:05 +0900619 self.file5_path = None
Georg Brandlb533e262008-05-25 18:19:30 +0000620
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000621 # The shebang line should be pure ASCII: use symlink if possible.
622 # See issue #7668.
Steve Dower9048c492019-06-29 10:34:11 -0700623 self._pythonexe_symlink = None
Hai Shi79bb2c92020-08-06 19:51:29 +0800624 if os_helper.can_symlink():
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000625 self.pythonexe = os.path.join(self.parent_dir, 'python')
Steve Dower9048c492019-06-29 10:34:11 -0700626 self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000627 else:
628 self.pythonexe = sys.executable
629
Victor Stinner3218c312010-10-17 20:13:36 +0000630 try:
631 # The python executable path is written as the first line of the
632 # CGI Python script. The encoding cookie cannot be used, and so the
633 # path should be encodable to the default script encoding (utf-8)
634 self.pythonexe.encode('utf-8')
635 except UnicodeEncodeError:
636 self.tearDown()
Serhiy Storchaka0b4591e2013-02-04 15:45:00 +0200637 self.skipTest("Python executable path is not encodable to utf-8")
Victor Stinner3218c312010-10-17 20:13:36 +0000638
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400639 self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
640 with open(self.nocgi_path, 'w') as fp:
641 fp.write(cgi_file1 % self.pythonexe)
642 os.chmod(self.nocgi_path, 0o777)
643
Georg Brandlb533e262008-05-25 18:19:30 +0000644 self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000645 with open(self.file1_path, 'w', encoding='utf-8') as file1:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000646 file1.write(cgi_file1 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000647 os.chmod(self.file1_path, 0o777)
648
649 self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
Victor Stinner6fb45752010-10-17 20:17:41 +0000650 with open(self.file2_path, 'w', encoding='utf-8') as file2:
Florent Xiclunafd1b0932010-03-28 00:25:02 +0000651 file2.write(cgi_file2 % self.pythonexe)
Georg Brandlb533e262008-05-25 18:19:30 +0000652 os.chmod(self.file2_path, 0o777)
653
Ned Deily915a30f2014-07-12 22:06:26 -0700654 self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
655 with open(self.file3_path, 'w', encoding='utf-8') as file3:
656 file3.write(cgi_file1 % self.pythonexe)
657 os.chmod(self.file3_path, 0o777)
658
Martin Pantera02e18a2015-10-03 05:38:07 +0000659 self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
660 with open(self.file4_path, 'w', encoding='utf-8') as file4:
661 file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
662 os.chmod(self.file4_path, 0o777)
663
Siwon Kang91daa9d2019-11-22 18:13:05 +0900664 self.file5_path = os.path.join(self.cgi_dir_in_sub_dir, 'file5.py')
665 with open(self.file5_path, 'w', encoding='utf-8') as file5:
666 file5.write(cgi_file1 % self.pythonexe)
667 os.chmod(self.file5_path, 0o777)
668
Georg Brandlb533e262008-05-25 18:19:30 +0000669 os.chdir(self.parent_dir)
670
671 def tearDown(self):
672 try:
673 os.chdir(self.cwd)
Steve Dower9048c492019-06-29 10:34:11 -0700674 if self._pythonexe_symlink:
675 self._pythonexe_symlink.__exit__(None, None, None)
Benjamin Peterson35aca892013-10-30 12:48:59 -0400676 if self.nocgi_path:
677 os.remove(self.nocgi_path)
Victor Stinner0b0ca0c2010-10-17 19:46:36 +0000678 if self.file1_path:
679 os.remove(self.file1_path)
680 if self.file2_path:
681 os.remove(self.file2_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700682 if self.file3_path:
683 os.remove(self.file3_path)
Martin Pantera02e18a2015-10-03 05:38:07 +0000684 if self.file4_path:
685 os.remove(self.file4_path)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900686 if self.file5_path:
687 os.remove(self.file5_path)
Ned Deily915a30f2014-07-12 22:06:26 -0700688 os.rmdir(self.cgi_child_dir)
Georg Brandlb533e262008-05-25 18:19:30 +0000689 os.rmdir(self.cgi_dir)
Siwon Kang91daa9d2019-11-22 18:13:05 +0900690 os.rmdir(self.cgi_dir_in_sub_dir)
691 os.rmdir(self.sub_dir_2)
692 os.rmdir(self.sub_dir_1)
Georg Brandlb533e262008-05-25 18:19:30 +0000693 os.rmdir(self.parent_dir)
694 finally:
695 BaseTestCase.tearDown(self)
696
Senthil Kumarand70846b2012-04-12 02:34:32 +0800697 def test_url_collapse_path(self):
698 # verify tail is the last portion and head is the rest on proper urls
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000699 test_vectors = {
Senthil Kumarand70846b2012-04-12 02:34:32 +0800700 '': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000701 '..': IndexError,
702 '/.//..': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800703 '/': '//',
704 '//': '//',
705 '/\\': '//\\',
706 '/.//': '//',
707 'cgi-bin/file1.py': '/cgi-bin/file1.py',
708 '/cgi-bin/file1.py': '/cgi-bin/file1.py',
709 'a': '//a',
710 '/a': '//a',
711 '//a': '//a',
712 './a': '//a',
713 './C:/': '/C:/',
714 '/a/b': '/a/b',
715 '/a/b/': '/a/b/',
716 '/a/b/.': '/a/b/',
717 '/a/b/c/..': '/a/b/',
718 '/a/b/c/../d': '/a/b/d',
719 '/a/b/c/../d/e/../f': '/a/b/d/f',
720 '/a/b/c/../d/e/../../f': '/a/b/f',
721 '/a/b/c/../d/e/.././././..//f': '/a/b/f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000722 '../a/b/c/../d/e/.././././..//f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800723 '/a/b/c/../d/e/../../../f': '/a/f',
724 '/a/b/c/../d/e/../../../../f': '//f',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000725 '/a/b/c/../d/e/../../../../../f': IndexError,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800726 '/a/b/c/../d/e/../../../../f/..': '//',
727 '/a/b/c/../d/e/../../../../f/../.': '//',
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000728 }
729 for path, expected in test_vectors.items():
730 if isinstance(expected, type) and issubclass(expected, Exception):
731 self.assertRaises(expected,
Senthil Kumarand70846b2012-04-12 02:34:32 +0800732 server._url_collapse_path, path)
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000733 else:
Senthil Kumarand70846b2012-04-12 02:34:32 +0800734 actual = server._url_collapse_path(path)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000735 self.assertEqual(expected, actual,
736 msg='path = %r\nGot: %r\nWanted: %r' %
737 (path, actual, expected))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000738
Georg Brandlb533e262008-05-25 18:19:30 +0000739 def test_headers_and_content(self):
740 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200741 self.assertEqual(
742 (res.read(), res.getheader('Content-type'), res.status),
743 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK))
Georg Brandlb533e262008-05-25 18:19:30 +0000744
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400745 def test_issue19435(self):
746 res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200747 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Benjamin Peterson04e9de42013-10-30 12:43:09 -0400748
Georg Brandlb533e262008-05-25 18:19:30 +0000749 def test_post(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000750 params = urllib.parse.urlencode(
751 {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
Georg Brandlb533e262008-05-25 18:19:30 +0000752 headers = {'Content-type' : 'application/x-www-form-urlencoded'}
753 res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
754
Antoine Pitroue768c392012-08-05 14:52:45 +0200755 self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
Georg Brandlb533e262008-05-25 18:19:30 +0000756
757 def test_invaliduri(self):
758 res = self.request('/cgi-bin/invalid')
759 res.read()
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200760 self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
Georg Brandlb533e262008-05-25 18:19:30 +0000761
762 def test_authorization(self):
763 headers = {b'Authorization' : b'Basic ' +
764 base64.b64encode(b'username:pass')}
765 res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200766 self.assertEqual(
767 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
768 (res.read(), res.getheader('Content-type'), res.status))
Georg Brandlb533e262008-05-25 18:19:30 +0000769
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000770 def test_no_leading_slash(self):
771 # http://bugs.python.org/issue2254
772 res = self.request('cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200773 self.assertEqual(
774 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
775 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000776
Senthil Kumaran42713722010-10-03 17:55:45 +0000777 def test_os_environ_is_not_altered(self):
778 signature = "Test CGI Server"
779 os.environ['SERVER_SOFTWARE'] = signature
780 res = self.request('/cgi-bin/file1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200781 self.assertEqual(
782 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
783 (res.read(), res.getheader('Content-type'), res.status))
Senthil Kumaran42713722010-10-03 17:55:45 +0000784 self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
785
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700786 def test_urlquote_decoding_in_cgi_check(self):
787 res = self.request('/cgi-bin%2ffile1.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200788 self.assertEqual(
789 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
790 (res.read(), res.getheader('Content-type'), res.status))
Benjamin Peterson73b8b1c2014-06-14 18:36:29 -0700791
Ned Deily915a30f2014-07-12 22:06:26 -0700792 def test_nested_cgi_path_issue21323(self):
793 res = self.request('/cgi-bin/child-dir/file3.py')
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200794 self.assertEqual(
795 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
796 (res.read(), res.getheader('Content-type'), res.status))
Ned Deily915a30f2014-07-12 22:06:26 -0700797
Martin Pantera02e18a2015-10-03 05:38:07 +0000798 def test_query_with_multiple_question_mark(self):
799 res = self.request('/cgi-bin/file4.py?a=b?c=d')
800 self.assertEqual(
Martin Pantereb1fee92015-10-03 06:07:22 +0000801 (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
Martin Pantera02e18a2015-10-03 05:38:07 +0000802 (res.read(), res.getheader('Content-type'), res.status))
803
Martin Pantercb29e8c2015-10-03 05:55:46 +0000804 def test_query_with_continuous_slashes(self):
805 res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
806 self.assertEqual(
807 (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
Martin Pantereb1fee92015-10-03 06:07:22 +0000808 'text/html', HTTPStatus.OK),
Martin Pantercb29e8c2015-10-03 05:55:46 +0000809 (res.read(), res.getheader('Content-type'), res.status))
810
Siwon Kang91daa9d2019-11-22 18:13:05 +0900811 def test_cgi_path_in_sub_directories(self):
Pablo Galindo24f5cac2019-12-04 09:29:10 +0000812 try:
813 CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
814 res = self.request('/sub/dir/cgi-bin/file5.py')
815 self.assertEqual(
816 (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
817 (res.read(), res.getheader('Content-type'), res.status))
818 finally:
819 CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
820
Siwon Kang91daa9d2019-11-22 18:13:05 +0900821
Georg Brandlb533e262008-05-25 18:19:30 +0000822
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000823class SocketlessRequestHandler(SimpleHTTPRequestHandler):
Géry Ogam781266e2019-09-11 15:03:46 +0200824 def __init__(self, directory=None):
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200825 request = mock.Mock()
826 request.makefile.return_value = BytesIO()
Géry Ogam781266e2019-09-11 15:03:46 +0200827 super().__init__(request, None, None, directory=directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200828
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000829 self.get_called = False
830 self.protocol_version = "HTTP/1.1"
831
832 def do_GET(self):
833 self.get_called = True
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200834 self.send_response(HTTPStatus.OK)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000835 self.send_header('Content-Type', 'text/html')
836 self.end_headers()
837 self.wfile.write(b'<html><body>Data</body></html>\r\n')
838
839 def log_message(self, format, *args):
Georg Brandl6fcac0d2010-08-02 18:56:54 +0000840 pass
841
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000842class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
843 def handle_expect_100(self):
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200844 self.send_error(HTTPStatus.EXPECTATION_FAILED)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000845 return False
846
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800847
848class AuditableBytesIO:
849
850 def __init__(self):
851 self.datas = []
852
853 def write(self, data):
854 self.datas.append(data)
855
856 def getData(self):
857 return b''.join(self.datas)
858
859 @property
860 def numWrites(self):
861 return len(self.datas)
862
863
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000864class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
Ezio Melotti3b3499b2011-03-16 11:35:38 +0200865 """Test the functionality of the BaseHTTPServer.
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000866
867 Test the support for the Expect 100-continue header.
868 """
869
870 HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
871
872 def setUp (self):
873 self.handler = SocketlessRequestHandler()
874
875 def send_typical_request(self, message):
876 input = BytesIO(message)
877 output = BytesIO()
878 self.handler.rfile = input
879 self.handler.wfile = output
880 self.handler.handle_one_request()
881 output.seek(0)
882 return output.readlines()
883
884 def verify_get_called(self):
885 self.assertTrue(self.handler.get_called)
886
887 def verify_expected_headers(self, headers):
888 for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
889 self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
890
891 def verify_http_server_response(self, response):
892 match = self.HTTPResponseMatch.search(response)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200893 self.assertIsNotNone(match)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000894
895 def test_http_1_1(self):
896 result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
897 self.verify_http_server_response(result[0])
898 self.verify_expected_headers(result[1:-1])
899 self.verify_get_called()
900 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500901 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
902 self.assertEqual(self.handler.command, 'GET')
903 self.assertEqual(self.handler.path, '/')
904 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
905 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000906
907 def test_http_1_0(self):
908 result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
909 self.verify_http_server_response(result[0])
910 self.verify_expected_headers(result[1:-1])
911 self.verify_get_called()
912 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500913 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
914 self.assertEqual(self.handler.command, 'GET')
915 self.assertEqual(self.handler.path, '/')
916 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
917 self.assertSequenceEqual(self.handler.headers.items(), ())
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000918
919 def test_http_0_9(self):
920 result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
921 self.assertEqual(len(result), 1)
922 self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
923 self.verify_get_called()
924
Martin Pantere82338d2016-11-19 01:06:37 +0000925 def test_extra_space(self):
926 result = self.send_typical_request(
927 b'GET /spaced out HTTP/1.1\r\n'
928 b'Host: dummy\r\n'
929 b'\r\n'
930 )
931 self.assertTrue(result[0].startswith(b'HTTP/1.1 400 '))
932 self.verify_expected_headers(result[1:result.index(b'\r\n')])
933 self.assertFalse(self.handler.get_called)
934
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000935 def test_with_continue_1_0(self):
936 result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
937 self.verify_http_server_response(result[0])
938 self.verify_expected_headers(result[1:-1])
939 self.verify_get_called()
940 self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
Benjamin Peterson70e28472015-02-17 21:11:10 -0500941 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.0')
942 self.assertEqual(self.handler.command, 'GET')
943 self.assertEqual(self.handler.path, '/')
944 self.assertEqual(self.handler.request_version, 'HTTP/1.0')
945 headers = (("Expect", "100-continue"),)
946 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000947
948 def test_with_continue_1_1(self):
949 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
950 self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
Benjamin Peterson04424232014-01-18 21:50:18 -0500951 self.assertEqual(result[1], b'\r\n')
952 self.assertEqual(result[2], b'HTTP/1.1 200 OK\r\n')
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000953 self.verify_expected_headers(result[2:-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.1')
957 self.assertEqual(self.handler.command, 'GET')
958 self.assertEqual(self.handler.path, '/')
959 self.assertEqual(self.handler.request_version, 'HTTP/1.1')
960 headers = (("Expect", "100-continue"),)
961 self.assertSequenceEqual(self.handler.headers.items(), headers)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000962
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800963 def test_header_buffering_of_send_error(self):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000964
965 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800966 output = AuditableBytesIO()
967 handler = SocketlessRequestHandler()
968 handler.rfile = input
969 handler.wfile = output
970 handler.request_version = 'HTTP/1.1'
971 handler.requestline = ''
972 handler.command = None
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000973
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800974 handler.send_error(418)
975 self.assertEqual(output.numWrites, 2)
976
977 def test_header_buffering_of_send_response_only(self):
978
979 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
980 output = AuditableBytesIO()
981 handler = SocketlessRequestHandler()
982 handler.rfile = input
983 handler.wfile = output
984 handler.request_version = 'HTTP/1.1'
985
986 handler.send_response_only(418)
987 self.assertEqual(output.numWrites, 0)
988 handler.end_headers()
989 self.assertEqual(output.numWrites, 1)
990
991 def test_header_buffering_of_send_header(self):
992
993 input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
994 output = AuditableBytesIO()
995 handler = SocketlessRequestHandler()
996 handler.rfile = input
997 handler.wfile = output
998 handler.request_version = 'HTTP/1.1'
999
1000 handler.send_header('Foo', 'foo')
1001 handler.send_header('bar', 'bar')
1002 self.assertEqual(output.numWrites, 0)
1003 handler.end_headers()
1004 self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
1005 self.assertEqual(output.numWrites, 1)
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001006
1007 def test_header_unbuffered_when_continue(self):
1008
1009 def _readAndReseek(f):
1010 pos = f.tell()
1011 f.seek(0)
1012 data = f.read()
1013 f.seek(pos)
1014 return data
1015
1016 input = BytesIO(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1017 output = BytesIO()
1018 self.handler.rfile = input
1019 self.handler.wfile = output
1020 self.handler.request_version = 'HTTP/1.1'
1021
1022 self.handler.handle_one_request()
1023 self.assertNotEqual(_readAndReseek(output), b'')
1024 result = _readAndReseek(output).split(b'\r\n')
1025 self.assertEqual(result[0], b'HTTP/1.1 100 Continue')
Benjamin Peterson04424232014-01-18 21:50:18 -05001026 self.assertEqual(result[1], b'')
1027 self.assertEqual(result[2], b'HTTP/1.1 200 OK')
Senthil Kumarane4dad4f2010-11-21 14:36:14 +00001028
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001029 def test_with_continue_rejected(self):
1030 usual_handler = self.handler # Save to avoid breaking any subsequent tests.
1031 self.handler = RejectingSocketlessRequestHandler()
1032 result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
1033 self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
1034 self.verify_expected_headers(result[1:-1])
1035 # The expect handler should short circuit the usual get method by
1036 # returning false here, so get_called should be false
1037 self.assertFalse(self.handler.get_called)
1038 self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
1039 self.handler = usual_handler # Restore to avoid breaking any subsequent tests.
1040
Antoine Pitrouc4924372010-12-16 16:48:36 +00001041 def test_request_length(self):
1042 # Issue #10714: huge request lines are discarded, to avoid Denial
1043 # of Service attacks.
1044 result = self.send_typical_request(b'GET ' + b'x' * 65537)
1045 self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
1046 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001047 self.assertIsInstance(self.handler.requestline, str)
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001048
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001049 def test_header_length(self):
1050 # Issue #6791: same for headers
1051 result = self.send_typical_request(
1052 b'GET / HTTP/1.1\r\nX-Foo: bar' + b'r' * 65537 + b'\r\n\r\n')
Martin Panter50badad2016-04-03 01:28:53 +00001053 self.assertEqual(result[0], b'HTTP/1.1 431 Line too long\r\n')
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001054 self.assertFalse(self.handler.get_called)
Benjamin Peterson70e28472015-02-17 21:11:10 -05001055 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1056
Martin Panteracc03192016-04-03 00:45:46 +00001057 def test_too_many_headers(self):
1058 result = self.send_typical_request(
1059 b'GET / HTTP/1.1\r\n' + b'X-Foo: bar\r\n' * 101 + b'\r\n')
1060 self.assertEqual(result[0], b'HTTP/1.1 431 Too many headers\r\n')
1061 self.assertFalse(self.handler.get_called)
1062 self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1')
1063
Martin Panterda3bb382016-04-11 00:40:08 +00001064 def test_html_escape_on_error(self):
1065 result = self.send_typical_request(
1066 b'<script>alert("hello")</script> / HTTP/1.1')
1067 result = b''.join(result)
1068 text = '<script>alert("hello")</script>'
1069 self.assertIn(html.escape(text, quote=False).encode('ascii'), result)
1070
Benjamin Peterson70e28472015-02-17 21:11:10 -05001071 def test_close_connection(self):
1072 # handle_one_request() should be repeatedly called until
1073 # it sets close_connection
1074 def handle_one_request():
1075 self.handler.close_connection = next(close_values)
1076 self.handler.handle_one_request = handle_one_request
1077
1078 close_values = iter((True,))
1079 self.handler.handle()
1080 self.assertRaises(StopIteration, next, close_values)
1081
1082 close_values = iter((False, False, True))
1083 self.handler.handle()
1084 self.assertRaises(StopIteration, next, close_values)
Senthil Kumaran5466bf12010-12-18 16:55:23 +00001085
Berker Peksag04bc5b92016-03-14 06:06:03 +02001086 def test_date_time_string(self):
1087 now = time.time()
1088 # this is the old code that formats the timestamp
1089 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
1090 expected = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
1091 self.handler.weekdayname[wd],
1092 day,
1093 self.handler.monthname[month],
1094 year, hh, mm, ss
1095 )
1096 self.assertEqual(self.handler.date_time_string(timestamp=now), expected)
1097
1098
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001099class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
1100 """ Test url parsing """
1101 def setUp(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001102 self.translated_1 = os.path.join(os.getcwd(), 'filename')
1103 self.translated_2 = os.path.join('foo', 'filename')
1104 self.translated_3 = os.path.join('bar', 'filename')
1105 self.handler_1 = SocketlessRequestHandler()
1106 self.handler_2 = SocketlessRequestHandler(directory='foo')
1107 self.handler_3 = SocketlessRequestHandler(directory=pathlib.PurePath('bar'))
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001108
1109 def test_query_arguments(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001110 path = self.handler_1.translate_path('/filename')
1111 self.assertEqual(path, self.translated_1)
1112 path = self.handler_2.translate_path('/filename')
1113 self.assertEqual(path, self.translated_2)
1114 path = self.handler_3.translate_path('/filename')
1115 self.assertEqual(path, self.translated_3)
1116
1117 path = self.handler_1.translate_path('/filename?foo=bar')
1118 self.assertEqual(path, self.translated_1)
1119 path = self.handler_2.translate_path('/filename?foo=bar')
1120 self.assertEqual(path, self.translated_2)
1121 path = self.handler_3.translate_path('/filename?foo=bar')
1122 self.assertEqual(path, self.translated_3)
1123
1124 path = self.handler_1.translate_path('/filename?a=b&spam=eggs#zot')
1125 self.assertEqual(path, self.translated_1)
1126 path = self.handler_2.translate_path('/filename?a=b&spam=eggs#zot')
1127 self.assertEqual(path, self.translated_2)
1128 path = self.handler_3.translate_path('/filename?a=b&spam=eggs#zot')
1129 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001130
1131 def test_start_with_double_slash(self):
Géry Ogam781266e2019-09-11 15:03:46 +02001132 path = self.handler_1.translate_path('//filename')
1133 self.assertEqual(path, self.translated_1)
1134 path = self.handler_2.translate_path('//filename')
1135 self.assertEqual(path, self.translated_2)
1136 path = self.handler_3.translate_path('//filename')
1137 self.assertEqual(path, self.translated_3)
1138
1139 path = self.handler_1.translate_path('//filename?foo=bar')
1140 self.assertEqual(path, self.translated_1)
1141 path = self.handler_2.translate_path('//filename?foo=bar')
1142 self.assertEqual(path, self.translated_2)
1143 path = self.handler_3.translate_path('//filename?foo=bar')
1144 self.assertEqual(path, self.translated_3)
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001145
Martin Panterd274b3f2016-04-18 03:45:18 +00001146 def test_windows_colon(self):
1147 with support.swap_attr(server.os, 'path', ntpath):
Géry Ogam781266e2019-09-11 15:03:46 +02001148 path = self.handler_1.translate_path('c:c:c:foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001149 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001150 self.assertEqual(path, self.translated_1)
1151 path = self.handler_2.translate_path('c:c:c:foo/filename')
1152 path = path.replace(ntpath.sep, os.sep)
1153 self.assertEqual(path, self.translated_2)
1154 path = self.handler_3.translate_path('c:c:c:foo/filename')
1155 path = path.replace(ntpath.sep, os.sep)
1156 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001157
Géry Ogam781266e2019-09-11 15:03:46 +02001158 path = self.handler_1.translate_path('\\c:../filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001159 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001160 self.assertEqual(path, self.translated_1)
1161 path = self.handler_2.translate_path('\\c:../filename')
1162 path = path.replace(ntpath.sep, os.sep)
1163 self.assertEqual(path, self.translated_2)
1164 path = self.handler_3.translate_path('\\c:../filename')
1165 path = path.replace(ntpath.sep, os.sep)
1166 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001167
Géry Ogam781266e2019-09-11 15:03:46 +02001168 path = self.handler_1.translate_path('c:\\c:..\\foo/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001169 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001170 self.assertEqual(path, self.translated_1)
1171 path = self.handler_2.translate_path('c:\\c:..\\foo/filename')
1172 path = path.replace(ntpath.sep, os.sep)
1173 self.assertEqual(path, self.translated_2)
1174 path = self.handler_3.translate_path('c:\\c:..\\foo/filename')
1175 path = path.replace(ntpath.sep, os.sep)
1176 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001177
Géry Ogam781266e2019-09-11 15:03:46 +02001178 path = self.handler_1.translate_path('c:c:foo\\c:c:bar/filename')
Martin Panterd274b3f2016-04-18 03:45:18 +00001179 path = path.replace(ntpath.sep, os.sep)
Géry Ogam781266e2019-09-11 15:03:46 +02001180 self.assertEqual(path, self.translated_1)
1181 path = self.handler_2.translate_path('c:c:foo\\c:c:bar/filename')
1182 path = path.replace(ntpath.sep, os.sep)
1183 self.assertEqual(path, self.translated_2)
1184 path = self.handler_3.translate_path('c:c:foo\\c:c:bar/filename')
1185 path = path.replace(ntpath.sep, os.sep)
1186 self.assertEqual(path, self.translated_3)
Martin Panterd274b3f2016-04-18 03:45:18 +00001187
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001188
Berker Peksag366c5702015-02-13 20:48:15 +02001189class MiscTestCase(unittest.TestCase):
1190 def test_all(self):
1191 expected = []
1192 blacklist = {'executable', 'nobody_uid', 'test'}
1193 for name in dir(server):
1194 if name.startswith('_') or name in blacklist:
1195 continue
1196 module_object = getattr(server, name)
1197 if getattr(module_object, '__module__', None) == 'http.server':
1198 expected.append(name)
1199 self.assertCountEqual(server.__all__, expected)
1200
1201
Lisa Roach433433f2018-11-26 10:43:38 -08001202class ScriptTestCase(unittest.TestCase):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001203
1204 def mock_server_class(self):
1205 return mock.MagicMock(
1206 return_value=mock.MagicMock(
1207 __enter__=mock.MagicMock(
1208 return_value=mock.MagicMock(
1209 socket=mock.MagicMock(
1210 getsockname=lambda: ('', 0),
1211 ),
1212 ),
1213 ),
1214 ),
1215 )
1216
1217 @mock.patch('builtins.print')
1218 def test_server_test_unspec(self, _):
1219 mock_server = self.mock_server_class()
1220 server.test(ServerClass=mock_server, bind=None)
1221 self.assertIn(
1222 mock_server.address_family,
1223 (socket.AF_INET6, socket.AF_INET),
1224 )
1225
1226 @mock.patch('builtins.print')
1227 def test_server_test_localhost(self, _):
1228 mock_server = self.mock_server_class()
1229 server.test(ServerClass=mock_server, bind="localhost")
1230 self.assertIn(
1231 mock_server.address_family,
1232 (socket.AF_INET6, socket.AF_INET),
1233 )
1234
1235 ipv6_addrs = (
1236 "::",
1237 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
1238 "::1",
1239 )
1240
1241 ipv4_addrs = (
1242 "0.0.0.0",
1243 "8.8.8.8",
1244 "127.0.0.1",
1245 )
1246
Lisa Roach433433f2018-11-26 10:43:38 -08001247 @mock.patch('builtins.print')
1248 def test_server_test_ipv6(self, _):
Jason R. Coombsf2890842019-02-07 08:22:45 -05001249 for bind in self.ipv6_addrs:
1250 mock_server = self.mock_server_class()
1251 server.test(ServerClass=mock_server, bind=bind)
1252 self.assertEqual(mock_server.address_family, socket.AF_INET6)
Lisa Roach433433f2018-11-26 10:43:38 -08001253
Jason R. Coombsf2890842019-02-07 08:22:45 -05001254 @mock.patch('builtins.print')
1255 def test_server_test_ipv4(self, _):
1256 for bind in self.ipv4_addrs:
1257 mock_server = self.mock_server_class()
1258 server.test(ServerClass=mock_server, bind=bind)
1259 self.assertEqual(mock_server.address_family, socket.AF_INET)
Lisa Roach433433f2018-11-26 10:43:38 -08001260
1261
Georg Brandlb533e262008-05-25 18:19:30 +00001262def test_main(verbose=None):
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001263 cwd = os.getcwd()
Georg Brandlb533e262008-05-25 18:19:30 +00001264 try:
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001265 support.run_unittest(
Serhiy Storchakac0a23e62015-03-07 11:51:37 +02001266 RequestHandlerLoggingTestCase,
Senthil Kumaran0f476d42010-09-30 06:09:18 +00001267 BaseHTTPRequestHandlerTestCase,
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001268 BaseHTTPServerTestCase,
1269 SimpleHTTPServerTestCase,
1270 CGIHTTPServerTestCase,
1271 SimpleHTTPRequestHandlerTestCase,
Berker Peksag366c5702015-02-13 20:48:15 +02001272 MiscTestCase,
Lisa Roach433433f2018-11-26 10:43:38 -08001273 ScriptTestCase
Georg Brandl6fcac0d2010-08-02 18:56:54 +00001274 )
Georg Brandlb533e262008-05-25 18:19:30 +00001275 finally:
1276 os.chdir(cwd)
1277
1278if __name__ == '__main__':
1279 test_main()