File: middleware.rb

package info (click to toggle)
ruby-faraday-multipart 1.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 96 kB
  • sloc: ruby: 201; makefile: 4
file content (134 lines) | stat: -rw-r--r-- 4,013 bytes parent folder | download
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