httparty 0.16.2 → 0.20.0

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.

Potentially problematic release.


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

Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/workflows/ci.yml +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop_todo.yml +1 -1
  6. data/Changelog.md +72 -0
  7. data/Gemfile +5 -0
  8. data/README.md +5 -5
  9. data/docs/README.md +70 -5
  10. data/examples/README.md +28 -11
  11. data/examples/aaws.rb +6 -2
  12. data/examples/body_stream.rb +14 -0
  13. data/examples/idn.rb +10 -0
  14. data/examples/microsoft_graph.rb +52 -0
  15. data/examples/multipart.rb +22 -0
  16. data/examples/peer_cert.rb +9 -0
  17. data/examples/stream_download.rb +8 -2
  18. data/httparty.gemspec +3 -3
  19. data/lib/httparty/connection_adapter.rb +59 -16
  20. data/lib/httparty/cookie_hash.rb +10 -8
  21. data/lib/httparty/decompressor.rb +92 -0
  22. data/lib/httparty/exceptions.rb +3 -1
  23. data/lib/httparty/hash_conversions.rb +10 -4
  24. data/lib/httparty/headers_processor.rb +32 -0
  25. data/lib/httparty/logger/apache_formatter.rb +31 -6
  26. data/lib/httparty/logger/curl_formatter.rb +9 -7
  27. data/lib/httparty/logger/logger.rb +5 -1
  28. data/lib/httparty/logger/logstash_formatter.rb +61 -0
  29. data/lib/httparty/module_inheritable_attributes.rb +6 -4
  30. data/lib/httparty/net_digest_auth.rb +15 -15
  31. data/lib/httparty/parser.rb +9 -5
  32. data/lib/httparty/request/body.rb +46 -27
  33. data/lib/httparty/request/multipart_boundary.rb +2 -0
  34. data/lib/httparty/request.rb +75 -96
  35. data/lib/httparty/response/headers.rb +4 -2
  36. data/lib/httparty/response.rb +51 -8
  37. data/lib/httparty/response_fragment.rb +21 -0
  38. data/lib/httparty/text_encoder.rb +72 -0
  39. data/lib/httparty/utils.rb +13 -0
  40. data/lib/httparty/version.rb +3 -1
  41. data/lib/httparty.rb +70 -24
  42. data/website/css/common.css +1 -1
  43. metadata +33 -104
  44. data/.travis.yml +0 -10
  45. data/features/basic_authentication.feature +0 -20
  46. data/features/command_line.feature +0 -95
  47. data/features/deals_with_http_error_codes.feature +0 -26
  48. data/features/digest_authentication.feature +0 -30
  49. data/features/handles_compressed_responses.feature +0 -27
  50. data/features/handles_multiple_formats.feature +0 -57
  51. data/features/steps/env.rb +0 -27
  52. data/features/steps/httparty_response_steps.rb +0 -56
  53. data/features/steps/httparty_steps.rb +0 -43
  54. data/features/steps/mongrel_helper.rb +0 -127
  55. data/features/steps/remote_service_steps.rb +0 -92
  56. data/features/supports_read_timeout_option.feature +0 -13
  57. data/features/supports_redirection.feature +0 -22
  58. data/features/supports_timeout_option.feature +0 -13
  59. data/spec/fixtures/delicious.xml +0 -23
  60. data/spec/fixtures/empty.xml +0 -0
  61. data/spec/fixtures/google.html +0 -3
  62. data/spec/fixtures/ssl/generate.sh +0 -29
  63. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  64. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  65. data/spec/fixtures/ssl/generated/ca.key +0 -15
  66. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  67. data/spec/fixtures/ssl/generated/server.crt +0 -13
  68. data/spec/fixtures/ssl/generated/server.key +0 -15
  69. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  70. data/spec/fixtures/tiny.gif +0 -0
  71. data/spec/fixtures/twitter.csv +0 -2
  72. data/spec/fixtures/twitter.json +0 -1
  73. data/spec/fixtures/twitter.xml +0 -403
  74. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  75. data/spec/httparty/connection_adapter_spec.rb +0 -498
  76. data/spec/httparty/cookie_hash_spec.rb +0 -100
  77. data/spec/httparty/exception_spec.rb +0 -45
  78. data/spec/httparty/hash_conversions_spec.rb +0 -56
  79. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  80. data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
  81. data/spec/httparty/logger/logger_spec.rb +0 -38
  82. data/spec/httparty/net_digest_auth_spec.rb +0 -270
  83. data/spec/httparty/parser_spec.rb +0 -190
  84. data/spec/httparty/request/body_spec.rb +0 -60
  85. data/spec/httparty/request_spec.rb +0 -1312
  86. data/spec/httparty/response_spec.rb +0 -347
  87. data/spec/httparty/ssl_spec.rb +0 -74
  88. data/spec/httparty_spec.rb +0 -896
  89. data/spec/spec_helper.rb +0 -51
  90. data/spec/support/ssl_test_helper.rb +0 -47
  91. data/spec/support/ssl_test_server.rb +0 -80
  92. data/spec/support/stub_response.rb +0 -49
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  # The default parser used by HTTParty, supports xml, json, html, csv and
3
5
  # plain text.
