Fixed python_path problem.
[smonitor.git] / lib / cherrypy / test / test_core.py
blob09544e34e805d3c90dc9b15a92ce1194b9926727
1 """Basic tests for the CherryPy core: request handling."""
3 import os
4 localDir = os.path.dirname(__file__)
5 import sys
6 import types
8 import cherrypy
9 from cherrypy._cpcompat import IncompleteRead, itervalues, ntob
10 from cherrypy import _cptools, tools
11 from cherrypy.lib import httputil, static
14 favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico")
16 # Client-side code #
18 from cherrypy.test import helper
20 class CoreRequestHandlingTest(helper.CPWebCase):
22 def setup_server():
23 class Root:
25 def index(self):
26 return "hello"
27 index.exposed = True
29 favicon_ico = tools.staticfile.handler(filename=favicon_path)
31 def defct(self, newct):
32 newct = "text/%s" % newct
33 cherrypy.config.update({'tools.response_headers.on': True,
34 'tools.response_headers.headers':
35 [('Content-Type', newct)]})
36 defct.exposed = True
38 def baseurl(self, path_info, relative=None):
39 return cherrypy.url(path_info, relative=bool(relative))
40 baseurl.exposed = True
42 root = Root()
44 if sys.version_info >= (2, 5):
45 from cherrypy.test._test_decorators import ExposeExamples
46 root.expose_dec = ExposeExamples()
49 class TestType(type):
50 """Metaclass which automatically exposes all functions in each subclass,
51 and adds an instance of the subclass as an attribute of root.
52 """
53 def __init__(cls, name, bases, dct):
54 type.__init__(cls, name, bases, dct)
55 for value in itervalues(dct):
56 if isinstance(value, types.FunctionType):
57 value.exposed = True
58 setattr(root, name.lower(), cls())
59 class Test(object):
60 __metaclass__ = TestType
63 class URL(Test):
65 _cp_config = {'tools.trailing_slash.on': False}
67 def index(self, path_info, relative=None):
68 if relative != 'server':
69 relative = bool(relative)
70 return cherrypy.url(path_info, relative=relative)
72 def leaf(self, path_info, relative=None):
73 if relative != 'server':
74 relative = bool(relative)
75 return cherrypy.url(path_info, relative=relative)
78 class Status(Test):
80 def index(self):
81 return "normal"
83 def blank(self):
84 cherrypy.response.status = ""
86 # According to RFC 2616, new status codes are OK as long as they
87 # are between 100 and 599.
89 # Here is an illegal code...
90 def illegal(self):
91 cherrypy.response.status = 781
92 return "oops"
94 # ...and here is an unknown but legal code.
95 def unknown(self):
96 cherrypy.response.status = "431 My custom error"
97 return "funky"
99 # Non-numeric code
100 def bad(self):
101 cherrypy.response.status = "error"
102 return "bad news"
105 class Redirect(Test):
107 class Error:
108 _cp_config = {"tools.err_redirect.on": True,
109 "tools.err_redirect.url": "/errpage",
110 "tools.err_redirect.internal": False,
113 def index(self):
114 raise NameError("redirect_test")
115 index.exposed = True
116 error = Error()
118 def index(self):
119 return "child"
121 def custom(self, url, code):
122 raise cherrypy.HTTPRedirect(url, code)
124 def by_code(self, code):
125 raise cherrypy.HTTPRedirect("somewhere%20else", code)
126 by_code._cp_config = {'tools.trailing_slash.extra': True}
128 def nomodify(self):
129 raise cherrypy.HTTPRedirect("", 304)
131 def proxy(self):
132 raise cherrypy.HTTPRedirect("proxy", 305)
134 def stringify(self):
135 return str(cherrypy.HTTPRedirect("/"))
137 def fragment(self, frag):
138 raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
140 def login_redir():
141 if not getattr(cherrypy.request, "login", None):
142 raise cherrypy.InternalRedirect("/internalredirect/login")
143 tools.login_redir = _cptools.Tool('before_handler', login_redir)
145 def redir_custom():
146 raise cherrypy.InternalRedirect("/internalredirect/custom_err")
148 class InternalRedirect(Test):
150 def index(self):
151 raise cherrypy.InternalRedirect("/")
153 def choke(self):
154 return 3 / 0
155 choke.exposed = True
156 choke._cp_config = {'hooks.before_error_response': redir_custom}
158 def relative(self, a, b):
159 raise cherrypy.InternalRedirect("cousin?t=6")
161 def cousin(self, t):
162 assert cherrypy.request.prev.closed
163 return cherrypy.request.prev.query_string
165 def petshop(self, user_id):
166 if user_id == "parrot":
167 # Trade it for a slug when redirecting
168 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug')
169 elif user_id == "terrier":
170 # Trade it for a fish when redirecting
171 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish')
172 else:
173 # This should pass the user_id through to getImagesByUser
174 raise cherrypy.InternalRedirect(
175 '/image/getImagesByUser?user_id=%s' % str(user_id))
177 # We support Python 2.3, but the @-deco syntax would look like this:
178 # @tools.login_redir()
179 def secure(self):
180 return "Welcome!"
181 secure = tools.login_redir()(secure)
182 # Since calling the tool returns the same function you pass in,
183 # you could skip binding the return value, and just write:
184 # tools.login_redir()(secure)
186 def login(self):
187 return "Please log in"
189 def custom_err(self):
190 return "Something went horribly wrong."
192 def early_ir(self, arg):
193 return "whatever"
194 early_ir._cp_config = {'hooks.before_request_body': redir_custom}
197 class Image(Test):
199 def getImagesByUser(self, user_id):
200 return "0 images for %s" % user_id
203 class Flatten(Test):
205 def as_string(self):
206 return "content"
208 def as_list(self):
209 return ["con", "tent"]
211 def as_yield(self):
212 yield ntob("content")
214 def as_dblyield(self):
215 yield self.as_yield()
216 as_dblyield._cp_config = {'tools.flatten.on': True}
218 def as_refyield(self):
219 for chunk in self.as_yield():
220 yield chunk
223 class Ranges(Test):
225 def get_ranges(self, bytes):
226 return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
228 def slice_file(self):
229 path = os.path.join(os.getcwd(), os.path.dirname(__file__))
230 return static.serve_file(os.path.join(path, "static/index.html"))
233 class Cookies(Test):
235 def single(self, name):
236 cookie = cherrypy.request.cookie[name]
237 # Python2's SimpleCookie.__setitem__ won't take unicode keys.
238 cherrypy.response.cookie[str(name)] = cookie.value
240 def multiple(self, names):
241 for name in names:
242 cookie = cherrypy.request.cookie[name]
243 # Python2's SimpleCookie.__setitem__ won't take unicode keys.
244 cherrypy.response.cookie[str(name)] = cookie.value
247 cherrypy.tree.mount(root)
248 setup_server = staticmethod(setup_server)
251 def testStatus(self):
252 self.getPage("/status/")
253 self.assertBody('normal')
254 self.assertStatus(200)
256 self.getPage("/status/blank")
257 self.assertBody('')
258 self.assertStatus(200)
260 self.getPage("/status/illegal")
261 self.assertStatus(500)
262 msg = "Illegal response status from server (781 is out of range)."
263 self.assertErrorPage(500, msg)
265 if not getattr(cherrypy.server, 'using_apache', False):
266 self.getPage("/status/unknown")
267 self.assertBody('funky')
268 self.assertStatus(431)
270 self.getPage("/status/bad")
271 self.assertStatus(500)
272 msg = "Illegal response status from server ('error' is non-numeric)."
273 self.assertErrorPage(500, msg)
275 def testSlashes(self):
276 # Test that requests for index methods without a trailing slash
277 # get redirected to the same URI path with a trailing slash.
278 # Make sure GET params are preserved.
279 self.getPage("/redirect?id=3")
280 self.assertStatus(301)
281 self.assertInBody("<a href='%s/redirect/?id=3'>"
282 "%s/redirect/?id=3</a>" % (self.base(), self.base()))
284 if self.prefix():
285 # Corner case: the "trailing slash" redirect could be tricky if
286 # we're using a virtual root and the URI is "/vroot" (no slash).
287 self.getPage("")
288 self.assertStatus(301)
289 self.assertInBody("<a href='%s/'>%s/</a>" %
290 (self.base(), self.base()))
292 # Test that requests for NON-index methods WITH a trailing slash
293 # get redirected to the same URI path WITHOUT a trailing slash.
294 # Make sure GET params are preserved.
295 self.getPage("/redirect/by_code/?code=307")
296 self.assertStatus(301)
297 self.assertInBody("<a href='%s/redirect/by_code?code=307'>"
298 "%s/redirect/by_code?code=307</a>"
299 % (self.base(), self.base()))
301 # If the trailing_slash tool is off, CP should just continue
302 # as if the slashes were correct. But it needs some help
303 # inside cherrypy.url to form correct output.
304 self.getPage('/url?path_info=page1')
305 self.assertBody('%s/url/page1' % self.base())
306 self.getPage('/url/leaf/?path_info=page1')
307 self.assertBody('%s/url/page1' % self.base())
309 def testRedirect(self):
310 self.getPage("/redirect/")
311 self.assertBody('child')
312 self.assertStatus(200)
314 self.getPage("/redirect/by_code?code=300")
315 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
316 self.assertStatus(300)
318 self.getPage("/redirect/by_code?code=301")
319 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
320 self.assertStatus(301)
322 self.getPage("/redirect/by_code?code=302")
323 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
324 self.assertStatus(302)
326 self.getPage("/redirect/by_code?code=303")
327 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
328 self.assertStatus(303)
330 self.getPage("/redirect/by_code?code=307")
331 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
332 self.assertStatus(307)
334 self.getPage("/redirect/nomodify")
335 self.assertBody('')
336 self.assertStatus(304)
338 self.getPage("/redirect/proxy")
339 self.assertBody('')
340 self.assertStatus(305)
342 # HTTPRedirect on error
343 self.getPage("/redirect/error/")
344 self.assertStatus(('302 Found', '303 See Other'))
345 self.assertInBody('/errpage')
347 # Make sure str(HTTPRedirect()) works.
348 self.getPage("/redirect/stringify", protocol="HTTP/1.0")
349 self.assertStatus(200)
350 self.assertBody("(['%s/'], 302)" % self.base())
351 if cherrypy.server.protocol_version == "HTTP/1.1":
352 self.getPage("/redirect/stringify", protocol="HTTP/1.1")
353 self.assertStatus(200)
354 self.assertBody("(['%s/'], 303)" % self.base())
356 # check that #fragments are handled properly
357 # http://skrb.org/ietf/http_errata.html#location-fragments
358 frag = "foo"
359 self.getPage("/redirect/fragment/%s" % frag)
360 self.assertMatchesBody(r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag))
361 loc = self.assertHeader('Location')
362 assert loc.endswith("#%s" % frag)
363 self.assertStatus(('302 Found', '303 See Other'))
365 # check injection protection
366 # See http://www.cherrypy.org/ticket/1003
367 self.getPage("/redirect/custom?code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval")
368 self.assertStatus(303)
369 loc = self.assertHeader('Location')
370 assert 'Set-Cookie' in loc
371 self.assertNoHeader('Set-Cookie')
373 def test_InternalRedirect(self):
374 # InternalRedirect
375 self.getPage("/internalredirect/")
376 self.assertBody('hello')
377 self.assertStatus(200)
379 # Test passthrough
380 self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film")
381 self.assertBody('0 images for Sir-not-appearing-in-this-film')
382 self.assertStatus(200)
384 # Test args
385 self.getPage("/internalredirect/petshop?user_id=parrot")
386 self.assertBody('0 images for slug')
387 self.assertStatus(200)
389 # Test POST
390 self.getPage("/internalredirect/petshop", method="POST",
391 body="user_id=terrier")
392 self.assertBody('0 images for fish')
393 self.assertStatus(200)
395 # Test ir before body read
396 self.getPage("/internalredirect/early_ir", method="POST",
397 body="arg=aha!")
398 self.assertBody("Something went horribly wrong.")
399 self.assertStatus(200)
401 self.getPage("/internalredirect/secure")
402 self.assertBody('Please log in')
403 self.assertStatus(200)
405 # Relative path in InternalRedirect.
406 # Also tests request.prev.
407 self.getPage("/internalredirect/relative?a=3&b=5")
408 self.assertBody("a=3&b=5")
409 self.assertStatus(200)
411 # InternalRedirect on error
412 self.getPage("/internalredirect/choke")
413 self.assertStatus(200)
414 self.assertBody("Something went horribly wrong.")
416 def testFlatten(self):
417 for url in ["/flatten/as_string", "/flatten/as_list",
418 "/flatten/as_yield", "/flatten/as_dblyield",
419 "/flatten/as_refyield"]:
420 self.getPage(url)
421 self.assertBody('content')
423 def testRanges(self):
424 self.getPage("/ranges/get_ranges?bytes=3-6")
425 self.assertBody("[(3, 7)]")
427 # Test multiple ranges and a suffix-byte-range-spec, for good measure.
428 self.getPage("/ranges/get_ranges?bytes=2-4,-1")
429 self.assertBody("[(2, 5), (7, 8)]")
431 # Get a partial file.
432 if cherrypy.server.protocol_version == "HTTP/1.1":
433 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
434 self.assertStatus(206)
435 self.assertHeader("Content-Type", "text/html;charset=utf-8")
436 self.assertHeader("Content-Range", "bytes 2-5/14")
437 self.assertBody("llo,")
439 # What happens with overlapping ranges (and out of order, too)?
440 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
441 self.assertStatus(206)
442 ct = self.assertHeader("Content-Type")
443 expected_type = "multipart/byteranges; boundary="
444 self.assert_(ct.startswith(expected_type))
445 boundary = ct[len(expected_type):]
446 expected_body = ("\r\n--%s\r\n"
447 "Content-type: text/html\r\n"
448 "Content-range: bytes 4-6/14\r\n"
449 "\r\n"
450 "o, \r\n"
451 "--%s\r\n"
452 "Content-type: text/html\r\n"
453 "Content-range: bytes 2-5/14\r\n"
454 "\r\n"
455 "llo,\r\n"
456 "--%s--\r\n" % (boundary, boundary, boundary))
457 self.assertBody(expected_body)
458 self.assertHeader("Content-Length")
460 # Test "416 Requested Range Not Satisfiable"
461 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
462 self.assertStatus(416)
463 # "When this status code is returned for a byte-range request,
464 # the response SHOULD include a Content-Range entity-header
465 # field specifying the current length of the selected resource"
466 self.assertHeader("Content-Range", "bytes */14")
467 elif cherrypy.server.protocol_version == "HTTP/1.0":
468 # Test Range behavior with HTTP/1.0 request
469 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
470 self.assertStatus(200)
471 self.assertBody("Hello, world\r\n")
473 def testFavicon(self):
474 # favicon.ico is served by staticfile.
475 icofilename = os.path.join(localDir, "../favicon.ico")
476 icofile = open(icofilename, "rb")
477 data = icofile.read()
478 icofile.close()
480 self.getPage("/favicon.ico")
481 self.assertBody(data)
483 def testCookies(self):
484 if sys.version_info >= (2, 5):
485 header_value = lambda x: x
486 else:
487 header_value = lambda x: x+';'
489 self.getPage("/cookies/single?name=First",
490 [('Cookie', 'First=Dinsdale;')])
491 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
493 self.getPage("/cookies/multiple?names=First&names=Last",
494 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
496 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
497 self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
499 self.getPage("/cookies/single?name=Something-With:Colon",
500 [('Cookie', 'Something-With:Colon=some-value')])
501 self.assertStatus(400)
503 def testDefaultContentType(self):
504 self.getPage('/')
505 self.assertHeader('Content-Type', 'text/html;charset=utf-8')
506 self.getPage('/defct/plain')
507 self.getPage('/')
508 self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
509 self.getPage('/defct/html')
511 def test_cherrypy_url(self):
512 # Input relative to current
513 self.getPage('/url/leaf?path_info=page1')
514 self.assertBody('%s/url/page1' % self.base())
515 self.getPage('/url/?path_info=page1')
516 self.assertBody('%s/url/page1' % self.base())
517 # Other host header
518 host = 'www.mydomain.example'
519 self.getPage('/url/leaf?path_info=page1',
520 headers=[('Host', host)])
521 self.assertBody('%s://%s/url/page1' % (self.scheme, host))
523 # Input is 'absolute'; that is, relative to script_name
524 self.getPage('/url/leaf?path_info=/page1')
525 self.assertBody('%s/page1' % self.base())
526 self.getPage('/url/?path_info=/page1')
527 self.assertBody('%s/page1' % self.base())
529 # Single dots
530 self.getPage('/url/leaf?path_info=./page1')
531 self.assertBody('%s/url/page1' % self.base())
532 self.getPage('/url/leaf?path_info=other/./page1')
533 self.assertBody('%s/url/other/page1' % self.base())
534 self.getPage('/url/?path_info=/other/./page1')
535 self.assertBody('%s/other/page1' % self.base())
537 # Double dots
538 self.getPage('/url/leaf?path_info=../page1')
539 self.assertBody('%s/page1' % self.base())
540 self.getPage('/url/leaf?path_info=other/../page1')
541 self.assertBody('%s/url/page1' % self.base())
542 self.getPage('/url/leaf?path_info=/other/../page1')
543 self.assertBody('%s/page1' % self.base())
545 # Output relative to current path or script_name
546 self.getPage('/url/?path_info=page1&relative=True')
547 self.assertBody('page1')
548 self.getPage('/url/leaf?path_info=/page1&relative=True')
549 self.assertBody('../page1')
550 self.getPage('/url/leaf?path_info=page1&relative=True')
551 self.assertBody('page1')
552 self.getPage('/url/leaf?path_info=leaf/page1&relative=True')
553 self.assertBody('leaf/page1')
554 self.getPage('/url/leaf?path_info=../page1&relative=True')
555 self.assertBody('../page1')
556 self.getPage('/url/?path_info=other/../page1&relative=True')
557 self.assertBody('page1')
559 # Output relative to /
560 self.getPage('/baseurl?path_info=ab&relative=True')
561 self.assertBody('ab')
562 # Output relative to /
563 self.getPage('/baseurl?path_info=/ab&relative=True')
564 self.assertBody('ab')
566 # absolute-path references ("server-relative")
567 # Input relative to current
568 self.getPage('/url/leaf?path_info=page1&relative=server')
569 self.assertBody('/url/page1')
570 self.getPage('/url/?path_info=page1&relative=server')
571 self.assertBody('/url/page1')
572 # Input is 'absolute'; that is, relative to script_name
573 self.getPage('/url/leaf?path_info=/page1&relative=server')
574 self.assertBody('/page1')
575 self.getPage('/url/?path_info=/page1&relative=server')
576 self.assertBody('/page1')
578 def test_expose_decorator(self):
579 if not sys.version_info >= (2, 5):
580 return self.skip("skipped (Python 2.5+ only) ")
582 # Test @expose
583 self.getPage("/expose_dec/no_call")
584 self.assertStatus(200)
585 self.assertBody("Mr E. R. Bradshaw")
587 # Test @expose()
588 self.getPage("/expose_dec/call_empty")
589 self.assertStatus(200)
590 self.assertBody("Mrs. B.J. Smegma")
592 # Test @expose("alias")
593 self.getPage("/expose_dec/call_alias")
594 self.assertStatus(200)
595 self.assertBody("Mr Nesbitt")
596 # Does the original name work?
597 self.getPage("/expose_dec/nesbitt")
598 self.assertStatus(200)
599 self.assertBody("Mr Nesbitt")
601 # Test @expose(["alias1", "alias2"])
602 self.getPage("/expose_dec/alias1")
603 self.assertStatus(200)
604 self.assertBody("Mr Ken Andrews")
605 self.getPage("/expose_dec/alias2")
606 self.assertStatus(200)
607 self.assertBody("Mr Ken Andrews")
608 # Does the original name work?
609 self.getPage("/expose_dec/andrews")
610 self.assertStatus(200)
611 self.assertBody("Mr Ken Andrews")
613 # Test @expose(alias="alias")
614 self.getPage("/expose_dec/alias3")
615 self.assertStatus(200)
616 self.assertBody("Mr. and Mrs. Watson")