httpclient 2.2.5 → 2.2.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  httpclient - HTTP accessing library.
2
- Copyright (C) 2000-2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ Copyright (C) 2000-2012 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
 
4
4
  'httpclient' gives something like the functionality of libwww-perl (LWP) in
5
5
  Ruby. 'httpclient' formerly known as 'http-access2'.
@@ -18,7 +18,7 @@ See HTTPClient for documentation.
18
18
  * MT-safe
19
19
  * streaming POST (POST with File/IO)
20
20
  * Digest auth
21
- * Negotiate/NTLM auth for WWW-Authenticate (requires net/htlm module; rubyntlm gem)
21
+ * Negotiate/NTLM auth for WWW-Authenticate (requires net/ntlm module; rubyntlm gem)
22
22
  * NTLM auth for Proxy-Authenticate (requires 'win32/sspi' module; rubysspi gem)
23
23
  * extensible with filter interface
24
24
  * you don't have to care HTTP/1.1 persistent connection
@@ -95,6 +95,61 @@ Thanks in advance.
95
95
 
96
96
  == Changes
97
97
 
98
+ = Changes in 2.2.6 =
99
+
100
+ August 14, 2012 - version 2.2.6
101
+
102
+ * Bug fixes
103
+
104
+ * Make get_content doesn't raise a BadResponseError for perfectly good
105
+ responses like 304 Not Modified. Thanks to Florian Hars.
106
+
107
+ * Add 'Content-Type: application/x-www-form-urlencoded' for the PUT
108
+ request that has urlencoded entity-body.
109
+
110
+ * Features
111
+
112
+ * Add HTTPClient::IncludeClient by Jonathan Rochkind, a mix-in for easily
113
+ adding a thread-safe lazily initialized class-level HTTPClient object
114
+ to your class.
115
+
116
+ * Proxy DigestAuth support. Thanks to Alexander Kotov and Florian Hars.
117
+
118
+ * Accept an array of strings (and IO-likes) as a query value
119
+ e.g. `{ x: 'a', y: [1,2,3] }` is encoded into `"x=a&y=1&y=2&y=3"`.
120
+ Thanks to Akinori MUSHA.
121
+
122
+ * Allow body for DELETE method.
123
+
124
+ * Allow :follow_redirect => true for HEAD request.
125
+
126
+ * Fill request parameters request_method, request_uri and request_query
127
+ as part of response Message::Header.
128
+
129
+ = Changes in 2.2.5 =
130
+
131
+ May 06, 2012 - version 2.2.5
132
+
133
+ * Bug fixes
134
+
135
+ * Added Magic encoding comment to hexdump.rb to avoid encoding error.
136
+ * Add workaround for JRuby issue on Windows (JRUBY-6136)
137
+ On Windows, calling File#size fails with an Unknown error (20047).
138
+ This workaround uses File#lstat instead.
139
+ * Require open-uri only on ruby 1.9, since it is not needed on 1.8.
140
+
141
+ * Features
142
+
143
+ * Allow symbol Header name for HTTP request.
144
+ * Dump more SSL certificate information under $DEBUG.
145
+ * Add HTTPClient::SSLConfig#ssl_version property.
146
+ * Add 'Accept: */*' header to request by default. Rails requies it.
147
+ It doesn't override given Accept header from API.
148
+ * Add HTTPClient::SSLConfig#set_default_paths. This method makes
149
+ HTTPClient instance to use OpenSSL's default trusted CA certificates.
150
+ * Allow to set Date header manually.
151
+ ex. clent.get(uri, :header => {'Date' => Time.now.httpdate})
152
+
98
153
  = Changes in 2.2.4 =
99
154
 
100
155
  Dec 08, 2011 - version 2.2.4
@@ -577,7 +577,7 @@ class HTTPClient
577
577
  # follow HTTP redirect by yourself if you need.
578
578
  def get_content(uri, *args, &block)
579
579
  query, header = keyword_argument(args, :query, :header)
580
- follow_redirect(:get, uri, query, nil, header || {}, &block).content
580
+ success_content(follow_redirect(:get, uri, query, nil, header || {}, &block))
581
581
  end
582
582
 
583
583
  # Posts a content.
@@ -607,12 +607,14 @@ class HTTPClient
607
607
  # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
608
608
  # internally and try to post the content to redirected URL. See
609
609
  # redirect_uri_callback= how HTTP redirection is handled.
610
+ # Bear in mind that you should not depend on post_content because it sends
611
+ # the same POST method to the new location which is prohibited in HTTP spec.
610
612
  #
611
613
  # If you need to get full HTTP response including HTTP status and headers,
612
614
  # use post method.
613
615
  def post_content(uri, *args, &block)
614
616
  body, header = keyword_argument(args, :body, :header)
615
- follow_redirect(:post, uri, nil, body, header || {}, &block).content
617
+ success_content(follow_redirect(:post, uri, nil, body, header || {}, &block))
616
618
  end
617
619
 
618
620
  # A method for redirect uri callback. How to use:
@@ -653,7 +655,7 @@ class HTTPClient
653
655
 
654
656
  # Sends HEAD request to the specified URL. See request for arguments.
655
657
  def head(uri, *args)
656
- request(:head, uri, argument_to_hash(args, :query, :header))
658
+ request(:head, uri, argument_to_hash(args, :query, :header, :follow_redirect))
657
659
  end
658
660
 
659
661
  # Sends GET request to the specified URL. See request for arguments.
@@ -662,6 +664,8 @@ class HTTPClient
662
664
  end
663
665
 
664
666
  # Sends POST request to the specified URL. See request for arguments.
667
+ # You should not depend on :follow_redirect => true for POST method. It
668
+ # sends the same POST method to the new location which is prohibited in HTTP spec.
665
669
  def post(uri, *args, &block)
666
670
  request(:post, uri, argument_to_hash(args, :body, :header, :follow_redirect), &block)
667
671
  end
