|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +module HTTP |
| 4 | + module FormData |
| 5 | + # Represents file form param. |
| 6 | + # |
| 7 | + # @example Usage with StringIO |
| 8 | + # |
| 9 | + # io = StringIO.new "foo bar baz" |
| 10 | + # FormData::File.new io, filename: "foobar.txt" |
| 11 | + # |
| 12 | + # @example Usage with IO |
| 13 | + # |
| 14 | + # File.open "/home/ixti/avatar.png" do |io| |
| 15 | + # FormData::File.new io |
| 16 | + # end |
| 17 | + # |
| 18 | + # @example Usage with pathname |
| 19 | + # |
| 20 | + # FormData::File.new "/home/ixti/avatar.png" |
| 21 | + class File < Part |
| 22 | + # Default MIME type |
| 23 | + DEFAULT_MIME = "application/octet-stream" |
| 24 | + |
| 25 | + # Creates a new File from a path or IO object |
| 26 | + # |
| 27 | + # @example |
| 28 | + # File.new("/path/to/file.txt") |
| 29 | + # |
| 30 | + # @api public |
| 31 | + # @see DEFAULT_MIME |
| 32 | + # @param [String, Pathname, IO] path_or_io Filename or IO instance |
| 33 | + # @param [#to_h] opts |
| 34 | + # @option opts [#to_s] :content_type (DEFAULT_MIME) |
| 35 | + # Value of Content-Type header |
| 36 | + # @option opts [#to_s] :filename |
| 37 | + # When `path_or_io` is a String, Pathname or File, defaults to basename. |
| 38 | + # When `path_or_io` is a IO, defaults to `"stream-{object_id}"` |
| 39 | + def initialize(path_or_io, opts = nil) # rubocop:disable Lint/MissingSuper |
| 40 | + opts = FormData.ensure_hash(opts) |
| 41 | + |
| 42 | + @io = make_io(path_or_io) |
| 43 | + @autoclose = path_or_io.is_a?(String) || path_or_io.is_a?(Pathname) |
| 44 | + @content_type = opts.fetch(:content_type, DEFAULT_MIME).to_s |
| 45 | + @filename = opts.fetch(:filename, filename_for(@io)) |
| 46 | + end |
| 47 | + |
| 48 | + # Closes the underlying IO if it was opened by this instance |
| 49 | + # |
| 50 | + # When the File was created from a String path or Pathname, the |
| 51 | + # underlying file handle is closed. When created from an existing |
| 52 | + # IO object, this is a no-op (the caller is responsible for |
| 53 | + # closing it). |
| 54 | + # |
| 55 | + # @example |
| 56 | + # file = FormData::File.new("/path/to/file.txt") |
| 57 | + # file.to_s |
| 58 | + # file.close |
| 59 | + # |
| 60 | + # @api public |
| 61 | + # @return [void] |
| 62 | + def close |
| 63 | + @io.close if @autoclose |
| 64 | + end |
| 65 | + |
| 66 | + private |
| 67 | + |
| 68 | + # Wraps path_or_io into an IO object |
| 69 | + # |
| 70 | + # @api private |
| 71 | + # @param [String, Pathname, IO] path_or_io |
| 72 | + # @return [IO] |
| 73 | + def make_io(path_or_io) |
| 74 | + case path_or_io |
| 75 | + when String then ::File.new(path_or_io, binmode: true) |
| 76 | + when Pathname then path_or_io.open(binmode: true) |
| 77 | + else path_or_io |
| 78 | + end |
| 79 | + end |
| 80 | + |
| 81 | + # Determines filename for the given IO |
| 82 | + # |
| 83 | + # @api private |
| 84 | + # @param [IO] io |
| 85 | + # @return [String] |
| 86 | + def filename_for(io) |
| 87 | + if io.respond_to?(:path) |
| 88 | + ::File.basename(io.path) |
| 89 | + else |
| 90 | + "stream-#{io.object_id}" |
| 91 | + end |
| 92 | + end |
| 93 | + end |
| 94 | + end |
| 95 | +end |
0 commit comments