httparty 0.15.6 → 0.21.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 (95) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/workflows/ci.yml +26 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop_todo.yml +1 -1
  6. data/Changelog.md +105 -0
  7. data/Gemfile +7 -0
  8. data/Guardfile +3 -2
  9. data/README.md +6 -6
  10. data/docs/README.md +90 -5
  11. data/examples/README.md +28 -11
  12. data/examples/aaws.rb +6 -2
  13. data/examples/body_stream.rb +14 -0
  14. data/examples/custom_parsers.rb +4 -0
  15. data/examples/headers_and_user_agents.rb +7 -3
  16. data/examples/idn.rb +10 -0
  17. data/examples/logging.rb +3 -3
  18. data/examples/microsoft_graph.rb +52 -0
  19. data/examples/multipart.rb +22 -0
  20. data/examples/peer_cert.rb +9 -0
  21. data/examples/stream_download.rb +8 -2
  22. data/httparty.gemspec +3 -3
  23. data/lib/httparty/connection_adapter.rb +59 -16
  24. data/lib/httparty/cookie_hash.rb +10 -8
  25. data/lib/httparty/decompressor.rb +102 -0
  26. data/lib/httparty/exceptions.rb +3 -1
  27. data/lib/httparty/hash_conversions.rb +28 -12
  28. data/lib/httparty/headers_processor.rb +32 -0
  29. data/lib/httparty/logger/apache_formatter.rb +31 -6
  30. data/lib/httparty/logger/curl_formatter.rb +9 -7
  31. data/lib/httparty/logger/logger.rb +5 -1
  32. data/lib/httparty/logger/logstash_formatter.rb +61 -0
  33. data/lib/httparty/module_inheritable_attributes.rb +6 -4
  34. data/lib/httparty/net_digest_auth.rb +15 -15
  35. data/lib/httparty/parser.rb +21 -15
  36. data/lib/httparty/request/body.rb +105 -0
  37. data/lib/httparty/request/multipart_boundary.rb +13 -0
  38. data/lib/httparty/request.rb +86 -94
  39. data/lib/httparty/response/headers.rb +4 -2
  40. data/lib/httparty/response.rb +59 -8
  41. data/lib/httparty/response_fragment.rb +21 -0
  42. data/lib/httparty/text_encoder.rb +72 -0
  43. data/lib/httparty/utils.rb +13 -0
  44. data/lib/httparty/version.rb +3 -1
  45. data/lib/httparty.rb +70 -25
  46. data/website/css/common.css +1 -1
  47. metadata +37 -103
  48. data/.simplecov +0 -1
  49. data/.travis.yml +0 -9
  50. data/features/basic_authentication.feature +0 -20
  51. data/features/command_line.feature +0 -95
  52. data/features/deals_with_http_error_codes.feature +0 -26
  53. data/features/digest_authentication.feature +0 -30
  54. data/features/handles_compressed_responses.feature +0 -27
  55. data/features/handles_multiple_formats.feature +0 -57
  56. data/features/steps/env.rb +0 -27
  57. data/features/steps/httparty_response_steps.rb +0 -56
  58. data/features/steps/httparty_steps.rb +0 -43
  59. data/features/steps/mongrel_helper.rb +0 -127
  60. data/features/steps/remote_service_steps.rb +0 -92
  61. data/features/supports_read_timeout_option.feature +0 -13
  62. data/features/supports_redirection.feature +0 -22
  63. data/features/supports_timeout_option.feature +0 -13
  64. data/spec/fixtures/delicious.xml +0 -23
  65. data/spec/fixtures/empty.xml +0 -0
  66. data/spec/fixtures/google.html +0 -3
  67. data/spec/fixtures/ssl/generate.sh +0 -29
  68. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  69. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  70. data/spec/fixtures/ssl/generated/ca.key +0 -15
  71. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  72. data/spec/fixtures/ssl/generated/server.crt +0 -13
  73. data/spec/fixtures/ssl/generated/server.key +0 -15
  74. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  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 -495
  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 -49
  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 -268
  87. data/spec/httparty/parser_spec.rb +0 -190
  88. data/spec/httparty/request_spec.rb +0 -1279
  89. data/spec/httparty/response_spec.rb +0 -347
  90. data/spec/httparty/ssl_spec.rb +0 -74
  91. data/spec/httparty_spec.rb +0 -878
  92. data/spec/spec_helper.rb +0 -51
  93. data/spec/support/ssl_test_helper.rb +0 -47
  94. data/spec/support/ssl_test_server.rb +0 -80
  95. data/spec/support/stub_response.rb +0 -49
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/md5'
2
4
  require 'net/http'