@@ -673,7 +677,7 @@ class HTTPClient
673
677
 
674
678
  # Sends DELETE request to the specified URL. See request for arguments.
675
679
  def delete(uri, *args, &block)
676
- request(:delete, uri, argument_to_hash(args, :header), &block)
680
+ request(:delete, uri, argument_to_hash(args, :body, :header), &block)
677
681
  end
678
682
 
679
683
  # Sends OPTIONS request to the specified URL. See request for arguments.
@@ -693,7 +697,7 @@ class HTTPClient
693
697
 
694
698
  # Sends TRACE request to the specified URL. See request for arguments.
695
699
  def trace(uri, *args, &block)
696
- request('TRACE', uri, argument_to_hash(args, :query, :body, :header), &block)
700
+ request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
697
701
  end
698
702
 
699
703
  # Sends a request to the specified URL.
@@ -939,18 +943,24 @@ private
939
943
  while retry_number < @follow_redirect_count
940
944
  body.pos = pos if pos
941
945
  res = do_request(method, uri, query, body, header, &filtered_block)
942
- if HTTP::Status.successful?(res.status)
943
- return res
944
- elsif HTTP::Status.redirect?(res.status)
946
+ if HTTP::Status.redirect?(res.status)
945
947
  uri = urify(@redirect_uri_callback.call(uri, res))
946
948
  retry_number += 1
947
949
  else
948
- raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
950
+ return res
949
951
  end
950
952
  end
951
953
  raise BadResponseError.new("retry count exceeded", res)
952
954
  end
953
955
 
956
+ def success_content(res)
957
+ if HTTP::Status.successful?(res.status)
958
+ return res.content
959
+ else
960
+ raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
961
+ end
962
+ end
963
+
954
964
  def protect_keep_alive_disconnected
955
965
  begin
956
966
  yield
@@ -984,7 +994,7 @@ private
984
994
  header = override_header(header, 'Content-Type', content_type)
985
995
  end
986
996
  end
987
- elsif method == 'POST'
997
+ else
988
998
  if file_in_form_data?(body)
989
999
  boundary = create_boundary
990
1000
  content_type = "multipart/form-data; boundary=#{boundary}"
@@ -1051,11 +1061,11 @@ private
1051
1061
  end
1052
1062
  if str = @test_loopback_response.shift
1053
1063
  dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1054
- conn.push(HTTP::Message.new_response(str))
1064
+ conn.push(HTTP::Message.new_response(str, req.header))
1055
1065
  return
1056
1066
  end
1057
1067
  content = block ? nil : ''
1058
- res = HTTP::Message.new_response(content)
1068
+ res = HTTP::Message.new_response(content, req.header)
1059
1069
  @debug_dev << "= Request\n\n" if @debug_dev
1060
1070
  sess = @session_manager.query(req, proxy)
1061
1071
  res.peer_cert = sess.ssl_peer_cert
@@ -1087,11 +1097,11 @@ private
1087
1097
  end
1088
1098
  if str = @test_loopback_response.shift
1089
1099
  dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1090
- conn.push(HTTP::Message.new_response(StringIO.new(str)))
1100
+ conn.push(HTTP::Message.new_response(StringIO.new(str), req.header))
1091
1101
  return
1092
1102
  end
1093
1103
  piper, pipew = IO.pipe
1094
- res = HTTP::Message.new_response(piper)
1104
+ res = HTTP::Message.new_response(piper, req.header)
1095
1105
  @debug_dev << "= Request\n\n" if @debug_dev
1096
1106
  sess = @session_manager.query(req, proxy)
1097
1107
  res.peer_cert = sess.ssl_peer_cert
@@ -120,6 +120,10 @@ class HTTPClient
120
120
 
121
121
  # Filter API implementation. Traps HTTP response and parses
122
122
  # 'WWW-Authenticate' header.
123
+ #
124
+ # This remembers the challenges for all authentication methods
125
+ # available to the client. On the subsequent retry of the request,
126
+ # filter_request will select the strongest method.
123
127
  def filter_response(req, res)
124
128
  command = nil
125
129
  if res.status == HTTP::Status::UNAUTHORIZED
@@ -156,17 +160,19 @@ class HTTPClient
156
160
  # SSPINegotiateAuth requires 'win32/sspi' module.
157
161
  class ProxyAuth < AuthFilterBase
158
162
  attr_reader :basic_auth
163
+ attr_reader :digest_auth
159
164
  attr_reader :negotiate_auth
160
165
  attr_reader :sspi_negotiate_auth
161
166
 
162
167
  # Creates new ProxyAuth.
163
168
  def initialize
164
- @basic_auth = BasicAuth.new
169
+ @basic_auth = ProxyBasicAuth.new
165
170
  @negotiate_auth = NegotiateAuth.new
166
171
  @ntlm_auth = NegotiateAuth.new('NTLM')
167
172
  @sspi_negotiate_auth = SSPINegotiateAuth.new
173
+ @digest_auth = ProxyDigestAuth.new
168
174
  # sort authenticators by priority
169
- @authenticator = [@negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @basic_auth]
175
+ @authenticator = [@negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @digest_auth, @basic_auth]
170
176
  end
171
177
 
172
178
  # Resets challenge state. See sub filters for more details.
@@ -281,6 +287,25 @@ class HTTPClient
281
287
  end
282
288
  end
283
289
 
290
+ class ProxyBasicAuth < BasicAuth
291
+
292
+ def set(uri, user, passwd)
293
+ @set = true
294
+ @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
295
+ end
296
+
297
+ def get(req)
298
+ target_uri = req.header.request_uri
299
+ return nil unless @challengeable['challenged']
300
+ @cred
301
+ end
302
+
303
+ # Challenge handler: remember URL for response.
304
+ def challenge(uri, param_str = nil)
305
+ @challengeable['challenged'] = true
306
+ true
307
+ end
308
+ end
284
309
 
285
310
  # Authentication filter for handling DigestAuth negotiation.
