blob: b7d02e868c9ffa3fdf1205567098924b35e2ca7d [file] [log] [blame]
Martin Panter50dd1f72016-04-17 02:17:03 +00001from unittest import mock
Guido van Rossumd8faa362007-04-27 19:54:29 +00002from unittest import TestCase
Thomas Wouters0e3f5912006-08-11 14:57:12 +00003from wsgiref.util import setup_testing_defaults
4from wsgiref.headers import Headers
5from wsgiref.handlers import BaseHandler, BaseCGIHandler
6from wsgiref import util
7from wsgiref.validate import validator
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -07008from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
Thomas Wouters0e3f5912006-08-11 14:57:12 +00009from wsgiref.simple_server import make_server
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000010from io import StringIO, BytesIO, BufferedReader
Alexandre Vassalottice261952008-05-12 02:31:37 +000011from socketserver import BaseServer
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -070012from platform import python_implementation
13
Antoine Pitrou8f5731b2009-11-03 17:20:10 +000014import os
15import re
16import sys
Senthil Kumaran22f2c0e2015-09-03 02:26:31 -070017import unittest
Thomas Wouters0e3f5912006-08-11 14:57:12 +000018
19
20class MockServer(WSGIServer):
21 """Non-socket HTTP server"""
22
23 def __init__(self, server_address, RequestHandlerClass):
24 BaseServer.__init__(self, server_address, RequestHandlerClass)
25 self.server_bind()
26
27 def server_bind(self):
28 host, port = self.server_address
29 self.server_name = host
30 self.server_port = port
31 self.setup_environ()
32
33
34class MockHandler(WSGIRequestHandler):
35 """Non-socket HTTP handler"""
36 def setup(self):
37 self.connection = self.request
38 self.rfile, self.wfile = self.connection
39
40 def finish(self):
41 pass
42
43
Thomas Wouters0e3f5912006-08-11 14:57:12 +000044def hello_app(environ,start_response):
45 start_response("200 OK", [
46 ('Content-Type','text/plain'),
47 ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
48 ])
Phillip J. Ebye1594222010-11-02 22:28:59 +000049 return [b"Hello, world!"]
Thomas Wouters0e3f5912006-08-11 14:57:12 +000050
Berker Peksag659f6312015-03-02 06:53:33 +020051
52def header_app(environ, start_response):
53 start_response("200 OK", [
54 ('Content-Type', 'text/plain'),
55 ('Date', 'Mon, 05 Jun 2006 18:49:54 GMT')
56 ])
57 return [';'.join([
58 environ['HTTP_X_TEST_HEADER'], environ['QUERY_STRING'],
59 environ['PATH_INFO']
60 ]).encode('iso-8859-1')]
61
62
Guido van Rossum6a10e022007-08-08 17:01:45 +000063def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
Thomas Wouters0e3f5912006-08-11 14:57:12 +000064 server = make_server("", 80, app, MockServer, MockHandler)
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000065 inp = BufferedReader(BytesIO(data))
Antoine Pitrou38a66ad2009-01-03 18:41:49 +000066 out = BytesIO()
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000067 olderr = sys.stderr
68 err = sys.stderr = StringIO()
Thomas Wouters0e3f5912006-08-11 14:57:12 +000069
70 try:
Jeremy Hyltone6b59c52007-08-10 19:13:33 +000071 server.finish_request((inp, out), ("127.0.0.1",8888))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000072 finally:
73 sys.stderr = olderr
74
75 return out.getvalue(), err.getvalue()
76
Thomas Wouters0e3f5912006-08-11 14:57:12 +000077def compare_generic_iter(make_it,match):
78 """Utility to compare a generic 2.1/2.2+ iterator with an iterable
79
80 If running under Python 2.2+, this tests the iterator using iter()/next(),
81 as well as __getitem__. 'make_it' must be a function returning a fresh
82 iterator to be tested (since this may test the iterator twice)."""
83
84 it = make_it()
85 n = 0
86 for item in match:
87 if not it[n]==item: raise AssertionError
88 n+=1
89 try:
90 it[n]
91 except IndexError:
92 pass
93 else:
94 raise AssertionError("Too many items from __getitem__",it)
95
96 try:
97 iter, StopIteration
98 except NameError:
99 pass
100 else:
101 # Only test iter mode under 2.2+
102 it = make_it()
103 if not iter(it) is it: raise AssertionError
104 for item in match:
Georg Brandla18af4e2007-04-21 15:47:16 +0000105 if not next(it) == item: raise AssertionError
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000106 try:
Georg Brandla18af4e2007-04-21 15:47:16 +0000107 next(it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000108 except StopIteration:
109 pass
110 else:
Georg Brandla18af4e2007-04-21 15:47:16 +0000111 raise AssertionError("Too many items from .__next__()", it)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000112
113
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000114class IntegrationTests(TestCase):
115
116 def check_hello(self, out, has_length=True):
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700117 pyver = (python_implementation() + "/" +
118 sys.version.split()[0])
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000119 self.assertEqual(out,
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000120 ("HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700121 "Server: WSGIServer/0.2 " + pyver +"\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 "Content-Type: text/plain\r\n"
123 "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
124 (has_length and "Content-Length: 13\r\n" or "") +
125 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000126 "Hello, world!").encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 )
128
129 def test_plain_hello(self):
130 out, err = run_amock()
131 self.check_hello(out)
132
Berker Peksag659f6312015-03-02 06:53:33 +0200133 def test_environ(self):
134 request = (
135 b"GET /p%61th/?query=test HTTP/1.0\n"
136 b"X-Test-Header: Python test \n"
137 b"X-Test-Header: Python test 2\n"
138 b"Content-Length: 0\n\n"
139 )
140 out, err = run_amock(header_app, request)
141 self.assertEqual(
142 out.splitlines()[-1],
143 b"Python test,Python test 2;query=test;/path/"
144 )
145
Senthil Kumarane025b522014-09-17 16:29:29 +0800146 def test_request_length(self):
147 out, err = run_amock(data=b"GET " + (b"x" * 65537) + b" HTTP/1.0\n\n")
148 self.assertEqual(out.splitlines()[0],
149 b"HTTP/1.0 414 Request-URI Too Long")
150
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000151 def test_validated_hello(self):
152 out, err = run_amock(validator(hello_app))
153 # the middleware doesn't support len(), so content-length isn't there
154 self.check_hello(out, has_length=False)
155
156 def test_simple_validation_error(self):
157 def bad_app(environ,start_response):
158 start_response("200 OK", ('Content-Type','text/plain'))
159 return ["Hello, world!"]
160 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000161 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000162 b"A server error occurred. Please contact the administrator."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000163 ))
164 self.assertEqual(
165 err.splitlines()[-2],
166 "AssertionError: Headers (('Content-Type', 'text/plain')) must"
Martin v. Löwis250ad612008-04-07 05:43:42 +0000167 " be of type list: <class 'tuple'>"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 )
169
Berker Peksag1cd4ff62016-03-19 09:04:59 +0200170 def test_status_validation_errors(self):
171 def create_bad_app(status):
172 def bad_app(environ, start_response):
173 start_response(status, [("Content-Type", "text/plain; charset=utf-8")])
174 return [b"Hello, world!"]
175 return bad_app
176
177 tests = [
178 ('200', 'AssertionError: Status must be at least 4 characters'),
179 ('20X OK', 'AssertionError: Status message must begin w/3-digit code'),
180 ('200OK', 'AssertionError: Status message must have a space after code'),
181 ]
182
183 for status, exc_message in tests:
184 with self.subTest(status=status):
185 out, err = run_amock(create_bad_app(status))
186 self.assertTrue(out.endswith(
187 b"A server error occurred. Please contact the administrator."
188 ))
189 self.assertEqual(err.splitlines()[-2], exc_message)
190
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000191 def test_wsgi_input(self):
192 def bad_app(e,s):
193 e["wsgi.input"].read()
Phillip J. Ebye1594222010-11-02 22:28:59 +0000194 s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000195 return [b"data"]
196 out, err = run_amock(validator(bad_app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000197 self.assertTrue(out.endswith(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000198 b"A server error occurred. Please contact the administrator."
199 ))
200 self.assertEqual(
201 err.splitlines()[-2], "AssertionError"
202 )
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000203
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000204 def test_bytes_validation(self):
205 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000206 s("200 OK", [
207 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000208 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
209 ])
210 return [b"data"]
211 out, err = run_amock(validator(app))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000212 self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
Antoine Pitrou5f817412009-01-03 18:49:41 +0000213 ver = sys.version.split()[0].encode('ascii')
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700214 py = python_implementation().encode('ascii')
215 pyver = py + b"/" + ver
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000216 self.assertEqual(
217 b"HTTP/1.0 200 OK\r\n"
Senthil Kumarana5e0eaf2012-07-07 14:29:58 -0700218 b"Server: WSGIServer/0.2 "+ pyver + b"\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000219 b"Content-Type: text/plain; charset=utf-8\r\n"
220 b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
221 b"\r\n"
222 b"data",
223 out)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000224
Martin Panter50dd1f72016-04-17 02:17:03 +0000225 def test_cp1252_url(self):
226 def app(e, s):
227 s("200 OK", [
228 ("Content-Type", "text/plain"),
229 ("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
230 ])
231 # PEP3333 says environ variables are decoded as latin1.
232 # Encode as latin1 to get original bytes
233 return [e["PATH_INFO"].encode("latin1")]
234
235 out, err = run_amock(
236 validator(app), data=b"GET /\x80%80 HTTP/1.0")
237 self.assertEqual(
238 [
239 b"HTTP/1.0 200 OK",
240 mock.ANY,
241 b"Content-Type: text/plain",
242 b"Date: Wed, 24 Dec 2008 13:29:32 GMT",
243 b"",
244 b"/\x80\x80",
245 ],
246 out.splitlines())
247
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000248
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000249class UtilityTests(TestCase):
250
251 def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
252 env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
253 util.setup_testing_defaults(env)
254 self.assertEqual(util.shift_path_info(env),part)
255 self.assertEqual(env['PATH_INFO'],pi_out)
256 self.assertEqual(env['SCRIPT_NAME'],sn_out)
257 return env
258
259 def checkDefault(self, key, value, alt=None):
260 # Check defaulting when empty
261 env = {}
262 util.setup_testing_defaults(env)
Ezio Melottie9615932010-01-24 19:26:24 +0000263 if isinstance(value, StringIO):
264 self.assertIsInstance(env[key], StringIO)
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000265 elif isinstance(value,BytesIO):
Ezio Melottie9615932010-01-24 19:26:24 +0000266 self.assertIsInstance(env[key],BytesIO)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000267 else:
Ezio Melottie9615932010-01-24 19:26:24 +0000268 self.assertEqual(env[key], value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000269
270 # Check existing value
271 env = {key:alt}
272 util.setup_testing_defaults(env)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200273 self.assertIs(env[key], alt)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000274
275 def checkCrossDefault(self,key,value,**kw):
276 util.setup_testing_defaults(kw)
277 self.assertEqual(kw[key],value)
278
279 def checkAppURI(self,uri,**kw):
280 util.setup_testing_defaults(kw)
281 self.assertEqual(util.application_uri(kw),uri)
282
283 def checkReqURI(self,uri,query=1,**kw):
284 util.setup_testing_defaults(kw)
285 self.assertEqual(util.request_uri(kw,query),uri)
286
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000287 def checkFW(self,text,size,match):
288
289 def make_it(text=text,size=size):
290 return util.FileWrapper(StringIO(text),size)
291
292 compare_generic_iter(make_it,match)
293
294 it = make_it()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000295 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000296
297 for item in it:
298 pass
299
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000300 self.assertFalse(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000301
302 it.close()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000303 self.assertTrue(it.filelike.closed)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000304
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000305 def testSimpleShifts(self):
306 self.checkShift('','/', '', '/', '')
307 self.checkShift('','/x', 'x', '/x', '')
308 self.checkShift('/','', None, '/', '')
309 self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
310 self.checkShift('/a','/x/', 'x', '/a/x', '/')
311
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000312 def testNormalizedShifts(self):
313 self.checkShift('/a/b', '/../y', '..', '/a', '/y')
314 self.checkShift('', '/../y', '..', '', '/y')
315 self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
316 self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
317 self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
318 self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
319 self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
320 self.checkShift('/a/b', '///', '', '/a/b/', '')
321 self.checkShift('/a/b', '/.//', '', '/a/b/', '')
322 self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
323 self.checkShift('/a/b', '/.', None, '/a/b', '')
324
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000325 def testDefaults(self):
326 for key, value in [
327 ('SERVER_NAME','127.0.0.1'),
328 ('SERVER_PORT', '80'),
329 ('SERVER_PROTOCOL','HTTP/1.0'),
330 ('HTTP_HOST','127.0.0.1'),
331 ('REQUEST_METHOD','GET'),
332 ('SCRIPT_NAME',''),
333 ('PATH_INFO','/'),
334 ('wsgi.version', (1,0)),
335 ('wsgi.run_once', 0),
336 ('wsgi.multithread', 0),
337 ('wsgi.multiprocess', 0),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000338 ('wsgi.input', BytesIO()),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000339 ('wsgi.errors', StringIO()),
340 ('wsgi.url_scheme','http'),
341 ]:
342 self.checkDefault(key,value)
343
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000344 def testCrossDefaults(self):
345 self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
346 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
347 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
348 self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
349 self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
350 self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
351 self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
352
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000353 def testGuessScheme(self):
354 self.assertEqual(util.guess_scheme({}), "http")
355 self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
356 self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
357 self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
358 self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
359
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000360 def testAppURIs(self):
361 self.checkAppURI("http://127.0.0.1/")
362 self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200363 self.checkAppURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000364 self.checkAppURI("http://spam.example.com:2071/",
365 HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
366 self.checkAppURI("http://spam.example.com/",
367 SERVER_NAME="spam.example.com")
368 self.checkAppURI("http://127.0.0.1/",
369 HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
370 self.checkAppURI("https://127.0.0.1/", HTTPS="on")
371 self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
372 HTTP_HOST=None)
373
374 def testReqURIs(self):
375 self.checkReqURI("http://127.0.0.1/")
376 self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200377 self.checkReqURI("http://127.0.0.1/sp%E4m", SCRIPT_NAME="/sp\xe4m")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000378 self.checkReqURI("http://127.0.0.1/spammity/spam",
379 SCRIPT_NAME="/spammity", PATH_INFO="/spam")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200380 self.checkReqURI("http://127.0.0.1/spammity/sp%E4m",
381 SCRIPT_NAME="/spammity", PATH_INFO="/sp\xe4m")
Senthil Kumaran299fa4c2010-12-29 06:25:42 +0000382 self.checkReqURI("http://127.0.0.1/spammity/spam;ham",
383 SCRIPT_NAME="/spammity", PATH_INFO="/spam;ham")
384 self.checkReqURI("http://127.0.0.1/spammity/spam;cookie=1234,5678",
385 SCRIPT_NAME="/spammity", PATH_INFO="/spam;cookie=1234,5678")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
387 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
Serhiy Storchaka0abbe8c2014-01-12 12:08:11 +0200388 self.checkReqURI("http://127.0.0.1/spammity/spam?s%E4y=ni",
389 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="s%E4y=ni")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000390 self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
391 SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
392
393 def testFileWrapper(self):
394 self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
395
396 def testHopByHop(self):
397 for hop in (
398 "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
399 "TE Trailers Transfer-Encoding Upgrade"
400 ).split():
401 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000402 self.assertTrue(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000403
404 # Not comprehensive, just a few random header names
405 for hop in (
406 "Accept Cache-Control Date Pragma Trailer Via Warning"
407 ).split():
408 for alt in hop, hop.title(), hop.upper(), hop.lower():
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000409 self.assertFalse(util.is_hop_by_hop(alt))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000410
411class HeaderTests(TestCase):
412
413 def testMappingInterface(self):
414 test = [('x','y')]
Berker Peksag3e887222014-07-02 08:37:22 +0300415 self.assertEqual(len(Headers()), 0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000416 self.assertEqual(len(Headers([])),0)
417 self.assertEqual(len(Headers(test[:])),1)
418 self.assertEqual(Headers(test[:]).keys(), ['x'])
419 self.assertEqual(Headers(test[:]).values(), ['y'])
420 self.assertEqual(Headers(test[:]).items(), test)
Serhiy Storchaka25d8aea2014-02-08 14:50:08 +0200421 self.assertIsNot(Headers(test).items(), test) # must be copy!
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422
Berker Peksag3e887222014-07-02 08:37:22 +0300423 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 del h['foo'] # should not raise an error
425
426 h['Foo'] = 'bar'
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000427 for m in h.__contains__, h.get, h.get_all, h.__getitem__:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000428 self.assertTrue(m('foo'))
429 self.assertTrue(m('Foo'))
430 self.assertTrue(m('FOO'))
431 self.assertFalse(m('bar'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000432
433 self.assertEqual(h['foo'],'bar')
434 h['foo'] = 'baz'
435 self.assertEqual(h['FOO'],'baz')
436 self.assertEqual(h.get_all('foo'),['baz'])
437
438 self.assertEqual(h.get("foo","whee"), "baz")
439 self.assertEqual(h.get("zoo","whee"), "whee")
440 self.assertEqual(h.setdefault("foo","whee"), "baz")
441 self.assertEqual(h.setdefault("zoo","whee"), "whee")
442 self.assertEqual(h["foo"],"baz")
443 self.assertEqual(h["zoo"],"whee")
444
445 def testRequireList(self):
446 self.assertRaises(TypeError, Headers, "foo")
447
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000448 def testExtras(self):
Berker Peksag3e887222014-07-02 08:37:22 +0300449 h = Headers()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000450 self.assertEqual(str(h),'\r\n')
451
452 h.add_header('foo','bar',baz="spam")
453 self.assertEqual(h['foo'], 'bar; baz="spam"')
454 self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
455
456 h.add_header('Foo','bar',cheese=None)
457 self.assertEqual(h.get_all('foo'),
458 ['bar; baz="spam"', 'bar; cheese'])
459
460 self.assertEqual(str(h),
461 'foo: bar; baz="spam"\r\n'
462 'Foo: bar; cheese\r\n'
463 '\r\n'
464 )
465
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000466class ErrorHandler(BaseCGIHandler):
467 """Simple handler subclass for testing BaseHandler"""
468
Antoine Pitrou8f5731b2009-11-03 17:20:10 +0000469 # BaseHandler records the OS environment at import time, but envvars
470 # might have been changed later by other tests, which trips up
471 # HandlerTests.testEnviron().
472 os_environ = dict(os.environ.items())
473
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000474 def __init__(self,**kw):
475 setup_testing_defaults(kw)
476 BaseCGIHandler.__init__(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000477 self, BytesIO(), BytesIO(), StringIO(), kw,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000478 multithread=True, multiprocess=True
479 )
480
481class TestHandler(ErrorHandler):
482 """Simple handler subclass for testing BaseHandler, w/error passthru"""
483
484 def handle_error(self):
485 raise # for testing, we want to see what's happening
486
487
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000488class HandlerTests(TestCase):
489
490 def checkEnvironAttrs(self, handler):
491 env = handler.environ
492 for attr in [
493 'version','multithread','multiprocess','run_once','file_wrapper'
494 ]:
495 if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
496 continue
497 self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
498
499 def checkOSEnviron(self,handler):
500 empty = {}; setup_testing_defaults(empty)
501 env = handler.environ
502 from os import environ
503 for k,v in environ.items():
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000504 if k not in empty:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000505 self.assertEqual(env[k],v)
506 for k,v in empty.items():
Benjamin Peterson577473f2010-01-19 00:09:57 +0000507 self.assertIn(k, env)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000508
509 def testEnviron(self):
510 h = TestHandler(X="Y")
511 h.setup_environ()
512 self.checkEnvironAttrs(h)
513 self.checkOSEnviron(h)
514 self.assertEqual(h.environ["X"],"Y")
515
516 def testCGIEnviron(self):
517 h = BaseCGIHandler(None,None,None,{})
518 h.setup_environ()
519 for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
Benjamin Peterson577473f2010-01-19 00:09:57 +0000520 self.assertIn(key, h.environ)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000521
522 def testScheme(self):
523 h=TestHandler(HTTPS="on"); h.setup_environ()
524 self.assertEqual(h.environ['wsgi.url_scheme'],'https')
525 h=TestHandler(); h.setup_environ()
526 self.assertEqual(h.environ['wsgi.url_scheme'],'http')
527
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000528 def testAbstractMethods(self):
529 h = BaseHandler()
530 for name in [
531 '_flush','get_stdin','get_stderr','add_cgi_vars'
532 ]:
533 self.assertRaises(NotImplementedError, getattr(h,name))
534 self.assertRaises(NotImplementedError, h._write, "test")
535
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000536 def testContentLength(self):
537 # Demo one reason iteration is better than write()... ;)
538
539 def trivial_app1(e,s):
540 s('200 OK',[])
Phillip J. Ebye1594222010-11-02 22:28:59 +0000541 return [e['wsgi.url_scheme'].encode('iso-8859-1')]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000542
543 def trivial_app2(e,s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000544 s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000545 return []
546
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000547 def trivial_app3(e,s):
548 s('200 OK',[])
549 return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
550
Antoine Pitroub715fac2011-01-06 17:17:04 +0000551 def trivial_app4(e,s):
552 # Simulate a response to a HEAD request
553 s('200 OK',[('Content-Length', '12345')])
554 return []
555
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000556 h = TestHandler()
557 h.run(trivial_app1)
558 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000559 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000560 "Content-Length: 4\r\n"
561 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000562 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563
564 h = TestHandler()
565 h.run(trivial_app2)
566 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000567 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000568 "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000569 "http").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000570
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000571 h = TestHandler()
572 h.run(trivial_app3)
573 self.assertEqual(h.stdout.getvalue(),
574 b'Status: 200 OK\r\n'
575 b'Content-Length: 8\r\n'
576 b'\r\n'
577 b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000578
Antoine Pitroub715fac2011-01-06 17:17:04 +0000579 h = TestHandler()
580 h.run(trivial_app4)
581 self.assertEqual(h.stdout.getvalue(),
582 b'Status: 200 OK\r\n'
583 b'Content-Length: 12345\r\n'
584 b'\r\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000585
586 def testBasicErrorOutput(self):
587
588 def non_error_app(e,s):
589 s('200 OK',[])
590 return []
591
592 def error_app(e,s):
593 raise AssertionError("This should be caught by handler")
594
595 h = ErrorHandler()
596 h.run(non_error_app)
597 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000598 ("Status: 200 OK\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000599 "Content-Length: 0\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000600 "\r\n").encode("iso-8859-1"))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000601 self.assertEqual(h.stderr.getvalue(),"")
602
603 h = ErrorHandler()
604 h.run(error_app)
605 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000606 ("Status: %s\r\n"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000607 "Content-Type: text/plain\r\n"
608 "Content-Length: %d\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000609 "\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
610 + h.error_body)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000611
Benjamin Peterson577473f2010-01-19 00:09:57 +0000612 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000613
614 def testErrorAfterOutput(self):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000615 MSG = b"Some output has been sent"
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000616 def error_app(e,s):
617 s("200 OK",[])(MSG)
618 raise AssertionError("This should be caught by handler")
619
620 h = ErrorHandler()
621 h.run(error_app)
622 self.assertEqual(h.stdout.getvalue(),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000623 ("Status: 200 OK\r\n"
Phillip J. Ebye1594222010-11-02 22:28:59 +0000624 "\r\n".encode("iso-8859-1")+MSG))
Benjamin Peterson577473f2010-01-19 00:09:57 +0000625 self.assertIn("AssertionError", h.stderr.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000626
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000627 def testHeaderFormats(self):
628
629 def non_error_app(e,s):
630 s('200 OK',[])
631 return []
632
633 stdpat = (
634 r"HTTP/%s 200 OK\r\n"
635 r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
636 r"%s" r"Content-Length: 0\r\n" r"\r\n"
637 )
638 shortpat = (
639 "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000640 ).encode("iso-8859-1")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000641
642 for ssw in "FooBar/1.0", None:
643 sw = ssw and "Server: %s\r\n" % ssw or ""
644
645 for version in "1.0", "1.1":
646 for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
647
648 h = TestHandler(SERVER_PROTOCOL=proto)
649 h.origin_server = False
650 h.http_version = version
651 h.server_software = ssw
652 h.run(non_error_app)
653 self.assertEqual(shortpat,h.stdout.getvalue())
654
655 h = TestHandler(SERVER_PROTOCOL=proto)
656 h.origin_server = True
657 h.http_version = version
658 h.server_software = ssw
659 h.run(non_error_app)
660 if proto=="HTTP/0.9":
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000661 self.assertEqual(h.stdout.getvalue(),b"")
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000662 else:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000663 self.assertTrue(
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000664 re.match((stdpat%(version,sw)).encode("iso-8859-1"),
665 h.stdout.getvalue()),
666 ((stdpat%(version,sw)).encode("iso-8859-1"),
667 h.stdout.getvalue())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000668 )
669
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000670 def testBytesData(self):
671 def app(e, s):
Phillip J. Ebye1594222010-11-02 22:28:59 +0000672 s("200 OK", [
673 ("Content-Type", "text/plain; charset=utf-8"),
Antoine Pitrou38a66ad2009-01-03 18:41:49 +0000674 ])
675 return [b"data"]
676
677 h = TestHandler()
678 h.run(app)
679 self.assertEqual(b"Status: 200 OK\r\n"
680 b"Content-Type: text/plain; charset=utf-8\r\n"
681 b"Content-Length: 4\r\n"
682 b"\r\n"
683 b"data",
684 h.stdout.getvalue())
685
Antoine Pitrouae247a52012-10-21 14:09:05 +0200686 def testCloseOnError(self):
687 side_effects = {'close_called': False}
688 MSG = b"Some output has been sent"
689 def error_app(e,s):
690 s("200 OK",[])(MSG)
691 class CrashyIterable(object):
692 def __iter__(self):
693 while True:
694 yield b'blah'
695 raise AssertionError("This should be caught by handler")
696 def close(self):
697 side_effects['close_called'] = True
698 return CrashyIterable()
699
700 h = ErrorHandler()
701 h.run(error_app)
702 self.assertEqual(side_effects['close_called'], True)
703
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000704
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000705if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500706 unittest.main()