3
5
 
@@ -14,11 +16,11 @@ module Net
14
16
 
15
17
  authenticator.authorization_header.each do |v|
16
18
  add_field('Authorization', v)
17
- end
19
+ end
18
20
 
19
21
  authenticator.cookie_header.each do |v|
20
22
  add_field('Cookie', v)
21
- end
23
+ end
22
24
  end
23
25
 
24
26
  class DigestAuthenticator
@@ -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?
@@ -64,7 +63,8 @@ module Net
64
63
 
65
64
  def parse(response_header)
66
65
  header = response_header['www-authenticate']
67
- .gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
66
+
67
+ header = header.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
68
68
 
69
69
  header =~ /Digest (.*)/
70
70
  params = {}
@@ -97,13 +97,13 @@ module Net
97
97
  end
98
98
 
99
99
  def random
100
- format "%x", (Time.now.to_i + rand(65535))
100
+ format '%x', (Time.now.to_i + rand(65535))
101
101
  end
102
102
 
103
103
  def request_digest
104
104
  a = [md5(a1), @response['nonce'], md5(a2)]
105
- a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
106
- md5(a.join(":"))
105
+ a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
106
+ md5(a.join(':'))
107
107
  end
108
108
 
109
109
  def md5(str)
@@ -113,11 +113,11 @@ module Net
113
113
  def algorithm_present?
114
114
  @response.key?('algorithm') && !@response['algorithm'].empty?
115
115
  end
116
-
116
+
117
117
  def use_md5_sess?
118
118
  algorithm_present? && @response['algorithm'] == 'MD5-sess'
119
119
  end
120
-
120
+
121
121
  def a1
122
122
  a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
123
123
  if use_md5_sess?
@@ -128,7 +128,7 @@ module Net
128
128
  end
129
129
 
130
130
  def a2
131
- [@method, @path].join(":")
131
+ [@method, @path].join(':')
132
132
  end
133
133
  end
134
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.
@@ -38,16 +40,18 @@ module HTTParty
38
40
  # @abstract Read the Custom Parsers section for more information.
39
41
  class Parser
40
42
  SupportedFormats = {
41
- 'text/xml' => :xml,
42
- 'application/xml' => :xml,
43
- 'application/json' => :json,
44
- 'text/json' => :json,
45
- 'application/javascript' => :plain,
46
- 'text/javascript' => :plain,
47
- 'text/html' => :html,
48
- 'text/plain' => :plain,
49
- 'text/csv' => :csv,
50
- 'application/csv' => :csv,
43
+ 'text/xml' => :xml,
44
+ 'application/xml' => :xml,
45
+ 'application/json' => :json,
46
+ 'application/vnd.api+json' => :json,
47
+ 'application/hal+json' => :json,
48
+ 'text/json' => :json,
49
+ 'application/javascript' => :plain,
50
+ 'text/javascript' => :plain,
51
+ 'text/html' => :html,
52
+ 'text/plain' => :plain,
53
+ 'text/csv' => :csv,
54
+ 'application/csv' => :csv,
51
55
  'text/comma-separated-values' => :csv
52
56
  }
53
57
 
@@ -99,7 +103,7 @@ module HTTParty
99
103
  # @return [nil] when the response body is nil, an empty string, spaces only or "null"
100
104
  def parse
101
105
  return nil if body.nil?
102
- return nil if body == "null"
106
+ return nil if body == 'null'
103
107
  return nil if body.valid_encoding? && body.strip.empty?