@@ -101,7 +103,7 @@ module HTTParty
101
103
  # @return [nil] when the response body is nil, an empty string, spaces only or "null"
102
104
  def parse
103
105
  return nil if body.nil?
104
- return nil if body == "null"
106
+ return nil if body == 'null'
105
107
  return nil if body.valid_encoding? && body.strip.empty?
106
108
  if body.valid_encoding? && body.encoding == Encoding::UTF_8
107
109
  @body = body.gsub(/\A#{UTF8_BOM}/, '')
@@ -119,7 +121,7 @@ module HTTParty
119
121
  MultiXml.parse(body)
120
122
  end
121
123
 
122
- UTF8_BOM = "\xEF\xBB\xBF".freeze
124
+ UTF8_BOM = "\xEF\xBB\xBF"
123
125
 
124
126
  def json
125
127
  JSON.parse(body, :quirks_mode => true, :allow_nan => true)
@@ -142,9 +144,11 @@ module HTTParty
142
144
  end
143
145
 
144
146
  def parse_supported_format
145
- send(format)
146
- rescue NoMethodError => e
147
- raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
147
+ if respond_to?(format, true)
148
+ send(format)
149
+ else
150
+ raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
151
+ end
148
152
  end
149
153
  end
150
154
  end
@@ -1,11 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'multipart_boundary'
2
4
 
3
5
  module HTTParty
4
6
  class Request
5
7
  class Body
6
- def initialize(params, query_string_normalizer: nil)
8
+ NEWLINE = "\r\n"
9
+ private_constant :NEWLINE
10
+
11
+ def initialize(params, query_string_normalizer: nil, force_multipart: false)
7
12
  @params = params
8
13
  @query_string_normalizer = query_string_normalizer
14
+ @force_multipart = force_multipart
9
15
  end
10
16
 
11
17
  def call
@@ -21,7 +27,7 @@ module HTTParty
21
27
  end
22
28
 
23
29
  def multipart?
24
- params.respond_to?(:to_hash) && has_file?(params.to_hash)
30
+ params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
25
31
  end
26
32
 
27
33
  private
@@ -29,40 +35,34 @@ module HTTParty
29
35
  def generate_multipart
30
36
  normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
31
37
 
32
- multipart = normalized_params.inject('') do |memo, (key, value)|
33
- memo += "--#{boundary}\r\n"
34
- memo += %(Content-Disposition: form-data; name="#{key}")
38
+ multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
39
+ memo << "--#{boundary}#{NEWLINE}"
40
+ memo << %(Content-Disposition: form-data; name="#{key}")
35
41
  # value.path is used to support ActionDispatch::Http::UploadedFile
36
42
  # https://github.com/jnunemaker/httparty/pull/585
37
- memo += %(; filename="#{File.basename(value.path)}") if file?(value)
38
- memo += "\r\n"
39
- memo += "Content-Type: application/octet-stream\r\n" if file?(value)
40
- memo += "\r\n"
41
- memo += file?(value) ? value.read : value.to_s
42
- memo += "\r\n"
43
+ memo << %(; filename="#{file_name(value)}") if file?(value)
44
+ memo << NEWLINE
45
+ memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
46
+ memo << NEWLINE
47
+ memo << content_body(value)
48
+ memo << NEWLINE
43
49
  end
44
50
 
45
- multipart += "--#{boundary}--\r\n"
51
+ multipart << "--#{boundary}--#{NEWLINE}"
46
52
  end
47
53
 
48
- def has_file?(hash)
49
- hash.detect do |key, value|
50
- if value.respond_to?(:to_hash) || includes_hash?(value)
51
- has_file?(value)
52
- elsif value.respond_to?(:to_ary)
53
- value.any? { |e| file?(e) }
54
- else
55
- file?(value)
56
- end
54
+ def has_file?(value)
55
+ if value.respond_to?(:to_hash)
56
+ value.to_hash.any? { |_, v| has_file?(v) }
57
+ elsif value.respond_to?(:to_ary)
58
+ value.to_ary.any? { |v| has_file?(v) }
59
+ else
60
+ file?(value)
57
61
  end
58
62
  end
59
63
 
60
64
  def file?(object)
61
- object.respond_to?(:path) && object.respond_to?(:read) # add memoization
62
- end
63
-
64
- def includes_hash?(object)
65
- object.respond_to?(:to_ary) && object.any? { |e| e.respond_to?(:hash) }
65
+ object.respond_to?(:path) && object.respond_to?(:read)
66
66
  end
67
67
 
68
68
  def normalize_query(query)
@@ -73,7 +73,26 @@ module HTTParty
73
73
  end
74
74
  end
75
75
 
76
- attr_reader :params, :query_string_normalizer
76
+ def content_body(object)
77
+ if file?(object)
78
+ object = (file = object).read
79
+ file.rewind if file.respond_to?(:rewind)
80
+ end
81
+
82
+ object.to_s
83
+ end
84
+
85
+ def content_type(object)
86
+ return object.content_type if object.respond_to?(:content_type)
87
+ mime = MIME::Types.type_for(object.path)
88
+ mime.empty? ? 'application/octet-stream' : mime[0].content_type
89
+ end
90
+
91
+ def file_name(object)
92
+ object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
93
+ end
94
+
95
+ attr_reader :params, :query_string_normalizer, :force_multipart
77
96
  end
78
97
  end
79
98
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  module HTTParty
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
- require 'httparty/request/body'
3
4
 
4
5
  module HTTParty
5
6
  class Request #:nodoc:
@@ -14,6 +15,8 @@ module HTTParty
14
15
  Net::HTTP::Move,
15
16
  Net::HTTP::Copy,
16
17
  Net::HTTP::Mkcol,
18
+ Net::HTTP::Lock,
19
+ Net::HTTP::Unlock,
17
20
  ]
