blob: 3f800eff2d66d0e9731330ba76ae634b56cd712c [file] [log] [blame]
Guido van Rossumd8faa362007-04-27 19:54:29 +00001from unittest import TestCase
Thomas Wouters0e3f5912006-08-11 14:57:12 +00002from wsgiref.util import setup_testing_defaults
3from wsgiref.headers import Headers
4from wsgiref.handlers import BaseHandler, BaseCGIHandler
5from wsgiref import util
6from wsgiref.validate import validator
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -07007from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
Thomas Wouters0e3f5912006-08-11 14:57:12 +00008from wsgiref.simple_server import make_server
Jeremy Hyltone6b59c52007-08-10 19:13:33 +00009from io import StringIO, BytesIO, BufferedReader
Alexandre Vassalottice261952008-05-12 02:31:37 +000010from socketserver import BaseServer
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -070011from platform import python_implementation
12
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000013import os
14import re
15import sys
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -070016import unittest
Thomas Wouters0e3f5912006-08-11 14:57:12 +000017
18
19class MockServer(WSGIServer):
20 """Non-socket HTTP server"""
21
22 def __init__(self, server_address, RequestHandlerClass):
23 BaseServer.__init__(self, server_address, RequestHandlerClass)
24 self.server_bind()
25
26 def server_bind(self):
27 host, port = self.server_address
28 self.server_name = host
29 self.server_port = port
30 self.setup_environ()
31
32
33class MockHandler(WSGIRequestHandler):
34 """Non-socket HTTP handler"""
35 def setup(self):
36 self.connection = self.request
37 self.rfile, self.wfile = self.connection
38
39 def finish(self):
40 pass
41
42
Thomas Wouters0e3f5912006-08-11 14:57:12 +000043def hello_app(environ,start_response):
44 start_response("200 OK", [
45 ('Content-Type','text/plain'),
46 ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
47 ])
Phillip J. Ebye1594222010-11-02 22:28:59 +000048 return [b"Hello, world!"]
Thomas Wouters0e3f5912006-08-11 14:57:12 +000049
Berker Peksag659f6312015-03-02 06:53:33 +020050
51def header_app(environ, start_response):
52 start_response("200 OK", [
53 ('Content-Type', 'text/plain'),
54 ('Date', 'Mon, 05 Jun 2006 18:49:54 GMT')
55 ])
56 return [';'.join([
57 environ['HTTP_X_TEST_HEADER'], environ['QUERY_STRING'],
58 environ['PATH_INFO']
59 ]).encode('iso-8859-1')]
60
61
Guido van Rossum6a10e022007-08-08 17:01:45 +000062def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
Thomas Wouters0e3f5912006-08-11 14:57:12 +000063 server = make_server("", 80, app, MockServer, MockHandler)
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000064 inp = BufferedReader(BytesIO(data))
Antoine Pitrou38a66ad2009-01-03 18:41:49 +000065 out = BytesIO()
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000066 olderr = sys.stderr
67 err = sys.stderr = StringIO()
Thomas Wouters0e3f5912006-08-11 14:57:12 +000068
69 try:
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000070 server.finish_request((inp, out), ("127.0.0.1",8888))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000071 finally:
72 sys.stderr = olderr
73
74 return out.getvalue(), err.getvalue()
75
Thomas Wouters0e3f5912006-08-11 14:57:12 +000076def compare_generic_iter(make_it,match):
77 """Utility to compare a generic 2.1/2.2+ iterator with an iterable
78
79 If running under Python 2.2+, this tests the iterator using iter()/next(),
80 as well as __getitem__. 'make_it' must be a function returning a fresh
81 iterator to be tested (since this may test the iterator twice)."""
82
83 it = make_it()
84 n = 0
85 for item in match:
86 if not it[n]==item: raise AssertionError
87 n+=1
88 try:
89 it[n]
90 except IndexError:
91 pass
92 else:
93 raise AssertionError("Too many items from __getitem__",it)
94
95 try:
96 iter, StopIteration
97 except NameError:
98 pass
99 else:
100 # Only test iter mode under 2.2+
101 it = make_it()
102 if not iter(it) is it: raise AssertionError
103 for item in match:
Georg Brandla18af4e2007-04-21 15:47:16 +0000104 if not next(it) == item: raise AssertionError
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000105 try:
Georg Brandla18af4e2007-04-21 15:47:16 +0000106 next(it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000107 except StopIteration:
108 pass
109 else:
Georg Brandla18af4e2007-04-21 15:47:16 +0000110 raise AssertionError("Too many items from .__next__()", it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000111
112
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113class IntegrationTests(TestCase):
114
115 def check_hello(self, out, has_length=True):
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700116 pyver = (python_implementation() + "/" +
117 sys.version.split()[0])
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 self.assertEqual(out,
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000119 ("HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700120 "Server: WSGIServer/0.2 " + pyver +"\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000121 "Content-Type: text/plain\r\n"
122 "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
123 (has_length and "Content-Length: 13\r\n" or "") +
124 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000125 "Hello, world!").encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 )
127
128 def test_plain_hello(self):
129 out, err = run_amock()
130 self.check_hello(out)
131
Berker Peksag659f6312015-03-02 06:53:33 +0200132 def test_environ(self):
133 request = (
134 b"GET /p%61th/?query=test HTTP/1.0\n"
135 b"X-Test-Header: Python test \n"
136 b"X-Test-Header: Python test 2\n"
137 b"Content-Length: 0\n\n"
138 )
139 out, err = run_amock(header_app, request)
140 self.assertEqual(
141 out.splitlines()[-1],
142 b"Python test,Python test 2;query=test;/path/"
143 )
144
Senthil Kumarane025b522014-09-17 16:29:29 +0800145 def test_request_length(self):
146 out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n")
147 self.assertEqual(out.splitlines()[0],
148 b"HTTP/1.0 414 Request-URI Too Long")
149
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000150 def test_validated_hello(self):
151 out, err = run_amock(validator(hello_app))
152 # the middleware doesn't support len(), so content-length isn't there
153 self.check_hello(out, has_length=False)
154
155 def test_simple_validation_error(self):
156 def bad_app(environ,start_response):
157 start_response("200 OK", ('Content-Type','text/plain'))
158 return ["Hello, world!"]
159 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000160 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000161 b"A server error occurred. Please contact the administrator."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000162 ))
163 self.assertEqual(
164 err.splitlines()[-2],
165 "AssertionError: Headers (('Content-Type', 'text/plain')) must"
Martin v. Löwis250ad612008-04-07 05:43:42 +0000166 " be of type list: <class 'tuple'>"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000167 )
168
Berker Peksag1cd4ff62016-03-19 09:04:59 +0200169 def test_status_validation_errors(self):
170 def create_bad_app(status):
171 def bad_app(environ, start_response):
172 start_response(status, [("Content-Type", "text/plain; charset=utf-8")])
173 return [b"Hello, world!"]
174 return bad_app
175
176 tests = [
177 ('200', 'AssertionError: Status must be at least 4 characters'),
178 ('20X OK', 'AssertionError: Status message must begin w/3-digit code'),
179 ('200OK', 'AssertionError: Status message must have a space after code'),
180 ]
181
182 for status, exc_message in tests:
183 with self.subTest(status=status):
184 out, err = run_amock(create_bad_app(status))
185 self.assertTrue(out.endswith(
186 b"A server error occurred. Please contact the administrator."
187 ))
188 self.assertEqual(err.splitlines()[-2], exc_message)
189
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000190 def test_wsgi_input(self):
191 def bad_app(e,s):
192 e["wsgi.input"].read()
Phillip J. Ebye1594222010-11-02 22:28:59 +0000193 s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000194 return [b"data"]
195 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000196 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000197 b"A server error occurred. Please contact the administrator."
198 ))
199 self.assertEqual(
200 err.splitlines()[-2], "AssertionError"
201 )
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000202
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000203 def test_bytes_validation(self):
204 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000205 s("200 OK", [
206 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000207 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
208 ])
209 return [b"data"]
210 out, err = run_amock(validator(app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000211 self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
Antoine Pitrou5f817412009-01-03 18:49:41 +0000212 ver = sys.version.split()[0].encode('ascii')
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700213 py = python_implementation().encode('ascii')
214 pyver = py + b"/" + ver
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000215 self.assertEqual(
216 b"HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700217 b"Server: WSGIServer/0.2 "+ pyver + b"\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000218 b"Content-Type: text/plain; charset=utf-8\r\n"
219 b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
220 b"\r\n"
221 b"data",
222 out)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000223
224
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000225class UtilityTests(TestCase):
226
227 def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
228 env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
229 util.setup_testing_defaults(env)
230 self.assertEqual(util.shift_path_info(env),part)
231 self.assertEqual(env['PATH_INFO'],pi_out)
232 self.assertEqual(env['SCRIPT_NAME'],sn_out)
233 return env
234
235 def checkDefault(self, key, value, alt=None):
236 # Check defaulting when empty
237 env = {}
238 util.setup_testing_defaults(env)
Ezio Melottie9615932010-01-24 19:26:24 +0000239 if isinstance(value, StringIO):
240 self.assertIsInstance(env[key], StringIO)
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000241 elif isinstance(value,BytesIO):
Ezio Melottie9615932010-01-24 19:26:24 +0000242 self.assertIsInstance(env[key],BytesIO)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000243 else:
Ezio Melottie9615932010-01-24 19:26:24 +0000244 self.assertEqual(env[key], value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000245
246 # Check existing value
247 env = {key:alt}
248 util.setup_testing_defaults(env)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200249 self.assertIs(env[key], alt)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000250
251 def checkCrossDefault(self,key,value,**kw):
252 util.setup_testing_defaults(kw)
253 self.assertEqual(kw[key],value)
254
255 def checkAppURI(self,uri,**kw):
256 util.setup_testing_defaults(kw)
257 self.assertEqual(util.application_uri(kw),uri)
258
259 def checkReqURI(self,uri,query=1,**kw):
260 util.setup_testing_defaults(kw)
261 self.assertEqual(util.request_uri(kw,query),uri)
262
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000263 def checkFW(self,text,size,match):
264
265 def make_it(text=text,size=size):
266 return util.FileWrapper(StringIO(text),size)
267
268 compare_generic_iter(make_it,match)
269
270 it = make_it()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000271 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000272
273 for item in it:
274 pass
275
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000276 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000277
278 it.close()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000279 self.assertTrue(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000280
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000281 def testSimpleShifts(self):
282 self.checkShift('','/', '', '/', '')
283 self.checkShift('','/x', 'x', '/x', '')
284 self.checkShift('/','', None, '/', '')
285 self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
286 self.checkShift('/a','/x/', 'x', '/a/x', '/')
287
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000288 def testNormalizedShifts(self):
289 self.checkShift('/a/b', '/../y', '..', '/a', '/y')
290 self.checkShift('', '/../y', '..', '', '/y')
291 self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
292 self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
293 self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
294 self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
295 self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
296 self.checkShift('/a/b', '///', '', '/a/b/', '')
297 self.checkShift('/a/b', '/.//', '', '/a/b/', '')
298 self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
299 self.checkShift('/a/b', '/.', None, '/a/b', '')
300
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000301 def testDefaults(self):
302 for key, value in [
303 ('SERVER_NAME','127.0.0.1'),
304 ('SERVER_PORT', '80'),
305 ('SERVER_PROTOCOL','HTTP/1.0'),
306 ('HTTP_HOST','127.0.0.1'),
307 ('REQUEST_METHOD','GET'),
308 ('SCRIPT_NAME',''),
309 ('PATH_INFO','/'),
310 ('wsgi.version', (1,0)),
311 ('wsgi.run_once', 0),
312 ('wsgi.multithread', 0),
313 ('wsgi.multiprocess', 0),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000314 ('wsgi.input', BytesIO()),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000315 ('wsgi.errors', StringIO()),
316 ('wsgi.url_scheme','http'),
317 ]:
318 self.checkDefault(key,value)
319
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000320 def testCrossDefaults(self):
321 self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
322 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
323 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
324 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
325 self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
326 self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
327 self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
328
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000329 def testGuessScheme(self):
330 self.assertEqual(util.guess_scheme({}), "http")
331 self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
332 self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
333 self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
334 self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
335
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000336 def testAppURIs(self):
337 self.checkAppURI("http://127.0.0.1/")
338 self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200339 self.checkAppURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000340 self.checkAppURI("http://spam.example.com:2071/",
341 HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
342 self.checkAppURI("http://spam.example.com/",
343 SERVER_NAME="spam.example.com")
344 self.checkAppURI("http://127.0.0.1/",
345 HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
346 self.checkAppURI("https://127.0.0.1/", HTTPS="on")
347 self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
348 HTTP_HOST=None)
349
350 def testReqURIs(self):
351 self.checkReqURI("http://127.0.0.1/")
352 self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200353 self.checkReqURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000354 self.checkReqURI("http://127.0.0.1/spammity/spam",
355 SCRIPT_NAME="/spammity", PATH_INFO="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200356 self.checkReqURI("http://127.0.0.1/spammity/sp%E4m",
357 SCRIPT_NAME="/spammity", PATH_INFO="/sp\xe4m")
Senthil Kumaran299fa4c2010-12-29 06:25:42 +0000358 self.checkReqURI("http://127.0.0.1/spammity/spam;ham",
359 SCRIPT_NAME="/spammity", PATH_INFO="/spam;ham")
360 self.checkReqURI("http://127.0.0.1/spammity/spam;cookie=1234,5678",
361 SCRIPT_NAME="/spammity", PATH_INFO="/spam;cookie=1234,5678")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362 self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
363 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200364 self.checkReqURI("http://127.0.0.1/spammity/spam?s%E4y=ni",
365 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="s%E4y=ni")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000366 self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
367 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
368
369 def testFileWrapper(self):
370 self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
371
372 def testHopByHop(self):
373 for hop in (
374 "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
375 "TE Trailers Transfer-Encoding Upgrade"
376 ).split():
377 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000378 self.assertTrue(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000379
380 # Not comprehensive, just a few random header names
381 for hop in (
382 "Accept Cache-Control Date Pragma Trailer Via Warning"
383 ).split():
384 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000385 self.assertFalse(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386
387class HeaderTests(TestCase):
388
389 def testMappingInterface(self):
390 test = [('x','y')]
Berker Peksag3e887222014-07-02 08:37:22 +0300391 self.assertEqual(len(Headers()), 0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000392 self.assertEqual(len(Headers([])),0)
393 self.assertEqual(len(Headers(test[:])),1)
394 self.assertEqual(Headers(test[:]).keys(), ['x'])
395 self.assertEqual(Headers(test[:]).values(), ['y'])
396 self.assertEqual(Headers(test[:]).items(), test)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200397 self.assertIsNot(Headers(test).items(), test) # must be copy!
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000398
Berker Peksag3e887222014-07-02 08:37:22 +0300399 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000400 del h['foo'] # should not raise an error
401
402 h['Foo'] = 'bar'
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000403 for m in h.__contains__, h.get, h.get_all, h.__getitem__:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000404 self.assertTrue(m('foo'))
405 self.assertTrue(m('Foo'))
406 self.assertTrue(m('FOO'))
407 self.assertFalse(m('bar'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000408
409 self.assertEqual(h['foo'],'bar')
410 h['foo'] = 'baz'
411 self.assertEqual(h['FOO'],'baz')
412 self.assertEqual(h.get_all('foo'),['baz'])
413
414 self.assertEqual(h.get("foo","whee"), "baz")
415 self.assertEqual(h.get("zoo","whee"), "whee")
416 self.assertEqual(h.setdefault("foo","whee"), "baz")
417 self.assertEqual(h.setdefault("zoo","whee"), "whee")
418 self.assertEqual(h["foo"],"baz")
419 self.assertEqual(h["zoo"],"whee")
420
421 def testRequireList(self):
422 self.assertRaises(TypeError, Headers, "foo")
423
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 def testExtras(self):
Berker Peksag3e887222014-07-02 08:37:22 +0300425 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000426 self.assertEqual(str(h),'\r\n')
427
428 h.add_header('foo','bar',baz="spam")
429 self.assertEqual(h['foo'], 'bar; baz="spam"')
430 self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
431
432 h.add_header('Foo','bar',cheese=None)
433 self.assertEqual(h.get_all('foo'),
434 ['bar; baz="spam"', 'bar; cheese'])
435
436 self.assertEqual(str(h),
437 'foo: bar; baz="spam"\r\n'
438 'Foo: bar; cheese\r\n'
439 '\r\n'
440 )
441
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000442class ErrorHandler(BaseCGIHandler):
443 """Simple handler subclass for testing BaseHandler"""
444
Antoine Pitrou8f5731b2009-11-03 17:20:10 +0000445 # BaseHandler records the OS environment at import time, but envvars
446 # might have been changed later by other tests, which trips up
447 # HandlerTests.testEnviron().
448 os_environ = dict(os.environ.items())
449
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000450 def __init__(self,**kw):
451 setup_testing_defaults(kw)
452 BaseCGIHandler.__init__(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000453 self, BytesIO(), BytesIO(), StringIO(), kw,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000454 multithread=True, multiprocess=True
455 )
456
457class TestHandler(ErrorHandler):
458 """Simple handler subclass for testing BaseHandler, w/error passthru"""
459
460 def handle_error(self):
461 raise # for testing, we want to see what's happening
462
463
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000464class HandlerTests(TestCase):
465
466 def checkEnvironAttrs(self, handler):
467 env = handler.environ
468 for attr in [
469 'version','multithread','multiprocess','run_once','file_wrapper'
470 ]:
471 if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
472 continue
473 self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
474
475 def checkOSEnviron(self,handler):
476 empty = {}; setup_testing_defaults(empty)
477 env = handler.environ
478 from os import environ
479 for k,v in environ.items():
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000480 if k not in empty:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000481 self.assertEqual(env[k],v)
482 for k,v in empty.items():
Benjamin Peterson577473f2010-01-19 00:09:57 +0000483 self.assertIn(k, env)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000484
485 def testEnviron(self):
486 h = TestHandler(X="Y")
487 h.setup_environ()
488 self.checkEnvironAttrs(h)
489 self.checkOSEnviron(h)
490 self.assertEqual(h.environ["X"],"Y")
491
492 def testCGIEnviron(self):
493 h = BaseCGIHandler(None,None,None,{})
494 h.setup_environ()
495 for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
Benjamin Peterson577473f2010-01-19 00:09:57 +0000496 self.assertIn(key, h.environ)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000497
498 def testScheme(self):
499 h=TestHandler(HTTPS="on"); h.setup_environ()
500 self.assertEqual(h.environ['wsgi.url_scheme'],'https')
501 h=TestHandler(); h.setup_environ()
502 self.assertEqual(h.environ['wsgi.url_scheme'],'http')
503
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000504 def testAbstractMethods(self):
505 h = BaseHandler()
506 for name in [
507 '_flush','get_stdin','get_stderr','add_cgi_vars'
508 ]:
509 self.assertRaises(NotImplementedError, getattr(h,name))
510 self.assertRaises(NotImplementedError, h._write, "test")
511
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000512 def testContentLength(self):
513 # Demo one reason iteration is better than write()... ;)
514
515 def trivial_app1(e,s):
516 s('200 OK',[])
Phillip J. Ebye1594222010-11-02 22:28:59 +0000517 return [e['wsgi.url_scheme'].encode('iso-8859-1')]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000518
519 def trivial_app2(e,s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000520 s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000521 return []
522
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000523 def trivial_app3(e,s):
524 s('200 OK',[])
525 return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
526
Antoine Pitroub715fac2011-01-06 17:17:04 +0000527 def trivial_app4(e,s):
528 # Simulate a response to a HEAD request
529 s('200 OK',[('Content-Length', '12345')])
530 return []
531
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000532 h = TestHandler()
533 h.run(trivial_app1)
534 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000535 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000536 "Content-Length: 4\r\n"
537 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000538 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000539
540 h = TestHandler()
541 h.run(trivial_app2)
542 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000543 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000544 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000545 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000546
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000547 h = TestHandler()
548 h.run(trivial_app3)
549 self.assertEqual(h.stdout.getvalue(),
550 b'Status: 200 OK\r\n'
551 b'Content-Length: 8\r\n'
552 b'\r\n'
553 b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000554
Antoine Pitroub715fac2011-01-06 17:17:04 +0000555 h = TestHandler()
556 h.run(trivial_app4)
557 self.assertEqual(h.stdout.getvalue(),
558 b'Status: 200 OK\r\n'
559 b'Content-Length: 12345\r\n'
560 b'\r\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000561
562 def testBasicErrorOutput(self):
563
564 def non_error_app(e,s):
565 s('200 OK',[])
566 return []
567
568 def error_app(e,s):
569 raise AssertionError("This should be caught by handler")
570
571 h = ErrorHandler()
572 h.run(non_error_app)
573 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000574 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000575 "Content-Length: 0\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000576 "\r\n").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000577 self.assertEqual(h.stderr.getvalue(),"")
578
579 h = ErrorHandler()
580 h.run(error_app)
581 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000582 ("Status: %s\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000583 "Content-Type: text/plain\r\n"
584 "Content-Length: %d\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000585 "\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
586 + h.error_body)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000587
Benjamin Peterson577473f2010-01-19 00:09:57 +0000588 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000589
590 def testErrorAfterOutput(self):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000591 MSG = b"Some output has been sent"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000592 def error_app(e,s):
593 s("200 OK",[])(MSG)
594 raise AssertionError("This should be caught by handler")
595
596 h = ErrorHandler()
597 h.run(error_app)
598 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000599 ("Status: 200 OK\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000600 "\r\n".encode("iso-8859-1")+MSG))
Benjamin Peterson577473f2010-01-19 00:09:57 +0000601 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000602
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000603 def testHeaderFormats(self):
604
605 def non_error_app(e,s):
606 s('200 OK',[])
607 return []
608
609 stdpat = (
610 r"HTTP/%s 200 OK\r\n"
611 r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
612 r"%s" r"Content-Length: 0\r\n" r"\r\n"
613 )
614 shortpat = (
615 "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000616 ).encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000617
618 for ssw in "FooBar/1.0", None:
619 sw = ssw and "Server: %s\r\n" % ssw or ""
620
621 for version in "1.0", "1.1":
622 for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
623
624 h = TestHandler(SERVER_PROTOCOL=proto)
625 h.origin_server = False
626 h.http_version = version
627 h.server_software = ssw
628 h.run(non_error_app)
629 self.assertEqual(shortpat,h.stdout.getvalue())
630
631 h = TestHandler(SERVER_PROTOCOL=proto)
632 h.origin_server = True
633 h.http_version = version
634 h.server_software = ssw
635 h.run(non_error_app)
636 if proto=="HTTP/0.9":
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000637 self.assertEqual(h.stdout.getvalue(),b"")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000638 else:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000639 self.assertTrue(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000640 re.match((stdpat%(version,sw)).encode("iso-8859-1"),
641 h.stdout.getvalue()),
642 ((stdpat%(version,sw)).encode("iso-8859-1"),
643 h.stdout.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000644 )
645
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000646 def testBytesData(self):
647 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000648 s("200 OK", [
649 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000650 ])
651 return [b"data"]
652
653 h = TestHandler()
654 h.run(app)
655 self.assertEqual(b"Status: 200 OK\r\n"
656 b"Content-Type: text/plain; charset=utf-8\r\n"
657 b"Content-Length: 4\r\n"
658 b"\r\n"
659 b"data",
660 h.stdout.getvalue())
661
Antoine Pitrouae247a52012-10-21 14:09:05 +0200662 def testCloseOnError(self):
663 side_effects = {'close_called': False}
664 MSG = b"Some output has been sent"
665 def error_app(e,s):
666 s("200 OK",[])(MSG)
667 class CrashyIterable(object):
668 def __iter__(self):
669 while True:
670 yield b'blah'
671 raise AssertionError("This should be caught by handler")
672 def close(self):
673 side_effects['close_called'] = True
674 return CrashyIterable()
675
676 h = ErrorHandler()
677 h.run(error_app)
678 self.assertEqual(side_effects['close_called'], True)
679
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000680
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000681if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500682 unittest.main()