Skip to content

Commit 5c8b45e

Browse files
authored
Merge pull request #823 from jnunemaker/mixed-encodings
Force binary encoding throughout
2 parents c74571f + 6419cb3 commit 5c8b45e

File tree

2 files changed

+37
-19
lines changed

2 files changed

+37
-19
lines changed

lib/httparty/request/body.rb

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ def multipart?
4242
def generate_multipart
4343
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
4444

45-
multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
46-
memo << "--#{boundary}#{NEWLINE}"
47-
memo << %(Content-Disposition: form-data; name="#{key}")
45+
multipart = normalized_params.inject(''.b) do |memo, (key, value)|
46+
memo << "--#{boundary}#{NEWLINE}".b
47+
memo << %(Content-Disposition: form-data; name="#{key}").b
4848
# value.path is used to support ActionDispatch::Http::UploadedFile
4949
# https://github.com/jnunemaker/httparty/pull/585
50-
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value)
51-
memo << NEWLINE
52-
memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
53-
memo << NEWLINE
50+
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}").b if file?(value)
51+
memo << NEWLINE.b
52+
memo << "Content-Type: #{content_type(value)}#{NEWLINE}".b if file?(value)
53+
memo << NEWLINE.b
5454
memo << content_body(value)
55-
memo << NEWLINE
55+
memo << NEWLINE.b
5656
end
5757

58-
multipart << "--#{boundary}--#{NEWLINE}"
58+
multipart << "--#{boundary}--#{NEWLINE}".b
5959
end
6060

6161
def has_file?(value)
@@ -83,11 +83,12 @@ def normalize_query(query)
8383
def content_body(object)
8484
if file?(object)
8585
object = (file = object).read
86-
object.force_encoding(Encoding::UTF_8) if object.respond_to?(:force_encoding)
86+
object.force_encoding(Encoding::BINARY) if object.respond_to?(:force_encoding)
8787
file.rewind if file.respond_to?(:rewind)
88+
object.to_s
89+
else
90+
object.to_s.b
8891
end
89-
90-
object.to_s
9192
end
9293

9394
def content_type(object)

spec/httparty/request/body_spec.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
let(:expected_file_contents) { "GIF89a\u0001\u0000\u0001\u0000\u0000\xFF\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0000;" }
4141
let(:expected_content_type) { 'image/gif' }
4242
let(:multipart_params) do
43-
"--------------------------c772861a5109d5ef\r\n" \
43+
("--------------------------c772861a5109d5ef\r\n" \
4444
"Content-Disposition: form-data; name=\"user[avatar]\"; filename=\"#{expected_file_name}\"\r\n" \
4545
"Content-Type: #{expected_content_type}\r\n" \
4646
"\r\n" \
@@ -57,7 +57,7 @@
5757
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
5858
"\r\n" \
5959
"true\r\n" \
60-
"--------------------------c772861a5109d5ef--\r\n"
60+
"--------------------------c772861a5109d5ef--\r\n").b
6161
end
6262

6363
it { is_expected.to eq multipart_params }
@@ -76,7 +76,7 @@
7676
}
7777
end
7878
let(:multipart_params) do
79-
"--------------------------c772861a5109d5ef\r\n" \
79+
("--------------------------c772861a5109d5ef\r\n" \
8080
"Content-Disposition: form-data; name=\"user[first_name]\"\r\n" \
8181
"\r\n" \
8282
"John\r\n" \
@@ -88,7 +88,7 @@
8888
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
8989
"\r\n" \
9090
"true\r\n" \
91-
"--------------------------c772861a5109d5ef--\r\n"
91+
"--------------------------c772861a5109d5ef--\r\n").b
9292
end
9393

9494
it { is_expected.to eq multipart_params }
@@ -122,7 +122,7 @@
122122
}
123123
end
124124
let(:multipart_params) do
125-
"--------------------------c772861a5109d5ef\r\n" \
125+
("--------------------------c772861a5109d5ef\r\n" \
126126
"Content-Disposition: form-data; name=\"user[attachment_file]\"; filename=\"#{expected_file_name}\"\r\n" \
127127
"Content-Type: text/plain\r\n" \
128128
"\r\n" \
@@ -131,7 +131,7 @@
131131
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
132132
"\r\n" \
133133
"true\r\n" \
134-
"--------------------------c772861a5109d5ef--\r\n"
134+
"--------------------------c772861a5109d5ef--\r\n").b
135135
end
136136

137137
it { is_expected.to eq multipart_params }
@@ -148,7 +148,24 @@
148148
}
149149
end
150150

151-
it { expect { subject }.not_to raise_error }
151+
it 'does not raise encoding errors' do
152+
expect { subject }.not_to raise_error
153+
end
154+
155+
it 'produces valid binary multipart body' do
156+
result = subject
157+
expect(result.encoding).to eq(Encoding::BINARY)
158+
expect(result).to include("Jöhn Döé".b)
159+
end
160+
161+
it 'concatenates binary file data with UTF-8 text without corruption' do
162+
result = subject
163+
# Should contain both the UTF-8 user field and binary GIF data
164+
expect(result).to include('Content-Disposition: form-data; name="user"'.b)
165+
expect(result).to include("Jöhn Döé".b)
166+
expect(result).to include('Content-Disposition: form-data; name="avatar"'.b)
167+
expect(result).to include("GIF89a".b) # GIF file header
168+
end
152169
end
153170
end
154171
end

0 commit comments

Comments
 (0)