18
21
 
19
22
  SupportedURISchemes = ['http', 'https', 'webcal', nil]
@@ -43,6 +46,11 @@ module HTTParty
43
46
  end.flatten.join('&')
44
47
  end
45
48
 
49
+ def self._load(data)
50
+ http_method, path, options = Marshal.load(data)
51
+ new(http_method, path, options)
52
+ end
53
+
46
54
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
47
55
  attr_reader :path
48
56
 
@@ -70,7 +78,7 @@ module HTTParty
70
78
  @path = if uri.is_a?(uri_adapter)
71
79
  uri
72
80
  elsif String.try_convert(uri)
73
- uri_adapter.parse uri
81
+ uri_adapter.parse(uri).normalize
74
82
  else
75
83
  raise ArgumentError,
76
84
  "bad argument (expected #{uri_adapter} object or URI string)"
@@ -86,17 +94,17 @@ module HTTParty
86
94
  end
87
95
 
88
96
  def uri
89
- if redirect && path.relative? && path.path[0] != "/"
90
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
97
+ if redirect && path.relative? && path.path[0] != '/'
98
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
91
99
 
92
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
93
- path.path = last_uri_host + path.path
100
+ path.path = "/#{path.path}" if last_uri_host[-1] != '/'
101
+ path.path = "#{last_uri_host}#{path.path}"
94
102
  end
95
103
 
96
104
  if path.relative? && path.host
97
- new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
105
+ new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
98
106
  elsif path.relative?
99
- new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
107
+ new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
100
108
  else
101
109
  new_uri = path.clone
102
110
  end
@@ -116,7 +124,7 @@ module HTTParty
116
124
  def base_uri
117
125
  if redirect
118
126
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
119
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
127
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
120
128
  base_uri
121
129
  else
122
130
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -139,21 +147,22 @@ module HTTParty
139
147
  validate
140
148
  setup_raw_request
141
149
  chunked_body = nil
150
+ current_http = http
142
151
 
143
- self.last_response = http.request(@raw_request) do |http_response|
152
+ self.last_response = current_http.request(@raw_request) do |http_response|
144
153
  if block
145
154
  chunks = []
146
155
 
147
156
  http_response.read_body do |fragment|
