httpclient 2.3.0.1 → 2.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +85 -0
  3. data/bin/httpclient +18 -6
  4. data/bin/jsonclient +85 -0
  5. data/lib/http-access2.rb +1 -1
  6. data/lib/httpclient.rb +262 -88
  7. data/lib/httpclient/auth.rb +269 -244
  8. data/lib/httpclient/cacert.pem +3952 -0
  9. data/lib/httpclient/cacert1024.pem +3866 -0
  10. data/lib/httpclient/connection.rb +1 -1
  11. data/lib/httpclient/cookie.rb +161 -514
  12. data/lib/httpclient/http.rb +57 -21
  13. data/lib/httpclient/include_client.rb +2 -0
  14. data/lib/httpclient/jruby_ssl_socket.rb +588 -0
  15. data/lib/httpclient/session.rb +259 -317
  16. data/lib/httpclient/ssl_config.rb +141 -188
  17. data/lib/httpclient/ssl_socket.rb +150 -0
  18. data/lib/httpclient/timeout.rb +1 -1
  19. data/lib/httpclient/util.rb +62 -1
  20. data/lib/httpclient/version.rb +1 -1
  21. data/lib/httpclient/webagent-cookie.rb +459 -0
  22. data/lib/jsonclient.rb +63 -0
  23. data/lib/oauthclient.rb +2 -1
  24. data/sample/jsonclient.rb +67 -0
  25. data/sample/oauth_twitter.rb +4 -4
  26. data/test/{ca-chain.cert → ca-chain.pem} +0 -0
  27. data/test/client-pass.key +18 -0
  28. data/test/helper.rb +10 -8
  29. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  30. data/test/test_auth.rb +175 -4
  31. data/test/test_cookie.rb +147 -243
  32. data/test/test_http-access2.rb +17 -16
  33. data/test/test_httpclient.rb +458 -77
  34. data/test/test_jsonclient.rb +80 -0
  35. data/test/test_ssl.rb +341 -17
  36. data/test/test_webagent-cookie.rb +465 -0
  37. metadata +57 -55
  38. data/README.txt +0 -721
  39. data/lib/httpclient/cacert.p7s +0 -1858
  40. data/lib/httpclient/cacert_sha1.p7s +0 -1858
  41. data/sample/oauth_salesforce_10.rb +0 -63
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -8,31 +8,14 @@
8
8
 
9
9
  require 'digest/md5'
10
10
  require 'httpclient/session'
11
+ require 'mutex_m'
11
12
 
12
13
 
13
14
  class HTTPClient
14
15
 
15
- begin
16
- require 'net/ntlm'
17
- NTLMEnabled = true
18
- rescue LoadError
19
- NTLMEnabled = false
20
- end
21
-
22
- begin
23
- require 'win32/sspi'
24
- SSPIEnabled = true
25
- rescue LoadError
26
- SSPIEnabled = false
27
- end
28
-
29
- begin
30
- require 'gssapi'
31
- GSSAPIEnabled = true
32
- rescue LoadError
33
- GSSAPIEnabled = false
34
- end
35
-
16
+ NTLMEnabled = false
17
+ SSPIEnabled = false
18
+ GSSAPIEnabled = false
36
19
 
37
20
  # Common abstract class for authentication filter.
38
21
  #
@@ -112,6 +95,13 @@ class HTTPClient
112
95
  @authenticator.each do |auth|
113
96
  next unless auth.set? # hasn't be set, don't use it
114
97
  if cred = auth.get(req)
98
+ if cred == :skip
99
+ # some authenticator (NTLM and Negotiate) does not
100
+ # need to send extra header after authorization. In such case
101
+ # it should block other authenticators to respond and :skip is
102
+ # the marker for such case.
103
+ return
104
+ end
115
105
  req.header.set('Authorization', auth.scheme + " " + cred)
116
106
  return
117
107
  end
@@ -196,6 +186,13 @@ class HTTPClient
196
186
  @authenticator.each do |auth|
197
187
  next unless auth.set? # hasn't be set, don't use it
