1import errno 2from http import client as httplib 3import logging 4import multiprocessing 5import os 6import signal 7import socket 8import string 9import subprocess 10import sys 11import time 12import unittest 13 14from waitress import server 15from waitress.compat import WIN 16from waitress.utilities import cleanup_unix_socket 17 18dn = os.path.dirname 19here = dn(__file__) 20 21 22class NullHandler(logging.Handler): # pragma: no cover 23 """A logging handler that swallows all emitted messages.""" 24 25 def emit(self, record): 26 pass 27 28 29def start_server(app, svr, queue, **kwargs): # pragma: no cover 30 """Run a fixture application.""" 31 logging.getLogger("waitress").addHandler(NullHandler()) 32 try_register_coverage() 33 svr(app, queue, **kwargs).run() 34 35 36def try_register_coverage(): # pragma: no cover 37 # Hack around multiprocessing exiting early and not triggering coverage's 38 # atexit handler by always registering a signal handler 39 40 if "COVERAGE_PROCESS_START" in os.environ: 41 42 def sigterm(*args): 43 sys.exit(0) 44 45 signal.signal(signal.SIGTERM, sigterm) 46 47 48class FixtureTcpWSGIServer(server.TcpWSGIServer): 49 """A version of TcpWSGIServer that relays back what it's bound to.""" 50 51 family = socket.AF_INET # Testing 52 53 def __init__(self, application, queue, **kw): # pragma: no cover 54 # Coverage doesn't see this as it's ran in a separate process. 55 kw["port"] = 0 # Bind to any available port. 56 super().__init__(application, **kw) 57 host, port = self.socket.getsockname() 58 59 if os.name == "nt": 60 host = "127.0.0.1" 61 queue.put((host, port)) 62 63 64class SubprocessTests: 65 66 # For nose: all tests may be ran in separate processes. 67 _multiprocess_can_split_ = True 68 69 exe = sys.executable 70 71 server = None 72 73 def start_subprocess(self, target, **kw): 74 # Spawn a server process. 75 self.queue = multiprocessing.Queue() 76 77 if "COVERAGE_RCFILE" in os.environ: 78 os.environ["COVERAGE_PROCESS_START"] = os.environ["COVERAGE_RCFILE"] 79 80 if not WIN: 81 ctx = multiprocessing.get_context("fork") 82 else: 83 ctx = multiprocessing.get_context("spawn") 84 85 self.proc = ctx.Process( 86 target=start_server, 87 args=(target, self.server, self.queue), 88 kwargs=kw, 89 ) 90 self.proc.start() 91 92 if self.proc.exitcode is not None: # pragma: no cover 93 raise RuntimeError("%s didn't start" % str(target)) 94 # Get the socket the server is listening on. 95 self.bound_to = self.queue.get(timeout=5) 96 self.sock = self.create_socket() 97 98 def stop_subprocess(self): 99 if self.proc.exitcode is None: 100 self.proc.terminate() 101 self.sock.close() 102 # This give us one FD back ... 103 self.queue.close() 104 self.proc.join() 105 106 def assertline(self, line, status, reason, version): 107 v, s, r = (x.strip() for x in line.split(None, 2)) 108 self.assertEqual(s, status.encode("latin-1")) 109 self.assertEqual(r, reason.encode("latin-1")) 110 self.assertEqual(v, version.encode("latin-1")) 111 112 def create_socket(self): 113 return socket.socket(self.server.family, socket.SOCK_STREAM) 114 115 def connect(self): 116 self.sock.connect(self.bound_to) 117 118 def make_http_connection(self): 119 raise NotImplementedError # pragma: no cover 120 121 def send_check_error(self, to_send): 122 self.sock.send(to_send) 123 124 125class TcpTests(SubprocessTests): 126 127 server = FixtureTcpWSGIServer 128 129 def make_http_connection(self): 130 return httplib.HTTPConnection(*self.bound_to) 131 132 133class SleepyThreadTests(TcpTests, unittest.TestCase): 134 # test that sleepy thread doesnt block other requests 135 136 def setUp(self): 137 from tests.fixtureapps import sleepy 138 139 self.start_subprocess(sleepy.app) 140 141 def tearDown(self): 142 self.stop_subprocess() 143 144 def test_it(self): 145 getline = os.path.join(here, "fixtureapps", "getline.py") 146 cmds = ( 147 [self.exe, getline, "http://%s:%d/sleepy" % self.bound_to], 148 [self.exe, getline, "http://%s:%d/" % self.bound_to], 149 ) 150 r, w = os.pipe() 151 procs = [] 152 153 for cmd in cmds: 154 procs.append(subprocess.Popen(cmd, stdout=w)) 155 time.sleep(3) 156 157 for proc in procs: 158 if proc.returncode is not None: # pragma: no cover 159 proc.terminate() 160 proc.wait() 161 # the notsleepy response should always be first returned (it sleeps 162 # for 2 seconds, then returns; the notsleepy response should be 163 # processed in the meantime) 164 result = os.read(r, 10000) 165 os.close(r) 166 os.close(w) 167 self.assertEqual(result, b"notsleepy returnedsleepy returned") 168 169 170class EchoTests: 171 def setUp(self): 172 from tests.fixtureapps import echo 173 174 self.start_subprocess( 175 echo.app, 176 trusted_proxy="*", 177 trusted_proxy_count=1, 178 trusted_proxy_headers={"x-forwarded-for", "x-forwarded-proto"}, 179 clear_untrusted_proxy_headers=True, 180 ) 181 182 def tearDown(self): 183 self.stop_subprocess() 184 185 def _read_echo(self, fp): 186 from tests.fixtureapps import echo 187 188 line, headers, body = read_http(fp) 189 190 return line, headers, echo.parse_response(body) 191 192 def test_date_and_server(self): 193 to_send = b"GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" 194 self.connect() 195 self.sock.send(to_send) 196 fp = self.sock.makefile("rb", 0) 197 line, headers, echo = self._read_echo(fp) 198 self.assertline(line, "200", "OK", "HTTP/1.0") 199 self.assertEqual(headers.get("server"), "waitress") 200 self.assertTrue(headers.get("date")) 201 202 def test_bad_host_header(self): 203 # https://corte.si/posts/code/pathod/pythonservers/index.html 204 to_send = b"GET / HTTP/1.0\r\n Host: 0\r\n\r\n" 205 self.connect() 206 self.sock.send(to_send) 207 fp = self.sock.makefile("rb", 0) 208 line, headers, response_body = read_http(fp) 209 self.assertline(line, "400", "Bad Request", "HTTP/1.0") 210 self.assertEqual(headers.get("server"), "waitress") 211 self.assertTrue(headers.get("date")) 212 213 def test_send_with_body(self): 214 to_send = b"GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" 215 to_send += b"hello" 216 self.connect() 217 self.sock.send(to_send) 218 fp = self.sock.makefile("rb", 0) 219 line, headers, echo = self._read_echo(fp) 220 self.assertline(line, "200", "OK", "HTTP/1.0") 221 self.assertEqual(echo.content_length, "5") 222 self.assertEqual(echo.body, b"hello") 223 224 def test_send_empty_body(self): 225 to_send = b"GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" 226 self.connect() 227 self.sock.send(to_send) 228 fp = self.sock.makefile("rb", 0) 229 line, headers, echo = self._read_echo(fp) 230 self.assertline(line, "200", "OK", "HTTP/1.0") 231 self.assertEqual(echo.content_length, "0") 232 self.assertEqual(echo.body, b"") 233 234 def test_multiple_requests_with_body(self): 235 orig_sock = self.sock 236 237 for x in range(3): 238 self.sock = self.create_socket() 239 self.test_send_with_body() 240 self.sock.close() 241 self.sock = orig_sock 242 243 def test_multiple_requests_without_body(self): 244 orig_sock = self.sock 245 246 for x in range(3): 247 self.sock = self.create_socket() 248 self.test_send_empty_body() 249 self.sock.close() 250 self.sock = orig_sock 251 252 def test_without_crlf(self): 253 data = b"Echo\r\nthis\r\nplease" 254 s = ( 255 b"GET / HTTP/1.0\r\n" 256 b"Connection: close\r\n" 257 b"Content-Length: %d\r\n" 258 b"\r\n" 259 b"%s" % (len(data), data) 260 ) 261 self.connect() 262 self.sock.send(s) 263 fp = self.sock.makefile("rb", 0) 264 line, headers, echo = self._read_echo(fp) 265 self.assertline(line, "200", "OK", "HTTP/1.0") 266 self.assertEqual(int(echo.content_length), len(data)) 267 self.assertEqual(len(echo.body), len(data)) 268 self.assertEqual(echo.body, (data)) 269 270 def test_large_body(self): 271 # 1024 characters. 272 body = b"This string has 32 characters.\r\n" * 32 273 s = b"GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(body), body) 274 self.connect() 275 self.sock.send(s) 276 fp = self.sock.makefile("rb", 0) 277 line, headers, echo = self._read_echo(fp) 278 self.assertline(line, "200", "OK", "HTTP/1.0") 279 self.assertEqual(echo.content_length, "1024") 280 self.assertEqual(echo.body, body) 281 282 def test_many_clients(self): 283 conns = [] 284 285 for n in range(50): 286 h = self.make_http_connection() 287 h.request("GET", "/", headers={"Accept": "text/plain"}) 288 conns.append(h) 289 responses = [] 290 291 for h in conns: 292 response = h.getresponse() 293 self.assertEqual(response.status, 200) 294 responses.append(response) 295 296 for response in responses: 297 response.read() 298 299 for h in conns: 300 h.close() 301 302 def test_chunking_request_without_content(self): 303 header = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" 304 self.connect() 305 self.sock.send(header) 306 self.sock.send(b"0\r\n\r\n") 307 fp = self.sock.makefile("rb", 0) 308 line, headers, echo = self._read_echo(fp) 309 self.assertline(line, "200", "OK", "HTTP/1.1") 310 self.assertEqual(echo.body, b"") 311 self.assertEqual(echo.content_length, "0") 312 self.assertFalse("transfer-encoding" in headers) 313 314 def test_chunking_request_with_content(self): 315 control_line = b"20;\r\n" # 20 hex = 32 dec 316 s = b"This string has 32 characters.\r\n" 317 expected = s * 12 318 header = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" 319 self.connect() 320 self.sock.send(header) 321 fp = self.sock.makefile("rb", 0) 322 323 for n in range(12): 324 self.sock.send(control_line) 325 self.sock.send(s) 326 self.sock.send(b"\r\n") # End the chunk 327 self.sock.send(b"0\r\n\r\n") 328 line, headers, echo = self._read_echo(fp) 329 self.assertline(line, "200", "OK", "HTTP/1.1") 330 self.assertEqual(echo.body, expected) 331 self.assertEqual(echo.content_length, str(len(expected))) 332 self.assertFalse("transfer-encoding" in headers) 333 334 def test_broken_chunked_encoding(self): 335 control_line = b"20;\r\n" # 20 hex = 32 dec 336 s = b"This string has 32 characters.\r\n" 337 to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" 338 to_send += control_line + s + b"\r\n" 339 # garbage in input 340 to_send += b"garbage\r\n" 341 self.connect() 342 self.sock.send(to_send) 343 fp = self.sock.makefile("rb", 0) 344 line, headers, response_body = read_http(fp) 345 # receiver caught garbage and turned it into a 400 346 self.assertline(line, "400", "Bad Request", "HTTP/1.1") 347 cl = int(headers["content-length"]) 348 self.assertEqual(cl, len(response_body)) 349 self.assertEqual( 350 sorted(headers.keys()), 351 ["connection", "content-length", "content-type", "date", "server"], 352 ) 353 self.assertEqual(headers["content-type"], "text/plain") 354 # connection has been closed 355 self.send_check_error(to_send) 356 self.assertRaises(ConnectionClosed, read_http, fp) 357 358 def test_broken_chunked_encoding_missing_chunk_end(self): 359 control_line = b"20;\r\n" # 20 hex = 32 dec 360 s = b"This string has 32 characters.\r\n" 361 to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" 362 to_send += control_line + s 363 # garbage in input 364 to_send += b"garbage" 365 self.connect() 366 self.sock.send(to_send) 367 fp = self.sock.makefile("rb", 0) 368 line, headers, response_body = read_http(fp) 369 # receiver caught garbage and turned it into a 400 370 self.assertline(line, "400", "Bad Request", "HTTP/1.1") 371 cl = int(headers["content-length"]) 372 self.assertEqual(cl, len(response_body)) 373 self.assertTrue(b"Chunk not properly terminated" in response_body) 374 self.assertEqual( 375 sorted(headers.keys()), 376 ["connection", "content-length", "content-type", "date", "server"], 377 ) 378 self.assertEqual(headers["content-type"], "text/plain") 379 # connection has been closed 380 self.send_check_error(to_send) 381 self.assertRaises(ConnectionClosed, read_http, fp) 382 383 def test_keepalive_http_10(self): 384 # Handling of Keep-Alive within HTTP 1.0 385 data = b"Default: Don't keep me alive" 386 s = b"GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) 387 self.connect() 388 self.sock.send(s) 389 response = httplib.HTTPResponse(self.sock) 390 response.begin() 391 self.assertEqual(int(response.status), 200) 392 connection = response.getheader("Connection", "") 393 # We sent no Connection: Keep-Alive header 394 # Connection: close (or no header) is default. 395 self.assertTrue(connection != "Keep-Alive") 396 397 def test_keepalive_http10_explicit(self): 398 # If header Connection: Keep-Alive is explicitly sent, 399 # we want to keept the connection open, we also need to return 400 # the corresponding header 401 data = b"Keep me alive" 402 s = ( 403 b"GET / HTTP/1.0\r\n" 404 b"Connection: Keep-Alive\r\n" 405 b"Content-Length: %d\r\n" 406 b"\r\n" 407 b"%s" % (len(data), data) 408 ) 409 self.connect() 410 self.sock.send(s) 411 response = httplib.HTTPResponse(self.sock) 412 response.begin() 413 self.assertEqual(int(response.status), 200) 414 connection = response.getheader("Connection", "") 415 self.assertEqual(connection, "Keep-Alive") 416 417 def test_keepalive_http_11(self): 418 # Handling of Keep-Alive within HTTP 1.1 419 420 # All connections are kept alive, unless stated otherwise 421 data = b"Default: Keep me alive" 422 s = b"GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) 423 self.connect() 424 self.sock.send(s) 425 response = httplib.HTTPResponse(self.sock) 426 response.begin() 427 self.assertEqual(int(response.status), 200) 428 self.assertTrue(response.getheader("connection") != "close") 429 430 def test_keepalive_http11_explicit(self): 431 # Explicitly set keep-alive 432 data = b"Default: Keep me alive" 433 s = ( 434 b"GET / HTTP/1.1\r\n" 435 b"Connection: keep-alive\r\n" 436 b"Content-Length: %d\r\n" 437 b"\r\n" 438 b"%s" % (len(data), data) 439 ) 440 self.connect() 441 self.sock.send(s) 442 response = httplib.HTTPResponse(self.sock) 443 response.begin() 444 self.assertEqual(int(response.status), 200) 445 self.assertTrue(response.getheader("connection") != "close") 446 447 def test_keepalive_http11_connclose(self): 448 # specifying Connection: close explicitly 449 data = b"Don't keep me alive" 450 s = ( 451 b"GET / HTTP/1.1\r\n" 452 b"Connection: close\r\n" 453 b"Content-Length: %d\r\n" 454 b"\r\n" 455 b"%s" % (len(data), data) 456 ) 457 self.connect() 458 self.sock.send(s) 459 response = httplib.HTTPResponse(self.sock) 460 response.begin() 461 self.assertEqual(int(response.status), 200) 462 self.assertEqual(response.getheader("connection"), "close") 463 464 def test_proxy_headers(self): 465 to_send = ( 466 b"GET / HTTP/1.0\r\n" 467 b"Content-Length: 0\r\n" 468 b"Host: www.google.com:8080\r\n" 469 b"X-Forwarded-For: 192.168.1.1\r\n" 470 b"X-Forwarded-Proto: https\r\n" 471 b"X-Forwarded-Port: 5000\r\n\r\n" 472 ) 473 self.connect() 474 self.sock.send(to_send) 475 fp = self.sock.makefile("rb", 0) 476 line, headers, echo = self._read_echo(fp) 477 self.assertline(line, "200", "OK", "HTTP/1.0") 478 self.assertEqual(headers.get("server"), "waitress") 479 self.assertTrue(headers.get("date")) 480 self.assertIsNone(echo.headers.get("X_FORWARDED_PORT")) 481 self.assertEqual(echo.headers["HOST"], "www.google.com:8080") 482 self.assertEqual(echo.scheme, "https") 483 self.assertEqual(echo.remote_addr, "192.168.1.1") 484 self.assertEqual(echo.remote_host, "192.168.1.1") 485 486 487class PipeliningTests: 488 def setUp(self): 489 from tests.fixtureapps import echo 490 491 self.start_subprocess(echo.app_body_only) 492 493 def tearDown(self): 494 self.stop_subprocess() 495 496 def test_pipelining(self): 497 s = ( 498 b"GET / HTTP/1.0\r\n" 499 b"Connection: %s\r\n" 500 b"Content-Length: %d\r\n" 501 b"\r\n" 502 b"%s" 503 ) 504 to_send = b"" 505 count = 25 506 507 for n in range(count): 508 body = b"Response #%d\r\n" % (n + 1) 509 510 if n + 1 < count: 511 conn = b"keep-alive" 512 else: 513 conn = b"close" 514 to_send += s % (conn, len(body), body) 515 516 self.connect() 517 self.sock.send(to_send) 518 fp = self.sock.makefile("rb", 0) 519 520 for n in range(count): 521 expect_body = b"Response #%d\r\n" % (n + 1) 522 line = fp.readline() # status line 523 version, status, reason = (x.strip() for x in line.split(None, 2)) 524 headers = parse_headers(fp) 525 length = int(headers.get("content-length")) or None 526 response_body = fp.read(length) 527 self.assertEqual(int(status), 200) 528 self.assertEqual(length, len(response_body)) 529 self.assertEqual(response_body, expect_body) 530 531 532class ExpectContinueTests: 533 def setUp(self): 534 from tests.fixtureapps import echo 535 536 self.start_subprocess(echo.app_body_only) 537 538 def tearDown(self): 539 self.stop_subprocess() 540 541 def test_expect_continue(self): 542 # specifying Connection: close explicitly 543 data = b"I have expectations" 544 to_send = ( 545 b"GET / HTTP/1.1\r\n" 546 b"Connection: close\r\n" 547 b"Content-Length: %d\r\n" 548 b"Expect: 100-continue\r\n" 549 b"\r\n" 550 b"%s" % (len(data), data) 551 ) 552 self.connect() 553 self.sock.send(to_send) 554 fp = self.sock.makefile("rb", 0) 555 line = fp.readline() # continue status line 556 version, status, reason = (x.strip() for x in line.split(None, 2)) 557 self.assertEqual(int(status), 100) 558 self.assertEqual(reason, b"Continue") 559 self.assertEqual(version, b"HTTP/1.1") 560 fp.readline() # blank line 561 line = fp.readline() # next status line 562 version, status, reason = (x.strip() for x in line.split(None, 2)) 563 headers = parse_headers(fp) 564 length = int(headers.get("content-length")) or None 565 response_body = fp.read(length) 566 self.assertEqual(int(status), 200) 567 self.assertEqual(length, len(response_body)) 568 self.assertEqual(response_body, data) 569 570 571class BadContentLengthTests: 572 def setUp(self): 573 from tests.fixtureapps import badcl 574 575 self.start_subprocess(badcl.app) 576 577 def tearDown(self): 578 self.stop_subprocess() 579 580 def test_short_body(self): 581 # check to see if server closes connection when body is too short 582 # for cl header 583 to_send = ( 584 b"GET /short_body HTTP/1.0\r\n" 585 b"Connection: Keep-Alive\r\n" 586 b"Content-Length: 0\r\n" 587 b"\r\n" 588 ) 589 self.connect() 590 self.sock.send(to_send) 591 fp = self.sock.makefile("rb", 0) 592 line = fp.readline() # status line 593 version, status, reason = (x.strip() for x in line.split(None, 2)) 594 headers = parse_headers(fp) 595 content_length = int(headers.get("content-length")) 596 response_body = fp.read(content_length) 597 self.assertEqual(int(status), 200) 598 self.assertNotEqual(content_length, len(response_body)) 599 self.assertEqual(len(response_body), content_length - 1) 600 self.assertEqual(response_body, b"abcdefghi") 601 # remote closed connection (despite keepalive header); not sure why 602 # first send succeeds 603 self.send_check_error(to_send) 604 self.assertRaises(ConnectionClosed, read_http, fp) 605 606 def test_long_body(self): 607 # check server doesnt close connection when body is too short 608 # for cl header 609 to_send = ( 610 b"GET /long_body HTTP/1.0\r\n" 611 b"Connection: Keep-Alive\r\n" 612 b"Content-Length: 0\r\n" 613 b"\r\n" 614 ) 615 self.connect() 616 self.sock.send(to_send) 617 fp = self.sock.makefile("rb", 0) 618 line = fp.readline() # status line 619 version, status, reason = (x.strip() for x in line.split(None, 2)) 620 headers = parse_headers(fp) 621 content_length = int(headers.get("content-length")) or None 622 response_body = fp.read(content_length) 623 self.assertEqual(int(status), 200) 624 self.assertEqual(content_length, len(response_body)) 625 self.assertEqual(response_body, b"abcdefgh") 626 # remote does not close connection (keepalive header) 627 self.sock.send(to_send) 628 fp = self.sock.makefile("rb", 0) 629 line = fp.readline() # status line 630 version, status, reason = (x.strip() for x in line.split(None, 2)) 631 headers = parse_headers(fp) 632 content_length = int(headers.get("content-length")) or None 633 response_body = fp.read(content_length) 634 self.assertEqual(int(status), 200) 635 636 637class NoContentLengthTests: 638 def setUp(self): 639 from tests.fixtureapps import nocl 640 641 self.start_subprocess(nocl.app) 642 643 def tearDown(self): 644 self.stop_subprocess() 645 646 def test_http10_generator(self): 647 body = string.ascii_letters.encode("latin-1") 648 to_send = ( 649 b"GET / HTTP/1.0\r\n" 650 b"Connection: Keep-Alive\r\n" 651 b"Content-Length: %d\r\n\r\n" % len(body) 652 ) 653 to_send += body 654 self.connect() 655 self.sock.send(to_send) 656 fp = self.sock.makefile("rb", 0) 657 line, headers, response_body = read_http(fp) 658 self.assertline(line, "200", "OK", "HTTP/1.0") 659 self.assertEqual(headers.get("content-length"), None) 660 self.assertEqual(headers.get("connection"), "close") 661 self.assertEqual(response_body, body) 662 # remote closed connection (despite keepalive header), because 663 # generators cannot have a content-length divined 664 self.send_check_error(to_send) 665 self.assertRaises(ConnectionClosed, read_http, fp) 666 667 def test_http10_list(self): 668 body = string.ascii_letters.encode("latin-1") 669 to_send = ( 670 b"GET /list HTTP/1.0\r\n" 671 b"Connection: Keep-Alive\r\n" 672 b"Content-Length: %d\r\n\r\n" % len(body) 673 ) 674 to_send += body 675 self.connect() 676 self.sock.send(to_send) 677 fp = self.sock.makefile("rb", 0) 678 line, headers, response_body = read_http(fp) 679 self.assertline(line, "200", "OK", "HTTP/1.0") 680 self.assertEqual(headers["content-length"], str(len(body))) 681 self.assertEqual(headers.get("connection"), "Keep-Alive") 682 self.assertEqual(response_body, body) 683 # remote keeps connection open because it divined the content length 684 # from a length-1 list 685 self.sock.send(to_send) 686 line, headers, response_body = read_http(fp) 687 self.assertline(line, "200", "OK", "HTTP/1.0") 688 689 def test_http10_listlentwo(self): 690 body = string.ascii_letters.encode("latin-1") 691 to_send = ( 692 b"GET /list_lentwo HTTP/1.0\r\n" 693 b"Connection: Keep-Alive\r\n" 694 b"Content-Length: %d\r\n\r\n" % len(body) 695 ) 696 to_send += body 697 self.connect() 698 self.sock.send(to_send) 699 fp = self.sock.makefile("rb", 0) 700 line, headers, response_body = read_http(fp) 701 self.assertline(line, "200", "OK", "HTTP/1.0") 702 self.assertEqual(headers.get("content-length"), None) 703 self.assertEqual(headers.get("connection"), "close") 704 self.assertEqual(response_body, body) 705 # remote closed connection (despite keepalive header), because 706 # lists of length > 1 cannot have their content length divined 707 self.send_check_error(to_send) 708 self.assertRaises(ConnectionClosed, read_http, fp) 709 710 def test_http11_generator(self): 711 body = string.ascii_letters 712 body = body.encode("latin-1") 713 to_send = b"GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) 714 to_send += body 715 self.connect() 716 self.sock.send(to_send) 717 fp = self.sock.makefile("rb") 718 line, headers, response_body = read_http(fp) 719 self.assertline(line, "200", "OK", "HTTP/1.1") 720 expected = b"" 721 722 for chunk in chunks(body, 10): 723 expected += b"%s\r\n%s\r\n" % ( 724 hex(len(chunk))[2:].upper().encode("latin-1"), 725 chunk, 726 ) 727 expected += b"0\r\n\r\n" 728 self.assertEqual(response_body, expected) 729 # connection is always closed at the end of a chunked response 730 self.send_check_error(to_send) 731 self.assertRaises(ConnectionClosed, read_http, fp) 732 733 def test_http11_list(self): 734 body = string.ascii_letters.encode("latin-1") 735 to_send = b"GET /list HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) 736 to_send += body 737 self.connect() 738 self.sock.send(to_send) 739 fp = self.sock.makefile("rb", 0) 740 line, headers, response_body = read_http(fp) 741 self.assertline(line, "200", "OK", "HTTP/1.1") 742 self.assertEqual(headers["content-length"], str(len(body))) 743 self.assertEqual(response_body, body) 744 # remote keeps connection open because it divined the content length 745 # from a length-1 list 746 self.sock.send(to_send) 747 line, headers, response_body = read_http(fp) 748 self.assertline(line, "200", "OK", "HTTP/1.1") 749 750 def test_http11_listlentwo(self): 751 body = string.ascii_letters.encode("latin-1") 752 to_send = b"GET /list_lentwo HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) 753 to_send += body 754 self.connect() 755 self.sock.send(to_send) 756 fp = self.sock.makefile("rb") 757 line, headers, response_body = read_http(fp) 758 self.assertline(line, "200", "OK", "HTTP/1.1") 759 expected = b"" 760 761 for chunk in (body[:1], body[1:]): 762 expected += b"%s\r\n%s\r\n" % ( 763 (hex(len(chunk))[2:].upper().encode("latin-1")), 764 chunk, 765 ) 766 expected += b"0\r\n\r\n" 767 self.assertEqual(response_body, expected) 768 # connection is always closed at the end of a chunked response 769 self.send_check_error(to_send) 770 self.assertRaises(ConnectionClosed, read_http, fp) 771 772 773class WriteCallbackTests: 774 def setUp(self): 775 from tests.fixtureapps import writecb 776 777 self.start_subprocess(writecb.app) 778 779 def tearDown(self): 780 self.stop_subprocess() 781 782 def test_short_body(self): 783 # check to see if server closes connection when body is too short 784 # for cl header 785 to_send = ( 786 b"GET /short_body HTTP/1.0\r\n" 787 b"Connection: Keep-Alive\r\n" 788 b"Content-Length: 0\r\n" 789 b"\r\n" 790 ) 791 self.connect() 792 self.sock.send(to_send) 793 fp = self.sock.makefile("rb", 0) 794 line, headers, response_body = read_http(fp) 795 # server trusts the content-length header (5) 796 self.assertline(line, "200", "OK", "HTTP/1.0") 797 cl = int(headers["content-length"]) 798 self.assertEqual(cl, 9) 799 self.assertNotEqual(cl, len(response_body)) 800 self.assertEqual(len(response_body), cl - 1) 801 self.assertEqual(response_body, b"abcdefgh") 802 # remote closed connection (despite keepalive header) 803 self.send_check_error(to_send) 804 self.assertRaises(ConnectionClosed, read_http, fp) 805 806 def test_long_body(self): 807 # check server doesnt close connection when body is too long 808 # for cl header 809 to_send = ( 810 b"GET /long_body HTTP/1.0\r\n" 811 b"Connection: Keep-Alive\r\n" 812 b"Content-Length: 0\r\n" 813 b"\r\n" 814 ) 815 self.connect() 816 self.sock.send(to_send) 817 fp = self.sock.makefile("rb", 0) 818 line, headers, response_body = read_http(fp) 819 content_length = int(headers.get("content-length")) or None 820 self.assertEqual(content_length, 9) 821 self.assertEqual(content_length, len(response_body)) 822 self.assertEqual(response_body, b"abcdefghi") 823 # remote does not close connection (keepalive header) 824 self.sock.send(to_send) 825 fp = self.sock.makefile("rb", 0) 826 line, headers, response_body = read_http(fp) 827 self.assertline(line, "200", "OK", "HTTP/1.0") 828 829 def test_equal_body(self): 830 # check server doesnt close connection when body is equal to 831 # cl header 832 to_send = ( 833 b"GET /equal_body HTTP/1.0\r\n" 834 b"Connection: Keep-Alive\r\n" 835 b"Content-Length: 0\r\n" 836 b"\r\n" 837 ) 838 self.connect() 839 self.sock.send(to_send) 840 fp = self.sock.makefile("rb", 0) 841 line, headers, response_body = read_http(fp) 842 content_length = int(headers.get("content-length")) or None 843 self.assertEqual(content_length, 9) 844 self.assertline(line, "200", "OK", "HTTP/1.0") 845 self.assertEqual(content_length, len(response_body)) 846 self.assertEqual(response_body, b"abcdefghi") 847 # remote does not close connection (keepalive header) 848 self.sock.send(to_send) 849 fp = self.sock.makefile("rb", 0) 850 line, headers, response_body = read_http(fp) 851 self.assertline(line, "200", "OK", "HTTP/1.0") 852 853 def test_no_content_length(self): 854 # wtf happens when there's no content-length 855 to_send = ( 856 b"GET /no_content_length HTTP/1.0\r\n" 857 b"Connection: Keep-Alive\r\n" 858 b"Content-Length: 0\r\n" 859 b"\r\n" 860 ) 861 self.connect() 862 self.sock.send(to_send) 863 fp = self.sock.makefile("rb", 0) 864 line = fp.readline() # status line 865 line, headers, response_body = read_http(fp) 866 content_length = headers.get("content-length") 867 self.assertEqual(content_length, None) 868 self.assertEqual(response_body, b"abcdefghi") 869 # remote closed connection (despite keepalive header) 870 self.send_check_error(to_send) 871 self.assertRaises(ConnectionClosed, read_http, fp) 872 873 874class TooLargeTests: 875 876 toobig = 1050 877 878 def setUp(self): 879 from tests.fixtureapps import toolarge 880 881 self.start_subprocess( 882 toolarge.app, max_request_header_size=1000, max_request_body_size=1000 883 ) 884 885 def tearDown(self): 886 self.stop_subprocess() 887 888 def test_request_headers_too_large_http11(self): 889 body = b"" 890 bad_headers = b"X-Random-Header: 100\r\n" * int(self.toobig / 20) 891 to_send = b"GET / HTTP/1.1\r\nContent-Length: 0\r\n" 892 to_send += bad_headers 893 to_send += b"\r\n\r\n" 894 to_send += body 895 self.connect() 896 self.sock.send(to_send) 897 fp = self.sock.makefile("rb") 898 response_line, headers, response_body = read_http(fp) 899 self.assertline( 900 response_line, "431", "Request Header Fields Too Large", "HTTP/1.0" 901 ) 902 self.assertEqual(headers["connection"], "close") 903 904 def test_request_body_too_large_with_wrong_cl_http10(self): 905 body = b"a" * self.toobig 906 to_send = b"GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" 907 to_send += body 908 self.connect() 909 self.sock.send(to_send) 910 fp = self.sock.makefile("rb") 911 # first request succeeds (content-length 5) 912 line, headers, response_body = read_http(fp) 913 self.assertline(line, "200", "OK", "HTTP/1.0") 914 cl = int(headers["content-length"]) 915 self.assertEqual(cl, len(response_body)) 916 # server trusts the content-length header; no pipelining, 917 # so request fulfilled, extra bytes are thrown away 918 # connection has been closed 919 self.send_check_error(to_send) 920 self.assertRaises(ConnectionClosed, read_http, fp) 921 922 def test_request_body_too_large_with_wrong_cl_http10_keepalive(self): 923 body = b"a" * self.toobig 924 to_send = ( 925 b"GET / HTTP/1.0\r\nContent-Length: 5\r\nConnection: Keep-Alive\r\n\r\n" 926 ) 927 to_send += body 928 self.connect() 929 self.sock.send(to_send) 930 fp = self.sock.makefile("rb") 931 # first request succeeds (content-length 5) 932 line, headers, response_body = read_http(fp) 933 self.assertline(line, "200", "OK", "HTTP/1.0") 934 cl = int(headers["content-length"]) 935 self.assertEqual(cl, len(response_body)) 936 line, headers, response_body = read_http(fp) 937 self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0") 938 cl = int(headers["content-length"]) 939 self.assertEqual(cl, len(response_body)) 940 # connection has been closed 941 self.send_check_error(to_send) 942 self.assertRaises(ConnectionClosed, read_http, fp) 943 944 def test_request_body_too_large_with_no_cl_http10(self): 945 body = b"a" * self.toobig 946 to_send = b"GET / HTTP/1.0\r\n\r\n" 947 to_send += body 948 self.connect() 949 self.sock.send(to_send) 950 fp = self.sock.makefile("rb", 0) 951 line, headers, response_body = read_http(fp) 952 self.assertline(line, "200", "OK", "HTTP/1.0") 953 cl = int(headers["content-length"]) 954 self.assertEqual(cl, len(response_body)) 955 # extra bytes are thrown away (no pipelining), connection closed 956 self.send_check_error(to_send) 957 self.assertRaises(ConnectionClosed, read_http, fp) 958 959 def test_request_body_too_large_with_no_cl_http10_keepalive(self): 960 body = b"a" * self.toobig 961 to_send = b"GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" 962 to_send += body 963 self.connect() 964 self.sock.send(to_send) 965 fp = self.sock.makefile("rb", 0) 966 line, headers, response_body = read_http(fp) 967 # server trusts the content-length header (assumed zero) 968 self.assertline(line, "200", "OK", "HTTP/1.0") 969 cl = int(headers["content-length"]) 970 self.assertEqual(cl, len(response_body)) 971 line, headers, response_body = read_http(fp) 972 # next response overruns because the extra data appears to be 973 # header data 974 self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0") 975 cl = int(headers["content-length"]) 976 self.assertEqual(cl, len(response_body)) 977 # connection has been closed 978 self.send_check_error(to_send) 979 self.assertRaises(ConnectionClosed, read_http, fp) 980 981 def test_request_body_too_large_with_wrong_cl_http11(self): 982 body = b"a" * self.toobig 983 to_send = b"GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\n" 984 to_send += body 985 self.connect() 986 self.sock.send(to_send) 987 fp = self.sock.makefile("rb") 988 # first request succeeds (content-length 5) 989 line, headers, response_body = read_http(fp) 990 self.assertline(line, "200", "OK", "HTTP/1.1") 991 cl = int(headers["content-length"]) 992 self.assertEqual(cl, len(response_body)) 993 # second response is an error response 994 line, headers, response_body = read_http(fp) 995 self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0") 996 cl = int(headers["content-length"]) 997 self.assertEqual(cl, len(response_body)) 998 # connection has been closed 999 self.send_check_error(to_send) 1000 self.assertRaises(ConnectionClosed, read_http, fp) 1001 1002 def test_request_body_too_large_with_wrong_cl_http11_connclose(self): 1003 body = b"a" * self.toobig 1004 to_send = b"GET / HTTP/1.1\r\nContent-Length: 5\r\nConnection: close\r\n\r\n" 1005 to_send += body 1006 self.connect() 1007 self.sock.send(to_send) 1008 fp = self.sock.makefile("rb", 0) 1009 line, headers, response_body = read_http(fp) 1010 # server trusts the content-length header (5) 1011 self.assertline(line, "200", "OK", "HTTP/1.1") 1012 cl = int(headers["content-length"]) 1013 self.assertEqual(cl, len(response_body)) 1014 # connection has been closed 1015 self.send_check_error(to_send) 1016 self.assertRaises(ConnectionClosed, read_http, fp) 1017 1018 def test_request_body_too_large_with_no_cl_http11(self): 1019 body = b"a" * self.toobig 1020 to_send = b"GET / HTTP/1.1\r\n\r\n" 1021 to_send += body 1022 self.connect() 1023 self.sock.send(to_send) 1024 fp = self.sock.makefile("rb") 1025 # server trusts the content-length header (assumed 0) 1026 line, headers, response_body = read_http(fp) 1027 self.assertline(line, "200", "OK", "HTTP/1.1") 1028 cl = int(headers["content-length"]) 1029 self.assertEqual(cl, len(response_body)) 1030 # server assumes pipelined requests due to http/1.1, and the first 1031 # request was assumed c-l 0 because it had no content-length header, 1032 # so entire body looks like the header of the subsequent request 1033 # second response is an error response 1034 line, headers, response_body = read_http(fp) 1035 self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0") 1036 cl = int(headers["content-length"]) 1037 self.assertEqual(cl, len(response_body)) 1038 # connection has been closed 1039 self.send_check_error(to_send) 1040 self.assertRaises(ConnectionClosed, read_http, fp) 1041 1042 def test_request_body_too_large_with_no_cl_http11_connclose(self): 1043 body = b"a" * self.toobig 1044 to_send = b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n" 1045 to_send += body 1046 self.connect() 1047 self.sock.send(to_send) 1048 fp = self.sock.makefile("rb", 0) 1049 line, headers, response_body = read_http(fp) 1050 # server trusts the content-length header (assumed 0) 1051 self.assertline(line, "200", "OK", "HTTP/1.1") 1052 cl = int(headers["content-length"]) 1053 self.assertEqual(cl, len(response_body)) 1054 # connection has been closed 1055 self.send_check_error(to_send) 1056 self.assertRaises(ConnectionClosed, read_http, fp) 1057 1058 def test_request_body_too_large_chunked_encoding(self): 1059 control_line = b"20;\r\n" # 20 hex = 32 dec 1060 s = b"This string has 32 characters.\r\n" 1061 to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" 1062 repeat = control_line + s 1063 to_send += repeat * ((self.toobig // len(repeat)) + 1) 1064 self.connect() 1065 self.sock.send(to_send) 1066 fp = self.sock.makefile("rb", 0) 1067 line, headers, response_body = read_http(fp) 1068 # body bytes counter caught a max_request_body_size overrun 1069 self.assertline(line, "413", "Request Entity Too Large", "HTTP/1.1") 1070 cl = int(headers["content-length"]) 1071 self.assertEqual(cl, len(response_body)) 1072 self.assertEqual(headers["content-type"], "text/plain") 1073 # connection has been closed 1074 self.send_check_error(to_send) 1075 self.assertRaises(ConnectionClosed, read_http, fp) 1076 1077 1078class InternalServerErrorTests: 1079 def setUp(self): 1080 from tests.fixtureapps import error 1081 1082 self.start_subprocess(error.app, expose_tracebacks=True) 1083 1084 def tearDown(self): 1085 self.stop_subprocess() 1086 1087 def test_before_start_response_http_10(self): 1088 to_send = b"GET /before_start_response HTTP/1.0\r\n\r\n" 1089 self.connect() 1090 self.sock.send(to_send) 1091 fp = self.sock.makefile("rb", 0) 1092 line, headers, response_body = read_http(fp) 1093 self.assertline(line, "500", "Internal Server Error", "HTTP/1.0") 1094 cl = int(headers["content-length"]) 1095 self.assertEqual(cl, len(response_body)) 1096 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1097 self.assertEqual(headers["connection"], "close") 1098 # connection has been closed 1099 self.send_check_error(to_send) 1100 self.assertRaises(ConnectionClosed, read_http, fp) 1101 1102 def test_before_start_response_http_11(self): 1103 to_send = b"GET /before_start_response HTTP/1.1\r\n\r\n" 1104 self.connect() 1105 self.sock.send(to_send) 1106 fp = self.sock.makefile("rb", 0) 1107 line, headers, response_body = read_http(fp) 1108 self.assertline(line, "500", "Internal Server Error", "HTTP/1.1") 1109 cl = int(headers["content-length"]) 1110 self.assertEqual(cl, len(response_body)) 1111 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1112 self.assertEqual( 1113 sorted(headers.keys()), 1114 ["connection", "content-length", "content-type", "date", "server"], 1115 ) 1116 # connection has been closed 1117 self.send_check_error(to_send) 1118 self.assertRaises(ConnectionClosed, read_http, fp) 1119 1120 def test_before_start_response_http_11_close(self): 1121 to_send = b"GET /before_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" 1122 self.connect() 1123 self.sock.send(to_send) 1124 fp = self.sock.makefile("rb", 0) 1125 line, headers, response_body = read_http(fp) 1126 self.assertline(line, "500", "Internal Server Error", "HTTP/1.1") 1127 cl = int(headers["content-length"]) 1128 self.assertEqual(cl, len(response_body)) 1129 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1130 self.assertEqual( 1131 sorted(headers.keys()), 1132 ["connection", "content-length", "content-type", "date", "server"], 1133 ) 1134 self.assertEqual(headers["connection"], "close") 1135 # connection has been closed 1136 self.send_check_error(to_send) 1137 self.assertRaises(ConnectionClosed, read_http, fp) 1138 1139 def test_after_start_response_http10(self): 1140 to_send = b"GET /after_start_response HTTP/1.0\r\n\r\n" 1141 self.connect() 1142 self.sock.send(to_send) 1143 fp = self.sock.makefile("rb", 0) 1144 line, headers, response_body = read_http(fp) 1145 self.assertline(line, "500", "Internal Server Error", "HTTP/1.0") 1146 cl = int(headers["content-length"]) 1147 self.assertEqual(cl, len(response_body)) 1148 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1149 self.assertEqual( 1150 sorted(headers.keys()), 1151 ["connection", "content-length", "content-type", "date", "server"], 1152 ) 1153 self.assertEqual(headers["connection"], "close") 1154 # connection has been closed 1155 self.send_check_error(to_send) 1156 self.assertRaises(ConnectionClosed, read_http, fp) 1157 1158 def test_after_start_response_http11(self): 1159 to_send = b"GET /after_start_response HTTP/1.1\r\n\r\n" 1160 self.connect() 1161 self.sock.send(to_send) 1162 fp = self.sock.makefile("rb", 0) 1163 line, headers, response_body = read_http(fp) 1164 self.assertline(line, "500", "Internal Server Error", "HTTP/1.1") 1165 cl = int(headers["content-length"]) 1166 self.assertEqual(cl, len(response_body)) 1167 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1168 self.assertEqual( 1169 sorted(headers.keys()), 1170 ["connection", "content-length", "content-type", "date", "server"], 1171 ) 1172 # connection has been closed 1173 self.send_check_error(to_send) 1174 self.assertRaises(ConnectionClosed, read_http, fp) 1175 1176 def test_after_start_response_http11_close(self): 1177 to_send = b"GET /after_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" 1178 self.connect() 1179 self.sock.send(to_send) 1180 fp = self.sock.makefile("rb", 0) 1181 line, headers, response_body = read_http(fp) 1182 self.assertline(line, "500", "Internal Server Error", "HTTP/1.1") 1183 cl = int(headers["content-length"]) 1184 self.assertEqual(cl, len(response_body)) 1185 self.assertTrue(response_body.startswith(b"Internal Server Error")) 1186 self.assertEqual( 1187 sorted(headers.keys()), 1188 ["connection", "content-length", "content-type", "date", "server"], 1189 ) 1190 self.assertEqual(headers["connection"], "close") 1191 # connection has been closed 1192 self.send_check_error(to_send) 1193 self.assertRaises(ConnectionClosed, read_http, fp) 1194 1195 def test_after_write_cb(self): 1196 to_send = b"GET /after_write_cb HTTP/1.1\r\n\r\n" 1197 self.connect() 1198 self.sock.send(to_send) 1199 fp = self.sock.makefile("rb", 0) 1200 line, headers, response_body = read_http(fp) 1201 self.assertline(line, "200", "OK", "HTTP/1.1") 1202 self.assertEqual(response_body, b"") 1203 # connection has been closed 1204 self.send_check_error(to_send) 1205 self.assertRaises(ConnectionClosed, read_http, fp) 1206 1207 def test_in_generator(self): 1208 to_send = b"GET /in_generator HTTP/1.1\r\n\r\n" 1209 self.connect() 1210 self.sock.send(to_send) 1211 fp = self.sock.makefile("rb", 0) 1212 line, headers, response_body = read_http(fp) 1213 self.assertline(line, "200", "OK", "HTTP/1.1") 1214 self.assertEqual(response_body, b"") 1215 # connection has been closed 1216 self.send_check_error(to_send) 1217 self.assertRaises(ConnectionClosed, read_http, fp) 1218 1219 1220class FileWrapperTests: 1221 def setUp(self): 1222 from tests.fixtureapps import filewrapper 1223 1224 self.start_subprocess(filewrapper.app) 1225 1226 def tearDown(self): 1227 self.stop_subprocess() 1228 1229 def test_filelike_http11(self): 1230 to_send = b"GET /filelike HTTP/1.1\r\n\r\n" 1231 1232 self.connect() 1233 1234 for t in range(0, 2): 1235 self.sock.send(to_send) 1236 fp = self.sock.makefile("rb", 0) 1237 line, headers, response_body = read_http(fp) 1238 self.assertline(line, "200", "OK", "HTTP/1.1") 1239 cl = int(headers["content-length"]) 1240 self.assertEqual(cl, len(response_body)) 1241 ct = headers["content-type"] 1242 self.assertEqual(ct, "image/jpeg") 1243 self.assertTrue(b"\377\330\377" in response_body) 1244 # connection has not been closed 1245 1246 def test_filelike_nocl_http11(self): 1247 to_send = b"GET /filelike_nocl HTTP/1.1\r\n\r\n" 1248 1249 self.connect() 1250 1251 for t in range(0, 2): 1252 self.sock.send(to_send) 1253 fp = self.sock.makefile("rb", 0) 1254 line, headers, response_body = read_http(fp) 1255 self.assertline(line, "200", "OK", "HTTP/1.1") 1256 cl = int(headers["content-length"]) 1257 self.assertEqual(cl, len(response_body)) 1258 ct = headers["content-type"] 1259 self.assertEqual(ct, "image/jpeg") 1260 self.assertTrue(b"\377\330\377" in response_body) 1261 # connection has not been closed 1262 1263 def test_filelike_shortcl_http11(self): 1264 to_send = b"GET /filelike_shortcl HTTP/1.1\r\n\r\n" 1265 1266 self.connect() 1267 1268 for t in range(0, 2): 1269 self.sock.send(to_send) 1270 fp = self.sock.makefile("rb", 0) 1271 line, headers, response_body = read_http(fp) 1272 self.assertline(line, "200", "OK", "HTTP/1.1") 1273 cl = int(headers["content-length"]) 1274 self.assertEqual(cl, 1) 1275 self.assertEqual(cl, len(response_body)) 1276 ct = headers["content-type"] 1277 self.assertEqual(ct, "image/jpeg") 1278 self.assertTrue(b"\377" in response_body) 1279 # connection has not been closed 1280 1281 def test_filelike_longcl_http11(self): 1282 to_send = b"GET /filelike_longcl HTTP/1.1\r\n\r\n" 1283 1284 self.connect() 1285 1286 for t in range(0, 2): 1287 self.sock.send(to_send) 1288 fp = self.sock.makefile("rb", 0) 1289 line, headers, response_body = read_http(fp) 1290 self.assertline(line, "200", "OK", "HTTP/1.1") 1291 cl = int(headers["content-length"]) 1292 self.assertEqual(cl, len(response_body)) 1293 ct = headers["content-type"] 1294 self.assertEqual(ct, "image/jpeg") 1295 self.assertTrue(b"\377\330\377" in response_body) 1296 # connection has not been closed 1297 1298 def test_notfilelike_http11(self): 1299 to_send = b"GET /notfilelike HTTP/1.1\r\n\r\n" 1300 1301 self.connect() 1302 1303 for t in range(0, 2): 1304 self.sock.send(to_send) 1305 fp = self.sock.makefile("rb", 0) 1306 line, headers, response_body = read_http(fp) 1307 self.assertline(line, "200", "OK", "HTTP/1.1") 1308 cl = int(headers["content-length"]) 1309 self.assertEqual(cl, len(response_body)) 1310 ct = headers["content-type"] 1311 self.assertEqual(ct, "image/jpeg") 1312 self.assertTrue(b"\377\330\377" in response_body) 1313 # connection has not been closed 1314 1315 def test_notfilelike_iobase_http11(self): 1316 to_send = b"GET /notfilelike_iobase HTTP/1.1\r\n\r\n" 1317 1318 self.connect() 1319 1320 for t in range(0, 2): 1321 self.sock.send(to_send) 1322 fp = self.sock.makefile("rb", 0) 1323 line, headers, response_body = read_http(fp) 1324 self.assertline(line, "200", "OK", "HTTP/1.1") 1325 cl = int(headers["content-length"]) 1326 self.assertEqual(cl, len(response_body)) 1327 ct = headers["content-type"] 1328 self.assertEqual(ct, "image/jpeg") 1329 self.assertTrue(b"\377\330\377" in response_body) 1330 # connection has not been closed 1331 1332 def test_notfilelike_nocl_http11(self): 1333 to_send = b"GET /notfilelike_nocl HTTP/1.1\r\n\r\n" 1334 1335 self.connect() 1336 1337 self.sock.send(to_send) 1338 fp = self.sock.makefile("rb", 0) 1339 line, headers, response_body = read_http(fp) 1340 self.assertline(line, "200", "OK", "HTTP/1.1") 1341 ct = headers["content-type"] 1342 self.assertEqual(ct, "image/jpeg") 1343 self.assertTrue(b"\377\330\377" in response_body) 1344 # connection has been closed (no content-length) 1345 self.send_check_error(to_send) 1346 self.assertRaises(ConnectionClosed, read_http, fp) 1347 1348 def test_notfilelike_shortcl_http11(self): 1349 to_send = b"GET /notfilelike_shortcl HTTP/1.1\r\n\r\n" 1350 1351 self.connect() 1352 1353 for t in range(0, 2): 1354 self.sock.send(to_send) 1355 fp = self.sock.makefile("rb", 0) 1356 line, headers, response_body = read_http(fp) 1357 self.assertline(line, "200", "OK", "HTTP/1.1") 1358 cl = int(headers["content-length"]) 1359 self.assertEqual(cl, 1) 1360 self.assertEqual(cl, len(response_body)) 1361 ct = headers["content-type"] 1362 self.assertEqual(ct, "image/jpeg") 1363 self.assertTrue(b"\377" in response_body) 1364 # connection has not been closed 1365 1366 def test_notfilelike_longcl_http11(self): 1367 to_send = b"GET /notfilelike_longcl HTTP/1.1\r\n\r\n" 1368 1369 self.connect() 1370 1371 self.sock.send(to_send) 1372 fp = self.sock.makefile("rb", 0) 1373 line, headers, response_body = read_http(fp) 1374 self.assertline(line, "200", "OK", "HTTP/1.1") 1375 cl = int(headers["content-length"]) 1376 self.assertEqual(cl, len(response_body) + 10) 1377 ct = headers["content-type"] 1378 self.assertEqual(ct, "image/jpeg") 1379 self.assertTrue(b"\377\330\377" in response_body) 1380 # connection has been closed 1381 self.send_check_error(to_send) 1382 self.assertRaises(ConnectionClosed, read_http, fp) 1383 1384 def test_filelike_http10(self): 1385 to_send = b"GET /filelike HTTP/1.0\r\n\r\n" 1386 1387 self.connect() 1388 1389 self.sock.send(to_send) 1390 fp = self.sock.makefile("rb", 0) 1391 line, headers, response_body = read_http(fp) 1392 self.assertline(line, "200", "OK", "HTTP/1.0") 1393 cl = int(headers["content-length"]) 1394 self.assertEqual(cl, len(response_body)) 1395 ct = headers["content-type"] 1396 self.assertEqual(ct, "image/jpeg") 1397 self.assertTrue(b"\377\330\377" in response_body) 1398 # connection has been closed 1399 self.send_check_error(to_send) 1400 self.assertRaises(ConnectionClosed, read_http, fp) 1401 1402 def test_filelike_nocl_http10(self): 1403 to_send = b"GET /filelike_nocl HTTP/1.0\r\n\r\n" 1404 1405 self.connect() 1406 1407 self.sock.send(to_send) 1408 fp = self.sock.makefile("rb", 0) 1409 line, headers, response_body = read_http(fp) 1410 self.assertline(line, "200", "OK", "HTTP/1.0") 1411 cl = int(headers["content-length"]) 1412 self.assertEqual(cl, len(response_body)) 1413 ct = headers["content-type"] 1414 self.assertEqual(ct, "image/jpeg") 1415 self.assertTrue(b"\377\330\377" in response_body) 1416 # connection has been closed 1417 self.send_check_error(to_send) 1418 self.assertRaises(ConnectionClosed, read_http, fp) 1419 1420 def test_notfilelike_http10(self): 1421 to_send = b"GET /notfilelike HTTP/1.0\r\n\r\n" 1422 1423 self.connect() 1424 1425 self.sock.send(to_send) 1426 fp = self.sock.makefile("rb", 0) 1427 line, headers, response_body = read_http(fp) 1428 self.assertline(line, "200", "OK", "HTTP/1.0") 1429 cl = int(headers["content-length"]) 1430 self.assertEqual(cl, len(response_body)) 1431 ct = headers["content-type"] 1432 self.assertEqual(ct, "image/jpeg") 1433 self.assertTrue(b"\377\330\377" in response_body) 1434 # connection has been closed 1435 self.send_check_error(to_send) 1436 self.assertRaises(ConnectionClosed, read_http, fp) 1437 1438 def test_notfilelike_nocl_http10(self): 1439 to_send = b"GET /notfilelike_nocl HTTP/1.0\r\n\r\n" 1440 1441 self.connect() 1442 1443 self.sock.send(to_send) 1444 fp = self.sock.makefile("rb", 0) 1445 line, headers, response_body = read_http(fp) 1446 self.assertline(line, "200", "OK", "HTTP/1.0") 1447 ct = headers["content-type"] 1448 self.assertEqual(ct, "image/jpeg") 1449 self.assertTrue(b"\377\330\377" in response_body) 1450 # connection has been closed (no content-length) 1451 self.send_check_error(to_send) 1452 self.assertRaises(ConnectionClosed, read_http, fp) 1453 1454 1455class TcpEchoTests(EchoTests, TcpTests, unittest.TestCase): 1456 pass 1457 1458 1459class TcpPipeliningTests(PipeliningTests, TcpTests, unittest.TestCase): 1460 pass 1461 1462 1463class TcpExpectContinueTests(ExpectContinueTests, TcpTests, unittest.TestCase): 1464 pass 1465 1466 1467class TcpBadContentLengthTests(BadContentLengthTests, TcpTests, unittest.TestCase): 1468 pass 1469 1470 1471class TcpNoContentLengthTests(NoContentLengthTests, TcpTests, unittest.TestCase): 1472 pass 1473 1474 1475class TcpWriteCallbackTests(WriteCallbackTests, TcpTests, unittest.TestCase): 1476 pass 1477 1478 1479class TcpTooLargeTests(TooLargeTests, TcpTests, unittest.TestCase): 1480 pass 1481 1482 1483class TcpInternalServerErrorTests( 1484 InternalServerErrorTests, TcpTests, unittest.TestCase 1485): 1486 pass 1487 1488 1489class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase): 1490 pass 1491 1492 1493if hasattr(socket, "AF_UNIX"): 1494 1495 class FixtureUnixWSGIServer(server.UnixWSGIServer): 1496 """A version of UnixWSGIServer that relays back what it's bound to.""" 1497 1498 family = socket.AF_UNIX # Testing 1499 1500 def __init__(self, application, queue, **kw): # pragma: no cover 1501 # Coverage doesn't see this as it's ran in a separate process. 1502 # To permit parallel testing, use a PID-dependent socket. 1503 kw["unix_socket"] = "/tmp/waitress.test-%d.sock" % os.getpid() 1504 super().__init__(application, **kw) 1505 queue.put(self.socket.getsockname()) 1506 1507 class UnixTests(SubprocessTests): 1508 1509 server = FixtureUnixWSGIServer 1510 1511 def make_http_connection(self): 1512 return UnixHTTPConnection(self.bound_to) 1513 1514 def stop_subprocess(self): 1515 super().stop_subprocess() 1516 cleanup_unix_socket(self.bound_to) 1517 1518 def send_check_error(self, to_send): 1519 # Unlike inet domain sockets, Unix domain sockets can trigger a 1520 # 'Broken pipe' error when the socket it closed. 1521 try: 1522 self.sock.send(to_send) 1523 except OSError as exc: 1524 valid_errors = {errno.EPIPE, errno.ENOTCONN} 1525 self.assertIn(get_errno(exc), valid_errors) 1526 1527 class UnixEchoTests(EchoTests, UnixTests, unittest.TestCase): 1528 pass 1529 1530 class UnixPipeliningTests(PipeliningTests, UnixTests, unittest.TestCase): 1531 pass 1532 1533 class UnixExpectContinueTests(ExpectContinueTests, UnixTests, unittest.TestCase): 1534 pass 1535 1536 class UnixBadContentLengthTests( 1537 BadContentLengthTests, UnixTests, unittest.TestCase 1538 ): 1539 pass 1540 1541 class UnixNoContentLengthTests(NoContentLengthTests, UnixTests, unittest.TestCase): 1542 pass 1543 1544 class UnixWriteCallbackTests(WriteCallbackTests, UnixTests, unittest.TestCase): 1545 pass 1546 1547 class UnixTooLargeTests(TooLargeTests, UnixTests, unittest.TestCase): 1548 pass 1549 1550 class UnixInternalServerErrorTests( 1551 InternalServerErrorTests, UnixTests, unittest.TestCase 1552 ): 1553 pass 1554 1555 class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase): 1556 pass 1557 1558 1559def parse_headers(fp): 1560 """Parses only RFC2822 headers from a file pointer.""" 1561 headers = {} 1562 1563 while True: 1564 line = fp.readline() 1565 1566 if line in (b"\r\n", b"\n", b""): 1567 break 1568 line = line.decode("iso-8859-1") 1569 name, value = line.strip().split(":", 1) 1570 headers[name.lower().strip()] = value.lower().strip() 1571 1572 return headers 1573 1574 1575class UnixHTTPConnection(httplib.HTTPConnection): 1576 """Patched version of HTTPConnection that uses Unix domain sockets.""" 1577 1578 def __init__(self, path): 1579 httplib.HTTPConnection.__init__(self, "localhost") 1580 self.path = path 1581 1582 def connect(self): 1583 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1584 sock.connect(self.path) 1585 self.sock = sock 1586 1587 1588class ConnectionClosed(Exception): 1589 pass 1590 1591 1592# stolen from gevent 1593def read_http(fp): # pragma: no cover 1594 try: 1595 response_line = fp.readline() 1596 except OSError as exc: 1597 fp.close() 1598 # errno 104 is ENOTRECOVERABLE, In WinSock 10054 is ECONNRESET 1599 1600 if get_errno(exc) in (errno.ECONNABORTED, errno.ECONNRESET, 104, 10054): 1601 raise ConnectionClosed 1602 raise 1603 1604 if not response_line: 1605 raise ConnectionClosed 1606 1607 header_lines = [] 1608 1609 while True: 1610 line = fp.readline() 1611 1612 if line in (b"\r\n", b"\r\n", b""): 1613 break 1614 else: 1615 header_lines.append(line) 1616 headers = dict() 1617 1618 for x in header_lines: 1619 x = x.strip() 1620 1621 if not x: 1622 continue 1623 key, value = x.split(b": ", 1) 1624 key = key.decode("iso-8859-1").lower() 1625 value = value.decode("iso-8859-1") 1626 assert key not in headers, "%s header duplicated" % key 1627 headers[key] = value 1628 1629 if "content-length" in headers: 1630 num = int(headers["content-length"]) 1631 body = b"" 1632 left = num 1633 1634 while left > 0: 1635 data = fp.read(left) 1636 1637 if not data: 1638 break 1639 body += data 1640 left -= len(data) 1641 else: 1642 # read until EOF 1643 body = fp.read() 1644 1645 return response_line, headers, body 1646 1647 1648# stolen from gevent 1649def get_errno(exc): # pragma: no cover 1650 """Get the error code out of socket.error objects. 1651 socket.error in <2.5 does not have errno attribute 1652 socket.error in 3.x does not allow indexing access 1653 e.args[0] works for all. 1654 There are cases when args[0] is not errno. 1655 i.e. http://bugs.python.org/issue6471 1656 Maybe there are cases when errno is set, but it is not the first argument? 1657 """ 1658 try: 1659 if exc.errno is not None: 1660 return exc.errno 1661 except AttributeError: 1662 pass 1663 try: 1664 return exc.args[0] 1665 except IndexError: 1666 return None 1667 1668 1669def chunks(l, n): 1670 """Yield successive n-sized chunks from l.""" 1671 1672 for i in range(0, len(l), n): 1673 yield l[i : i + n] 1674