blob: 42432bfbd260e730ce1dc61b0de0b350be6bedaf [file] [log] [blame]
Martin Panter50dd1f72016-04-17 02:17:03 +00001from unittest import mock
Martin Pantered0425c2016-06-05 06:28:55 +00002from test import support
3from test.test_httpservers import NoLogRequestHandler
Guido van Rossumd8faa362007-04-27 19:54:29 +00004from unittest import TestCase
Thomas Wouters0e3f5912006-08-11 14:57:12 +00005from wsgiref.util import setup_testing_defaults
6from wsgiref.headers import Headers
Martin Pantered0425c2016-06-05 06:28:55 +00007from wsgiref.handlers import BaseHandler, BaseCGIHandler, SimpleHandler
Thomas Wouters0e3f5912006-08-11 14:57:12 +00008from wsgiref import util
9from wsgiref.validate import validator
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -070010from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
Thomas Wouters0e3f5912006-08-11 14:57:12 +000011from wsgiref.simple_server import make_server
Martin Pantered0425c2016-06-05 06:28:55 +000012from http.client import HTTPConnection
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000013from io import StringIO, BytesIO, BufferedReader
Alexandre Vassalottice261952008-05-12 02:31:37 +000014from socketserver import BaseServer
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -070015from platform import python_implementation
16
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000017import os
18import re
Martin Pantered0425c2016-06-05 06:28:55 +000019import signal
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000020import sys
Antoine Pitrou88c60c92017-09-18 23:50:44 +020021import threading
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -070022import unittest
Thomas Wouters0e3f5912006-08-11 14:57:12 +000023
24
25class MockServer(WSGIServer):
26 """Non-socket HTTP server"""
27
28 def __init__(self, server_address, RequestHandlerClass):
29 BaseServer.__init__(self, server_address, RequestHandlerClass)
30 self.server_bind()
31
32 def server_bind(self):
33 host, port = self.server_address
34 self.server_name = host
35 self.server_port = port
36 self.setup_environ()
37
38
39class MockHandler(WSGIRequestHandler):
40 """Non-socket HTTP handler"""
41 def setup(self):
42 self.connection = self.request
43 self.rfile, self.wfile = self.connection
44
45 def finish(self):
46 pass
47
48
Thomas Wouters0e3f5912006-08-11 14:57:12 +000049def hello_app(environ,start_response):
50 start_response("200 OK", [
51 ('Content-Type','text/plain'),
52 ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
53 ])
Phillip J. Ebye1594222010-11-02 22:28:59 +000054 return [b"Hello, world!"]
Thomas Wouters0e3f5912006-08-11 14:57:12 +000055
Berker Peksag659f6312015-03-02 06:53:33 +020056
57def header_app(environ, start_response):
58 start_response("200 OK", [
59 ('Content-Type', 'text/plain'),
60 ('Date', 'Mon, 05 Jun 2006 18:49:54 GMT')
61 ])
62 return [';'.join([
63 environ['HTTP_X_TEST_HEADER'], environ['QUERY_STRING'],
64 environ['PATH_INFO']
65 ]).encode('iso-8859-1')]
66
67
Guido van Rossum6a10e022007-08-08 17:01:45 +000068def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
Thomas Wouters0e3f5912006-08-11 14:57:12 +000069 server = make_server("", 80, app, MockServer, MockHandler)
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000070 inp = BufferedReader(BytesIO(data))
Antoine Pitrou38a66ad2009-01-03 18:41:49 +000071 out = BytesIO()
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000072 olderr = sys.stderr
73 err = sys.stderr = StringIO()
Thomas Wouters0e3f5912006-08-11 14:57:12 +000074
75 try:
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000076 server.finish_request((inp, out), ("127.0.0.1",8888))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000077 finally:
78 sys.stderr = olderr
79
80 return out.getvalue(), err.getvalue()
81
Thomas Wouters0e3f5912006-08-11 14:57:12 +000082def compare_generic_iter(make_it,match):
83 """Utility to compare a generic 2.1/2.2+ iterator with an iterable
84
85 If running under Python 2.2+, this tests the iterator using iter()/next(),
86 as well as __getitem__. 'make_it' must be a function returning a fresh
87 iterator to be tested (since this may test the iterator twice)."""
88
89 it = make_it()
90 n = 0
91 for item in match:
92 if not it[n]==item: raise AssertionError
93 n+=1
94 try:
95 it[n]
96 except IndexError:
97 pass
98 else:
99 raise AssertionError("Too many items from __getitem__",it)
100
101 try:
102 iter, StopIteration
103 except NameError:
104 pass
105 else:
106 # Only test iter mode under 2.2+
107 it = make_it()
108 if not iter(it) is it: raise AssertionError
109 for item in match:
Georg Brandla18af4e2007-04-21 15:47:16 +0000110 if not next(it) == item: raise AssertionError
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000111 try:
Georg Brandla18af4e2007-04-21 15:47:16 +0000112 next(it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 except StopIteration:
114 pass
115 else:
Georg Brandla18af4e2007-04-21 15:47:16 +0000116 raise AssertionError("Too many items from .__next__()", it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000117
118
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000119class IntegrationTests(TestCase):
120
121 def check_hello(self, out, has_length=True):
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700122 pyver = (python_implementation() + "/" +
123 sys.version.split()[0])
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000124 self.assertEqual(out,
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000125 ("HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700126 "Server: WSGIServer/0.2 " + pyver +"\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 "Content-Type: text/plain\r\n"
128 "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
129 (has_length and "Content-Length: 13\r\n" or "") +
130 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000131 "Hello, world!").encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000132 )
133
134 def test_plain_hello(self):
135 out, err = run_amock()
136 self.check_hello(out)
137
Berker Peksag659f6312015-03-02 06:53:33 +0200138 def test_environ(self):
139 request = (
140 b"GET /p%61th/?query=test HTTP/1.0\n"
141 b"X-Test-Header: Python test \n"
142 b"X-Test-Header: Python test 2\n"
143 b"Content-Length: 0\n\n"
144 )
145 out, err = run_amock(header_app, request)
146 self.assertEqual(
147 out.splitlines()[-1],
148 b"Python test,Python test 2;query=test;/path/"
149 )
150
Senthil Kumarane025b522014-09-17 16:29:29 +0800151 def test_request_length(self):
152 out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n")
153 self.assertEqual(out.splitlines()[0],
154 b"HTTP/1.0 414 Request-URI Too Long")
155
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000156 def test_validated_hello(self):
157 out, err = run_amock(validator(hello_app))
158 # the middleware doesn't support len(), so content-length isn't there
159 self.check_hello(out, has_length=False)
160
161 def test_simple_validation_error(self):
162 def bad_app(environ,start_response):
163 start_response("200 OK", ('Content-Type','text/plain'))
164 return ["Hello, world!"]
165 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000166 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000167 b"A server error occurred. Please contact the administrator."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 ))
Benjamin Petersonab078e92016-07-13 21:13:29 -0700169 self.assertEqual(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000170 err.splitlines()[-2],
Benjamin Petersonab078e92016-07-13 21:13:29 -0700171 "AssertionError: Headers (('Content-Type', 'text/plain')) must"
172 " be of type list: <class 'tuple'>"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000173 )
174
Berker Peksag1cd4ff62016-03-19 09:04:59 +0200175 def test_status_validation_errors(self):
176 def create_bad_app(status):
177 def bad_app(environ, start_response):
178 start_response(status, [("Content-Type", "text/plain; charset=utf-8")])
179 return [b"Hello, world!"]
180 return bad_app
181
182 tests = [
183 ('200', 'AssertionError: Status must be at least 4 characters'),
184 ('20X OK', 'AssertionError: Status message must begin w/3-digit code'),
185 ('200OK', 'AssertionError: Status message must have a space after code'),
186 ]
187
188 for status, exc_message in tests:
189 with self.subTest(status=status):
190 out, err = run_amock(create_bad_app(status))
191 self.assertTrue(out.endswith(
192 b"A server error occurred. Please contact the administrator."
193 ))
194 self.assertEqual(err.splitlines()[-2], exc_message)
195
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000196 def test_wsgi_input(self):
197 def bad_app(e,s):
198 e["wsgi.input"].read()
Phillip J. Ebye1594222010-11-02 22:28:59 +0000199 s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000200 return [b"data"]
201 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000202 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000203 b"A server error occurred. Please contact the administrator."
204 ))
205 self.assertEqual(
206 err.splitlines()[-2], "AssertionError"
207 )
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000208
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000209 def test_bytes_validation(self):
210 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000211 s("200 OK", [
212 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000213 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
214 ])
215 return [b"data"]
216 out, err = run_amock(validator(app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000217 self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
Antoine Pitrou5f817412009-01-03 18:49:41 +0000218 ver = sys.version.split()[0].encode('ascii')
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700219 py = python_implementation().encode('ascii')
220 pyver = py + b"/" + ver
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000221 self.assertEqual(
222 b"HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700223 b"Server: WSGIServer/0.2 "+ pyver + b"\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000224 b"Content-Type: text/plain; charset=utf-8\r\n"
225 b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
226 b"\r\n"
227 b"data",
228 out)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000229
Martin Panter50dd1f72016-04-17 02:17:03 +0000230 def test_cp1252_url(self):
231 def app(e, s):
232 s("200 OK", [
233 ("Content-Type", "text/plain"),
234 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
235 ])
236 # PEP3333 says environ variables are decoded as latin1.
237 # Encode as latin1 to get original bytes
238 return [e["PATH_INFO"].encode("latin1")]
239
240 out, err = run_amock(
241 validator(app), data=b"GET /\x80%80 HTTP/1.0")
242 self.assertEqual(
243 [
244 b"HTTP/1.0 200 OK",
245 mock.ANY,
246 b"Content-Type: text/plain",
247 b"Date: Wed, 24 Dec 2008 13:29:32 GMT",
248 b"",
249 b"/\x80\x80",
250 ],
251 out.splitlines())
252
Martin Pantered0425c2016-06-05 06:28:55 +0000253 def test_interrupted_write(self):
254 # BaseHandler._write() and _flush() have to write all data, even if
255 # it takes multiple send() calls. Test this by interrupting a send()
256 # call with a Unix signal.
Martin Pantered0425c2016-06-05 06:28:55 +0000257 pthread_kill = support.get_attribute(signal, "pthread_kill")
258
259 def app(environ, start_response):
260 start_response("200 OK", [])
Serhiy Storchaka5f1a5182016-09-11 14:41:02 +0300261 return [b'\0' * support.SOCK_MAX_SIZE]
Martin Pantered0425c2016-06-05 06:28:55 +0000262
263 class WsgiHandler(NoLogRequestHandler, WSGIRequestHandler):
264 pass
265
266 server = make_server(support.HOST, 0, app, handler_class=WsgiHandler)
267 self.addCleanup(server.server_close)
268 interrupted = threading.Event()
269
270 def signal_handler(signum, frame):
271 interrupted.set()
272
273 original = signal.signal(signal.SIGUSR1, signal_handler)
274 self.addCleanup(signal.signal, signal.SIGUSR1, original)
275 received = None
276 main_thread = threading.get_ident()
277
278 def run_client():
279 http = HTTPConnection(*server.server_address)
280 http.request("GET", "/")
281 with http.getresponse() as response:
282 response.read(100)
283 # The main thread should now be blocking in a send() system
284 # call. But in theory, it could get interrupted by other
285 # signals, and then retried. So keep sending the signal in a
286 # loop, in case an earlier signal happens to be delivered at
287 # an inconvenient moment.
288 while True:
289 pthread_kill(main_thread, signal.SIGUSR1)
290 if interrupted.wait(timeout=float(1)):
291 break
292 nonlocal received
293 received = len(response.read())
294 http.close()
295
296 background = threading.Thread(target=run_client)
297 background.start()
298 server.handle_request()
299 background.join()
300 self.assertEqual(received, support.SOCK_MAX_SIZE - 100)
301
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000302
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000303class UtilityTests(TestCase):
304
305 def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
306 env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
307 util.setup_testing_defaults(env)
308 self.assertEqual(util.shift_path_info(env),part)
309 self.assertEqual(env['PATH_INFO'],pi_out)
310 self.assertEqual(env['SCRIPT_NAME'],sn_out)
311 return env
312
313 def checkDefault(self, key, value, alt=None):
314 # Check defaulting when empty
315 env = {}
316 util.setup_testing_defaults(env)
Ezio Melottie9615932010-01-24 19:26:24 +0000317 if isinstance(value, StringIO):
318 self.assertIsInstance(env[key], StringIO)
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000319 elif isinstance(value,BytesIO):
Ezio Melottie9615932010-01-24 19:26:24 +0000320 self.assertIsInstance(env[key],BytesIO)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000321 else:
Ezio Melottie9615932010-01-24 19:26:24 +0000322 self.assertEqual(env[key], value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000323
324 # Check existing value
325 env = {key:alt}
326 util.setup_testing_defaults(env)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200327 self.assertIs(env[key], alt)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000328
329 def checkCrossDefault(self,key,value,**kw):
330 util.setup_testing_defaults(kw)
331 self.assertEqual(kw[key],value)
332
333 def checkAppURI(self,uri,**kw):
334 util.setup_testing_defaults(kw)
335 self.assertEqual(util.application_uri(kw),uri)
336
337 def checkReqURI(self,uri,query=1,**kw):
338 util.setup_testing_defaults(kw)
339 self.assertEqual(util.request_uri(kw,query),uri)
340
Berker Peksag84a13fb2018-08-11 09:05:04 +0300341 @support.ignore_warnings(category=DeprecationWarning)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000342 def checkFW(self,text,size,match):
343
344 def make_it(text=text,size=size):
345 return util.FileWrapper(StringIO(text),size)
346
347 compare_generic_iter(make_it,match)
348
349 it = make_it()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000350 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000351
352 for item in it:
353 pass
354
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000355 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000356
357 it.close()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000358 self.assertTrue(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000359
Berker Peksag84a13fb2018-08-11 09:05:04 +0300360 def test_filewrapper_getitem_deprecation(self):
361 wrapper = util.FileWrapper(StringIO('foobar'), 3)
362 with self.assertWarnsRegex(DeprecationWarning,
363 r'Use iterator protocol instead'):
364 # This should have returned 'bar'.
365 self.assertEqual(wrapper[1], 'foo')
366
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000367 def testSimpleShifts(self):
368 self.checkShift('','/', '', '/', '')
369 self.checkShift('','/x', 'x', '/x', '')
370 self.checkShift('/','', None, '/', '')
371 self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
372 self.checkShift('/a','/x/', 'x', '/a/x', '/')
373
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000374 def testNormalizedShifts(self):
375 self.checkShift('/a/b', '/../y', '..', '/a', '/y')
376 self.checkShift('', '/../y', '..', '', '/y')
377 self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
378 self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
379 self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
380 self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
381 self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
382 self.checkShift('/a/b', '///', '', '/a/b/', '')
383 self.checkShift('/a/b', '/.//', '', '/a/b/', '')
384 self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
385 self.checkShift('/a/b', '/.', None, '/a/b', '')
386
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000387 def testDefaults(self):
388 for key, value in [
389 ('SERVER_NAME','127.0.0.1'),
390 ('SERVER_PORT', '80'),
391 ('SERVER_PROTOCOL','HTTP/1.0'),
392 ('HTTP_HOST','127.0.0.1'),
393 ('REQUEST_METHOD','GET'),
394 ('SCRIPT_NAME',''),
395 ('PATH_INFO','/'),
396 ('wsgi.version', (1,0)),
397 ('wsgi.run_once', 0),
398 ('wsgi.multithread', 0),
399 ('wsgi.multiprocess', 0),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000400 ('wsgi.input', BytesIO()),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000401 ('wsgi.errors', StringIO()),
402 ('wsgi.url_scheme','http'),
403 ]:
404 self.checkDefault(key,value)
405
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000406 def testCrossDefaults(self):
407 self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
408 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
409 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
410 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
411 self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
412 self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
413 self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
414
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000415 def testGuessScheme(self):
416 self.assertEqual(util.guess_scheme({}), "http")
417 self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
418 self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
419 self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
420 self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
421
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422 def testAppURIs(self):
423 self.checkAppURI("http://127.0.0.1/")
424 self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200425 self.checkAppURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000426 self.checkAppURI("http://spam.example.com:2071/",
427 HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
428 self.checkAppURI("http://spam.example.com/",
429 SERVER_NAME="spam.example.com")
430 self.checkAppURI("http://127.0.0.1/",
431 HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
432 self.checkAppURI("https://127.0.0.1/", HTTPS="on")
433 self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
434 HTTP_HOST=None)
435
436 def testReqURIs(self):
437 self.checkReqURI("http://127.0.0.1/")
438 self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200439 self.checkReqURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000440 self.checkReqURI("http://127.0.0.1/spammity/spam",
441 SCRIPT_NAME="/spammity", PATH_INFO="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200442 self.checkReqURI("http://127.0.0.1/spammity/sp%E4m",
443 SCRIPT_NAME="/spammity", PATH_INFO="/sp\xe4m")
Senthil Kumaran299fa4c2010-12-29 06:25:42 +0000444 self.checkReqURI("http://127.0.0.1/spammity/spam;ham",
445 SCRIPT_NAME="/spammity", PATH_INFO="/spam;ham")
446 self.checkReqURI("http://127.0.0.1/spammity/spam;cookie=1234,5678",
447 SCRIPT_NAME="/spammity", PATH_INFO="/spam;cookie=1234,5678")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000448 self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
449 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200450 self.checkReqURI("http://127.0.0.1/spammity/spam?s%E4y=ni",
451 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="s%E4y=ni")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000452 self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
453 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
454
455 def testFileWrapper(self):
456 self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
457
458 def testHopByHop(self):
459 for hop in (
460 "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
461 "TE Trailers Transfer-Encoding Upgrade"
462 ).split():
463 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000464 self.assertTrue(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000465
466 # Not comprehensive, just a few random header names
467 for hop in (
468 "Accept Cache-Control Date Pragma Trailer Via Warning"
469 ).split():
470 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000471 self.assertFalse(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000472
473class HeaderTests(TestCase):
474
475 def testMappingInterface(self):
476 test = [('x','y')]
Berker Peksag3e887222014-07-02 08:37:22 +0300477 self.assertEqual(len(Headers()), 0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000478 self.assertEqual(len(Headers([])),0)
479 self.assertEqual(len(Headers(test[:])),1)
480 self.assertEqual(Headers(test[:]).keys(), ['x'])
481 self.assertEqual(Headers(test[:]).values(), ['y'])
482 self.assertEqual(Headers(test[:]).items(), test)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200483 self.assertIsNot(Headers(test).items(), test) # must be copy!
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000484
Berker Peksag3e887222014-07-02 08:37:22 +0300485 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000486 del h['foo'] # should not raise an error
487
488 h['Foo'] = 'bar'
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000489 for m in h.__contains__, h.get, h.get_all, h.__getitem__:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000490 self.assertTrue(m('foo'))
491 self.assertTrue(m('Foo'))
492 self.assertTrue(m('FOO'))
493 self.assertFalse(m('bar'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000494
495 self.assertEqual(h['foo'],'bar')
496 h['foo'] = 'baz'
497 self.assertEqual(h['FOO'],'baz')
498 self.assertEqual(h.get_all('foo'),['baz'])
499
500 self.assertEqual(h.get("foo","whee"), "baz")
501 self.assertEqual(h.get("zoo","whee"), "whee")
502 self.assertEqual(h.setdefault("foo","whee"), "baz")
503 self.assertEqual(h.setdefault("zoo","whee"), "whee")
504 self.assertEqual(h["foo"],"baz")
505 self.assertEqual(h["zoo"],"whee")
506
507 def testRequireList(self):
508 self.assertRaises(TypeError, Headers, "foo")
509
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000510 def testExtras(self):
Berker Peksag3e887222014-07-02 08:37:22 +0300511 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000512 self.assertEqual(str(h),'\r\n')
513
514 h.add_header('foo','bar',baz="spam")
515 self.assertEqual(h['foo'], 'bar; baz="spam"')
516 self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
517
518 h.add_header('Foo','bar',cheese=None)
519 self.assertEqual(h.get_all('foo'),
520 ['bar; baz="spam"', 'bar; cheese'])
521
522 self.assertEqual(str(h),
523 'foo: bar; baz="spam"\r\n'
524 'Foo: bar; cheese\r\n'
525 '\r\n'
526 )
527
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000528class ErrorHandler(BaseCGIHandler):
529 """Simple handler subclass for testing BaseHandler"""
530
Antoine Pitrou8f5731b2009-11-03 17:20:10 +0000531 # BaseHandler records the OS environment at import time, but envvars
532 # might have been changed later by other tests, which trips up
533 # HandlerTests.testEnviron().
534 os_environ = dict(os.environ.items())
535
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000536 def __init__(self,**kw):
537 setup_testing_defaults(kw)
538 BaseCGIHandler.__init__(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000539 self, BytesIO(), BytesIO(), StringIO(), kw,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000540 multithread=True, multiprocess=True
541 )
542
543class TestHandler(ErrorHandler):
544 """Simple handler subclass for testing BaseHandler, w/error passthru"""
545
546 def handle_error(self):
547 raise # for testing, we want to see what's happening
548
549
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000550class HandlerTests(TestCase):
551
552 def checkEnvironAttrs(self, handler):
553 env = handler.environ
554 for attr in [
555 'version','multithread','multiprocess','run_once','file_wrapper'
556 ]:
557 if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
558 continue
559 self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
560
561 def checkOSEnviron(self,handler):
562 empty = {}; setup_testing_defaults(empty)
563 env = handler.environ
564 from os import environ
565 for k,v in environ.items():
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000566 if k not in empty:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000567 self.assertEqual(env[k],v)
568 for k,v in empty.items():
Benjamin Peterson577473f2010-01-19 00:09:57 +0000569 self.assertIn(k, env)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000570
571 def testEnviron(self):
572 h = TestHandler(X="Y")
573 h.setup_environ()
574 self.checkEnvironAttrs(h)
575 self.checkOSEnviron(h)
576 self.assertEqual(h.environ["X"],"Y")
577
578 def testCGIEnviron(self):
579 h = BaseCGIHandler(None,None,None,{})
580 h.setup_environ()
581 for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
Benjamin Peterson577473f2010-01-19 00:09:57 +0000582 self.assertIn(key, h.environ)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000583
584 def testScheme(self):
585 h=TestHandler(HTTPS="on"); h.setup_environ()
586 self.assertEqual(h.environ['wsgi.url_scheme'],'https')
587 h=TestHandler(); h.setup_environ()
588 self.assertEqual(h.environ['wsgi.url_scheme'],'http')
589
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000590 def testAbstractMethods(self):
591 h = BaseHandler()
592 for name in [
593 '_flush','get_stdin','get_stderr','add_cgi_vars'
594 ]:
595 self.assertRaises(NotImplementedError, getattr(h,name))
596 self.assertRaises(NotImplementedError, h._write, "test")
597
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000598 def testContentLength(self):
599 # Demo one reason iteration is better than write()... ;)
600
601 def trivial_app1(e,s):
602 s('200 OK',[])
Phillip J. Ebye1594222010-11-02 22:28:59 +0000603 return [e['wsgi.url_scheme'].encode('iso-8859-1')]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000604
605 def trivial_app2(e,s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000606 s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000607 return []
608
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000609 def trivial_app3(e,s):
610 s('200 OK',[])
611 return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
612
Antoine Pitroub715fac2011-01-06 17:17:04 +0000613 def trivial_app4(e,s):
614 # Simulate a response to a HEAD request
615 s('200 OK',[('Content-Length', '12345')])
616 return []
617
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000618 h = TestHandler()
619 h.run(trivial_app1)
620 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000621 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000622 "Content-Length: 4\r\n"
623 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000624 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000625
626 h = TestHandler()
627 h.run(trivial_app2)
628 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000629 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000630 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000631 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000632
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000633 h = TestHandler()
634 h.run(trivial_app3)
635 self.assertEqual(h.stdout.getvalue(),
636 b'Status: 200 OK\r\n'
637 b'Content-Length: 8\r\n'
638 b'\r\n'
639 b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000640
Antoine Pitroub715fac2011-01-06 17:17:04 +0000641 h = TestHandler()
642 h.run(trivial_app4)
643 self.assertEqual(h.stdout.getvalue(),
644 b'Status: 200 OK\r\n'
645 b'Content-Length: 12345\r\n'
646 b'\r\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000647
648 def testBasicErrorOutput(self):
649
650 def non_error_app(e,s):
651 s('200 OK',[])
652 return []
653
654 def error_app(e,s):
655 raise AssertionError("This should be caught by handler")
656
657 h = ErrorHandler()
658 h.run(non_error_app)
659 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000660 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000661 "Content-Length: 0\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000662 "\r\n").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000663 self.assertEqual(h.stderr.getvalue(),"")
664
665 h = ErrorHandler()
666 h.run(error_app)
667 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000668 ("Status: %s\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000669 "Content-Type: text/plain\r\n"
670 "Content-Length: %d\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000671 "\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
672 + h.error_body)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000673
Benjamin Peterson577473f2010-01-19 00:09:57 +0000674 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000675
676 def testErrorAfterOutput(self):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000677 MSG = b"Some output has been sent"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000678 def error_app(e,s):
679 s("200 OK",[])(MSG)
680 raise AssertionError("This should be caught by handler")
681
682 h = ErrorHandler()
683 h.run(error_app)
684 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000685 ("Status: 200 OK\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000686 "\r\n".encode("iso-8859-1")+MSG))
Benjamin Peterson577473f2010-01-19 00:09:57 +0000687 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000688
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 def testHeaderFormats(self):
690
691 def non_error_app(e,s):
692 s('200 OK',[])
693 return []
694
695 stdpat = (
696 r"HTTP/%s 200 OK\r\n"
697 r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
698 r"%s" r"Content-Length: 0\r\n" r"\r\n"
699 )
700 shortpat = (
701 "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000702 ).encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000703
704 for ssw in "FooBar/1.0", None:
705 sw = ssw and "Server: %s\r\n" % ssw or ""
706
707 for version in "1.0", "1.1":
708 for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
709
710 h = TestHandler(SERVER_PROTOCOL=proto)
711 h.origin_server = False
712 h.http_version = version
713 h.server_software = ssw
714 h.run(non_error_app)
715 self.assertEqual(shortpat,h.stdout.getvalue())
716
717 h = TestHandler(SERVER_PROTOCOL=proto)
718 h.origin_server = True
719 h.http_version = version
720 h.server_software = ssw
721 h.run(non_error_app)
722 if proto=="HTTP/0.9":
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000723 self.assertEqual(h.stdout.getvalue(),b"")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000724 else:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000725 self.assertTrue(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000726 re.match((stdpat%(version,sw)).encode("iso-8859-1"),
727 h.stdout.getvalue()),
728 ((stdpat%(version,sw)).encode("iso-8859-1"),
729 h.stdout.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000730 )
731
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000732 def testBytesData(self):
733 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000734 s("200 OK", [
735 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000736 ])
737 return [b"data"]
738
739 h = TestHandler()
740 h.run(app)
741 self.assertEqual(b"Status: 200 OK\r\n"
742 b"Content-Type: text/plain; charset=utf-8\r\n"
743 b"Content-Length: 4\r\n"
744 b"\r\n"
745 b"data",
746 h.stdout.getvalue())
747
Antoine Pitrouae247a52012-10-21 14:09:05 +0200748 def testCloseOnError(self):
749 side_effects = {'close_called': False}
750 MSG = b"Some output has been sent"
751 def error_app(e,s):
752 s("200 OK",[])(MSG)
753 class CrashyIterable(object):
754 def __iter__(self):
755 while True:
756 yield b'blah'
757 raise AssertionError("This should be caught by handler")
758 def close(self):
759 side_effects['close_called'] = True
760 return CrashyIterable()
761
762 h = ErrorHandler()
763 h.run(error_app)
764 self.assertEqual(side_effects['close_called'], True)
765
Martin Pantered0425c2016-06-05 06:28:55 +0000766 def testPartialWrite(self):
767 written = bytearray()
768
769 class PartialWriter:
770 def write(self, b):
771 partial = b[:7]
772 written.extend(partial)
773 return len(partial)
774
775 def flush(self):
776 pass
777
778 environ = {"SERVER_PROTOCOL": "HTTP/1.0"}
779 h = SimpleHandler(BytesIO(), PartialWriter(), sys.stderr, environ)
780 msg = "should not do partial writes"
781 with self.assertWarnsRegex(DeprecationWarning, msg):
782 h.run(hello_app)
783 self.assertEqual(b"HTTP/1.0 200 OK\r\n"
784 b"Content-Type: text/plain\r\n"
785 b"Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n"
786 b"Content-Length: 13\r\n"
787 b"\r\n"
788 b"Hello, world!",
789 written)
790
Petter Strandmark3d37ea22019-05-01 19:32:15 +0200791 def testClientConnectionTerminations(self):
792 environ = {"SERVER_PROTOCOL": "HTTP/1.0"}
793 for exception in (
794 ConnectionAbortedError,
795 BrokenPipeError,
796 ConnectionResetError,
797 ):
798 with self.subTest(exception=exception):
799 class AbortingWriter:
800 def write(self, b):
801 raise exception
802
803 stderr = StringIO()
804 h = SimpleHandler(BytesIO(), AbortingWriter(), stderr, environ)
805 h.run(hello_app)
806
807 self.assertFalse(stderr.getvalue())
808
Berker Peksag7c593622019-05-19 18:56:15 +0300809 def testDontResetInternalStateOnException(self):
810 class CustomException(ValueError):
811 pass
812
813 # We are raising CustomException here to trigger an exception
814 # during the execution of SimpleHandler.finish_response(), so
815 # we can easily test that the internal state of the handler is
816 # preserved in case of an exception.
817 class AbortingWriter:
818 def write(self, b):
819 raise CustomException
820
821 stderr = StringIO()
822 environ = {"SERVER_PROTOCOL": "HTTP/1.0"}
823 h = SimpleHandler(BytesIO(), AbortingWriter(), stderr, environ)
824 h.run(hello_app)
825
826 self.assertIn("CustomException", stderr.getvalue())
827
828 # Test that the internal state of the handler is preserved.
829 self.assertIsNotNone(h.result)
830 self.assertIsNotNone(h.headers)
831 self.assertIsNotNone(h.status)
832 self.assertIsNotNone(h.environ)
833
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000834
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000835if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500836 unittest.main()