glebtv-httpclient 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +108 -0
  3. data/bin/httpclient +65 -0
  4. data/lib/glebtv-httpclient.rb +1 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/http-access2.rb +55 -0
  9. data/lib/httpclient/auth.rb +899 -0
  10. data/lib/httpclient/cacert.p7s +1912 -0
  11. data/lib/httpclient/connection.rb +88 -0
  12. data/lib/httpclient/cookie.rb +438 -0
  13. data/lib/httpclient/http.rb +1050 -0
  14. data/lib/httpclient/include_client.rb +83 -0
  15. data/lib/httpclient/session.rb +1031 -0
  16. data/lib/httpclient/ssl_config.rb +403 -0
  17. data/lib/httpclient/timeout.rb +140 -0
  18. data/lib/httpclient/util.rb +186 -0
  19. data/lib/httpclient/version.rb +3 -0
  20. data/lib/httpclient.rb +1157 -0
  21. data/lib/oauthclient.rb +110 -0
  22. data/sample/async.rb +8 -0
  23. data/sample/auth.rb +11 -0
  24. data/sample/cookie.rb +18 -0
  25. data/sample/dav.rb +103 -0
  26. data/sample/howto.rb +49 -0
  27. data/sample/oauth_buzz.rb +57 -0
  28. data/sample/oauth_friendfeed.rb +59 -0
  29. data/sample/oauth_twitter.rb +61 -0
  30. data/sample/ssl/0cert.pem +22 -0
  31. data/sample/ssl/0key.pem +30 -0
  32. data/sample/ssl/1000cert.pem +19 -0
  33. data/sample/ssl/1000key.pem +18 -0
  34. data/sample/ssl/htdocs/index.html +10 -0
  35. data/sample/ssl/ssl_client.rb +22 -0
  36. data/sample/ssl/webrick_httpsd.rb +29 -0
  37. data/sample/stream.rb +21 -0
  38. data/sample/thread.rb +27 -0
  39. data/sample/wcat.rb +21 -0
  40. data/test/ca-chain.cert +44 -0
  41. data/test/ca.cert +23 -0
  42. data/test/client.cert +19 -0
  43. data/test/client.key +15 -0
  44. data/test/helper.rb +129 -0
  45. data/test/htdigest +1 -0
  46. data/test/htpasswd +2 -0
  47. data/test/runner.rb +2 -0
  48. data/test/server.cert +19 -0
  49. data/test/server.key +15 -0
  50. data/test/sslsvr.rb +65 -0
  51. data/test/subca.cert +21 -0
  52. data/test/test_auth.rb +321 -0
  53. data/test/test_cookie.rb +412 -0
  54. data/test/test_hexdump.rb +14 -0
  55. data/test/test_http-access2.rb +507 -0
  56. data/test/test_httpclient.rb +1801 -0
  57. data/test/test_include_client.rb +52 -0
  58. data/test/test_ssl.rb +235 -0
  59. metadata +102 -0