198
188
  if cred = auth.get(req)
189
+ if cred == :skip
190
+ # some authenticator (NTLM and Negotiate) does not
191
+ # need to send extra header after authorization. In such case
192
+ # it should block other authenticators to respond and :skip is
193
+ # the marker for such case.
194
+ return
195
+ end
199
196
  req.header.set('Proxy-Authorization', auth.scheme + " " + cred)
200
197
  return
201
198
  end
@@ -225,44 +222,59 @@ class HTTPClient
225
222
  end
226
223
  end
227
224
 
228
- # Authentication filter for handling BasicAuth negotiation.
229
- # Used in WWWAuth and ProxyAuth.
230
- class BasicAuth
225
+ # Authentication filter base class.
226
+ class AuthBase
231
227
  include HTTPClient::Util
232
228
 
233
229
  # Authentication scheme.
234
230
  attr_reader :scheme
235
231
 
236
- # Creates new BasicAuth filter.
237
- def initialize
238
- @cred = nil
239
- @set = false
240
- @auth = {}
241
- @challengeable = {}
242
- @scheme = "Basic"
232
+ def initialize(scheme)
233
+ @scheme = scheme
234
+ @challenge = {}
243
235
  end
244
236
 
245
237
  # Resets challenge state. Do not send '*Authorization' header until the
246
238
  # server sends '*Authentication' again.
247
239
  def reset_challenge
248
- @challengeable.clear
240
+ synchronize do
241
+ @challenge.clear
242
+ end
243
+ end
244
+ end
245
+
246
+ # Authentication filter for handling BasicAuth negotiation.
247
+ # Used in WWWAuth and ProxyAuth.
248
+ class BasicAuth < AuthBase
249
+ include Mutex_m
250
+
251
+ # Send Authorization Header without receiving 401
252
+ attr_accessor :force_auth
253
+
254
+ # Creates new BasicAuth filter.
255
+ def initialize
256
+ super('Basic')
257
+ @cred = nil
258
+ @auth = {}
259
+ @force_auth = false
249
260
  end
250
261
 
251
262
  # Set authentication credential.
252
263
  # uri == nil for generic purpose (allow to use user/password for any URL).
253
264
  def set(uri, user, passwd)
254
- @set = true
255
- if uri.nil?
256
- @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
257
- else
258
- uri = Util.uri_dirname(uri)
259
- @auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
265
+ synchronize do
266
+ if uri.nil?
267
+ @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
268
+ else
269
+ uri = Util.uri_dirname(uri)
270
+ @auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
271
+ end
260
272
  end
261
273
  end
262
274
 
263
275
  # have we marked this as set - ie that it's valid to use in this context?
264
276
  def set?
265
- @set == true
277
+ @cred || @auth.any?
266
278
  end
267
279
 
268
280
  # Response handler: returns credential.
@@ -271,76 +283,75 @@ class HTTPClient
271
283
  # * child page of defined credential
272
284
  def get(req)
273
285
  target_uri = req.header.request_uri
