rubyforge 0.4.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ require 'test/unit' unless defined? $ZENTEST and $ZENTEST
2
+ require 'rubyforge'
3
+
4
+ class RubyForge::FakeAgent
5
+ class << self
6
+ attr_accessor :t_data, :t_request
7
+ end
8
+
9
+ def initialize(*args)
10
+ end
11
+
12
+ def request(request, data)
13
+ self.class.t_request = request
14
+ self.class.t_data = data
15
+ response = Net::HTTPOK.new('1.1', 200, '')
16
+ def response.read_body; ''; end
17
+ return response
18
+ end
19
+
20
+ class Post
21
+ def initialize(*args)
22
+ @args = args
23
+ @stuff = {}
24
+ end
25
+
26
+ def [] key
27
+ @stuff[key.downcase]
28
+ end
29
+
30
+ def []= key, val
31
+ @stuff[key.downcase] = val
32
+ end
33
+
34
+ def method_missing(*stuff)
35
+ # warn stuff.inspect
36
+ end
37
+ end
38
+ end
39
+
40
+ class TestRubyForgeClient < Test::Unit::TestCase
41
+ def setup
42
+ @client = RubyForge::Client.new
43
+ @client.agent_class = RubyForge::FakeAgent
44
+ RubyForge::FakeAgent.t_data = :unassigned
45
+ RubyForge::FakeAgent.t_request = :unassigned
46
+ end
47
+
48
+ def test_post_with_params
49
+ @client.post_content('http://example.com', { :f => 'adsf'})
50
+ assert_equal('f=adsf', RubyForge::FakeAgent.t_data)
51
+
52
+ @client.post_content('http://example.com', { :a => 'b', :c => 'd' })
53
+ assert_equal('a=b&c=d', RubyForge::FakeAgent.t_data)
54
+ end
55
+
56
+ def test_multipart_post_one_param
57
+ random = Array::new(8){ "%2.2d" % rand(42) }.join('__')
58
+ boundary = "multipart/form-data; boundary=___#{ random }___"
59
+
60
+ request = <<-END
61
+ --___#{random}___\r
62
+ Content-Disposition: form-data; name="a"\r\n\r
63
+ b\r
64
+ --___#{random}___--\r
65
+ END
66
+
67
+ @client.post_content( 'http://example.com',
68
+ { :a => 'b' },
69
+ { 'content-type' => boundary }
70
+ )
71
+ assert_equal(request, RubyForge::FakeAgent.t_data)
72
+ end
73
+
74
+ def test_multipart_post_two_params
75
+ random = Array::new(8){ "%2.2d" % rand(42) }.join('__')
76
+ boundary = "multipart/form-data; boundary=___#{ random }___"
77
+
78
+ request = <<-END
79
+ --___#{random}___\r
80
+ Content-Disposition: form-data; name="a"\r\n\r
81
+ b\r
82
+ --___#{random}___\r
83
+ Content-Disposition: form-data; name="c"\r\n\r
84
+ d\r
85
+ --___#{random}___--\r
86
+ END
87
+
88
+ @client.post_content( 'http://example.com',
89
+ { :a => 'b', :c => 'd' },
90
+ { 'content-type' => boundary }
91
+ )
92
+ assert_equal(request, RubyForge::FakeAgent.t_data)
93
+ end
94
+
95
+ def test_multipart_io
96
+ random = Array::new(8){ "%2.2d" % rand(42) }.join('__')
97
+ boundary = "multipart/form-data; boundary=___#{ random }___"
98
+
99
+ file_contents = 'blah blah blah'
100
+ file = StringIO.new(file_contents)
101
+ class << file
102
+ def path
103
+ '/one/two/three.rb'
104
+ end
105
+ end
106
+
107
+ request = <<-END
108
+ --___#{random}___\r
109
+ Content-Disposition: form-data; name="userfile"; filename="three.rb"\r
110
+ Content-Transfer-Encoding: binary\r
111
+ Content-Type: text/plain\r\n\r
112
+ #{file_contents}\r
113
+ --___#{random}___--\r
114
+ END
115
+
116
+ @client.post_content( 'http://example.com',
117
+ { :userfile => file },
118
+ { 'content-type' => boundary }
119
+ )
120
+ assert_equal(request, RubyForge::FakeAgent.t_data)
121
+ end
122
+ end
@@ -0,0 +1,97 @@
1
+ require 'test/unit' unless defined? $ZENTEST and $ZENTEST
2
+ require 'rubyforge'
3
+ require 'time'
4
+ require 'yaml'
5
+ require 'tempfile'
6
+
7
+ class TestRubyForgeCookieManager < Test::Unit::TestCase
8
+ def setup
9
+ cookie = cookie_string('session_ser', 'zzzzzzzzzzzzzzzzzz')
10
+ @url = URI.parse('http://rubyforge.org/account/login.php')
11
+ @cookie = WEBrick::Cookie.parse_set_cookie(cookie)
12
+ @manager = RubyForge::CookieManager.new
13
+ end
14
+
15
+ def test_empty?
16
+ manager = RubyForge::CookieManager.new
17
+ assert(manager.empty?)
18
+
19
+ assert_equal(0, manager[URI.parse('http://rubyforge.org/')].length)
20
+ assert(manager.empty?)
21
+ end
22
+
23
+ def test_add
24
+ manager = RubyForge::CookieManager.new
25
+ assert(manager.empty?)
26
+ manager.add(@url, @cookie)
27
+ assert(!manager.empty?)
28
+
29
+ cookie = manager[@url][@cookie.name]
30
+ assert cookie
31
+ assert_equal(@cookie.object_id, cookie.object_id)
32
+ end
33
+
34
+ def test_add_expired_cookie
35
+ cookie = cookie_string('session_ser', 'zzzz',
36
+ :expires => (Time.now - 10).strftime('%A, %d-%b-%Y %H:%M:%S %Z'))
37
+ cookie = WEBrick::Cookie.parse_set_cookie(cookie)
38
+
39
+ manager = RubyForge::CookieManager.new
40
+ assert(manager.empty?)
41
+ manager.add(@url, cookie)
42
+ assert(manager.empty?)
43
+ end
44
+
45
+ def test_marshal
46
+ @manager.add(@url, @cookie)
47
+ assert(!@manager.empty?)
48
+ m = YAML.load(YAML.dump(@manager))
49
+ assert(!m.empty?)
50
+ end
51
+
52
+ def test_save!
53
+ tmp = Tempfile.new('cookie_jar')
54
+ @manager.cookies_file = tmp.path
55
+ @manager.add(@url, @cookie)
56
+ @manager.save!
57
+
58
+ @manager = RubyForge::CookieManager.load(tmp.path)
59
+ assert(! @manager.empty?)
60
+ end
61
+
62
+ def test_load_empty_file
63
+ tmp = Tempfile.new('cookie_jar')
64
+ manager = RubyForge::CookieManager.load(tmp.path)
65
+ assert(manager.empty?)
66
+ end
67
+
68
+ def test_load_legacy_file
69
+ tmp = Tempfile.new('cookie_jar')
70
+ tmp.write([
71
+ 'https://rubyforge.org/account/login.php',
72
+ 'session_ser',
73
+ 'zzzzzz',
74
+ '123456',
75
+ '.rubyforge.org',
76
+ '/',
77
+ '13'
78
+ ].join("\t"))
79
+ manager = RubyForge::CookieManager.load(tmp.path)
80
+ assert(manager.empty?)
81
+ end
82
+
83
+ def test_cookies_file=
84
+ tmp = Tempfile.new('cookie_jar')
85
+ @manager.cookies_file = tmp.path
86
+ assert(@manager.empty?)
87
+ end
88
+
89
+ def cookie_string(name, value, options = {})
90
+ options = {
91
+ :expires => (Time.now + 86400).strftime('%A, %d-%b-%Y %H:%M:%S %Z'),
92
+ :path => '/',
93
+ :domain => '.rubyforge.org',
94
+ }.merge(options)
95
+ "#{name}=#{value}; #{options.map { |o| o.join('=') }.join('; ')}"
96
+ end
97
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyforge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Davis
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2008-03-11 00:00:00 -07:00
14
+ date: 2008-05-20 00:00:00 -07:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
@@ -34,11 +34,12 @@ files:
34
34
  - README.txt
