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