1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# frozen_string_literal: true
require 'securerandom'
module Faraday
module Multipart
# Middleware for supporting multi-part requests.
class Middleware < Faraday::Middleware
CONTENT_TYPE = 'Content-Type'
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
def initialize(app = nil, options = {})
super(app)
@options = options
end
# Checks for files in the payload, otherwise leaves everything untouched.
#
# @param env [Faraday::Env]
def call(env)
match_content_type(env) do |params|
env.request.boundary ||= unique_boundary
env.request_headers[CONTENT_TYPE] +=
"; boundary=#{env.request.boundary}"
env.body = create_multipart(env, params)
end
@app.call env
end
private
# @param env [Faraday::Env]
# @yield [request_body] Body of the request
def match_content_type(env)
return unless process_request?(env)
env.request_headers[CONTENT_TYPE] ||= mime_type
return if env.body.respond_to?(:to_str) || env.body.respond_to?(:read)
yield(env.body)
end
# @param env [Faraday::Env]
def process_request?(env)
type = request_type(env)
env.body.respond_to?(:each_key) && !env.body.empty? && (
(type.empty? && has_multipart?(env.body)) ||
(type == mime_type)
)
end
# @param env [Faraday::Env]
#
# @return [String]
def request_type(env)
type = env.request_headers[CONTENT_TYPE].to_s
type = type.split(';', 2).first if type.index(';')
type
end
# Returns true if obj is an enumerable with values that are multipart.
#
# @param obj [Object]
# @return [Boolean]
def has_multipart?(obj)
if obj.respond_to?(:each)
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
return true if val.respond_to?(:content_type) || has_multipart?(val)
end
end
false
end
# @param env [Faraday::Env]
# @param params [Hash]
def create_multipart(env, params)
boundary = env.request.boundary
parts = process_params(params) do |key, value|
part(boundary, key, value)
end
parts << Faraday::Multipart::Parts::EpiloguePart.new(boundary)
body = Faraday::Multipart::CompositeReadIO.new(parts)
env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
body
end
def part(boundary, key, value)
if value.respond_to?(:to_part)
value.to_part(boundary, key)
else
Faraday::Multipart::Parts::Part.new(boundary, key, value)
end
end
# @return [String]
def unique_boundary
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
end
# @param params [Hash]
# @param prefix [String]
# @param pieces [Array]
def process_params(params, prefix = nil, pieces = nil, &block)
params.inject(pieces || []) do |all, (key, value)|
if prefix
key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
end
case value
when Array
values = value.inject([]) { |a, v| a << [nil, v] }
process_params(values, key, all, &block)
when Hash
process_params(value, key, all, &block)
else
all << block.call(key, value) # rubocop:disable Performance/RedundantBlockCall
end
end
end
# Determines and provides the multipart mime type for the request.
#
# @return [String] the multipart mime type
def mime_type
@mime_type ||= if @options[:content_type].to_s.match?(%r{\Amultipart/.+})
@options[:content_type].to_s
else
'multipart/form-data'
end
end
end
end
end
|