httparty 0.16.2 → 0.22.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.
Files changed (96) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +23 -0
  5. data/.gitignore +2 -0
  6. data/.rubocop_todo.yml +1 -1
  7. data/Changelog.md +425 -280
  8. data/Gemfile +7 -0
  9. data/Guardfile +3 -2
  10. data/README.md +5 -5
  11. data/docs/README.md +90 -5
  12. data/examples/README.md +28 -11
  13. data/examples/aaws.rb +6 -2
  14. data/examples/body_stream.rb +14 -0
  15. data/examples/idn.rb +10 -0
  16. data/examples/microsoft_graph.rb +52 -0
  17. data/examples/multipart.rb +22 -0
  18. data/examples/peer_cert.rb +9 -0
  19. data/examples/stream_download.rb +8 -2
  20. data/httparty.gemspec +4 -3
  21. data/lib/httparty/connection_adapter.rb +44 -20
  22. data/lib/httparty/cookie_hash.rb +10 -8
  23. data/lib/httparty/decompressor.rb +102 -0
  24. data/lib/httparty/exceptions.rb +3 -1
  25. data/lib/httparty/hash_conversions.rb +10 -4
  26. data/lib/httparty/headers_processor.rb +32 -0
  27. data/lib/httparty/logger/apache_formatter.rb +31 -6
  28. data/lib/httparty/logger/curl_formatter.rb +9 -7
  29. data/lib/httparty/logger/logger.rb +5 -1
  30. data/lib/httparty/logger/logstash_formatter.rb +62 -0
  31. data/lib/httparty/module_inheritable_attributes.rb +9 -9
  32. data/lib/httparty/net_digest_auth.rb +15 -15
  33. data/lib/httparty/parser.rb +12 -5
  34. data/lib/httparty/request/body.rb +54 -27
  35. data/lib/httparty/request/multipart_boundary.rb +2 -0
  36. data/lib/httparty/request.rb +105 -107
  37. data/lib/httparty/response/headers.rb +4 -2
  38. data/lib/httparty/response.rb +52 -9
  39. data/lib/httparty/response_fragment.rb +21 -0
  40. data/lib/httparty/text_encoder.rb +72 -0
  41. data/lib/httparty/utils.rb +13 -0
  42. data/lib/httparty/version.rb +3 -1
  43. data/lib/httparty.rb +81 -33
  44. data/script/release +4 -4
  45. data/website/css/common.css +1 -1
  46. metadata +50 -107
  47. data/.simplecov +0 -1
  48. data/.travis.yml +0 -10
  49. data/features/basic_authentication.feature +0 -20
  50. data/features/command_line.feature +0 -95
  51. data/features/deals_with_http_error_codes.feature +0 -26
  52. data/features/digest_authentication.feature +0 -30
  53. data/features/handles_compressed_responses.feature +0 -27
  54. data/features/handles_multiple_formats.feature +0 -57
  55. data/features/steps/env.rb +0 -27
  56. data/features/steps/httparty_response_steps.rb +0 -56
  57. data/features/steps/httparty_steps.rb +0 -43
  58. data/features/steps/mongrel_helper.rb +0 -127
  59. data/features/steps/remote_service_steps.rb +0 -92
  60. data/features/supports_read_timeout_option.feature +0 -13
  61. data/features/supports_redirection.feature +0 -22
  62. data/features/supports_timeout_option.feature +0 -13
  63. data/spec/fixtures/delicious.xml +0 -23
  64. data/spec/fixtures/empty.xml +0 -0
  65. data/spec/fixtures/google.html +0 -3
  66. data/spec/fixtures/ssl/generate.sh +0 -29
  67. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  68. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  69. data/spec/fixtures/ssl/generated/ca.key +0 -15
  70. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  71. data/spec/fixtures/ssl/generated/server.crt +0 -13
  72. data/spec/fixtures/ssl/generated/server.key +0 -15
  73. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  74. data/spec/fixtures/tiny.gif +0 -0
  75. data/spec/fixtures/twitter.csv +0 -2
  76. data/spec/fixtures/twitter.json +0 -1
  77. data/spec/fixtures/twitter.xml +0 -403
  78. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  79. data/spec/httparty/connection_adapter_spec.rb +0 -498
  80. data/spec/httparty/cookie_hash_spec.rb +0 -100
  81. data/spec/httparty/exception_spec.rb +0 -45
  82. data/spec/httparty/hash_conversions_spec.rb +0 -56
  83. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  84. data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
  85. data/spec/httparty/logger/logger_spec.rb +0 -38
  86. data/spec/httparty/net_digest_auth_spec.rb +0 -270
  87. data/spec/httparty/parser_spec.rb +0 -190
  88. data/spec/httparty/request/body_spec.rb +0 -60
  89. data/spec/httparty/request_spec.rb +0 -1312
  90. data/spec/httparty/response_spec.rb +0 -347
  91. data/spec/httparty/ssl_spec.rb +0 -74
  92. data/spec/httparty_spec.rb +0 -896
  93. data/spec/spec_helper.rb +0 -51
  94. data/spec/support/ssl_test_helper.rb +0 -47
  95. data/spec/support/ssl_test_server.rb +0 -80
  96. data/spec/support/stub_response.rb +0 -49