286
311
  # Used in WWWAuth.
@@ -352,9 +377,12 @@ class HTTPClient
352
377
  path = req.header.create_query_uri
353
378
  a_1 = "#{user}:#{param['realm']}:#{passwd}"
354
379
  a_2 = "#{method}:#{path}"
380
+ qop = param['qop']
355
381
  nonce = param['nonce']
356
- cnonce = generate_cnonce()
357
- @nonce_count += 1
382
+ cnonce = nil
383
+ if qop || param['algorithm'] =~ /MD5-sess/
384
+ cnonce = generate_cnonce()
385
+ end
358
386
  a_1_md5sum = Digest::MD5.hexdigest(a_1)
359
387
  if param['algorithm'] =~ /MD5-sess/
360
388
  a_1_md5sum = Digest::MD5.hexdigest("#{a_1_md5sum}:#{nonce}:#{cnonce}")
@@ -365,18 +393,25 @@ class HTTPClient
365
393
  message_digest = []
366
394
  message_digest << a_1_md5sum
367
395
  message_digest << nonce
368
- message_digest << ('%08x' % @nonce_count)
369
- message_digest << cnonce
370
- message_digest << param['qop']
396
+ if qop
397
+ @nonce_count += 1
398
+ message_digest << ('%08x' % @nonce_count)
399
+ message_digest << cnonce
400
+ message_digest << param['qop']
401
+ end
371
402
  message_digest << Digest::MD5.hexdigest(a_2)
372
403
  header = []
373
404
  header << "username=\"#{user}\""
374
405
  header << "realm=\"#{param['realm']}\""
375
406
  header << "nonce=\"#{nonce}\""
376
407
  header << "uri=\"#{path}\""
377
- header << "cnonce=\"#{cnonce}\""
378
- header << "nc=#{'%08x' % @nonce_count}"
379
- header << "qop=#{param['qop']}"
408
+ if cnonce
409
+ header << "cnonce=\"#{cnonce}\""
410
+ end
411
+ if qop
412
+ header << "nc=#{'%08x' % @nonce_count}"
413
+ header << "qop=#{param['qop']}"
414
+ end
380
415
  header << "response=\"#{Digest::MD5.hexdigest(message_digest.join(":"))}\""
381
416
  header << "algorithm=#{algorithm}"
382
417
  header << "opaque=\"#{param['opaque']}\"" if param.key?('opaque')
@@ -404,6 +439,39 @@ class HTTPClient
404
439
  end
405
440
 
406
441
 
442
+ # Authentication filter for handling DigestAuth negotiation.
443
+ # Ignores uri argument. Used in ProxyAuth.
444
+ class ProxyDigestAuth < DigestAuth
445
+
446
+ # overrides DigestAuth#set. sets default user name and password. uri is not used.
447
+ def set(uri, user, passwd)
448
+ @set = true
449
+ @auth = [user, passwd]
450
+ end
451
+
452
+ # overrides DigestAuth#get. Uses default user name and password
453
+ # regardless of target uri if the proxy has required authentication
454
+ # before
455
+ 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)
462
+ end
463
+
464
+ def reset_challenge
465
+ @challenge = nil
466
+ end
467
+
468
+ def challenge(uri, param_str)
469
+ @challenge = parse_challenge_param(param_str)
470
+ true
471
+ end
472
+
473
+ end
474
+
407
475
  # Authentication filter for handling Negotiate/NTLM negotiation.
408
476
  # Used in WWWAuth and ProxyAuth.
409
477
  #
@@ -199,9 +199,14 @@ module HTTP
199
199
  end
200
200
 
201
201
  # Initialize this instance as a response.
202
- def init_response(status_code)
202
+ def init_response(status_code, req)
203
203
  @is_request = false
204
204
  self.status_code = status_code
205
+ if req
206
+ @request_method = req.request_method
207
+ @request_uri = req.request_uri
208
+ @request_query = req.request_query
209
+ end
205
210
  end
206
211
 
207
212
  # Sets status code and reason phrase.
@@ -456,7 +461,7 @@ module HTTP
456
461
  end
457
462
 
458
463
  # Initialize this instance as a response.
459
- def init_response(body = nil)
464
+ def init_response(body)
460
465
  @body = body
461
466
  if @body.respond_to?(:bytesize)
462
467
  @size = @body.bytesize
@@ -549,7 +554,7 @@ module HTTP
549
554
 
550
555
  def remember_pos(io)
551
556
  # IO may not support it (ex. IO.pipe)
552
- @positions[io] = io.pos rescue nil
557
+ @positions[io] = io.pos if io.respond_to?(:pos)
553
558
  end
554
559
 
555
560
  def reset_pos(io)
@@ -721,9 +726,9 @@ module HTTP
721
726
 
722
727
  # Creates a Message instance of response.
723
728
  # body:: a String or an IO of response message body.
724
- def new_response(body)
729
+ def new_response(body, req = nil)
725
730
  m = new
726
- m.http_header.init_response(Status::OK)
731
+ m.http_header.init_response(Status::OK, req)
727
732
  m.http_body = Body.new
728
733
  m.http_body.init_response(body)
729
734
  m.http_header.body_size = m.http_body.size || 0
@@ -821,13 +826,36 @@ module HTTP
821
826
  end
822
827
  end
823
828
 
829
+ def Array.try_convert(value)
830
+ return value if value.instance_of?(Array)
831
+ return nil if !value.respond_to?(:to_ary)
832
+ converted = value.to_ary
833
+ return converted if converted.instance_of?(Array)
834
+
835
+ cname = value.class.name
836
+ raise TypeError, "can't convert %s to %s (%s#%s gives %s)" %
837
+ [cname, Array.name, cname, :to_ary, converted.class.name]
838
+ end unless Array.respond_to?(:try_convert)
839
+
824
840
  def escape_query(query) # :nodoc:
825
- query.collect { |attr, value|
826
- if value.respond_to?(:read)
827
- value = value.read
841
+ pairs = []
842
+ query.each { |attr, value|
843
+ left = escape(attr.to_s) << '='
844
+ if values = Array.try_convert(value)
845
+ values.each { |value|
846
+ if value.respond_to?(:read)
847
+ value = value.read
848
+ end
849
+ pairs.push(left + escape(value.to_s))
850
+ }
851
+ else
852
+ if value.respond_to?(:read)
853
+ value = value.read
854
+ end
855
+ pairs.push(left << escape(value.to_s))
828
856
  end
829
- escape(attr.to_s) << '=' << escape(value.to_s)
830
- }.join('&')
857
+ }
858
+ pairs.join('&')
831
859
  end
832
860
 
833
861
  # from CGI.escape
@@ -0,0 +1,83 @@
1
+ # It is useful to re-use a HTTPClient instance for multiple requests, to
2
+ # re-use HTTP 1.1 persistent connections.
3
+ #
4
+ # To do that, you sometimes want to store an HTTPClient instance in a global/
5
+ # class variable location, so it can be accessed and re-used.
6
+ #
7
+ # This mix-in makes it easy to create class-level access to one or more
8
+ # HTTPClient instances. The HTTPClient instances are lazily initialized
9
+ # on first use (to, for instance, avoid interfering with WebMock/VCR),
10
+ # and are initialized in a thread-safe manner. Note that a
11
+ # HTTPClient, once initialized, is safe for use in multiple threads.
12
+ #
13
+ # Note that you `extend` HTTPClient::IncludeClient, not `include.
14
+ #
15
+ # require 'httpclient/include_client'
16
+ # class Widget
17
+ # extend HTTPClient::IncludeClient
18
+ #
19
+ # include_http_client
20
+ # # and/or, specify more stuff
21
+ # include_http_client('http://myproxy:8080', :method_name => :my_client) do |client|
22
+ # # any init you want
23
+ # client.set_cookie_store nil
24
+ # client.
25
+ # end
26
+ # end
27
+ #
28
+ # That creates two HTTPClient instances available at the class level.
29
+ # The first will be available from Widget.http_client (default method
30
+ # name for `include_http_client`), with default initialization.
31
+ #
32
+ # The second will be available at Widget.my_client, with the init arguments
33
+ # provided, further initialized by the block provided.
34
+ #
35
+ # In addition to a class-level method, for convenience instance-level methods
36
+ # are also provided. Widget.http_client is identical to Widget.new.http_client
37
+ #
38
+ #
39
+ class HTTPClient
40
+ module IncludeClient
41
+
42
+
43
+ def include_http_client(*args, &block)
44
+ # We're going to dynamically define a class
45
+ # to hold our state, namespaced, as well as possibly dynamic
46
+ # name of cover method.
47
+ method_name = (args.last.delete(:method_name) if args.last.kind_of? Hash) || :http_client
48
+ args.pop if args.last == {} # if last arg was named methods now empty, remove it.
49
+
50
+ # By the amazingness of closures, we can create these things
51
+ # in local vars here and use em in our method, we don't even
52
+ # need iVars for state.
53
+ client_instance = nil
54
+ client_mutex = Mutex.new
55
+ client_args = args
56
+ client_block = block
57
+
58
+ # to define a _class method_ on the specific class that's currently
59
+ # `self`, we have to use this bit of metaprogramming, sorry.
60
+ (class << self; self ; end).instance_eval do
61
+ define_method(method_name) do
62
+ # implementation copied from ruby stdlib singleton
63
+ # to create this global obj thread-safely.
64
+ return client_instance if client_instance
65
+ client_mutex.synchronize do
66
+ return client_instance if client_instance
67
+ # init HTTPClient with specified args/block
68
+ client_instance = HTTPClient.new(*client_args)
69
+ client_block.call(client_instance) if client_block
70
+ end
71
+ return client_instance
72
+ end
73
+ end
74
+
75
+ # And for convenience, an _instance method_ on the class that just
76
+ # delegates to the class method.
77
+ define_method(method_name) do
78
+ self.class.send(method_name)
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -316,6 +316,18 @@ class HTTPClient
316
316
  end
317
317
  end
318
318
 
319
+ def ssl_version
320
+ @ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
321
+ end
322
+
323
+ def ssl_cipher
324
+ @ssl_socket.cipher
325
+ end
326
+
327
+ def ssl_state
328
+ @ssl_socket.state
329
+ end
330
+
319
331
  def peer_cert
320
332
  @ssl_socket.peer_cert
321
333
  end
@@ -741,7 +753,15 @@ class HTTPClient
741
753
  else
742
754
  @socket = create_ssl_socket(@socket)
743
755
  connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
744
- @socket.ssl_connect(@dest.host)
756
+ begin
757
+ @socket.ssl_connect(@dest.host)
758
+ ensure
759
+ if $DEBUG
760
+ warn("Protocol version: #{@socket.ssl_version}")
761
+ warn("Cipher: #{@socket.ssl_cipher.inspect}")
762
+ warn("State: #{@socket.ssl_state}")
763
+ end
764
+ end
745
765
  @socket.post_connection_check(@dest)
746
766
  @ssl_peer_cert = @socket.peer_cert
747
767
  end
@@ -1,3 +1,3 @@
1
1
  class HTTPClient
2
- VERSION = '2.2.5'
2
+ VERSION = '2.2.6'
3
3
  end