274
- return nil unless @challengeable.find { |uri, ok|
275
- Util.uri_part_of(target_uri, uri) and ok
276
- }
277
- return @cred if @cred
278
- Util.hash_find_value(@auth) { |uri, cred|
279
- Util.uri_part_of(target_uri, uri)
286
+ synchronize {
287
+ return nil if !@force_auth and !@challenge.any? { |uri, ok|
288
+ Util.uri_part_of(target_uri, uri) and ok
289
+ }
290
+ return @cred if @cred
291
+ Util.hash_find_value(@auth) { |uri, cred|
292
+ Util.uri_part_of(target_uri, uri)
293
+ }
280
294
  }
281
295
  end
282
296
 
283
297
  # Challenge handler: remember URL for response.
284
298
  def challenge(uri, param_str = nil)
285
- @challengeable[urify(uri)] = true
286
- true
299
+ synchronize {
300
+ @challenge[urify(uri)] = true
301
+ true
302
+ }
287
303
  end
288
304
  end
289
305
 
290
306
  class ProxyBasicAuth < BasicAuth
291
-
292
307
  def set(uri, user, passwd)
293
- @set = true
294
- @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
308
+ synchronize do
309
+ @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
310
+ end
295
311
  end
296
312
 
297
313
  def get(req)
298
- target_uri = req.header.request_uri
299
- return nil unless @challengeable['challenged']
300
- @cred
314
+ synchronize {
315
+ return nil if !@force_auth and !@challenge['challenged']
316
+ @cred
317
+ }
301
318
  end
302
319
 
303
320
  # Challenge handler: remember URL for response.
304
321
  def challenge(uri, param_str = nil)
305
- @challengeable['challenged'] = true
306
- true
322
+ synchronize {
323
+ @challenge['challenged'] = true
324
+ true
325
+ }
307
326
  end
308
327
  end
309
328
 
310
329
  # Authentication filter for handling DigestAuth negotiation.
311
330
  # Used in WWWAuth.
312
- class DigestAuth
313
- # Authentication scheme.
314
- attr_reader :scheme
331
+ class DigestAuth < AuthBase
332
+ include Mutex_m
315
333
 
316
334
  # Creates new DigestAuth filter.
317
335
  def initialize
336
+ super('Digest')
318
337
  @auth = {}
319
- @challenge = {}
320
- @set = false
321
338
  @nonce_count = 0
322
- @scheme = "Digest"
323
- end
324
-
325
- # Resets challenge state. Do not send '*Authorization' header until the
326
- # server sends '*Authentication' again.
327
- def reset_challenge
328
- @challenge.clear
329
339
  end
330
340
 
331
341
  # Set authentication credential.
332
342
  # uri == nil is ignored.
333
343
  def set(uri, user, passwd)
334
- @set = true
335
- if uri
336
- uri = Util.uri_dirname(uri)
337
- @auth[uri] = [user, passwd]
344
+ synchronize do
345
+ if uri
346
+ uri = Util.uri_dirname(uri)
347
+ @auth[uri] = [user, passwd]
348
+ end
338
349
  end
339
350
  end
340
351
 
341
352
  # have we marked this as set - ie that it's valid to use in this context?
342
353
  def set?
343
- @set == true
354
+ @auth.any?
344
355
  end
345
356
 
346
357
  # Response handler: returns credential.
@@ -349,21 +360,25 @@ class HTTPClient
349
360
  # * child page of defined credential
350
361
  def get(req)
351
362
  target_uri = req.header.request_uri
352
- param = Util.hash_find_value(@challenge) { |uri, v|
353
- Util.uri_part_of(target_uri, uri)
354
- }
355
- return nil unless param
356
- user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
357
- Util.uri_part_of(target_uri, uri)
363
+ synchronize {
364
+ param = Util.hash_find_value(@challenge) { |uri, v|
365
+ Util.uri_part_of(target_uri, uri)
366
+ }
367
+ return nil unless param
368
+ user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
369
+ Util.uri_part_of(target_uri, uri)
370
+ }
371
+ return nil unless user
372
+ calc_cred(req, user, passwd, param)
358
373
  }
359
- return nil unless user
360
- calc_cred(req, user, passwd, param)
361
374
  end
362
375
 
363
376
  # Challenge handler: remember URL and challenge token for response.
364
377
  def challenge(uri, param_str)
365
- @challenge[uri] = parse_challenge_param(param_str)
366
- true
378
+ synchronize {
379
+ @challenge[uri] = parse_challenge_param(param_str)
380
+ true
381
+ }
367
382
  end
368
383
 
369
384
  private
@@ -445,127 +460,131 @@ class HTTPClient
445
460
 
446
461
  # overrides DigestAuth#set. sets default user name and password. uri is not used.
447
462
  def set(uri, user, passwd)
448
- @set = true
449
- @auth = [user, passwd]
463
+ synchronize do
464
+ @auth = [user, passwd]
465
+ end
450
466
  end
451
467
 
