httparty 0.13.7 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.editorconfig +18 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +1 -1
- data/Changelog.md +624 -0
- data/Gemfile +11 -3
- data/Guardfile +3 -2
- data/README.md +18 -17
- data/bin/httparty +3 -1
- data/docs/README.md +223 -0
- data/examples/README.md +35 -12
- data/examples/aaws.rb +7 -3
- data/examples/body_stream.rb +14 -0
- data/examples/crack.rb +1 -1
- data/examples/custom_parsers.rb +5 -1
- data/examples/delicious.rb +4 -4
- data/examples/headers_and_user_agents.rb +7 -3
- data/examples/idn.rb +10 -0
- data/examples/logging.rb +4 -4
- data/examples/microsoft_graph.rb +52 -0
- data/examples/multipart.rb +35 -0
- data/examples/party_foul_mode.rb +90 -0
- data/examples/peer_cert.rb +9 -0
- data/examples/stackexchange.rb +1 -1
- data/examples/stream_download.rb +26 -0
- data/examples/tripit_sign_in.rb +17 -6
- data/examples/twitter.rb +2 -2
- data/examples/whoismyrep.rb +1 -1
- data/httparty.gemspec +9 -5
- data/lib/httparty/connection_adapter.rb +71 -24
- data/lib/httparty/cookie_hash.rb +10 -8
- data/lib/httparty/decompressor.rb +102 -0
- data/lib/httparty/exceptions.rb +42 -5
- data/lib/httparty/hash_conversions.rb +30 -8
- data/lib/httparty/headers_processor.rb +32 -0
- data/lib/httparty/logger/apache_formatter.rb +31 -6
- data/lib/httparty/logger/curl_formatter.rb +68 -23
- data/lib/httparty/logger/logger.rb +5 -1
- data/lib/httparty/logger/logstash_formatter.rb +62 -0
- data/lib/httparty/module_inheritable_attributes.rb +9 -9
- data/lib/httparty/net_digest_auth.rb +23 -21
- data/lib/httparty/parser.rb +28 -14
- data/lib/httparty/request/body.rb +125 -0
- data/lib/httparty/request/multipart_boundary.rb +13 -0
- data/lib/httparty/request/streaming_multipart_body.rb +190 -0
- data/lib/httparty/request.rb +224 -122
- data/lib/httparty/response/headers.rb +23 -19
- data/lib/httparty/response.rb +92 -13
- data/lib/httparty/response_fragment.rb +21 -0
- data/lib/httparty/text_encoder.rb +72 -0
- data/lib/httparty/utils.rb +13 -0
- data/lib/httparty/version.rb +3 -1
- data/lib/httparty.rb +118 -42
- data/script/release +4 -4
- data/website/css/common.css +1 -1
- metadata +50 -112
- data/.simplecov +0 -1
- data/.travis.yml +0 -7
- data/History +0 -390
- data/features/basic_authentication.feature +0 -20
- data/features/command_line.feature +0 -95
- data/features/deals_with_http_error_codes.feature +0 -26
- data/features/digest_authentication.feature +0 -30
- data/features/handles_compressed_responses.feature +0 -27
- data/features/handles_multiple_formats.feature +0 -57
- data/features/steps/env.rb +0 -27
- data/features/steps/httparty_response_steps.rb +0 -52
- data/features/steps/httparty_steps.rb +0 -43
- data/features/steps/mongrel_helper.rb +0 -127
- data/features/steps/remote_service_steps.rb +0 -90
- data/features/supports_read_timeout_option.feature +0 -13
- data/features/supports_redirection.feature +0 -22
- data/features/supports_timeout_option.feature +0 -13
- data/spec/fixtures/delicious.xml +0 -23
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +0 -3
- data/spec/fixtures/ssl/generate.sh +0 -29
- data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
- data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
- data/spec/fixtures/ssl/generated/ca.crt +0 -16
- data/spec/fixtures/ssl/generated/ca.key +0 -15
- data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
- data/spec/fixtures/ssl/generated/server.crt +0 -13
- data/spec/fixtures/ssl/generated/server.key +0 -15
- data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
- data/spec/fixtures/twitter.csv +0 -2
- data/spec/fixtures/twitter.json +0 -1
- data/spec/fixtures/twitter.xml +0 -403
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
- data/spec/httparty/connection_adapter_spec.rb +0 -468
- data/spec/httparty/cookie_hash_spec.rb +0 -83
- data/spec/httparty/exception_spec.rb +0 -38
- data/spec/httparty/hash_conversions_spec.rb +0 -41
- data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
- data/spec/httparty/logger/curl_formatter_spec.rb +0 -18
- data/spec/httparty/logger/logger_spec.rb +0 -38
- data/spec/httparty/net_digest_auth_spec.rb +0 -230
- data/spec/httparty/parser_spec.rb +0 -173
- data/spec/httparty/request_spec.rb +0 -1073
- data/spec/httparty/response_spec.rb +0 -241
- data/spec/httparty/ssl_spec.rb +0 -74
- data/spec/httparty_spec.rb +0 -850
- data/spec/spec_helper.rb +0 -59
- data/spec/support/ssl_test_helper.rb +0 -47
- data/spec/support/ssl_test_server.rb +0 -80
- data/spec/support/stub_response.rb +0 -49
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'multipart_boundary'
|
|
4
|
+
require_relative 'streaming_multipart_body'
|
|
5
|
+
|
|
6
|
+
module HTTParty
|
|
7
|
+
class Request
|
|
8
|
+
class Body
|
|
9
|
+
NEWLINE = "\r\n"
|
|
10
|
+
private_constant :NEWLINE
|
|
11
|
+
|
|
12
|
+
def initialize(params, query_string_normalizer: nil, force_multipart: false)
|
|
13
|
+
@params = params
|
|
14
|
+
@query_string_normalizer = query_string_normalizer
|
|
15
|
+
@force_multipart = force_multipart
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
if params.respond_to?(:to_hash)
|
|
20
|
+
multipart? ? generate_multipart : normalize_query(params)
|
|
21
|
+
else
|
|
22
|
+
params
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def boundary
|
|
27
|
+
@boundary ||= MultipartBoundary.generate
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def multipart?
|
|
31
|
+
params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def streaming?
|
|
35
|
+
multipart? && has_file?(params)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_stream
|
|
39
|
+
return nil unless streaming?
|
|
40
|
+
StreamingMultipartBody.new(prepared_parts, boundary)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prepared_parts
|
|
44
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
45
|
+
normalized_params.map do |key, value|
|
|
46
|
+
[key, value, file?(value)]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# https://html.spec.whatwg.org/#multipart-form-data
|
|
53
|
+
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
|
54
|
+
'"' => '%22',
|
|
55
|
+
"\r" => '%0D',
|
|
56
|
+
"\n" => '%0A'
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
def generate_multipart
|
|
60
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
61
|
+
|
|
62
|
+
multipart = normalized_params.inject(''.b) do |memo, (key, value)|
|
|
63
|
+
memo << "--#{boundary}#{NEWLINE}".b
|
|
64
|
+
memo << %(Content-Disposition: form-data; name="#{key}").b
|
|
65
|
+
# value.path is used to support ActionDispatch::Http::UploadedFile
|
|
66
|
+
# https://github.com/jnunemaker/httparty/pull/585
|
|
67
|
+
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}").b if file?(value)
|
|
68
|
+
memo << NEWLINE.b
|
|
69
|
+
memo << "Content-Type: #{content_type(value)}#{NEWLINE}".b if file?(value)
|
|
70
|
+
memo << NEWLINE.b
|
|
71
|
+
memo << content_body(value)
|
|
72
|
+
memo << NEWLINE.b
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
multipart << "--#{boundary}--#{NEWLINE}".b
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def has_file?(value)
|
|
79
|
+
if value.respond_to?(:to_hash)
|
|
80
|
+
value.to_hash.any? { |_, v| has_file?(v) }
|
|
81
|
+
elsif value.respond_to?(:to_ary)
|
|
82
|
+
value.to_ary.any? { |v| has_file?(v) }
|
|
83
|
+
else
|
|
84
|
+
file?(value)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def file?(object)
|
|
89
|
+
object.respond_to?(:path) && object.respond_to?(:read)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize_query(query)
|
|
93
|
+
if query_string_normalizer
|
|
94
|
+
query_string_normalizer.call(query)
|
|
95
|
+
else
|
|
96
|
+
HashConversions.to_params(query)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def content_body(object)
|
|
101
|
+
if file?(object)
|
|
102
|
+
object = (file = object).read
|
|
103
|
+
object.force_encoding(Encoding::BINARY) if object.respond_to?(:force_encoding)
|
|
104
|
+
file.rewind if file.respond_to?(:rewind)
|
|
105
|
+
object.to_s
|
|
106
|
+
else
|
|
107
|
+
object.to_s.b
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def content_type(object)
|
|
112
|
+
return object.content_type if object.respond_to?(:content_type)
|
|
113
|
+
require 'mini_mime'
|
|
114
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
|
115
|
+
mime ? mime.content_type : 'application/octet-stream'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def file_name(object)
|
|
119
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
attr_reader :params, :query_string_normalizer, :force_multipart
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
class Request
|
|
5
|
+
class StreamingMultipartBody
|
|
6
|
+
NEWLINE = "\r\n"
|
|
7
|
+
CHUNK_SIZE = 64 * 1024 # 64 KB chunks
|
|
8
|
+
|
|
9
|
+
def initialize(parts, boundary)
|
|
10
|
+
@parts = parts
|
|
11
|
+
@boundary = boundary
|
|
12
|
+
@part_index = 0
|
|
13
|
+
@state = :header
|
|
14
|
+
@current_file = nil
|
|
15
|
+
@header_buffer = nil
|
|
16
|
+
@header_offset = 0
|
|
17
|
+
@footer_sent = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def size
|
|
21
|
+
@size ||= calculate_size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read(length = nil, outbuf = nil)
|
|
25
|
+
outbuf = outbuf ? outbuf.replace(''.b) : ''.b
|
|
26
|
+
|
|
27
|
+
return read_all(outbuf) if length.nil?
|
|
28
|
+
|
|
29
|
+
while outbuf.bytesize < length
|
|
30
|
+
chunk = read_chunk(length - outbuf.bytesize)
|
|
31
|
+
break if chunk.nil?
|
|
32
|
+
outbuf << chunk
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
outbuf.empty? ? nil : outbuf
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def rewind
|
|
39
|
+
@part_index = 0
|
|
40
|
+
@state = :header
|
|
41
|
+
@current_file = nil
|
|
42
|
+
@header_buffer = nil
|
|
43
|
+
@header_offset = 0
|
|
44
|
+
@footer_sent = false
|
|
45
|
+
@parts.each do |_key, value, _is_file|
|
|
46
|
+
value.rewind if value.respond_to?(:rewind)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def read_all(outbuf)
|
|
53
|
+
while (chunk = read_chunk(CHUNK_SIZE))
|
|
54
|
+
outbuf << chunk
|
|
55
|
+
end
|
|
56
|
+
outbuf.empty? ? nil : outbuf
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def read_chunk(max_length)
|
|
60
|
+
loop do
|
|
61
|
+
return nil if @part_index >= @parts.size && @footer_sent
|
|
62
|
+
|
|
63
|
+
if @part_index >= @parts.size
|
|
64
|
+
@footer_sent = true
|
|
65
|
+
return "--#{@boundary}--#{NEWLINE}".b
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
key, value, is_file = @parts[@part_index]
|
|
69
|
+
|
|
70
|
+
case @state
|
|
71
|
+
when :header
|
|
72
|
+
chunk = read_header_chunk(key, value, is_file, max_length)
|
|
73
|
+
return chunk if chunk
|
|
74
|
+
|
|
75
|
+
when :body
|
|
76
|
+
chunk = read_body_chunk(value, is_file, max_length)
|
|
77
|
+
return chunk if chunk
|
|
78
|
+
|
|
79
|
+
when :newline
|
|
80
|
+
@state = :header
|
|
81
|
+
@part_index += 1
|
|
82
|
+
return NEWLINE.b
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def read_header_chunk(key, value, is_file, max_length)
|
|
88
|
+
if @header_buffer.nil?
|
|
89
|
+
@header_buffer = build_part_header(key, value, is_file)
|
|
90
|
+
@header_offset = 0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
remaining = @header_buffer.bytesize - @header_offset
|
|
94
|
+
if remaining > 0
|
|
95
|
+
chunk_size = [remaining, max_length].min
|
|
96
|
+
chunk = @header_buffer.byteslice(@header_offset, chunk_size)
|
|
97
|
+
@header_offset += chunk_size
|
|
98
|
+
return chunk
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@header_buffer = nil
|
|
102
|
+
@header_offset = 0
|
|
103
|
+
@state = :body
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read_body_chunk(value, is_file, max_length)
|
|
108
|
+
if is_file
|
|
109
|
+
chunk = read_file_chunk(value, max_length)
|
|
110
|
+
if chunk
|
|
111
|
+
return chunk
|
|
112
|
+
else
|
|
113
|
+
@current_file = nil
|
|
114
|
+
@state = :newline
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
@state = :newline
|
|
119
|
+
return value.to_s.b
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_file_chunk(file, max_length)
|
|
124
|
+
chunk_size = [max_length, CHUNK_SIZE].min
|
|
125
|
+
chunk = file.read(chunk_size)
|
|
126
|
+
return nil if chunk.nil?
|
|
127
|
+
chunk.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
|
|
128
|
+
chunk
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_part_header(key, value, is_file)
|
|
132
|
+
header = "--#{@boundary}#{NEWLINE}".b
|
|
133
|
+
header << %(Content-Disposition: form-data; name="#{key}").b
|
|
134
|
+
if is_file
|
|
135
|
+
header << %(; filename="#{file_name(value).gsub(/["\r\n]/, replacement_table)}").b
|
|
136
|
+
header << NEWLINE.b
|
|
137
|
+
header << "Content-Type: #{content_type(value)}#{NEWLINE}".b
|
|
138
|
+
else
|
|
139
|
+
header << NEWLINE.b
|
|
140
|
+
end
|
|
141
|
+
header << NEWLINE.b
|
|
142
|
+
header
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def calculate_size
|
|
146
|
+
total = 0
|
|
147
|
+
@parts.each do |key, value, is_file|
|
|
148
|
+
total += build_part_header(key, value, is_file).bytesize
|
|
149
|
+
total += content_size(value, is_file)
|
|
150
|
+
total += NEWLINE.bytesize
|
|
151
|
+
end
|
|
152
|
+
total += "--#{@boundary}--#{NEWLINE}".bytesize
|
|
153
|
+
total
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def content_size(value, is_file)
|
|
157
|
+
if is_file
|
|
158
|
+
if value.respond_to?(:size)
|
|
159
|
+
value.size
|
|
160
|
+
elsif value.respond_to?(:stat)
|
|
161
|
+
value.stat.size
|
|
162
|
+
else
|
|
163
|
+
value.read.bytesize.tap { value.rewind }
|
|
164
|
+
end
|
|
165
|
+
else
|
|
166
|
+
value.to_s.b.bytesize
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def content_type(object)
|
|
171
|
+
return object.content_type if object.respond_to?(:content_type)
|
|
172
|
+
require 'mini_mime'
|
|
173
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
|
174
|
+
mime ? mime.content_type : 'application/octet-stream'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def file_name(object)
|
|
178
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def replacement_table
|
|
182
|
+
@replacement_table ||= {
|
|
183
|
+
'"' => '%22',
|
|
184
|
+
"\r" => '%0D',
|
|
185
|
+
"\n" => '%0A'
|
|
186
|
+
}.freeze
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|