@@ -0,0 +1,403 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ class HTTPClient
10
+
11
+ begin
12
+ require 'openssl'
13
+ SSLEnabled = true
14
+ rescue LoadError
15
+ SSLEnabled = false
16
+ end
17
+
18
+ # Represents SSL configuration for HTTPClient instance.
19
+ # The implementation depends on OpenSSL.
20
+ #
21
+ # == Trust Anchor Control
22
+ #
23
+ # SSLConfig loads 'httpclient/cacert.p7s' as a trust anchor
24
+ # (trusted certificate(s)) with add_trust_ca in initialization time.
25
+ # This means that HTTPClient instance trusts some CA certificates by default,
26
+ # like Web browsers. 'httpclient/cacert.p7s' is created by the author and
27
+ # included in released package.
28
+ #
29
+ # 'cacert.p7s' is automatically generated from JDK 1.6. Regardless its
30
+ # filename extension (p7s), HTTPClient doesn't verify the signature in it.
31
+ #
32
+ # You may want to change trust anchor by yourself. Call clear_cert_store
33
+ # then add_trust_ca for that purpose.
34
+ class SSLConfig
35
+ include OpenSSL if SSLEnabled
36
+
37
+ # String name of OpenSSL's SSL version method name: SSLv2, SSLv23 or SSLv3
38
+ attr_reader :ssl_version
39
+ # OpenSSL::X509::Certificate:: certificate for SSL client authenticateion.
40
+ # nil by default. (no client authenticateion)
41
+ attr_reader :client_cert
42
+ # OpenSSL::PKey::PKey:: private key for SSL client authentication.
43
+ # nil by default. (no client authenticateion)
44
+ attr_reader :client_key
45
+
46
+ # A number which represents OpenSSL's verify mode. Default value is
47
+ # OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
48
+ attr_reader :verify_mode
49
+ # A number of verify depth. Certification path which length is longer than
50
+ # this depth is not allowed.
51
+ attr_reader :verify_depth
52
+ # A callback handler for custom certificate verification. nil by default.
53
+ # If the handler is set, handler.call is invoked just after general
54
+ # OpenSSL's verification. handler.call is invoked with 2 arguments,
55
+ # ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
56
+ # OpenSSL::X509::StoreContext.
57
+ attr_reader :verify_callback
58
+ # SSL timeout in sec. nil by default.
59
+ attr_reader :timeout
60
+ # A number of OpenSSL's SSL options. Default value is
61
+ # OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
62
+ attr_reader :options
63
+ # A String of OpenSSL's cipher configuration. Default value is
64
+ # ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
65
+ # See ciphers(1) man in OpenSSL for more detail.
66
+ attr_reader :ciphers
67
+
68
+ # OpenSSL::X509::X509::Store used for verification. You can reset the
69
+ # store with clear_cert_store and set the new store with cert_store=.
70
+ attr_reader :cert_store # don't use if you don't know what it is.
71
+
72
+ # For server side configuration. Ignore this.
73
+ attr_reader :client_ca # :nodoc:
74
+
75
+ # Creates a SSLConfig.
76
+ def initialize(client)
77
+ return unless SSLEnabled
78
+ @client = client
79
+ @cert_store = X509::Store.new
80
+ @client_cert = @client_key = @client_ca = nil
81
+ @verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
82
+ @verify_depth = nil
83
+ @verify_callback = nil
84
+ @dest = nil
85
+ @timeout = nil
86
+ @ssl_version = "SSLv3"
87
+ @options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
88
+ # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
89
+ @ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
90
+ @cacerts_loaded = false
91
+ end
92
+
93
+ # Sets SSL version method String. Possible values: "SSLv2" for SSL2,
94
+ # "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
95
+ def ssl_version=(ssl_version)
96
+ @ssl_version = ssl_version
97
+ change_notify
98
+ end
99
+
100
+ # Sets certificate (OpenSSL::X509::Certificate) for SSL client
101
+ # authentication.
102
+ # client_key and client_cert must be a pair.
103
+ #
104
+ # Calling this method resets all existing sessions.
105
+ def client_cert=(client_cert)
106
+ @client_cert = client_cert
107
+ change_notify
108
+ end
109
+
110
+ # Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
111
+ # client_key and client_cert must be a pair.
112
+ #
113
+ # Calling this method resets all existing sessions.
114
+ def client_key=(client_key)
115
+ @client_key = client_key
116
+ change_notify
117
+ end
118
+
119
+ # Sets certificate and private key for SSL client authentication.
120
+ # cert_file:: must be a filename of PEM/DER formatted file.
121
+ # key_file:: must be a filename of PEM/DER formatted file. Key must be an
122
+ # RSA key. If you want to use other PKey algorithm,
123
+ # use client_key=.
124
+ #
125
+ # Calling this method resets all existing sessions.
126
+ def set_client_cert_file(cert_file, key_file)
127
+ @client_cert = X509::Certificate.new(File.open(cert_file) { |f| f.read })
128
+ @client_key = PKey::RSA.new(File.open(key_file) { |f| f.read })
129
+ change_notify
130
+ end
131
+
132
+ # Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
133
+ # configured to use OS's trusted CA certificates located at
134
+ # /etc/pki/certs or /etc/ssl/certs. Unfortunately OpenSSL's Windows build
135
+ # does not work with Windows Certificate Storage.
136
+ #
137
+ # On Windows or when you build OpenSSL manually, you can set the
138
+ # CA certificates directory by SSL_CERT_DIR env variable at runtime.
139
+ #
140
+ # SSL_CERT_DIR=/etc/ssl/certs ruby -rhttpclient -e "..."
141
+ #
142
+ # Calling this method resets all existing sessions.
143
+ def set_default_paths
144
+ @cacerts_loaded = true # avoid lazy override
145
+ @cert_store = X509::Store.new
146
+ @cert_store.set_default_paths
147
+ change_notify
148
+ end
149
+
150
+ # Drops current certificate store (OpenSSL::X509::Store) for SSL and create
151
+ # new one for the next session.
152
+ #
153
+ # Calling this method resets all existing sessions.
154
+ def clear_cert_store
155
+ @cacerts_loaded = true # avoid lazy override
156
+ @cert_store = X509::Store.new
157
+ change_notify
158
+ end
159
+
160
+ # Sets new certificate store (OpenSSL::X509::Store).
161
+ # don't use if you don't know what it is.
162
+ #
163
+ # Calling this method resets all existing sessions.
164
+ def cert_store=(cert_store)
165
+ @cacerts_loaded = true # avoid lazy override
166
+ @cert_store = cert_store
167
+ change_notify
168
+ end
169
+
170
+ # Sets trust anchor certificate(s) for verification.
171
+ # trust_ca_file_or_hashed_dir:: a filename of a PEM/DER formatted
172
+ # OpenSSL::X509::Certificate or
173
+ # a 'c-rehash'eddirectory name which stores
174
+ # trusted certificate files.
175
+ #
176
+ # Calling this method resets all existing sessions.
177
+ def add_trust_ca(trust_ca_file_or_hashed_dir)
178
+ @cacerts_loaded = true # avoid lazy override
179
+ add_trust_ca_to_store(@cert_store, trust_ca_file_or_hashed_dir)
180
+ change_notify
181
+ end
182
+ alias set_trust_ca add_trust_ca
183
+
184
+ def add_trust_ca_to_store(cert_store, trust_ca_file_or_hashed_dir)
185
+ if FileTest.directory?(trust_ca_file_or_hashed_dir)
186
+ cert_store.add_path(trust_ca_file_or_hashed_dir)
187
+ else
188
+ cert_store.add_file(trust_ca_file_or_hashed_dir)
189
+ end
190
+ end
191
+
192
+ # Loads default trust anchors.
193
+ # Calling this method resets all existing sessions.
194
+ def load_trust_ca
195
+ load_cacerts(@cert_store)
196
+ change_notify
197
+ end
198
+
199
+ # Adds CRL for verification.
200
+ # crl:: a OpenSSL::X509::CRL or a filename of a PEM/DER formatted
201
+ # OpenSSL::X509::CRL.
202
+ #
203
+ # Calling this method resets all existing sessions.
204
+ def add_crl(crl)
205
+ unless crl.is_a?(X509::CRL)
206
+ crl = X509::CRL.new(File.open(crl) { |f| f.read })
207
+ end
208
+ @cert_store.add_crl(crl)
209
+ @cert_store.flags = X509::V_FLAG_CRL_CHECK | X509::V_FLAG_CRL_CHECK_ALL
210
+ change_notify
211
+ end
212
+ alias set_crl add_crl
213
+
214
+ # Sets verify mode of OpenSSL. New value must be a combination of
215
+ # constants OpenSSL::SSL::VERIFY_*
216
+ #
217
+ # Calling this method resets all existing sessions.
218
+ def verify_mode=(verify_mode)
219
+ @verify_mode = verify_mode
220
+ change_notify
221
+ end
222
+
223
+ # Sets verify depth. New value must be a number.
224
+ #
225
+ # Calling this method resets all existing sessions.
226
+ def verify_depth=(verify_depth)
227
+ @verify_depth = verify_depth
228
+ change_notify
229
+ end
230
+
231
+ # Sets callback handler for custom certificate verification.
232
+ # See verify_callback.
233
+ #
234
+ # Calling this method resets all existing sessions.
235
+ def verify_callback=(verify_callback)
236
+ @verify_callback = verify_callback
237
+ change_notify
238
+ end
239
+
240
+ # Sets SSL timeout in sec.
241
+ #
242
+ # Calling this method resets all existing sessions.
243
+ def timeout=(timeout)
244
+ @timeout = timeout
245
+ change_notify
246
+ end
247
+
248
+ # Sets SSL options. New value must be a combination of # constants
249
+ # OpenSSL::SSL::OP_*
250
+ #
251
+ # Calling this method resets all existing sessions.
252
+ def options=(options)
253
+ @options = options
254
+ change_notify
255
+ end
256
+
257
+ # Sets cipher configuration. New value must be a String.
258
+ #
259
+ # Calling this method resets all existing sessions.
260
+ def ciphers=(ciphers)
261
+ @ciphers = ciphers
262
+ change_notify
263
+ end
264
+
265
+ def client_ca=(client_ca) # :nodoc:
266
+ @client_ca = client_ca
267
+ change_notify
268
+ end
269
+
270
+ # interfaces for SSLSocketWrap.
271
+ def set_context(ctx) # :nodoc:
272
+ load_trust_ca unless @cacerts_loaded
273
+ @cacerts_loaded = true
274
+ # Verification: Use Store#verify_callback instead of SSLContext#verify*?
275
+ ctx.cert_store = @cert_store
276
+ ctx.verify_mode = @verify_mode
277
+ ctx.verify_depth = @verify_depth if @verify_depth
278
+ ctx.verify_callback = @verify_callback || method(:default_verify_callback)
279
+ # SSL config
280
+ ctx.cert = @client_cert
281
+ ctx.key = @client_key
282
+ ctx.client_ca = @client_ca
283
+ ctx.timeout = @timeout
284
+ ctx.options = @options
285
+ ctx.ciphers = @ciphers
286
+ ctx.ssl_version = @ssl_version
287
+ end
288
+
289
+ # post connection check proc for ruby < 1.8.5.
290
+ # this definition must match with the one in ext/openssl/lib/openssl/ssl.rb
291
+ def post_connection_check(peer_cert, hostname) # :nodoc:
292
+ check_common_name = true
293
+ cert = peer_cert
294
+ cert.extensions.each{|ext|
295
+ next if ext.oid != "subjectAltName"
296
+ ext.value.split(/,\s+/).each{|general_name|
297
+ if /\ADNS:(.*)/ =~ general_name
298
+ check_common_name = false
299
+ reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
300
+ return true if /\A#{reg}\z/i =~ hostname
301
+ elsif /\AIP Address:(.*)/ =~ general_name
302
+ check_common_name = false
303
+ return true if $1 == hostname
304
+ end
305
+ }
306
+ }
307
+ if check_common_name
308
+ cert.subject.to_a.each{|oid, value|
309
+ if oid == "CN"
310
+ reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
311
+ return true if /\A#{reg}\z/i =~ hostname
312
+ end
313
+ }
314
+ end
315
+ raise SSL::SSLError, "hostname was not match with the server certificate"
316
+ end
317
+
318
+ # Default callback for verification: only dumps error.
319
+ def default_verify_callback(is_ok, ctx)
320
+ if $DEBUG
321
+ if is_ok
322
+ warn("ok: #{ctx.current_cert.subject.to_s.dump}")
323
+ else
324
+ warn("ng: #{ctx.current_cert.subject.to_s.dump} at depth #{ctx.error_depth} - #{ctx.error}: #{ctx.error_string} in #{ctx.chain.inspect}")
325
+ end
326
+ warn(ctx.current_cert.to_text)
327
+ warn(ctx.current_cert.to_pem)
328
+ end
329
+ if !is_ok
330
+ depth = ctx.error_depth
331
+ code = ctx.error
332
+ msg = ctx.error_string
333
+ warn("at depth #{depth} - #{code}: #{msg}")
334
+ end
335
+ is_ok
336
+ end
337
+
338
+ # Sample callback method: CAUTION: does not check CRL/ARL.
339
+ def sample_verify_callback(is_ok, ctx)
340
+ unless is_ok
341
+ depth = ctx.error_depth
342
+ code = ctx.error
343
+ msg = ctx.error_string
344
+ warn("at depth #{depth} - #{code}: #{msg}") if $DEBUG
345
+ return false
346
+ end
347
+
348
+ cert = ctx.current_cert
349
+ self_signed = false
350
+ ca = false
351
+ pathlen = nil
352
+ server_auth = true
353
+ self_signed = (cert.subject.cmp(cert.issuer) == 0)
354
+
355
+ # Check extensions whatever its criticality is. (sample)
356
+ cert.extensions.each do |ex|
357
+ case ex.oid
358
+ when 'basicConstraints'
359
+ /CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value
360
+ ca = ($1 == 'TRUE')
361
+ pathlen = $2.to_i
362
+ when 'keyUsage'
363
+ usage = ex.value.split(/\s*,\s*/)
364
+ ca = usage.include?('Certificate Sign')
365
+ server_auth = usage.include?('Key Encipherment')
366
+ when 'extendedKeyUsage'
367
+ usage = ex.value.split(/\s*,\s*/)
368
+ server_auth = usage.include?('Netscape Server Gated Crypto')
369
+ when 'nsCertType'
370
+ usage = ex.value.split(/\s*,\s*/)
371
+ ca = usage.include?('SSL CA')
372
+ server_auth = usage.include?('SSL Server')
373
+ end
374
+ end
375
+
376
+ if self_signed
377
+ warn('self signing CA') if $DEBUG
378
+ return true
379
+ elsif ca
380
+ warn('middle level CA') if $DEBUG
381
+ return true
382
+ elsif server_auth
383
+ warn('for server authentication') if $DEBUG
384
+ return true
385
+ end
386
+
387
+ return false
388
+ end
389
+
390
+ private
391
+
392
+ def change_notify
393
+ @client.reset_all
394
+ end
395
+
396
+ def load_cacerts(cert_store)
397
+ file = File.join(File.dirname(__FILE__), 'cacert.p7s')
398
+ add_trust_ca_to_store(cert_store, file)
399
+ end
400
+ end
401
+
402
+
403
+ end
@@ -0,0 +1,140 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ require 'timeout'
10
+ require 'thread'
11
+
12
+
13
+ class HTTPClient
14
+
15
+
16
+ # Replaces timeout.rb to avoid Thread creation and scheduling overhead.
17
+ #
18
+ # You should check another timeout replace in WEBrick.
19
+ # See lib/webrick/utils.rb in ruby/1.9.
20
+ #
21
+ # About this implementation:
22
+ # * Do not create Thread for each timeout() call. Just create 1 Thread for
23
+ # timeout scheduler.
24
+ # * Do not wakeup the scheduler thread so often. Let scheduler thread sleep
25
+ # until the nearest period.
26
+ if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
27
+ class TimeoutScheduler
28
+
29
+ # Represents timeout period.
30
+ class Period
31
+ attr_reader :thread, :time
32
+
33
+ # Creates new Period.
34
+ def initialize(thread, time, ex)
35
+ @thread, @time, @ex = thread, time, ex
36
+ @lock = Mutex.new
37
+ end
38
+
39
+ # Raises if thread exists and alive.
40
+ def raise(message)
41
+ @lock.synchronize do
42
+ if @thread and @thread.alive?
43
+ @thread.raise(@ex, message)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Cancel this Period. Mutex is needed to avoid too-late exception.
49
+ def cancel
50
+ @lock.synchronize do
51
+ @thread = nil
52
+ end
53
+ end
54
+ end
55
+
56
+ # Creates new TimeoutScheduler.
57
+ def initialize
58
+ @pool = {}
59
+ @next = nil
60
+ @thread = start_timer_thread
61
+ end
62
+
63
+ # Registers new timeout period.
64
+ def register(thread, sec, ex)
65
+ period = Period.new(thread, Time.now + sec, ex || ::Timeout::Error)
66
+ @pool[period] = true
67
+ if @next.nil? or period.time < @next
68
+ begin
69
+ @thread.wakeup
70
+ rescue ThreadError
71
+ # Thread may be dead by fork.
72
+ @thread = start_timer_thread
73
+ end
74
+ end
75
+ period
76
+ end
77
+
78
+ # Cancels the given period.
79
+ def cancel(period)
80
+ @pool.delete(period)
81
+ period.cancel
82
+ end
83
+
84
+ private
85
+
86
+ def start_timer_thread
87
+ thread = Thread.new {
88
+ while true
89
+ if @pool.empty?
90
+ @next = nil
91
+ sleep
92
+ else
93
+ min, = @pool.min { |a, b| a[0].time <=> b[0].time }
94
+ @next = min.time
95
+ sec = @next - Time.now
96
+ if sec > 0
97
+ sleep(sec)
98
+ end
99
+ end
100
+ now = Time.now
101
+ @pool.keys.each do |period|
102
+ if period.time < now
103
+ period.raise('execution expired')
104
+ cancel(period)
105
+ end
106
+ end
107
+ end
108
+ }
109
+ Thread.pass while thread.status != 'sleep'
110
+ thread
111
+ end
112
+ end
113
+
114
+ class << self
115
+ # CAUTION: caller must aware of race condition.
116
+ def timeout_scheduler
117
+ @timeout_scheduler ||= TimeoutScheduler.new
118
+ end
119
+ end
120
+ timeout_scheduler # initialize at first time.
121
+ end
122
+
123
+ module Timeout
124
+ if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
125
+ def timeout(sec, ex = nil, &block)
126
+ return yield if sec == nil or sec.zero?
127
+ scheduler = nil
128
+ begin
129
+ scheduler = HTTPClient.timeout_scheduler
130
+ period = scheduler.register(Thread.current, sec, ex)
131
+ yield(sec)
132
+ ensure
133
+ scheduler.cancel(period) if scheduler and period
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+
140
+ end