452
468
  # overrides DigestAuth#get. Uses default user name and password
453
469
  # regardless of target uri if the proxy has required authentication
454
470
  # before
455
471
  def get(req)
456
- target_uri = req.header.request_uri
457
- param = @challenge
458
- return nil unless param
459
- user, passwd = @auth
460
- return nil unless user
461
- calc_cred(req, user, passwd, param)
472
+ synchronize {
473
+ param = @challenge
474
+ return nil unless param
475
+ user, passwd = @auth
476
+ return nil unless user
477
+ calc_cred(req, user, passwd, param)
478
+ }
462
479
  end
463
480
 
464
481
  def reset_challenge
465
- @challenge = nil
482
+ synchronize do
483
+ @challenge = nil
484
+ end
466
485
  end
467
486
 
468
487
  def challenge(uri, param_str)
469
- @challenge = parse_challenge_param(param_str)
470
- true
488
+ synchronize {
489
+ @challenge = parse_challenge_param(param_str)
490
+ true
491
+ }
471
492
  end
472
-
473
493
  end
474
494
 
475
495
  # Authentication filter for handling Negotiate/NTLM negotiation.
476
496
  # Used in WWWAuth and ProxyAuth.
477
497
  #
478
498
  # NegotiateAuth depends on 'ruby/ntlm' module.
479
- class NegotiateAuth
480
- # Authentication scheme.
481
- attr_reader :scheme
499
+ class NegotiateAuth < AuthBase
500
+ include Mutex_m
501
+
482
502
  # NTLM opt for ruby/ntlm. {:ntlmv2 => true} by default.
483
503
  attr_reader :ntlm_opt
484
504
 
485
505
  # Creates new NegotiateAuth filter.
486
506
  def initialize(scheme = "Negotiate")
507
+ super(scheme)
487
508
  @auth = {}
488
509
  @auth_default = nil
489
- @challenge = {}
490
- @scheme = scheme
491
- @set = false
492
510
  @ntlm_opt = {
493
511
  :ntlmv2 => true
494
512
  }
495
513
  end
496
514
 
497
- # Resets challenge state. Do not send '*Authorization' header until the
498
- # server sends '*Authentication' again.
499
- def reset_challenge
500
- @challenge.clear
501
- end
502
-
503
515
  # Set authentication credential.
504
516
  # uri == nil for generic purpose (allow to use user/password for any URL).
505
517
  def set(uri, user, passwd)
506
- @set = true
507
- if uri
508
- uri = Util.uri_dirname(uri)
509
- @auth[uri] = [user, passwd]
510
- else
511
- @auth_default = [user, passwd]
518
+ synchronize do
519
+ if uri
520
+ uri = Util.uri_dirname(uri)
521
+ @auth[uri] = [user, passwd]
522
+ else
523
+ @auth_default = [user, passwd]
524
+ end
512
525
  end
513
526
  end
514
527
 
515
528
  # have we marked this as set - ie that it's valid to use in this context?
516
529
  def set?
517
- @set == true
530
+ @auth_default || @auth.any?
518
531
  end
519
532
 
520
533
  # Response handler: returns credential.
521
534
  # See ruby/ntlm for negotiation state transition.
522
535
  def get(req)
523
- return nil unless NTLMEnabled
524
536
  target_uri = req.header.request_uri
