httparty 0.16.4 → 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 (87) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +23 -0
  3. data/.rubocop_todo.yml +1 -1
  4. data/Changelog.md +55 -0
  5. data/Gemfile +5 -0
  6. data/README.md +4 -4
  7. data/docs/README.md +70 -5
  8. data/examples/README.md +6 -0
  9. data/examples/aaws.rb +6 -2
  10. data/examples/idn.rb +10 -0
  11. data/examples/peer_cert.rb +9 -0
  12. data/httparty.gemspec +1 -2
  13. data/lib/httparty/connection_adapter.rb +41 -10
  14. data/lib/httparty/cookie_hash.rb +10 -8
  15. data/lib/httparty/decompressor.rb +92 -0
  16. data/lib/httparty/exceptions.rb +3 -1
  17. data/lib/httparty/hash_conversions.rb +4 -2
  18. data/lib/httparty/headers_processor.rb +32 -0
  19. data/lib/httparty/logger/apache_formatter.rb +4 -2
  20. data/lib/httparty/logger/curl_formatter.rb +6 -4
  21. data/lib/httparty/logger/logger.rb +2 -0
  22. data/lib/httparty/logger/logstash_formatter.rb +4 -2
  23. data/lib/httparty/module_inheritable_attributes.rb +3 -1
  24. data/lib/httparty/net_digest_auth.rb +9 -10
  25. data/lib/httparty/parser.rb +9 -5
  26. data/lib/httparty/request/body.rb +24 -10
  27. data/lib/httparty/request/multipart_boundary.rb +2 -0
  28. data/lib/httparty/request.rb +67 -96
  29. data/lib/httparty/response/headers.rb +2 -0
  30. data/lib/httparty/response.rb +24 -4
  31. data/lib/httparty/{fragment_with_response.rb → response_fragment.rb} +6 -5
  32. data/lib/httparty/text_encoder.rb +72 -0
  33. data/lib/httparty/utils.rb +2 -0
  34. data/lib/httparty/version.rb +3 -1
  35. data/lib/httparty.rb +58 -35
  36. metadata +12 -108
  37. data/.travis.yml +0 -11
  38. data/features/basic_authentication.feature +0 -20
  39. data/features/command_line.feature +0 -95
  40. data/features/deals_with_http_error_codes.feature +0 -26
  41. data/features/digest_authentication.feature +0 -30
  42. data/features/handles_compressed_responses.feature +0 -27
  43. data/features/handles_multiple_formats.feature +0 -57
  44. data/features/steps/env.rb +0 -27
  45. data/features/steps/httparty_response_steps.rb +0 -56
  46. data/features/steps/httparty_steps.rb +0 -43
  47. data/features/steps/mongrel_helper.rb +0 -127
  48. data/features/steps/remote_service_steps.rb +0 -92
  49. data/features/supports_read_timeout_option.feature +0 -13
  50. data/features/supports_redirection.feature +0 -22
  51. data/features/supports_timeout_option.feature +0 -13
  52. data/spec/fixtures/delicious.xml +0 -23
  53. data/spec/fixtures/empty.xml +0 -0
  54. data/spec/fixtures/example.html +0 -10
  55. data/spec/fixtures/ssl/generate.sh +0 -29
  56. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  57. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  58. data/spec/fixtures/ssl/generated/ca.key +0 -15
  59. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  60. data/spec/fixtures/ssl/generated/server.crt +0 -13
  61. data/spec/fixtures/ssl/generated/server.key +0 -15
  62. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  63. data/spec/fixtures/tiny.gif +0 -0
  64. data/spec/fixtures/twitter.csv +0 -2
  65. data/spec/fixtures/twitter.json +0 -1
  66. data/spec/fixtures/twitter.xml +0 -403
  67. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  68. data/spec/httparty/connection_adapter_spec.rb +0 -502
  69. data/spec/httparty/cookie_hash_spec.rb +0 -100
  70. data/spec/httparty/exception_spec.rb +0 -45
  71. data/spec/httparty/fragment_with_response_spec.rb +0 -14
  72. data/spec/httparty/hash_conversions_spec.rb +0 -58
  73. data/spec/httparty/logger/apache_formatter_spec.rb +0 -40
  74. data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
  75. data/spec/httparty/logger/logger_spec.rb +0 -43
  76. data/spec/httparty/logger/logstash_formatter_spec.rb +0 -44
  77. data/spec/httparty/net_digest_auth_spec.rb +0 -270
  78. data/spec/httparty/parser_spec.rb +0 -190
  79. data/spec/httparty/request/body_spec.rb +0 -165
  80. data/spec/httparty/request_spec.rb +0 -1367
  81. data/spec/httparty/response_spec.rb +0 -368
  82. data/spec/httparty/ssl_spec.rb +0 -74
  83. data/spec/httparty_spec.rb +0 -923
  84. data/spec/spec_helper.rb +0 -56
  85. data/spec/support/ssl_test_helper.rb +0 -47
  86. data/spec/support/ssl_test_server.rb +0 -80
  87. data/spec/support/stub_response.rb +0 -49
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module Logger
3
5
  class CurlFormatter #:nodoc:
