From 47351c9780b121e64e75bf4bc73271551b8c3239 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Thu, 3 Aug 2023 14:39:33 +0900 Subject: [PATCH] Backport #2104 to 3-0-stable Return empty when parsing a multi-part POST with only one end delimiter. Fixed: #2103 Sending the following request in a browser generates a request with with only one end delimiter. ```javascript const formData = new FormData(); const request = new Request('http://127.0.0.1:8080/', { method: 'POST', body: formData, }); const response = fetch(request); ``` ``` curl 'http://127.0.0.1:8080/' \ -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryR1LC4tR6ayskIXJm' \ --data-raw $'------WebKitFormBoundaryR1LC4tR6ayskIXJm--\r\n' ``` This request is not compliant RFC7578, but is generated by major browsers such as FireFox and Chrome. Supporting this request will cause the multipart parser to return an empty value. --- lib/rack/multipart/parser.rb | 10 +++++++++- test/spec_request.rb | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb index 2469459d7..345cc258b 100644 --- a/lib/rack/multipart/parser.rb +++ b/lib/rack/multipart/parser.rb @@ -213,6 +213,7 @@ def initialize(boundary, tempfile, bufsize, query_parser) @sbuf = StringScanner.new("".dup) @body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m + @end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish) @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish) @head_regex = /(.*?#{EOL})#{EOL}/m end @@ -279,7 +280,14 @@ def handle_fast_forward @state = :MIME_HEAD return when :END_BOUNDARY - # invalid multipart upload, but retry for opening boundary + # invalid multipart upload + if @sbuf.pos == @end_boundary_size && @sbuf.rest == EOL + # stop parsing a buffer if a buffer is only an end boundary. + @state = :DONE + return + end + + # retry for opening boundary else # no boundary found, keep reading data return :want_read diff --git a/test/spec_request.rb b/test/spec_request.rb index 169118635..c22619063 100644 --- a/test/spec_request.rb +++ b/test/spec_request.rb @@ -1333,6 +1333,24 @@ def initialize(*) f[:tempfile].size.must_equal 76 end + it "parse multipart delimiter-only boundary" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input + ) + + req = make_request mr + req.query_string.must_equal "" + req.GET.must_be :empty? + req.POST.must_be :empty? + req.params.must_equal({}) + end + it "MultipartPartLimitError when request has too many multipart file parts if limit set" do begin data = 10000.times.map { "--AaB03x\r\ncontent-type: text/plain\r\ncontent-disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n")