Skip to content

Commit

Permalink
Backport set CONTENT_LENGTH for chunked requests (#2496)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRusskiy committed Nov 30, 2020
1 parent a418e5c commit 77a90ec
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 7 deletions.
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -3,6 +3,7 @@
* Bugfixes
* Explicitly include ctype.h to fix compilation warning and build error on macOS with Xcode 12 (#2304)
* Don't require json at boot (#2269)
* Set `CONTENT_LENGTH` for chunked requests (#2287)

## 4.3.4/4.3.5 and 3.12.5/3.12.6 / 2020-05-22

Expand Down
26 changes: 19 additions & 7 deletions lib/puma/client.rb
Expand Up @@ -397,7 +397,10 @@ def read_chunked_body
raise EOFError
end

return true if decode_chunk(chunk)
if decode_chunk(chunk)
@env[CONTENT_LENGTH] = @chunked_content_length
return true
end
end
end

Expand All @@ -410,19 +413,28 @@ def setup_chunked_body(body)
@body.binmode
@tempfile = @body

return decode_chunk(body)
@chunked_content_length = 0

if decode_chunk(body)
@env[CONTENT_LENGTH] = @chunked_content_length
return true
end
end

def write_chunk(str)
@chunked_content_length += @body.write(str)
end

def decode_chunk(chunk)
if @partial_part_left > 0
if @partial_part_left <= chunk.size
if @partial_part_left > 2
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
end
chunk = chunk[@partial_part_left..-1]
@partial_part_left = 0
else
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
write_chunk(chunk) if @partial_part_left > 2 # don't include the last \r\n
@partial_part_left -= chunk.size
return false
end
Expand Down Expand Up @@ -469,12 +481,12 @@ def decode_chunk(chunk)

case
when got == len
@body << part[0..-3] # to skip the ending \r\n
write_chunk(part[0..-3]) # to skip the ending \r\n
when got <= len - 2
@body << part
write_chunk(part)
@partial_part_left = len - part.size
when got == len - 1 # edge where we get just \r but not \n
@body << part[0..-2]
write_chunk(part[0..-2])
@partial_part_left = len - part.size
end
else
Expand Down
41 changes: 41 additions & 0 deletions test/test_puma_server.rb
Expand Up @@ -445,21 +445,26 @@ def test_Expect_100

def test_chunked_request
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_before_value
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -472,12 +477,15 @@ def test_chunked_request_pause_before_value

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_between_chunks
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -490,12 +498,15 @@ def test_chunked_request_pause_between_chunks

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_mid_count
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -508,12 +519,15 @@ def test_chunked_request_pause_mid_count

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_before_count_newline
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -526,12 +540,15 @@ def test_chunked_request_pause_before_count_newline

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_mid_value
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -544,12 +561,15 @@ def test_chunked_request_pause_mid_value

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -571,12 +591,15 @@ def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal (part1 + 'b'), body
assert_equal 4201, content_length
end

def test_chunked_request_pause_between_closing_cr_lf
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -590,12 +613,15 @@ def test_chunked_request_pause_between_closing_cr_lf

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal 'hello', body
assert_equal 5, content_length
end

def test_chunked_request_pause_before_closing_cr_lf
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -609,25 +635,31 @@ def test_chunked_request_pause_before_closing_cr_lf

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal 'hello', body
assert_equal 5, content_length
end

def test_chunked_request_header_case
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: Chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"

assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal "hello", body
assert_equal 5, content_length
end

def test_chunked_keep_alive
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -637,14 +669,17 @@ def test_chunked_keep_alive

assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "hello", body
assert_equal 5, content_length

sock.close
end

def test_chunked_keep_alive_two_back_to_back
body = nil
content_length = nil
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
[200, {}, [""]]
}

Expand All @@ -662,6 +697,7 @@ def test_chunked_keep_alive_two_back_to_back
h = header(sock)
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "hello", body
assert_equal 5, content_length
assert_equal true, last_crlf_written

last_crlf_writer.join
Expand All @@ -673,16 +709,19 @@ def test_chunked_keep_alive_two_back_to_back

assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "goodbye", body
assert_equal 7, content_length

sock.close
end

def test_chunked_keep_alive_two_back_to_back_with_set_remote_address
body = nil
content_length = nil
remote_addr =nil
@server = Puma::Server.new @app, @events, { remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR'}
server_run app: ->(env) {
body = env['rack.input'].read
content_length = env['CONTENT_LENGTH']
remote_addr = env['REMOTE_ADDR']
[200, {}, [""]]
}
Expand All @@ -692,6 +731,7 @@ def test_chunked_keep_alive_two_back_to_back_with_set_remote_address
h = header sock
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "hello", body
assert_equal 5, content_length
assert_equal "127.0.0.1", remote_addr

sock << "GET / HTTP/1.1\r\nX-Forwarded-For: 127.0.0.2\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ngood\r\n3\r\nbye\r\n0\r\n\r\n"
Expand All @@ -701,6 +741,7 @@ def test_chunked_keep_alive_two_back_to_back_with_set_remote_address

assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "goodbye", body
assert_equal 7, content_length
assert_equal "127.0.0.2", remote_addr

sock.close
Expand Down

0 comments on commit 77a90ec

Please sign in to comment.