@@ -0,0 +1,63 @@
1
+ require 'oauthclient'
2
+
3
+ # Get your own consumer token from http://twitter.com/apps
4
+ consumer_key = '3MVG9y6x0357HledNGHa9tJrrlOmpCSo5alTv4W4AG1M0f9a8cGBIwo5wN2bQ7hjAEsjD7SBWf3H2Oycc9Qql'
5
+ consumer_secret = '1404017425765973464'
6
+
7
+ callback = ARGV.shift # can be nil for OAuth 1.0. (not 1.0a)
8
+ request_token_url = 'https://login.salesforce.com/_nc_external/system/security/oauth/RequestTokenHandler'
9
+ oob_authorize_url = 'https://login.salesforce.com/setup/secur/RemoteAccessAuthorizationPage.apexp'
10
+ access_token_url = 'https://login.salesforce.com/_nc_external/system/security/oauth/AccessTokenHandler'
11
+
12
+ STDOUT.sync = true
13
+
14
+ # create OAuth client.
15
+ client = OAuthClient.new
16
+ client.oauth_config.consumer_key = consumer_key
17
+ client.oauth_config.consumer_secret = consumer_secret
18
+ client.oauth_config.signature_method = 'HMAC-SHA1'
19
+ client.oauth_config.http_method = :get # Twitter does not allow :post
20
+ client.debug_dev = STDERR if $DEBUG
21
+
22
+ client.ssl_config.ssl_version = "TLSv1_1"
23
+
24
+ # Get request token.
25
+ res = client.get_request_token(request_token_url, callback)
26
+ p res.status
27
+ p res.oauth_params
28
+ p res.content
29
+ p client.oauth_config
30
+ token = res.oauth_params['oauth_token']
31
+ secret = res.oauth_params['oauth_token_secret']
32
+ raise if token.nil? or secret.nil?
33
+
34
+ # You need to confirm authorization out of band.
35
+ puts
36
+ puts "Go here and do confirm: #{oob_authorize_url}?oauth_token=#{token}&oauth_consumer_key=#{consumer_key}"
37
+ puts "Type oauth_verifier/PIN (if given) and hit [enter] to go"
38
+ verifier = gets.chomp
39
+ verifier = nil if verifier.empty?
40
+
41
+ # Get access token.
42
+ # FYI: You may need to re-construct OAuthClient instance here.
43
+ # In normal web app flow, getting access token and getting request token
44
+ # must be done in different HTTP requests.
45
+ # client = OAuthClient.new
46
+ # client.oauth_config.consumer_key = consumer_key
47
+ # client.oauth_config.consumer_secret = consumer_secret
48
+ # client.oauth_config.signature_method = 'HMAC-SHA1'
49
+ # client.oauth_config.http_method = :get # Twitter does not allow :post
50
+ res = client.get_access_token(access_token_url, token, secret, verifier)
51
+ p res.status
52
+ p res.oauth_params
53
+ p res.content
54
+ p client.oauth_config
55
+ id = res.oauth_params['user_id']
56
+
57
+ puts
58
+ puts "Access token usage example"
59
+ puts "Hit [enter] to go"
60
+ gets
61
+
62
+ # Access to a protected resource. (DM)
63
+ puts client.get("http://twitter.com/direct_messages.json")
@@ -1,9 +1,12 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'test/unit'
3
- require 'simplecov'
4
- require 'simplecov-rcov'
5
- SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
6
- SimpleCov.start
3
+ begin
4
+ require 'simplecov'
5
+ require 'simplecov-rcov'
6
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
7
+ SimpleCov.start
8
+ rescue LoadError
9
+ end
7
10
 
8
11
  require 'httpclient'
9
12
  require 'webrick'
@@ -1,5 +1,5 @@
1
1
  require File.expand_path('helper', File.dirname(__FILE__))
2
-
2
+ require 'digest/md5'
3
3
 
4
4
  class TestAuth < Test::Unit::TestCase
5
5
  include Helper
@@ -56,6 +56,23 @@ class TestAuth < Test::Unit::TestCase
56
56
  :UserDB => htdigest_userdb
57
57
  )
58
58
  @server_thread = start_server_thread(@server)
59
+
60
+ @proxy_digest_auth = WEBrick::HTTPAuth::ProxyDigestAuth.new(
61
+ :Logger => @proxylogger,
62
+ :Algorithm => 'MD5',
63
+ :Realm => 'auth',
64
+ :UserDB => htdigest_userdb
65
+ )
66
+
67
+ @proxyserver = WEBrick::HTTPProxyServer.new(
68
+ :ProxyAuthProc => @proxy_digest_auth.method(:authenticate).to_proc,
69
+ :BindAddress => "localhost",
70
+ :Logger => @proxylogger,
71
+ :Port => 0,
72
+ :AccessLog => []
73
+ )
74
+ @proxyport = @proxyserver.config[:Port]
75
+ @proxyserver_thread = start_server_thread(@proxyserver)
59
76
  end
60
77
 
61
78
  def do_basic_auth(req, res)
@@ -105,12 +122,32 @@ class TestAuth < Test::Unit::TestCase
105
122
  end
106
123
  end
107
124
 
125
+ def test_basic_auth_reuses_credentials
126
+ c = HTTPClient.new
127
+ c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
128
+ assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth/"))
129
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
130
+ c.debug_dev = str = ''
131
+ c.get_content("http://localhost:#{serverport}/basic_auth/sub/dir/")
132
+ assert_match /Authorization: Basic YWRtaW46YWRtaW4=/, str
133
+ end
134
+
108
135
  def test_digest_auth
109
136
  c = HTTPClient.new
110
137
  c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
111
138
  assert_equal('digest_auth OK', c.get_content("http://localhost:#{serverport}/digest_auth"))
112
139
  end
113
140
 
141
+ def test_digest_auth_reuses_credentials
142
+ c = HTTPClient.new
143
+ c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
144
+ assert_equal('digest_auth OK', c.get_content("http://localhost:#{serverport}/digest_auth/"))
145
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
146
+ c.debug_dev = str = ''
147
+ c.get_content("http://localhost:#{serverport}/digest_auth/sub/dir/")
148
+ assert_match /Authorization: Digest/, str
149
+ end
150
+
114
151
  def test_digest_auth_with_block
115
152
  c = HTTPClient.new
116
153
  c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
@@ -147,6 +184,16 @@ class TestAuth < Test::Unit::TestCase
147
184
  assert_equal('digest_auth OKbar=baz', c.get_content("http://localhost:#{serverport}/digest_auth/foo?bar=baz"))