35
35
  - Rakefile
36
36
  - bin/rubyforge
37
- - lib/http-access2.rb
38
- - lib/http-access2/cookie.rb
39
- - lib/http-access2/http.rb
40
37
  - lib/rubyforge.rb
38
+ - lib/rubyforge/client.rb
39
+ - lib/rubyforge/cookie_manager.rb
41
40
  - test/test_rubyforge.rb
41
+ - test/test_rubyforge_client.rb
42
+ - test/test_rubyforge_cookie_manager.rb
42
43
  has_rdoc: true
43
44
  homepage: http://codeforpeople.rubyforge.org/rubyforge/
44
45
  post_install_message:
@@ -62,9 +63,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
63
  requirements: []
63
64
 
64
65
  rubyforge_project: codeforpeople
65
- rubygems_version: 1.0.1
66
+ rubygems_version: 1.1.1
66
67
  signing_key:
67
68
  specification_version: 2
68
69
  summary: A script which automates a limited set of rubyforge operations
69
70
  test_files:
70
71
  - test/test_rubyforge.rb
72
+ - test/test_rubyforge_client.rb
73
+ - test/test_rubyforge_cookie_manager.rb
data/lib/http-access2.rb DELETED
@@ -1,1588 +0,0 @@
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,v 1.1.1.1 2006/09/13 04:28:30 zenspider Exp $
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 |s|
439
- block.call(s) if block
440
- content << s
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 |s|
459
- pipew.syswrite(s)
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