httparty 0.15.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 (93) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/workflows/ci.yml +23 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop_todo.yml +1 -1
  6. data/Changelog.md +105 -0
  7. data/Gemfile +6 -1
  8. data/README.md +6 -6
  9. data/docs/README.md +100 -38
  10. data/examples/README.md +28 -11
  11. data/examples/aaws.rb +6 -2
  12. data/examples/body_stream.rb +14 -0
  13. data/examples/custom_parsers.rb +4 -0
  14. data/examples/headers_and_user_agents.rb +7 -3
  15. data/examples/idn.rb +10 -0
  16. data/examples/logging.rb +3 -3
  17. data/examples/microsoft_graph.rb +52 -0
  18. data/examples/multipart.rb +22 -0
  19. data/examples/peer_cert.rb +9 -0
  20. data/examples/stream_download.rb +8 -2
  21. data/httparty.gemspec +3 -3
  22. data/lib/httparty/connection_adapter.rb +59 -16
  23. data/lib/httparty/cookie_hash.rb +10 -8
  24. data/lib/httparty/decompressor.rb +92 -0
  25. data/lib/httparty/exceptions.rb +4 -1
  26. data/lib/httparty/hash_conversions.rb +28 -12
  27. data/lib/httparty/headers_processor.rb +32 -0
  28. data/lib/httparty/logger/apache_formatter.rb +31 -6
  29. data/lib/httparty/logger/curl_formatter.rb +9 -7
  30. data/lib/httparty/logger/logger.rb +5 -1
  31. data/lib/httparty/logger/logstash_formatter.rb +61 -0
  32. data/lib/httparty/module_inheritable_attributes.rb +6 -4
  33. data/lib/httparty/net_digest_auth.rb +15 -15
  34. data/lib/httparty/parser.rb +22 -16
  35. data/lib/httparty/request/body.rb +98 -0
  36. data/lib/httparty/request/multipart_boundary.rb +13 -0
  37. data/lib/httparty/request.rb +82 -95
  38. data/lib/httparty/response/headers.rb +4 -2
  39. data/lib/httparty/response.rb +59 -8
  40. data/lib/httparty/response_fragment.rb +21 -0
  41. data/lib/httparty/text_encoder.rb +72 -0
  42. data/lib/httparty/utils.rb +13 -0
  43. data/lib/httparty/version.rb +3 -1
  44. data/lib/httparty.rb +70 -24
  45. data/website/css/common.css +1 -1
  46. metadata +35 -100
  47. data/.travis.yml +0 -8
  48. data/features/basic_authentication.feature +0 -20
  49. data/features/command_line.feature +0 -95
  50. data/features/deals_with_http_error_codes.feature +0 -26
  51. data/features/digest_authentication.feature +0 -30
  52. data/features/handles_compressed_responses.feature +0 -27
  53. data/features/handles_multiple_formats.feature +0 -57
  54. data/features/steps/env.rb +0 -27
  55. data/features/steps/httparty_response_steps.rb +0 -56
  56. data/features/steps/httparty_steps.rb +0 -43
  57. data/features/steps/mongrel_helper.rb +0 -127
  58. data/features/steps/remote_service_steps.rb +0 -92
  59. data/features/supports_read_timeout_option.feature +0 -13
  60. data/features/supports_redirection.feature +0 -22
  61. data/features/supports_timeout_option.feature +0 -13
  62. data/spec/fixtures/delicious.xml +0 -23
  63. data/spec/fixtures/empty.xml +0 -0
  64. data/spec/fixtures/google.html +0 -3
  65. data/spec/fixtures/ssl/generate.sh +0 -29
  66. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  67. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  68. data/spec/fixtures/ssl/generated/ca.key +0 -15
  69. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  70. data/spec/fixtures/ssl/generated/server.crt +0 -13
  71. data/spec/fixtures/ssl/generated/server.key +0 -15
  72. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  73. data/spec/fixtures/twitter.csv +0 -2
  74. data/spec/fixtures/twitter.json +0 -1
  75. data/spec/fixtures/twitter.xml +0 -403
  76. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  77. data/spec/httparty/connection_adapter_spec.rb +0 -495
  78. data/spec/httparty/cookie_hash_spec.rb +0 -100
  79. data/spec/httparty/exception_spec.rb +0 -45
  80. data/spec/httparty/hash_conversions_spec.rb +0 -49
  81. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  82. data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
  83. data/spec/httparty/logger/logger_spec.rb +0 -38
  84. data/spec/httparty/net_digest_auth_spec.rb +0 -268
  85. data/spec/httparty/parser_spec.rb +0 -185
  86. data/spec/httparty/request_spec.rb +0 -1251
  87. data/spec/httparty/response_spec.rb +0 -347
  88. data/spec/httparty/ssl_spec.rb +0 -74
  89. data/spec/httparty_spec.rb +0 -877
  90. data/spec/spec_helper.rb +0 -59
  91. data/spec/support/ssl_test_helper.rb +0 -47
  92. data/spec/support/ssl_test_server.rb +0 -80
  93. 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,10 +103,10 @@ 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
