diff --git a/benchmark.py b/benchmark.py
index 9c69ff0..5ce5414 100755
--- a/benchmark.py
+++ b/benchmark.py
@@ -56,27 +56,46 @@ class Benchmark:
                 writer.write(proxy.build_http_request(
                     proxy.httpMethods.GET, b'/'
                 ))
-                # await asyncio.sleep(0.1)
+                await asyncio.sleep(0.01)
         except KeyboardInterrupt:
             pass
 
+    @staticmethod
+    def parse_pipeline_response(response: proxy.HttpParser, raw: bytes, counter: int = 0) -> \
+            Tuple[proxy.HttpParser, int]:
+        response.parse(raw)
+        if response.state != proxy.httpParserStates.COMPLETE:
+            # Need more data
+            return response, counter
+
+        if response.buffer == b'':
+            # No more buffer left to parse
+            return response, counter + 1
+
+        # For pipelined requests we may have pending buffer, try parse them as responses
+        pipelined_response = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
+        return Benchmark.parse_pipeline_response(pipelined_response, response.buffer, counter + 1)
+
     @staticmethod
     async def recv(idd: int, reader: asyncio.StreamReader) -> None:
-        last_status_time = time.time()
-        num_completed_requests_per_connection: int = 0
+        print_every = 1000
+        last_print = time.time()
+        num_completed_requests: int = 0
+        response = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
         try:
             while True:
-                response = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
-                while response.state != proxy.httpParserStates.COMPLETE:
-                    raw = await reader.read(proxy.DEFAULT_BUFFER_SIZE)
-                    print(raw)
-                    response.parse(raw)
-
-                num_completed_requests_per_connection += 1
-                if num_completed_requests_per_connection % 50 == 0:
-                    now = time.time()
-                    print('[%d] Made 50 requests in last %.2f seconds' % (idd, now - last_status_time))
-                    last_status_time = now
+                raw = await reader.read(proxy.DEFAULT_BUFFER_SIZE)
+                response, total_parsed = Benchmark.parse_pipeline_response(response, raw)
+                if response.state == proxy.httpParserStates.COMPLETE:
+                    response = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
+                if total_parsed > 0:
+                    num_completed_requests += total_parsed
+                    # print('total parsed %d' % total_parsed)
+                    if num_completed_requests % print_every == 0:
+                        now = time.time()
+                        print('[%d] Completed last %d requests in %.2f secs' %
+                              (idd, print_every, now - last_print))
+                        last_print = now
         except KeyboardInterrupt:
             pass
 
diff --git a/proxy.py b/proxy.py
index 4324505..6631abc 100755
--- a/proxy.py
+++ b/proxy.py
@@ -257,8 +257,16 @@ def build_http_response(status_code: int,
         line.append(reason)
     if headers is None:
         headers = {}
-    if body is not None and not any(
-            k.lower() == b'content-length' for k in headers):
+    has_content_length = False
+    has_transfer_encoding = False
+    for k in headers:
+        if k.lower() == b'content-length':
+            has_content_length = True
+        if k.lower() == b'transfer-encoding':
+            has_transfer_encoding = True
+    if body is not None and \
+            not has_transfer_encoding and \
+            not has_content_length:
         headers[b'Content-Length'] = bytes_(len(body))
     return build_http_pkt(line, headers, body)
 
@@ -501,10 +509,11 @@ class ChunkParser:
         # Expected size of next following chunk
         self.size: Optional[int] = None
 
-    def parse(self, raw: bytes) -> None:
+    def parse(self, raw: bytes) -> bytes:
         more = True if len(raw) > 0 else False
-        while more:
+        while more and self.state != chunkParserStates.COMPLETE:
             more, raw = self.process(raw)
+        return raw
 
     def process(self, raw: bytes) -> Tuple[bool, bytes]:
         if self.state == chunkParserStates.WAITING_FOR_SIZE:
@@ -651,27 +660,29 @@ class HttpParser:
         self.buffer = b''
 
         more = True if len(raw) > 0 else False
-        while more:
+        while more and self.state != httpParserStates.COMPLETE:
             if self.state in (
                     httpParserStates.HEADERS_COMPLETE,
-                    httpParserStates.RCVING_BODY,
-                    httpParserStates.COMPLETE):
+                    httpParserStates.RCVING_BODY):
                 if b'content-length' in self.headers:
                     self.state = httpParserStates.RCVING_BODY
                     if self.body is None:
                         self.body = b''
