httpclient 2.4.0 → 2.5.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.
data/lib/httpclient.rb CHANGED
@@ -870,8 +870,11 @@ private
870
870
 
871
871
  class KeepAliveDisconnected < StandardError # :nodoc:
872
872
  attr_reader :sess
873
- def initialize(sess = nil)
873
+ attr_reader :cause
874
+ def initialize(sess = nil, cause = nil)
875
+ super("#{self.class.name}: #{cause ? cause.message : nil}")
874
876
  @sess = sess
877
+ @cause = cause
875
878
  end
876
879
  end
877
880
 
@@ -13,27 +13,9 @@ require 'mutex_m'
13
13
 
14
14
  class HTTPClient
15
15
 
16
- begin
17
- require 'net/ntlm'
18
- NTLMEnabled = true
19
- rescue LoadError
20
- NTLMEnabled = false
21
- end
22
-
23
- begin
24
- require 'win32/sspi'
25
- SSPIEnabled = true
26
- rescue LoadError
27
- SSPIEnabled = false
28
- end
29
-
30
- begin
31
- require 'gssapi'
32
- GSSAPIEnabled = true
33
- rescue LoadError
34
- GSSAPIEnabled = false
35
- end
36
-
16
+ NTLMEnabled = false
17
+ SSPIEnabled = false
18
+ GSSAPIEnabled = false
37
19
 
38
20
  # Common abstract class for authentication filter.
39
21
  #
@@ -239,7 +221,6 @@ class HTTPClient
239
221
  def initialize
240
222
  super
241
223
  @cred = nil
242
- @set = false
243
224
  @auth = {}
244
225
  @challenge = {}
245
226
  @scheme = "Basic"
@@ -263,15 +244,12 @@ class HTTPClient
263
244
  uri = Util.uri_dirname(uri)
264
245
  @auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
265
246
  end
266
- @set = true
267
247
  end
268
248
  end
269
249
 
270
250
  # have we marked this as set - ie that it's valid to use in this context?
271
251
  def set?
272
- synchronize {
273
- @set == true
274
- }
252
+ @cred || @auth.any?
275
253
  end
276
254
 
277
255
  # Response handler: returns credential.
@@ -305,12 +283,10 @@ class HTTPClient
305
283
  def set(uri, user, passwd)
306
284
  synchronize do
307
285
  @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
308
- @set = true
309
286
  end
310
287
  end
311
288
 
312
289
  def get(req)
