httparty 0.16.4 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httparty might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8ea06db7242bc4bea719ac28217cb7a03643277
4
- data.tar.gz: 3341968702712ab91fc1348f000337afe1bf4047
3
+ metadata.gz: 3e555084c3fc85098b7da66339a47958b2383014
4
+ data.tar.gz: 937364fd6bd11140590f15beb4b324fa89eef30c
5
5
  SHA512:
6
- metadata.gz: bfd0717ef9adb117bc7568a35648b0bc295d0f92210696a423c268be80b7ef12d06d84d2d94ed33a24d31367fd03c3fae3cabc6afbc5cc5b0446f15f01feff79
7
- data.tar.gz: 853d4aa140b4717e36ae0baeeb6c1ba113567242398abd56a944bcf29543f74e3a0a3430c0f8823b9fe9c00d8a4bdb2c3a3ff53c59cf9263e080ac7002013f4e
6
+ metadata.gz: 0f0a8e29081a7c99e7ebebbe885e717326cbd45930ed096c88d953c7ef0a53e16eec6cdf6f273eaa6dd83e1a2eb42c861e7917c81e267e3085cc709e42fa4506
7
+ data.tar.gz: 1fcbb78cf1e3b7ede28e10f8de4ac40f34da5d1ec241dbfac9ff432e12f859ffd50457e469c39c3d4351f0a9997e916a1e09cb322fbe363b5e6df1e4ef7284f0
@@ -6,6 +6,6 @@ rvm:
6
6
  - 2.3.8
7
7
  - 2.4.5
8
8
  - 2.5.3
9
- - 2.6.0
9
+ - 2.6.1
10
10
  bundler_args: --without development
11
11
  before_install: gem install bundler -v '< 2'
