1 """Basic tests for the CherryPy core: request handling."""
2
3 from cherrypy.test import test
4 test.prefer_parent_path()
5
6 import os
7 localDir = os.path.dirname(__file__)
8 import sys
9 import types
10 from httplib import IncompleteRead
11
12 import cherrypy
13 from cherrypy import _cptools, tools
14 from cherrypy.lib import http, static
15
16
17 favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico")
18
19 defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
20 "TRACE", "PROPFIND")
21
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 andnow(self):
33 return "the larch"
34 andnow.exposed = True
35
36 def global_(self):
37 pass
38 global_.exposed = True
39
40 def delglobal(self):
41 del self.__class__.__dict__['global_']
42 delglobal.exposed = True
43
44 def defct(self, newct):
45 newct = "text/%s" % newct
46 cherrypy.config.update({'tools.response_headers.on': True,
47 'tools.response_headers.headers':
48 [('Content-Type', newct)]})
49 defct.exposed = True
50
51 def upload(self, file):
52 return "Size: %s" % len(file.file.read())
53 upload.exposed = True
54
55 def baseurl(self, path_info, relative=None):
56 return cherrypy.url(path_info, relative=bool(relative))
57 baseurl.exposed = True
58
59 root = Root()
60
61
62 class TestType(type):
63 """Metaclass which automatically exposes all functions in each subclass,
64 and adds an instance of the subclass as an attribute of root.
65 """
66 def __init__(cls, name, bases, dct):
67 type.__init__(cls, name, bases, dct)
68 for value in dct.itervalues():
69 if isinstance(value, types.FunctionType):
70 value.exposed = True
71 setattr(root, name.lower(), cls())
72 class Test(object):
73 __metaclass__ = TestType
74
75
76 class URL(Test):
77
78 _cp_config = {'tools.trailing_slash.on': False}
79
80 def index(self, path_info, relative=None):
81 if relative != 'server':
82 relative = bool(relative)
83 return cherrypy.url(path_info, relative=relative)
84
85 def leaf(self, path_info, relative=None):
86 if relative != 'server':
87 relative = bool(relative)
88 return cherrypy.url(path_info, relative=relative)
89
90
91 class Params(Test):
92
93 def index(self, thing):
94 return repr(thing)
95
96 def ismap(self, x, y):
97 return "Coordinates: %s, %s" % (x, y)
98
99 def default(self, *args, **kwargs):
100 return "args: %s kwargs: %s" % (args, kwargs)
101
102 class ParamErrors(Test):
103
104 def one_positional(self, param1):
105 return "data"
106 one_positional.exposed = True
107
108 def one_positional_args(self, param1, *args):
109 return "data"
110 one_positional_args.exposed = True
111
112 def one_positional_args_kwargs(self, param1, *args, **kwargs):
113 return "data"
114 one_positional_args_kwargs.exposed = True
115
116 def one_positional_kwargs(self, param1, **kwargs):
117 return "data"
118 one_positional_kwargs.exposed = True
119
120 def no_positional(self):
121 return "data"
122 no_positional.exposed = True
123
124 def no_positional_args(self, *args):
125 return "data"
126 no_positional_args.exposed = True
127
128 def no_positional_args_kwargs(self, *args, **kwargs):
129 return "data"
130 no_positional_args_kwargs.exposed = True
131
132 def no_positional_kwargs(self, **kwargs):
133 return "data"
134 no_positional_kwargs.exposed = True
135
136
137 class Status(Test):
138
139 def index(self):
140 return "normal"
141
142 def blank(self):
143 cherrypy.response.status = ""
144
145
146
147
148
149 def illegal(self):
150 cherrypy.response.status = 781
151 return "oops"
152
153
154 def unknown(self):
155 cherrypy.response.status = "431 My custom error"
156 return "funky"
157
158
159 def bad(self):
160 cherrypy.response.status = "error"
161 return "bad news"
162
163
164 class Redirect(Test):
165
166 class Error:
167 _cp_config = {"tools.err_redirect.on": True,
168 "tools.err_redirect.url": "/errpage",
169 "tools.err_redirect.internal": False,
170 }
171
172 def index(self):
173 raise NameError("redirect_test")
174 index.exposed = True
175 error = Error()
176
177 def index(self):
178 return "child"
179
180 def by_code(self, code):
181 raise cherrypy.HTTPRedirect("somewhere else", code)
182 by_code._cp_config = {'tools.trailing_slash.extra': True}
183
184 def nomodify(self):
185 raise cherrypy.HTTPRedirect("", 304)
186
187 def proxy(self):
188 raise cherrypy.HTTPRedirect("proxy", 305)
189
190 def stringify(self):
191 return str(cherrypy.HTTPRedirect("/"))
192
193 def fragment(self, frag):
194 raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
195
196 def login_redir():
197 if not getattr(cherrypy.request, "login", None):
198 raise cherrypy.InternalRedirect("/internalredirect/login")
199 tools.login_redir = _cptools.Tool('before_handler', login_redir)
200
201 def redir_custom():
202 raise cherrypy.InternalRedirect("/internalredirect/custom_err")
203
204 class InternalRedirect(Test):
205
206 def index(self):
207 raise cherrypy.InternalRedirect("/")
208
209 def choke(self):
210 return 3 / 0
211 choke.exposed = True
212 choke._cp_config = {'hooks.before_error_response': redir_custom}
213
214 def relative(self, a, b):
215 raise cherrypy.InternalRedirect("cousin?t=6")
216
217 def cousin(self, t):
218 assert cherrypy.request.prev.closed
219 return cherrypy.request.prev.query_string
220
221 def petshop(self, user_id):
222 if user_id == "parrot":
223
224 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug')
225 elif user_id == "terrier":
226
227 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish')
228 else:
229
230 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=%s' % user_id)
231
232
233
234 def secure(self):
235 return "Welcome!"
236 secure = tools.login_redir()(secure)
237
238
239
240
241 def login(self):
242 return "Please log in"
243
244 def custom_err(self):
245 return "Something went horribly wrong."
246
247 def early_ir(self, arg):
248 return "whatever"
249 early_ir._cp_config = {'hooks.before_request_body': redir_custom}
250
251
252 class Image(Test):
253
254 def getImagesByUser(self, user_id):
255 return "0 images for %s" % user_id
256
257
258 class Flatten(Test):
259
260 def as_string(self):
261 return "content"
262
263 def as_list(self):
264 return ["con", "tent"]
265
266 def as_yield(self):
267 yield "content"
268
269 def as_dblyield(self):
270 yield self.as_yield()
271 as_dblyield._cp_config = {'tools.flatten.on': True}
272
273 def as_refyield(self):
274 for chunk in self.as_yield():
275 yield chunk
276
277
278 def callable_error_page(status, **kwargs):
279 return "Error %s - Well, I'm very sorry but you haven't paid!" % status
280
281
282 class Error(Test):
283
284 _cp_config = {'tools.log_tracebacks.on': True,
285 }
286
287 def custom(self, err='404'):
288 raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!")
289 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"),
290 'error_page.401': callable_error_page,
291 }
292
293 def custom_default(self):
294 return 1 + 'a'
295 custom_default._cp_config = {'error_page.default': callable_error_page}
296
297 def noexist(self):
298 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
299 noexist._cp_config = {'error_page.404': "nonexistent.html"}
300
301 def page_method(self):
302 raise ValueError()
303
304 def page_yield(self):
305 yield "howdy"
306 raise ValueError()
307
308 def page_streamed(self):
309 yield "word up"
310 raise ValueError()
311 yield "very oops"
312 page_streamed._cp_config = {"response.stream": True}
313
314 def cause_err_in_finalize(self):
315
316 cherrypy.response.status = "ZOO OK"
317 cause_err_in_finalize._cp_config = {'request.show_tracebacks': False}
318
319 def rethrow(self):
320 """Test that an error raised here will be thrown out to the server."""
321 raise ValueError()
322 rethrow._cp_config = {'request.throw_errors': True}
323
324
325 class Ranges(Test):
326
327 def get_ranges(self, bytes):
328 return repr(http.get_ranges('bytes=%s' % bytes, 8))
329
330 def slice_file(self):
331 path = os.path.join(os.getcwd(), os.path.dirname(__file__))
332 return static.serve_file(os.path.join(path, "static/index.html"))
333
334
335 class Expect(Test):
336
337 def expectation_failed(self):
338 expect = cherrypy.request.headers.elements("Expect")
339 if expect and expect[0].value != '100-continue':
340 raise cherrypy.HTTPError(400)
341 raise cherrypy.HTTPError(417, 'Expectation Failed')
342
343 class Headers(Test):
344
345 def default(self, headername):
346 """Spit back out the value for the requested header."""
347 return cherrypy.request.headers[headername]
348
349 def doubledheaders(self):
350
351
352
353
354
355
356 hMap = cherrypy.response.headers
357 hMap['content-type'] = "text/html"
358 hMap['content-length'] = 18
359 hMap['server'] = 'CherryPy headertest'
360 hMap['location'] = ('%s://%s:%s/headers/'
361 % (cherrypy.request.local.ip,
362 cherrypy.request.local.port,
363 cherrypy.request.scheme))
364
365
366 hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT'
367
368 return "double header test"
369
370 def ifmatch(self):
371 val = cherrypy.request.headers['If-Match']
372 cherrypy.response.headers['ETag'] = val
373 return repr(val)
374
375
376 class HeaderElements(Test):
377
378 def get_elements(self, headername):
379 e = cherrypy.request.headers.elements(headername)
380 return "\n".join([unicode(x) for x in e])
381
382
383 class Method(Test):
384
385 def index(self):
386 m = cherrypy.request.method
387 if m in defined_http_methods or m == "CONNECT":
388 return m
389
390 if m == "LINK":
391 raise cherrypy.HTTPError(405)
392 else:
393 raise cherrypy.HTTPError(501)
394
395 def parameterized(self, data):
396 return data
397
398 def request_body(self):
399
400
401 return cherrypy.request.body
402
403 def reachable(self):
404 return "success"
405
406 class Divorce:
407 """HTTP Method handlers shouldn't collide with normal method names.
408 For example, a GET-handler shouldn't collide with a method named 'get'.
409
410 If you build HTTP method dispatching into CherryPy, rewrite this class
411 to use your new dispatch mechanism and make sure that:
412 "GET /divorce HTTP/1.1" maps to divorce.index() and
413 "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get()
414 """
415
416 documents = {}
417
418 def index(self):
419 yield "<h1>Choose your document</h1>\n"
420 yield "<ul>\n"
421 for id, contents in self.documents.iteritems():
422 yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n"
423 % (id, id, contents))
424 yield "</ul>"
425 index.exposed = True
426
427 def get(self, ID):
428 return ("Divorce document %s: %s" %
429 (ID, self.documents.get(ID, "empty")))
430 get.exposed = True
431
432 root.divorce = Divorce()
433
434
435 class Cookies(Test):
436
437 def single(self, name):
438 cookie = cherrypy.request.cookie[name]
439 cherrypy.response.cookie[name] = cookie.value
440
441 def multiple(self, names):
442 for name in names:
443 cookie = cherrypy.request.cookie[name]
444 cherrypy.response.cookie[name] = cookie.value
445
446
447 class ThreadLocal(Test):
448
449 def index(self):
450 existing = repr(getattr(cherrypy.request, "asdf", None))
451 cherrypy.request.asdf = "rassfrassin"
452 return existing
453
454 if sys.version_info >= (2, 5):
455 from cherrypy.test import py25
456 Root.expose_dec = py25.ExposeExamples()
457
458 cherrypy.config.update({
459 'environment': 'test_suite',
460 'server.max_request_body_size': 200,
461 'server.max_request_header_size': 500,
462 })
463 appconf = {
464 '/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")},
465 }
466 cherrypy.tree.mount(root, config=appconf)
467
468
469
470
471 from cherrypy.test import helper
472
474
476 self.getPage("/params/?thing=a")
477 self.assertBody("'a'")
478
479 self.getPage("/params/?thing=a&thing=b&thing=c")
480 self.assertBody("['a', 'b', 'c']")
481
482
483 self.getPage("/params/?notathing=meeting")
484 self.assertInBody("Missing parameters: thing")
485 self.getPage("/params/?thing=meeting¬athing=meeting")
486 self.assertInBody("Unexpected query string parameters: notathing")
487
488
489 self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville")
490 self.assertBody(r"args: ('\xd4 \xe3', 'cheese') "
491 r"kwargs: {'Gruy\xe8re': 'Bulgn\xe9ville'}")
492
493
494 self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2")
495 self.assertBody(r"args: ('code',) "
496 r"kwargs: {'url': 'http://cherrypy.org/index?a=1&b=2'}")
497
498
499 self.getPage("/params/ismap?223,114")
500 self.assertBody("Coordinates: 223, 114")
501
503
504
505
506
507 for uri in (
508 '/paramerrors/one_positional?param1=foo',
509 '/paramerrors/one_positional_args?param1=foo',
510 '/paramerrors/one_positional_args/foo',
511 '/paramerrors/one_positional_args/foo/bar/baz',
512 '/paramerrors/one_positional_args_kwargs?param1=foo¶m2=bar',
513 '/paramerrors/one_positional_args_kwargs/foo?param2=bar¶m3=baz',
514 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
515 '/paramerrors/one_positional_kwargs?param1=foo¶m2=bar¶m3=baz',
516 '/paramerrors/one_positional_kwargs/foo?param4=foo¶m2=bar¶m3=baz',
517 '/paramerrors/no_positional',
518 '/paramerrors/no_positional_args/foo',
519 '/paramerrors/no_positional_args/foo/bar/baz',
520 '/paramerrors/no_positional_args_kwargs?param1=foo¶m2=bar',
521 '/paramerrors/no_positional_args_kwargs/foo?param2=bar',
522 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
523 '/paramerrors/no_positional_kwargs?param1=foo¶m2=bar',
524 ):
525 self.getPage(uri)
526 self.assertStatus(200)
527
528
529
530 for uri in (
531 '/paramerrors/one_positional',
532 '/paramerrors/one_positional?foo=foo',
533 '/paramerrors/one_positional/foo/bar/baz',
534 '/paramerrors/one_positional/foo?param1=foo',
535 '/paramerrors/one_positional/foo?param1=foo¶m2=foo',
536 '/paramerrors/one_positional_args/foo?param1=foo¶m2=foo',
537 '/paramerrors/one_positional_args/foo/bar/baz?param2=foo',
538 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar¶m3=baz',
539 '/paramerrors/one_positional_kwargs/foo?param1=foo¶m2=bar¶m3=baz',
540 '/paramerrors/no_positional/boo',
541 '/paramerrors/no_positional?param1=foo',
542 '/paramerrors/no_positional_args/boo?param1=foo',
543 '/paramerrors/no_positional_kwargs/boo?param1=foo',
544 ):
545 self.getPage(uri)
546 self.assertStatus(404)
547
548
549 for uri, body in (
550 ('/paramerrors/one_positional/foo', 'param1=foo',),
551 ('/paramerrors/one_positional/foo', 'param1=foo¶m2=foo',),
552 ('/paramerrors/one_positional_args/foo', 'param1=foo¶m2=foo',),
553 ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo',),
554 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar¶m3=baz',),
555 ('/paramerrors/one_positional_kwargs/foo', 'param1=foo¶m2=bar¶m3=baz',),
556 ('/paramerrors/no_positional', 'param1=foo',),
557 ('/paramerrors/no_positional_args/boo', 'param1=foo',),
558 ):
559 self.getPage(uri, method='POST', body=body)
560 self.assertStatus(400)
561
562
563
564
565 for uri, body in (
566 ('/paramerrors/one_positional?param2=foo', 'param1=foo',),
567 ('/paramerrors/one_positional/foo/bar', 'param2=foo',),
568 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo',),
569 ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar¶m3=baz',),
570 ('/paramerrors/no_positional?param1=foo', 'param2=foo',),
571 ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo',),
572 ):
573 self.getPage(uri, method='POST', body=body)
574 self.assertStatus(404)
575
576
600
602
603
604
605 self.getPage("/redirect?id=3")
606 self.assertStatus(('302 Found', '303 See Other'))
607 self.assertInBody("<a href='%s/redirect/?id=3'>"
608 "%s/redirect/?id=3</a>" % (self.base(), self.base()))
609
610 if self.prefix():
611
612
613 self.getPage("")
614 self.assertStatus(('302 Found', '303 See Other'))
615 self.assertInBody("<a href='%s/'>%s/</a>" %
616 (self.base(), self.base()))
617
618
619
620
621 self.getPage("/redirect/by_code/?code=307")
622 self.assertStatus(('302 Found', '303 See Other'))
623 self.assertInBody("<a href='%s/redirect/by_code?code=307'>"
624 "%s/redirect/by_code?code=307</a>"
625 % (self.base(), self.base()))
626
627
628
629
630 self.getPage('/url?path_info=page1')
631 self.assertBody('%s/url/page1' % self.base())
632 self.getPage('/url/leaf/?path_info=page1')
633 self.assertBody('%s/url/page1' % self.base())
634
690
733
735 for url in ["/flatten/as_string", "/flatten/as_list",
736 "/flatten/as_yield", "/flatten/as_dblyield",
737 "/flatten/as_refyield"]:
738 self.getPage(url)
739 self.assertBody('content')
740
742 self.getPage("/error/missing")
743 self.assertStatus(404)
744 self.assertErrorPage(404, "The path '/error/missing' was not found.")
745
746 ignore = helper.webtest.ignored_exceptions
747 ignore.append(ValueError)
748 try:
749 valerr = '\n raise ValueError()\nValueError'
750 self.getPage("/error/page_method")
751 self.assertErrorPage(500, pattern=valerr)
752
753 self.getPage("/error/page_yield")
754 self.assertErrorPage(500, pattern=valerr)
755
756 if (cherrypy.server.protocol_version == "HTTP/1.0" or
757 getattr(cherrypy.server, "using_apache", False)):
758 self.getPage("/error/page_streamed")
759
760
761 self.assertStatus(200)
762 self.assertBody("word up")
763 else:
764
765
766 self.assertRaises((ValueError, IncompleteRead), self.getPage,
767 "/error/page_streamed")
768
769
770 self.getPage("/error/cause_err_in_finalize")
771 msg = "Illegal response status from server ('ZOO' is non-numeric)."
772 self.assertErrorPage(500, msg, None)
773 finally:
774 ignore.pop()
775
776
777 self.getPage("/error/custom")
778 self.assertStatus(404)
779 self.assertBody("Hello, world\r\n" + (" " * 499))
780
781
782 self.getPage("/error/custom?err=401")
783 self.assertStatus(401)
784 self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!")
785
786
787 self.getPage("/error/custom_default")
788 self.assertStatus(500)
789 self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513))
790
791
792
793 self.getPage("/error/noexist")
794 self.assertStatus(404)
795 msg = ("No, <b>really</b>, not found!<br />"
796 "In addition, the custom error page failed:\n<br />"
797 "IOError: [Errno 2] No such file or directory: 'nonexistent.html'")
798 self.assertInBody(msg)
799
800 if getattr(cherrypy.server, "using_apache", False):
801 pass
802 else:
803
804 self.getPage("/error/rethrow")
805 self.assertInBody("raise ValueError()")
806
808 self.getPage("/ranges/get_ranges?bytes=3-6")
809 self.assertBody("[(3, 7)]")
810
811
812 self.getPage("/ranges/get_ranges?bytes=2-4,-1")
813 self.assertBody("[(2, 5), (7, 8)]")
814
815
816 if cherrypy.server.protocol_version == "HTTP/1.1":
817 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
818 self.assertStatus(206)
819 self.assertHeader("Content-Type", "text/html")
820 self.assertHeader("Content-Range", "bytes 2-5/14")
821 self.assertBody("llo,")
822
823
824 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
825 self.assertStatus(206)
826 ct = self.assertHeader("Content-Type")
827 expected_type = "multipart/byteranges; boundary="
828 self.assert_(ct.startswith(expected_type))
829 boundary = ct[len(expected_type):]
830 expected_body = ("\r\n--%s\r\n"
831 "Content-type: text/html\r\n"
832 "Content-range: bytes 4-6/14\r\n"
833 "\r\n"
834 "o, \r\n"
835 "--%s\r\n"
836 "Content-type: text/html\r\n"
837 "Content-range: bytes 2-5/14\r\n"
838 "\r\n"
839 "llo,\r\n"
840 "--%s--\r\n" % (boundary, boundary, boundary))
841 self.assertBody(expected_body)
842 self.assertHeader("Content-Length")
843
844
845 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
846 self.assertStatus(416)
847
848
849
850 self.assertHeader("Content-Range", "bytes */14")
851 elif cherrypy.server.protocol_version == "HTTP/1.0":
852
853 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
854 self.assertStatus(200)
855 self.assertBody("Hello, world\r\n")
856
858 e = ('Expect', '100-continue')
859 self.getPage("/headerelements/get_elements?headername=Expect", [e])
860 self.assertBody('100-continue')
861
862 self.getPage("/expect/expectation_failed", [e])
863 self.assertStatus(417)
864
866
867 h = [('Accept', 'audio/*; q=0.2, audio/basic')]
868 self.getPage("/headerelements/get_elements?headername=Accept", h)
869 self.assertStatus(200)
870 self.assertBody("audio/basic\n"
871 "audio/*;q=0.2")
872
873 h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')]
874 self.getPage("/headerelements/get_elements?headername=Accept", h)
875 self.assertStatus(200)
876 self.assertBody("text/x-c\n"
877 "text/html\n"
878 "text/x-dvi;q=0.8\n"
879 "text/plain;q=0.5")
880
881
882 h = [('Accept', 'text/*, text/html, text/html;level=1, */*')]
883 self.getPage("/headerelements/get_elements?headername=Accept", h)
884 self.assertStatus(200)
885 self.assertBody("text/html;level=1\n"
886 "text/html\n"
887 "text/*\n"
888 "*/*")
889
890
891 h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')]
892 self.getPage("/headerelements/get_elements?headername=Accept-Charset", h)
893 self.assertStatus("200 OK")
894 self.assertBody("iso-8859-5\n"
895 "unicode-1-1;q=0.8")
896
897
898 h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')]
899 self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h)
900 self.assertStatus("200 OK")
901 self.assertBody("gzip;q=1.0\n"
902 "identity;q=0.5\n"
903 "*;q=0")
904
905
906 h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')]
907 self.getPage("/headerelements/get_elements?headername=Accept-Language", h)
908 self.assertStatus("200 OK")
909 self.assertBody("da\n"
910 "en-gb;q=0.8\n"
911 "en;q=0.7")
912
913
914 self.getPage("/headerelements/get_elements?headername=Content-Type",
915
916 headers=[('Content-Type', 'text/html; charset=utf-8;')])
917 self.assertStatus(200)
918 self.assertBody("text/html;charset=utf-8")
919
921
922 self.getPage("/headers/doubledheaders")
923 self.assertBody("double header test")
924 hnames = [name.title() for name, val in self.headers]
925 for key in ['Content-Length', 'Content-Type', 'Date',
926 'Expires', 'Location', 'Server']:
927 self.assertEqual(hnames.count(key), 1, self.headers)
928
929 if cherrypy.server.protocol_version == "HTTP/1.1":
930
931 c = "=E2=84=ABngstr=C3=B6m"
932 self.getPage("/headers/ifmatch", [('If-Match', '=?utf-8?q?%s?=' % c)])
933 self.assertBody("u'\\u212bngstr\\xf6m'")
934 self.assertHeader("ETag", '=?utf-8?b?4oSrbmdzdHLDtm0=?=')
935
936
937 self.getPage("/headers/ifmatch",
938 [('If-Match', '=?utf-8?q?%s?=' % (c * 10))])
939 self.assertBody("u'%s'" % ('\\u212bngstr\\xf6m' * 10))
940 self.assertHeader("ETag",
941 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt4oSrbmdzdHLDtm0=?='
942 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt4oSrbmdzdHLDtm0=?='
943 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2bQ==?=')
944
945
946
947 self.getPage("/headers/Accept-Charset",
948 headers=[("Accept-Charset", "iso-8859-5"),
949 ("Accept-Charset", "unicode-1-1;q=0.8")])
950 self.assertBody("iso-8859-5, unicode-1-1;q=0.8")
951
952
953
954 self.getPage("/headers/Content-Type",
955 headers=[])
956 self.assertStatus(500)
957
958
959
960 self.getPage("/headers/Content-Type",
961 headers=[("Content-type", "application/json")])
962 self.assertBody("application/json")
963
965 helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND")
966
967
968 for m in defined_http_methods:
969 self.getPage("/method/", method=m)
970
971
972 if m == "HEAD":
973 self.assertBody("")
974 elif m == "TRACE":
975
976 self.assertEqual(self.body[:5], "TRACE")
977 else:
978 self.assertBody(m)
979
980
981 self.getPage("/method/parameterized", method="PUT",
982 body="data=on+top+of+other+things")
983 self.assertBody("on top of other things")
984
985
986 b = "one thing on top of another"
987 h = [("Content-Type", "text/plain"),
988 ("Content-Length", str(len(b)))]
989 self.getPage("/method/request_body", headers=h, method="PUT", body=b)
990 self.assertStatus(200)
991 self.assertBody(b)
992
993
994
995 b = "one thing on top of another"
996 self.persistent = True
997 try:
998 conn = self.HTTP_CONN
999
1000 conn.putrequest("PUT", "/method/request_body", skip_host=True)
1001 conn.putheader("Host", self.HOST)
1002 conn.putheader('Content-Length', str(len(b)))
1003 conn.endheaders()
1004 conn.send(b)
1005 response = conn.response_class(conn.sock, method="PUT")
1006 response.begin()
1007 self.assertEqual(response.status, 200)
1008 self.body = response.read()
1009 self.assertBody(b)
1010 finally:
1011 self.persistent = False
1012
1013
1014
1015
1016 h = [("Content-Type", "text/plain")]
1017 self.getPage("/method/reachable", headers=h, method="PUT")
1018 self.assertStatus(411)
1019
1020
1021 b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n'
1022 '<propfind xmlns="DAV:"><prop><getlastmodified/>'
1023 '</prop></propfind>')
1024 h = [('Content-Type', 'text/xml'),
1025 ('Content-Length', str(len(b)))]
1026 self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b)
1027 self.assertStatus(200)
1028 self.assertBody(b)
1029
1030
1031 self.getPage("/method/", method="LINK")
1032 self.assertStatus(405)
1033
1034
1035 self.getPage("/method/", method="SEARCH")
1036 self.assertStatus(501)
1037
1038
1039
1040
1041
1042 self.getPage("/divorce/get?ID=13")
1043 self.assertBody('Divorce document 13: empty')
1044 self.assertStatus(200)
1045 self.getPage("/divorce/", method="GET")
1046 self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>')
1047 self.assertStatus(200)
1048
1050 if getattr(cherrypy.server, "using_apache", False):
1051 print "skipped due to known Apache differences...",
1052 return
1053
1054 self.getPage("/method/", method="CONNECT")
1055 self.assertBody("CONNECT")
1056
1066
1068 if sys.version_info >= (2, 5):
1069 header_value = lambda x: x
1070 else:
1071 header_value = lambda x: x+';'
1072
1073 self.getPage("/cookies/single?name=First",
1074 [('Cookie', 'First=Dinsdale;')])
1075 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
1076
1077 self.getPage("/cookies/multiple?names=First&names=Last",
1078 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
1079 ])
1080 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
1081 self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
1082
1083 self.getPage("/cookies/single?name=Something-With:Colon",
1084 [('Cookie', 'Something-With:Colon=some-value')])
1085 self.assertStatus(400)
1086
1088 if getattr(cherrypy.server, "using_apache", False):
1089 print "skipped due to known Apache differences...",
1090 return
1091
1092 for size in (500, 5000, 50000):
1093 self.getPage("/", headers=[('From', "x" * 500)])
1094 self.assertStatus(413)
1095
1096
1097
1098
1099 lines256 = "x" * 248
1100 self.getPage("/",
1101 headers=[('Host', '%s:%s' % (self.HOST, self.PORT)),
1102 ('From', lines256)])
1103
1104
1105 body = """--x
1106 Content-Disposition: form-data; name="file"; filename="hello.txt"
1107 Content-Type: text/plain
1108
1109 %s
1110 --x--
1111 """
1112 b = body % ("x" * 96)
1113 h = [("Content-type", "multipart/form-data; boundary=x"),
1114 ("Content-Length", len(b))]
1115 self.getPage('/upload', h, "POST", b)
1116 self.assertBody('Size: 96')
1117
1118 b = body % ("x" * 200)
1119 h = [("Content-type", "multipart/form-data; boundary=x"),
1120 ("Content-Length", len(b))]
1121 self.getPage('/upload', h, "POST", b)
1122 self.assertStatus(413)
1123
1125 results = []
1126 for x in xrange(20):
1127 self.getPage("/threadlocal/")
1128 results.append(self.body)
1129 self.assertEqual(results, ["None"] * 20)
1130
1132 self.getPage('/')
1133 self.assertHeader('Content-Type', 'text/html')
1134 self.getPage('/defct/plain')
1135 self.getPage('/')
1136 self.assertHeader('Content-Type', 'text/plain')
1137 self.getPage('/defct/html')
1138
1140
1141 self.getPage('/url/leaf?path_info=page1')
1142 self.assertBody('%s/url/page1' % self.base())
1143 self.getPage('/url/?path_info=page1')
1144 self.assertBody('%s/url/page1' % self.base())
1145
1146
1147 self.getPage('/url/leaf?path_info=/page1')
1148 self.assertBody('%s/page1' % self.base())
1149 self.getPage('/url/?path_info=/page1')
1150 self.assertBody('%s/page1' % self.base())
1151
1152
1153 self.getPage('/url/leaf?path_info=./page1')
1154 self.assertBody('%s/url/page1' % self.base())
1155 self.getPage('/url/leaf?path_info=other/./page1')
1156 self.assertBody('%s/url/other/page1' % self.base())
1157 self.getPage('/url/?path_info=/other/./page1')
1158 self.assertBody('%s/other/page1' % self.base())
1159
1160
1161 self.getPage('/url/leaf?path_info=../page1')
1162 self.assertBody('%s/page1' % self.base())
1163 self.getPage('/url/leaf?path_info=other/../page1')
1164 self.assertBody('%s/url/page1' % self.base())
1165 self.getPage('/url/leaf?path_info=/other/../page1')
1166 self.assertBody('%s/page1' % self.base())
1167
1168
1169 self.getPage('/url/?path_info=page1&relative=True')
1170 self.assertBody('page1')
1171 self.getPage('/url/leaf?path_info=/page1&relative=True')
1172 self.assertBody('../page1')
1173 self.getPage('/url/leaf?path_info=page1&relative=True')
1174 self.assertBody('page1')
1175 self.getPage('/url/leaf?path_info=leaf/page1&relative=True')
1176 self.assertBody('leaf/page1')
1177 self.getPage('/url/leaf?path_info=../page1&relative=True')
1178 self.assertBody('../page1')
1179 self.getPage('/url/?path_info=other/../page1&relative=True')
1180 self.assertBody('page1')
1181
1182
1183 self.getPage('/baseurl?path_info=ab&relative=True')
1184 self.assertBody('ab')
1185
1186 self.getPage('/baseurl?path_info=/ab&relative=True')
1187 self.assertBody('ab')
1188
1189
1190
1191 self.getPage('/url/leaf?path_info=page1&relative=server')
1192 self.assertBody('/url/page1')
1193 self.getPage('/url/?path_info=page1&relative=server')
1194 self.assertBody('/url/page1')
1195
1196 self.getPage('/url/leaf?path_info=/page1&relative=server')
1197 self.assertBody('/page1')
1198 self.getPage('/url/?path_info=/page1&relative=server')
1199 self.assertBody('/page1')
1200
1241
1242
1243 if __name__ == '__main__':
1244 setup_server()
1245 helper.testmain()
1246