148
- chunks << fragment unless options[:stream_body]
149
- block.call(fragment)
157
+ encoded_fragment = encode_text(fragment, http_response['content-type'])
158
+ chunks << encoded_fragment if !options[:stream_body]
159
+ block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
150
160
  end
151
161
 
152
162
  chunked_body = chunks.join
153
163
  end
154
164
  end
155
165
 
156
-
157
166
  handle_host_redirection if response_redirects?
158
167
  result = handle_unauthorized
159
168
  result ||= handle_response(chunked_body, &block)
@@ -171,6 +180,13 @@ module HTTParty
171
180
  @raw_request.body
172
181
  end
173
182
 
183
+ def _dump(_level)
184
+ opts = options.dup
185
+ opts.delete(:logger)
186
+ opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc)
187
+ Marshal.dump([http_method, path, opts])
188
+ end
189
+
174
190
  private
175
191
 
176
192
  def http
@@ -202,24 +218,22 @@ module HTTParty
202
218
  end
203
219
 
204
220
  def setup_raw_request
205
- @raw_request = http_method.new(request_uri(uri))
206
- @raw_request.body_stream = options[:body_stream] if options[:body_stream]
207
-
208
221
  if options[:headers].respond_to?(:to_hash)
209
222
  headers_hash = options[:headers].to_hash
210
-
211
- @raw_request.initialize_http_header(headers_hash)
212
- # If the caller specified a header of 'Accept-Encoding', assume they want to
213
- # deal with encoding of content. Disable the internal logic in Net:HTTP
214
- # that handles encoding, if the platform supports it.
215
- if @raw_request.respond_to?(:decode_content) && (headers_hash.key?('Accept-Encoding') || headers_hash.key?('accept-encoding'))
216
- # Using the '[]=' sets decode_content to false
217
- @raw_request['accept-encoding'] = @raw_request['accept-encoding']
218
- end
223
+ else
224
+ headers_hash = nil
219
225
  end
220
226
 
227
+ @raw_request = http_method.new(request_uri(uri), headers_hash)
228
+ @raw_request.body_stream = options[:body_stream] if options[:body_stream]
229
+
221
230
  if options[:body]
222
- body = Body.new(options[:body], query_string_normalizer: query_string_normalizer)
231
+ body = Body.new(
232
+ options[:body],
233
+ query_string_normalizer: query_string_normalizer,
234
+ force_multipart: options[:multipart]
235
+ )
236
+
223
237
  if body.multipart?
224
238
  content_type = "multipart/form-data; boundary=#{body.boundary}"
225
239
  @raw_request['Content-Type'] = content_type
@@ -227,6 +241,8 @@ module HTTParty
227
241
  @raw_request.body = body.call
228
242
  end
229
243
 
244
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
245
+
230
246
  if options[:basic_auth] && send_authorization_header?
231
247
  @raw_request.basic_auth(username, password)
232
248
  @credentials_sent = true
@@ -238,6 +254,10 @@ module HTTParty
238
254
  !!options[:digest_auth]
239
255
  end
240
256
 
257
+ def decompress_content?
258
+ !options[:skip_decompression]
259
+ end
260
+
241
261
  def response_unauthorized?
242
262
  !!last_response && last_response.code == '401'
243
263
  end
@@ -261,79 +281,15 @@ module HTTParty
261
281
  query_string_parts << options[:query] unless options[:query].nil?
262
282
  end
263
283
 
264
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
284
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
265
285
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
266
286
  end
267
287
 
268
- def get_charset
269
- content_type = last_response["content-type"]
270
- if content_type.nil?
271
- return nil
272
- end
273
-
274
- if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
275
- return $1
276
- end
277
-
278
- if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
279
- return $1.gsub(/\\(.)/, '\1')
280
- end
281
-
282
- nil
283
- end
284
-
285
- def encode_with_ruby_encoding(body, charset)
286
- # NOTE: This will raise an argument error if the
287
- # charset does not exist
288
- encoding = Encoding.find(charset)
289
- body.force_encoding(encoding.to_s)
290
- rescue ArgumentError
291
- body
292
- end
293
-
294
288
  def assume_utf16_is_big_endian
295
289
  options[:assume_utf16_is_big_endian]
296
290
  end
297
291
 