525
- domain_uri, param = @challenge.find { |uri, v|
526
- Util.uri_part_of(target_uri, uri)
527
- }
528
- return nil unless param
529
- user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
530
- Util.uri_part_of(target_uri, uri)
537
+ synchronize {
538
+ _domain_uri, param = @challenge.find { |uri, v|
539
+ Util.uri_part_of(target_uri, uri)
540
+ }
541
+ return nil unless param
542
+ user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
543
+ Util.uri_part_of(target_uri, uri)
544
+ }
545
+ unless user
546
+ user, passwd = @auth_default
547
+ end
548
+ return nil unless user
549
+ Util.try_require('net/ntlm') || return
550
+ domain = nil
551
+ domain, user = user.split("\\") if user.index("\\")
552
+ state = param[:state]
553
+ authphrase = param[:authphrase]
554
+ case state
555
+ when :init
556
+ t1 = Net::NTLM::Message::Type1.new
557
+ t1.domain = domain if domain
558
+ t1.encode64
559
+ when :response
560
+ t2 = Net::NTLM::Message.decode64(authphrase)
561
+ param = {:user => user, :password => passwd}
562
+ param[:domain] = domain if domain
563
+ t3 = t2.response(param, @ntlm_opt.dup)
564
+ @challenge[target_uri][:state] = :done
565
+ t3.encode64
566
+ when :done
567
+ :skip
568
+ else
569
+ nil
570
+ end
531
571
  }
532
- unless user
533
- user, passwd = @auth_default
534
- end
535
- return nil unless user
536
- domain = nil
537
- domain, user = user.split("\\") if user.index("\\")
538
- state = param[:state]
539
- authphrase = param[:authphrase]
540
- case state
541
- when :init
542
- t1 = Net::NTLM::Message::Type1.new
543
- t1.domain = domain if domain
544
- return t1.encode64
545
- when :response
546
- t2 = Net::NTLM::Message.decode64(authphrase)
547
- param = {:user => user, :password => passwd}
548
- param[:domain] = domain if domain
549
- t3 = t2.response(param, @ntlm_opt.dup)
550
- @challenge.delete(domain_uri)
551
- return t3.encode64
552
- end
553
- nil
554
572
  end
555
573
 
556
574
  # Challenge handler: remember URL and challenge token for response.
557
575
  def challenge(uri, param_str)
558
- return false unless NTLMEnabled
559
- if param_str.nil? or @challenge[uri].nil?
560
- c = @challenge[uri] = {}
561
- c[:state] = :init
562
- c[:authphrase] = ""
563
- else
564
- c = @challenge[uri]
565
- c[:state] = :response
566
- c[:authphrase] = param_str
567
- end
568
- true
576
+ synchronize {
577
+ if param_str.nil? or @challenge[uri].nil?
578
+ c = @challenge[uri] = {}
579
+ c[:state] = :init
580
+ c[:authphrase] = ""
581
+ else
582
+ c = @challenge[uri]
583
+ c[:state] = :response
584
+ c[:authphrase] = param_str
585
+ end
586
+ true
587
+ }
569
588
  end
570
589
  end
571
590
 
@@ -574,20 +593,12 @@ class HTTPClient
574
593
  # Used in ProxyAuth.
575
594
  #
576
595
  # SSPINegotiateAuth depends on 'win32/sspi' module.
577
- class SSPINegotiateAuth
578
- # Authentication scheme.
579
- attr_reader :scheme
596
+ class SSPINegotiateAuth < AuthBase
597
+ include Mutex_m
580
598
 
581
599
  # Creates new SSPINegotiateAuth filter.
582
600
  def initialize
583
- @challenge = {}
584
- @scheme = "Negotiate"
585
- end
586
-
587
- # Resets challenge state. Do not send '*Authorization' header until the
588
- # server sends '*Authentication' again.
589
- def reset_challenge
590
- @challenge.clear
601
+ super('Negotiate')
591
602
  end
592
603
 
593
604
  # Set authentication credential.
@@ -597,58 +608,64 @@ class HTTPClient
597
608
  # not supported
598
609
  end
599
610
 
600
- # have we marked this as set - ie that it's valid to use in this context?
611
+ # Check always (not effective but it works)
601
612
  def set?
602
- SSPIEnabled || GSSAPIEnabled
613
+ !@challenge.empty?
603
614
  end
604
615
 
605
616
  # Response handler: returns credential.
606
617
  # See win32/sspi for negotiation state transition.
607
618
  def get(req)
608
- return nil unless SSPIEnabled || GSSAPIEnabled
609
619
  target_uri = req.header.request_uri