4
6
  TAG_NAME = HTTParty.name
5
- OUT = '>'.freeze
6
- IN = '<'.freeze
7
+ OUT = '>'
8
+ IN = '<'
7
9
 
8
10
  attr_accessor :level, :logger
9
11
 
@@ -20,7 +22,7 @@ module HTTParty
20
22
  log_request
21
23
  log_response
22
24
 
23
- logger.public_send level, messages.join("\n")
25
+ logger.public_send level, messages.join('\n')
24
26
  end
25
27
 
26
28
  private
@@ -44,7 +46,7 @@ module HTTParty
44
46
  end
45
47
 
46
48
  def log_url
47
- http_method = request.http_method.name.split("::").last.upcase
49
+ http_method = request.http_method.name.split('::').last.upcase
48
50
  uri = if request.options[:base_uri]
49
51
  request.options[:base_uri] + request.path.path
50
52
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'httparty/logger/apache_formatter'
2
4
  require 'httparty/logger/curl_formatter'
3
5
  require 'httparty/logger/logstash_formatter'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module Logger
3
5
  class LogstashFormatter #:nodoc:
@@ -40,11 +42,11 @@ module HTTParty
40
42
  end
41
43
 
42
44
  def current_time
43
- Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
45
+ Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
44
46
  end
45
47
 
46
48
  def http_method
47
- @http_method ||= request.http_method.name.split("::").last.upcase
49
+ @http_method ||= request.http_method.name.split('::').last.upcase
48
50
  end
49
51
 
50
52
  def path
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module ModuleInheritableAttributes #:nodoc:
3
5
  def self.included(base)
@@ -36,7 +38,7 @@ module HTTParty
36
38
  def inherited(subclass)
37
39
  super
38
40
  @mattr_inheritable_attrs.each do |inheritable_attribute|
39
- ivar = "@#{inheritable_attribute}"
41
+ ivar = :"@#{inheritable_attribute}"
40
42
  subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
41
43
 
42
44
  if instance_variable_get(ivar).respond_to?(:merge)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/md5'
2
4
  require 'net/http'
3
5
 
@@ -44,12 +46,9 @@ module Net
44
46
  header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
45
47
 
46
48
  if qop_present?
47
- fields = [
48
- %(cnonce="#{@cnonce}"),
49
- %(qop="#{@response['qop']}"),
50
- "nc=00000001"
51
- ]
52
- fields.each { |field| header << field }
49
+ header << %(cnonce="#{@cnonce}")
50
+ header << %(qop="#{@response['qop']}")
51
+ header << 'nc=00000001'
53
52
  end
54
53
 
55
54
  header << %(opaque="#{@response['opaque']}") if opaque_present?
@@ -98,13 +97,13 @@ module Net
98
97
  end
99
98
 
100
99
  def random
101
- format "%x", (Time.now.to_i + rand(65535))
100
+ format '%x', (Time.now.to_i + rand(65535))
102
101
  end
103
102
 
104
103
  def request_digest
105
104
  a = [md5(a1), @response['nonce'], md5(a2)]
