diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 2da485884a871..e7b930eed797c 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -290,6 +290,7 @@ def media_type # Returns the content length of the request as an integer. def content_length + return raw_post.bytesize if headers.key?("Transfer-Encoding") super.to_i end @@ -344,9 +345,8 @@ def server_software # work with raw requests directly. def raw_post unless has_header? "RAW_POST_DATA" - raw_post_body = body - set_header("RAW_POST_DATA", raw_post_body.read(content_length)) - raw_post_body.rewind if raw_post_body.respond_to?(:rewind) + set_header("RAW_POST_DATA", read_body_stream) + body_stream.rewind if body_stream.respond_to?(:rewind) end get_header "RAW_POST_DATA" end @@ -472,6 +472,12 @@ def check_method(name) def default_session Session.disabled(self) end + + def read_body_stream + body_stream.rewind if body_stream.respond_to?(:rewind) + return body_stream.read if headers.key?("Transfer-Encoding") # Read body stream until EOF if "Transfer-Encoding" is present + body_stream.read(content_length) + end end end diff --git a/railties/test/railties/http_request_test.rb b/railties/test/railties/http_request_test.rb new file mode 100644 index 0000000000000..ece2bc481a9d3 --- /dev/null +++ b/railties/test/railties/http_request_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "stringio" +require "rack/test" +require "active_support/core_ext/module/delegation" + +module RailtiesTest + class HttpRequestTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + post "posts", to: "posts#create" + end + RUBY + + controller "posts", <<-RUBY + class PostsController < ApplicationController + def create + render json: { + raw_post: request.raw_post, + content_length: request.content_length + } + end + end + RUBY + end + + def teardown + teardown_app + end + + class TestInput < StringIO + undef_method :size + end + + test "parses request raw_post correctly when request has Transfer-Encoding header without a Content-Length value" do + require "#{app_path}/config/environment" + + header "Transfer-Encoding", "gzip, chunked;foo=bar" + post "/posts", TestInput.new("foo=bar") # prevents Rack::MockRequest from adding a Content-Length + + json_response = JSON.parse(last_response.body) + assert_equal 7, json_response["content_length"] + assert_equal "foo=bar", json_response["raw_post"] + end + end +end