298
- def encode_utf_16(body)
299
- if body.bytesize >= 2
300
- if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
301
- return body.force_encoding("UTF-16LE")
302
- elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
303
- return body.force_encoding("UTF-16BE")
304
- end
305
- end
306
-
307
- if assume_utf16_is_big_endian
308
- body.force_encoding("UTF-16BE")
309
- else
310
- body.force_encoding("UTF-16LE")
311
- end
312
- end
313
-
314
- def _encode_body(body)
315
- charset = get_charset
316
-
317
- if charset.nil?
318
- return body
319
- end
320
-
321
- if "utf-16".casecmp(charset) == 0
322
- encode_utf_16(body)
323
- else
324
- encode_with_ruby_encoding(body, charset)
325
- end
326
- end
327
-
328
- def encode_body(body)
329
- if "".respond_to?(:encoding)
330
- _encode_body(body)
331
- else
332
- body
333
- end
334
- end
335
-
336
- def handle_response(body, &block)
292
+ def handle_response(raw_body, &block)
337
293
  if response_redirects?
338
294
  options[:limit] -= 1
339
295
  if options[:logger]
@@ -354,15 +310,26 @@ module HTTParty
354
310
  capture_cookies(last_response)
355
311
  perform(&block)
356
312
  else
357
- body ||= last_response.body
358
- body = body.nil? ? body : encode_body(body)
359
- Response.new(self, last_response, lambda { parse_response(body) }, body: body)
313
+ raw_body ||= last_response.body
314
+
315
+ body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
316
+
317
+ unless body.nil?
318
+ body = encode_text(body, last_response['content-type'])
319
+
320
+ if decompress_content?
321
+ last_response.delete('content-encoding')
322
+ raw_body = body
323
+ end
324
+ end
325
+
326
+ Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
360
327
  end
361
328
  end
362
329
 
363
330
  def handle_host_redirection
364
331
  check_duplicate_location_header
365
- redirect_path = options[:uri_adapter].parse last_response['location']
332
+ redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
366
333
  return if redirect_path.relative? || path.host == redirect_path.host
367
334
  @changed_hosts = true
368
335
  end
@@ -431,5 +398,17 @@ module HTTParty
431
398
  @credentials_sent = true
432
399
  end
433
400
  end
401
+
402
+ def decompress(body, encoding)
403
+ Decompressor.new(body, encoding).decompress
404
+ end
405
+
406
+ def encode_text(text, content_type)
407
+ TextEncoder.new(
408
+ text,
409
+ content_type: content_type,
410
+ assume_utf16_is_big_endian: assume_utf16_is_big_endian
411
+ ).call
412
+ end
434
413
  end
435
414
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
@@ -22,10 +24,10 @@ module HTTParty
22
24
  end
23
25
 
24
26
  def ==(other)
25
- if other.is_a?(::Net::HTTPHeader)
27
+ if other.is_a?(::Net::HTTPHeader)
26
28
  @header == other.instance_variable_get(:@header)
27
29
  elsif other.is_a?(Hash)
28
- @header == other || @header == Headers.new(other).instance_variable_get(:@header)
30
+ @header == other || @header == Headers.new(other).instance_variable_get(:@header)
29
31
  end
30
32
  end
31
33
  end
@@ -1,9 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
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,7 +22,11 @@ module HTTParty
14
22
  @headers = Headers.new(response.to_hash)
15
23
 
16
24
  if request.options[:logger]
17
- logger = ::HTTParty::Logger.build(request.options[:logger], request.options[:log_level], request.options[:log_format])
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
20
32
 
@@ -29,20 +41,24 @@ module HTTParty
29
41
  response.code.to_i
30
42
  end
31
43
 
44
+ def http_version
45
+ response.http_version
46
+ end
47
+
32
48
  def tap
33
49
  yield self
34
50
  self
35
51
  end
36
52
 
37
53
  def inspect
38
- inspect_id = ::Kernel::format "%x", (object_id * 2)
54
+ inspect_id = ::Kernel::format '%x', (object_id * 2)
39
55
  %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
40
56
  end
41
57
 
42
58
  CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
43
59
 
44
60
  CODES_TO_OBJ.each do |response_code, klass|
45
- name = klass.name.sub("Net::HTTP", '')
61
+ name = klass.name.sub('Net::HTTP', '')
46
62
  name = "#{underscore(name)}?".to_sym
47
63
 
48
64
  define_method(name) do
@@ -51,18 +67,28 @@ module HTTParty
51
67
  end
52
68
 
53
69
  # Support old multiple_choice? method from pre 2.0.0 era.