- body.gsub!(/\A#{UTF8_BOM}/, '')
109
+ @body = body.gsub(/\A#{UTF8_BOM}/, '')
106
110
  end
107
111
  if supports_format?
108
112
  parse_supported_format
@@ -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,98 @@
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
+ def generate_multipart
36
+ normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
37
+
38
+ multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
39
+ memo << "--#{boundary}#{NEWLINE}"
40
+ memo << %(Content-Disposition: form-data; name="#{key}")
41
+ # value.path is used to support ActionDispatch::Http::UploadedFile
42
+ # https://github.com/jnunemaker/httparty/pull/585
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
49
+ end
50
+
51
+ multipart << "--#{boundary}--#{NEWLINE}"
52
+ end
53
+
54
+ def has_file?(value)
55
+ if value.respond_to?(:to_hash)
56
+ value.to_hash.any? { |_, v| has_file?(v) }
57
+ elsif value.respond_to?(:to_ary)
58
+ value.to_ary.any? { |v| has_file?(v) }
59
+ else
60
+ file?(value)
61
+ end
62
+ end
63
+
64
+ def file?(object)
65
+ object.respond_to?(:path) && object.respond_to?(:read)
66
+ end
67
+
68
+ def normalize_query(query)
69
+ if query_string_normalizer
70
+ query_string_normalizer.call(query)
71
+ else
72
+ HashConversions.to_params(query)
73
+ end
74
+ end
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
+
85
+ def content_type(object)
86
+ return object.content_type if object.respond_to?(:content_type)
87
+ mime = MIME::Types.type_for(object.path)
88
+ mime.empty? ? 'application/octet-stream' : mime[0].content_type
89
+ end
90
+
91
+ def file_name(object)
92
+ object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
93
+ end
94
+
95
+ attr_reader :params, :query_string_normalizer, :force_multipart
96
+ end
97
+ end
98
+ 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,11 @@ module HTTParty
42
46
  end.flatten.join('&')
43
47
  end
44
48
 
49
+ def self._load(data)
50
+ http_method, path, options = Marshal.load(data)
51
+ new(http_method, path, options)
52
+ end
53
+
45
54
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
46
55
  attr_reader :path
47
56
 
@@ -69,7 +78,7 @@ module HTTParty
69
78
  @path = if uri.is_a?(uri_adapter)
70
79
  uri
71
80
  elsif String.try_convert(uri)
72
- uri_adapter.parse uri
81
+ uri_adapter.parse(uri).normalize
73
82
  else
74
83
  raise ArgumentError,
75
84
  "bad argument (expected #{uri_adapter} object or URI string)"
@@ -85,17 +94,17 @@ module HTTParty
85
94
  end
86
95
 
87
96
  def uri
88
- if redirect && path.relative? && path.path[0] != "/"
89
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
97
+ if redirect && path.relative? && path.path[0] != '/'
98
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
90
99
 
91
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
92
- 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}"
93
102
  end
94
103
 
95
104
  if path.relative? && path.host
96
- new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
105
+ new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
97
106
  elsif path.relative?
98
- new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
107
+ new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
99
108
  else
100
109
  new_uri = path.clone
101
110
  end
@@ -115,7 +124,7 @@ module HTTParty
115
124
  def base_uri
116
125
  if redirect
117
126
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
118
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
127
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
119
128
  base_uri
120
129
  else
121
130
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -138,21 +147,22 @@ module HTTParty
138
147
  validate
139
148
  setup_raw_request
140
149
  chunked_body = nil
150
+ current_http = http
141
151
 
142
- self.last_response = http.request(@raw_request) do |http_response|
152
+ self.last_response = current_http.request(@raw_request) do |http_response|
143
153
  if block
144
154
  chunks = []
145
155
 
146
156
  http_response.read_body do |fragment|
147
- chunks << fragment unless options[:stream_body]
148
- block.call(fragment)
157
+ encoded_fragment = encode_text(fragment, http_response['content-type'])
158
+ chunks << encoded_fragment if !options[:stream_body]
159
+ block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
149
160
  end
150
161
 
151
162
  chunked_body = chunks.join
152
163
  end
153
164
  end
154
165
 
155
-
156
166
  handle_host_redirection if response_redirects?
157
167
  result = handle_unauthorized
158
168
  result ||= handle_response(chunked_body, &block)
@@ -170,16 +180,19 @@ module HTTParty
170
180
  @raw_request.body
171
181
  end
172
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
+
173
190
  private
174
191
 
175
192
  def http
176
193
  connection_adapter.call(uri, options)
177
194
  end
178
195
 
179
- def body
180
- options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
181
- end
182
-
183
196
  def credentials
184
197
  (options[:basic_auth] || options[:digest_auth]).to_hash
185
198
  end
@@ -205,21 +218,31 @@ 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 = body if body
210
- @raw_request.body_stream = options[:body_stream] if options[:body_stream]
211
221
  if options[:headers].respond_to?(:to_hash)
212
222
  headers_hash = options[:headers].to_hash
223
+ else
224
+ headers_hash = nil
225
+ end
213
226
 
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']
227
+ @raw_request = http_method.new(request_uri(uri), headers_hash)
228
+ @raw_request.body_stream = options[:body_stream] if options[:body_stream]
229
+
230
+ if options[:body]
231
+ body = Body.new(
232
+ options[:body],
233
+ query_string_normalizer: query_string_normalizer,
234
+ force_multipart: options[:multipart]
235
+ )
236
+
237
+ if body.multipart?
238
+ content_type = "multipart/form-data; boundary=#{body.boundary}"
239
+ @raw_request['Content-Type'] = content_type
221
240
  end
241
+ @raw_request.body = body.call
222
242
  end
243
+
244
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
245
+
223
246
  if options[:basic_auth] && send_authorization_header?
224
247
  @raw_request.basic_auth(username, password)
225
248
  @credentials_sent = true
@@ -231,6 +254,10 @@ module HTTParty
231
254
  !!options[:digest_auth]
232
255
  end
233
256
 
257
+ def decompress_content?
258
+ !options[:skip_decompression]
259
+ end
260
+
234
261
  def response_unauthorized?
235
262
  !!last_response && last_response.code == '401'
236
263
  end
@@ -254,78 +281,15 @@ module HTTParty
254
281
  query_string_parts << options[:query] unless options[:query].nil?
255
282
  end
256
283
 
257
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
284
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
258
285
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
259
286
  end
260
287
 
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
- if !body.nil? && Encoding.name_list.include?(charset)
280
- body.force_encoding(charset)
281
- else
282
- body
283
- end
284
- end
285
-
286
288
  def assume_utf16_is_big_endian
287
289
  options[:assume_utf16_is_big_endian]
288
290
  end
289
291
 
290
- def encode_utf_16(body)
291
- if body.bytesize >= 2
292
- if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
293
- return body.force_encoding("UTF-16LE")
294
- elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
295
- return body.force_encoding("UTF-16BE")
296
- end
297
- end
298
-
299
- if assume_utf16_is_big_endian
300
- body.force_encoding("UTF-16BE")
301
- else
302
- body.force_encoding("UTF-16LE")
303
- end
304
- end
305
-
306
- def _encode_body(body)
307
- charset = get_charset
308
-
309
- if charset.nil?
310
- return body
311
- end
312
-
313
- if "utf-16".casecmp(charset) == 0
314
- encode_utf_16(body)
315
- else
316
- encode_with_ruby_encoding(body, charset)
317
- end
318
- end
319
-
320
- def encode_body(body)
321
- if "".respond_to?(:encoding)
322
- _encode_body(body)
323
- else
324
- body
325
- end
326
- end
327
-
328
- def handle_response(body, &block)
292
+ def handle_response(raw_body, &block)
329
293
  if response_redirects?
330
294
  options[:limit] -= 1
331
295
  if options[:logger]
@@ -346,15 +310,26 @@ module HTTParty
346
310
  capture_cookies(last_response)
347
311
  perform(&block)
348
312
  else
349
- body ||= last_response.body
350
- body = body.nil? ? body : encode_body(body)
351
- 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)
352
327
  end
353
328
  end
354
329
 
355
330
  def handle_host_redirection
356
331
  check_duplicate_location_header
357
- redirect_path = options[:uri_adapter].parse last_response['location']
332
+ redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
358
333
  return if redirect_path.relative? || path.host == redirect_path.host
359
334
  @changed_hosts = true
360
335
  end
@@ -423,5 +398,17 @@ module HTTParty
423
398
  @credentials_sent = true
424
399
  end
425
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
426
413
  end
427
414
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
@@ -22,10 +24,10 @@ module HTTParty
22
24
  end
23
25
 
24
26
  def ==(other)
25
- if other.is_a?(::Net::HTTPHeader)
27
+ if other.is_a?(::Net::HTTPHeader)
26
28
  @header == other.instance_variable_get(:@header)
27
29
  elsif other.is_a?(Hash)
28
- @header == other || @header == Headers.new(other).instance_variable_get(:@header)
30
+ @header == other || @header == Headers.new(other).instance_variable_get(:@header)
29
31
  end
30
32
  end
31
33
  end