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
data/lib/httparty/request.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
1
5
|
module HTTParty
|
|
2
6
|
class Request #:nodoc:
|
|
3
7
|
SupportedHTTPMethods = [
|
|
@@ -9,7 +13,10 @@ module HTTParty
|
|
|
9
13
|
Net::HTTP::Head,
|
|
10
14
|
Net::HTTP::Options,
|
|
11
15
|
Net::HTTP::Move,
|
|
12
|
-
Net::HTTP::Copy
|
|
16
|
+
Net::HTTP::Copy,
|
|
17
|
+
Net::HTTP::Mkcol,
|
|
18
|
+
Net::HTTP::Lock,
|
|
19
|
+
Net::HTTP::Unlock,
|
|
13
20
|
]
|
|
14
21
|
|
|
15
22
|
SupportedURISchemes = ['http', 'https', 'webcal', nil]
|
|
@@ -26,10 +33,35 @@ module HTTParty
|
|
|
26
33
|
end.flatten.join('&')
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
JSON_API_QUERY_STRING_NORMALIZER = proc do |query|
|
|
37
|
+
Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
|
|
38
|
+
if value.nil?
|
|
39
|
+
key.to_s
|
|
40
|
+
elsif value.respond_to?(:to_ary)
|
|
41
|
+
values = value.to_ary.map{|v| ERB::Util.url_encode(v.to_s)}
|
|
42
|
+
"#{key}=#{values.join(',')}"
|
|
43
|
+
else
|
|
44
|
+
HashConversions.to_params(key => value)
|
|
45
|
+
end
|
|
46
|
+
end.flatten.join('&')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self._load(data)
|
|
50
|
+
http_method, path, options, last_response, last_uri, raw_request = Marshal.load(data)
|
|
51
|
+
instance = new(http_method, path, options)
|
|
52
|
+
instance.last_response = last_response
|
|
53
|
+
instance.last_uri = last_uri
|
|
54
|
+
instance.instance_variable_set("@raw_request", raw_request)
|
|
55
|
+
instance
|
|
56
|
+
end
|
|
57
|
+
|
|
29
58
|
attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
|
|
30
59
|
attr_reader :path
|
|
31
60
|
|
|
32
61
|
def initialize(http_method, path, o = {})
|
|
62
|
+
@changed_hosts = false
|
|
63
|
+
@credentials_sent = false
|
|
64
|
+
|
|
33
65
|
self.http_method = http_method
|
|
34
66
|
self.options = {
|
|
35
67
|
limit: o.delete(:no_follow) ? 1 : 5,
|
|
@@ -50,7 +82,7 @@ module HTTParty
|
|
|
50
82
|
@path = if uri.is_a?(uri_adapter)
|
|
51
83
|
uri
|
|
52
84
|
elsif String.try_convert(uri)
|
|
53
|
-
uri_adapter.parse
|
|
85
|
+
uri_adapter.parse(uri).normalize
|
|
54
86
|
else
|
|
55
87
|
raise ArgumentError,
|
|
56
88
|
"bad argument (expected #{uri_adapter} object or URI string)"
|
|
@@ -66,14 +98,22 @@ module HTTParty
|
|
|
66
98
|
end
|
|
67
99
|
|
|
68
100
|
def uri
|
|
69
|
-
if redirect && path.relative? && path.path[0] !=
|
|
70
|
-
last_uri_host = @last_uri.path.gsub(/[^\/]+$/,
|
|
101
|
+
if redirect && path.relative? && path.path[0] != '/'
|
|
102
|
+
last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
|
|
71
103
|
|
|
72
|
-
path.path = "/#{path.path}" if last_uri_host[-1] !=
|
|
73
|
-
path.path = last_uri_host
|
|
104
|
+
path.path = "/#{path.path}" if last_uri_host[-1] != '/'
|
|
105
|
+
path.path = "#{last_uri_host}#{path.path}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if path.relative? && path.host
|
|
109
|
+
new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
|
|
110
|
+
elsif path.relative?
|
|
111
|
+
new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
|
|
112
|
+
else
|
|
113
|
+
new_uri = path.clone
|
|
74
114
|
end
|
|
75
115
|
|
|
76
|
-
new_uri
|
|
116
|
+
validate_uri_safety!(new_uri) unless redirect
|
|
77
117
|
|
|
78
118
|
# avoid double query string on redirects [#12]
|
|
79
119
|
unless redirect
|
|
@@ -90,10 +130,10 @@ module HTTParty
|
|
|
90
130
|
def base_uri
|
|
91
131
|
if redirect
|
|
92
132
|
base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
|
|
93
|
-
base_uri
|
|
133
|
+
base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
|
|
94
134
|
base_uri
|
|
95
135
|
else
|
|
96
|
-
options[:base_uri]
|
|
136
|
+
options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
|
|
97
137
|
end
|
|
98
138
|
end
|
|
99
139
|
|
|
@@ -113,38 +153,56 @@ module HTTParty
|
|
|
113
153
|
validate
|
|
114
154
|
setup_raw_request
|
|
115
155
|
chunked_body = nil
|
|
156
|
+
current_http = http
|
|
116
157
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
begin
|
|
159
|
+
self.last_response = current_http.request(@raw_request) do |http_response|
|
|
160
|
+
if block
|
|
161
|
+
chunks = []
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
163
|
+
http_response.read_body do |fragment|
|
|
164
|
+
encoded_fragment = encode_text(fragment, http_response['content-type'])
|
|
165
|
+
chunks << encoded_fragment if !options[:stream_body]
|
|
166
|
+
block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
|
|
167
|
+
end
|
|
125
168
|
|
|
126
|
-
|
|
169
|
+
chunked_body = chunks.join
|
|
170
|
+
end
|
|
127
171
|
end
|
|
172
|
+
|
|
173
|
+
handle_host_redirection if response_redirects?
|
|
174
|
+
result = handle_unauthorized
|
|
175
|
+
result ||= handle_response(chunked_body, &block)
|
|
176
|
+
result
|
|
177
|
+
rescue *COMMON_NETWORK_ERRORS => e
|
|
178
|
+
raise options[:foul] ? HTTParty::NetworkError.new("#{e.class}: #{e.message}") : e
|
|
128
179
|
end
|
|
180
|
+
end
|
|
129
181
|
|
|
130
|
-
|
|
131
|
-
|
|
182
|
+
def handle_unauthorized(&block)
|
|
183
|
+
return unless digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
|
184
|
+
return if @credentials_sent
|
|
185
|
+
@credentials_sent = true
|
|
186
|
+
perform(&block)
|
|
132
187
|
end
|
|
133
188
|
|
|
134
189
|
def raw_body
|
|
135
190
|
@raw_request.body
|
|
136
191
|
end
|
|
137
192
|
|
|
193
|
+
def _dump(_level)
|
|
194
|
+
opts = options.dup
|
|
195
|
+
opts.delete(:logger)
|
|
196
|
+
opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc)
|
|
197
|
+
Marshal.dump([http_method, path, opts, last_response, @last_uri, @raw_request])
|
|
198
|
+
end
|
|
199
|
+
|
|
138
200
|
private
|
|
139
201
|
|
|
140
202
|
def http
|
|
141
203
|
connection_adapter.call(uri, options)
|
|
142
204
|
end
|
|
143
205
|
|
|
144
|
-
def body
|
|
145
|
-
options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
|
|
146
|
-
end
|
|
147
|
-
|
|
148
206
|
def credentials
|
|
149
207
|
(options[:basic_auth] || options[:digest_auth]).to_hash
|
|
150
208
|
end
|
|
@@ -170,22 +228,65 @@ module HTTParty
|
|
|
170
228
|
end
|
|
171
229
|
|
|
172
230
|
def setup_raw_request
|
|
173
|
-
|
|
174
|
-
|
|
231
|
+
if options[:headers].respond_to?(:to_hash)
|
|
232
|
+
headers_hash = options[:headers].to_hash
|
|
233
|
+
else
|
|
234
|
+
headers_hash = nil
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
@raw_request = http_method.new(request_uri(uri), headers_hash)
|
|
175
238
|
@raw_request.body_stream = options[:body_stream] if options[:body_stream]
|
|
176
|
-
@raw_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
|
|
177
|
-
@raw_request.basic_auth(username, password) if options[:basic_auth]
|
|
178
|
-
setup_digest_auth if options[:digest_auth]
|
|
179
|
-
end
|
|
180
239
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
if options[:body]
|
|
241
|
+
body = Body.new(
|
|
242
|
+
options[:body],
|
|
243
|
+
query_string_normalizer: query_string_normalizer,
|
|
244
|
+
force_multipart: options[:multipart]
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if body.multipart?
|
|
248
|
+
content_type = "multipart/form-data; boundary=#{body.boundary}"
|
|
249
|
+
@raw_request['Content-Type'] = content_type
|
|
250
|
+
elsif options[:body].respond_to?(:to_hash) && !@raw_request['Content-Type']
|
|
251
|
+
@raw_request['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
252
|
+
end
|
|
185
253
|
|
|
186
|
-
|
|
187
|
-
|
|
254
|
+
if body.streaming? && options[:stream_body] == true
|
|
255
|
+
stream = body.to_stream
|
|
256
|
+
@raw_request.body_stream = stream
|
|
257
|
+
@raw_request['Content-Length'] = stream.size.to_s
|
|
258
|
+
else
|
|
259
|
+
@raw_request.body = body.call
|
|
260
|
+
end
|
|
188
261
|
end
|
|
262
|
+
|
|
263
|
+
@raw_request.instance_variable_set(:@decode_content, decompress_content?)
|
|
264
|
+
|
|
265
|
+
if options[:basic_auth] && send_authorization_header?
|
|
266
|
+
@raw_request.basic_auth(username, password)
|
|
267
|
+
@credentials_sent = true
|
|
268
|
+
end
|
|
269
|
+
setup_digest_auth if digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def digest_auth?
|
|
273
|
+
!!options[:digest_auth]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def decompress_content?
|
|
277
|
+
!options[:skip_decompression]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def response_unauthorized?
|
|
281
|
+
!!last_response && last_response.code == '401'
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def response_has_digest_auth_challenge?
|
|
285
|
+
!last_response['www-authenticate'].nil? && last_response['www-authenticate'].length > 0
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def setup_digest_auth
|
|
289
|
+
@raw_request.digest_auth(username, password, last_response)
|
|
189
290
|
end
|
|
190
291
|
|
|
191
292
|
def query_string(uri)
|
|
@@ -199,116 +300,77 @@ module HTTParty
|
|
|
199
300
|
query_string_parts << options[:query] unless options[:query].nil?
|
|
200
301
|
end
|
|
201
302
|
|
|
202
|
-
query_string_parts.reject!(&:empty?) unless query_string_parts == [
|
|
303
|
+
query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
|
|
203
304
|
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
|
|
204
305
|
end
|
|
205
306
|
|
|
206
|
-
def get_charset
|
|
207
|
-
content_type = last_response["content-type"]
|
|
208
|
-
if content_type.nil?
|
|
209
|
-
return nil
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
|
|
213
|
-
return $1
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
|
|
217
|
-
return $1.gsub(/\\(.)/, '\1')
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
nil
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def encode_with_ruby_encoding(body, charset)
|
|
224
|
-
encoding = Encoding.find(charset)
|
|
225
|
-
body.force_encoding(encoding)
|
|
226
|
-
rescue
|
|
227
|
-
body
|
|
228
|
-
end
|
|
229
|
-
|
|
230
307
|
def assume_utf16_is_big_endian
|
|
231
308
|
options[:assume_utf16_is_big_endian]
|
|
232
309
|
end
|
|
233
310
|
|
|
234
|
-
def
|
|
235
|
-
if
|
|
236
|
-
|
|
237
|
-
return body.force_encoding("UTF-16LE")
|
|
238
|
-
elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
|
|
239
|
-
return body.force_encoding("UTF-16BE")
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
if assume_utf16_is_big_endian
|
|
244
|
-
body.force_encoding("UTF-16BE")
|
|
311
|
+
def handle_response(raw_body, &block)
|
|
312
|
+
if response_redirects?
|
|
313
|
+
handle_redirection(&block)
|
|
245
314
|
else
|
|
246
|
-
body
|
|
247
|
-
end
|
|
248
|
-
end
|
|
315
|
+
raw_body ||= last_response.body
|
|
249
316
|
|
|
250
|
-
|
|
251
|
-
charset = get_charset
|
|
317
|
+
body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
|
|
252
318
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
end
|
|
319
|
+
unless body.nil?
|
|
320
|
+
body = encode_text(body, last_response['content-type'])
|
|
256
321
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
end
|
|
322
|
+
if decompress_content?
|
|
323
|
+
last_response.delete('content-encoding')
|
|
324
|
+
raw_body = body
|
|
325
|
+
end
|
|
326
|
+
end
|
|
263
327
|
|
|
264
|
-
|
|
265
|
-
if "".respond_to?(:encoding)
|
|
266
|
-
_encode_body(body)
|
|
267
|
-
else
|
|
268
|
-
body
|
|
328
|
+
Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
|
|
269
329
|
end
|
|
270
330
|
end
|
|
271
331
|
|
|
272
|
-
def
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
332
|
+
def handle_redirection(&block)
|
|
333
|
+
options[:limit] -= 1
|
|
334
|
+
if options[:logger]
|
|
335
|
+
logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
|
|
336
|
+
logger.format(self, last_response)
|
|
337
|
+
end
|
|
338
|
+
self.path = last_response['location']
|
|
339
|
+
self.redirect = true
|
|
340
|
+
if last_response.class == Net::HTTPSeeOther
|
|
341
|
+
unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
|
|
342
|
+
self.http_method = Net::HTTP::Get
|
|
278
343
|
end
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
|
|
283
|
-
self.http_method = Net::HTTP::Get
|
|
284
|
-
end
|
|
285
|
-
elsif last_response.code != '307' && last_response.code != '308'
|
|
286
|
-
unless options[:maintain_method_across_redirects]
|
|
287
|
-
self.http_method = Net::HTTP::Get
|
|
288
|
-
end
|
|
344
|
+
elsif last_response.code != '307' && last_response.code != '308'
|
|
345
|
+
unless options[:maintain_method_across_redirects]
|
|
346
|
+
self.http_method = Net::HTTP::Get
|
|
289
347
|
end
|
|
290
|
-
capture_cookies(last_response)
|
|
291
|
-
perform(&block)
|
|
292
|
-
else
|
|
293
|
-
body ||= last_response.body
|
|
294
|
-
body = encode_body(body)
|
|
295
|
-
Response.new(self, last_response, lambda { parse_response(body) }, body: body)
|
|
296
348
|
end
|
|
349
|
+
if http_method == Net::HTTP::Get
|
|
350
|
+
clear_body
|
|
351
|
+
end
|
|
352
|
+
capture_cookies(last_response)
|
|
353
|
+
perform(&block)
|
|
297
354
|
end
|
|
298
355
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
356
|
+
def handle_host_redirection
|
|
357
|
+
check_duplicate_location_header
|
|
358
|
+
redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
|
|
359
|
+
return if redirect_path.relative? || path.host == redirect_path.host || uri.host == redirect_path.host
|
|
360
|
+
@changed_hosts = true
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def check_duplicate_location_header
|
|
364
|
+
location = last_response.get_fields('location')
|
|
365
|
+
if location.is_a?(Array) && location.count > 1
|
|
366
|
+
raise DuplicateLocationHeader.new(last_response)
|
|
309
367
|
end
|
|
310
368
|
end
|
|
311
369
|
|
|
370
|
+
def send_authorization_header?
|
|
371
|
+
!@changed_hosts
|
|
372
|
+
end
|
|
373
|
+
|
|
312
374
|
def response_redirects?
|
|
313
375
|
case last_response
|
|
314
376
|
when Net::HTTPNotModified # 304
|
|
@@ -322,11 +384,20 @@ module HTTParty
|
|
|
322
384
|
parser.call(body, format)
|
|
323
385
|
end
|
|
324
386
|
|
|
387
|
+
# Some Web Application Firewalls reject incoming GET requests that have a body
|
|
388
|
+
# if we redirect, and the resulting verb is GET then we will clear the body that
|
|
389
|
+
# may be left behind from the initiating request
|
|
390
|
+
def clear_body
|
|
391
|
+
options[:body] = nil
|
|
392
|
+
@raw_request.body = nil
|
|
393
|
+
end
|
|
394
|
+
|
|
325
395
|
def capture_cookies(response)
|
|
326
396
|
return unless response['Set-Cookie']
|
|
327
397
|
cookies_hash = HTTParty::CookieHash.new
|
|
328
398
|
cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
|
|
329
399
|
response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
|
|
400
|
+
|
|
330
401
|
options[:headers] ||= {}
|
|
331
402
|
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
|
332
403
|
end
|
|
@@ -358,7 +429,38 @@ module HTTParty
|
|
|
358
429
|
if path.userinfo
|
|
359
430
|
username, password = path.userinfo.split(':')
|
|
360
431
|
options[:basic_auth] = {username: username, password: password}
|
|
432
|
+
@credentials_sent = true
|
|
361
433
|
end
|
|
362
434
|
end
|
|
435
|
+
|
|
436
|
+
def decompress(body, encoding)
|
|
437
|
+
Decompressor.new(body, encoding).decompress
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def encode_text(text, content_type)
|
|
441
|
+
TextEncoder.new(
|
|
442
|
+
text,
|
|
443
|
+
content_type: content_type,
|
|
444
|
+
assume_utf16_is_big_endian: assume_utf16_is_big_endian
|
|
445
|
+
).call
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def validate_uri_safety!(new_uri)
|
|
449
|
+
return if options[:skip_uri_validation]
|
|
450
|
+
|
|
451
|
+
configured_base_uri = options[:base_uri]
|
|
452
|
+
return unless configured_base_uri
|
|
453
|
+
|
|
454
|
+
normalized_base = options[:uri_adapter].parse(
|
|
455
|
+
HTTParty.normalize_base_uri(configured_base_uri)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
return if new_uri.host == normalized_base.host
|
|
459
|
+
|
|
460
|
+
raise UnsafeURIError,
|
|
461
|
+
"Requested URI '#{new_uri}' has host '#{new_uri.host}' but the " \
|
|
462
|
+
"configured base_uri '#{normalized_base}' has host '#{normalized_base.host}'. " \
|
|
463
|
+
"This request could send credentials to an unintended server."
|
|
464
|
+
end
|
|
363
465
|
end
|
|
364
466
|
end
|
|
@@ -1,31 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'delegate'
|
|
4
|
+
|
|
1
5
|
module HTTParty
|
|
2
6
|
class Response #:nodoc:
|
|
3
|
-
class Headers
|
|
7
|
+
class Headers < ::SimpleDelegator
|
|
4
8
|
include ::Net::HTTPHeader
|
|
5
9
|
|
|
6
|
-
def initialize(
|
|
7
|
-
@header =
|
|
10
|
+
def initialize(header_values = nil)
|
|
11
|
+
@header = {}
|
|
12
|
+
if header_values
|
|
13
|
+
header_values.each_pair do |k,v|
|
|
14
|
+
if v.is_a?(Array)
|
|
15
|
+
v.each do |sub_v|
|
|
16
|
+
add_field(k, sub_v)
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
add_field(k, v)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
super(@header)
|
|
8
24
|
end
|
|
9
25
|
|
|
10
26
|
def ==(other)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@header.inspect
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def method_missing(name, *args, &block)
|
|
19
|
-
if @header.respond_to?(name)
|
|
20
|
-
@header.send(name, *args, &block)
|
|
21
|
-
else
|
|
22
|
-
super
|
|
27
|
+
if other.is_a?(::Net::HTTPHeader)
|
|
28
|
+
@header == other.instance_variable_get(:@header)
|
|
29
|
+
elsif other.is_a?(Hash)
|
|
30
|
+
@header == other || @header == Headers.new(other).instance_variable_get(:@header)
|
|
23
31
|
end
|
|
24
32
|
end
|
|
25
|
-
|
|
26
|
-
def respond_to?(method, include_all = false)
|
|
27
|
-
super || @header.respond_to?(method, include_all)
|
|
28
|
-
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
data/lib/httparty/response.rb
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module HTTParty
|
|
2
|
-
class Response <
|
|
4
|
+
class Response < Object
|
|
3
5
|
def self.underscore(string)
|
|
4
6
|
string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
5
7
|
end
|
|
6
8
|
|
|
9
|
+
def self._load(data)
|
|
10
|
+
req, resp, parsed_resp, resp_body = Marshal.load(data)
|
|
11
|
+
|
|
12
|
+
new(req, resp, -> { parsed_resp }, body: resp_body)
|
|
13
|
+
end
|
|
14
|
+
|
|
7
15
|
attr_reader :request, :response, :body, :headers
|
|
8
16
|
|
|
9
17
|
def initialize(request, response, parsed_block, options = {})
|
|
@@ -14,50 +22,102 @@ module HTTParty
|
|
|
14
22
|
@headers = Headers.new(response.to_hash)
|
|
15
23
|
|
|
16
24
|
if request.options[:logger]
|
|
17
|
-
logger = ::HTTParty::Logger.build(
|
|
25
|
+
logger = ::HTTParty::Logger.build(
|
|
26
|
+
request.options[:logger],
|
|
27
|
+
request.options[:log_level],
|
|
28
|
+
request.options[:log_format]
|
|
29
|
+
)
|
|
18
30
|
logger.format(request, self)
|
|
19
31
|
end
|
|
32
|
+
|
|
33
|
+
throw_exception
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
def parsed_response
|
|
23
37
|
@parsed_response ||= @parsed_block.call
|
|
24
38
|
end
|
|
25
39
|
|
|
26
|
-
def class
|
|
27
|
-
Response
|
|
28
|
-
end
|
|
29
|
-
|
|
30
40
|
def code
|
|
31
41
|
response.code.to_i
|
|
32
42
|
end
|
|
33
43
|
|
|
44
|
+
def http_version
|
|
45
|
+
response.http_version
|
|
46
|
+
end
|
|
47
|
+
|
|
34
48
|
def tap
|
|
35
49
|
yield self
|
|
36
50
|
self
|
|
37
51
|
end
|
|
38
52
|
|
|
39
53
|
def inspect
|
|
40
|
-
inspect_id = ::Kernel::format
|
|
54
|
+
inspect_id = ::Kernel::format '%x', (object_id * 2)
|
|
41
55
|
%(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
|
|
42
56
|
end
|
|
43
57
|
|
|
44
58
|
CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
|
|
45
59
|
|
|
46
60
|
CODES_TO_OBJ.each do |response_code, klass|
|
|
47
|
-
name = klass.name.sub(
|
|
48
|
-
|
|
61
|
+
name = klass.name.sub('Net::HTTP', '')
|
|
62
|
+
name = "#{underscore(name)}?".to_sym
|
|
63
|
+
|
|
64
|
+
define_method(name) do
|
|
49
65
|
klass === response
|
|
50
66
|
end
|
|
51
67
|
end
|
|
52
68
|
|
|
53
69
|
# Support old multiple_choice? method from pre 2.0.0 era.
|
|
54
|
-
if ::
|
|
70
|
+
if ::RUBY_PLATFORM != 'java'
|
|
55
71
|
alias_method :multiple_choice?, :multiple_choices?
|
|
56
72
|
end
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
# Support old status codes method from pre 2.6.0 era.
|
|
75
|
+
if ::RUBY_PLATFORM != 'java'
|
|
76
|
+
alias_method :gateway_time_out?, :gateway_timeout?
|
|
77
|
+
alias_method :request_entity_too_large?, :payload_too_large?
|
|
78
|
+
alias_method :request_time_out?, :request_timeout?
|
|
79
|
+
alias_method :request_uri_too_long?, :uri_too_long?
|
|
80
|
+
alias_method :requested_range_not_satisfiable?, :range_not_satisfiable?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def nil?
|
|
84
|
+
warn_about_nil_deprecation
|
|
85
|
+
response.nil? || response.body.nil? || response.body.empty?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_s
|
|
89
|
+
if !response.nil? && !response.body.nil? && response.body.respond_to?(:to_s)
|
|
90
|
+
response.body.to_s
|
|
91
|
+
else
|
|
92
|
+
inspect
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def pretty_print(pp)
|
|
97
|
+
if !parsed_response.nil? && parsed_response.respond_to?(:pretty_print)
|
|
98
|
+
parsed_response.pretty_print(pp)
|
|
99
|
+
else
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def display(port=$>)
|
|
105
|
+
if !parsed_response.nil? && parsed_response.respond_to?(:display)
|
|
106
|
+
parsed_response.display(port)
|
|
107
|
+
elsif !response.nil? && !response.body.nil? && response.body.respond_to?(:display)
|
|
108
|
+
response.body.display(port)
|
|
109
|
+
else
|
|
110
|
+
port.write(inspect)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def respond_to_missing?(name, *args)
|
|
115
|
+
return true if super
|
|
116
|
+
parsed_response.respond_to?(name) || response.respond_to?(name)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def _dump(_level)
|
|
120
|
+
Marshal.dump([request, response, parsed_response, body])
|
|
61
121
|
end
|
|
62
122
|
|
|
63
123
|
protected
|
|
@@ -71,6 +131,25 @@ module HTTParty
|
|
|
71
131
|
super
|
|
72
132
|
end
|
|
73
133
|
end
|
|
134
|
+
|
|
135
|
+
def throw_exception
|
|
136
|
+
if @request.options[:raise_on].to_a.detect { |c| code.to_s.match(/#{c.to_s}/) }
|
|
137
|
+
::Kernel.raise ::HTTParty::ResponseError.new(@response), "Code #{code} - #{body}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def warn_about_nil_deprecation
|
|
144
|
+
trace_line = caller.reject { |line| line.include?('httparty') }.first
|
|
145
|
+
warning = "[DEPRECATION] HTTParty will no longer override `response#nil?`. " \
|
|
146
|
+
"This functionality will be removed in future versions. " \
|
|
147
|
+
"Please, add explicit check `response.body.nil? || response.body.empty?`. " \
|
|
148
|
+
"For more info refer to: https://github.com/jnunemaker/httparty/issues/568\n" \
|
|
149
|
+
"#{trace_line}"
|
|
150
|
+
|
|
151
|
+
warn(warning)
|
|
152
|
+
end
|
|
74
153
|
end
|
|
75
154
|
end
|
|
76
155
|
|