Skip to content

Commit aefe58f

Browse files
fix: always send filename=... for multipart requests where a file is expected
1 parent 6566897 commit aefe58f

File tree

5 files changed

+39
-22
lines changed

5 files changed

+39
-22
lines changed

lib/orb/file_part.rb

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,21 @@ def to_json(*a) = read.to_json(*a)
3838
def to_yaml(*a) = read.to_yaml(*a)
3939

4040
# @param content [Pathname, StringIO, IO, String]
41-
# @param filename [String, nil]
41+
# @param filename [Pathname, String, nil]
4242
# @param content_type [String, nil]
4343
def initialize(content, filename: nil, content_type: nil)
44-
@content = content
44+
@content_type = content_type
4545
@filename =
46-
case content
47-
in Pathname
48-
filename.nil? ? content.basename.to_path : ::File.basename(filename)
46+
case [filename, (@content = content)]
47+
in [String | Pathname, _]
48+
::File.basename(filename)
49+
in [nil, Pathname]
50+
content.basename.to_path
51+
in [nil, IO]
52+
content.to_path
4953
else
50-
filename.nil? ? nil : ::File.basename(filename)
54+
filename
5155
end
52-
@content_type = content_type
5356
end
5457
end
5558
end

lib/orb/internal/type/file_input.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,20 @@ def coerce(value, state:)
8181
#
8282
# @return [Pathname, StringIO, IO, String, Object]
8383
def dump(value, state:)
84-
# rubocop:disable Lint/DuplicateBranch
8584
case value
85+
in StringIO | String
86+
# https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
87+
# while not required, a filename is recommended, and in practice many servers do expect this
88+
Orb::FilePart.new(value, filename: "upload")
8689
in IO
8790
state[:can_retry] = false
91+
value.to_path.nil? ? Orb::FilePart.new(value, filename: "upload") : value
8892
in Orb::FilePart if value.content.is_a?(IO)
8993
state[:can_retry] = false
94+
value
9095
else
96+
value
9197
end
92-
# rubocop:enable Lint/DuplicateBranch
93-
94-
value
9598
end
9699

97100
# @api private

rbi/orb/file_part.rbi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module Orb
2727
sig do
2828
params(
2929
content: T.any(Pathname, StringIO, IO, String),
30-
filename: T.nilable(String),
30+
filename: T.nilable(T.any(Pathname, String)),
3131
content_type: T.nilable(String)
3232
).returns(T.attached_class)
3333
end

sig/orb/file_part.rbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Orb
1414

1515
def initialize: (
1616
Pathname | StringIO | IO | String content,
17-
?filename: String?,
17+
?filename: (Pathname | String)?,
1818
?content_type: String?
1919
) -> void
2020
end

test/orb/internal/util_test.rb

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,24 @@ def test_encoding_length
227227

228228
def test_file_encode
229229
file = Pathname(__FILE__)
230+
fileinput = Orb::Internal::Type::Converter.dump(Orb::Internal::Type::FileInput, "abc")
230231
headers = {"content-type" => "multipart/form-data"}
231232
cases = {
232-
"abc" => "abc",
233-
StringIO.new("abc") => "abc",
234-
Orb::FilePart.new("abc") => "abc",
235-
Orb::FilePart.new(StringIO.new("abc")) => "abc",
236-
file => /^class Orb/,
237-
Orb::FilePart.new(file) => /^class Orb/
233+
"abc" => ["", "abc"],
234+
StringIO.new("abc") => ["", "abc"],
235+
fileinput => %w[upload abc],
236+
Orb::FilePart.new(StringIO.new("abc")) => ["", "abc"],
237+
file => [file.basename.to_path, /^class Orb/],
238+
Orb::FilePart.new(file, filename: "d o g") => ["d%20o%20g", /^class Orb/]
238239
}
239-
cases.each do |body, val|
240+
cases.each do |body, testcase|
241+
filename, val = testcase
240242
encoded = Orb::Internal::Util.encode_content(headers, body)
241243
cgi = FakeCGI.new(*encoded)
244+
io = cgi[""]
242245
assert_pattern do
243-
cgi[""].read => ^val
246+
io.original_filename => ^filename
247+
io.read => ^val
244248
end
245249
end
246250
end
@@ -261,7 +265,14 @@ def test_hash_encode
261265
cgi = FakeCGI.new(*encoded)
262266
testcase.each do |key, val|
263267
assert_pattern do
264-
cgi[key] => ^val
268+
parsed =
269+
case (p = cgi[key])
270+
in StringIO
271+
p.read
272+
else
273+
p
274+
end
275+
parsed => ^val
265276
end
266277
end
267278
end

0 commit comments

Comments
 (0)