httparty 0.10.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httparty might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +92 -0
  4. data/.rubocop_todo.yml +124 -0
  5. data/.simplecov +1 -0
  6. data/.travis.yml +5 -4
  7. data/CONTRIBUTING.md +23 -0
  8. data/Gemfile +9 -5
  9. data/Guardfile +3 -3
  10. data/History +109 -8
  11. data/README.md +21 -21
  12. data/Rakefile +5 -10
  13. data/bin/httparty +21 -14
  14. data/docs/README.md +100 -0
  15. data/examples/README.md +67 -0
  16. data/examples/aaws.rb +9 -9
  17. data/examples/basic.rb +6 -10
  18. data/examples/crack.rb +3 -3
  19. data/examples/custom_parsers.rb +1 -4
  20. data/examples/delicious.rb +12 -12
  21. data/examples/google.rb +2 -2
  22. data/examples/headers_and_user_agents.rb +2 -2
  23. data/examples/logging.rb +36 -0
  24. data/examples/nokogiri_html_parser.rb +0 -3
  25. data/examples/rescue_json.rb +17 -0
  26. data/examples/rubyurl.rb +3 -3
  27. data/examples/stackexchange.rb +24 -0
  28. data/examples/tripit_sign_in.rb +20 -9
  29. data/examples/twitter.rb +11 -11
  30. data/examples/whoismyrep.rb +2 -2
  31. data/features/command_line.feature +90 -2
  32. data/features/digest_authentication.feature +10 -0
  33. data/features/handles_compressed_responses.feature +8 -0
  34. data/features/handles_multiple_formats.feature +23 -0
  35. data/features/steps/env.rb +16 -11
  36. data/features/steps/httparty_response_steps.rb +40 -10
  37. data/features/steps/httparty_steps.rb +19 -3
  38. data/features/steps/mongrel_helper.rb +35 -2
  39. data/features/steps/remote_service_steps.rb +31 -8
  40. data/features/supports_read_timeout_option.feature +13 -0
  41. data/httparty.gemspec +9 -6
  42. data/lib/httparty/connection_adapter.rb +76 -11
  43. data/lib/httparty/cookie_hash.rb +3 -4
  44. data/lib/httparty/exceptions.rb +10 -4
  45. data/lib/httparty/hash_conversions.rb +19 -17
  46. data/lib/httparty/logger/apache_formatter.rb +22 -0
  47. data/lib/httparty/logger/curl_formatter.rb +91 -0
  48. data/lib/httparty/logger/logger.rb +26 -0
  49. data/lib/httparty/module_inheritable_attributes.rb +1 -1
  50. data/lib/httparty/net_digest_auth.rb +69 -18
  51. data/lib/httparty/parser.rb +15 -11
  52. data/lib/httparty/request.rb +186 -47
  53. data/lib/httparty/response/headers.rb +2 -2
  54. data/lib/httparty/response.rb +44 -9
  55. data/lib/httparty/version.rb +1 -1
  56. data/lib/httparty.rb +187 -65
  57. data/script/release +42 -0
  58. data/spec/fixtures/twitter.csv +2 -0
  59. data/spec/httparty/connection_adapter_spec.rb +334 -62
  60. data/spec/httparty/cookie_hash_spec.rb +53 -23
  61. data/spec/httparty/exception_spec.rb +45 -0
  62. data/spec/httparty/hash_conversions_spec.rb +49 -0
  63. data/spec/httparty/logger/apache_formatter_spec.rb +41 -0
  64. data/spec/httparty/logger/curl_formatter_spec.rb +119 -0
  65. data/spec/httparty/logger/logger_spec.rb +38 -0
  66. data/spec/httparty/net_digest_auth_spec.rb +148 -23
  67. data/spec/httparty/parser_spec.rb +48 -41
  68. data/spec/httparty/request_spec.rb +845 -151
  69. data/spec/httparty/response_spec.rb +147 -70
  70. data/spec/httparty/ssl_spec.rb +33 -21
  71. data/spec/httparty_spec.rb +337 -186
  72. data/spec/spec_helper.rb +38 -9
  73. data/spec/support/ssl_test_helper.rb +10 -10
  74. data/spec/support/ssl_test_server.rb +21 -21
  75. data/spec/support/stub_response.rb +20 -14
  76. data/website/index.html +3 -3
  77. metadata +46 -37
  78. data/lib/httparty/core_extensions.rb +0 -32
  79. data/spec/spec.opts +0 -2
