Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions lib/http/form_data/multipart.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

@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]
Comment thread
mathisto marked this conversation as resolved.
Outdated
end

# Generates a string suitable for using as a boundary in multipart form
Expand Down Expand Up @@ -54,6 +52,14 @@ def glue
def tail
@tail ||= "--#{@boundary}--#{CRLF}"
end

def parts(data)
if data.is_a?(Array)
Param.coerce data
else
Param.coerce FormData.ensure_hash data
end
end
end
end
end
6 changes: 3 additions & 3 deletions lib/http/form_data/multipart/param.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ def initialize(name, value)
@io = CompositeIO.new [header, @part, footer]
end

# Flattens given `data` Hash into an array of `Param`'s.
# Nested array are unwinded.
# Flattens given `data` Hash or Array into an array of `Param`'s.
# Nested arrays are unwinded.
# Behavior is similar to `URL.encode_www_form`.
#
# @param [Hash] data
# @param [Array || Hash] data
# @return [Array<FormData::MultiPart::Param>]
def self.coerce(data)
params = []
Expand Down
42 changes: 40 additions & 2 deletions spec/lib/http/form_data/multipart_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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 } }
Expand All @@ -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}",
Expand Down Expand Up @@ -87,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
Expand Down
6 changes: 6 additions & 0 deletions spec/lib/http/form_data/urlencoded_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
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([%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
subject { form_data.content_type }
it { is_expected.to eq "application/x-www-form-urlencoded" }
Expand Down
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down