@@ -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,15 @@ module HTTParty
43
46
  end.flatten.join('&')
44
47
  end
45
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
+
46
58
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
47
59
  attr_reader :path
48
60
 
@@ -70,7 +82,7 @@ module HTTParty
70
82
  @path = if uri.is_a?(uri_adapter)
71
83
  uri
72
84
  elsif String.try_convert(uri)
73
- uri_adapter.parse uri
85
+ uri_adapter.parse(uri).normalize
74
86
  else
75
87
  raise ArgumentError,
76
88
  "bad argument (expected #{uri_adapter} object or URI string)"
@@ -86,17 +98,17 @@ module HTTParty
86
98
  end
87
99
 
88
100
  def uri
89
- if redirect && path.relative? && path.path[0] != "/"
90
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
101
+ if redirect && path.relative? && path.path[0] != '/'
102
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
91
103
 
92
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
93
- path.path = last_uri_host + path.path
104
+ path.path = "/#{path.path}" if last_uri_host[-1] != '/'
105
+ path.path = "#{last_uri_host}#{path.path}"
94
106
  end
95
107
 
96
108
  if path.relative? && path.host
97
- new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
109
+ new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
98
110
  elsif path.relative?
99
- new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
111
+ new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
100
112
  else
101
113
  new_uri = path.clone
102
114
  end
@@ -116,7 +128,7 @@ module HTTParty
116
128
  def base_uri
117
129
  if redirect
118
130
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
119
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
131
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
120
132
  base_uri
121
133
  else
122
134
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -139,21 +151,22 @@ module HTTParty
139
151
  validate
140
152
  setup_raw_request
141
153
  chunked_body = nil
154
+ current_http = http
142
155
 
143
- self.last_response = http.request(@raw_request) do |http_response|
156
+ self.last_response = current_http.request(@raw_request) do |http_response|
144
157
  if block
145
158
  chunks = []
146
159
 
147
160
  http_response.read_body do |fragment|
148
- chunks << fragment unless options[:stream_body]
149
- block.call(fragment)
161
+ encoded_fragment = encode_text(fragment, http_response['content-type'])
162
+ chunks << encoded_fragment if !options[:stream_body]
163
+ block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
150
164
  end
151
165
 
152
166
  chunked_body = chunks.join
153
167
  end
154
168
  end
155
169
 
156
-
157
170
  handle_host_redirection if response_redirects?
158
171
  result = handle_unauthorized
159
172
  result ||= handle_response(chunked_body, &block)
@@ -171,6 +184,13 @@ module HTTParty
171
184
  @raw_request.body
172
185
  end
173
186
 
187
+ def _dump(_level)
188
+ opts = options.dup
189
+ opts.delete(:logger)
190
+ opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc)
191
+ Marshal.dump([http_method, path, opts, last_response, @last_uri, @raw_request])
192
+ end
193
+
174
194
  private
175
195
 
176
196
  def http
@@ -202,24 +222,22 @@ module HTTParty
202
222
  end
203
223
 
204
224
  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
225
  if options[:headers].respond_to?(:to_hash)
209
226
  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
227
+ else
228
+ headers_hash = nil
219
229
  end
220
230
 
231
+ @raw_request = http_method.new(request_uri(uri), headers_hash)
232
+ @raw_request.body_stream = options[:body_stream] if options[:body_stream]
233
+
221
234
  if options[:body]
222
- body = Body.new(options[:body], query_string_normalizer: query_string_normalizer)
235
+ body = Body.new(
236
+ options[:body],
237
+ query_string_normalizer: query_string_normalizer,
238
+ force_multipart: options[:multipart]
239
+ )
240
+
223
241
  if body.multipart?
224
242
  content_type = "multipart/form-data; boundary=#{body.boundary}"
225
243
  @raw_request['Content-Type'] = content_type
@@ -227,6 +245,8 @@ module HTTParty
227
245
  @raw_request.body = body.call
228
246
  end
229
247
 
248
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
249
+
230
250
  if options[:basic_auth] && send_authorization_header?
231
251
  @raw_request.basic_auth(username, password)
232
252
  @credentials_sent = true
@@ -238,6 +258,10 @@ module HTTParty
238
258
  !!options[:digest_auth]
239
259
  end
240
260
 
261
+ def decompress_content?
262
+ !options[:skip_decompression]
263
+ end
264
+
241
265
  def response_unauthorized?
242
266
  !!last_response && last_response.code == '401'
243
267
  end
@@ -261,109 +285,63 @@ module HTTParty
261
285
  query_string_parts << options[:query] unless options[:query].nil?
