httpclient 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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