@@ -7,40 +7,55 @@ module HTTParty
7
7
  Net::HTTP::Put,
8
8
  Net::HTTP::Delete,
9
9
  Net::HTTP::Head,
10
- Net::HTTP::Options
10
+ Net::HTTP::Options,
11
+ Net::HTTP::Move,
12
+ Net::HTTP::Copy,
13
+ Net::HTTP::Mkcol,
11
14
  ]
12
15
 
13
- SupportedURISchemes = [URI::HTTP, URI::HTTPS, URI::Generic]
16
+ SupportedURISchemes = ['http', 'https', 'webcal', nil]
14
17
 
15
- NON_RAILS_QUERY_STRING_NORMALIZER = Proc.new do |query|
16
- Array(query).map do |key, value|
18
+ NON_RAILS_QUERY_STRING_NORMALIZER = proc do |query|
19
+ Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
17
20
  if value.nil?
18
21
  key.to_s
19
- elsif value.is_a?(Array)
20
- value.map {|v| "#{key}=#{URI.encode(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}"}
22
+ elsif value.respond_to?(:to_ary)
23
+ value.to_ary.map {|v| "#{key}=#{ERB::Util.url_encode(v.to_s)}"}
21
24
  else
22
25
  HashConversions.to_params(key => value)
23
26
  end
24
- end.flatten.sort.join('&')
27
+ end.flatten.join('&')
25
28
  end
26
29
 
27
30
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
28
31
  attr_reader :path
29
32
 
30
- def initialize(http_method, path, o={})
33
+ def initialize(http_method, path, o = {})
31
34
  self.http_method = http_method
32
- self.path = path
33
35
  self.options = {
34
- :limit => o.delete(:no_follow) ? 1 : 5,
35
- :default_params => {},
36
- :follow_redirects => true,
37
- :parser => Parser,
38
- :connection_adapter => ConnectionAdapter
36
+ limit: o.delete(:no_follow) ? 1 : 5,
37
+ assume_utf16_is_big_endian: true,
38
+ default_params: {},
39
+ follow_redirects: true,
40
+ parser: Parser,
41
+ uri_adapter: URI,
42
+ connection_adapter: ConnectionAdapter
39
43
  }.merge(o)
44
+ self.path = path
45
+ set_basic_auth_from_uri
40
46
  end
41
47
 
42
48
  def path=(uri)
43
- @path = URI.parse(uri)
49
+ uri_adapter = options[:uri_adapter]
50
+
51
+ @path = if uri.is_a?(uri_adapter)
52
+ uri
53
+ elsif String.try_convert(uri)
54
+ uri_adapter.parse uri
55
+ else
56
+ raise ArgumentError,
57
+ "bad argument (expected #{uri_adapter} object or URI string)"
58
+ end
44
59
  end
45
60
 
46
61
  def request_uri(uri)
@@ -52,14 +67,21 @@ module HTTParty
52
67
  end
53
68
 
54
69
  def uri
55
- new_uri = path.relative? ? URI.parse("#{base_uri}#{path}") : path
70
+ if redirect && path.relative? && path.path[0] != "/"
71
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
72
+
73
+ path.path = "/#{path.path}" if last_uri_host[-1] != "/"
74
+ path.path = last_uri_host + path.path
75
+ end
76
+
77
+ new_uri = path.relative? ? options[:uri_adapter].parse("#{base_uri}#{path}") : path.clone
56
78
 
57
79
  # avoid double query string on redirects [#12]
58
80
  unless redirect
59
81
  new_uri.query = query_string(new_uri)
60
82
  end
61
83
 
62
- unless SupportedURISchemes.include? new_uri.class
84
+ unless SupportedURISchemes.include? new_uri.scheme
63
85
  raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP, HTTPS or Generic"
64
86
  end
65
87
 
@@ -67,7 +89,13 @@ module HTTParty
67
89
  end
68
90
 
69
91
  def base_uri
70
- redirect ? "#{@last_uri.scheme}://#{@last_uri.host}" : options[:base_uri]
92
+ if redirect
93
+ base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
94
+ base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
95
+ base_uri
96
+ else
97
+ options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
98
+ end
71
99
  end
