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