54
- if ::RUBY_VERSION >= "2.0.0" && ::RUBY_PLATFORM != "java"
70
+ if ::RUBY_VERSION >= '2.0.0' && ::RUBY_PLATFORM != 'java'
55
71
  alias_method :multiple_choice?, :multiple_choices?
56
72
  end
57
73
 
74
+ # Support old status codes method from pre 2.6.0 era.
75
+ if ::RUBY_VERSION >= '2.6.0' && ::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
+
58
83
  def nil?
84
+ warn_about_nil_deprecation
59
85
  response.nil? || response.body.nil? || response.body.empty?
60
86
  end
61
87
 
62
- def to_s
88
+ def to_s
63
89
  if !response.nil? && !response.body.nil? && response.body.respond_to?(:to_s)
64
90
  response.body.to_s
65
- else
91
+ else
66
92
  inspect
67
93
  end
68
94
  end
@@ -80,7 +106,7 @@ module HTTParty
80
106
  parsed_response.display(port)
81
107
  elsif !response.nil? && !response.body.nil? && response.body.respond_to?(:display)
82
108
  response.body.display(port)
83
- else
109
+ else
84
110
  port.write(inspect)
85
111
  end
86
112
  end
@@ -89,7 +115,11 @@ module HTTParty
89
115
  return true if super
90
116
  parsed_response.respond_to?(name) || response.respond_to?(name)
91
117
  end
92
-
118
+
119
+ def _dump(_level)
120
+ Marshal.dump([request, response, parsed_response, body])
121
+ end
122
+
93
123
  protected
94
124
 
95
125
  def method_missing(name, *args, &block)
@@ -107,6 +137,19 @@ module HTTParty
107
137
  ::Kernel.raise ::HTTParty::ResponseError.new(@response), "Code #{code} - #{body}"
108
138
  end
109
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
110
153
  end
111
154
  end
112
155
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module HTTParty
6
+ # Allow access to http_response and code by delegation on fragment
7
+ class ResponseFragment < SimpleDelegator
8
+ attr_reader :http_response, :connection
9
+
10
+ def code
11
+ @http_response.code.to_i
12
+ end
13
+
14
+ def initialize(fragment, http_response, connection)
15
+ @fragment = fragment
16
+ @http_response = http_response
17
+ @connection = connection
18
+ super fragment
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTParty
4
+ class TextEncoder
5
+ attr_reader :text, :content_type, :assume_utf16_is_big_endian
6
+
7
+ def initialize(text, assume_utf16_is_big_endian: true, content_type: nil)
8
+ @text = text.dup
9
+ @content_type = content_type
10
+ @assume_utf16_is_big_endian = assume_utf16_is_big_endian
11
+ end
12
+
13
+ def call
14
+ if can_encode?
15
+ encoded_text
16
+ else
17
+ text
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def can_encode?
24
+ ''.respond_to?(:encoding) && charset
25
+ end
26
+
27
+ def encoded_text
28
+ if 'utf-16'.casecmp(charset) == 0
29
+ encode_utf_16
30
+ else
31
+ encode_with_ruby_encoding
32
+ end
33
+ end
34
+
35
+ def encode_utf_16
36
+ if text.bytesize >= 2
37
+ if text.getbyte(0) == 0xFF && text.getbyte(1) == 0xFE
38
+ return text.force_encoding('UTF-16LE')
39
+ elsif text.getbyte(0) == 0xFE && text.getbyte(1) == 0xFF
40
+ return text.force_encoding('UTF-16BE')
41
+ end
42
+ end
43
+
44
+ if assume_utf16_is_big_endian # option
45
+ text.force_encoding('UTF-16BE')
46
+ else
47
+ text.force_encoding('UTF-16LE')
48
+ end
49
+ end
50
+
51
+ def encode_with_ruby_encoding
52
+ # NOTE: This will raise an argument error if the
53
+ # charset does not exist
54
+ encoding = Encoding.find(charset)
55
+ text.force_encoding(encoding.to_s)
56
+ rescue ArgumentError
57
+ text
58
+ end
59
+
60
+ def charset
61
+ return nil if content_type.nil?
62
+
63
+ if (matchdata = content_type.match(/;\s*charset\s*=\s*([^=,;"\s]+)/i))
64
+ return matchdata.captures.first
65
+ end
66
+
67
+ if (matchdata = content_type.match(/;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i))
68
+ return matchdata.captures.first.gsub(/\\(.)/, '\1')
69
+ end
70
+ end
71
+ end
72
+ end