-                    self.body += raw
+                    total_size = int(self.header(b'content-length'))
+                    received_size = len(self.body)
+                    self.body += raw[:total_size - received_size]
                     if self.body and \
-                            len(self.body) >= int(self.header(b'content-length')):
+                            len(self.body) == int(self.header(b'content-length')):
                         self.state = httpParserStates.COMPLETE
+                    more, raw = len(raw) > 0, raw[total_size - received_size:]
                 elif self.is_chunked_encoded():
                     if not self.chunk_parser:
                         self.chunk_parser = ChunkParser()
-                    self.chunk_parser.parse(raw)
+                    raw = self.chunk_parser.parse(raw)
                     if self.chunk_parser.state == chunkParserStates.COMPLETE:
                         self.body = self.chunk_parser.body
                         self.state = httpParserStates.COMPLETE
-                more, raw = False, b''
+                    more = False
             else:
                 more, raw = self.process(raw)
         self.buffer = raw
@@ -713,6 +724,7 @@ class HttpParser:
         elif self.state == httpParserStates.HEADERS_COMPLETE and \
                 self.type == httpParserTypes.REQUEST_PARSER and \
                 self.method == httpMethods.POST and \
+                not self.is_chunked_encoded() and \
                 (b'content-length' not in self.headers or
                  (b'content-length' in self.headers and
                   int(self.headers[b'content-length'][1]) == 0)) and \

[HttpParser] Handle invalid/malformed request chunks from client
**Describe the bug**
Currently, `HttpParser` works well assuming incoming client is not notorious :)  Handle cases when client can send invalid chunks in requests.

**To Reproduce**
Steps to reproduce the behavior:
1. Run `proxy.py` as `proxy.py --pac-file proxy.pac`
2. `telnet localhost 8899` and hit `Enter` key to send line feed
```
$ telnet localhost 8899
Trying ::1...
Connected to localhost.
Escape character is '^]'.

Connection closed by foreign host.
```
3. See error

```
2019-10-11 11:12:06,277 - pid:55396 [E] run:2215 - Exception while handling connection <socket.socket fd=27, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=0, laddr=('::1', 8899, 0, 0), raddr=('::1', 49902, 0, 0)>
Traceback (most recent call last):
  File "./proxy.py", line 2205, in run
    teardown = self.run_once()
  File "./proxy.py", line 2173, in run_once
    teardown = self.handle_events(readables, writables)
  File "./proxy.py", line 2138, in handle_events
    teardown = self.handle_readables(readables)
  File "./proxy.py", line 2076, in handle_readables
    self.request.parse(client_data)
  File "./proxy.py", line 615, in parse
    more, raw = self.process(raw)
  File "./proxy.py", line 625, in process
    self.process_line(line)
  File "./proxy.py", line 667, in process_line
    self.url = urlparse.urlsplit(line[1])
IndexError: list index out of range
2019-10-11 11:12:06,279 - pid:55396 [I] access_log:1955 - ::1:49902 -  None
```

**Expected behavior**
`HttpParser` must either ignore invalid chunks or be strict about it and drop the connection.

diff --git a/tests.py b/tests.py
index dddcd2a..b4d0e86 100644
--- a/tests.py
+++ b/tests.py
@@ -874,6 +874,41 @@ class TestHttpParser(unittest.TestCase):
         self.assertEqual(self.parser.body, b'Wikipedia in\r\n\r\nchunks.')
         self.assertEqual(self.parser.state, proxy.httpParserStates.COMPLETE)
 