106
- a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
107
- md5(a.join(":"))
105
+ a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
106
+ md5(a.join(':'))
108
107
  end
109
108
 
110
109
  def md5(str)
@@ -129,7 +128,7 @@ module Net
129
128
  end
130
129
 
131
130
  def a2
132
- [@method, @path].join(":")
131
+ [@method, @path].join(':')
133
132
  end
134
133
  end
135
134
  end
@@ -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,8 +1,13 @@
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
8
+ NEWLINE = "\r\n"
9
+ private_constant :NEWLINE
10
+
6
11
  def initialize(params, query_string_normalizer: nil, force_multipart: false)
7
12
  @params = params
8
13
  @query_string_normalizer = query_string_normalizer
@@ -30,20 +35,20 @@ module HTTParty
30
35
  def generate_multipart
31
36
  normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
32
37
 
33
- multipart = normalized_params.inject('') do |memo, (key, value)|
34
- memo += "--#{boundary}\r\n"
35
- 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}")
36
41
  # value.path is used to support ActionDispatch::Http::UploadedFile
37
42
  # https://github.com/jnunemaker/httparty/pull/585
38
- memo += %(; filename="#{file_name(value)}") if file?(value)
39
- memo += "\r\n"
40
- memo += "Content-Type: #{content_type(value)}\r\n" if file?(value)
41
- memo += "\r\n"
42
- memo += file?(value) ? value.read : value.to_s
43
- 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
44
49
  end
45
50
 
46
- multipart += "--#{boundary}--\r\n"
51
+ multipart << "--#{boundary}--#{NEWLINE}"
47
52
  end
48
53
 
49
54
  def has_file?(value)
@@ -68,6 +73,15 @@ module HTTParty
68
73
  end
69
74
  end
70
75
 
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
+
71
85
  def content_type(object)
72
86
  return object.content_type if object.respond_to?(:content_type)
73
87
  mime = MIME::Types.type_for(object.path)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  module HTTParty
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
- require 'httparty/request/body'
3
- require 'httparty/fragment_with_response'
4
4
 
5
5
  module HTTParty
6
6
  class Request #:nodoc:
@@ -46,6 +46,11 @@ module HTTParty
46
46
  end.flatten.join('&')
47
47
  end
48
48
 
49
+ def self._load(data)
50
+ http_method, path, options = Marshal.load(data)
51
+ new(http_method, path, options)
52
+ end
53
+
49
54
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
50
55
  attr_reader :path
51
56
 
@@ -73,7 +78,7 @@ module HTTParty
73
78
  @path = if uri.is_a?(uri_adapter)
74
79
  uri
75
80
  elsif String.try_convert(uri)
76
- uri_adapter.parse uri
81
+ uri_adapter.parse(uri).normalize
77
82
  else
78
83
  raise ArgumentError,
79
84
  "bad argument (expected #{uri_adapter} object or URI string)"
@@ -89,17 +94,17 @@ module HTTParty
89
94
  end
90
95
 
91
96
  def uri
92
- if redirect && path.relative? && path.path[0] != "/"
93
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
97
+ if redirect && path.relative? && path.path[0] != '/'
98
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
94
99
 
95
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
96
- 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}"
97
102
  end
98
103
 
99
104
  if path.relative? && path.host
100
- new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
105
+ new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
101
106
  elsif path.relative?
102
- new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
107
+ new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
103
108
  else
104
109
  new_uri = path.clone
105
110
  end
@@ -119,7 +124,7 @@ module HTTParty
119
124
  def base_uri
120
125
  if redirect
121
126
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
122
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
127
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
123
128
  base_uri
124
129
  else
125
130
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -142,21 +147,22 @@ module HTTParty
142
147
  validate
143
148
  setup_raw_request
144
149
  chunked_body = nil
150
+ current_http = http
145
151
 
146
- self.last_response = http.request(@raw_request) do |http_response|
152
+ self.last_response = current_http.request(@raw_request) do |http_response|
147
153
  if block
148
154
  chunks = []
149
155
 
150
156
  http_response.read_body do |fragment|
151
- chunks << fragment unless options[:stream_body]
152
- block.call FragmentWithResponse.new(fragment, http_response)
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)
153
160
  end