262
286
  end
263
287
 
264
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
288
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
265
289
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
266
290
  end
267
291
 
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
292
  def assume_utf16_is_big_endian
295
293
  options[:assume_utf16_is_big_endian]
296
294
  end
297
295
 
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")
296
+ def handle_response(raw_body, &block)
297
+ if response_redirects?
298
+ handle_redirection(&block)
309
299
  else
310
- body.force_encoding("UTF-16LE")
311
- end
312
- end
300
+ raw_body ||= last_response.body
313
301
 
314
- def _encode_body(body)
315
- charset = get_charset
302
+ body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
316
303
 
317
- if charset.nil?
318
- return body
319
- end
304
+ unless body.nil?
305
+ body = encode_text(body, last_response['content-type'])
320
306
 
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
307
+ if decompress_content?
308
+ last_response.delete('content-encoding')
309
+ raw_body = body
310
+ end
311
+ end
327
312
 
328
- def encode_body(body)
329
- if "".respond_to?(:encoding)
330
- _encode_body(body)
331
- else
332
- body
313
+ Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
333
314
  end
334
315
  end
335
316
 
336
- def handle_response(body, &block)
337
- if response_redirects?
338
- options[:limit] -= 1
339
- if options[:logger]
340
- logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
341
- logger.format(self, last_response)
317
+ def handle_redirection(&block)
318
+ options[:limit] -= 1
319
+ if options[:logger]
320
+ logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
321
+ logger.format(self, last_response)
322
+ end
323
+ self.path = last_response['location']
324
+ self.redirect = true
325
+ if last_response.class == Net::HTTPSeeOther
326
+ unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
327
+ self.http_method = Net::HTTP::Get
342
328
  end
343
- self.path = last_response['location']
344
- self.redirect = true
345
- if last_response.class == Net::HTTPSeeOther
346
- unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
347
- self.http_method = Net::HTTP::Get
348
- end
349
- elsif last_response.code != '307' && last_response.code != '308'
350
- unless options[:maintain_method_across_redirects]
351
- self.http_method = Net::HTTP::Get
352
- end
329
+ elsif last_response.code != '307' && last_response.code != '308'
330
+ unless options[:maintain_method_across_redirects]
331
+ self.http_method = Net::HTTP::Get
353
332
  end
354
- capture_cookies(last_response)
355
- perform(&block)
356
- 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)
360
333
  end
334
+ if http_method == Net::HTTP::Get
335
+ clear_body
336
+ end
337
+ capture_cookies(last_response)
338
+ perform(&block)
361
339
  end
362
340
 
363
341
  def handle_host_redirection
364
342
  check_duplicate_location_header
365
- redirect_path = options[:uri_adapter].parse last_response['location']
366
- return if redirect_path.relative? || path.host == redirect_path.host
343
+ redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
344
+ return if redirect_path.relative? || path.host == redirect_path.host || uri.host == redirect_path.host
367
345
  @changed_hosts = true
368
346
  end
369
347
 
@@ -391,6 +369,14 @@ module HTTParty
391
369
  parser.call(body, format)
392
370
  end
393
371
 
372
+ # Some Web Application Firewalls reject incoming GET requests that have a body
373
+ # if we redirect, and the resulting verb is GET then we will clear the body that
374
+ # may be left behind from the initiating request
375
+ def clear_body
376
+ options[:body] = nil
377
+ @raw_request.body = nil
378
+ end
379
+
394
380
  def capture_cookies(response)
395
381
  return unless response['Set-Cookie']
396
382
  cookies_hash = HTTParty::CookieHash.new
@@ -431,5 +417,17 @@ module HTTParty
431
417
  @credentials_sent = true
432
418
  end
433
419
  end
420
+
421
+ def decompress(body, encoding)
422
+ Decompressor.new(body, encoding).decompress
423
+ end
424
+
425
+ def encode_text(text, content_type)
426
+ TextEncoder.new(
427
+ text,
428
+ content_type: content_type,
429
+ assume_utf16_is_big_endian: assume_utf16_is_big_endian
430
+ ).call
431
+ end
434
432
  end
435
433
  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_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_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)
@@ -103,10 +133,23 @@ module HTTParty
103
133
  end
104
134
 
105
135
  def throw_exception
106
- if @request.options[:raise_on] && @request.options[:raise_on].include?(code)
136
+ if @request.options[:raise_on].to_a.detect { |c| code.to_s.match(/#{c.to_s}/) }
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
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTParty
4
+ module Utils
5
+ def self.stringify_keys(hash)
6
+ return hash.transform_keys(&:to_s) if hash.respond_to?(:transform_keys)
7
+
8
+ hash.each_with_object({}) do |(key, value), new_hash|
9
+ new_hash[key.to_s] = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
- VERSION = "0.16.2"
4
+ VERSION = '0.22.0'
3
5
  end