glebtv-httpclient 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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