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
20
22
24 class Root:
25
26 def index(self):
27 return "hello"
28 index.exposed = True
29
30 favicon_ico = tools.staticfile.handler(filename=favicon_path)
31
32 def defct(self, newct):
33 newct = "text/%s" % newct
34 cherrypy.config.update({'tools.response_headers.on': True,
35 'tools.response_headers.headers':
36 [('Content-Type', newct)]})
37 defct.exposed = True
38
39 def baseurl(self, path_info, relative=None):
40 return cherrypy.url(path_info, relative=bool(relative))
41 baseurl.exposed = True
42
43 root = Root()
44
45 if sys.version_info >= (2, 5):
46 from cherrypy.test._test_decorators import ExposeExamples
47 root.expose_dec = ExposeExamples()
48
49 class TestType(type):
50
51 """Metaclass which automatically exposes all functions in each
52 subclass, and adds an instance of the subclass as an attribute
53 of root.
54 """
55 def __init__(cls, name, bases, dct):
56 type.__init__(cls, name, bases, dct)
57 for value in itervalues(dct):
58 if isinstance(value, types.FunctionType):
59 value.exposed = True
60 setattr(root, name.lower(), cls())
61 Test = TestType('Test', (object, ), {})
62
63 class URL(Test):
64
65 _cp_config = {'tools.trailing_slash.on': False}
66
67 def index(self, path_info, relative=None):
68 if relative != 'server':
69 relative = bool(relative)
70 return cherrypy.url(path_info, relative=relative)
71
72 def leaf(self, path_info, relative=None):
73 if relative != 'server':
74 relative = bool(relative)
75 return cherrypy.url(path_info, relative=relative)
76
77 def log_status():
78 Status.statuses.append(cherrypy.response.status)
79 cherrypy.tools.log_status = cherrypy.Tool(
80 'on_end_resource', log_status)
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
110 def on_end_resource_stage(self):
111 return repr(self.statuses)
112 on_end_resource_stage._cp_config = {'tools.log_status.on': True}
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 url_with_quote(self):
150 raise cherrypy.HTTPRedirect("/some\"url/that'we/want")
151
152 def login_redir():
153 if not getattr(cherrypy.request, "login", None):
154 raise cherrypy.InternalRedirect("/internalredirect/login")
155 tools.login_redir = _cptools.Tool('before_handler', login_redir)
156
157 def redir_custom():
158 raise cherrypy.InternalRedirect("/internalredirect/custom_err")
159
160 class InternalRedirect(Test):
161
162 def index(self):
163 raise cherrypy.InternalRedirect("/")
164
165 def choke(self):
166 return 3 / 0
167 choke.exposed = True
168 choke._cp_config = {'hooks.before_error_response': redir_custom}
169
170 def relative(self, a, b):
171 raise cherrypy.InternalRedirect("cousin?t=6")
172
173 def cousin(self, t):
174 assert cherrypy.request.prev.closed
175 return cherrypy.request.prev.query_string
176
177 def petshop(self, user_id):
178 if user_id == "parrot":
179
180 raise cherrypy.InternalRedirect(
181 '/image/getImagesByUser?user_id=slug')
182 elif user_id == "terrier":
183
184 raise cherrypy.InternalRedirect(
185 '/image/getImagesByUser?user_id=fish')
186 else:
187
188 raise cherrypy.InternalRedirect(
189 '/image/getImagesByUser?user_id=%s' % str(user_id))
190
191
192
193
194 def secure(self):
195 return "Welcome!"
196 secure = tools.login_redir()(secure)
197
198
199
200
201 def login(self):
202 return "Please log in"
203
204 def custom_err(self):
205 return "Something went horribly wrong."
206
207 def early_ir(self, arg):
208 return "whatever"
209 early_ir._cp_config = {'hooks.before_request_body': redir_custom}
210
211 class Image(Test):
212
213 def getImagesByUser(self, user_id):
214 return "0 images for %s" % user_id
215
216 class Flatten(Test):
217
218 def as_string(self):
219 return "content"
220
221 def as_list(self):
222 return ["con", "tent"]
223
224 def as_yield(self):
225 yield ntob("content")
226
227 def as_dblyield(self):
228 yield self.as_yield()
229 as_dblyield._cp_config = {'tools.flatten.on': True}
230
231 def as_refyield(self):
232 for chunk in self.as_yield():
233 yield chunk
234
235 class Ranges(Test):
236
237 def get_ranges(self, bytes):
238 return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
239
240 def slice_file(self):
241 path = os.path.join(os.getcwd(), os.path.dirname(__file__))
242 return static.serve_file(
243 os.path.join(path, "static/index.html"))
244
245 class Cookies(Test):
246
247 def single(self, name):
248 cookie = cherrypy.request.cookie[name]
249
250 cherrypy.response.cookie[str(name)] = cookie.value
251
252 def multiple(self, names):
253 for name in names:
254 cookie = cherrypy.request.cookie[name]
255
256
257 cherrypy.response.cookie[str(name)] = cookie.value
258
259 def append_headers(header_list, debug=False):
260 if debug:
261 cherrypy.log(
262 "Extending response headers with %s" % repr(header_list),
263 "TOOLS.APPEND_HEADERS")
264 cherrypy.serving.response.header_list.extend(header_list)
265 cherrypy.tools.append_headers = cherrypy.Tool(
266 'on_end_resource', append_headers)
267
268 class MultiHeader(Test):
269
270 def header_list(self):
271 pass
272 header_list = cherrypy.tools.append_headers(header_list=[
273 (ntob('WWW-Authenticate'), ntob('Negotiate')),
274 (ntob('WWW-Authenticate'), ntob('Basic realm="foo"')),
275 ])(header_list)
276
277 def commas(self):
278 cherrypy.response.headers[
279 'WWW-Authenticate'] = 'Negotiate,Basic realm="foo"'
280
281 cherrypy.tree.mount(root)
282 setup_server = staticmethod(setup_server)
283
307
313
347
428
429
430
431 self.getPage("/redirect/by_code?code=303")
432 self.assertStatus(303)
433 assertValidXHTML()
434
435
436 self.getPage("/redirect/url_with_quote")
437 self.assertStatus(303)
438 assertValidXHTML()
439
483
485 for url in ["/flatten/as_string", "/flatten/as_list",
486 "/flatten/as_yield", "/flatten/as_dblyield",
487 "/flatten/as_refyield"]:
488 self.getPage(url)
489 self.assertBody('content')
490
492 self.getPage("/ranges/get_ranges?bytes=3-6")
493 self.assertBody("[(3, 7)]")
494
495
496 self.getPage("/ranges/get_ranges?bytes=2-4,-1")
497 self.assertBody("[(2, 5), (7, 8)]")
498
499
500 if cherrypy.server.protocol_version == "HTTP/1.1":
501 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
502 self.assertStatus(206)
503 self.assertHeader("Content-Type", "text/html;charset=utf-8")
504 self.assertHeader("Content-Range", "bytes 2-5/14")
505 self.assertBody("llo,")
506
507
508 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
509 self.assertStatus(206)
510 ct = self.assertHeader("Content-Type")
511 expected_type = "multipart/byteranges; boundary="
512 self.assert_(ct.startswith(expected_type))
513 boundary = ct[len(expected_type):]
514 expected_body = ("\r\n--%s\r\n"
515 "Content-type: text/html\r\n"
516 "Content-range: bytes 4-6/14\r\n"
517 "\r\n"
518 "o, \r\n"
519 "--%s\r\n"
520 "Content-type: text/html\r\n"
521 "Content-range: bytes 2-5/14\r\n"
522 "\r\n"
523 "llo,\r\n"
524 "--%s--\r\n" % (boundary, boundary, boundary))
525 self.assertBody(expected_body)
526 self.assertHeader("Content-Length")
527
528
529 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
530 self.assertStatus(416)
531
532
533
534 self.assertHeader("Content-Range", "bytes */14")
535 elif cherrypy.server.protocol_version == "HTTP/1.0":
536
537 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
538 self.assertStatus(200)
539 self.assertBody("Hello, world\r\n")
540
550
552 if sys.version_info >= (2, 5):
553 header_value = lambda x: x
554 else:
555 header_value = lambda x: x + ';'
556
557 self.getPage("/cookies/single?name=First",
558 [('Cookie', 'First=Dinsdale;')])
559 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
560
561 self.getPage("/cookies/multiple?names=First&names=Last",
562 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
563 ])
564 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
565 self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
566
567 self.getPage("/cookies/single?name=Something-With%2CComma",
568 [('Cookie', 'Something-With,Comma=some-value')])
569 self.assertStatus(400)
570
572 self.getPage('/')
573 self.assertHeader('Content-Type', 'text/html;charset=utf-8')
574 self.getPage('/defct/plain')
575 self.getPage('/')
576 self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
577 self.getPage('/defct/html')
578
580 self.getPage('/multiheader/header_list')
581 self.assertEqual(
582 [(k, v) for k, v in self.headers if k == 'WWW-Authenticate'],
583 [('WWW-Authenticate', 'Negotiate'),
584 ('WWW-Authenticate', 'Basic realm="foo"'),
585 ])
586 self.getPage('/multiheader/commas')
587 self.assertHeader('WWW-Authenticate', 'Negotiate,Basic realm="foo"')
588
590
591 self.getPage('/url/leaf?path_info=page1')
592 self.assertBody('%s/url/page1' % self.base())
593 self.getPage('/url/?path_info=page1')
594 self.assertBody('%s/url/page1' % self.base())
595
596 host = 'www.mydomain.example'
597 self.getPage('/url/leaf?path_info=page1',
598 headers=[('Host', host)])
599 self.assertBody('%s://%s/url/page1' % (self.scheme, host))
600
601
602 self.getPage('/url/leaf?path_info=/page1')
603 self.assertBody('%s/page1' % self.base())
604 self.getPage('/url/?path_info=/page1')
605 self.assertBody('%s/page1' % self.base())
606
607
608 self.getPage('/url/leaf?path_info=./page1')
609 self.assertBody('%s/url/page1' % self.base())
610 self.getPage('/url/leaf?path_info=other/./page1')
611 self.assertBody('%s/url/other/page1' % self.base())
612 self.getPage('/url/?path_info=/other/./page1')
613 self.assertBody('%s/other/page1' % self.base())
614
615
616 self.getPage('/url/leaf?path_info=../page1')
617 self.assertBody('%s/page1' % self.base())
618 self.getPage('/url/leaf?path_info=other/../page1')
619 self.assertBody('%s/url/page1' % self.base())
620 self.getPage('/url/leaf?path_info=/other/../page1')
621 self.assertBody('%s/page1' % self.base())
622
623
624 self.getPage('/url/?path_info=page1&relative=True')
625 self.assertBody('page1')
626 self.getPage('/url/leaf?path_info=/page1&relative=True')
627 self.assertBody('../page1')
628 self.getPage('/url/leaf?path_info=page1&relative=True')
629 self.assertBody('page1')
630 self.getPage('/url/leaf?path_info=leaf/page1&relative=True')
631 self.assertBody('leaf/page1')
632 self.getPage('/url/leaf?path_info=../page1&relative=True')
633 self.assertBody('../page1')
634 self.getPage('/url/?path_info=other/../page1&relative=True')
635 self.assertBody('page1')
636
637
638 self.getPage('/baseurl?path_info=ab&relative=True')
639 self.assertBody('ab')
640
641 self.getPage('/baseurl?path_info=/ab&relative=True')
642 self.assertBody('ab')
643
644
645
646 self.getPage('/url/leaf?path_info=page1&relative=server')
647 self.assertBody('/url/page1')
648 self.getPage('/url/?path_info=page1&relative=server')
649 self.assertBody('/url/page1')
650
651 self.getPage('/url/leaf?path_info=/page1&relative=server')
652 self.assertBody('/page1')
653 self.getPage('/url/?path_info=/page1&relative=server')
654 self.assertBody('/page1')
655
695
696
698
703 cherrypy.tools.break_header = cherrypy.Tool(
704 'on_end_resource', break_header)
705
706 class Root:
707
708 def index(self):
709 return "hello"
710 index.exposed = True
711
712 def start_response_error(self):
713 return "salud!"
714 start_response_error._cp_config = {'tools.break_header.on': True}
715 root = Root()
716
717 cherrypy.tree.mount(root)
718 setup_server = staticmethod(setup_server)
719
721 self.getPage("/start_response_error")
722 self.assertStatus(500)
723 self.assertInBody(
724 "TypeError: response.header_list key 2 is not a byte string.")
725