154
161
 
155
162
  chunked_body = chunks.join
156
163
  end
157
164
  end
158
165
 
159
-
160
166
  handle_host_redirection if response_redirects?
161
167
  result = handle_unauthorized
162
168
  result ||= handle_response(chunked_body, &block)
@@ -174,6 +180,13 @@ module HTTParty
174
180
  @raw_request.body
175
181
  end
176
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
+
177
190
  private
178
191
 
179
192
  def http
@@ -205,22 +218,15 @@ module HTTParty
205
218
  end
206
219
 
207
220
  def setup_raw_request
208
- @raw_request = http_method.new(request_uri(uri))
209
- @raw_request.body_stream = options[:body_stream] if options[:body_stream]
210
-
211
221
  if options[:headers].respond_to?(:to_hash)
212
222
  headers_hash = options[:headers].to_hash
213
-
214
- @raw_request.initialize_http_header(headers_hash)
215
- # If the caller specified a header of 'Accept-Encoding', assume they want to
216
- # deal with encoding of content. Disable the internal logic in Net:HTTP
217
- # that handles encoding, if the platform supports it.
218
- if @raw_request.respond_to?(:decode_content) && (headers_hash.key?('Accept-Encoding') || headers_hash.key?('accept-encoding'))
219
- # Using the '[]=' sets decode_content to false
220
- @raw_request['accept-encoding'] = @raw_request['accept-encoding']
221
- end
223
+ else
224
+ headers_hash = nil
222
225
  end
223
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
+
224
230
  if options[:body]
225
231
  body = Body.new(
226
232
  options[:body],
@@ -235,6 +241,8 @@ module HTTParty
235
241
  @raw_request.body = body.call
236
242
  end
237
243
 
244
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
245
+
238
246
  if options[:basic_auth] && send_authorization_header?
239
247
  @raw_request.basic_auth(username, password)
240
248
  @credentials_sent = true
@@ -246,6 +254,10 @@ module HTTParty
246
254
  !!options[:digest_auth]
247
255
  end
248
256
 
257
+ def decompress_content?
258
+ !options[:skip_decompression]
259
+ end
260
+
249
261
  def response_unauthorized?
250
262
  !!last_response && last_response.code == '401'
251
263
  end
@@ -269,79 +281,15 @@ module HTTParty
269
281
  query_string_parts << options[:query] unless options[:query].nil?
270
282
  end
271
283
 
272
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
284
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
273
285
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
274
286
  end
275
287
 
276
- def get_charset
277
- content_type = last_response["content-type"]
278
- if content_type.nil?
279
- return nil
280
- end
281
-
282
- if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
283
- return $1
284
- end
285
-
286
- if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
287
- return $1.gsub(/\\(.)/, '\1')
288
- end
289
-
290
- nil
291
- end
292
-
293
- def encode_with_ruby_encoding(body, charset)
294
- # NOTE: This will raise an argument error if the
295
- # charset does not exist
296
- encoding = Encoding.find(charset)
297
- body.force_encoding(encoding.to_s)
298
- rescue ArgumentError
299
- body
300
- end
301
-
302
288
  def assume_utf16_is_big_endian
303
289
  options[:assume_utf16_is_big_endian]
304
290
  end
305
291
 
306
- def encode_utf_16(body)
307
- if body.bytesize >= 2
308
- if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
309
- return body.force_encoding("UTF-16LE")
310
- elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
311
- return body.force_encoding("UTF-16BE")
312
- end
313
- end
314
-
315
- if assume_utf16_is_big_endian
316
- body.force_encoding("UTF-16BE")
317
- else
318
- body.force_encoding("UTF-16LE")
319
- end
320
- end
321
-
322
- def _encode_body(body)
323
- charset = get_charset
324
-
325
- if charset.nil?
326
- return body
327
- end
328
-
329
- if "utf-16".casecmp(charset) == 0
330
- encode_utf_16(body)
331
- else
332
- encode_with_ruby_encoding(body, charset)
333
- end
334
- end
335
-
336
- def encode_body(body)
337
- if "".respond_to?(:encoding)
338
- _encode_body(body)
339
- else
340
- body
341
- end
342
- end
343
-
344
- def handle_response(body, &block)
292
+ def handle_response(raw_body, &block)
345
293
  if response_redirects?
346
294
  options[:limit] -= 1
347
295
  if options[:logger]
@@ -362,15 +310,26 @@ module HTTParty
362
310
  capture_cookies(last_response)
363
311
  perform(&block)
364
312
  else
365
- body ||= last_response.body
366
- body = body.nil? ? body : encode_body(body)
367
- 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)
368
327
  end
