Package cherrypy :: Package test :: Module test_request_obj
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.test.test_request_obj

  1  """Basic tests for the cherrypy.Request object.""" 
  2   
  3  import os 
  4  localDir = os.path.dirname(__file__) 
  5  import sys 
  6  import types 
  7  from cherrypy._cpcompat import IncompleteRead, ntob, ntou, unicodestr 
  8   
  9  import cherrypy 
 10  from cherrypy import _cptools, tools 
 11  from cherrypy.lib import httputil 
 12   
 13  defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", 
 14                          "TRACE", "PROPFIND") 
 15   
 16   
 17  #                             Client-side code                             # 
 18   
 19  from cherrypy.test import helper 
 20   
 21   
22 -class RequestObjectTests(helper.CPWebCase):
23
24 - def setup_server():
25 class Root: 26 27 def index(self): 28 return "hello"
29 index.exposed = True 30 31 def scheme(self): 32 return cherrypy.request.scheme
33 scheme.exposed = True 34 35 root = Root() 36 37 class TestType(type): 38 """Metaclass which automatically exposes all functions in each 39 subclass, and adds an instance of the subclass as an attribute 40 of root. 41 """ 42 def __init__(cls, name, bases, dct): 43 type.__init__(cls, name, bases, dct) 44 for value in dct.values(): 45 if isinstance(value, types.FunctionType): 46 value.exposed = True 47 setattr(root, name.lower(), cls()) 48 Test = TestType('Test', (object,), {}) 49 50 class PathInfo(Test): 51 52 def default(self, *args): 53 return cherrypy.request.path_info 54 55 class Params(Test): 56 57 def index(self, thing): 58 return repr(thing) 59 60 def ismap(self, x, y): 61 return "Coordinates: %s, %s" % (x, y) 62 63 def default(self, *args, **kwargs): 64 return "args: %s kwargs: %s" % (args, sorted(kwargs.items())) 65 default._cp_config = {'request.query_string_encoding': 'latin1'} 66 67 class ParamErrorsCallable(object): 68 exposed = True 69 70 def __call__(self): 71 return "data" 72 73 class ParamErrors(Test): 74 75 def one_positional(self, param1): 76 return "data" 77 one_positional.exposed = True 78 79 def one_positional_args(self, param1, *args): 80 return "data" 81 one_positional_args.exposed = True 82 83 def one_positional_args_kwargs(self, param1, *args, **kwargs): 84 return "data" 85 one_positional_args_kwargs.exposed = True 86 87 def one_positional_kwargs(self, param1, **kwargs): 88 return "data" 89 one_positional_kwargs.exposed = True 90 91 def no_positional(self): 92 return "data" 93 no_positional.exposed = True 94 95 def no_positional_args(self, *args): 96 return "data" 97 no_positional_args.exposed = True 98 99 def no_positional_args_kwargs(self, *args, **kwargs): 100 return "data" 101 no_positional_args_kwargs.exposed = True 102 103 def no_positional_kwargs(self, **kwargs): 104 return "data" 105 no_positional_kwargs.exposed = True 106 107 callable_object = ParamErrorsCallable() 108 109 def raise_type_error(self, **kwargs): 110 raise TypeError("Client Error") 111 raise_type_error.exposed = True 112 113 def raise_type_error_with_default_param(self, x, y=None): 114 return '%d' % 'a' # throw an exception 115 raise_type_error_with_default_param.exposed = True 116 117 def callable_error_page(status, **kwargs): 118 return "Error %s - Well, I'm very sorry but you haven't paid!" % ( 119 status) 120 121 class Error(Test): 122 123 _cp_config = {'tools.log_tracebacks.on': True, 124 } 125 126 def reason_phrase(self): 127 raise cherrypy.HTTPError("410 Gone fishin'") 128 129 def custom(self, err='404'): 130 raise cherrypy.HTTPError( 131 int(err), "No, <b>really</b>, not found!") 132 custom._cp_config = { 133 'error_page.404': os.path.join(localDir, "static/index.html"), 134 'error_page.401': callable_error_page, 135 } 136 137 def custom_default(self): 138 return 1 + 'a' # raise an unexpected error 139 custom_default._cp_config = { 140 'error_page.default': callable_error_page} 141 142 def noexist(self): 143 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!") 144 noexist._cp_config = {'error_page.404': "nonexistent.html"} 145 146 def page_method(self): 147 raise ValueError() 148 149 def page_yield(self): 150 yield "howdy" 151 raise ValueError() 152 153 def page_streamed(self): 154 yield "word up" 155 raise ValueError() 156 yield "very oops" 157 page_streamed._cp_config = {"response.stream": True} 158 159 def cause_err_in_finalize(self): 160 # Since status must start with an int, this should error. 161 cherrypy.response.status = "ZOO OK" 162 cause_err_in_finalize._cp_config = { 163 'request.show_tracebacks': False} 164 165 def rethrow(self): 166 """Test that an error raised here will be thrown out to 167 the server. 168 """ 169 raise ValueError() 170 rethrow._cp_config = {'request.throw_errors': True} 171 172 class Expect(Test): 173 174 def expectation_failed(self): 175 expect = cherrypy.request.headers.elements("Expect") 176 if expect and expect[0].value != '100-continue': 177 raise cherrypy.HTTPError(400) 178 raise cherrypy.HTTPError(417, 'Expectation Failed') 179 180 class Headers(Test): 181 182 def default(self, headername): 183 """Spit back out the value for the requested header.""" 184 return cherrypy.request.headers[headername] 185 186 def doubledheaders(self): 187 # From https://bitbucket.org/cherrypy/cherrypy/issue/165: 188 # "header field names should not be case sensitive sayes the 189 # rfc. if i set a headerfield in complete lowercase i end up 190 # with two header fields, one in lowercase, the other in 191 # mixed-case." 192 193 # Set the most common headers 194 hMap = cherrypy.response.headers 195 hMap['content-type'] = "text/html" 196 hMap['content-length'] = 18 197 hMap['server'] = 'CherryPy headertest' 198 hMap['location'] = ('%s://%s:%s/headers/' 199 % (cherrypy.request.local.ip, 200 cherrypy.request.local.port, 201 cherrypy.request.scheme)) 202 203 # Set a rare header for fun 204 hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT' 205 206 return "double header test" 207 208 def ifmatch(self): 209 val = cherrypy.request.headers['If-Match'] 210 assert isinstance(val, unicodestr) 211 cherrypy.response.headers['ETag'] = val 212 return val 213 214 class HeaderElements(Test): 215 216 def get_elements(self, headername): 217 e = cherrypy.request.headers.elements(headername) 218 return "\n".join([unicodestr(x) for x in e]) 219 220 class Method(Test): 221 222 def index(self): 223 m = cherrypy.request.method 224 if m in defined_http_methods or m == "CONNECT": 225 return m 226 227 if m == "LINK": 228 raise cherrypy.HTTPError(405) 229 else: 230 raise cherrypy.HTTPError(501) 231 232 def parameterized(self, data): 233 return data 234 235 def request_body(self): 236 # This should be a file object (temp file), 237 # which CP will just pipe back out if we tell it to. 238 return cherrypy.request.body 239 240 def reachable(self): 241 return "success" 242 243 class Divorce: 244 245 """HTTP Method handlers shouldn't collide with normal method names. 246 For example, a GET-handler shouldn't collide with a method named 247 'get'. 248 249 If you build HTTP method dispatching into CherryPy, rewrite this 250 class to use your new dispatch mechanism and make sure that: 251 "GET /divorce HTTP/1.1" maps to divorce.index() and 252 "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get() 253 """ 254 255 documents = {} 256 257 def index(self): 258 yield "<h1>Choose your document</h1>\n" 259 yield "<ul>\n" 260 for id, contents in self.documents.items(): 261 yield ( 262 " <li><a href='/divorce/get?ID=%s'>%s</a>:" 263 " %s</li>\n" % (id, id, contents)) 264 yield "</ul>" 265 index.exposed = True 266 267 def get(self, ID): 268 return ("Divorce document %s: %s" % 269 (ID, self.documents.get(ID, "empty"))) 270 get.exposed = True 271 272 root.divorce = Divorce() 273 274 class ThreadLocal(Test): 275 276 def index(self): 277 existing = repr(getattr(cherrypy.request, "asdf", None)) 278 cherrypy.request.asdf = "rassfrassin" 279 return existing 280 281 appconf = { 282 '/method': { 283 'request.methods_with_bodies': ("POST", "PUT", "PROPFIND") 284 }, 285 } 286 cherrypy.tree.mount(root, config=appconf) 287 setup_server = staticmethod(setup_server) 288
289 - def test_scheme(self):
290 self.getPage("/scheme") 291 self.assertBody(self.scheme)
292
293 - def testRelativeURIPathInfo(self):
294 self.getPage("/pathinfo/foo/bar") 295 self.assertBody("/pathinfo/foo/bar")
296
297 - def testAbsoluteURIPathInfo(self):
298 # http://cherrypy.org/ticket/1061 299 self.getPage("http://localhost/pathinfo/foo/bar") 300 self.assertBody("/pathinfo/foo/bar")
301
302 - def testParams(self):
303 self.getPage("/params/?thing=a") 304 self.assertBody(repr(ntou("a"))) 305 306 self.getPage("/params/?thing=a&thing=b&thing=c") 307 self.assertBody(repr([ntou('a'), ntou('b'), ntou('c')])) 308 309 # Test friendly error message when given params are not accepted. 310 cherrypy.config.update({"request.show_mismatched_params": True}) 311 self.getPage("/params/?notathing=meeting") 312 self.assertInBody("Missing parameters: thing") 313 self.getPage("/params/?thing=meeting&notathing=meeting") 314 self.assertInBody("Unexpected query string parameters: notathing") 315 316 # Test ability to turn off friendly error messages 317 cherrypy.config.update({"request.show_mismatched_params": False}) 318 self.getPage("/params/?notathing=meeting") 319 self.assertInBody("Not Found") 320 self.getPage("/params/?thing=meeting&notathing=meeting") 321 self.assertInBody("Not Found") 322 323 # Test "% HEX HEX"-encoded URL, param keys, and values 324 self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville") 325 self.assertBody("args: %s kwargs: %s" % 326 (('\xd4 \xe3', 'cheese'), 327 [('Gruy\xe8re', ntou('Bulgn\xe9ville'))])) 328 329 # Make sure that encoded = and & get parsed correctly 330 self.getPage( 331 "/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2") 332 self.assertBody("args: %s kwargs: %s" % 333 (('code',), 334 [('url', ntou('http://cherrypy.org/index?a=1&b=2'))])) 335 336 # Test coordinates sent by <img ismap> 337 self.getPage("/params/ismap?223,114") 338 self.assertBody("Coordinates: 223, 114") 339 340 # Test "name[key]" dict-like params 341 self.getPage("/params/dictlike?a[1]=1&a[2]=2&b=foo&b[bar]=baz") 342 self.assertBody("args: %s kwargs: %s" % 343 (('dictlike',), 344 [('a[1]', ntou('1')), ('a[2]', ntou('2')), 345 ('b', ntou('foo')), ('b[bar]', ntou('baz'))]))
346
347 - def testParamErrors(self):
348 349 # test that all of the handlers work when given 350 # the correct parameters in order to ensure that the 351 # errors below aren't coming from some other source. 352 for uri in ( 353 '/paramerrors/one_positional?param1=foo', 354 '/paramerrors/one_positional_args?param1=foo', 355 '/paramerrors/one_positional_args/foo', 356 '/paramerrors/one_positional_args/foo/bar/baz', 357 '/paramerrors/one_positional_args_kwargs?' 358 'param1=foo&param2=bar', 359 '/paramerrors/one_positional_args_kwargs/foo?' 360 'param2=bar&param3=baz', 361 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?' 362 'param2=bar&param3=baz', 363 '/paramerrors/one_positional_kwargs?' 364 'param1=foo&param2=bar&param3=baz', 365 '/paramerrors/one_positional_kwargs/foo?' 366 'param4=foo&param2=bar&param3=baz', 367 '/paramerrors/no_positional', 368 '/paramerrors/no_positional_args/foo', 369 '/paramerrors/no_positional_args/foo/bar/baz', 370 '/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar', 371 '/paramerrors/no_positional_args_kwargs/foo?param2=bar', 372 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?' 373 'param2=bar&param3=baz', 374 '/paramerrors/no_positional_kwargs?param1=foo&param2=bar', 375 '/paramerrors/callable_object', 376 ): 377 self.getPage(uri) 378 self.assertStatus(200) 379 380 # query string parameters are part of the URI, so if they are wrong 381 # for a particular handler, the status MUST be a 404. 382 error_msgs = [ 383 'Missing parameters', 384 'Nothing matches the given URI', 385 'Multiple values for parameters', 386 'Unexpected query string parameters', 387 'Unexpected body parameters', 388 ] 389 for uri, msg in ( 390 ('/paramerrors/one_positional', error_msgs[0]), 391 ('/paramerrors/one_positional?foo=foo', error_msgs[0]), 392 ('/paramerrors/one_positional/foo/bar/baz', error_msgs[1]), 393 ('/paramerrors/one_positional/foo?param1=foo', error_msgs[2]), 394 ('/paramerrors/one_positional/foo?param1=foo&param2=foo', 395 error_msgs[2]), 396 ('/paramerrors/one_positional_args/foo?param1=foo&param2=foo', 397 error_msgs[2]), 398 ('/paramerrors/one_positional_args/foo/bar/baz?param2=foo', 399 error_msgs[3]), 400 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz?' 401 'param1=bar&param3=baz', 402 error_msgs[2]), 403 ('/paramerrors/one_positional_kwargs/foo?' 404 'param1=foo&param2=bar&param3=baz', 405 error_msgs[2]), 406 ('/paramerrors/no_positional/boo', error_msgs[1]), 407 ('/paramerrors/no_positional?param1=foo', error_msgs[3]), 408 ('/paramerrors/no_positional_args/boo?param1=foo', error_msgs[3]), 409 ('/paramerrors/no_positional_kwargs/boo?param1=foo', 410 error_msgs[1]), 411 ('/paramerrors/callable_object?param1=foo', error_msgs[3]), 412 ('/paramerrors/callable_object/boo', error_msgs[1]), 413 ): 414 for show_mismatched_params in (True, False): 415 cherrypy.config.update( 416 {'request.show_mismatched_params': show_mismatched_params}) 417 self.getPage(uri) 418 self.assertStatus(404) 419 if show_mismatched_params: 420 self.assertInBody(msg) 421 else: 422 self.assertInBody("Not Found") 423 424 # if body parameters are wrong, a 400 must be returned. 425 for uri, body, msg in ( 426 ('/paramerrors/one_positional/foo', 427 'param1=foo', error_msgs[2]), 428 ('/paramerrors/one_positional/foo', 429 'param1=foo&param2=foo', error_msgs[2]), 430 ('/paramerrors/one_positional_args/foo', 431 'param1=foo&param2=foo', error_msgs[2]), 432 ('/paramerrors/one_positional_args/foo/bar/baz', 433 'param2=foo', error_msgs[4]), 434 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 435 'param1=bar&param3=baz', error_msgs[2]), 436 ('/paramerrors/one_positional_kwargs/foo', 437 'param1=foo&param2=bar&param3=baz', error_msgs[2]), 438 ('/paramerrors/no_positional', 'param1=foo', error_msgs[4]), 439 ('/paramerrors/no_positional_args/boo', 440 'param1=foo', error_msgs[4]), 441 ('/paramerrors/callable_object', 'param1=foo', error_msgs[4]), 442 ): 443 for show_mismatched_params in (True, False): 444 cherrypy.config.update( 445 {'request.show_mismatched_params': show_mismatched_params}) 446 self.getPage(uri, method='POST', body=body) 447 self.assertStatus(400) 448 if show_mismatched_params: 449 self.assertInBody(msg) 450 else: 451 self.assertInBody("400 Bad") 452 453 # even if body parameters are wrong, if we get the uri wrong, then 454 # it's a 404 455 for uri, body, msg in ( 456 ('/paramerrors/one_positional?param2=foo', 457 'param1=foo', error_msgs[3]), 458 ('/paramerrors/one_positional/foo/bar', 459 'param2=foo', error_msgs[1]), 460 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 461 'param3=foo', error_msgs[3]), 462 ('/paramerrors/one_positional_kwargs/foo/bar', 463 'param2=bar&param3=baz', error_msgs[1]), 464 ('/paramerrors/no_positional?param1=foo', 465 'param2=foo', error_msgs[3]), 466 ('/paramerrors/no_positional_args/boo?param2=foo', 467 'param1=foo', error_msgs[3]), 468 ('/paramerrors/callable_object?param2=bar', 469 'param1=foo', error_msgs[3]), 470 ): 471 for show_mismatched_params in (True, False): 472 cherrypy.config.update( 473 {'request.show_mismatched_params': show_mismatched_params}) 474 self.getPage(uri, method='POST', body=body) 475 self.assertStatus(404) 476 if show_mismatched_params: 477 self.assertInBody(msg) 478 else: 479 self.assertInBody("Not Found") 480 481 # In the case that a handler raises a TypeError we should 482 # let that type error through. 483 for uri in ( 484 '/paramerrors/raise_type_error', 485 '/paramerrors/raise_type_error_with_default_param?x=0', 486 '/paramerrors/raise_type_error_with_default_param?x=0&y=0', 487 ): 488 self.getPage(uri, method='GET') 489 self.assertStatus(500) 490 self.assertTrue('Client Error', self.body)
491
492 - def testErrorHandling(self):
493 self.getPage("/error/missing") 494 self.assertStatus(404) 495 self.assertErrorPage(404, "The path '/error/missing' was not found.") 496 497 ignore = helper.webtest.ignored_exceptions 498 ignore.append(ValueError) 499 try: 500 valerr = '\n raise ValueError()\nValueError' 501 self.getPage("/error/page_method") 502 self.assertErrorPage(500, pattern=valerr) 503 504 self.getPage("/error/page_yield") 505 self.assertErrorPage(500, pattern=valerr) 506 507 if (cherrypy.server.protocol_version == "HTTP/1.0" or 508 getattr(cherrypy.server, "using_apache", False)): 509 self.getPage("/error/page_streamed") 510 # Because this error is raised after the response body has 511 # started, the status should not change to an error status. 512 self.assertStatus(200) 513 self.assertBody("word up") 514 else: 515 # Under HTTP/1.1, the chunked transfer-coding is used. 516 # The HTTP client will choke when the output is incomplete. 517 self.assertRaises((ValueError, IncompleteRead), self.getPage, 518 "/error/page_streamed") 519 520 # No traceback should be present 521 self.getPage("/error/cause_err_in_finalize") 522 msg = "Illegal response status from server ('ZOO' is non-numeric)." 523 self.assertErrorPage(500, msg, None) 524 finally: 525 ignore.pop() 526 527 # Test HTTPError with a reason-phrase in the status arg. 528 self.getPage('/error/reason_phrase') 529 self.assertStatus("410 Gone fishin'") 530 531 # Test custom error page for a specific error. 532 self.getPage("/error/custom") 533 self.assertStatus(404) 534 self.assertBody("Hello, world\r\n" + (" " * 499)) 535 536 # Test custom error page for a specific error. 537 self.getPage("/error/custom?err=401") 538 self.assertStatus(401) 539 self.assertBody( 540 "Error 401 Unauthorized - " 541 "Well, I'm very sorry but you haven't paid!") 542 543 # Test default custom error page. 544 self.getPage("/error/custom_default") 545 self.assertStatus(500) 546 self.assertBody( 547 "Error 500 Internal Server Error - " 548 "Well, I'm very sorry but you haven't paid!".ljust(513)) 549 550 # Test error in custom error page (ticket #305). 551 # Note that the message is escaped for HTML (ticket #310). 552 self.getPage("/error/noexist") 553 self.assertStatus(404) 554 if sys.version_info >= (3, 3): 555 exc_name = "FileNotFoundError" 556 else: 557 exc_name = "IOError" 558 msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />" 559 "In addition, the custom error page failed:\n<br />" 560 "%s: [Errno 2] " 561 "No such file or directory: 'nonexistent.html'") % (exc_name,) 562 self.assertInBody(msg) 563 564 if getattr(cherrypy.server, "using_apache", False): 565 pass 566 else: 567 # Test throw_errors (ticket #186). 568 self.getPage("/error/rethrow") 569 self.assertInBody("raise ValueError()")
570
571 - def testExpect(self):
572 e = ('Expect', '100-continue') 573 self.getPage("/headerelements/get_elements?headername=Expect", [e]) 574 self.assertBody('100-continue') 575 576 self.getPage("/expect/expectation_failed", [e]) 577 self.assertStatus(417)
578
579 - def testHeaderElements(self):
580 # Accept-* header elements should be sorted, with most preferred first. 581 h = [('Accept', 'audio/*; q=0.2, audio/basic')] 582 self.getPage("/headerelements/get_elements?headername=Accept", h) 583 self.assertStatus(200) 584 self.assertBody("audio/basic\n" 585 "audio/*;q=0.2") 586 587 h = [ 588 ('Accept', 589 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c') 590 ] 591 self.getPage("/headerelements/get_elements?headername=Accept", h) 592 self.assertStatus(200) 593 self.assertBody("text/x-c\n" 594 "text/html\n" 595 "text/x-dvi;q=0.8\n" 596 "text/plain;q=0.5") 597 598 # Test that more specific media ranges get priority. 599 h = [('Accept', 'text/*, text/html, text/html;level=1, */*')] 600 self.getPage("/headerelements/get_elements?headername=Accept", h) 601 self.assertStatus(200) 602 self.assertBody("text/html;level=1\n" 603 "text/html\n" 604 "text/*\n" 605 "*/*") 606 607 # Test Accept-Charset 608 h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')] 609 self.getPage( 610 "/headerelements/get_elements?headername=Accept-Charset", h) 611 self.assertStatus("200 OK") 612 self.assertBody("iso-8859-5\n" 613 "unicode-1-1;q=0.8") 614 615 # Test Accept-Encoding 616 h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')] 617 self.getPage( 618 "/headerelements/get_elements?headername=Accept-Encoding", h) 619 self.assertStatus("200 OK") 620 self.assertBody("gzip;q=1.0\n" 621 "identity;q=0.5\n" 622 "*;q=0") 623 624 # Test Accept-Language 625 h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')] 626 self.getPage( 627 "/headerelements/get_elements?headername=Accept-Language", h) 628 self.assertStatus("200 OK") 629 self.assertBody("da\n" 630 "en-gb;q=0.8\n" 631 "en;q=0.7") 632 633 # Test malformed header parsing. See 634 # https://bitbucket.org/cherrypy/cherrypy/issue/763. 635 self.getPage("/headerelements/get_elements?headername=Content-Type", 636 # Note the illegal trailing ";" 637 headers=[('Content-Type', 'text/html; charset=utf-8;')]) 638 self.assertStatus(200) 639 self.assertBody("text/html;charset=utf-8")
640
641 - def test_repeated_headers(self):
642 # Test that two request headers are collapsed into one. 643 # See https://bitbucket.org/cherrypy/cherrypy/issue/542. 644 self.getPage("/headers/Accept-Charset", 645 headers=[("Accept-Charset", "iso-8859-5"), 646 ("Accept-Charset", "unicode-1-1;q=0.8")]) 647 self.assertBody("iso-8859-5, unicode-1-1;q=0.8") 648 649 # Tests that each header only appears once, regardless of case. 650 self.getPage("/headers/doubledheaders") 651 self.assertBody("double header test") 652 hnames = [name.title() for name, val in self.headers] 653 for key in ['Content-Length', 'Content-Type', 'Date', 654 'Expires', 'Location', 'Server']: 655 self.assertEqual(hnames.count(key), 1, self.headers)
656
657 - def test_encoded_headers(self):
658 # First, make sure the innards work like expected. 659 self.assertEqual( 660 httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr")) 661 662 if cherrypy.server.protocol_version == "HTTP/1.1": 663 # Test RFC-2047-encoded request and response header values 664 u = ntou('\u212bngstr\xf6m', 'escape') 665 c = ntou("=E2=84=ABngstr=C3=B6m") 666 self.getPage("/headers/ifmatch", 667 [('If-Match', ntou('=?utf-8?q?%s?=') % c)]) 668 # The body should be utf-8 encoded. 669 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m")) 670 # But the Etag header should be RFC-2047 encoded (binary) 671 self.assertHeader("ETag", ntou('=?utf-8?b?4oSrbmdzdHLDtm0=?=')) 672 673 # Test a *LONG* RFC-2047-encoded request and response header value 674 self.getPage("/headers/ifmatch", 675 [('If-Match', ntou('=?utf-8?q?%s?=') % (c * 10))]) 676 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m") * 10) 677 # Note: this is different output for Python3, but it decodes fine. 678 etag = self.assertHeader( 679 "ETag", 680 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 681 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 682 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 683 '4oSrbmdzdHLDtm0=?=') 684 self.assertEqual(httputil.decode_TEXT(etag), u * 10)
685
686 - def test_header_presence(self):
687 # If we don't pass a Content-Type header, it should not be present 688 # in cherrypy.request.headers 689 self.getPage("/headers/Content-Type", 690 headers=[]) 691 self.assertStatus(500) 692 693 # If Content-Type is present in the request, it should be present in 694 # cherrypy.request.headers 695 self.getPage("/headers/Content-Type", 696 headers=[("Content-type", "application/json")]) 697 self.assertBody("application/json")
698
699 - def test_basic_HTTPMethods(self):
700 helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND") 701 702 # Test that all defined HTTP methods work. 703 for m in defined_http_methods: 704 self.getPage("/method/", method=m) 705 706 # HEAD requests should not return any body. 707 if m == "HEAD": 708 self.assertBody("") 709 elif m == "TRACE": 710 # Some HTTP servers (like modpy) have their own TRACE support 711 self.assertEqual(self.body[:5], ntob("TRACE")) 712 else: 713 self.assertBody(m) 714 715 # Request a PUT method with a form-urlencoded body 716 self.getPage("/method/parameterized", method="PUT", 717 body="data=on+top+of+other+things") 718 self.assertBody("on top of other things") 719 720 # Request a PUT method with a file body 721 b = "one thing on top of another" 722 h = [("Content-Type", "text/plain"), 723 ("Content-Length", str(len(b)))] 724 self.getPage("/method/request_body", headers=h, method="PUT", body=b) 725 self.assertStatus(200) 726 self.assertBody(b) 727 728 # Request a PUT method with a file body but no Content-Type. 729 # See https://bitbucket.org/cherrypy/cherrypy/issue/790. 730 b = ntob("one thing on top of another") 731 self.persistent = True 732 try: 733 conn = self.HTTP_CONN 734 conn.putrequest("PUT", "/method/request_body", skip_host=True) 735 conn.putheader("Host", self.HOST) 736 conn.putheader('Content-Length', str(len(b))) 737 conn.endheaders() 738 conn.send(b) 739 response = conn.response_class(conn.sock, method="PUT") 740 response.begin() 741 self.assertEqual(response.status, 200) 742 self.body = response.read() 743 self.assertBody(b) 744 finally: 745 self.persistent = False 746 747 # Request a PUT method with no body whatsoever (not an empty one). 748 # See https://bitbucket.org/cherrypy/cherrypy/issue/650. 749 # Provide a C-T or webtest will provide one (and a C-L) for us. 750 h = [("Content-Type", "text/plain")] 751 self.getPage("/method/reachable", headers=h, method="PUT") 752 self.assertStatus(411) 753 754 # Request a custom method with a request body 755 b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n' 756 '<propfind xmlns="DAV:"><prop><getlastmodified/>' 757 '</prop></propfind>') 758 h = [('Content-Type', 'text/xml'), 759 ('Content-Length', str(len(b)))] 760 self.getPage("/method/request_body", headers=h, 761 method="PROPFIND", body=b) 762 self.assertStatus(200) 763 self.assertBody(b) 764 765 # Request a disallowed method 766 self.getPage("/method/", method="LINK") 767 self.assertStatus(405) 768 769 # Request an unknown method 770 self.getPage("/method/", method="SEARCH") 771 self.assertStatus(501) 772 773 # For method dispatchers: make sure that an HTTP method doesn't 774 # collide with a virtual path atom. If you build HTTP-method 775 # dispatching into the core, rewrite these handlers to use 776 # your dispatch idioms. 777 self.getPage("/divorce/get?ID=13") 778 self.assertBody('Divorce document 13: empty') 779 self.assertStatus(200) 780 self.getPage("/divorce/", method="GET") 781 self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>') 782 self.assertStatus(200)
783
784 - def test_CONNECT_method(self):
785 if getattr(cherrypy.server, "using_apache", False): 786 return self.skip("skipped due to known Apache differences... ") 787 788 self.getPage("/method/", method="CONNECT") 789 self.assertBody("CONNECT")
790
791 - def testEmptyThreadlocals(self):
792 results = [] 793 for x in range(20): 794 self.getPage("/threadlocal/") 795 results.append(self.body) 796 self.assertEqual(results, [ntob("None")] * 20)
797