webrick 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of webrick might be problematic. Click here for more details.
- data/README.txt +21 -0
- data/lib/webrick.rb +227 -0
- data/lib/webrick/accesslog.rb +151 -0
- data/lib/webrick/cgi.rb +260 -0
- data/lib/webrick/compat.rb +35 -0
- data/lib/webrick/config.rb +121 -0
- data/lib/webrick/cookie.rb +110 -0
- data/lib/webrick/htmlutils.rb +28 -0
- data/lib/webrick/httpauth.rb +95 -0
- data/lib/webrick/httpauth/authenticator.rb +112 -0
- data/lib/webrick/httpauth/basicauth.rb +108 -0
- data/lib/webrick/httpauth/digestauth.rb +392 -0
- data/lib/webrick/httpauth/htdigest.rb +128 -0
- data/lib/webrick/httpauth/htgroup.rb +93 -0
- data/lib/webrick/httpauth/htpasswd.rb +121 -0
- data/lib/webrick/httpauth/userdb.rb +52 -0
- data/lib/webrick/httpproxy.rb +305 -0
- data/lib/webrick/httprequest.rb +461 -0
- data/lib/webrick/httpresponse.rb +399 -0
- data/lib/webrick/https.rb +64 -0
- data/lib/webrick/httpserver.rb +264 -0
- data/lib/webrick/httpservlet.rb +22 -0
- data/lib/webrick/httpservlet/abstract.rb +153 -0
- data/lib/webrick/httpservlet/cgi_runner.rb +46 -0
- data/lib/webrick/httpservlet/cgihandler.rb +108 -0
- data/lib/webrick/httpservlet/erbhandler.rb +87 -0
- data/lib/webrick/httpservlet/filehandler.rb +470 -0
- data/lib/webrick/httpservlet/prochandler.rb +33 -0
- data/lib/webrick/httpstatus.rb +184 -0
- data/lib/webrick/httputils.rb +394 -0
- data/lib/webrick/httpversion.rb +49 -0
- data/lib/webrick/log.rb +136 -0
- data/lib/webrick/server.rb +218 -0
- data/lib/webrick/ssl.rb +127 -0
- data/lib/webrick/utils.rb +241 -0
- data/lib/webrick/version.rb +13 -0
- data/sample/webrick/demo-app.rb +66 -0
- data/sample/webrick/demo-multipart.cgi +12 -0
- data/sample/webrick/demo-servlet.rb +6 -0
- data/sample/webrick/demo-urlencoded.cgi +12 -0
- data/sample/webrick/hello.cgi +11 -0
- data/sample/webrick/hello.rb +8 -0
- data/sample/webrick/httpd.rb +23 -0
- data/sample/webrick/httpproxy.rb +25 -0
- data/sample/webrick/httpsd.rb +33 -0
- data/test/openssl/utils.rb +313 -0
- data/test/ruby/envutil.rb +208 -0
- data/test/webrick/test_cgi.rb +134 -0
- data/test/webrick/test_cookie.rb +131 -0
- data/test/webrick/test_filehandler.rb +285 -0
- data/test/webrick/test_httpauth.rb +167 -0
- data/test/webrick/test_httpproxy.rb +282 -0
- data/test/webrick/test_httprequest.rb +411 -0
- data/test/webrick/test_httpresponse.rb +49 -0
- data/test/webrick/test_httpserver.rb +305 -0
- data/test/webrick/test_httputils.rb +96 -0
- data/test/webrick/test_httpversion.rb +40 -0
- data/test/webrick/test_server.rb +67 -0
- data/test/webrick/test_utils.rb +64 -0
- data/test/webrick/utils.rb +58 -0
- data/test/webrick/webrick.cgi +36 -0
- data/test/webrick/webrick_long_filename.cgi +36 -0
- metadata +106 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
#
|
2
|
+
# httpauth/htdigest.rb -- Apache compatible htdigest file
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
6
|
+
# reserved.
|
7
|
+
#
|
8
|
+
# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
|
9
|
+
|
10
|
+
require 'webrick/httpauth/userdb'
|
11
|
+
require 'webrick/httpauth/digestauth'
|
12
|
+
require 'tempfile'
|
13
|
+
|
14
|
+
module WEBrick
|
15
|
+
module HTTPAuth
|
16
|
+
|
17
|
+
##
|
18
|
+
# Htdigest accesses apache-compatible digest password files. Passwords are
|
19
|
+
# matched to a realm where they are valid. For security, the path for a
|
20
|
+
# digest password database should be stored outside of the paths available
|
21
|
+
# to the HTTP server.
|
22
|
+
#
|
23
|
+
# Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
|
24
|
+
# stores passwords using cryptographic hashes.
|
25
|
+
#
|
26
|
+
# htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
|
27
|
+
# htpasswd.set_passwd 'my realm', 'username', 'password'
|
28
|
+
# htpasswd.flush
|
29
|
+
|
30
|
+
class Htdigest
|
31
|
+
include UserDB
|
32
|
+
|
33
|
+
##
|
34
|
+
# Open a digest password database at +path+
|
35
|
+
|
36
|
+
def initialize(path)
|
37
|
+
@path = path
|
38
|
+
@mtime = Time.at(0)
|
39
|
+
@digest = Hash.new
|
40
|
+
@mutex = Mutex::new
|
41
|
+
@auth_type = DigestAuth
|
42
|
+
open(@path,"a").close unless File::exist?(@path)
|
43
|
+
reload
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Reloads passwords from the database
|
48
|
+
|
49
|
+
def reload
|
50
|
+
mtime = File::mtime(@path)
|
51
|
+
if mtime > @mtime
|
52
|
+
@digest.clear
|
53
|
+
open(@path){|io|
|
54
|
+
while line = io.gets
|
55
|
+
line.chomp!
|
56
|
+
user, realm, pass = line.split(/:/, 3)
|
57
|
+
unless @digest[realm]
|
58
|
+
@digest[realm] = Hash.new
|
59
|
+
end
|
60
|
+
@digest[realm][user] = pass
|
61
|
+
end
|
62
|
+
}
|
63
|
+
@mtime = mtime
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Flush the password database. If +output+ is given the database will
|
69
|
+
# be written there instead of to the original path.
|
70
|
+
|
71
|
+
def flush(output=nil)
|
72
|
+
output ||= @path
|
73
|
+
tmp = Tempfile.new("htpasswd", File::dirname(output))
|
74
|
+
begin
|
75
|
+
each{|item| tmp.puts(item.join(":")) }
|
76
|
+
tmp.close
|
77
|
+
File::rename(tmp.path, output)
|
78
|
+
rescue
|
79
|
+
tmp.close(true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Retrieves a password from the database for +user+ in +realm+. If
|
85
|
+
# +reload_db+ is true the database will be reloaded first.
|
86
|
+
|
87
|
+
def get_passwd(realm, user, reload_db)
|
88
|
+
reload() if reload_db
|
89
|
+
if hash = @digest[realm]
|
90
|
+
hash[user]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Sets a password in the database for +user+ in +realm+ to +pass+.
|
96
|
+
|
97
|
+
def set_passwd(realm, user, pass)
|
98
|
+
@mutex.synchronize{
|
99
|
+
unless @digest[realm]
|
100
|
+
@digest[realm] = Hash.new
|
101
|
+
end
|
102
|
+
@digest[realm][user] = make_passwd(realm, user, pass)
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Removes a password from the database for +user+ in +realm+.
|
108
|
+
|
109
|
+
def delete_passwd(realm, user)
|
110
|
+
if hash = @digest[realm]
|
111
|
+
hash.delete(user)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Iterate passwords in the database.
|
117
|
+
|
118
|
+
def each # :yields: [user, realm, password_hash]
|
119
|
+
@digest.keys.sort.each{|realm|
|
120
|
+
hash = @digest[realm]
|
121
|
+
hash.keys.sort.each{|user|
|
122
|
+
yield([user, realm, hash[user]])
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#
|
2
|
+
# httpauth/htgroup.rb -- Apache compatible htgroup file
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
6
|
+
# reserved.
|
7
|
+
#
|
8
|
+
# $IPR: htgroup.rb,v 1.1 2003/02/16 22:22:56 gotoyuzo Exp $
|
9
|
+
|
10
|
+
require 'tempfile'
|
11
|
+
|
12
|
+
module WEBrick
|
13
|
+
module HTTPAuth
|
14
|
+
|
15
|
+
##
|
16
|
+
# Htgroup accesses apache-compatible group files. Htgroup can be used to
|
17
|
+
# provide group-based authentication for users. Currently Htgroup is not
|
18
|
+
# directly integrated with any authenticators in WEBrick. For security,
|
19
|
+
# the path for a digest password database should be stored outside of the
|
20
|
+
# paths available to the HTTP server.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# htgroup = WEBrick::HTTPAuth::Htgroup.new 'my_group_file'
|
25
|
+
# htgroup.add 'superheroes', %w[spiderman batman]
|
26
|
+
#
|
27
|
+
# htgroup.members('superheroes').include? 'magneto' # => false
|
28
|
+
|
29
|
+
class Htgroup
|
30
|
+
|
31
|
+
##
|
32
|
+
# Open a group database at +path+
|
33
|
+
|
34
|
+
def initialize(path)
|
35
|
+
@path = path
|
36
|
+
@mtime = Time.at(0)
|
37
|
+
@group = Hash.new
|
38
|
+
open(@path,"a").close unless File::exist?(@path)
|
39
|
+
reload
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Reload groups from the database
|
44
|
+
|
45
|
+
def reload
|
46
|
+
if (mtime = File::mtime(@path)) > @mtime
|
47
|
+
@group.clear
|
48
|
+
open(@path){|io|
|
49
|
+
while line = io.gets
|
50
|
+
line.chomp!
|
51
|
+
group, members = line.split(/:\s*/)
|
52
|
+
@group[group] = members.split(/\s+/)
|
53
|
+
end
|
54
|
+
}
|
55
|
+
@mtime = mtime
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Flush the group database. If +output+ is given the database will be
|
61
|
+
# written there instead of to the original path.
|
62
|
+
|
63
|
+
def flush(output=nil)
|
64
|
+
output ||= @path
|
65
|
+
tmp = Tempfile.new("htgroup", File::dirname(output))
|
66
|
+
begin
|
67
|
+
@group.keys.sort.each{|group|
|
68
|
+
tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
|
69
|
+
}
|
70
|
+
tmp.close
|
71
|
+
File::rename(tmp.path, output)
|
72
|
+
rescue
|
73
|
+
tmp.close(true)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Retrieve the list of members from +group+
|
79
|
+
|
80
|
+
def members(group)
|
81
|
+
reload
|
82
|
+
@group[group] || []
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Add an Array of +members+ to +group+
|
87
|
+
|
88
|
+
def add(group, members)
|
89
|
+
@group[group] = members(group) | members
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#
|
2
|
+
# httpauth/htpasswd -- Apache compatible htpasswd file
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
6
|
+
# reserved.
|
7
|
+
#
|
8
|
+
# $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
|
9
|
+
|
10
|
+
require 'webrick/httpauth/userdb'
|
11
|
+
require 'webrick/httpauth/basicauth'
|
12
|
+
require 'tempfile'
|
13
|
+
|
14
|
+
module WEBrick
|
15
|
+
module HTTPAuth
|
16
|
+
|
17
|
+
##
|
18
|
+
# Htpasswd accesses apache-compatible password files. Passwords are
|
19
|
+
# matched to a realm where they are valid. For security, the path for a
|
20
|
+
# password database should be stored outside of the paths available to the
|
21
|
+
# HTTP server.
|
22
|
+
#
|
23
|
+
# Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
|
24
|
+
#
|
25
|
+
# To create an Htpasswd database with a single user:
|
26
|
+
#
|
27
|
+
# htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
|
28
|
+
# htpasswd.set_passwd 'my realm', 'username', 'password'
|
29
|
+
# htpasswd.flush
|
30
|
+
|
31
|
+
class Htpasswd
|
32
|
+
include UserDB
|
33
|
+
|
34
|
+
##
|
35
|
+
# Open a password database at +path+
|
36
|
+
|
37
|
+
def initialize(path)
|
38
|
+
@path = path
|
39
|
+
@mtime = Time.at(0)
|
40
|
+
@passwd = Hash.new
|
41
|
+
@auth_type = BasicAuth
|
42
|
+
open(@path,"a").close unless File::exist?(@path)
|
43
|
+
reload
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Reload passwords from the database
|
48
|
+
|
49
|
+
def reload
|
50
|
+
mtime = File::mtime(@path)
|
51
|
+
if mtime > @mtime
|
52
|
+
@passwd.clear
|
53
|
+
open(@path){|io|
|
54
|
+
while line = io.gets
|
55
|
+
line.chomp!
|
56
|
+
case line
|
57
|
+
when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
|
58
|
+
user, pass = line.split(":")
|
59
|
+
when /:\$/, /:{SHA}/
|
60
|
+
raise NotImplementedError,
|
61
|
+
'MD5, SHA1 .htpasswd file not supported'
|
62
|
+
else
|
63
|
+
raise StandardError, 'bad .htpasswd file'
|
64
|
+
end
|
65
|
+
@passwd[user] = pass
|
66
|
+
end
|
67
|
+
}
|
68
|
+
@mtime = mtime
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Flush the password database. If +output+ is given the database will
|
74
|
+
# be written there instead of to the original path.
|
75
|
+
|
76
|
+
def flush(output=nil)
|
77
|
+
output ||= @path
|
78
|
+
tmp = Tempfile.new("htpasswd", File::dirname(output))
|
79
|
+
begin
|
80
|
+
each{|item| tmp.puts(item.join(":")) }
|
81
|
+
tmp.close
|
82
|
+
File::rename(tmp.path, output)
|
83
|
+
rescue
|
84
|
+
tmp.close(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Retrieves a password from the database for +user+ in +realm+. If
|
90
|
+
# +reload_db+ is true the database will be reloaded first.
|
91
|
+
|
92
|
+
def get_passwd(realm, user, reload_db)
|
93
|
+
reload() if reload_db
|
94
|
+
@passwd[user]
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Sets a password in the database for +user+ in +realm+ to +pass+.
|
99
|
+
|
100
|
+
def set_passwd(realm, user, pass)
|
101
|
+
@passwd[user] = make_passwd(realm, user, pass)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Removes a password from the database for +user+ in +realm+.
|
106
|
+
|
107
|
+
def delete_passwd(realm, user)
|
108
|
+
@passwd.delete(user)
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Iterate passwords in the database.
|
113
|
+
|
114
|
+
def each # :yields: [user, password]
|
115
|
+
@passwd.keys.sort.each{|user|
|
116
|
+
yield([user, @passwd[user]])
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#--
|
2
|
+
# httpauth/userdb.rb -- UserDB mix-in module.
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
6
|
+
# reserved.
|
7
|
+
#
|
8
|
+
# $IPR: userdb.rb,v 1.2 2003/02/20 07:15:48 gotoyuzo Exp $
|
9
|
+
|
10
|
+
module WEBrick
|
11
|
+
module HTTPAuth
|
12
|
+
|
13
|
+
##
|
14
|
+
# User database mixin for HTTPAuth. This mixin dispatches user record
|
15
|
+
# access to the underlying auth_type for this database.
|
16
|
+
|
17
|
+
module UserDB
|
18
|
+
|
19
|
+
##
|
20
|
+
# The authentication type.
|
21
|
+
#
|
22
|
+
# WEBrick::HTTPAuth::BasicAuth or WEBrick::HTTPAuth::DigestAuth are
|
23
|
+
# built-in.
|
24
|
+
|
25
|
+
attr_accessor :auth_type
|
26
|
+
|
27
|
+
##
|
28
|
+
# Creates an obscured password in +realm+ with +user+ and +password+
|
29
|
+
# using the auth_type of this database.
|
30
|
+
|
31
|
+
def make_passwd(realm, user, pass)
|
32
|
+
@auth_type::make_passwd(realm, user, pass)
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Sets a password in +realm+ with +user+ and +password+ for the
|
37
|
+
# auth_type of this database.
|
38
|
+
|
39
|
+
def set_passwd(realm, user, pass)
|
40
|
+
self[user] = pass
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Retrieves a password in +realm+ for +user+ for the auth_type of this
|
45
|
+
# database. +reload_db+ is a dummy value.
|
46
|
+
|
47
|
+
def get_passwd(realm, user, reload_db=false)
|
48
|
+
make_passwd(realm, user, self[user])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
#
|
2
|
+
# httpproxy.rb -- HTTPProxy Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2002 GOTO Kentaro
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
|
10
|
+
# $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
|
11
|
+
|
12
|
+
require "webrick/httpserver"
|
13
|
+
require "net/http"
|
14
|
+
|
15
|
+
Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
|
16
|
+
|
17
|
+
module WEBrick
|
18
|
+
NullReader = Object.new
|
19
|
+
class << NullReader
|
20
|
+
def read(*args)
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
alias gets read
|
24
|
+
end
|
25
|
+
|
26
|
+
FakeProxyURI = Object.new
|
27
|
+
class << FakeProxyURI
|
28
|
+
def method_missing(meth, *args)
|
29
|
+
if %w(scheme host port path query userinfo).member?(meth.to_s)
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# An HTTP Proxy server which proxies GET, HEAD and POST requests.
|
38
|
+
|
39
|
+
class HTTPProxyServer < HTTPServer
|
40
|
+
|
41
|
+
##
|
42
|
+
# Proxy server configurations. The proxy server handles the following
|
43
|
+
# configuration items in addition to those supported by HTTPServer:
|
44
|
+
#
|
45
|
+
# :ProxyAuthProc:: Called with a request and response to authorize a
|
46
|
+
# request
|
47
|
+
# :ProxyVia:: Appended to the via header
|
48
|
+
# :ProxyURI:: The proxy server's URI
|
49
|
+
# :ProxyContentHandler:: Called with a request and resopnse and allows
|
50
|
+
# modification of the response
|
51
|
+
# :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
|
52
|
+
# seconds for read operations
|
53
|
+
|
54
|
+
def initialize(config={}, default=Config::HTTP)
|
55
|
+
super(config, default)
|
56
|
+
c = @config
|
57
|
+
@via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def service(req, res)
|
61
|
+
if req.request_method == "CONNECT"
|
62
|
+
do_CONNECT(req, res)
|
63
|
+
elsif req.unparsed_uri =~ %r!^http://!
|
64
|
+
proxy_service(req, res)
|
65
|
+
else
|
66
|
+
super(req, res)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def proxy_auth(req, res)
|
71
|
+
if proc = @config[:ProxyAuthProc]
|
72
|
+
proc.call(req, res)
|
73
|
+
end
|
74
|
+
req.header.delete("proxy-authorization")
|
75
|
+
end
|
76
|
+
|
77
|
+
def proxy_uri(req, res)
|
78
|
+
# should return upstream proxy server's URI
|
79
|
+
return @config[:ProxyURI]
|
80
|
+
end
|
81
|
+
|
82
|
+
def proxy_service(req, res)
|
83
|
+
# Proxy Authentication
|
84
|
+
proxy_auth(req, res)
|
85
|
+
|
86
|
+
begin
|
87
|
+
self.send("do_#{req.request_method}", req, res)
|
88
|
+
rescue NoMethodError
|
89
|
+
raise HTTPStatus::MethodNotAllowed,
|
90
|
+
"unsupported method `#{req.request_method}'."
|
91
|
+
rescue => err
|
92
|
+
logger.debug("#{err.class}: #{err.message}")
|
93
|
+
raise HTTPStatus::ServiceUnavailable, err.message
|
94
|
+
end
|
95
|
+
|
96
|
+
# Process contents
|
97
|
+
if handler = @config[:ProxyContentHandler]
|
98
|
+
handler.call(req, res)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def do_CONNECT(req, res)
|
103
|
+
# Proxy Authentication
|
104
|
+
proxy_auth(req, res)
|
105
|
+
|
106
|
+
ua = Thread.current[:WEBrickSocket] # User-Agent
|
107
|
+
raise HTTPStatus::InternalServerError,
|
108
|
+
"[BUG] cannot get socket" unless ua
|
109
|
+
|
110
|
+
host, port = req.unparsed_uri.split(":", 2)
|
111
|
+
# Proxy authentication for upstream proxy server
|
112
|
+
if proxy = proxy_uri(req, res)
|
113
|
+
proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
|
114
|
+
if proxy.userinfo
|
115
|
+
credentials = "Basic " + [proxy.userinfo].pack("m").delete("\n")
|
116
|
+
end
|
117
|
+
host, port = proxy.host, proxy.port
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
@logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
|
122
|
+
os = TCPSocket.new(host, port) # origin server
|
123
|
+
|
124
|
+
if proxy
|
125
|
+
@logger.debug("CONNECT: sending a Request-Line")
|
126
|
+
os << proxy_request_line << CRLF
|
127
|
+
@logger.debug("CONNECT: > #{proxy_request_line}")
|
128
|
+
if credentials
|
129
|
+
@logger.debug("CONNECT: sending a credentials")
|
130
|
+
os << "Proxy-Authorization: " << credentials << CRLF
|
131
|
+
end
|
132
|
+
os << CRLF
|
133
|
+
proxy_status_line = os.gets(LF)
|
134
|
+
@logger.debug("CONNECT: read a Status-Line form the upstream server")
|
135
|
+
@logger.debug("CONNECT: < #{proxy_status_line}")
|
136
|
+
if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
|
137
|
+
while line = os.gets(LF)
|
138
|
+
break if /\A(#{CRLF}|#{LF})\z/om =~ line
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise HTTPStatus::BadGateway
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@logger.debug("CONNECT #{host}:#{port}: succeeded")
|
145
|
+
res.status = HTTPStatus::RC_OK
|
146
|
+
rescue => ex
|
147
|
+
@logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
|
148
|
+
res.set_error(ex)
|
149
|
+
raise HTTPStatus::EOFError
|
150
|
+
ensure
|
151
|
+
if handler = @config[:ProxyContentHandler]
|
152
|
+
handler.call(req, res)
|
153
|
+
end
|
154
|
+
res.send_response(ua)
|
155
|
+
access_log(@config, req, res)
|
156
|
+
|
157
|
+
# Should clear request-line not to send the sesponse twice.
|
158
|
+
# see: HTTPServer#run
|
159
|
+
req.parse(NullReader) rescue nil
|
160
|
+
end
|
161
|
+
|
162
|
+
begin
|
163
|
+
while fds = IO::select([ua, os])
|
164
|
+
if fds[0].member?(ua)
|
165
|
+
buf = ua.sysread(1024);
|
166
|
+
@logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
|
167
|
+
os.syswrite(buf)
|
168
|
+
elsif fds[0].member?(os)
|
169
|
+
buf = os.sysread(1024);
|
170
|
+
@logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
|
171
|
+
ua.syswrite(buf)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
rescue => ex
|
175
|
+
os.close
|
176
|
+
@logger.debug("CONNECT #{host}:#{port}: closed")
|
177
|
+
end
|
178
|
+
|
179
|
+
raise HTTPStatus::EOFError
|
180
|
+
end
|
181
|
+
|
182
|
+
def do_GET(req, res)
|
183
|
+
perform_proxy_request(req, res) do |http, path, header|
|
184
|
+
http.get(path, header)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def do_HEAD(req, res)
|
189
|
+
perform_proxy_request(req, res) do |http, path, header|
|
190
|
+
http.head(path, header)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def do_POST(req, res)
|
195
|
+
perform_proxy_request(req, res) do |http, path, header|
|
196
|
+
http.post(path, req.body || "", header)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def do_OPTIONS(req, res)
|
201
|
+
res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
# Some header fields should not be transferred.
|
207
|
+
HopByHop = %w( connection keep-alive proxy-authenticate upgrade
|
208
|
+
proxy-authorization te trailers transfer-encoding )
|
209
|
+
ShouldNotTransfer = %w( set-cookie proxy-connection )
|
210
|
+
def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
|
211
|
+
|
212
|
+
def choose_header(src, dst)
|
213
|
+
connections = split_field(src['connection'])
|
214
|
+
src.each{|key, value|
|
215
|
+
key = key.downcase
|
216
|
+
if HopByHop.member?(key) || # RFC2616: 13.5.1
|
217
|
+
connections.member?(key) || # RFC2616: 14.10
|
218
|
+
ShouldNotTransfer.member?(key) # pragmatics
|
219
|
+
@logger.debug("choose_header: `#{key}: #{value}'")
|
220
|
+
next
|
221
|
+
end
|
222
|
+
dst[key] = value
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
# Net::HTTP is stupid about the multiple header fields.
|
227
|
+
# Here is workaround:
|
228
|
+
def set_cookie(src, dst)
|
229
|
+
if str = src['set-cookie']
|
230
|
+
cookies = []
|
231
|
+
str.split(/,\s*/).each{|token|
|
232
|
+
if /^[^=]+;/o =~ token
|
233
|
+
cookies[-1] << ", " << token
|
234
|
+
elsif /=/o =~ token
|
235
|
+
cookies << token
|
236
|
+
else
|
237
|
+
cookies[-1] << ", " << token
|
238
|
+
end
|
239
|
+
}
|
240
|
+
dst.cookies.replace(cookies)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def set_via(h)
|
245
|
+
if @config[:ProxyVia]
|
246
|
+
if h['via']
|
247
|
+
h['via'] << ", " << @via
|
248
|
+
else
|
249
|
+
h['via'] = @via
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def setup_proxy_header(req, res)
|
255
|
+
# Choose header fields to transfer
|
256
|
+
header = Hash.new
|
257
|
+
choose_header(req, header)
|
258
|
+
set_via(header)
|
259
|
+
return header
|
260
|
+
end
|
261
|
+
|
262
|
+
def setup_upstream_proxy_authentication(req, res, header)
|
263
|
+
if upstream = proxy_uri(req, res)
|
264
|
+
if upstream.userinfo
|
265
|
+
header['proxy-authorization'] =
|
266
|
+
"Basic " + [upstream.userinfo].pack("m").delete("\n")
|
267
|
+
end
|
268
|
+
return upstream
|
269
|
+
end
|
270
|
+
return FakeProxyURI
|
271
|
+
end
|
272
|
+
|
273
|
+
def perform_proxy_request(req, res)
|
274
|
+
uri = req.request_uri
|
275
|
+
path = uri.path.dup
|
276
|
+
path << "?" << uri.query if uri.query
|
277
|
+
header = setup_proxy_header(req, res)
|
278
|
+
upstream = setup_upstream_proxy_authentication(req, res, header)
|
279
|
+
response = nil
|
280
|
+
|
281
|
+
http = Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
|
282
|
+
http.start do
|
283
|
+
if @config[:ProxyTimeout]
|
284
|
+
################################## these issues are
|
285
|
+
http.open_timeout = 30 # secs # necessary (maybe bacause
|
286
|
+
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
287
|
+
##################################
|
288
|
+
end
|
289
|
+
response = yield(http, path, header)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Persistent connection requirements are mysterious for me.
|
293
|
+
# So I will close the connection in every response.
|
294
|
+
res['proxy-connection'] = "close"
|
295
|
+
res['connection'] = "close"
|
296
|
+
|
297
|
+
# Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
298
|
+
res.status = response.code.to_i
|
299
|
+
choose_header(response, res)
|
300
|
+
set_cookie(response, res)
|
301
|
+
set_via(res)
|
302
|
+
res.body = response.body
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|