369
328
  end
370
329
 
371
330
  def handle_host_redirection
372
331
  check_duplicate_location_header
373
- redirect_path = options[:uri_adapter].parse last_response['location']
332
+ redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
374
333
  return if redirect_path.relative? || path.host == redirect_path.host
375
334
  @changed_hosts = true
376
335
  end
@@ -439,5 +398,17 @@ module HTTParty
439
398
  @credentials_sent = true
440
399
  end
441
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
442
413
  end
443
414
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  class Response < Object
3
5
  def self.underscore(string)
@@ -39,20 +41,24 @@ module HTTParty
39
41
  response.code.to_i
40
42
  end
41
43
 
44
+ def http_version
45
+ response.http_version
46
+ end
47
+
42
48
  def tap
43
49
  yield self
44
50
  self
45
51
  end
46
52
 
47
53
  def inspect
48
- inspect_id = ::Kernel::format "%x", (object_id * 2)
54
+ inspect_id = ::Kernel::format '%x', (object_id * 2)
49
55
  %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
50
56
  end
51
57
 
52
58
  CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
53
59
 
54
60
  CODES_TO_OBJ.each do |response_code, klass|
55
- name = klass.name.sub("Net::HTTP", '')
61
+ name = klass.name.sub('Net::HTTP', '')
56
62
  name = "#{underscore(name)}?".to_sym
57
63
 
58
64
  define_method(name) do
@@ -61,12 +67,12 @@ module HTTParty
61
67
  end
62
68
 
63
69
  # Support old multiple_choice? method from pre 2.0.0 era.
64
- if ::RUBY_VERSION >= "2.0.0" && ::RUBY_PLATFORM != "java"
70
+ if ::RUBY_VERSION >= '2.0.0' && ::RUBY_PLATFORM != 'java'
65
71
  alias_method :multiple_choice?, :multiple_choices?
66
72
  end
67
73
 
68
74
  # Support old status codes method from pre 2.6.0 era.
69
- if ::RUBY_VERSION >= "2.6.0" && ::RUBY_PLATFORM != "java"
75
+ if ::RUBY_VERSION >= '2.6.0' && ::RUBY_PLATFORM != 'java'
70
76
  alias_method :gateway_time_out?, :gateway_timeout?
71
77
  alias_method :request_entity_too_large?, :payload_too_large?
72
78
  alias_method :request_time_out?, :request_timeout?
@@ -75,6 +81,7 @@ module HTTParty
75
81
  end
76
82
 
77
83
  def nil?
84
+ warn_about_nil_deprecation
78
85
  response.nil? || response.body.nil? || response.body.empty?
79
86
  end
80
87
 
@@ -130,6 +137,19 @@ module HTTParty
130
137
  ::Kernel.raise ::HTTParty::ResponseError.new(@response), "Code #{code} - #{body}"
131
138
  end
132
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
133
153
  end
134
154
  end
135
155
 
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
4
6
  # Allow access to http_response and code by delegation on fragment
5
- class FragmentWithResponse < SimpleDelegator
6
- extend Forwardable
7
-
8
- attr_reader :http_response
7
+ class ResponseFragment < SimpleDelegator
8
+ attr_reader :http_response, :connection
9
9
 
10
10
  def code
11
11
  @http_response.code.to_i
12
12
  end
13
13
 
14
- def initialize(fragment, http_response)
14
+ def initialize(fragment, http_response, connection)
15
15
  @fragment = fragment
16
16
  @http_response = http_response
17
+ @connection = connection
17
18
  super fragment
18
19
  end
19
20
  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