313
- target_uri = req.header.request_uri
314
290
  synchronize {
315
291
  return nil unless @challenge['challenged']
316
292
  @cred
@@ -339,7 +315,6 @@ class HTTPClient
339
315
  super
340
316
  @auth = {}
341
317
  @challenge = {}
342
- @set = false
343
318
  @nonce_count = 0
344
319
  @scheme = "Digest"
345
320
  end
@@ -360,15 +335,12 @@ class HTTPClient
360
335
  uri = Util.uri_dirname(uri)
361
336
  @auth[uri] = [user, passwd]
362
337
  end
363
- @set = true
364
338
  end
365
339
  end
366
340
 
367
341
  # have we marked this as set - ie that it's valid to use in this context?
368
342
  def set?
369
- synchronize {
370
- @set == true
371
- }
343
+ @auth.any?
372
344
  end
373
345
 
374
346
  # Response handler: returns credential.
@@ -478,7 +450,6 @@ class HTTPClient
478
450
  # overrides DigestAuth#set. sets default user name and password. uri is not used.
479
451
  def set(uri, user, passwd)
480
452
  synchronize do
481
- @set = true
482
453
  @auth = [user, passwd]
483
454
  end
484
455
  end
@@ -487,7 +458,6 @@ class HTTPClient
487
458
  # regardless of target uri if the proxy has required authentication
488
459
  # before
489
460
  def get(req)
490
- target_uri = req.header.request_uri
491
461
  synchronize {
492
462
  param = @challenge
493
463
  return nil unless param
@@ -531,7 +501,6 @@ class HTTPClient
531
501
  @auth_default = nil
532
502
  @challenge = {}
533
503
  @scheme = scheme
534
- @set = false
535
504
  @ntlm_opt = {
536
505
  :ntlmv2 => true
537
506
  }
@@ -555,21 +524,17 @@ class HTTPClient
555
524
  else
556
525
  @auth_default = [user, passwd]
557
526
  end
558
- @set = true
559
527
  end
560
528
  end
561
529
 
562
530
  # have we marked this as set - ie that it's valid to use in this context?
563
531
  def set?
564
- synchronize {
565
- @set == true
566
- }
532
+ @auth_default || @auth.any?
567
533
  end
568
534
 
569
535
  # Response handler: returns credential.
570
536
  # See ruby/ntlm for negotiation state transition.
571
537
  def get(req)
572
- return nil unless NTLMEnabled
573
538
  target_uri = req.header.request_uri
574
539
  synchronize {
575
540
  domain_uri, param = @challenge.find { |uri, v|
@@ -583,6 +548,7 @@ class HTTPClient
583
548
  user, passwd = @auth_default
584
549
  end
585
550
  return nil unless user
551
+ Util.try_require('net/ntlm') || return
586
552
  domain = nil
587
553
  domain, user = user.split("\\") if user.index("\\")
588
554
  state = param[:state]
@@ -606,7 +572,6 @@ class HTTPClient
606
572
 
607
573
  # Challenge handler: remember URL and challenge token for response.
608
574
  def challenge(uri, param_str)
609
- return false unless NTLMEnabled
610
575
  synchronize {
611
576
  if param_str.nil? or @challenge[uri].nil?
612
577
  c = @challenge[uri] = {}
@@ -655,27 +620,27 @@ class HTTPClient
655
620
  # not supported
656
621
  end
657
622
 
658
- # have we marked this as set - ie that it's valid to use in this context?
623
+ # Check always (not effective but it works)
659
624
  def set?
660
- SSPIEnabled || GSSAPIEnabled
625
+ !@challenge.empty?
661
626
  end
662
627
 
663
628
  # Response handler: returns credential.
664
629
  # See win32/sspi for negotiation state transition.
665
630
  def get(req)
666
- return nil unless SSPIEnabled || GSSAPIEnabled
667
631
  target_uri = req.header.request_uri
668
632
  synchronize {
669
633
  domain_uri, param = @challenge.find { |uri, v|
670
634
  Util.uri_part_of(target_uri, uri)
671
635
  }
672
636
  return nil unless param
637
+ Util.try_require('win32/sspi') || Util.try_require('gssapi') || return
673
638
  state = param[:state]
674
639
  authenticator = param[:authenticator]
675
640
  authphrase = param[:authphrase]
676
641
  case state
677
642
  when :init
678
- if SSPIEnabled
643
+ if defined?(Win32::SSPI)
679
644
  authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
680
645
  return authenticator.get_initial_token(@scheme)
681
646
  else # use GSSAPI
@@ -685,7 +650,7 @@ class HTTPClient
685
650
  end
686
651
  when :response
687
652
  @challenge.delete(domain_uri)
688
- if SSPIEnabled
653
+ if defined?(Win32::SSPI)
689
654
  return authenticator.complete_authentication(authphrase)
690
655
  else # use GSSAPI
691
656
  return authenticator.init_context(authphrase.unpack('m').pop)
@@ -697,7 +662,6 @@ class HTTPClient
697
662
 
698
663
  # Challenge handler: remember URL and challenge token for response.
699
664
  def challenge(uri, param_str)
700
- return false unless SSPIEnabled || GSSAPIEnabled
701
665
  synchronize {
702
666
  if param_str.nil? or @challenge[uri].nil?
703
667
  c = @challenge[uri] = {}
@@ -825,9 +789,9 @@ class HTTPClient
825
789
  # not supported
826
790
  end
827
791
 
828
- # have we marked this as set - ie that it's valid to use in this context?
792
+ # Check always (not effective but it works)
829
793
  def set?
830
- true
794
+ !@challenge.empty?
831
795
  end
832
796
 
833
797
  # Set authentication credential.
@@ -844,11 +844,11 @@ module HTTP
844
844
  query.each { |attr, value|
845
845
  left = escape(attr.to_s) << '='
846
846
  if values = Array.try_convert(value)
847
- values.each { |value|
848
- if value.respond_to?(:read)
849
- value = value.read
847
+ values.each { |v|
848
+ if v.respond_to?(:read)
849
+ v = v.read
850
850
  end
851
- pairs.push(left + escape(value.to_s))
851
+ pairs.push(left + escape(v.to_s))
852
852
  }
853
853
  else
854
854
  if value.respond_to?(:read)
@@ -619,14 +619,14 @@ class HTTPClient
619
619
  rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
620
620
  # JRuby can raise IOError instead of ECONNRESET for now
621
621
  close
622
- raise KeepAliveDisconnected.new(self)
622
+ raise KeepAliveDisconnected.new(self, $!)
623
623
  rescue HTTPClient::TimeoutError
624
624
  close
625
625
  raise
626
626
  rescue
627
627
  close
628
628
  if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
629
- raise KeepAliveDisconnected.new(self)
629
+ raise KeepAliveDisconnected.new(self, $!)
630
630
  else
631
631
  raise
632
632
  end
@@ -886,7 +886,7 @@ class HTTPClient
886
886
  rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
887
887
  # JRuby can raise IOError instead of ECONNRESET for now
888
888
  close
889
- raise KeepAliveDisconnected.new(self)
889
+ raise KeepAliveDisconnected.new(self, $!)
890
890
  end
891
891
  if StatusParseRegexp !~ initial_line
892
892
  @version = '0.9'
@@ -90,7 +90,12 @@ class HTTPClient
90
90
  @dest = nil
91
91
  @timeout = nil
92
92
  @ssl_version = :auto
93
- @options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
93
+ # Follow ruby-ossl's definition
94
+ @options = OpenSSL::SSL::OP_ALL
95
+ @options &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
96
+ @options |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
97
+ @options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
98
+ @options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
94
99
  # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
95
100
  @ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
96
101
  @cacerts_loaded = false
@@ -164,6 +164,18 @@ class HTTPClient
164
164
  end
165
165
  module_function :hash_find_value
166
166
 
167
+ # Try to require a feature and returns true/false if loaded
168
+ #
169
+ # It returns 'true' for the second require in contrast of the standard
170
+ # require returns false if the feature is already loaded.
171
+ def try_require(feature)
172
+ require feature
173
+ true
174
+ rescue LoadError
175
+ false
176
+ end
177
+ module_function :try_require
178
+
167
179
  # Checks if the given URI is https.
168
180
  def https?(uri)
169
181
  uri.scheme && uri.scheme.downcase == 'https'
@@ -1,3 +1,3 @@
1
1
  class HTTPClient
2
- VERSION = '2.4.0'
2
+ VERSION = '2.5.0'
3
3
  end
data/sample/github.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'httpclient'
2
+
3
+ # Obtain Personal access token from https://github.com/settings/applications
4
+ personal_access_token = '03d1c4bc880e51c06215cc77eb075a7856e6a9c7'
5
+
6
+ h = HTTPClient.new
7
+ h.set_auth(nil, personal_access_token, 'x-oauth-basic')
8
+ h.www_auth.basic_auth.challenge('https://api.github.com/')
9
+ h.debug_dev = STDERR
10
+
11
+ puts h.get_content('https://api.github.com/user/repos?type=private')
data/test/test_auth.rb CHANGED
@@ -150,6 +150,19 @@ class TestAuth < Test::Unit::TestCase
150
150
  end
151
151
  end
152
152
 
153
+ def test_BASIC_auth_nil_uri
154
+ c = HTTPClient.new
155
+ webrick_backup = @basic_auth.instance_eval { @auth_scheme }
156
+ begin
157
+ @basic_auth.instance_eval { @auth_scheme = "BASIC" }
158
+ c.www_auth.basic_auth.instance_eval { @scheme = "BASIC" }
159
+ c.set_auth(nil, 'admin', 'admin')
160
+ assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth"))
161
+ ensure
162
+ @basic_auth.instance_eval { @auth_scheme = webrick_backup }
163
+ end
164
+ end
165
+
153
166
  # To work this test consistently on CRuby you can to add 'Thread.pass' in
154
167
  # @challenge iteration at BasicAuth#get like;
155
168
  #
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'http-access2'
2
3
  require File.expand_path('helper', File.dirname(__FILE__))
3
4
 
@@ -116,7 +117,7 @@ class TestClient < Test::Unit::TestCase
116
117
  setup_proxyserver
117
118
  escape_noproxy do
118
119
  begin
119
- @client.proxy = "http://"
120
+ @client.proxy = "http://あ"
120
121
  rescue
121
122
  assert_match(/InvalidURIError/, $!.class.to_s)
122
123
  end
@@ -176,7 +176,7 @@ class TestHTTPClient < Test::Unit::TestCase
176
176
  setup_proxyserver
177
177
  escape_noproxy do
178
178
  begin
179
- @client.proxy = "http://"
179
+ @client.proxy = "http://あ"
180
180
  rescue
181
181
  assert_match(/InvalidURIError/, $!.class.to_s)
182
182
  end
data/test/test_ssl.rb CHANGED
@@ -33,7 +33,10 @@ class TestSSL < Test::Unit::TestCase
33
33
  assert_equal(OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT, cfg.verify_mode)
34
34
  assert_nil(cfg.verify_callback)
35
35
  assert_nil(cfg.timeout)
36
- assert_equal(OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2, cfg.options)
36
+ expected_options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
37
+ expected_options &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
38
+ expected_options |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
39
+ assert_equal(expected_options, cfg.options)
37
40
  assert_equal("ALL:!aNULL:!eNULL:!SSLv2", cfg.ciphers)
38
41
  assert_instance_of(OpenSSL::X509::Store, cfg.cert_store)
39
42
  end
@@ -166,6 +169,24 @@ end
166
169
  end
167
170
  end
168
171
 
172
+ def test_no_sslv3
173
+ teardown_server
174
+ setup_server_with_ssl_version(:SSLv3)
175
+ assert_raise(OpenSSL::SSL::SSLError) do
176
+ @client.ssl_config.verify_mode = nil
177
+ @client.get("https://localhost:#{serverport}/hello")
178
+ end
179
+ end
180
+
181
+ def test_allow_tlsv1
182
+ teardown_server
183
+ setup_server#_with_ssl_version(:TLSv1)
184
+ assert_nothing_raised do
185
+ @client.ssl_config.verify_mode = nil
186
+ @client.get("https://localhost:#{serverport}/hello")
187
+ end
188
+ end
189
+
169
190
  private
170
191
 
171
192
  def cert(filename)
@@ -207,6 +228,31 @@ private
207
228
  @server_thread = start_server_thread(@server)
208
229
  end
209
230
 
231
+ def setup_server_with_ssl_version(ssl_version)
232
+ logger = Logger.new(STDERR)
233
+ #logger.level = Logger::Severity::FATAL # avoid logging SSLError (ERROR level)
234
+ @server = WEBrick::HTTPServer.new(
235
+ :BindAddress => "localhost",
236
+ :Logger => logger,
237
+ :Port => 0,
238
+ :AccessLog => [],
239
+ :DocumentRoot => DIR,
240
+ :SSLEnable => true,
241
+ :SSLCACertificateFile => File.join(DIR, 'ca.cert'),
242
+ :SSLCertificate => cert('server.cert'),
243
+ :SSLPrivateKey => key('server.key')
244
+ )
245
+ @server.ssl_context.ssl_version = ssl_version
246
+ @serverport = @server.config[:Port]
247
+ [:hello].each do |sym|
248
+ @server.mount(
249
+ "/#{sym}",
250
+ WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
251
+ )
252
+ end
253
+ @server_thread = start_server_thread(@server)
254
+ end
255
+
210
256
  def do_hello(req, res)
211
257
  res['content-type'] = 'text/html'
212
258
  res.body = "hello"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroshi Nakamura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-08 00:00:00.000000000 Z
11
+ date: 2014-10-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: nahi@ruby-lang.org
@@ -41,6 +41,7 @@ files:
41
41
  - sample/auth.rb
42
42
  - sample/cookie.rb
43
43
  - sample/dav.rb
44
+ - sample/github.rb
44
45
  - sample/howto.rb
45
46
  - sample/oauth_buzz.rb
46
47
  - sample/oauth_friendfeed.rb
@@ -84,7 +85,7 @@ files:
84
85
  - test/test_httpclient.rb
85
86
  - test/test_include_client.rb
86
87
  - test/test_ssl.rb
87
- homepage: http://github.com/nahi/httpclient
88
+ homepage: https://github.com/nahi/httpclient
88
89
  licenses:
89
90
  - ruby
90
91
  metadata: {}
@@ -104,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
105
  version: '0'
105
106
  requirements: []
106
107
  rubyforge_project:
107
- rubygems_version: 2.2.2
108
+ rubygems_version: 2.4.1
108
109
  signing_key:
109
110
  specification_version: 4
110
111
  summary: gives something like the functionality of libwww-perl (LWP) in Ruby