From 11bb4464f5c0527666050772f47698ef1c9710a6 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 27 May 2021 11:14:11 -0400 Subject: [PATCH 01/13] Introduce multi-part pair spec --- lib/http/form_data/multipart.rb | 10 +++++++++- spec/lib/http/form_data/multipart_spec.rb | 24 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index a667643..3434346 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -16,7 +16,7 @@ class Multipart # @param [#to_h, Hash] data form data key-value Hash def initialize(data, boundary: self.class.generate_boundary) - parts = Param.coerce FormData.ensure_hash data + parts = Param.coerce FormData.ensure_hash data unless @params.is_a?(Array) @boundary = boundary.to_s.freeze @io = CompositeIO.new [*parts.flat_map { |part| [glue, part] }, tail] @@ -54,6 +54,14 @@ def glue def tail @tail ||= "--#{@boundary}--#{CRLF}" end + + def join_multipart_pairs(pairs) + pairs.each_cons(2).each do |pair| + multipart = pair.first + file = pair.last + + end + end end end end diff --git a/spec/lib/http/form_data/multipart_spec.rb b/spec/lib/http/form_data/multipart_spec.rb index a6d3f55..310de23 100644 --- a/spec/lib/http/form_data/multipart_spec.rb +++ b/spec/lib/http/form_data/multipart_spec.rb @@ -87,6 +87,30 @@ def disposition(params) ].join) end end + + # https://github.com/httprb/http/issues/663 + context "when params is a list of pairs" do + let(:params) do + [ + ["metadata", %(filename=first.txt)], + ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => 'plain/text', :filename => "stream-123")], + ["metadata", %(filename=second.txt)], + ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => 'plain/text', :filename => "stream-456")] + ] + end + + it "allows duplicate param names and preservesd given order" do + # form_data.boundary + + expect(form_data.to_s).to eq([ + %(--#{form_data.boundary}#{crlf}), + %(Content-Disposition: form-data; name="metadata"#{crlf}#{crlf}), + %(filename=first.txt#{crlf}), + %(--#{form_data.boundary}#{crlf}), + %(--#{boundary_value}--#{crlf}) + ].join) + end + end end describe "#size" do From 77f70d7dbcdc1c2bce4c30dd6ae47b0f2ad3b951 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Thu, 27 May 2021 17:33:16 +0200 Subject: [PATCH 02/13] Finalize spec to solve https://github.com/httprb/http/issues/663 --- spec/lib/http/form_data/multipart_spec.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/spec/lib/http/form_data/multipart_spec.rb b/spec/lib/http/form_data/multipart_spec.rb index 310de23..291630a 100644 --- a/spec/lib/http/form_data/multipart_spec.rb +++ b/spec/lib/http/form_data/multipart_spec.rb @@ -93,21 +93,28 @@ def disposition(params) let(:params) do [ ["metadata", %(filename=first.txt)], - ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => 'plain/text', :filename => "stream-123")], + ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => "plain/text", :filename => "abc")], ["metadata", %(filename=second.txt)], - ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => 'plain/text', :filename => "stream-456")] + ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => "plain/text", :filename => "xyz")] ] end it "allows duplicate param names and preservesd given order" do - # form_data.boundary - expect(form_data.to_s).to eq([ %(--#{form_data.boundary}#{crlf}), + %(Content-Disposition: form-data; name="metadata"#{crlf}), + %(#{crlf}filename=first.txt#{crlf}), + %(--#{form_data.boundary}#{crlf}), + %(Content-Disposition: form-data; name="file"; filename="abc"#{crlf}), + %(Content-Type: plain/text#{crlf}), + %(#{crlf}uno#{crlf}), %(Content-Disposition: form-data; name="metadata"#{crlf}#{crlf}), - %(filename=first.txt#{crlf}), + %(filename=second.txt#{crlf}), %(--#{form_data.boundary}#{crlf}), - %(--#{boundary_value}--#{crlf}) + %(Content-Disposition: form-data; name="file"; filename="xyz"#{crlf}), + %(Content-Type: plain/text#{crlf}), + %(#{crlf}dos#{crlf}), + %(--#{form_data.boundary}--#{crlf}) ].join) end end From 5a79261c44f41d0ee47080b3734d18dfbd329134 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 27 May 2021 17:31:59 -0400 Subject: [PATCH 03/13] Handle Hash vs Array of pairs parts --- lib/http/form_data/multipart.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index 3434346..d6f1bf7 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -16,10 +16,8 @@ class Multipart # @param [#to_h, Hash] data form data key-value Hash def initialize(data, boundary: self.class.generate_boundary) - parts = Param.coerce FormData.ensure_hash data unless @params.is_a?(Array) - @boundary = boundary.to_s.freeze - @io = CompositeIO.new [*parts.flat_map { |part| [glue, part] }, tail] + @io = CompositeIO.new [*parts(data).flat_map { |part| [glue, part] }, tail] end # Generates a string suitable for using as a boundary in multipart form @@ -55,12 +53,9 @@ def tail @tail ||= "--#{@boundary}--#{CRLF}" end - def join_multipart_pairs(pairs) - pairs.each_cons(2).each do |pair| - multipart = pair.first - file = pair.last - - end + def parts(data) + return Param.coerce FormData.ensure_hash data if @params.is_a?(Hash) + Param.coerce_array_of_pairs data end end end From c38b2d0eabf0aba735139756de8beb11f6c6e3c1 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 27 May 2021 17:35:21 -0400 Subject: [PATCH 04/13] Add helper for Array of pairs param --- lib/http/form_data/multipart/param.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/http/form_data/multipart/param.rb b/lib/http/form_data/multipart/param.rb index 5972752..9ff6c58 100644 --- a/lib/http/form_data/multipart/param.rb +++ b/lib/http/form_data/multipart/param.rb @@ -59,6 +59,24 @@ def self.coerce(data) params end + # Flattens given Array of `data` Array pairs into an array of `Param`'s. + # Nested array are unwinded. + # Behavior is similar to `URL.encode_www_form`. + # + # @param [Array] data + # @return [Array] + def self.coerce_array_of_pairs(data) + params = [] + + data.each_cons(2) do |name, values| + Array(values).each do |value| + params << new(name, value) + end + end + + params + end + private def header From 246a2ca4a517a5e8cace12408459f88b2afb3505 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 27 May 2021 18:00:37 -0400 Subject: [PATCH 05/13] Use each slice vice cons --- lib/http/form_data/multipart/param.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/http/form_data/multipart/param.rb b/lib/http/form_data/multipart/param.rb index 9ff6c58..beddbd0 100644 --- a/lib/http/form_data/multipart/param.rb +++ b/lib/http/form_data/multipart/param.rb @@ -2,6 +2,7 @@ require "http/form_data/readable" require "http/form_data/composite_io" +require "pry" module HTTP module FormData @@ -51,6 +52,7 @@ def self.coerce(data) params = [] data.each do |name, values| + binding.pry Array(values).each do |value| params << new(name, value) end @@ -68,10 +70,11 @@ def self.coerce(data) def self.coerce_array_of_pairs(data) params = [] - data.each_cons(2) do |name, values| - Array(values).each do |value| - params << new(name, value) - end + data.each_slice(2) do |first, second| + binding.pry + params << new(first[0], first[1]) + params << new(second[0], second[1]) + binding.pry end params From 2631b36bef771b3e62539af29d3b0a3ff56d71bc Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 27 May 2021 23:53:48 -0400 Subject: [PATCH 06/13] Add required boundary between 'pairs' in spec --- lib/http/form_data/multipart.rb | 4 ++-- lib/http/form_data/multipart/param.rb | 14 +++++--------- spec/lib/http/form_data/multipart_spec.rb | 4 ++-- spec/spec_helper.rb | 2 ++ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index d6f1bf7..b7c387e 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -54,8 +54,8 @@ def tail end def parts(data) - return Param.coerce FormData.ensure_hash data if @params.is_a?(Hash) - Param.coerce_array_of_pairs data + return Param.coerce_array_of_pairs data if data.is_a?(Array) + Param.coerce FormData.ensure_hash data end end end diff --git a/lib/http/form_data/multipart/param.rb b/lib/http/form_data/multipart/param.rb index beddbd0..0b19b22 100644 --- a/lib/http/form_data/multipart/param.rb +++ b/lib/http/form_data/multipart/param.rb @@ -31,14 +31,12 @@ class Param # @param [FormData::File, FormData::Part, #to_s] value def initialize(name, value) @name = name.to_s - @part = if value.is_a?(FormData::Part) value else FormData::Part.new(value) end - @io = CompositeIO.new [header, @part, footer] end @@ -52,12 +50,10 @@ def self.coerce(data) params = [] data.each do |name, values| - binding.pry Array(values).each do |value| params << new(name, value) end end - params end @@ -70,11 +66,11 @@ def self.coerce(data) def self.coerce_array_of_pairs(data) params = [] - data.each_slice(2) do |first, second| - binding.pry - params << new(first[0], first[1]) - params << new(second[0], second[1]) - binding.pry + data.each do |pair| + name, values = pair + Array(values).each do |value| + params << new(name, value) + end end params diff --git a/spec/lib/http/form_data/multipart_spec.rb b/spec/lib/http/form_data/multipart_spec.rb index 291630a..3cbb855 100644 --- a/spec/lib/http/form_data/multipart_spec.rb +++ b/spec/lib/http/form_data/multipart_spec.rb @@ -17,7 +17,6 @@ def disposition(params) it "properly generates multipart data" do boundary_value = form_data.boundary - expect(form_data.to_s).to eq([ "--#{boundary_value}#{crlf}", "#{disposition 'name' => 'foo'}#{crlf}", @@ -99,7 +98,7 @@ def disposition(params) ] end - it "allows duplicate param names and preservesd given order" do + it "allows duplicate param names and preserves given order" do expect(form_data.to_s).to eq([ %(--#{form_data.boundary}#{crlf}), %(Content-Disposition: form-data; name="metadata"#{crlf}), @@ -108,6 +107,7 @@ def disposition(params) %(Content-Disposition: form-data; name="file"; filename="abc"#{crlf}), %(Content-Type: plain/text#{crlf}), %(#{crlf}uno#{crlf}), + %(--#{form_data.boundary}#{crlf}), %(Content-Disposition: form-data; name="metadata"#{crlf}#{crlf}), %(filename=second.txt#{crlf}), %(--#{form_data.boundary}#{crlf}), diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 80d8cba..1f0ca63 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,8 @@ # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true + # This option will remove the default 200 character limit for RSpec diffs + expectations.max_formatted_output_length = nil end config.mock_with :rspec do |mocks| From efb5549a707585f25ac2415119ebf0b8ae05b8e4 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Fri, 28 May 2021 00:22:16 -0400 Subject: [PATCH 07/13] Kill redudant coercion method --- lib/http/form_data/multipart.rb | 2 +- lib/http/form_data/multipart/param.rb | 24 ++---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index b7c387e..ed10c56 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -54,7 +54,7 @@ def tail end def parts(data) - return Param.coerce_array_of_pairs data if data.is_a?(Array) + return Param.coerce data if data.is_a?(Array) Param.coerce FormData.ensure_hash data end end diff --git a/lib/http/form_data/multipart/param.rb b/lib/http/form_data/multipart/param.rb index 0b19b22..ecf0ce5 100644 --- a/lib/http/form_data/multipart/param.rb +++ b/lib/http/form_data/multipart/param.rb @@ -2,7 +2,6 @@ require "http/form_data/readable" require "http/form_data/composite_io" -require "pry" module HTTP module FormData @@ -40,11 +39,11 @@ def initialize(name, value) @io = CompositeIO.new [header, @part, footer] end - # Flattens given `data` Hash into an array of `Param`'s. + # Flattens given `data` Hash or Array into an array of `Param`'s. # Nested array are unwinded. # Behavior is similar to `URL.encode_www_form`. # - # @param [Hash] data + # @param [Array || Hash] data # @return [Array] def self.coerce(data) params = [] @@ -57,25 +56,6 @@ def self.coerce(data) params end - # Flattens given Array of `data` Array pairs into an array of `Param`'s. - # Nested array are unwinded. - # Behavior is similar to `URL.encode_www_form`. - # - # @param [Array] data - # @return [Array] - def self.coerce_array_of_pairs(data) - params = [] - - data.each do |pair| - name, values = pair - Array(values).each do |value| - params << new(name, value) - end - end - - params - end - private def header From 2be3ec5c77ff90509770e428d1b516d0badbc562 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Fri, 28 May 2021 00:45:56 -0400 Subject: [PATCH 08/13] Use if-else vice guard clause --- lib/http/form_data/multipart.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index ed10c56..48103aa 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -54,8 +54,11 @@ def tail end def parts(data) - return Param.coerce data if data.is_a?(Array) - Param.coerce FormData.ensure_hash data + if data.is_a?(Array) + Param.coerce data + else + Param.coerce FormData.ensure_hash data + end end end end From 705ab84b8b533df4016521a3ab84e38ae74eed15 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Fri, 28 May 2021 00:49:49 -0400 Subject: [PATCH 09/13] Restore whitespace --- lib/http/form_data/multipart/param.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/http/form_data/multipart/param.rb b/lib/http/form_data/multipart/param.rb index ecf0ce5..327ad2b 100644 --- a/lib/http/form_data/multipart/param.rb +++ b/lib/http/form_data/multipart/param.rb @@ -30,17 +30,19 @@ class Param # @param [FormData::File, FormData::Part, #to_s] value def initialize(name, value) @name = name.to_s + @part = if value.is_a?(FormData::Part) value else FormData::Part.new(value) end + @io = CompositeIO.new [header, @part, footer] end # Flattens given `data` Hash or Array into an array of `Param`'s. - # Nested array are unwinded. + # Nested arrays are unwinded. # Behavior is similar to `URL.encode_www_form`. # # @param [Array || Hash] data @@ -53,6 +55,7 @@ def self.coerce(data) params << new(name, value) end end + params end From 52c66d95b9d66ef36107e7082944f64a09565bd4 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Sat, 29 May 2021 02:29:37 +0200 Subject: [PATCH 10/13] [broken] update specs to test any enumerable case --- spec/lib/http/form_data/multipart_spec.rb | 71 ++++++++++++---------- spec/lib/http/form_data/urlencoded_spec.rb | 11 ++++ 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/spec/lib/http/form_data/multipart_spec.rb b/spec/lib/http/form_data/multipart_spec.rb index 3cbb855..3a9e4b7 100644 --- a/spec/lib/http/form_data/multipart_spec.rb +++ b/spec/lib/http/form_data/multipart_spec.rb @@ -1,12 +1,49 @@ # frozen_string_literal: true RSpec.describe HTTP::FormData::Multipart do - subject(:form_data) { HTTP::FormData::Multipart.new params } + subject(:form_data) { described_class.new params } let(:file) { HTTP::FormData::File.new fixture "the-http-gem.info" } let(:params) { { :foo => :bar, :baz => file } } let(:boundary) { /-{21}[a-f0-9]{42}/ } + # https://github.com/httprb/http/issues/663 + it "supports any Enumerables of pairs" do + form_data = described_class.new( + Enumerator.new do |y| + y << ["metadata", %(filename="first.txt")] + y << ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => "plain/text", :filename => "abc")] + y << ["metadata", %(filename="second.txt")] + y << ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => "plain/text", :filename => "xyz")] + y << ["metadata", %w[why not]] + end + ) + + expect(form_data.to_s).to eq([ + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nfilename="first.txt"\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="file"; filename="abc"\r\n), + %(Content-Type: plain/text\r\n), + %(\r\nuno\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nfilename="second.txt"\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="file"; filename="xyz"\r\n), + %(Content-Type: plain/text\r\n), + %(\r\ndos\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nwhy\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nnot\r\n), + %(--#{form_data.boundary}--\r\n) + ].join) + end + describe "#to_s" do def disposition(params) params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ") @@ -86,38 +123,6 @@ def disposition(params) ].join) end end - - # https://github.com/httprb/http/issues/663 - context "when params is a list of pairs" do - let(:params) do - [ - ["metadata", %(filename=first.txt)], - ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => "plain/text", :filename => "abc")], - ["metadata", %(filename=second.txt)], - ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => "plain/text", :filename => "xyz")] - ] - end - - it "allows duplicate param names and preserves given order" do - expect(form_data.to_s).to eq([ - %(--#{form_data.boundary}#{crlf}), - %(Content-Disposition: form-data; name="metadata"#{crlf}), - %(#{crlf}filename=first.txt#{crlf}), - %(--#{form_data.boundary}#{crlf}), - %(Content-Disposition: form-data; name="file"; filename="abc"#{crlf}), - %(Content-Type: plain/text#{crlf}), - %(#{crlf}uno#{crlf}), - %(--#{form_data.boundary}#{crlf}), - %(Content-Disposition: form-data; name="metadata"#{crlf}#{crlf}), - %(filename=second.txt#{crlf}), - %(--#{form_data.boundary}#{crlf}), - %(Content-Disposition: form-data; name="file"; filename="xyz"#{crlf}), - %(Content-Type: plain/text#{crlf}), - %(#{crlf}dos#{crlf}), - %(--#{form_data.boundary}--#{crlf}) - ].join) - end - end end describe "#size" do diff --git a/spec/lib/http/form_data/urlencoded_spec.rb b/spec/lib/http/form_data/urlencoded_spec.rb index be25a38..8f9d733 100644 --- a/spec/lib/http/form_data/urlencoded_spec.rb +++ b/spec/lib/http/form_data/urlencoded_spec.rb @@ -4,6 +4,17 @@ let(:data) { { "foo[bar]" => "test" } } subject(:form_data) { HTTP::FormData::Urlencoded.new data } + it "supports any Enumerables of pairs" do + form_data = described_class.new( + Enumerator.new do |y| + y << %w[foo abc] + y << ["foo", %w[def xyz]] + end + ) + + expect(form_data.to_s).to eq("foo=abc&foo=def&foo=xyz") + end + describe "#content_type" do subject { form_data.content_type } it { is_expected.to eq "application/x-www-form-urlencoded" } From 672bc46a4cb0cf66e36cc53f682d8bc06bebb04c Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Sat, 29 May 2021 02:50:33 +0200 Subject: [PATCH 11/13] Revert "[broken] update specs to test any enumerable case" This reverts commit 52c66d95b9d66ef36107e7082944f64a09565bd4. --- spec/lib/http/form_data/multipart_spec.rb | 76 +++++++++++----------- spec/lib/http/form_data/urlencoded_spec.rb | 11 +--- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/spec/lib/http/form_data/multipart_spec.rb b/spec/lib/http/form_data/multipart_spec.rb index 3a9e4b7..dc8ee9d 100644 --- a/spec/lib/http/form_data/multipart_spec.rb +++ b/spec/lib/http/form_data/multipart_spec.rb @@ -7,43 +7,6 @@ let(:params) { { :foo => :bar, :baz => file } } let(:boundary) { /-{21}[a-f0-9]{42}/ } - # https://github.com/httprb/http/issues/663 - it "supports any Enumerables of pairs" do - form_data = described_class.new( - Enumerator.new do |y| - y << ["metadata", %(filename="first.txt")] - y << ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => "plain/text", :filename => "abc")] - y << ["metadata", %(filename="second.txt")] - y << ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => "plain/text", :filename => "xyz")] - y << ["metadata", %w[why not]] - end - ) - - expect(form_data.to_s).to eq([ - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="metadata"\r\n), - %(\r\nfilename="first.txt"\r\n), - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="file"; filename="abc"\r\n), - %(Content-Type: plain/text\r\n), - %(\r\nuno\r\n), - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="metadata"\r\n), - %(\r\nfilename="second.txt"\r\n), - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="file"; filename="xyz"\r\n), - %(Content-Type: plain/text\r\n), - %(\r\ndos\r\n), - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="metadata"\r\n), - %(\r\nwhy\r\n), - %(--#{form_data.boundary}\r\n), - %(Content-Disposition: form-data; name="metadata"\r\n), - %(\r\nnot\r\n), - %(--#{form_data.boundary}--\r\n) - ].join) - end - describe "#to_s" do def disposition(params) params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ") @@ -123,6 +86,45 @@ def disposition(params) ].join) end end + + # https://github.com/httprb/http/issues/663 + context "when params is an Array of pairs" do + let(:params) do + [ + ["metadata", %(filename="first.txt")], + ["file", HTTP::FormData::File.new(StringIO.new("uno"), :content_type => "plain/text", :filename => "abc")], + ["metadata", %(filename="second.txt")], + ["file", HTTP::FormData::File.new(StringIO.new("dos"), :content_type => "plain/text", :filename => "xyz")], + ["metadata", %w[question=why question=not]] + ] + end + + it "allows duplicate param names and preserves given order" do + expect(form_data.to_s).to eq([ + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nfilename="first.txt"\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="file"; filename="abc"\r\n), + %(Content-Type: plain/text\r\n), + %(\r\nuno\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nfilename="second.txt"\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="file"; filename="xyz"\r\n), + %(Content-Type: plain/text\r\n), + %(\r\ndos\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nquestion=why\r\n), + %(--#{form_data.boundary}\r\n), + %(Content-Disposition: form-data; name="metadata"\r\n), + %(\r\nquestion=not\r\n), + %(--#{form_data.boundary}--\r\n) + ].join) + end + end end describe "#size" do diff --git a/spec/lib/http/form_data/urlencoded_spec.rb b/spec/lib/http/form_data/urlencoded_spec.rb index 8f9d733..02d1e21 100644 --- a/spec/lib/http/form_data/urlencoded_spec.rb +++ b/spec/lib/http/form_data/urlencoded_spec.rb @@ -5,14 +5,9 @@ subject(:form_data) { HTTP::FormData::Urlencoded.new data } it "supports any Enumerables of pairs" do - form_data = described_class.new( - Enumerator.new do |y| - y << %w[foo abc] - y << ["foo", %w[def xyz]] - end - ) - - expect(form_data.to_s).to eq("foo=abc&foo=def&foo=xyz") + form_data = described_class.new([%w[foo bar], ["foo", %w[baz moo]]]) + + expect(form_data.to_s).to eq("foo=bar&foo=baz&foo=moo") end describe "#content_type" do From c543ce37cd5568b24e256008f676678269658803 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Fri, 28 May 2021 21:20:19 -0400 Subject: [PATCH 12/13] Update lib/http/form_data/multipart.rb Co-authored-by: Alexey Zapparov --- lib/http/form_data/multipart.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http/form_data/multipart.rb b/lib/http/form_data/multipart.rb index 48103aa..2724b2b 100644 --- a/lib/http/form_data/multipart.rb +++ b/lib/http/form_data/multipart.rb @@ -17,7 +17,7 @@ class Multipart # @param [#to_h, Hash] data form data key-value Hash def initialize(data, boundary: self.class.generate_boundary) @boundary = boundary.to_s.freeze - @io = CompositeIO.new [*parts(data).flat_map { |part| [glue, part] }, tail] + @io = CompositeIO.new(parts(data).flat_map { |part| [glue, part] } << tail) end # Generates a string suitable for using as a boundary in multipart form From fb93be197189c86d8e2913d64962e55dadc22107 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Wed, 9 Jun 2021 11:52:53 -0400 Subject: [PATCH 13/13] Revert rspec max output length config --- spec/spec_helper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1f0ca63..80d8cba 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,8 +17,6 @@ # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true - # This option will remove the default 200 character limit for RSpec diffs - expectations.max_formatted_output_length = nil end config.mock_with :rspec do |mocks|