File: body.rb

package info (click to toggle)
ruby-httparty 0.24.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 964 kB
  • sloc: ruby: 7,521; xml: 425; sh: 35; makefile: 14
file content (125 lines) | stat: -rw-r--r-- 3,682 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
# frozen_string_literal: true

require_relative 'multipart_boundary'
require_relative 'streaming_multipart_body'

module HTTParty
  class Request
    class Body
      NEWLINE = "\r\n"
      private_constant :NEWLINE

      def initialize(params, query_string_normalizer: nil, force_multipart: false)
        @params = params
        @query_string_normalizer = query_string_normalizer
        @force_multipart = force_multipart
      end

      def call
        if params.respond_to?(:to_hash)
          multipart? ? generate_multipart : normalize_query(params)
        else
          params
        end
      end

      def boundary
        @boundary ||= MultipartBoundary.generate
      end

      def multipart?
        params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
      end

      def streaming?
        multipart? && has_file?(params)
      end

      def to_stream
        return nil unless streaming?
        StreamingMultipartBody.new(prepared_parts, boundary)
      end

      def prepared_parts
        normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
        normalized_params.map do |key, value|
          [key, value, file?(value)]
        end
      end

      private

      # https://html.spec.whatwg.org/#multipart-form-data
      MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
        '"'  => '%22',
        "\r" => '%0D',
        "\n" => '%0A'
      }.freeze

      def generate_multipart
        normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }

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

        multipart << "--#{boundary}--#{NEWLINE}".b
      end

      def has_file?(value)
        if value.respond_to?(:to_hash)
          value.to_hash.any? { |_, v| has_file?(v) }
        elsif value.respond_to?(:to_ary)
          value.to_ary.any? { |v| has_file?(v) }
        else
          file?(value)
        end
      end

      def file?(object)
        object.respond_to?(:path) && object.respond_to?(:read)
      end

      def normalize_query(query)
        if query_string_normalizer
          query_string_normalizer.call(query)
        else
          HashConversions.to_params(query)
        end
      end

      def content_body(object)
        if file?(object)
          object = (file = object).read
          object.force_encoding(Encoding::BINARY) if object.respond_to?(:force_encoding)
          file.rewind if file.respond_to?(:rewind)
          object.to_s
        else
          object.to_s.b
        end
      end

      def content_type(object)
        return object.content_type if object.respond_to?(:content_type)
        require 'mini_mime'
        mime = MiniMime.lookup_by_filename(object.path)
        mime ? mime.content_type : 'application/octet-stream'
      end

      def file_name(object)
        object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
      end

      attr_reader :params, :query_string_normalizer, :force_multipart
    end
  end
end