httparty 0.16.2 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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