104
108
  if body.valid_encoding? && body.encoding == Encoding::UTF_8
105
109
  @body = body.gsub(/\A#{UTF8_BOM}/, '')
@@ -117,7 +121,7 @@ module HTTParty
117
121
  MultiXml.parse(body)
118
122
  end
119
123
 
120
- UTF8_BOM = "\xEF\xBB\xBF".freeze
124
+ UTF8_BOM = "\xEF\xBB\xBF"
121
125
 
122
126
  def json
123
127
  JSON.parse(body, :quirks_mode => true, :allow_nan => true)
@@ -140,9 +144,11 @@ module HTTParty
140
144
  end
141
145
 
142
146
  def parse_supported_format
143
- send(format)
144
- rescue NoMethodError => e
145
- 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
146
152
  end
147
153
  end
148
154
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'multipart_boundary'
4
+
5
+ module HTTParty
6
+ class Request
7
+ class Body
8
+ NEWLINE = "\r\n"
9
+ private_constant :NEWLINE
10
+
11
+ def initialize(params, query_string_normalizer: nil, force_multipart: false)
12
+ @params = params
13
+ @query_string_normalizer = query_string_normalizer
14
+ @force_multipart = force_multipart
15
+ end
16
+
17
+ def call
18
+ if params.respond_to?(:to_hash)
19
+ multipart? ? generate_multipart : normalize_query(params)
20
+ else
21
+ params
22
+ end
23
+ end
24
+
25
+ def boundary
26
+ @boundary ||= MultipartBoundary.generate
27
+ end
28
+
29
+ def multipart?
30
+ params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
31
+ end
32
+
33
+ private
34
+
35
+ # https://html.spec.whatwg.org/#multipart-form-data
36
+ MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
37
+ '"' => '%22',
38
+ "\r" => '%0D',
39
+ "\n" => '%0A'
40
+ }.freeze
41
+
42
+ def generate_multipart
43
+ normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
44
+
45
+ multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
46
+ memo << "--#{boundary}#{NEWLINE}"
47
+ memo << %(Content-Disposition: form-data; name="#{key}")
48
+ # value.path is used to support ActionDispatch::Http::UploadedFile
49
+ # https://github.com/jnunemaker/httparty/pull/585
50
+ memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value)
51
+ memo << NEWLINE
52
+ memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
53
+ memo << NEWLINE
54
+ memo << content_body(value)
55
+ memo << NEWLINE
56
+ end
57
+
58
+ multipart << "--#{boundary}--#{NEWLINE}"
59
+ end
60
+
61
+ def has_file?(value)
62
+ if value.respond_to?(:to_hash)
63
+ value.to_hash.any? { |_, v| has_file?(v) }
64
+ elsif value.respond_to?(:to_ary)
65
+ value.to_ary.any? { |v| has_file?(v) }
66
+ else
67
+ file?(value)
68
+ end
69
+ end
70
+
71
+ def file?(object)
72
+ object.respond_to?(:path) && object.respond_to?(:read)
73
+ end
74
+
75
+ def normalize_query(query)
76
+ if query_string_normalizer
77
+ query_string_normalizer.call(query)
78
+ else
79
+ HashConversions.to_params(query)
80
+ end
81
+ end
82
+
83
+ def content_body(object)
84
+ if file?(object)
85
+ object = (file = object).read
86
+ file.rewind if file.respond_to?(:rewind)
87
+ end
88
+
89
+ object.to_s
90
+ end
91
+
92
+ def content_type(object)
93
+ return object.content_type if object.respond_to?(:content_type)
94
+ mime = MiniMime.lookup_by_filename(object.path)
95
+ mime ? mime.content_type : 'application/octet-stream'
96
+ end
97
+
98
+ def file_name(object)
99
+ object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
100
+ end
101
+
102
+ attr_reader :params, :query_string_normalizer, :force_multipart
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module HTTParty
6
+ class Request
7
+ class MultipartBoundary
8
+ def self.generate
9
+ "------------------------#{SecureRandom.urlsafe_base64(12)}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module HTTParty
@@ -13,6 +15,8 @@ module HTTParty
13
15
  Net::HTTP::Move,
