Repository: puma/puma (Ruby)

Issue:
Remaining chunk data causing malformed requests
### Steps to reproduce

On localhost after updating Puma to `>= 3.7.0` we've started seeing these errors in the log `HTTP parse error, malformed request` with requests randomly failing.

It seems like this was caused by this change released in v3.7.0: https://github.com/puma/puma/commit/c8f0339f723eaa04ce09cc7bb4a81b8a5b66fa53

Reverting this commit seems to fix the issue

Inspection of `io.read` mostly returns nil, but also sometimes some end of lines `\r\n`

### Actual behavior

Some requests fail randomly and Puma raises an `HTTP parse error, malformed request`:

```
web.1    | I, [2017-11-13T16:09:16.833578 #10360]  INFO -- : Started PATCH "/hub/cupido/sessions/bump_by_ticket/d1f4a533-1204-46dd-8f12-1bb2f4aaa44c" for 127.0.0.1 at 2017-11-13 16:09:16 +0100
16:09:16 web.1    | I, [2017-11-13T16:09:16.853101 #10360]  INFO -- : Processing by Cupido::SessionsController#bump_by_ticket as XML
16:09:16 web.1    | I, [2017-11-13T16:09:16.853143 #10360]  INFO -- :   Parameters: {"id"=>"[FILTERED]"}
...
16:09:16 web.1    | I, [2017-11-13T16:09:16.864725 #10360]  INFO -- : Completed 200 OK in 12ms (Views: 0.5ms | ActiveRecord: 1.4ms | Redis: 2.4ms)
16:09:16 web.1    | 2017-11-13 16:09:16 +0100: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
16:09:16 web.1    | 2017-11-13 16:09:16 +0100: ENV: {"rack.version"=>[1, 3], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "SCRIPT_NAME"=>"", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 3.7.0 Snowy Sagebrush", "GATEWAY_INTERFACE"=>"CGI/1.2"}
16:09:16 web.1    | ---
16:09:17 web.1    | D, [2017-11-13T16:09:17.310849 #10360] DEBUG -- : 
16:09:17 web.1    | D, [2017-11-13T16:09:17.310902 #10360] DEBUG -- : 
```

### System configuration

**Ruby version**: 2.4.2
**Rails version**: 4.2.9
**Puma version**: >= 3.7.0

Incorrect chunked transfer encoding handling
Hi,
version 3.7.0 (probably change "Pickup any remaining chunk data as the next request") introduced bug with consequent chunked requests.
It seems puma implementation does not conform to https://tools.ietf.org/html/rfc7230#section-4.1. Valid chunked body is:

chunked-body = *chunk
                           last-chunk
                           trailer-part
                           CRLF

but Puma does not count with the last CRLF which is wrongly appended to consequent request, while printing:

HTTP parse error, malformed request (): #<Puma::HttpParserError [047a99aabe6323c0020b9e290f19d997]: Invalid HTTP format, parsing fails.>

I've tested this via Wireshark. When last CRLF is not present, puma sends response without printing any error. But Wireshark does not display request, because it is incomplete/invalid. In case last CRLF is present, Wireshark prints request, but puma prints error and consequent request within same connection fails.


Discussion:


+1 Also experiencing issues with chunked request.
its's still not work in puma 3.12.0

![image](https://user-images.githubusercontent.com/12691638/55848437-86ad7680-5b7f-11e9-8f84-a9fff93b6823.png)

and the error is 

![image](https://user-images.githubusercontent.com/12691638/55848452-9036de80-5b7f-11e9-8d4e-6fbe06b5f62d.png)

May rack still read content_Length?

![image](https://user-images.githubusercontent.com/12691638/55848479-a47adb80-5b7f-11e9-8a5d-47375ae5dbb2.png)




Patch:
diff --git a/lib/puma/client.rb b/lib/puma/client.rb
index c395555cd4..8253245abe 100644
--- a/lib/puma/client.rb
+++ b/lib/puma/client.rb
@@ -168,6 +168,7 @@ def decode_chunk(chunk)
           if len == 0
             @body.rewind
             rest = io.read
+            rest = rest[2..-1] if rest.start_with?("\r\n")
             @buffer = rest.empty? ? nil : rest
             @requests_served += 1
             @ready = true


Test patch:
diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb
index 94292eed50..5168392f22 100644
--- a/test/test_puma_server.rb
+++ b/test/test_puma_server.rb
@@ -677,6 +677,55 @@ def test_chunked_request_header_case
     assert_equal "hello", body
   end
 
+  def test_chunked_keep_alive
+    body = nil
+    @server.app = proc { |env|
+      body = env['rack.input'].read
+      [200, {}, [""]]
+    }
+
+    @server.add_tcp_listener @host, @port
+    @server.run
+
+    sock = TCPSocket.new @host, @server.connected_port
+    sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"
+
+    h = header(sock)
+
+    assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
+    assert_equal "hello", body
+
+    sock.close
+  end
+
+  def test_chunked_keep_alive_two_back_to_back
+    body = nil
+    @server.app = proc { |env|
+      body = env['rack.input'].read
+      [200, {}, [""]]
+    }
+
+    @server.add_tcp_listener @host, @port
+    @server.run
+
+    sock = TCPSocket.new @host, @server.connected_port
+    sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"
+
+    h = header(sock)
+    assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
+    assert_equal "hello", body
+
+    sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ngood\r\n3\r\nbye\r\n0\r\n\r\n"
+    sleep 0.1
+
+    h = header(sock)
+
+    assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
+    assert_equal "goodbye", body
+
+    sock.close
+  end
+
   def test_empty_header_values
     @server.app = proc { |env| [200, {"X-Empty-Header" => ""}, []] }