148
185
  end
149
186
 
187
+ def test_perfer_digest
188
+ c = HTTPClient.new
189
+ c.set_auth('http://example.com/', 'admin', 'admin')
190
+ c.test_loopback_http_response << "HTTP/1.0 401 Unauthorized\nWWW-Authenticate: Basic realm=\"foo\"\nWWW-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
191
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
192
+ c.debug_dev = str = ''
193
+ c.get_content('http://example.com/')
194
+ assert_match(/^Authorization: Digest/, str)
195
+ end
196
+
150
197
  def test_digest_sess_auth
151
198
  c = HTTPClient.new
152
199
  c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
@@ -163,6 +210,81 @@ class TestAuth < Test::Unit::TestCase
163
210
  assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
164
211
  end
165
212
 
213
+ def test_proxy_auth_reuses_credentials
214
+ c = HTTPClient.new
215
+ c.set_proxy_auth('admin', 'admin')
216
+ c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Basic realm=\"foo\"\nContent-Length: 2\n\nNG"
217
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
218
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
219
+ c.get_content('http://www1.example.com/')
220
+ c.debug_dev = str = ''
221
+ c.get_content('http://www2.example.com/')
222
+ assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
223
+ end
224
+
225
+ def test_digest_proxy_auth_loop
226
+ c = HTTPClient.new
227
+ c.set_proxy_auth('admin', 'admin')
228
+ c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
229
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
230
+ md5 = Digest::MD5.new
231
+ ha1 = md5.hexdigest("admin:foo:admin")
232
+ ha2 = md5.hexdigest("GET:/")
233
+ response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
234
+ c.debug_dev = str = ''
235
+ c.get_content('http://example.com/')
236
+ assert_match(/Proxy-Authorization: Digest/, str)
237
+ assert_match(%r"response=\"#{response}\"", str)
238
+ end
239
+
240
+ def test_digest_proxy_auth
241
+ c=HTTPClient.new("http://localhost:#{proxyport}/")
242
+ c.set_proxy_auth('admin', 'admin')
243
+ c.set_auth("http://127.0.0.1:#{serverport}/", 'admin', 'admin')
244
+ assert_equal('basic_auth OK', c.get_content("http://127.0.0.1:#{serverport}/basic_auth"))
245
+ end
246
+
247
+ def test_digest_proxy_invalid_auth
248
+ c=HTTPClient.new("http://localhost:#{proxyport}/")
249
+ c.set_proxy_auth('admin', 'wrong')
250
+ c.set_auth("http://127.0.0.1:#{serverport}/", 'admin', 'admin')
251
+ assert_raises(HTTPClient::BadResponseError) do
252
+ c.get_content("http://127.0.0.1:#{serverport}/basic_auth")
253
+ end
254
+ end
255
+
256
+ def test_prefer_digest_to_basic_proxy_auth
257
+ c = HTTPClient.new
258
+ c.set_proxy_auth('admin', 'admin')
259
+ c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nProxy-Authenticate: Basic realm=\"bar\"\nContent-Length: 2\n\nNG"
260
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
261
+ md5 = Digest::MD5.new
262
+ ha1 = md5.hexdigest("admin:foo:admin")
263
+ ha2 = md5.hexdigest("GET:/")
264
+ response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
265
+ c.debug_dev = str = ''
266
+ c.get_content('http://example.com/')
267
+ assert_match(/Proxy-Authorization: Digest/, str)
268
+ assert_match(%r"response=\"#{response}\"", str)
269
+ end
270
+
271
+ def test_digest_proxy_auth_reuses_credentials
272
+ c = HTTPClient.new
273
+ c.set_proxy_auth('admin', 'admin')
274
+ c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
275
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
276
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
277
+ md5 = Digest::MD5.new
278
+ ha1 = md5.hexdigest("admin:foo:admin")
279
+ ha2 = md5.hexdigest("GET:/")
280
+ response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
281
+ c.get_content('http://www1.example.com/')
282
+ c.debug_dev = str = ''
283
+ c.get_content('http://www2.example.com/')
284
+ assert_match(/Proxy-Authorization: Digest/, str)
285
+ assert_match(%r"response=\"#{response}\"", str)
286
+ end
287
+
166
288
  def test_oauth
167
289
  c = HTTPClient.new