14
16
  Net::HTTP::Copy,
15
17
  Net::HTTP::Mkcol,
18
+ Net::HTTP::Lock,
19
+ Net::HTTP::Unlock,
16
20
  ]
17
21
 
18
22
  SupportedURISchemes = ['http', 'https', 'webcal', nil]
@@ -42,6 +46,15 @@ module HTTParty
42
46
  end.flatten.join('&')
43
47
  end
44
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
+
45
58
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
46
59
  attr_reader :path
47
60
 
@@ -69,7 +82,7 @@ module HTTParty
69
82
  @path = if uri.is_a?(uri_adapter)
70
83
  uri
71
84
  elsif String.try_convert(uri)
72
- uri_adapter.parse uri
85
+ uri_adapter.parse(uri).normalize
73
86
  else
74
87
  raise ArgumentError,
75
88
  "bad argument (expected #{uri_adapter} object or URI string)"
@@ -85,17 +98,17 @@ module HTTParty
85
98
  end
86
99
 
87
100
  def uri
88
- if redirect && path.relative? && path.path[0] != "/"
89
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
101
+ if redirect && path.relative? && path.path[0] != '/'
102
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
90
103
 
91
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
92
- 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}"
93
106
  end
94
107
 
95
108
  if path.relative? && path.host
96
- new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
109
+ new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
97
110
  elsif path.relative?
98
- new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
111
+ new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
99
112
  else
100
113
  new_uri = path.clone
101
114
  end
@@ -115,7 +128,7 @@ module HTTParty
115
128
  def base_uri
116
129
  if redirect
117
130
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
118
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
131
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
119
132
  base_uri
120
133
  else
121
134
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -138,21 +151,22 @@ module HTTParty
138
151
  validate
139
152
  setup_raw_request
140
153
  chunked_body = nil
154
+ current_http = http
141
155
 
142
- self.last_response = http.request(@raw_request) do |http_response|
156
+ self.last_response = current_http.request(@raw_request) do |http_response|
143
157
  if block
144
158
  chunks = []
145
159
 
146
160
  http_response.read_body do |fragment|
147
- chunks << fragment unless options[:stream_body]
148
- 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)
149
164
  end
150
165
 
151
166
  chunked_body = chunks.join
152
167
  end
153
168
  end
154
169
 
155
-
156
170
  handle_host_redirection if response_redirects?
157
171
  result = handle_unauthorized
158
172
  result ||= handle_response(chunked_body, &block)
@@ -170,16 +184,19 @@ module HTTParty
170
184
  @raw_request.body
171
185
  end
172
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
+
173
194
  private
174
195
 
175
196
  def http
176
197
  connection_adapter.call(uri, options)
177
198
  end
178
199
 
179
- def body
180
- options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
181
- end
182
-
183
200
  def credentials
184
201
  (options[:basic_auth] || options[:digest_auth]).to_hash
185
202
  end
@@ -205,21 +222,31 @@ module HTTParty
205
222
  end
206
223
 
207
224
  def setup_raw_request
208
- @raw_request = http_method.new(request_uri(uri))
209
- @raw_request.body = body if body
210
- @raw_request.body_stream = options[:body_stream] if options[:body_stream]
211
225
  if options[:headers].respond_to?(:to_hash)
212
226
  headers_hash = options[:headers].to_hash
227
+ else
228
+ headers_hash = nil
229
+ end
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
+
234
+ if options[:body]
235
+ body = Body.new(
236
+ options[:body],
237
+ query_string_normalizer: query_string_normalizer,
238
+ force_multipart: options[:multipart]
239
+ )
213
240
 
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']
241
+ if body.multipart?
242
+ content_type = "multipart/form-data; boundary=#{body.boundary}"
243
+ @raw_request['Content-Type'] = content_type
221
244
  end
245
+ @raw_request.body = body.call
222
246
  end
247
+
248
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
249
+
223
250
  if options[:basic_auth] && send_authorization_header?