72
100
 
73
101
  def format
@@ -92,7 +120,7 @@ module HTTParty
92
120
  chunks = []
93
121
 
94
122
  http_response.read_body do |fragment|
95
- chunks << fragment
123
+ chunks << fragment unless options[:stream_body]
96
124
  block.call(fragment)
97
125
  end
98
126
 
@@ -100,8 +128,13 @@ module HTTParty
100
128
  end
101
129
  end
102
130
 
103
- handle_deflation
104
- handle_response(chunked_body)
131
+ handle_deflation unless http_method == Net::HTTP::Head
132
+ handle_host_redirection if response_redirects?
133
+ handle_response(chunked_body, &block)
134
+ end
135
+
136
+ def raw_body
137
+ @raw_request.body
105
138
  end
106
139
 
107
140
  private
@@ -111,11 +144,11 @@ module HTTParty
111
144
  end
112
145
 
113
146
  def body
114
- options[:body].is_a?(Hash) ? normalize_query(options[:body]) : options[:body]
147
+ options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
115
148
  end
116
149
 
117
150
  def credentials
118
- options[:basic_auth] || options[:digest_auth]
151
+ (options[:basic_auth] || options[:digest_auth]).to_hash
119
152
  end
120
153
 
121
154
  def username
@@ -141,17 +174,18 @@ module HTTParty
141
174
  def setup_raw_request
142
175
  @raw_request = http_method.new(request_uri(uri))
143
176
  @raw_request.body = body if body
144
- @raw_request.initialize_http_header(options[:headers])
145
- @raw_request.basic_auth(username, password) if options[:basic_auth]
177
+ @raw_request.body_stream = options[:body_stream] if options[:body_stream]
178
+ @raw_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
179
+ @raw_request.basic_auth(username, password) if options[:basic_auth] && send_authorization_header?
146
180
  setup_digest_auth if options[:digest_auth]
147
181
  end
148
182
 
149
183
  def setup_digest_auth
150
184
  auth_request = http_method.new(uri.request_uri)
151
- auth_request.initialize_http_header(options[:headers])
185
+ auth_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
152
186
  res = http.request(auth_request)
153
187
 
154
- if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
188
+ if !res['www-authenticate'].nil? && res['www-authenticate'].length > 0
155
189
  @raw_request.digest_auth(username, password, res)
156
190
  end
157
191
  end
@@ -160,32 +194,115 @@ module HTTParty
160
194
  query_string_parts = []
161
195
  query_string_parts << uri.query unless uri.query.nil?
162
196
 
163
- if options[:query].is_a?(Hash)
164
- query_string_parts << normalize_query(options[:default_params].merge(options[:query]))
197
+ if options[:query].respond_to?(:to_hash)
198
+ query_string_parts << normalize_query(options[:default_params].merge(options[:query].to_hash))
165
199
  else
166
200
  query_string_parts << normalize_query(options[:default_params]) unless options[:default_params].empty?
167
201
  query_string_parts << options[:query] unless options[:query].nil?
168
202
  end
169
203
 
204
+ query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
170
205
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
171
206
  end
172
207
 
173
- def handle_response(body)
208
+ def get_charset
209
+ content_type = last_response["content-type"]
210
+ if content_type.nil?
211
+ return nil
212
+ end
213
+
214
+ if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
215
+ return $1
216
+ end
217
+
218
+ if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
219
+ return $1.gsub(/\\(.)/, '\1')
220
+ end
221
+
222
+ nil
223
+ end
224
+
225
+ def encode_with_ruby_encoding(body, charset)
226
+ encoding = Encoding.find(charset)
227
+ body.force_encoding(encoding)
228
+ rescue
229
+ body
230
+ end
231
+
232
+ def assume_utf16_is_big_endian
233
+ options[:assume_utf16_is_big_endian]
234
+ end
235
+
236
+ def encode_utf_16(body)
237
+ if body.bytesize >= 2
238
+ if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
239
+ return body.force_encoding("UTF-16LE")
240
+ elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
241
+ return body.force_encoding("UTF-16BE")
242
+ end
243
+ end
244
+
245
+ if assume_utf16_is_big_endian
246
+ body.force_encoding("UTF-16BE")
247
+ else
248
+ body.force_encoding("UTF-16LE")
249
+ end
250
+ end
251
+
252
+ def _encode_body(body)
253
+ charset = get_charset
254
+
255
+ if charset.nil?
256
+ return body
257
+ end
258
+
259
+ if "utf-16".casecmp(charset) == 0
260
+ encode_utf_16(body)
261
+ else
262
+ encode_with_ruby_encoding(body, charset)
263
+ end
264
+ end
265
+
266
+ def encode_body(body)
267
+ if "".respond_to?(:encoding)
268
+ _encode_body(body)
269
+ else
270
+ body
271
+ end
272
+ end
273
+
274
+ def handle_response(body, &block)
174
275
  if response_redirects?