610
- domain_uri, param = @challenge.find { |uri, v|
611
- Util.uri_part_of(target_uri, uri)
612
- }
613
- return nil unless param
614
- state = param[:state]
615
- authenticator = param[:authenticator]
616
- authphrase = param[:authphrase]
617
- case state
618
- when :init
619
- if SSPIEnabled
620
- authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
621
- return authenticator.get_initial_token(@scheme)
622
- else # use GSSAPI
623
- authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
624
- # Base64 encode the context token
625
- return [authenticator.init_context].pack('m').gsub(/\n/,'')
626
- end
627
- when :response
628
- @challenge.delete(domain_uri)
629
- if SSPIEnabled
630
- return authenticator.complete_authentication(authphrase)
631
- else # use GSSAPI
632
- return authenticator.init_context(authphrase.unpack('m').pop)
620
+ synchronize {
621
+ domain_uri, param = @challenge.find { |uri, v|
622
+ Util.uri_part_of(target_uri, uri)
623
+ }
624
+ return nil unless param
625
+ Util.try_require('win32/sspi') || Util.try_require('gssapi') || return
626
+ state = param[:state]
627
+ authenticator = param[:authenticator]
628
+ authphrase = param[:authphrase]
629
+ case state
630
+ when :init
631
+ if defined?(Win32::SSPI)
632
+ authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
633
+ authenticator.get_initial_token(@scheme)
634
+ else # use GSSAPI
635
+ authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
636
+ # Base64 encode the context token
637
+ [authenticator.init_context].pack('m').gsub(/\n/,'')
638
+ end
639
+ when :response
640
+ @challenge[target_uri][:state] = :done
641
+ if defined?(Win32::SSPI)
642
+ authenticator.complete_authentication(authphrase)
643
+ else # use GSSAPI
644
+ authenticator.init_context(authphrase.unpack('m').pop)
645
+ end
646
+ when :done
647
+ :skip
648
+ else
649
+ nil
633
650
  end
634
- end
635
- nil
651
+ }
636
652
  end
637
653
 
638
654
  # Challenge handler: remember URL and challenge token for response.
639
655
  def challenge(uri, param_str)
640
- return false unless SSPIEnabled || GSSAPIEnabled
641
- if param_str.nil? or @challenge[uri].nil?
642
- c = @challenge[uri] = {}
643
- c[:state] = :init
644
- c[:authenticator] = nil
645
- c[:authphrase] = ""
646
- else
647
- c = @challenge[uri]
648
- c[:state] = :response
649
- c[:authphrase] = param_str
650
- end
651
- true
656
+ synchronize {
657
+ if param_str.nil? or @challenge[uri].nil?
658
+ c = @challenge[uri] = {}
659
+ c[:state] = :init
660
+ c[:authenticator] = nil
661
+ c[:authphrase] = ""
662
+ else
663
+ c = @challenge[uri]
664
+ c[:state] = :response
665
+ c[:authphrase] = param_str
666
+ end
667
+ true
668
+ }
652
669
  end
653
670
  end
654
671
 
@@ -662,11 +679,8 @@ class HTTPClient
662
679
  # CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
663
680
  # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
664
681
  #
665
- class OAuth
666
- include HTTPClient::Util
667
-
668
- # Authentication scheme.
669
- attr_reader :scheme
682
+ class OAuth < AuthBase
683
+ include Mutex_m
670
684
 
671
685
  class Config
672
686
  include HTTPClient::Util
@@ -737,20 +751,13 @@ class HTTPClient
737
751
 
738
752
  # Creates new DigestAuth filter.
739
753
  def initialize
754
+ super('OAuth')
740
755
  @config = nil # common config
741
756
  @auth = {} # configs for each site
742
- @challengeable = {}
743
757
  @nonce_count = 0
744
758
  @signature_handler = {
745
759
  'HMAC-SHA1' => method(:sign_hmac_sha1)
746
760
  }
747
- @scheme = "OAuth"
748
- end
749
-
750
- # Resets challenge state. Do not send '*Authorization' header until the
751
- # server sends '*Authentication' again.
752
- def reset_challenge
753
- @challengeable.clear
754
761
  end