168
290
  config = HTTPClient::OAuth::Config.new(
@@ -134,6 +134,14 @@ class TestHTTPClient < Test::Unit::TestCase
134
134
  assert_equal("Host: foo", lines[4]) # use given param
135
135
  end
136
136
 
137
+ def test_redirect_returns_not_modified
138
+ assert_nothing_raised do
139
+ timeout(2) do
140
+ @client.get(serverurl + 'status', {:status => 306}, {:follow_redirect => true})
141
+ end
142
+ end
143
+ end
144
+
137
145
  def test_protocol_version_http11
138
146
  assert_equal(nil, @client.protocol_version)
139
147
  str = ""
@@ -167,7 +175,7 @@ class TestHTTPClient < Test::Unit::TestCase
167
175
  setup_proxyserver
168
176
  escape_noproxy do
169
177
  assert_raises(URI::InvalidURIError) do
170
- @client.proxy = "http://"
178
+ @client.proxy = "http://"
171
179
  end
172
180
  @client.proxy = ""
173
181
  assert_nil(@client.proxy)
@@ -387,6 +395,17 @@ EOS
387
395
  assert_equal('message body 1', res.content)
388
396
  end
389
397
 
398
+ def test_request_uri_in_response
399
+ @client.test_loopback_http_response << "HTTP/1.0 200 OK\ncontent-length: 100\n\nmessage body"
400
+ assert_equal(URI('http://google.com/'), @client.get('http://google.com/').header.request_uri)
401
+ end
402
+
403
+ def test_request_uri_in_response_when_redirect
404
+ expected = URI(serverurl + 'hello')
405
+ assert_equal(expected, @client.get(serverurl + 'redirect1', :follow_redirect => true).header.request_uri)
406
+ assert_equal(expected, @client.get(serverurl + 'redirect2', :follow_redirect => true).header.request_uri)
407
+ end
408
+
390
409
  def test_redirect_non_https
391
410
  url = serverurl + 'redirect1'
392
411
  https_url = URI.parse(url)
@@ -584,6 +603,13 @@ EOS
584
603
  assert_nil(res.contenttype)
585
604
  end
586
605
 
606
+ def test_head_follow_redirect
607
+ expected = URI(serverurl + 'hello')
608
+ assert_equal(expected, @client.head(serverurl + 'hello', :follow_redirect => true).header.request_uri)
609
+ assert_equal(expected, @client.head(serverurl + 'redirect1', :follow_redirect => true).header.request_uri)
610
+ assert_equal(expected, @client.head(serverurl + 'redirect2', :follow_redirect => true).header.request_uri)
611
+ end
612
+
587
613
  def test_get_follow_redirect
588
614
  assert_equal('hello', @client.get(serverurl + 'hello', :follow_redirect => true).body)
589
615
  assert_equal('hello', @client.get(serverurl + 'redirect1', :follow_redirect => true).body)
@@ -777,8 +803,10 @@ EOS
777
803
  def test_put
778
804
  assert_equal("put", @client.put(serverurl + 'servlet').content)
779
805
  param = {'1'=>'2', '3'=>'4'}
806
+ @client.debug_dev = str = ''
780
807
  res = @client.put(serverurl + 'servlet', param)
781
808
  assert_equal(param, params(res.header["x-query"][0]))
809
+ assert_equal('Content-Type: application/x-www-form-urlencoded', str.split(/\r?\n/)[5])
782
810
  end
783
811
 
784
812
  def test_put_bytesize
@@ -799,6 +827,14 @@ EOS
799
827
  assert_equal("delete", @client.delete(serverurl + 'servlet').content)
800
828
  end
801
829
 
830
+ # Not prohibited by spec, but normally it's ignored
831
+ def test_delete_with_body
832
+ param = {'1'=>'2', '3'=>'4'}
833
+ @client.debug_dev = str = ''
834
+ assert_equal("delete", @client.delete(serverurl + 'servlet', param).content)
835
+ assert_equal({'1' => ['2'], '3' => ['4']}, HTTP::Message.parse(str.split(/\r?\n\r?\n/)[2]))
836
+ end
837
+
802
838
  def test_delete_async
803
839
  conn = @client.delete_async(serverurl + 'servlet')
804
840
  Thread.pass while !conn.finished?
@@ -882,6 +918,18 @@ EOS
882
918
  assert_equal({}, check_query_get(''))
883
919
  assert_equal({'1'=>'2'}, check_query_get({1=>StringIO.new('2')}))
884
920
  assert_equal({'1'=>'2', '3'=>'4'}, check_query_get(StringIO.new('3=4&1=2')))
921
+
922
+ hash = check_query_get({"a"=>["A","a"], "B"=>"b"})
923
+ assert_equal({'a'=>'A', 'B'=>'b'}, hash)
924
+ assert_equal(['A','a'], hash['a'].to_ary)
925
+
926
+ hash = check_query_get({"a"=>WEBrick::HTTPUtils::FormData.new("A","a"), "B"=>"b"})
927
+ assert_equal({'a'=>'A', 'B'=>'b'}, hash)
928
+ assert_equal(['A','a'], hash['a'].to_ary)
929
+
930
+ hash = check_query_get({"a"=>[StringIO.new("A"),StringIO.new("a")], "B"=>StringIO.new("b")})
931
+ assert_equal({'a'=>'A', 'B'=>'b'}, hash)
932
+ assert_equal(['A','a'], hash['a'].to_ary)
885
933
  end
886
934
 
887
935
  def test_post_body
@@ -1002,7 +1050,7 @@ EOS
1002
1050
  def test_cookies
1003
1051
  cookiefile = File.join(File.dirname(File.expand_path(__FILE__)), 'test_cookies_file')
1004
1052
  File.open(cookiefile, "wb") do |f|
1005
- f << "http://rubyforge.org/account/login.php session_ser LjEwMy45Ni40Ni0q%2A-fa0537de8cc31 2000000000 .rubyforge.org / 13\n"
1053
+ f << "http://rubyforge.org/account/login.php\tsession_ser\tLjEwMy45Ni40Ni0q%2A-fa0537de8cc31\t2000000000\t.rubyforge.org\t/\t13\n"
1006
1054
  end
1007
1055
  @client.set_cookie_store(cookiefile)
1008
1056
  cookie = @client.cookie_manager.cookies.first
@@ -1014,7 +1062,7 @@ EOS
1014
1062
  @client.get_content('http://rubyforge.org/account/login.php')
1015
1063
  @client.save_cookie_store
1016
1064
  str = File.read(cookiefile)
1017
- assert_match(%r(http://rubyforge.org/account/login.php foo bar 1924873200 rubyforge.org /account 1), str)
1065
+ assert_match(%r(http://rubyforge.org/account/login.php\tfoo\tbar\t1924873200\trubyforge.org\t/account\t1), str)
1018
1066
  File.unlink(cookiefile)
1019
1067
  end
1020
1068
 
@@ -1460,8 +1508,8 @@ private
1460
1508
  @serverport = @server.config[:Port]
1461
1509
  [:hello, :sleep, :servlet_redirect, :redirect1, :redirect2, :redirect3, :redirect_self, :relative_redirect, :chunked, :largebody, :status, :compressed, :charset].each do |sym|
1462
1510
  @server.mount(
1463
- "/#{sym}",
1464
- WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
1511
+ "/#{sym}",
1512
+ WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
1465
1513
  )
1466
1514
  end
1467
1515
  @server.mount('/servlet', TestServlet.new(@server))
@@ -1489,11 +1537,11 @@ private
1489
1537
  end
1490
1538
 
1491
1539
  def do_servlet_redirect(req, res)
1492
- res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "servlet")
1540
+ res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "servlet")
1493
1541
  end
1494
1542
 
1495
1543
  def do_redirect1(req, res)
1496
- res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, serverurl + "hello")
1544
+ res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, serverurl + "hello")
1497
1545
  end
1498
1546
 
1499
1547
  def do_redirect2(req, res)
@@ -1501,15 +1549,15 @@ private
1501
1549
  end
1502
1550
 
1503
1551
  def do_redirect3(req, res)
1504
- res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "hello")
1552
+ res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "hello")
1505
1553
  end
