1 """Basic tests for the CherryPy core: request handling."""
2
3 import os
4 localDir = os.path.dirname(__file__)
5 import sys
6 import types
7
8 import cherrypy
9 from cherrypy._cpcompat import IncompleteRead, itervalues, ntob
10 from cherrypy import _cptools, tools
11 from cherrypy.lib import httputil, static
12
13
14 favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico")
15
16
17
18 from cherrypy.test import helper
19
21
23 class Root:
24
25 def index(self):
26 return "hello"
27 index.exposed = True
28
29 favicon_ico = tools.staticfile.handler(filename=favicon_path)
30
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
37
38 def baseurl(self, path_info, relative=None):
39 return cherrypy.url(path_info, relative=bool(relative))
40 baseurl.exposed = True
41
42 root = Root()
43
44 if sys.version_info >= (2, 5):
45 from cherrypy.test._test_decorators import ExposeExamples
46 root.expose_dec = ExposeExamples()
47
48
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 Test = TestType('Test', (object, ), {})
60
61
62 class URL(Test):
63
64 _cp_config = {'tools.trailing_slash.on': False}
65
66 def index(self, path_info, relative=None):
67 if relative != 'server':
68 relative = bool(relative)
69 return cherrypy.url(path_info, relative=relative)
70
71 def leaf(self, path_info, relative=None):
72 if relative != 'server':
73 relative = bool(relative)
74 return cherrypy.url(path_info, relative=relative)
75
76
77 def log_status():
78 Status.statuses.append(cherrypy.response.status)
79 cherrypy.tools.log_status = cherrypy.Tool('on_end_resource', log_status)
80
81
82 class Status(Test):
83
84 def index(self):
85 return "normal"
86
87 def blank(self):
88 cherrypy.response.status = ""
89
90
91
92
93
94 def illegal(self):
95 cherrypy.response.status = 781
96 return "oops"
97
98
99 def unknown(self):
100 cherrypy.response.status = "431 My custom error"
101 return "funky"
102
103
104 def bad(self):
105 cherrypy.response.status = "error"
106 return "bad news"
107
108 statuses = []
109 def on_end_resource_stage(self):
110 return repr(self.statuses)
111 on_end_resource_stage._cp_config = {'tools.log_status.on': True}
112
113
114 class Redirect(Test):
115
116 class Error:
117 _cp_config = {"tools.err_redirect.on": True,
118 "tools.err_redirect.url": "/errpage",
119 "tools.err_redirect.internal": False,
120 }
121
122 def index(self):
123 raise NameError("redirect_test")
124 index.exposed = True
125 error = Error()
126
127 def index(self):
128 return "child"
129
130 def custom(self, url, code):
131 raise cherrypy.HTTPRedirect(url, code)
132
133 def by_code(self, code):
134 raise cherrypy.HTTPRedirect("somewhere%20else", code)
135 by_code._cp_config = {'tools.trailing_slash.extra': True}
136
137 def nomodify(self):
138 raise cherrypy.HTTPRedirect("", 304)
139
140 def proxy(self):
141 raise cherrypy.HTTPRedirect("proxy", 305)
142
143 def stringify(self):
144 return str(cherrypy.HTTPRedirect("/"))
145
146 def fragment(self, frag):
147 raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
148
149 def login_redir():
150 if not getattr(cherrypy.request, "login", None):
151 raise cherrypy.InternalRedirect("/internalredirect/login")
152 tools.login_redir = _cptools.Tool('before_handler', login_redir)
153
154 def redir_custom():
155 raise cherrypy.InternalRedirect("/internalredirect/custom_err")
156
157 class InternalRedirect(Test):
158
159 def index(self):
160 raise cherrypy.InternalRedirect("/")
161
162 def choke(self):
163 return 3 / 0
164 choke.exposed = True
165 choke._cp_config = {'hooks.before_error_response': redir_custom}
166
167 def relative(self, a, b):
168 raise cherrypy.InternalRedirect("cousin?t=6")
169
170 def cousin(self, t):
171 assert cherrypy.request.prev.closed
172 return cherrypy.request.prev.query_string
173
174 def petshop(self, user_id):
175 if user_id == "parrot":
176
177 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug')
178 elif user_id == "terrier":
179
180 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish')
181 else:
182
183 raise cherrypy.InternalRedirect(
184 '/image/getImagesByUser?user_id=%s' % str(user_id))
185
186
187
188 def secure(self):
189 return "Welcome!"
190 secure = tools.login_redir()(secure)
191
192
193
194
195 def login(self):
196 return "Please log in"
197
198 def custom_err(self):
199 return "Something went horribly wrong."
200
201 def early_ir(self, arg):
202 return "whatever"
203 early_ir._cp_config = {'hooks.before_request_body': redir_custom}
204
205
206 class Image(Test):
207
208 def getImagesByUser(self, user_id):
209 return "0 images for %s" % user_id
210
211
212 class Flatten(Test):
213
214 def as_string(self):
215 return "content"
216
217 def as_list(self):
218 return ["con", "tent"]
219
220 def as_yield(self):
221 yield ntob("content")
222
223 def as_dblyield(self):
224 yield self.as_yield()
225 as_dblyield._cp_config = {'tools.flatten.on': True}
226
227 def as_refyield(self):
228 for chunk in self.as_yield():
229 yield chunk
230
231
232 class Ranges(Test):
233
234 def get_ranges(self, bytes):
235 return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
236
237 def slice_file(self):
238 path = os.path.join(os.getcwd(), os.path.dirname(__file__))
239 return static.serve_file(os.path.join(path, "static/index.html"))
240
241
242 class Cookies(Test):
243
244 def single(self, name):
245 cookie = cherrypy.request.cookie[name]
246
247 cherrypy.response.cookie[str(name)] = cookie.value
248
249 def multiple(self, names):
250 for name in names:
251 cookie = cherrypy.request.cookie[name]
252
253 cherrypy.response.cookie[str(name)] = cookie.value
254
255 def append_headers(header_list, debug=False):
256 if debug:
257 cherrypy.log(
258 "Extending response headers with %s" % repr(header_list),
259 "TOOLS.APPEND_HEADERS")
260 cherrypy.serving.response.header_list.extend(header_list)
261 cherrypy.tools.append_headers = cherrypy.Tool('on_end_resource', append_headers)
262
263 class MultiHeader(Test):
264
265 def header_list(self):
266 pass
267 header_list = cherrypy.tools.append_headers(header_list=[
268 (ntob('WWW-Authenticate'), ntob('Negotiate')),
269 (ntob('WWW-Authenticate'), ntob('Basic realm="foo"')),
270 ])(header_list)
271
272 def commas(self):
273 cherrypy.response.headers['WWW-Authenticate'] = 'Negotiate,Basic realm="foo"'
274
275
276 cherrypy.tree.mount(root)
277 setup_server = staticmethod(setup_server)
278
279
303
309
343
407
450
452 for url in ["/flatten/as_string", "/flatten/as_list",
453 "/flatten/as_yield", "/flatten/as_dblyield",
454 "/flatten/as_refyield"]:
455 self.getPage(url)
456 self.assertBody('content')
457
459 self.getPage("/ranges/get_ranges?bytes=3-6")
460 self.assertBody("[(3, 7)]")
461
462
463 self.getPage("/ranges/get_ranges?bytes=2-4,-1")
464 self.assertBody("[(2, 5), (7, 8)]")
465
466
467 if cherrypy.server.protocol_version == "HTTP/1.1":
468 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
469 self.assertStatus(206)
470 self.assertHeader("Content-Type", "text/html;charset=utf-8")
471 self.assertHeader("Content-Range", "bytes 2-5/14")
472 self.assertBody("llo,")
473
474
475 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
476 self.assertStatus(206)
477 ct = self.assertHeader("Content-Type")
478 expected_type = "multipart/byteranges; boundary="
479 self.assert_(ct.startswith(expected_type))
480 boundary = ct[len(expected_type):]
481 expected_body = ("\r\n--%s\r\n"
482 "Content-type: text/html\r\n"
483 "Content-range: bytes 4-6/14\r\n"
484 "\r\n"
485 "o, \r\n"
486 "--%s\r\n"
487 "Content-type: text/html\r\n"
488 "Content-range: bytes 2-5/14\r\n"
489 "\r\n"
490 "llo,\r\n"
491 "--%s--\r\n" % (boundary, boundary, boundary))
492 self.assertBody(expected_body)
493 self.assertHeader("Content-Length")
494
495
496 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
497 self.assertStatus(416)
498
499
500
501 self.assertHeader("Content-Range", "bytes */14")
502 elif cherrypy.server.protocol_version == "HTTP/1.0":
503
504 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
505 self.assertStatus(200)
506 self.assertBody("Hello, world\r\n")
507
517
519 if sys.version_info >= (2, 5):
520 header_value = lambda x: x
521 else:
522 header_value = lambda x: x+';'
523
524 self.getPage("/cookies/single?name=First",
525 [('Cookie', 'First=Dinsdale;')])
526 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
527
528 self.getPage("/cookies/multiple?names=First&names=Last",
529 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
530 ])
531 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
532 self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
533
534 self.getPage("/cookies/single?name=Something-With:Colon",
535 [('Cookie', 'Something-With:Colon=some-value')])
536 self.assertStatus(400)
537
539 self.getPage('/')
540 self.assertHeader('Content-Type', 'text/html;charset=utf-8')
541 self.getPage('/defct/plain')
542 self.getPage('/')
543 self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
544 self.getPage('/defct/html')
545
547 self.getPage('/multiheader/header_list')
548 self.assertEqual([(k, v) for k, v in self.headers if k == 'WWW-Authenticate'],
549 [('WWW-Authenticate', 'Negotiate'),
550 ('WWW-Authenticate', 'Basic realm="foo"'),
551 ])
552 self.getPage('/multiheader/commas')
553 self.assertHeader('WWW-Authenticate', 'Negotiate,Basic realm="foo"')
554
556
557 self.getPage('/url/leaf?path_info=page1')
558 self.assertBody('%s/url/page1' % self.base())
559 self.getPage('/url/?path_info=page1')
560 self.assertBody('%s/url/page1' % self.base())
561
562 host = 'www.mydomain.example'
563 self.getPage('/url/leaf?path_info=page1',
564 headers=[('Host', host)])
565 self.assertBody('%s://%s/url/page1' % (self.scheme, host))
566
567
568 self.getPage('/url/leaf?path_info=/page1')
569 self.assertBody('%s/page1' % self.base())
570 self.getPage('/url/?path_info=/page1')
571 self.assertBody('%s/page1' % self.base())
572
573
574 self.getPage('/url/leaf?path_info=./page1')
575 self.assertBody('%s/url/page1' % self.base())
576 self.getPage('/url/leaf?path_info=other/./page1')
577 self.assertBody('%s/url/other/page1' % self.base())
578 self.getPage('/url/?path_info=/other/./page1')
579 self.assertBody('%s/other/page1' % self.base())
580
581
582 self.getPage('/url/leaf?path_info=../page1')
583 self.assertBody('%s/page1' % self.base())
584 self.getPage('/url/leaf?path_info=other/../page1')
585 self.assertBody('%s/url/page1' % self.base())
586 self.getPage('/url/leaf?path_info=/other/../page1')
587 self.assertBody('%s/page1' % self.base())
588
589
590 self.getPage('/url/?path_info=page1&relative=True')
591 self.assertBody('page1')
592 self.getPage('/url/leaf?path_info=/page1&relative=True')
593 self.assertBody('../page1')
594 self.getPage('/url/leaf?path_info=page1&relative=True')
595 self.assertBody('page1')
596 self.getPage('/url/leaf?path_info=leaf/page1&relative=True')
597 self.assertBody('leaf/page1')
598 self.getPage('/url/leaf?path_info=../page1&relative=True')
599 self.assertBody('../page1')
600 self.getPage('/url/?path_info=other/../page1&relative=True')
601 self.assertBody('page1')
602
603
604 self.getPage('/baseurl?path_info=ab&relative=True')
605 self.assertBody('ab')
606
607 self.getPage('/baseurl?path_info=/ab&relative=True')
608 self.assertBody('ab')
609
610
611
612 self.getPage('/url/leaf?path_info=page1&relative=server')
613 self.assertBody('/url/page1')
614 self.getPage('/url/?path_info=page1&relative=server')
615 self.assertBody('/url/page1')
616
617 self.getPage('/url/leaf?path_info=/page1&relative=server')
618 self.assertBody('/page1')
619 self.getPage('/url/?path_info=/page1&relative=server')
620 self.assertBody('/page1')
621
661
662
664
669 cherrypy.tools.break_header = cherrypy.Tool('on_end_resource', break_header)
670
671 class Root:
672 def index(self):
673 return "hello"
674 index.exposed = True
675
676 def start_response_error(self):
677 return "salud!"
678 start_response_error._cp_config = {'tools.break_header.on': True}
679 root = Root()
680
681 cherrypy.tree.mount(root)
682 setup_server = staticmethod(setup_server)
683
688