755
762
 
756
763
  # Set authentication credential.
@@ -759,31 +766,28 @@ class HTTPClient
759
766
  # not supported
760
767
  end
761
768
 
762
- # have we marked this as set - ie that it's valid to use in this context?
769
+ # Check always (not effective but it works)
763
770
  def set?
764
- true
771
+ !@challenge.empty?
765
772
  end
766
773
 
767
774
  # Set authentication credential.
768
775
  def set_config(uri, config)
769
- if uri.nil?
770
- @config = config
771
- else
772
- uri = Util.uri_dirname(urify(uri))
773
- @auth[uri] = config
776
+ synchronize do
777
+ if uri.nil?
778
+ @config = config
779
+ else
780
+ uri = Util.uri_dirname(urify(uri))
781
+ @auth[uri] = config
782
+ end
774
783
  end
775
784
  end
776
785
 
777
786
  # Get authentication credential.
778
787
  def get_config(uri = nil)
779
- if uri.nil?
780
- @config
781
- else
782
- uri = urify(uri)
783
- Util.hash_find_value(@auth) { |cand_uri, cred|
784
- Util.uri_part_of(uri, cand_uri)
785
- }
786
- end
788
+ synchronize {
789
+ do_get_config(uri)
790
+ }
787
791
  end
788
792
 
789
793
  # Response handler: returns credential.
@@ -792,33 +796,54 @@ class HTTPClient
792
796
  # * child page of defined credential
793
797
  def get(req)
794
798
  target_uri = req.header.request_uri
795
- return nil unless @challengeable[nil] or @challengeable.find { |uri, ok|
796
- Util.uri_part_of(target_uri, uri) and ok
799
+ synchronize {
800
+ return nil unless @challenge[nil] or @challenge.find { |uri, ok|
801
+ Util.uri_part_of(target_uri, uri) and ok
802
+ }
803
+ config = do_get_config(target_uri) || @config
804
+ return nil unless config
805
+ calc_cred(req, config)
797
806
  }
798
- config = get_config(target_uri) || @config
799
- return nil unless config
800
- calc_cred(req, config)
801
807
  end
802
808
 
803
809
  # Challenge handler: remember URL for response.
810
+ #
811
+ # challenge() in OAuth handler always returns false to avoid connection
812
+ # retry which should not work in OAuth authentication context. This
813
+ # method just remember URL (nil means 'any') for the next connection.
814
+ # Normally OAuthClient handles this correctly but see how it uses when
815
+ # you need to use this class directly.
804
816
  def challenge(uri, param_str = nil)
817
+ synchronize {
818
+ if uri.nil?
819
+ @challenge[nil] = true
820
+ else
821
+ @challenge[urify(uri)] = true
822
+ end
823
+ false
824
+ }
825
+ end
826
+
827
+ private
828
+
829
+ def do_get_config(uri = nil)
805
830
  if uri.nil?
806
- @challengeable[nil] = true
831
+ @config
807
832
  else
808
- @challengeable[urify(uri)] = true
833
+ uri = urify(uri)
834
+ Util.hash_find_value(@auth) { |cand_uri, cred|
835
+ Util.uri_part_of(uri, cand_uri)
836
+ }
809
837
  end
810
- true
811
838
  end
812
839
 
813
- private
814
-
815
840
  def calc_cred(req, config)
816
841
  header = {}
817
842
  header['oauth_consumer_key'] = config.consumer_key
818
- header['oauth_token'] = config.token
819
843
  header['oauth_signature_method'] = config.signature_method
820
844
  header['oauth_timestamp'] = config.debug_timestamp || Time.now.to_i.to_s
821
845
  header['oauth_nonce'] = config.debug_nonce || generate_nonce()
846
+ header['oauth_token'] = config.token if config.token
822
847
  header['oauth_version'] = config.version if config.version
823
848
  header['oauth_callback'] = config.callback if config.callback
824
849
  header['oauth_verifier'] = config.verifier if config.verifier