1506
1554
 
1507
1555
  def do_redirect_self(req, res)
1508
- res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "redirect_self")
1556
+ res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "redirect_self")
1509
1557
  end
1510
1558
 
1511
1559
  def do_relative_redirect(req, res)
1512
- res.set_redirect(WEBrick::HTTPStatus::Found, "hello")
1560
+ res.set_redirect(WEBrick::HTTPStatus::Found, "hello")
1513
1561
  end
1514
1562
 
1515
1563
  def do_chunked(req, res)
@@ -1555,7 +1603,7 @@ private
1555
1603
  end
1556
1604
 
1557
1605
  def do_HEAD(req, res)
1558
- res["x-head"] = 'head' # use this for test purpose only.
1606
+ res["x-head"] = 'head' # use this for test purpose only.
1559
1607
  res["x-query"] = query_response(req)
1560
1608
  end
1561
1609
 
@@ -1615,9 +1663,9 @@ private
1615
1663
  def query_escape(query)
1616
1664
  escaped = []
1617
1665
  query.sort_by { |k, v| k }.collect do |k, v|
1618
- v.to_ary.each do |ve|
1619
- escaped << CGI.escape(k) + '=' + CGI.escape(ve)
1620
- end
1666
+ v.to_ary.each do |ve|
1667
+ escaped << CGI.escape(k) + '=' + CGI.escape(ve)
1668
+ end
1621
1669
  end
1622
1670
  escaped.join('&')
1623
1671
  end
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('helper', File.dirname(__FILE__))
3
+
4
+ require 'httpclient/include_client'
5
+ class TestIncludeClient < Test::Unit::TestCase
6
+ class Widget
7
+ extend HTTPClient::IncludeClient
8
+
9
+ include_http_client("http://example.com") do |client|
10
+ client.cookie_manager = nil
11
+ client.agent_name = "iMonkey 4k"
12
+ end
13
+ end
14
+
15
+ class OtherWidget
16
+ extend HTTPClient::IncludeClient
17
+
18
+ include_http_client
19
+ include_http_client(:method_name => :other_http_client)
20
+ end
21
+
22
+ class UnrelatedBlankClass ; end
23
+
24
+ def test_client_class_level_singleton
25
+ assert_equal Widget.http_client.object_id, Widget.http_client.object_id
26
+
27
+ assert_equal Widget.http_client.object_id, Widget.new.http_client.object_id
28
+
29
+ assert_not_equal Widget.http_client.object_id, OtherWidget.http_client.object_id
30
+ end
31
+
32
+ def test_configured
33
+ assert_equal Widget.http_client.agent_name, "iMonkey 4k"
34
+ assert_nil Widget.http_client.cookie_manager
35
+ assert_equal Widget.http_client.proxy.to_s, "http://example.com"
36
+ end
37
+
38
+ def test_two_includes
39
+ assert_not_equal OtherWidget.http_client.object_id, OtherWidget.other_http_client.object_id
40
+
41
+ assert_equal OtherWidget.other_http_client.object_id, OtherWidget.new.other_http_client.object_id
42
+ end
43
+
44
+ # meta-programming gone wrong sometimes accidentally
45
+ # adds the class method to _everyone_, a mistake we've made before.
46
+ def test_not_infected_class_hieararchy
47
+ assert ! Class.respond_to?(:http_client)
48
+ assert ! UnrelatedBlankClass.respond_to?(:http_client)
49
+ end
50
+
51
+
52
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.5
4
+ version: 2.2.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-06 00:00:00.000000000 Z
12
+ date: 2012-08-14 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: nahi@ruby-lang.org
@@ -24,6 +24,7 @@ files:
24
24
  - lib/httpclient/timeout.rb
25
25
  - lib/httpclient/version.rb
26
26
  - lib/httpclient/connection.rb
27
+ - lib/httpclient/include_client.rb
27
28
  - lib/httpclient/session.rb
28
29
  - lib/httpclient/cacert.p7s
29
30
  - lib/httpclient/cookie.rb
@@ -46,6 +47,7 @@ files:
46
47
  - sample/ssl/htdocs/index.html
47
48
  - sample/ssl/0cert.pem
48
49
  - sample/ssl/webrick_httpsd.rb
50
+ - sample/oauth_salesforce_10.rb
49
51
  - sample/thread.rb
50
52
  - sample/oauth_friendfeed.rb
51
53
  - sample/oauth_twitter.rb
@@ -63,6 +65,7 @@ files:
63
65
  - test/htpasswd
64
66
  - test/test_auth.rb
65
67
  - test/client.key
68
+ - test/test_include_client.rb
66
69
  - test/helper.rb
67
70
  - test/ca.cert
68
71
  - test/sslsvr.rb
@@ -85,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
88
  version: '0'
86
89
  segments:
87
90
  - 0
88
- hash: -3213102379838367907
91
+ hash: 1629827102590288352
89
92
  required_rubygems_version: !ruby/object:Gem::Requirement
90
93
  none: false
91
94
  requirements:
@@ -94,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
97
  version: '0'
95
98
  segments:
96
99
  - 0
97
- hash: -3213102379838367907
100
+ hash: 1629827102590288352
98
101
  requirements: []
99
102
  rubyforge_project:
100
103
  rubygems_version: 1.8.23