@@ -1,3 +1,9 @@
1
+ ## 0.17.0
2
+
3
+ * [Fix encoding of streamed chunk](https://github.com/jnunemaker/httparty/pull/644)
4
+ * [Avoid modifying frozen strings](https://github.com/jnunemaker/httparty/pull/649)
5
+ * [Expose .connection on fragment block param](https://github.com/jnunemaker/httparty/pull/648)
6
+ * [Add support for `Net::HTTP#write_timeout` method (Ruby 2.6.0)](https://github.com/jnunemaker/httparty/pull/647)
1
7
 
2
8
  ## 0.16.4
3
9
  * [Add support for Ruby 2.6](https://github.com/jnunemaker/httparty/pull/636)
data/Gemfile CHANGED
@@ -17,3 +17,7 @@ group :test do
17
17
  gem 'cucumber', '~> 2.3'
18
18
  gem 'webmock'
19
19
  end
20
+
21
+ group :development, :test do
22
+ gem 'pry'
23
+ end
@@ -9,7 +9,7 @@ Makes http fun again!
9
9
  ## Parsing JSON
10
10
  If the response Content Type is `application/json`, HTTParty will parse the response and return Ruby objects such as a hash or array. The default behavior for parsing JSON will return keys as strings. This can be supressed with the `format` option. To get hash keys as symbols:
11
11
 
12
- ```
12
+ ```ruby
13
13
  response = HTTParty.get('http://example.com', format: :plain)
14
14
  JSON.parse response, symbolize_names: true
15
15
  ```
@@ -103,4 +103,4 @@ class Client
103
103
  # get("resources", verify_peer: false)
104
104
  end
105
105
  end
106
- ```
106
+ ```
@@ -81,3 +81,6 @@
81
81
 
82
82
  * [Uploading File](body_stream.rb)
83
83
  * Uses `body_stream` to upload file
84
+
85
+ * [Accessing x509 Peer Certificate](peer_cert.rb)
86
+ * Provides access to the server's TLS certificate
@@ -0,0 +1,9 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+
4
+ peer_cert = nil
5
+ HTTParty.get("https://www.example.com") do |fragment|
6
+ peer_cert ||= fragment.connection.peer_cert
7
+ end
8
+
9
+ puts "The server's certificate expires #{peer_cert.not_after}"
@@ -14,6 +14,9 @@ require 'httparty/net_digest_auth'
14
14
  require 'httparty/version'
15
15
  require 'httparty/connection_adapter'
16
16
  require 'httparty/logger/logger'
17
+ require 'httparty/request/body'
18
+ require 'httparty/response_fragment'
19
+ require 'httparty/text_encoder'
17
20
 
18
21
  # @see HTTParty::ClassMethods
19
22
  module HTTParty
@@ -170,9 +173,9 @@ module HTTParty
170
173
  # include HTTParty
171
174
  # default_timeout 10
172
175
  # end
173
- def default_timeout(t)
174
- raise ArgumentError, 'Timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
175
- default_options[:timeout] = t
176
+ def default_timeout(value)
177
+ validate_timeout_argument(__method__, value)
178
+ default_options[:timeout] = value
176
179
  end
177
180
 
178
181
  # Allows setting a default open_timeout for all HTTP calls in seconds
@@ -181,9 +184,9 @@ module HTTParty
181
184
  # include HTTParty
182
185
  # open_timeout 10
183
186
  # end
184
- def open_timeout(t)
185
- raise ArgumentError, 'open_timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
186
- default_options[:open_timeout] = t
187
+ def open_timeout(value)
188
+ validate_timeout_argument(__method__, value)
189
+ default_options[:open_timeout] = value
187
190
  end
188
191
 
189
192
  # Allows setting a default read_timeout for all HTTP calls in seconds
@@ -192,11 +195,24 @@ module HTTParty
192
195
  # include HTTParty
193
196
  # read_timeout 10
194
197
  # end
195
- def read_timeout(t)
196
- raise ArgumentError, 'read_timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
197
- default_options[:read_timeout] = t
198
+ def read_timeout(value)
199
+ validate_timeout_argument(__method__, value)
200
+ default_options[:read_timeout] = value
198
201
  end
199
202
 
203
+ # Allows setting a default write_timeout for all HTTP calls in seconds
204
+ # Supported by Ruby > 2.6.0
205
+ #
206
+ # class Foo
207
+ # include HTTParty
208
+ # write_timeout 10
209
+ # end
210
+ def write_timeout(value)
211
+ validate_timeout_argument(__method__, value)
212
+ default_options[:write_timeout] = value
213
+ end
214
+
215
+
200
216
  # Set an output stream for debugging, defaults to $stderr.
201
217
  # The output stream is passed on to Net::HTTP#set_debug_output.
202
218
  #
@@ -560,6 +576,10 @@ module HTTParty
560
576
 
561
577
  private
562
578
 
579
+ def validate_timeout_argument(timeout_type, value)
580
+ raise ArgumentError, "#{ timeout_type } must be an integer or float" unless value && (value.is_a?(Integer) || value.is_a?(Float))
581
+ end
582
+
563
583
  def ensure_method_maintained_across_redirects(options)
564
584
  unless options.key?(:maintain_method_across_redirects)
565
585
  options[:maintain_method_across_redirects] = true
@@ -42,6 +42,7 @@ module HTTParty
42
42
  # * :+timeout+: timeout in seconds
43
43
  # * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
44
44
  # * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
45
+ # * :+write_timeout+: http connection write_timeout in seconds, overrides timeout if set (Ruby >= 2.6.0 required)
45
46
  # * :+debug_output+: see HTTParty::ClassMethods.debug_output.
46
47
  # * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
47
48
  # * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates'
@@ -113,19 +114,29 @@ module HTTParty
113
114
 
114
115
  attach_ssl_certificates(http, options)
115
116
 
116
- if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
117
+ if add_timeout?(options[:timeout])
117
118
  http.open_timeout = options[:timeout]
118
119
  http.read_timeout = options[:timeout]
120
+
121
+ from_ruby_version('2.6.0', option: :write_timeout, warn: false) do
122
+ http.write_timeout = options[:timeout]
123
+ end
119
124
  end
120
125
 
121
- if options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
126
+ if add_timeout?(options[:read_timeout])
122
127
  http.read_timeout = options[:read_timeout]
123
128
  end
124
129
 
125
- if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
130
+ if add_timeout?(options[:open_timeout])
126
131
  http.open_timeout = options[:open_timeout]
127
132
  end
128
133
 
134
+ if add_timeout?(options[:write_timeout])
135
+ from_ruby_version('2.6.0', option: :write_timeout) do
136
+ http.write_timeout = options[:write_timeout]
137
+ end
138
+ end
139
+
129
140
  if options[:debug_output]
130
141
  http.set_debug_output(options[:debug_output])
131
142
  end
@@ -138,18 +149,14 @@ module HTTParty
138
149
  #
139
150
  # @see https://bugs.ruby-lang.org/issues/6617
140
151
  if options[:local_host]
141
- if RUBY_VERSION >= "2.0.0"
152
+ from_ruby_version('2.0.0', option: :local_host) do
142
153
  http.local_host = options[:local_host]
143
- else
144
- Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
145
154
  end
146
155
  end
147
156
 
148
157
  if options[:local_port]
149
- if RUBY_VERSION >= "2.0.0"
158
+ from_ruby_version('2.0.0', option: :local_port) do
150
159
  http.local_port = options[:local_port]
151
- else
152
- Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
153
160
  end
154
161
  end
155
162
 
@@ -158,6 +165,18 @@ module HTTParty
158
165
 
159
166
  private
160
167
 
168
+ def from_ruby_version(ruby_version, option: nil, warn: true)
169
+ if RUBY_VERSION >= ruby_version
170
+ yield
171
+ elsif warn
172
+ Kernel.warn("Warning: option #{ option } requires Ruby version #{ ruby_version } or later")
173
+ end
174
+ end
175
+
176
+ def add_timeout?(timeout)
177
+ timeout && (timeout.is_a?(Integer) || timeout.is_a?(Float))
178
+ end
179
+
161
180
  def clean_host(host)
162
181
  strip_ipv6_brackets(host)
163
182
  end
@@ -1,6 +1,4 @@
1
1
  require 'erb'
2
- require 'httparty/request/body'
3
- require 'httparty/fragment_with_response'
4
2
 
5
3
  module HTTParty
6
4
  class Request #:nodoc:
@@ -142,21 +140,22 @@ module HTTParty
142
140
  validate
143
141
  setup_raw_request
144
142
  chunked_body = nil
143
+ current_http = http
145
144
 
146
- self.last_response = http.request(@raw_request) do |http_response|
145
+ self.last_response = current_http.request(@raw_request) do |http_response|
147
146
  if block
148
147
  chunks = []
149
148
 
150
149
  http_response.read_body do |fragment|
151
- chunks << fragment unless options[:stream_body]
152
- block.call FragmentWithResponse.new(fragment, http_response)
150
+ encoded_fragment = encode_text(fragment, http_response['content-type'])
151
+ chunks << encoded_fragment if !options[:stream_body]
152
+ block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
153
153
  end
154
154
 
155
155
  chunked_body = chunks.join
156
156
  end
157
157
  end
158
158
 
159
-
160
159
  handle_host_redirection if response_redirects?
161
160
  result = handle_unauthorized
162
161
  result ||= handle_response(chunked_body, &block)
@@ -273,74 +272,10 @@ module HTTParty
273
272
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
274
273
  end
275
274
 
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
275
  def assume_utf16_is_big_endian
303
276
  options[:assume_utf16_is_big_endian]
304
277
  end
305
278
 
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
279
  def handle_response(body, &block)
345
280
  if response_redirects?
346
281
  options[:limit] -= 1
@@ -363,7 +298,7 @@ module HTTParty
363
298
  perform(&block)
364
299
  else
365
300
  body ||= last_response.body
366
- body = body.nil? ? body : encode_body(body)
301
+ body = body.nil? ? body : encode_text(body, last_response['content-type'])
367
302
  Response.new(self, last_response, lambda { parse_response(body) }, body: body)
368
303
  end
369
304
  end
@@ -439,5 +374,13 @@ module HTTParty
439
374
  @credentials_sent = true
440
375
  end
441
376
  end
377
+
378
+ def encode_text(text, content_type)
379
+ TextEncoder.new(
380
+ text,
381
+ content_type: content_type,
382
+ assume_utf16_is_big_endian: assume_utf16_is_big_endian
383
+ ).call
384
+ end
442
385
  end
443
386
  end
@@ -39,6 +39,10 @@ module HTTParty
39
39
  response.code.to_i
40
40
  end
41
41
 
42
+ def http_version
43
+ response.http_version
44
+ end
45
+
42
46
  def tap
43
47
  yield self
44
48
  self
@@ -2,18 +2,17 @@ require 'delegate'
2
2
 
3
3
  module HTTParty
4
4
  # Allow access to http_response and code by delegation on fragment
5
- class FragmentWithResponse < SimpleDelegator
6
- extend Forwardable
7
-
8
- attr_reader :http_response
5
+ class ResponseFragment < SimpleDelegator
6
+ attr_reader :http_response, :connection
9
7
 
10
8
  def code
11
9
  @http_response.code.to_i
12
10
  end
13
11
 
14
- def initialize(fragment, http_response)
12
+ def initialize(fragment, http_response, connection)
15
13
  @fragment = fragment
16
14
  @http_response = http_response
15
+ @connection = connection
17
16
  super fragment
18
17
  end
19
18
  end
@@ -0,0 +1,70 @@
1
+ module HTTParty
2
+ class TextEncoder
3
+ attr_reader :text, :content_type, :assume_utf16_is_big_endian
4
+
5
+ def initialize(text, assume_utf16_is_big_endian: true, content_type: nil)
6
+ @text = text.dup
7
+ @content_type = content_type
8
+ @assume_utf16_is_big_endian = assume_utf16_is_big_endian
9
+ end
10
+
11
+ def call
12
+ if can_encode?
13
+ encoded_text
14
+ else
15
+ text
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def can_encode?
22
+ ''.respond_to?(:encoding) && charset
23
+ end
24
+
25
+ def encoded_text
26
+ if 'utf-16'.casecmp(charset) == 0
27
+ encode_utf_16
28
+ else
29
+ encode_with_ruby_encoding
30
+ end
31
+ end
32
+
33
+ def encode_utf_16
34
+ if text.bytesize >= 2
35
+ if text.getbyte(0) == 0xFF && text.getbyte(1) == 0xFE
36
+ return text.force_encoding("UTF-16LE")
37
+ elsif text.getbyte(0) == 0xFE && text.getbyte(1) == 0xFF
38
+ return text.force_encoding("UTF-16BE")
39
+ end
40
+ end
41
+
42
+ if assume_utf16_is_big_endian # option
43
+ text.force_encoding("UTF-16BE")
44
+ else
45
+ text.force_encoding("UTF-16LE")
46
+ end
47
+ end
48
+
49
+ def encode_with_ruby_encoding
50
+ # NOTE: This will raise an argument error if the
51
+ # charset does not exist
52
+ encoding = Encoding.find(charset)
53
+ text.force_encoding(encoding.to_s)
54
+ rescue ArgumentError
55
+ text
56
+ end
57
+
58
+ def charset
59
+ return nil if content_type.nil?
60
+
61
+ if (matchdata = content_type.match(/;\s*charset\s*=\s*([^=,;"\s]+)/i))
62
+ return matchdata.captures.first
63
+ end
64
+
65
+ if (matchdata = content_type.match(/;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i))
66
+ return matchdata.captures.first.gsub(/\\(.)/, '\1')
67
+ end
68
+ end
69
+ end
70
+ end