+    def test_pipelined_response_parse(self) -> None:
+        response = proxy.build_http_response(
+            proxy.httpStatusCodes.OK, reason=b'OK',
+            headers={
+                b'Content-Length': b'15'
+            },
+            body=b'{"key":"value"}',
+        )
+        self.assert_pipeline_response(response)
+
+    def test_pipelined_chunked_response_parse(self) -> None:
+        response = proxy.build_http_response(
+            proxy.httpStatusCodes.OK, reason=b'OK',
+            headers={
+                b'Transfer-Encoding': b'chunked',
+                b'Content-Type': b'application/json',
+            },
+            body=b'f\r\n{"key":"value"}\r\n0\r\n\r\n'
+        )
+        self.assert_pipeline_response(response)
+
+    def assert_pipeline_response(self, response: bytes) -> None:
+        self.parser = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
+        self.parser.parse(response + response)
+        self.assertEqual(self.parser.state, proxy.httpParserStates.COMPLETE)
+        self.assertEqual(self.parser.body, b'{"key":"value"}')
+        self.assertEqual(self.parser.buffer, response)
+
+        # parse buffer
+        parser = proxy.HttpParser(proxy.httpParserTypes.RESPONSE_PARSER)
+        parser.parse(self.parser.buffer)
+        self.assertEqual(parser.state, proxy.httpParserStates.COMPLETE)
+        self.assertEqual(parser.body, b'{"key":"value"}')
+        self.assertEqual(parser.buffer, b'')
+
     def test_chunked_request_parse(self) -> None:
         self.parser.parse(proxy.build_http_request(
             proxy.httpMethods.POST, b'http://example.org/',

pip install -e .
parse_log_pytest
requirements.txt
pytest coverage flake8 mypy autopep8 py-spy
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
autopep8==2.3.2
coverage==7.8.0
exceptiongroup==1.2.2
flake8==7.2.0
iniconfig==2.1.0
mccabe==0.7.0
mypy==1.15.0
mypy-extensions==1.0.0
packaging==24.2
pluggy==1.5.0
-e git+https://github.com/abhinavsingh/proxy.py.git@69445a8921ba82738bde1f64e0939a34d08aaae2#egg=proxy.py
py-spy==0.4.0
pycodestyle==2.13.0
pyflakes==3.3.2
pytest==8.3.5
tomli==2.2.1
typing-extensions==3.7.4

name: proxy.py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - autopep8==2.3.2
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - iniconfig==2.1.0
      - mccabe==0.7.0
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - packaging==24.2
      - pluggy==1.5.0
      - py-spy==0.4.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.2
      - pytest==8.3.5
      - tomli==2.2.1
      - typing-extensions==3.7.4
prefix: /opt/conda/envs/proxy.py

tests.py::TestHttpParser::test_pipelined_chunked_response_parse tests.py::TestHttpParser::test_pipelined_response_parse
tests.py::TestHttpProtocolHandler::test_authenticated_proxy_http_tunnel tests.py::TestHttpProtocolHandler::test_http_tunnel tests.py::TestHttpProxyTlsInterception::test_e2e tests.py::TestHttpProxyPluginExamplesWithTlsInterception::test_man_in_the_middle_plugin tests.py::TestHttpProxyPluginExamplesWithTlsInterception::test_modify_post_data_plugin
tests.py::TestTextBytes::test_bytes tests.py::TestTextBytes::test_bytes_int tests.py::TestTextBytes::test_bytes_nochange tests.py::TestTextBytes::test_text tests.py::TestTextBytes::test_text_int tests.py::TestTextBytes::test_text_nochange tests.py::TestTcpConnection::testClosesIfNotClosed tests.py::TestTcpConnection::testFlushReturnsIfNoBuffer tests.py::TestTcpConnection::testNoOpIfAlreadyClosed tests.py::TestTcpConnection::testTcpClientRaisesTcpConnectionUninitializedException tests.py::TestTcpConnection::testTcpServerConnectionProperty tests.py::TestTcpConnection::testTcpServerEstablishesIPv4Connection tests.py::TestTcpConnection::testTcpServerEstablishesIPv6Connection tests.py::TestTcpConnection::testTcpServerIgnoresDoubleConnectSilently tests.py::TestTcpConnection::testTcpServerRaisesTcpConnectionUninitializedException tests.py::TestTcpConnection::testThrowsKeyErrorIfNoConn tests.py::TestSocketConnectionUtils::test_context_manager tests.py::TestSocketConnectionUtils::test_decorator tests.py::TestSocketConnectionUtils::test_new_socket_connection_dual tests.py::TestSocketConnectionUtils::test_new_socket_connection_ipv4 tests.py::TestSocketConnectionUtils::test_new_socket_connection_ipv6 tests.py::TestAcceptorPool::test_setup_and_shutdown tests.py::TestWorker::test_accepts_client_from_server_socket tests.py::TestWorker::test_continues_when_no_events tests.py::TestWorker::test_worker_doesnt_teardown_on_blocking_io_error tests.py::TestChunkParser::test_chunk_parse_basic tests.py::TestChunkParser::test_chunk_parse_issue_27 tests.py::TestChunkParser::test_to_chunks tests.py::TestHttpParser::test_build_header tests.py::TestHttpParser::test_build_request tests.py::TestHttpParser::test_build_response tests.py::TestHttpParser::test_build_response_adds_content_length_header tests.py::TestHttpParser::test_build_url_none tests.py::TestHttpParser::test_chunked_request_parse tests.py::TestHttpParser::test_chunked_response_parse tests.py::TestHttpParser::test_connect_request_with_crlf_as_separate_chunk tests.py::TestHttpParser::test_connect_request_without_host_header_request_parse tests.py::TestHttpParser::test_find_line tests.py::TestHttpParser::test_find_line_returns_None tests.py::TestHttpParser::test_get_full_parse tests.py::TestHttpParser::test_get_partial_parse1 tests.py::TestHttpParser::test_get_partial_parse2 tests.py::TestHttpParser::test_has_header tests.py::TestHttpParser::test_header_raises tests.py::TestHttpParser::test_is_http_1_1_keep_alive tests.py::TestHttpParser::test_is_http_1_1_keep_alive_with_non_close_connection_header tests.py::TestHttpParser::test_is_not_http_1_1_keep_alive_for_http_1_0 tests.py::TestHttpParser::test_is_not_http_1_1_keep_alive_with_close_header tests.py::TestHttpParser::test_line_rcvd_to_rcving_headers_state_change tests.py::TestHttpParser::test_post_full_parse tests.py::TestHttpParser::test_post_partial_parse tests.py::TestHttpParser::test_request_parse_without_content_length tests.py::TestHttpParser::test_response_parse tests.py::TestHttpParser::test_response_parse_without_content_length tests.py::TestHttpParser::test_response_partial_parse tests.py::TestHttpParser::test_set_host_port_raises tests.py::TestWebsocketFrame::test_build_with_mask tests.py::TestWebsocketFrame::test_parse_with_mask tests.py::TestWebsocketClient::test_handshake tests.py::TestHttpProtocolHandler::test_authenticated_proxy_http_get tests.py::TestHttpProtocolHandler::test_http_get tests.py::TestHttpProtocolHandler::test_proxy_authentication_failed tests.py::TestHttpProtocolHandler::test_proxy_connection_failed tests.py::TestWebServerPlugin::test_default_web_server_returns_404 tests.py::TestWebServerPlugin::test_on_client_connection_called_on_teardown tests.py::TestWebServerPlugin::test_pac_file_served_from_buffer tests.py::TestWebServerPlugin::test_pac_file_served_from_disk tests.py::TestWebServerPlugin::test_static_web_server_serves tests.py::TestWebServerPlugin::test_static_web_server_serves_404 tests.py::TestHttpProxyPlugin::test_proxy_plugin_before_upstream_connection_can_teardown tests.py::TestHttpProxyPlugin::test_proxy_plugin_initialized tests.py::TestHttpProxyPlugin::test_proxy_plugin_on_and_before_upstream_connection tests.py::TestHttpProxyPluginExamples::test_filter_by_upstream_host_plugin tests.py::TestHttpProxyPluginExamples::test_man_in_the_middle_plugin tests.py::TestHttpProxyPluginExamples::test_modify_post_data_plugin tests.py::TestHttpProxyPluginExamples::test_proposed_rest_api_plugin tests.py::TestHttpProxyPluginExamples::test_redirect_to_custom_server_plugin tests.py::TestHttpRequestRejected::test_body_response tests.py::TestHttpRequestRejected::test_empty_response tests.py::TestHttpRequestRejected::test_status_code_response tests.py::TestMain::test_basic_auth tests.py::TestMain::test_init_with_no_arguments tests.py::TestMain::test_main_py2_exit tests.py::TestMain::test_main_py3_runs tests.py::TestMain::test_main_version tests.py::TestMain::test_pid_file_is_written_and_removed tests.py::TestSetOpenFileLimit::test_set_open_file_limit tests.py::TestSetOpenFileLimit::test_set_open_file_limit_not_called tests.py::TestSetOpenFileLimit::test_set_open_file_limit_not_called_coz_upper_bound_check
proxy.py benchmark.py
proxy.py benchmark.py