175
276
  options[:limit] -= 1
277
+ if options[:logger]
278
+ logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
279
+ logger.format(self, last_response)
280
+ end
176
281
  self.path = last_response['location']
177
282
  self.redirect = true
178
- self.http_method = Net::HTTP::Get unless options[:maintain_method_across_redirects]
283
+ if last_response.class == Net::HTTPSeeOther
284
+ unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
285
+ self.http_method = Net::HTTP::Get
286
+ end
287
+ elsif last_response.code != '307' && last_response.code != '308'
288
+ unless options[:maintain_method_across_redirects]
289
+ self.http_method = Net::HTTP::Get
290
+ end
291
+ end
179
292
  capture_cookies(last_response)
180
- perform
293
+ perform(&block)
181
294
  else
182
- body = body || last_response.body
183
- Response.new(self, last_response, lambda { parse_response(body) }, :body => body)
295
+ body ||= last_response.body
296
+ body = encode_body(body)
297
+ Response.new(self, last_response, lambda { parse_response(body) }, body: body)
184
298
  end
185
299
  end
186
300
 
187
301
  # Inspired by Ruby 1.9
188
302
  def handle_deflation
303
+ return if response_redirects?
304
+ return if last_response.body.nil?
305
+
189
306
  case last_response["content-encoding"]
190
307
  when "gzip", "x-gzip"
191
308
  body_io = StringIO.new(last_response.body)
@@ -197,14 +314,29 @@ module HTTParty
197
314
  end
198
315
  end
199
316
 
317
+ def handle_host_redirection
318
+ check_duplicate_location_header
319
+ redirect_path = options[:uri_adapter].parse last_response['location']
320
+ return if redirect_path.relative? || path.host == redirect_path.host
321
+ @changed_hosts = true
322
+ end
323
+
324
+ def check_duplicate_location_header
325
+ location = last_response.get_fields('location')
326
+ if location.is_a?(Array) && location.count > 1
327
+ raise DuplicateLocationHeader.new(last_response)
328
+ end
329
+ end
330
+
331
+ def send_authorization_header?
332
+ !defined?(@changed_hosts)
333
+ end
334
+
200
335
  def response_redirects?
201
336
  case last_response
202
- when Net::HTTPMultipleChoice, # 300
203
- Net::HTTPMovedPermanently, # 301
204
- Net::HTTPFound, # 302
205
- Net::HTTPSeeOther, # 303
206
- Net::HTTPUseProxy, # 305
207
- Net::HTTPTemporaryRedirect
337
+ when Net::HTTPNotModified # 304
338
+ false
339
+ when Net::HTTPRedirection
208
340
  options[:follow_redirects] && last_response.key?('location')
209
341
  end
210
342
  end
@@ -215,9 +347,9 @@ module HTTParty
215
347
 
216
348
  def capture_cookies(response)
217
349
  return unless response['Set-Cookie']
218
- cookies_hash = HTTParty::CookieHash.new()
219
- cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
220
- cookies_hash.add_cookies(response['Set-Cookie'])
350
+ cookies_hash = HTTParty::CookieHash.new
351
+ cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
352
+ response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
221
353
  options[:headers] ||= {}
222
354
  options[:headers]['Cookie'] = cookies_hash.to_cookie_string
223
355
  end
@@ -234,15 +366,22 @@ module HTTParty
234
366
  def validate
235
367
  raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
236
368
  raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
237
- raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
369
+ raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
238
370
  raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
239
- raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
240
- raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].is_a?(Hash)
241
- raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
371
+ raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
372
+ raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
373
+ raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
242
374
  end
