magnoline 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,18 @@
1
+ Magnoline is a simple command line tool to import/export
2
+ The port done in ruby is good bit faster than the java implementation.
3
+
4
+ main_client.rb is the entry class, and you may want to type something like:
5
+ ruby main_client.rb --help
6
+
7
+ to see the different options.
8
+
9
+ This tool requires:
10
+ - cooloptions as a dependencies, but that should be pulled up by the gem install
11
+
12
+ This tool includes:
13
+ - http-access2: because it was not available as a separate gem. I will remove it from here
14
+ as soon as it is.
15
+
16
+ That's it. Enjoy !
17
+
18
+ Niko
@@ -0,0 +1,1588 @@
1
+ # HTTPAccess2 - HTTP accessing library.
2
+ # Copyright (C) 2000-2005 NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>.
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
+ # http-access2.rb is based on http-access.rb in http-access/0.0.4. Some part
9
+ # of code in http-access.rb was recycled in http-access2.rb. Those part is
10
+ # copyrighted by Maehashi-san.
11
+
12
+
13
+ # Ruby standard library
14
+ require 'timeout'
15
+ require 'uri'
16
+ require 'socket'
17
+ require 'thread'
18
+
19
+ # Extra library
20
+ require 'http-access2/http'
21
+ require 'http-access2/cookie'
22
+
23
+
24
+ module HTTPAccess2
25
+ VERSION = '2.0.6'
26
+ RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
27
+ s = %w$Id: http-access2.rb 114 2005-09-13 03:20:38Z nahi $
28
+ RCS_FILE, RCS_REVISION = s[1][/.*(?=,v$)/], s[2]
29
+
30
+ SSLEnabled = begin
31
+ require 'openssl'
32
+ true
33
+ rescue LoadError
34
+ false
35
+ end
36
+
37
+ DEBUG_SSL = true
38
+
39
+
40
+ # DESCRIPTION
41
+ # HTTPAccess2::Client -- Client to retrieve web resources via HTTP.
42
+ #
43
+ # How to create your client.
44
+ # 1. Create simple client.
45
+ # clnt = HTTPAccess2::Client.new
46
+ #
47
+ # 2. Accessing resources through HTTP proxy.
48
+ # clnt = HTTPAccess2::Client.new("http://myproxy:8080")
49
+ #
50
+ # 3. Set User-Agent and From in HTTP request header.(nil means "No proxy")
51
+ # clnt = HTTPAccess2::Client.new(nil, "MyAgent", "nahi@keynauts.com")
52
+ #
53
+ # How to retrieve web resources.
54
+ # 1. Get content of specified URL.
55
+ # puts clnt.get_content("http://www.ruby-lang.org/en/")
56
+ #
57
+ # 2. Do HEAD request.
58
+ # res = clnt.head(uri)
59
+ #
60
+ # 3. Do GET request with query.
61
+ # res = clnt.get(uri)
62
+ #
63
+ # 4. Do POST request.
64
+ # res = clnt.post(uri)
65
+ # res = clnt.get|post|head(uri, proxy)
66
+ #
67
+ class Client
68
+ attr_reader :agent_name
69
+ attr_reader :from
70
+ attr_reader :ssl_config
71
+ attr_accessor :cookie_manager
72
+ attr_reader :test_loopback_response
73
+
74
+ class << self
75
+ %w(get_content head get post put delete options trace).each do |name|
76
+ eval <<-EOD
77
+ def #{name}(*arg)
78
+ new.#{name}(*arg)
79
+ end
80
+ EOD
81
+ end
82
+ end
83
+
84
+ # SYNOPSIS
85
+ # Client.new(proxy = nil, agent_name = nil, from = nil)
86
+ #
87
+ # ARGS
88
+ # proxy A String of HTTP proxy URL. ex. "http://proxy:8080".
89
+ # agent_name A String for "User-Agent" HTTP request header.
90
+ # from A String for "From" HTTP request header.
91
+ #
92
+ # DESCRIPTION
93
+ # Create an instance.
94
+ # SSLConfig cannot be re-initialized. Create new client.
95
+ #
96
+ def initialize(proxy = nil, agent_name = nil, from = nil)
97
+ @proxy = nil # assigned later.
98
+ @no_proxy = nil
99
+ @agent_name = agent_name
100
+ @from = from
101
+ @basic_auth = BasicAuth.new(self)
102
+ @debug_dev = nil
103
+ @ssl_config = SSLConfig.new(self)
104
+ @redirect_uri_callback = method(:default_redirect_uri_callback)
105
+ @test_loopback_response = []
106
+ @session_manager = SessionManager.new
107
+ @session_manager.agent_name = @agent_name
108
+ @session_manager.from = @from
109
+ @session_manager.ssl_config = @ssl_config
110
+ @cookie_manager = WebAgent::CookieManager.new
111
+ self.proxy = proxy
112
+ end
113
+
114
+ def debug_dev
115
+ @debug_dev
116
+ end
117
+
118
+ def debug_dev=(dev)
119
+ @debug_dev = dev
120
+ reset_all
121
+ @session_manager.debug_dev = dev
122
+ end
123
+
124
+ def protocol_version
125
+ @session_manager.protocol_version
126
+ end
127
+
128
+ def protocol_version=(protocol_version)
129
+ reset_all
130
+ @session_manager.protocol_version = protocol_version
131
+ end
132
+
133
+ def connect_timeout
134
+ @session_manager.connect_timeout
135
+ end
136
+
137
+ def connect_timeout=(connect_timeout)
138
+ reset_all
139
+ @session_manager.connect_timeout = connect_timeout
140
+ end
141
+
142
+ def send_timeout
143
+ @session_manager.send_timeout
144
+ end
145
+
146
+ def send_timeout=(send_timeout)
147
+ reset_all
148
+ @session_manager.send_timeout = send_timeout
149
+ end
150
+
151
+ def receive_timeout
152
+ @session_manager.receive_timeout
153
+ end
154
+
155
+ def receive_timeout=(receive_timeout)
156
+ reset_all
157
+ @session_manager.receive_timeout = receive_timeout
158
+ end
159
+
160
+ def proxy
161
+ @proxy
162
+ end
163
+
164
+ def proxy=(proxy)
165
+ if proxy.nil?
166
+ @proxy = nil
167
+ else
168
+ if proxy.is_a?(URI)
169
+ @proxy = proxy
170
+ else
171
+ @proxy = URI.parse(proxy)
172
+ end
173
+ if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
174
+ @proxy.host == nil or @proxy.port == nil
175
+ raise ArgumentError.new("unsupported proxy `#{proxy}'")
176
+ end
177
+ end
178
+ reset_all
179
+ @proxy
180
+ end
181
+
182
+ def no_proxy
183
+ @no_proxy
184
+ end
185
+
186
+ def no_proxy=(no_proxy)
187
+ @no_proxy = no_proxy
188
+ reset_all
189
+ end
190
+
191
+ # if your ruby is older than 2005-09-06, do not set socket_sync = false to
192
+ # avoid an SSL socket blocking bug in openssl/buffering.rb.
193
+ def socket_sync=(socket_sync)
194
+ @session_manager.socket_sync = socket_sync
195
+ end
196
+
197
+ def set_basic_auth(uri, user_id, passwd)
198
+ unless uri.is_a?(URI)
199
+ uri = URI.parse(uri)
200
+ end
201
+ @basic_auth.set(uri, user_id, passwd)
202
+ end
203
+
204
+ def set_cookie_store(filename)
205
+ if @cookie_manager.cookies_file
206
+ raise RuntimeError.new("overriding cookie file location")
207
+ end
208
+ @cookie_manager.cookies_file = filename
209
+ @cookie_manager.load_cookies if filename
210
+ end
211
+
212
+ def save_cookie_store
213
+ @cookie_manager.save_cookies
214
+ end
215
+
216
+ def redirect_uri_callback=(redirect_uri_callback)
217
+ @redirect_uri_callback = redirect_uri_callback
218
+ end
219
+
220
+ # SYNOPSIS
221
+ # Client#get_content(uri, query = nil, extheader = {}, &block = nil)
222
+ #
223
+ # ARGS
224
+ # uri an_URI or a_string of uri to connect.
225
+ # query a_hash or an_array of query part. e.g. { "a" => "b" }.
226
+ # Give an array to pass multiple value like
227
+ # [["a" => "b"], ["a" => "c"]].
228
+ # extheader
229
+ # a_hash of extra headers like { "SOAPAction" => "urn:foo" }.
230
+ # &block Give a block to get chunked message-body of response like
231
+ # get_content(uri) { |chunked_body| ... }
232
+ # Size of each chunk may not be the same.
233
+ #
234
+ # DESCRIPTION
235
+ # Get a_sring of message-body of response.
236
+ #
237
+ def get_content(uri, query = nil, extheader = {}, &block)
238
+ retry_connect(uri, query) do |uri, query|
239
+ get(uri, query, extheader, &block)
240
+ end
241
+ end
242
+
243
+ def post_content(uri, body = nil, extheader = {}, &block)
244
+ retry_connect(uri, nil) do |uri, query|
245
+ post(uri, body, extheader, &block)
246
+ end
247
+ end
248
+
249
+ def default_redirect_uri_callback(res)
250
+ uri = res.header['location'][0]
251
+ puts "Redirect to: #{uri}" if $DEBUG
252
+ uri
253
+ end
254
+
255
+ def head(uri, query = nil, extheader = {})
256
+ request('HEAD', uri, query, nil, extheader)
257
+ end
258
+
259
+ def get(uri, query = nil, extheader = {}, &block)
260
+ request('GET', uri, query, nil, extheader, &block)
261
+ end
262
+
263
+ def post(uri, body = nil, extheader = {}, &block)
264
+ request('POST', uri, nil, body, extheader, &block)
265
+ end
266
+
267
+ def put(uri, body = nil, extheader = {}, &block)
268
+ request('PUT', uri, nil, body, extheader, &block)
269
+ end
270
+
271
+ def delete(uri, extheader = {}, &block)
272
+ request('DELETE', uri, nil, nil, extheader, &block)
273
+ end
274
+
275
+ def options(uri, extheader = {}, &block)
276
+ request('OPTIONS', uri, nil, nil, extheader, &block)
277
+ end
278
+
279
+ def trace(uri, query = nil, body = nil, extheader = {}, &block)
280
+ request('TRACE', uri, query, body, extheader, &block)
281
+ end
282
+
283
+ def request(method, uri, query = nil, body = nil, extheader = {}, &block)
284
+ conn = Connection.new
285
+ conn_request(conn, method, uri, query, body, extheader, &block)
286
+ conn.pop
287
+ end
288
+
289
+ # Async interface.
290
+
291
+ def head_async(uri, query = nil, extheader = {})
292
+ request_async('HEAD', uri, query, nil, extheader)
293
+ end
294
+
295
+ def get_async(uri, query = nil, extheader = {})
296
+ request_async('GET', uri, query, nil, extheader)
297
+ end
298
+
299
+ def post_async(uri, body = nil, extheader = {})
300
+ request_async('POST', uri, nil, body, extheader)
301
+ end
302
+
303
+ def put_async(uri, body = nil, extheader = {})
304
+ request_async('PUT', uri, nil, body, extheader)
305
+ end
306
+
307
+ def delete_async(uri, extheader = {})
308
+ request_async('DELETE', uri, nil, nil, extheader)
309
+ end
310
+
311
+ def options_async(uri, extheader = {})
312
+ request_async('OPTIONS', uri, nil, nil, extheader)
313
+ end
314
+
315
+ def trace_async(uri, query = nil, body = nil, extheader = {})
316
+ request_async('TRACE', uri, query, body, extheader)
317
+ end
318
+
319
+ def request_async(method, uri, query = nil, body = nil, extheader = {})
320
+ conn = Connection.new
321
+ t = Thread.new(conn) { |tconn|
322
+ conn_request(tconn, method, uri, query, body, extheader)
323
+ }
324
+ conn.async_thread = t
325
+ conn
326
+ end
327
+
328
+ ##
329
+ # Multiple call interface.
330
+
331
+ # ???
332
+
333
+ ##
334
+ # Management interface.
335
+
336
+ def reset(uri)
337
+ @session_manager.reset(uri)
338
+ end
339
+
340
+ def reset_all
341
+ @session_manager.reset_all
342
+ end
343
+
344
+ private
345
+
346
+ def retry_connect(uri, query = nil)
347
+ retry_number = 0
348
+ while retry_number < 10
349
+ res = yield(uri, query)
350
+ if res.status == HTTP::Status::OK
351
+ return res.content
352
+ elsif HTTP::Status.redirect?(res.status)
353
+ uri = @redirect_uri_callback.call(res)
354
+ query = nil
355
+ retry_number += 1
356
+ else
357
+ raise RuntimeError.new("Unexpected response: #{res.header.inspect}")
358
+ end
359
+ end
360
+ raise RuntimeError.new("Retry count exceeded.")
361
+ end
362
+
363
+ def conn_request(conn, method, uri, query, body, extheader, &block)
364
+ unless uri.is_a?(URI)
365
+ uri = URI.parse(uri)
366
+ end
367
+ proxy = no_proxy?(uri) ? nil : @proxy
368
+ begin
369
+ req = create_request(method, uri, query, body, extheader, !proxy.nil?)
370
+ do_get_block(req, proxy, conn, &block)
371
+ rescue Session::KeepAliveDisconnected
372
+ req = create_request(method, uri, query, body, extheader, !proxy.nil?)
373
+ do_get_block(req, proxy, conn, &block)
374
+ end
375
+ end
376
+
377
+ def create_request(method, uri, query, body, extheader, proxy)
378
+ if extheader.is_a?(Hash)
379
+ extheader = extheader.to_a
380
+ end
381
+ cred = @basic_auth.get(uri)
382
+ if cred
383
+ extheader << ['Authorization', "Basic " << cred]
384
+ end
385
+ if cookies = @cookie_manager.find(uri)
386
+ extheader << ['Cookie', cookies]
387
+ end
388
+ boundary = nil
389
+ content_type = extheader.find { |key, value|
390
+ key.downcase == 'content-type'
391
+ }
392
+ if content_type && content_type[1] =~ /boundary=(.+)\z/
393
+ boundary = $1
394
+ end
395
+ req = HTTP::Message.new_request(method, uri, query, body, proxy, boundary)
396
+ extheader.each do |key, value|
397
+ req.header.set(key, value)
398
+ end
399
+ if content_type.nil? and !body.nil?
400
+ req.header.set('content-type', 'application/x-www-form-urlencoded')
401
+ end
402
+ req
403
+ end
404
+
405
+ NO_PROXY_HOSTS = ['localhost']
406
+
407
+ def no_proxy?(uri)
408
+ if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
409
+ return true
410
+ end
411
+ unless @no_proxy
412
+ return false
413
+ end
414
+ @no_proxy.scan(/([^:,]+)(?::(\d+))?/) do |host, port|
415
+ if /(\A|\.)#{Regexp.quote(host)}\z/i =~ uri.host &&
416
+ (!port || uri.port == port.to_i)
417
+ return true
418
+ end
419
+ end
420
+ false
421
+ end
422
+
423
+ # !! CAUTION !!
424
+ # Method 'do_get*' runs under MT conditon. Be careful to change.
425
+ def do_get_block(req, proxy, conn, &block)
426
+ if str = @test_loopback_response.shift
427
+ dump_dummy_request_response(req.body.dump, str) if @debug_dev
428
+ conn.push(HTTP::Message.new_response(str))
429
+ return
430
+ end
431
+ content = ''
432
+ res = HTTP::Message.new_response(content)
433
+ @debug_dev << "= Request\n\n" if @debug_dev
434
+ sess = @session_manager.query(req, proxy)
435
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
436
+ do_get_header(req, res, sess)
437
+ conn.push(res)
438
+ sess.get_data() do |str|
439
+ block.call(str) if block
440
+ content << str
441
+ end
442
+ @session_manager.keep(sess) unless sess.closed?
443
+ end
444
+
445
+ def do_get_stream(req, proxy, conn)
446
+ if str = @test_loopback_response.shift
447
+ dump_dummy_request_response(req.body.dump, str) if @debug_dev
448
+ conn.push(HTTP::Message.new_response(str))
449
+ return
450
+ end
451
+ piper, pipew = IO.pipe
452
+ res = HTTP::Message.new_response(piper)
453
+ @debug_dev << "= Request\n\n" if @debug_dev
454
+ sess = @session_manager.query(req, proxy)
455
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
456
+ do_get_header(req, res, sess)
457
+ conn.push(res)
458
+ sess.get_data() do |str|
459
+ pipew.syswrite(str)
460
+ end
461
+ pipew.close
462
+ @session_manager.keep(sess) unless sess.closed?
463
+ end
464
+
465
+ def do_get_header(req, res, sess)
466
+ res.version, res.status, res.reason = sess.get_status
467
+ sess.get_header().each do |line|
468
+ unless /^([^:]+)\s*:\s*(.*)$/ =~ line
469
+ raise RuntimeError.new("Unparsable header: '#{line}'.") if $DEBUG
470
+ end
471
+ res.header.set($1, $2)
472
+ end
473
+ if res.header['set-cookie']
474
+ res.header['set-cookie'].each do |cookie|
475
+ @cookie_manager.parse(cookie, req.header.request_uri)
476
+ end
477
+ end
478
+ end
479
+
480
+ def dump_dummy_request_response(req, res)
481
+ @debug_dev << "= Dummy Request\n\n"
482
+ @debug_dev << req
483
+ @debug_dev << "\n\n= Dummy Response\n\n"
484
+ @debug_dev << res
485
+ end
486
+ end
487
+
488
+
489
+ # HTTPAccess2::SSLConfig -- SSL configuration of a client.
490
+ #
491
+ class SSLConfig # :nodoc:
492
+ attr_reader :client_cert
493
+ attr_reader :client_key
494
+ attr_reader :client_ca
495
+
496
+ attr_reader :verify_mode
497
+ attr_reader :verify_depth
498
+ attr_reader :verify_callback
499
+
500
+ attr_reader :timeout
501
+ attr_reader :options
502
+ attr_reader :ciphers
503
+
504
+ attr_reader :cert_store # don't use if you don't know what it is.
505
+
506
+ def initialize(client)
507
+ return unless SSLEnabled
508
+ @client = client
509
+ @cert_store = OpenSSL::X509::Store.new
510
+ @client_cert = @client_key = @client_ca = nil
511
+ @verify_mode = OpenSSL::SSL::VERIFY_PEER |
512
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
513
+ @verify_depth = nil
514
+ @verify_callback = nil
515
+ @dest = nil
516
+ @timeout = nil
517
+ @options = defined?(OpenSSL::SSL::OP_ALL) ?
518
+ OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2 : nil
519
+ @ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
520
+ end
521
+
522
+ def set_client_cert_file(cert_file, key_file)
523
+ @client_cert = OpenSSL::X509::Certificate.new(File.open(cert_file).read)
524
+ @client_key = OpenSSL::PKey::RSA.new(File.open(key_file).read)
525
+ change_notify
526
+ end
527
+
528
+ def set_trust_ca(trust_ca_file_or_hashed_dir)
529
+ if FileTest.directory?(trust_ca_file_or_hashed_dir)
530
+ @cert_store.add_path(trust_ca_file_or_hashed_dir)
531
+ else
532
+ @cert_store.add_file(trust_ca_file_or_hashed_dir)
533
+ end
534
+ change_notify
535
+ end
536
+
537
+ def set_crl(crl_file)
538
+ crl = OpenSSL::X509::CRL.new(File.open(crl_file).read)
539
+ @cert_store.add_crl(crl)
540
+ @cert_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
541
+ change_notify
542
+ end
543
+
544
+ def client_cert=(client_cert)
545
+ @client_cert = client_cert
546
+ change_notify
547
+ end
548
+
549
+ def client_key=(client_key)
550
+ @client_key = client_key
551
+ change_notify
552
+ end
553
+
554
+ def client_ca=(client_ca)
555
+ @client_ca = client_ca
556
+ change_notify
557
+ end
558
+
559
+ def verify_mode=(verify_mode)
560
+ @verify_mode = verify_mode
561
+ change_notify
562
+ end
563
+
564
+ def verify_depth=(verify_depth)
565
+ @verify_depth = verify_depth
566
+ change_notify
567
+ end
568
+
569
+ def verify_callback=(verify_callback)
570
+ @verify_callback = verify_callback
571
+ change_notify
572
+ end
573
+
574
+ def timeout=(timeout)
575
+ @timeout = timeout
576
+ change_notify
577
+ end
578
+
579
+ def options=(options)
580
+ @options = options
581
+ change_notify
582
+ end
583
+
584
+ def ciphers=(ciphers)
585
+ @ciphers = ciphers
586
+ change_notify
587
+ end
588
+
589
+ # don't use if you don't know what it is.
590
+ def cert_store=(cert_store)
591
+ @cert_store = cert_store
592
+ change_notify
593
+ end
594
+
595
+ # interfaces for SSLSocketWrap.
596
+
597
+ def set_context(ctx)
598
+ # Verification: Use Store#verify_callback instead of SSLContext#verify*?
599
+ ctx.cert_store = @cert_store
600
+ ctx.verify_mode = @verify_mode
601
+ ctx.verify_depth = @verify_depth if @verify_depth
602
+ ctx.verify_callback = @verify_callback || method(:default_verify_callback)
603
+ # SSL config
604
+ ctx.cert = @client_cert
605
+ ctx.key = @client_key
606
+ ctx.client_ca = @client_ca
607
+ ctx.timeout = @timeout
608
+ ctx.options = @options
609
+ ctx.ciphers = @ciphers
610
+ end
611
+
612
+ # this definition must match with the one in ext/openssl/lib/openssl/ssl.rb
613
+ def post_connection_check(peer_cert, hostname)
614
+ check_common_name = true
615
+ cert = peer_cert
616
+ cert.extensions.each{|ext|
617
+ next if ext.oid != "subjectAltName"
618
+ ext.value.split(/,\s+/).each{|general_name|
619
+ if /\ADNS:(.*)/ =~ general_name
620
+ check_common_name = false
621
+ reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
622
+ return true if /\A#{reg}\z/i =~ hostname
623
+ elsif /\AIP Address:(.*)/ =~ general_name
624
+ check_common_name = false
625
+ return true if $1 == hostname
626
+ end
627
+ }
628
+ }
629
+ if check_common_name
630
+ cert.subject.to_a.each{|oid, value|
631
+ if oid == "CN" && value.casecmp(hostname) == 0
632
+ return true
633
+ end
634
+ }
635
+ end
636
+ raise OpenSSL::SSL::SSLError, "hostname not match"
637
+ end
638
+
639
+ # Default callback for verification: only dumps error.
640
+ def default_verify_callback(is_ok, ctx)
641
+ if $DEBUG
642
+ puts "#{ is_ok ? 'ok' : 'ng' }: #{ctx.current_cert.subject}"
643
+ end
644
+ if !is_ok
645
+ depth = ctx.error_depth
646
+ code = ctx.error
647
+ msg = ctx.error_string
648
+ STDERR.puts "at depth #{depth} - #{code}: #{msg}"
649
+ end
650
+ is_ok
651
+ end
652
+
653
+ # Sample callback method: CAUTION: does not check CRL/ARL.
654
+ def sample_verify_callback(is_ok, ctx)
655
+ unless is_ok
656
+ depth = ctx.error_depth
657
+ code = ctx.error
658
+ msg = ctx.error_string
659
+ STDERR.puts "at depth #{depth} - #{code}: #{msg}" if $DEBUG
660
+ return false
661
+ end
662
+
663
+ cert = ctx.current_cert
664
+ self_signed = false
665
+ ca = false
666
+ pathlen = nil
667
+ server_auth = true
668
+ self_signed = (cert.subject.cmp(cert.issuer) == 0)
669
+
670
+ # Check extensions whatever its criticality is. (sample)
671
+ cert.extensions.each do |ex|
672
+ case ex.oid
673
+ when 'basicConstraints'
674
+ /CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value
675
+ ca = ($1 == 'TRUE')
676
+ pathlen = $2.to_i
677
+ when 'keyUsage'
678
+ usage = ex.value.split(/\s*,\s*/)
679
+ ca = usage.include?('Certificate Sign')
680
+ server_auth = usage.include?('Key Encipherment')
681
+ when 'extendedKeyUsage'
682
+ usage = ex.value.split(/\s*,\s*/)
683
+ server_auth = usage.include?('Netscape Server Gated Crypto')
684
+ when 'nsCertType'
685
+ usage = ex.value.split(/\s*,\s*/)
686
+ ca = usage.include?('SSL CA')
687
+ server_auth = usage.include?('SSL Server')
688
+ end
689
+ end
690
+
691
+ if self_signed
692
+ STDERR.puts 'self signing CA' if $DEBUG
693
+ return true
694
+ elsif ca
695
+ STDERR.puts 'middle level CA' if $DEBUG
696
+ return true
697
+ elsif server_auth
698
+ STDERR.puts 'for server authentication' if $DEBUG
699
+ return true
700
+ end
701
+
702
+ return false
703
+ end
704
+
705
+ private
706
+
707
+ def change_notify
708
+ @client.reset_all
709
+ end
710
+ end
711
+
712
+
713
+ # HTTPAccess2::BasicAuth -- BasicAuth repository.
714
+ #
715
+ class BasicAuth # :nodoc:
716
+ def initialize(client)
717
+ @client = client
718
+ @auth = {}
719
+ end
720
+
721
+ def set(uri, user_id, passwd)
722
+ uri = uri.clone
723
+ uri.path = uri.path.sub(/\/[^\/]*$/, '/')
724
+ @auth[uri] = ["#{user_id}:#{passwd}"].pack('m').strip
725
+ @client.reset_all
726
+ end
727
+
728
+ def get(uri)
729
+ @auth.each do |realm_uri, cred|
730
+ if ((realm_uri.host == uri.host) and
731
+ (realm_uri.scheme == uri.scheme) and
732
+ (realm_uri.port == uri.port) and
733
+ uri.path.upcase.index(realm_uri.path.upcase) == 0)
734
+ return cred
735
+ end
736
+ end
737
+ nil
738
+ end
739
+ end
740
+
741
+
742
+ # HTTPAccess2::Site -- manage a site(host and port)
743
+ #
744
+ class Site # :nodoc:
745
+ attr_accessor :scheme
746
+ attr_accessor :host
747
+ attr_reader :port
748
+
749
+ def initialize(uri = nil)
750
+ if uri
751
+ @scheme = uri.scheme
752
+ @host = uri.host
753
+ @port = uri.port.to_i
754
+ else
755
+ @scheme = 'tcp'
756
+ @host = '0.0.0.0'
757
+ @port = 0
758
+ end
759
+ end
760
+
761
+ def addr
762
+ "#{@scheme}://#{@host}:#{@port.to_s}"
763
+ end
764
+
765
+ def port=(port)
766
+ @port = port.to_i
767
+ end
768
+
769
+ def ==(rhs)
770
+ if rhs.is_a?(Site)
771
+ ((@scheme == rhs.scheme) and (@host == rhs.host) and (@port == rhs.port))
772
+ else
773
+ false
774
+ end
775
+ end
776
+
777
+ def to_s
778
+ addr
779
+ end
780
+
781
+ def inspect
782
+ sprintf("#<%s:0x%x %s>", self.class.name, __id__, addr)
783
+ end
784
+ end
785
+
786
+
787
+ # HTTPAccess2::Connection -- magage a connection(one request and response to it).
788
+ #
789
+ class Connection # :nodoc:
790
+ attr_accessor :async_thread
791
+
792
+ def initialize(header_queue = [], body_queue = [])
793
+ @headers = header_queue
794
+ @body = body_queue
795
+ @async_thread = nil
796
+ @queue = Queue.new
797
+ end
798
+
799
+ def finished?
800
+ if !@async_thread
801
+ # Not in async mode.
802
+ true
803
+ elsif @async_thread.alive?
804
+ # Working...
805
+ false
806
+ else
807
+ # Async thread have been finished.
808
+ @async_thread.join
809
+ true
810
+ end
811
+ end
812
+
813
+ def pop
814
+ @queue.pop
815
+ end
816
+
817
+ def push(result)
818
+ @queue.push(result)
819
+ end
820
+
821
+ def join
822
+ unless @async_thread
823
+ false
824
+ else
825
+ @async_thread.join
826
+ end
827
+ end
828
+ end
829
+
830
+
831
+ # HTTPAccess2::SessionManager -- manage several sessions.
832
+ #
833
+ class SessionManager # :nodoc:
834
+ attr_accessor :agent_name # Name of this client.
835
+ attr_accessor :from # Owner of this client.
836
+
837
+ attr_accessor :protocol_version # Requested protocol version
838
+ attr_accessor :chunk_size # Chunk size for chunked request
839
+ attr_accessor :debug_dev # Device for dumping log for debugging
840
+ attr_accessor :socket_sync # Boolean value for Socket#sync
841
+
842
+ # These parameters are not used now...
843
+ attr_accessor :connect_timeout
844
+ attr_accessor :connect_retry # Maximum retry count. 0 for infinite.
845
+ attr_accessor :send_timeout
846
+ attr_accessor :receive_timeout
847
+ attr_accessor :read_block_size
848
+
849
+ attr_accessor :ssl_config
850
+
851
+ def initialize
852
+ @proxy = nil
853
+
854
+ @agent_name = nil
855
+ @from = nil
856
+
857
+ @protocol_version = nil
858
+ @debug_dev = nil
859
+ @socket_sync = true
860
+ @chunk_size = 4096
861
+
862
+ @connect_timeout = 60
863
+ @connect_retry = 1
864
+ @send_timeout = 120
865
+ @receive_timeout = 60 # For each read_block_size bytes
866
+ @read_block_size = 8192
867
+
868
+ @ssl_config = nil
869
+
870
+ @sess_pool = []
871
+ @sess_pool_mutex = Mutex.new
872
+ end
873
+
874
+ def proxy=(proxy)
875
+ if proxy.nil?
876
+ @proxy = nil
877
+ else
878
+ @proxy = Site.new(proxy)
879
+ end
880
+ end
881
+
882
+ def query(req, proxy)
883
+ req.body.chunk_size = @chunk_size
884
+ dest_site = Site.new(req.header.request_uri)
885
+ proxy_site = if proxy
886
+ Site.new(proxy)
887
+ else
888
+ @proxy
889
+ end
890
+ sess = open(dest_site, proxy_site)
891
+ begin
892
+ sess.query(req)
893
+ rescue
894
+ sess.close
895
+ raise
896
+ end
897
+ sess
898
+ end
899
+
900
+ def reset(uri)
901
+ unless uri.is_a?(URI)
902
+ uri = URI.parse(uri.to_s)
903
+ end
904
+ site = Site.new(uri)
905
+ close(site)
906
+ end
907
+
908
+ def reset_all
909
+ close_all
910
+ end
911
+
912
+ def keep(sess)
913
+ add_cached_session(sess)
914
+ end
915
+
916
+ private
917
+
918
+ def open(dest, proxy = nil)
919
+ sess = nil
920
+ if cached = get_cached_session(dest)
921
+ sess = cached
922
+ else
923
+ sess = Session.new(dest, @agent_name, @from)
924
+ sess.proxy = proxy
925
+ sess.socket_sync = @socket_sync
926
+ sess.requested_version = @protocol_version if @protocol_version
927
+ sess.connect_timeout = @connect_timeout
928
+ sess.connect_retry = @connect_retry
929
+ sess.send_timeout = @send_timeout
930
+ sess.receive_timeout = @receive_timeout
931
+ sess.read_block_size = @read_block_size
932
+ sess.ssl_config = @ssl_config
933
+ sess.debug_dev = @debug_dev
934
+ end
935
+ sess
936
+ end
937
+
938
+ def close_all
939
+ each_sess do |sess|
940
+ sess.close
941
+ end
942
+ @sess_pool.clear
943
+ end
944
+
945
+ def close(dest)
946
+ if cached = get_cached_session(dest)
947
+ cached.close
948
+ true
949
+ else
950
+ false
951
+ end
952
+ end
953
+
954
+ def get_cached_session(dest)
955
+ cached = nil
956
+ @sess_pool_mutex.synchronize do
957
+ new_pool = []
958
+ @sess_pool.each do |s|
959
+ if s.dest == dest
960
+ cached = s
961
+ else
962
+ new_pool << s
963
+ end
964
+ end
965
+ @sess_pool = new_pool
966
+ end
967
+ cached
968
+ end
969
+
970
+ def add_cached_session(sess)
971
+ @sess_pool_mutex.synchronize do
972
+ @sess_pool << sess
973
+ end
974
+ end
975
+
976
+ def each_sess
977
+ @sess_pool_mutex.synchronize do
978
+ @sess_pool.each do |sess|
979
+ yield(sess)
980
+ end
981
+ end
982
+ end
983
+ end
984
+
985
+
986
+ # HTTPAccess2::SSLSocketWrap
987
+ #
988
+ class SSLSocketWrap
989
+ def initialize(socket, context, debug_dev = nil)
990
+ unless SSLEnabled
991
+ raise RuntimeError.new(
992
+ "Ruby/OpenSSL module is required for https access.")
993
+ end
994
+ @context = context
995
+ @socket = socket
996
+ @ssl_socket = create_ssl_socket(@socket)
997
+ @debug_dev = debug_dev
998
+ end
999
+
1000
+ def ssl_connect
1001
+ @ssl_socket.connect
1002
+ end
1003
+
1004
+ def post_connection_check(host)
1005
+ verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
1006
+ if verify_mode == OpenSSL::SSL::VERIFY_NONE
1007
+ return
1008
+ elsif @ssl_socket.peer_cert.nil? and
1009
+ check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
1010
+ raise OpenSSL::SSL::SSLError, "no peer cert"
1011
+ end
1012
+ hostname = host.host
1013
+ if @ssl_socket.respond_to?(:post_connection_check)
1014
+ @ssl_socket.post_connection_check(hostname)
1015
+ end
1016
+ @context.post_connection_check(@ssl_socket.peer_cert, hostname)
1017
+ end
1018
+
1019
+ def peer_cert
1020
+ @ssl_socket.peer_cert
1021
+ end
1022
+
1023
+ def addr
1024
+ @socket.addr
1025
+ end
1026
+
1027
+ def close
1028
+ @ssl_socket.close
1029
+ @socket.close
1030
+ end
1031
+
1032
+ def closed?
1033
+ @socket.closed?
1034
+ end
1035
+
1036
+ def eof?
1037
+ @ssl_socket.eof?
1038
+ end
1039
+
1040
+ def gets(*args)
1041
+ str = @ssl_socket.gets(*args)
1042
+ @debug_dev << str if @debug_dev
1043
+ str
1044
+ end
1045
+
1046
+ def read(*args)
1047
+ str = @ssl_socket.read(*args)
1048
+ @debug_dev << str if @debug_dev
1049
+ str
1050
+ end
1051
+
1052
+ def <<(str)
1053
+ rv = @ssl_socket.write(str)
1054
+ @debug_dev << str if @debug_dev
1055
+ rv
1056
+ end
1057
+
1058
+ def flush
1059
+ @ssl_socket.flush
1060
+ end
1061
+
1062
+ def sync
1063
+ @ssl_socket.sync
1064
+ end
1065
+
1066
+ def sync=(sync)
1067
+ @ssl_socket.sync = sync
1068
+ end
1069
+
1070
+ private
1071
+
1072
+ def check_mask(value, mask)
1073
+ value & mask == mask
1074
+ end
1075
+
1076
+ def create_ssl_socket(socket)
1077
+ ssl_socket = nil
1078
+ if OpenSSL::SSL.const_defined?("SSLContext")
1079
+ ctx = OpenSSL::SSL::SSLContext.new
1080
+ @context.set_context(ctx)
1081
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
1082
+ else
1083
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
1084
+ @context.set_context(ssl_socket)
1085
+ end
1086
+ ssl_socket
1087
+ end
1088
+ end
1089
+
1090
+
1091
+ # HTTPAccess2::DebugSocket -- debugging support
1092
+ #
1093
+ class DebugSocket < TCPSocket
1094
+ attr_accessor :debug_dev # Device for logging.
1095
+
1096
+ class << self
1097
+ def create_socket(host, port, debug_dev)
1098
+ debug_dev << "! CONNECT TO #{host}:#{port}\n"
1099
+ socket = new(host, port)
1100
+ socket.debug_dev = debug_dev
1101
+ socket.log_connect
1102
+ socket
1103
+ end
1104
+
1105
+ private :new
1106
+ end
1107
+
1108
+ def initialize(*args)
1109
+ super
1110
+ @debug_dev = nil
1111
+ end
1112
+
1113
+ def log_connect
1114
+ @debug_dev << '! CONNECTION ESTABLISHED' << "\n"
1115
+ end
1116
+
1117
+ def close
1118
+ super
1119
+ @debug_dev << '! CONNECTION CLOSED' << "\n"
1120
+ end
1121
+
1122
+ def gets(*args)
1123
+ str = super
1124
+ @debug_dev << str if str
1125
+ str
1126
+ end
1127
+
1128
+ def read(*args)
1129
+ str = super
1130
+ @debug_dev << str if str
1131
+ str
1132
+ end
1133
+
1134
+ def <<(str)
1135
+ super
1136
+ @debug_dev << str
1137
+ end
1138
+ end
1139
+
1140
+
1141
+ # HTTPAccess2::Session -- manage http session with one site.
1142
+ # One or more TCP sessions with the site may be created.
1143
+ # Only 1 TCP session is live at the same time.
1144
+ #
1145
+ class Session # :nodoc:
1146
+
1147
+ class Error < StandardError # :nodoc:
1148
+ end
1149
+
1150
+ class InvalidState < Error # :nodoc:
1151
+ end
1152
+
1153
+ class BadResponse < Error # :nodoc:
1154
+ end
1155
+
1156
+ class KeepAliveDisconnected < Error # :nodoc:
1157
+ end
1158
+
1159
+ attr_reader :dest # Destination site
1160
+ attr_reader :src # Source site
1161
+ attr_accessor :proxy # Proxy site
1162
+ attr_accessor :socket_sync # Boolean value for Socket#sync
1163
+
1164
+ attr_accessor :requested_version # Requested protocol version
1165
+
1166
+ attr_accessor :debug_dev # Device for dumping log for debugging
1167
+
1168
+ # These session parameters are not used now...
1169
+ attr_accessor :connect_timeout
1170
+ attr_accessor :connect_retry
1171
+ attr_accessor :send_timeout
1172
+ attr_accessor :receive_timeout
1173
+ attr_accessor :read_block_size
1174
+
1175
+ attr_accessor :ssl_config
1176
+
1177
+ def initialize(dest, user_agent, from)
1178
+ @dest = dest
1179
+ @src = Site.new
1180
+ @proxy = nil
1181
+ @socket_sync = true
1182
+ @requested_version = nil
1183
+
1184
+ @debug_dev = nil
1185
+
1186
+ @connect_timeout = nil
1187
+ @connect_retry = 1
1188
+ @send_timeout = nil
1189
+ @receive_timeout = nil
1190
+ @read_block_size = nil
1191
+
1192
+ @ssl_config = nil
1193
+
1194
+ @user_agent = user_agent
1195
+ @from = from
1196
+ @state = :INIT
1197
+
1198
+ @requests = []
1199
+
1200
+ @status = nil
1201
+ @reason = nil
1202
+ @headers = []
1203
+
1204
+ @socket = nil
1205
+ end
1206
+
1207
+ # Send a request to the server
1208
+ def query(req)
1209
+ connect() if @state == :INIT
1210
+ begin
1211
+ timeout(@send_timeout) do
1212
+ set_header(req)
1213
+ req.dump(@socket)
1214
+ # flush the IO stream as IO::sync mode is false
1215
+ @socket.flush unless @socket_sync
1216
+ end
1217
+ rescue Errno::ECONNABORTED
1218
+ close
1219
+ raise KeepAliveDisconnected.new
1220
+ rescue
1221
+ if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
1222
+ raise KeepAliveDisconnected.new
1223
+ elsif $!.is_a?(TimeoutError)
1224
+ close
1225
+ raise
1226
+ else
1227
+ raise
1228
+ end
1229
+ end
1230
+
1231
+ @state = :META if @state == :WAIT
1232
+ @next_connection = nil
1233
+ @requests.push(req)
1234
+ end
1235
+
1236
+ def close
1237
+ unless @socket.nil?
1238
+ @socket.flush
1239
+ @socket.close unless @socket.closed?
1240
+ end
1241
+ @state = :INIT
1242
+ end
1243
+
1244
+ def closed?
1245
+ @state == :INIT
1246
+ end
1247
+
1248
+ def get_status
1249
+ version = status = reason = nil
1250
+ begin
1251
+ if @state != :META
1252
+ raise RuntimeError.new("get_status must be called at the beginning of a session.")
1253
+ end
1254
+ version, status, reason = read_header()
1255
+ rescue
1256
+ close
1257
+ raise
1258
+ end
1259
+ return version, status, reason
1260
+ end
1261
+
1262
+ def get_header(&block)
1263
+ begin
1264
+ read_header() if @state == :META
1265
+ rescue
1266
+ close
1267
+ raise
1268
+ end
1269
+ if block
1270
+ @headers.each do |line|
1271
+ block.call(line)
1272
+ end
1273
+ else
1274
+ @headers
1275
+ end
1276
+ end
1277
+
1278
+ def eof?
1279
+ if @content_length == 0
1280
+ true
1281
+ elsif @readbuf.length > 0
1282
+ false
1283
+ else
1284
+ @socket.closed? or @socket.eof?
1285
+ end
1286
+ end
1287
+
1288
+ def get_data(&block)
1289
+ begin
1290
+ read_header() if @state == :META
1291
+ return nil if @state != :DATA
1292
+ unless @state == :DATA
1293
+ raise InvalidState.new('state != DATA')
1294
+ end
1295
+ data = nil
1296
+ if block
1297
+ until eof?
1298
+ begin
1299
+ timeout(@receive_timeout) do
1300
+ data = read_body()
1301
+ end
1302
+ rescue TimeoutError
1303
+ raise
1304
+ end
1305
+ block.call(data) if data
1306
+ end
1307
+ data = nil # Calling with block returns nil.
1308
+ else
1309
+ begin
1310
+ timeout(@receive_timeout) do
1311
+ data = read_body()
1312
+ end
1313
+ rescue TimeoutError
1314
+ raise
1315
+ end
1316
+ end
1317
+ rescue
1318
+ close
1319
+ raise
1320
+ end
1321
+ if eof?
1322
+ if @next_connection
1323
+ @state = :WAIT
1324
+ else
1325
+ close
1326
+ end
1327
+ end
1328
+ data
1329
+ end
1330
+
1331
+ private
1332
+
1333
+ LibNames = "(#{RCS_FILE}/#{RCS_REVISION}, #{RUBY_VERSION_STRING})"
1334
+
1335
+ def set_header(req)
1336
+ req.version = @requested_version if @requested_version
1337
+ if @user_agent
1338
+ req.header.set('User-Agent', "#{@user_agent} #{LibNames}")
1339
+ end
1340
+ if @from
1341
+ req.header.set('From', @from)
1342
+ end
1343
+ req.header.set('Date', Time.now)
1344
+ end
1345
+
1346
+ # Connect to the server
1347
+ def connect
1348
+ site = @proxy || @dest
1349
+ begin
1350
+ retry_number = 0
1351
+ timeout(@connect_timeout) do
1352
+ @socket = create_socket(site)
1353
+ begin
1354
+ @src.host = @socket.addr[3]
1355
+ @src.port = @socket.addr[1]
1356
+ rescue SocketError
1357
+ # to avoid IPSocket#addr problem on Mac OS X 10.3 + ruby-1.8.1.
1358
+ # cf. [ruby-talk:84909], [ruby-talk:95827]
1359
+ end
1360
+ if @dest.scheme == 'https'
1361
+ @socket = create_ssl_socket(@socket)
1362
+ connect_ssl_proxy(@socket) if @proxy
1363
+ @socket.ssl_connect
1364
+ @socket.post_connection_check(@dest)
1365
+ end
1366
+ # Use Ruby internal buffering instead of passing data immediatly
1367
+ # to the underlying layer
1368
+ # => we need to to call explicitely flush on the socket
1369
+ @socket.sync = @socket_sync
1370
+ end
1371
+ rescue TimeoutError
1372
+ if @connect_retry == 0
1373
+ retry
1374
+ else
1375
+ retry_number += 1
1376
+ retry if retry_number < @connect_retry
1377
+ end
1378
+ close
1379
+ raise
1380
+ end
1381
+
1382
+ @state = :WAIT
1383
+ @readbuf = ''
1384
+ end
1385
+
1386
+ def create_socket(site)
1387
+ begin
1388
+ if @debug_dev
1389
+ DebugSocket.create_socket(site.host, site.port, @debug_dev)
1390
+ else
1391
+ TCPSocket.new(site.host, site.port)
1392
+ end
1393
+ rescue SystemCallError => e
1394
+ e.message << " (#{site.host}, ##{site.port})"
1395
+ raise
1396
+ end
1397
+ end
1398
+
1399
+ # wrap socket with OpenSSL.
1400
+ def create_ssl_socket(raw_socket)
1401
+ SSLSocketWrap.new(raw_socket, @ssl_config, (DEBUG_SSL ? @debug_dev : nil))
1402
+ end
1403
+
1404
+ def connect_ssl_proxy(socket)
1405
+ socket << sprintf("CONNECT %s:%s HTTP/1.1\r\n\r\n", @dest.host, @dest.port)
1406
+ parse_header(socket)
1407
+ unless @status == 200
1408
+ raise BadResponse.new(
1409
+ "connect to ssl proxy failed with status #{@status} #{@reason}")
1410
+ end
1411
+ end
1412
+
1413
+ # Read status block.
1414
+ def read_header
1415
+ if @state == :DATA
1416
+ get_data {}
1417
+ check_state()
1418
+ end
1419
+ unless @state == :META
1420
+ raise InvalidState, 'state != :META'
1421
+ end
1422
+ parse_header(@socket)
1423
+ @content_length = nil
1424
+ @chunked = false
1425
+ @headers.each do |line|
1426
+ case line
1427
+ when /^Content-Length:\s+(\d+)/i
1428
+ @content_length = $1.to_i
1429
+ when /^Transfer-Encoding:\s+chunked/i
1430
+ @chunked = true
1431
+ @content_length = true # how?
1432
+ @chunk_length = 0
1433
+ when /^Connection:\s+([\-\w]+)/i, /^Proxy-Connection:\s+([\-\w]+)/i
1434
+ case $1
1435
+ when /^Keep-Alive$/i
1436
+ @next_connection = true
1437
+ when /^close$/i
1438
+ @next_connection = false
1439
+ end
1440
+ else
1441
+ # Nothing to parse.
1442
+ end
1443
+ end
1444
+
1445
+ # Head of the request has been parsed.
1446
+ @state = :DATA
1447
+ req = @requests.shift
1448
+
1449
+ if req.header.request_method == 'HEAD'
1450
+ @content_length = 0
1451
+ if @next_connection
1452
+ @state = :WAIT
1453
+ else
1454
+ close
1455
+ end
1456
+ end
1457
+ @next_connection = false unless @content_length
1458
+ return [@version, @status, @reason]
1459
+ end
1460
+
1461
+ StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d+)(?:\s+(.*))?\r?\n\z)
1462
+ def parse_header(socket)
1463
+ begin
1464
+ timeout(@receive_timeout) do
1465
+ begin
1466
+ initial_line = socket.gets("\n")
1467
+ if initial_line.nil?
1468
+ raise KeepAliveDisconnected.new
1469
+ end
1470
+ if StatusParseRegexp =~ initial_line
1471
+ @version, @status, @reason = $1, $2.to_i, $3
1472
+ @next_connection = HTTP.keep_alive_enabled?(@version)
1473
+ else
1474
+ @version = '0.9'
1475
+ @status = nil
1476
+ @reason = nil
1477
+ @next_connection = false
1478
+ @readbuf = initial_line
1479
+ break
1480
+ end
1481
+ @headers = []
1482
+ while true
1483
+ line = socket.gets("\n")
1484
+ unless line
1485
+ raise BadResponse.new('Unexpected EOF.')
1486
+ end
1487
+ line.sub!(/\r?\n\z/, '')
1488
+ break if line.empty?
1489
+ if line.sub!(/^\t/, '')
1490
+ @headers[-1] << line
1491
+ else
1492
+ @headers.push(line)
1493
+ end
1494
+ end
1495
+ end while (@version == '1.1' && @status == 100)
1496
+ end
1497
+ rescue TimeoutError
1498
+ raise
1499
+ end
1500
+ end
1501
+
1502
+ def read_body
1503
+ if @chunked
1504
+ return read_body_chunked()
1505
+ elsif @content_length == 0
1506
+ return nil
1507
+ elsif @content_length
1508
+ return read_body_length()
1509
+ else
1510
+ if @readbuf.length > 0
1511
+ data = @readbuf
1512
+ @readbuf = ''
1513
+ return data
1514
+ else
1515
+ data = @socket.read(@read_block_size)
1516
+ data = nil if data.empty? # Absorbing interface mismatch.
1517
+ return data
1518
+ end
1519
+ end
1520
+ end
1521
+
1522
+ def read_body_length
1523
+ maxbytes = @read_block_size
1524
+ if @readbuf.length > 0
1525
+ data = @readbuf[0, @content_length]
1526
+ @readbuf[0, @content_length] = ''
1527
+ @content_length -= data.length
1528
+ return data
1529
+ end
1530
+ maxbytes = @content_length if maxbytes > @content_length
1531
+ data = @socket.read(maxbytes)
1532
+ if data
1533
+ @content_length -= data.length
1534
+ else
1535
+ @content_length = 0
1536
+ end
1537
+ return data
1538
+ end
1539
+
1540
+ RS = "\r\n"
1541
+ ChunkDelimiter = "0#{RS}"
1542
+ ChunkTrailer = "0#{RS}#{RS}"
1543
+ def read_body_chunked
1544
+ if @chunk_length == 0
1545
+ until (i = @readbuf.index(RS))
1546
+ @readbuf << @socket.gets(RS)
1547
+ end
1548
+ i += 2
1549
+ if @readbuf[0, i] == ChunkDelimiter
1550
+ @content_length = 0
1551
+ unless @readbuf[0, 5] == ChunkTrailer
1552
+ @readbuf << @socket.gets(RS)
1553
+ end
1554
+ @readbuf[0, 5] = ''
1555
+ return nil
1556
+ end
1557
+ @chunk_length = @readbuf[0, i].hex
1558
+ @readbuf[0, i] = ''
1559
+ end
1560
+ while @readbuf.length < @chunk_length + 2
1561
+ @readbuf << @socket.read(@chunk_length + 2 - @readbuf.length)
1562
+ end
1563
+ data = @readbuf[0, @chunk_length]
1564
+ @readbuf[0, @chunk_length + 2] = ''
1565
+ @chunk_length = 0
1566
+ return data
1567
+ end
1568
+
1569
+ def check_state
1570
+ if @state == :DATA
1571
+ if eof?
1572
+ if @next_connection
1573
+ if @requests.empty?
1574
+ @state = :WAIT
1575
+ else
1576
+ @state = :META
1577
+ end
1578
+ end
1579
+ end
1580
+ end
1581
+ end
1582
+ end
1583
+
1584
+
1585
+ end
1586
+
1587
+
1588
+ HTTPClient = HTTPAccess2::Client