224
251
  @raw_request.basic_auth(username, password)
225
252
  @credentials_sent = true
@@ -231,6 +258,10 @@ module HTTParty
231
258
  !!options[:digest_auth]
232
259
  end
233
260
 
261
+ def decompress_content?
262
+ !options[:skip_decompression]
263
+ end
264
+
234
265
  def response_unauthorized?
235
266
  !!last_response && last_response.code == '401'
236
267
  end
@@ -254,77 +285,15 @@ module HTTParty
254
285
  query_string_parts << options[:query] unless options[:query].nil?
255
286
  end
256
287
 
257
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
288
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
258
289
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
259
290
  end
260
291
 
261
- def get_charset
262
- content_type = last_response["content-type"]
263
- if content_type.nil?
264
- return nil
265
- end
266
-
267
- if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
268
- return $1
269
- end
270
-
271
- if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
272
- return $1.gsub(/\\(.)/, '\1')
273
- end
274
-
275
- nil
276
- end
277
-
278
- def encode_with_ruby_encoding(body, charset)
279
- encoding = Encoding.find(charset)
280
- body.force_encoding(charset)
281
- rescue ArgumentError
282
- body
283
- end
284
-
285
292
  def assume_utf16_is_big_endian
286
293
  options[:assume_utf16_is_big_endian]
287
294
  end
288
295
 
289
- def encode_utf_16(body)
290
- if body.bytesize >= 2
291
- if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
292
- return body.force_encoding("UTF-16LE")
293
- elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
294
- return body.force_encoding("UTF-16BE")
295
- end
296
- end
297
-
298
- if assume_utf16_is_big_endian
299
- body.force_encoding("UTF-16BE")
300
- else
301
- body.force_encoding("UTF-16LE")
302
- end
303
- end
304
-
305
- def _encode_body(body)
306
- charset = get_charset
307
-
308
- if charset.nil?
309
- return body
310
- end
311
-
312
- if "utf-16".casecmp(charset) == 0
313
- encode_utf_16(body)
314
- else
315
- encode_with_ruby_encoding(body, charset)
316
- end
317
- end
318
-
319
- def encode_body(body)
320
- if "".respond_to?(:encoding)
321
- _encode_body(body)
322
- else
323
- body
324
- end
325
- end
326
-
327
- def handle_response(body, &block)
296
+ def handle_response(raw_body, &block)
328
297
  if response_redirects?
329
298
  options[:limit] -= 1
330
299
  if options[:logger]
@@ -345,15 +314,26 @@ module HTTParty
345
314
  capture_cookies(last_response)
346
315
  perform(&block)
347
316
  else
348
- body ||= last_response.body
349
- body = body.nil? ? body : encode_body(body)
350
- Response.new(self, last_response, lambda { parse_response(body) }, body: body)
317
+ raw_body ||= last_response.body
318
+
319
+ body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
320
+
321
+ unless body.nil?
322
+ body = encode_text(body, last_response['content-type'])
323
+
324
+ if decompress_content?
325
+ last_response.delete('content-encoding')
326
+ raw_body = body
327
+ end
328
+ end
329
+
330
+ Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
351
331
  end
352
332
  end
353
333
 
354
334
  def handle_host_redirection
355
335
  check_duplicate_location_header
356
- redirect_path = options[:uri_adapter].parse last_response['location']
336
+ redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
357
337
  return if redirect_path.relative? || path.host == redirect_path.host
358
338
  @changed_hosts = true
359
339
  end
@@ -422,5 +402,17 @@ module HTTParty
422
402
  @credentials_sent = true
423
403
  end
424
404
  end
405
+
406
+ def decompress(body, encoding)
407
+ Decompressor.new(body, encoding).decompress
408
+ end
409
+
410
+ def encode_text(text, content_type)
411
+ TextEncoder.new(
412
+ text,
413
+ content_type: content_type,
414
+ assume_utf16_is_big_endian: assume_utf16_is_big_endian
415
+ ).call
416
+ end
425
417
  end
426
418
  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