243
375
 
244
376
  def post?
245
377
  Net::HTTP::Post == http_method
246
378
  end
379
+
380
+ def set_basic_auth_from_uri
381
+ if path.userinfo
382
+ username, password = path.userinfo.split(':')
383
+ options[:basic_auth] = {username: username, password: password}
384
+ end
385
+ end
247
386
  end
248
387
  end
@@ -23,8 +23,8 @@ module HTTParty
23
23
  end
24
24
  end
25
25
 
26
- def respond_to?(method)
27
- super || @header.respond_to?(method)
26
+ def respond_to?(method, include_all = false)
27
+ super || @header.respond_to?(method, include_all)
28
28
  end
29
29
  end
30
30
  end
@@ -1,17 +1,24 @@
1
1
  module HTTParty
2
- class Response < HTTParty::BasicObject #:nodoc:
2
+ class Response < BasicObject
3
3
  def self.underscore(string)
4
- string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
4
+ string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
5
5
  end
6
6
 
7
7
  attr_reader :request, :response, :body, :headers
8
8
 
9
- def initialize(request, response, parsed_block, options={})
9
+ def initialize(request, response, parsed_block, options = {})
10
10
  @request = request
11
11
  @response = response
12
- @body = response.body || options[:body]
12
+ @body = options[:body] || response.body
13
13
  @parsed_block = parsed_block
14
14
  @headers = Headers.new(response.to_hash)
15
+
16
+ if request.options[:logger]
17
+ logger = ::HTTParty::Logger.build(request.options[:logger], request.options[:log_level], request.options[:log_format])
18
+ logger.format(request, self)
19
+ end
20
+
21
+ throw_exception
15
22
  end
16
23
 
17
24
  def parsed_response
@@ -22,27 +29,49 @@ module HTTParty
22
29
  Response
23
30
  end
24
31
 
32
+ def is_a?(klass)
33
+ self.class == klass || self.class < klass
34
+ end
35
+
36
+ alias_method :kind_of?, :is_a?
37
+
25
38
  def code
26
39
  response.code.to_i
27
40
  end
28
41
 
42
+ def tap
43
+ yield self
44
+ self
45
+ end
46
+
29
47
  def inspect
30
- inspect_id = "%x" % (object_id * 2)
48
+ inspect_id = ::Kernel::format "%x", (object_id * 2)
31
49
  %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
32
50
  end
33
51
 
52
+ RESPOND_TO_METHODS = [:request, :response, :parsed_response, :body, :headers]
53
+
34
54
  CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
35
55
 
36
56
  CODES_TO_OBJ.each do |response_code, klass|
37
57
  name = klass.name.sub("Net::HTTP", '')
38
- define_method("#{underscore(name)}?") do
58
+ name = "#{underscore(name)}?".to_sym
59
+
60
+ RESPOND_TO_METHODS << name
61
+
62
+ define_method(name) do
39
63
  klass === response
40
64
  end
41
65
  end
42
66
 
43
- def respond_to?(name)
44
- return true if [:request, :response, :parsed_response, :body, :headers].include?(name)
45
- parsed_response.respond_to?(name) || response.respond_to?(name)
67
+ # Support old multiple_choice? method from pre 2.0.0 era.
68
+ if ::RUBY_VERSION >= "2.0.0" && ::RUBY_PLATFORM != "java"
69
+ alias_method :multiple_choice?, :multiple_choices?
70
+ end
71
+
72
+ def respond_to?(name, include_all = false)
73
+ return true if RESPOND_TO_METHODS.include?(name)
74
+ parsed_response.respond_to?(name, include_all) || response.respond_to?(name, include_all)
46
75
  end
47
76
 
48
77
  protected
@@ -56,6 +85,12 @@ module HTTParty
56
85
  super
57
86
  end
58
87
  end
88
+
89
+ def throw_exception
90
+ if @request.options[:raise_on] && @request.options[:raise_on].include?(code)
91
+ ::Kernel.raise ::HTTParty::ResponseError.new(@response), "Code #{code} - #{body}"
92
+ end
93
+ end
59
94
  end
60
95
  end
61
96
 
@@ -1,3 +1,3 @@
1
1
  module HTTParty
2
- VERSION = "0.10.0"
2
+ VERSION = "0.14.0"
3
3
  end