rubycas-server 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +37 -0
- data/Manifest.txt +10 -0
- data/Rakefile +3 -2
- data/bin/rubycas-server-ctl +1 -1
- data/config.example.yml +44 -11
- data/custom_views.example.rb +11 -0
- data/lib/casserver/authenticators/ldap.rb +11 -5
- data/lib/casserver/conf.rb +35 -12
- data/lib/casserver/controllers.rb +111 -23
- data/lib/casserver/models.rb +23 -0
- data/lib/casserver/postambles.rb +21 -24
- data/lib/casserver/utils.rb +16 -0
- data/lib/casserver/version.rb +3 -3
- data/lib/casserver/views.rb +50 -36
- data/lib/casserver.rb +9 -2
- data/lib/themes/cas.css +1 -0
- data/vendor/camping-1.5.180/lib/camping/db.rb +78 -0
- data/vendor/camping-1.5.180/lib/camping/fastcgi.rb +244 -0
- data/vendor/camping-1.5.180/lib/camping/reloader.rb +163 -0
- data/vendor/camping-1.5.180/lib/camping/session.rb +123 -0
- data/vendor/camping-1.5.180/lib/camping/webrick.rb +65 -0
- data/vendor/camping-1.5.180/lib/camping-unabridged.rb +762 -0
- data/vendor/camping-1.5.180/lib/camping.rb +55 -0
- metadata +10 -11
data/lib/casserver/postambles.rb
CHANGED
@@ -13,26 +13,28 @@ module CASServer
|
|
13
13
|
key_path = CASServer::Conf.ssl_key || CASServer::Conf.ssl_cert
|
14
14
|
# look for the key in the ssl_cert if no ssl_key is specified
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
webrick_options = {:BindAddress => "0.0.0.0", :Port => CASServer::Conf.port}
|
17
|
+
|
18
|
+
unless cert_path.nil? && key_path.nil?
|
19
|
+
raise "'#{cert_path}' is not a valid ssl certificate. Your 'ssl_cert' configuration" +
|
20
|
+
" setting must be a path to a valid ssl certificate file." unless
|
21
|
+
File.exists? cert_path
|
22
|
+
|
23
|
+
raise "'#{key_path}' is not a valid ssl private key. Your 'ssl_key' configuration" +
|
24
|
+
" setting must be a path to a valid ssl private key file." unless
|
25
|
+
File.exists? key_path
|
26
|
+
|
27
|
+
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
|
28
|
+
key = OpenSSL::PKey::RSA.new(File.read(key_path))
|
29
|
+
|
30
|
+
webrick_options[:SSLEnable] = true
|
31
|
+
webrick_options[:SSLVerifyClient] = ::OpenSSL::SSL::VERIFY_NONE
|
32
|
+
webrick_options[:SSLCertificate] = cert
|
33
|
+
webrick_options[:SSLPrivateKey] = key
|
34
|
+
end
|
26
35
|
|
27
36
|
begin
|
28
|
-
s = WEBrick::HTTPServer.new(
|
29
|
-
:BindAddress => "0.0.0.0",
|
30
|
-
:Port => CASServer::Conf.port,
|
31
|
-
:SSLEnable => true,
|
32
|
-
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
|
33
|
-
:SSLCertificate => cert,
|
34
|
-
:SSLPrivateKey => key
|
35
|
-
)
|
37
|
+
s = WEBrick::HTTPServer.new(webrick_options)
|
36
38
|
rescue Errno::EACCES
|
37
39
|
puts "\nThe server could not launch. Are you running on a privileged port? (e.g. port 443) If so, you must run the server as root."
|
38
40
|
exit 2
|
@@ -41,7 +43,7 @@ module CASServer
|
|
41
43
|
CASServer.create
|
42
44
|
s.mount "#{CASServer::Conf.uri_path}", WEBrick::CampingHandler, CASServer
|
43
45
|
|
44
|
-
puts "\n** CASServer is running at http
|
46
|
+
puts "\n** CASServer is running at http#{webrick_options[:SSLEnable] ? 's' : ''}://#{Socket.gethostname}:#{CASServer::Conf.port}#{CASServer::Conf.uri_path} and logging to '#{CASServer::Conf.log[:file]}'\n\n"
|
45
47
|
|
46
48
|
# This lets Ctrl+C shut down your server
|
47
49
|
trap(:INT) do
|
@@ -68,11 +70,6 @@ module CASServer
|
|
68
70
|
require 'rubygems'
|
69
71
|
require 'mongrel/camping'
|
70
72
|
|
71
|
-
# camping has fixes for mongrel currently only availabe in SVN
|
72
|
-
# ... you can install camping from svn (1.5.180) by running:
|
73
|
-
# gem install camping --source code.whytheluckystiff.net
|
74
|
-
gem 'camping', '~> 1.5.180'
|
75
|
-
|
76
73
|
if $DAEMONIZE
|
77
74
|
# check if log and pid are writable before daemonizing, otherwise we won't be able to notify
|
78
75
|
# the user if we run into trouble later (since once daemonized, we can't write to stdout/stderr)
|
data/lib/casserver/utils.rb
CHANGED
@@ -5,6 +5,22 @@ module CASServer
|
|
5
5
|
"#{Time.now.to_i}r%X" % rand(10**32)
|
6
6
|
end
|
7
7
|
module_function :random_string
|
8
|
+
|
9
|
+
def log_controller_action(controller, params)
|
10
|
+
$LOG << "\n"
|
11
|
+
|
12
|
+
/`(.*)'/.match(caller[1])
|
13
|
+
method = $~[1]
|
14
|
+
|
15
|
+
if params.respond_to? :dup
|
16
|
+
params2 = params.dup
|
17
|
+
params2['password'] = '******' if params2['password']
|
18
|
+
else
|
19
|
+
params2 = params
|
20
|
+
end
|
21
|
+
$LOG.debug("Processing #{controller}::#{method} #{params2.inspect}")
|
22
|
+
end
|
23
|
+
module_function :log_controller_action
|
8
24
|
|
9
25
|
class Logger < ::Logger
|
10
26
|
def initialize(logdev, shift_age = 0, shift_size = 1048576)
|
data/lib/casserver/version.rb
CHANGED
data/lib/casserver/views.rb
CHANGED
@@ -8,7 +8,6 @@ Markaby::Builder.set(:indent, 2)
|
|
8
8
|
module CASServer::Views
|
9
9
|
|
10
10
|
def layout
|
11
|
-
|
12
11
|
# wrap as XHTML only when auto_validation is on, otherwise pass right through
|
13
12
|
if @use_layout
|
14
13
|
xhtml_strict do
|
@@ -28,9 +27,10 @@ module CASServer::Views
|
|
28
27
|
|
29
28
|
|
30
29
|
# 2.1.3
|
30
|
+
# The full login page.
|
31
31
|
def login
|
32
32
|
@use_layout = true
|
33
|
-
|
33
|
+
|
34
34
|
table(:id => "login-box") do
|
35
35
|
tr do
|
36
36
|
td(:colspan => 2) do
|
@@ -52,47 +52,54 @@ module CASServer::Views
|
|
52
52
|
img(:id => "logo", :src => "/themes/#{current_theme}/logo.png")
|
53
53
|
end
|
54
54
|
td(:id => "login-form-container") do
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
tr do
|
86
|
-
td(:colspan => 2, :id => "infoline") { infoline }
|
87
|
-
end
|
88
|
-
end
|
55
|
+
@include_infoline = true
|
56
|
+
login_form
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Just the login form.
|
63
|
+
def login_form
|
64
|
+
form(:method => "post", :action => @form_action || '/login', :id => "login-form",
|
65
|
+
:onsubmit => "submit = document.getElementById('login-submit'); submit.value='Please wait...'; submit.disabled=true; return true;") do
|
66
|
+
table(:id => "form-layout") do
|
67
|
+
tr do
|
68
|
+
td(:id => "username-label-container") do
|
69
|
+
label(:id => "username-label", :for => "username") { "Username" }
|
70
|
+
end
|
71
|
+
td(:id => "username-container") do
|
72
|
+
input(:type => "text", :id => "username", :name => "username",
|
73
|
+
:size => "32", :tabindex => "1", :accesskey => "u")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
tr do
|
77
|
+
td(:id => "password-label-container") do
|
78
|
+
label(:id => "password-label", :for => "password") { "Password" }
|
79
|
+
end
|
80
|
+
td(:id => "password-container") do
|
81
|
+
input(:type => "password", :id => "password", :name => "password",
|
82
|
+
:size => "32", :tabindex => "2", :accesskey => "p", :autocomplete => "off")
|
89
83
|
end
|
90
84
|
end
|
85
|
+
tr do
|
86
|
+
td{}
|
87
|
+
td(:id => "submit-container") do
|
88
|
+
input(:type => "hidden", :id => "lt", :name => "lt", :value => @lt)
|
89
|
+
input(:type => "hidden", :id => "service", :name => "service", :value => @service)
|
90
|
+
input(:type => "hidden", :id => "warn", :name => "warn", :value => @warn)
|
91
|
+
input(:type => "submit", :class => "button", :accesskey => "l", :value => "LOGIN", :tabindex => "4", :id => "login-submit")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
tr do
|
95
|
+
td(:colspan => 2, :id => "infoline") { infoline }
|
96
|
+
end if @include_infoline
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
94
100
|
|
95
101
|
# 2.4.2
|
102
|
+
# CAS 1.0 validate response.
|
96
103
|
def validate
|
97
104
|
if @success
|
98
105
|
text "yes\n#{@username}\n"
|
@@ -102,6 +109,7 @@ module CASServer::Views
|
|
102
109
|
end
|
103
110
|
|
104
111
|
# 2.5.2
|
112
|
+
# CAS 2.0 service validate response.
|
105
113
|
def service_validate
|
106
114
|
if @success
|
107
115
|
tag!("cas:serviceResponse", 'xmlns:cas' => "http://www.yale.edu/tp/cas") do
|
@@ -120,6 +128,7 @@ module CASServer::Views
|
|
120
128
|
end
|
121
129
|
|
122
130
|
# 2.6.2
|
131
|
+
# CAS 2.0 proxy validate response.
|
123
132
|
def proxy_validate
|
124
133
|
if @success
|
125
134
|
tag!("cas:serviceResponse", 'xmlns:cas' => "http://www.yale.edu/tp/cas") do
|
@@ -145,6 +154,7 @@ module CASServer::Views
|
|
145
154
|
end
|
146
155
|
|
147
156
|
# 2.7.2
|
157
|
+
# CAS 2.0 proxy request response.
|
148
158
|
def proxy
|
149
159
|
if @success
|
150
160
|
tag!("cas:serviceResponse", 'xmlns:cas' => "http://www.yale.edu/tp/cas") do
|
@@ -183,3 +193,7 @@ module CASServer::Views
|
|
183
193
|
end
|
184
194
|
module_function :infoline
|
185
195
|
end
|
196
|
+
|
197
|
+
if CASServer::Conf.custom_views_file
|
198
|
+
require CASServer::Conf.custom_views_file
|
199
|
+
end
|
data/lib/casserver.rb
CHANGED
@@ -14,7 +14,9 @@ unless Object.method_defined? :gem
|
|
14
14
|
alias gem require_gem
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
|
18
|
+
#gem 'camping', '~> 1.5.180'
|
19
|
+
$: << $CASSERVER_HOME + "/../vendor/camping-1.5.180/lib"
|
18
20
|
require 'camping'
|
19
21
|
|
20
22
|
require 'active_support'
|
@@ -64,7 +66,7 @@ CASServer.init_logger
|
|
64
66
|
def CASServer.create
|
65
67
|
CASServer::Models.create_schema
|
66
68
|
|
67
|
-
$LOG.info("RubyCAS-Server initialized.")
|
69
|
+
$LOG.info("RubyCAS-Server #{CASServer::VERSION::STRING} initialized.")
|
68
70
|
|
69
71
|
$LOG.debug("Configuration is:\n#{$CONF.to_yaml}")
|
70
72
|
$LOG.debug("Authenticator is: #{$AUTH}")
|
@@ -88,6 +90,11 @@ if __FILE__ == $0 || $RUN
|
|
88
90
|
$LOG.warn("Unable to create a pid file. You must use mongrel or webrick for this feature.")
|
89
91
|
end
|
90
92
|
|
93
|
+
require 'casserver/version'
|
94
|
+
puts
|
95
|
+
puts "*** Starting RubyCAS-Server #{CASServer::VERSION::STRING} using codebase at #{$CASSERVER_HOME}"
|
96
|
+
|
97
|
+
|
91
98
|
begin
|
92
99
|
raise NoMethodError if CASServer::Conf.server.nil?
|
93
100
|
send(CASServer::Conf.server)
|
data/lib/themes/cas.css
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
class MissingLibrary < Exception #:nodoc: all
|
2
|
+
end
|
3
|
+
begin
|
4
|
+
require 'active_record'
|
5
|
+
rescue LoadError => e
|
6
|
+
raise MissingLibrary, "ActiveRecord could not be loaded (is it installed?): #{e.message}"
|
7
|
+
end
|
8
|
+
|
9
|
+
$AR_EXTRAS = %{
|
10
|
+
Base = ActiveRecord::Base unless const_defined? :Base
|
11
|
+
|
12
|
+
def Y; ActiveRecord::Base.verify_active_connections!; self; end
|
13
|
+
|
14
|
+
class SchemaInfo < Base
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.V(n)
|
18
|
+
@final = [n, @final.to_i].max
|
19
|
+
m = (@migrations ||= [])
|
20
|
+
Class.new(ActiveRecord::Migration) do
|
21
|
+
meta_def(:version) { n }
|
22
|
+
meta_def(:inherited) { |k| m << k }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create_schema(opts = {})
|
27
|
+
opts[:assume] ||= 0
|
28
|
+
opts[:version] ||= @final
|
29
|
+
if @migrations
|
30
|
+
unless SchemaInfo.table_exists?
|
31
|
+
ActiveRecord::Schema.define do
|
32
|
+
create_table SchemaInfo.table_name do |t|
|
33
|
+
t.column :version, :float
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
si = SchemaInfo.find(:first) || SchemaInfo.new(:version => opts[:assume])
|
39
|
+
if si.version < opts[:version]
|
40
|
+
@migrations.each do |k|
|
41
|
+
k.migrate(:up) if si.version < k.version and k.version <= opts[:version]
|
42
|
+
k.migrate(:down) if si.version > k.version and k.version > opts[:version]
|
43
|
+
end
|
44
|
+
si.update_attributes(:version => opts[:version])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
module Camping
|
51
|
+
module Models
|
52
|
+
A = ActiveRecord
|
53
|
+
# Base is an alias for ActiveRecord::Base. The big warning I'm going to give you
|
54
|
+
# about this: *Base overloads table_name_prefix.* This means that if you have a
|
55
|
+
# model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>.
|
56
|
+
#
|
57
|
+
# ActiveRecord is not loaded if you never reference this class. The minute you
|
58
|
+
# use the ActiveRecord or Camping::Models::Base class, then the ActiveRecord library
|
59
|
+
# is loaded.
|
60
|
+
Base = A::Base
|
61
|
+
|
62
|
+
# The default prefix for Camping model classes is the topmost module name lowercase
|
63
|
+
# and followed with an underscore.
|
64
|
+
#
|
65
|
+
# Tepee::Models::Page.table_name_prefix
|
66
|
+
# #=> "tepee_pages"
|
67
|
+
#
|
68
|
+
def Base.table_name_prefix
|
69
|
+
"#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'')
|
70
|
+
end
|
71
|
+
module_eval $AR_EXTRAS
|
72
|
+
end
|
73
|
+
end
|
74
|
+
Camping::S.sub! "autoload:Base,'camping/db'", ""
|
75
|
+
Camping::S.sub! "def Y;self;end", $AR_EXTRAS
|
76
|
+
Camping::Apps.each do |app|
|
77
|
+
app::Models.module_eval $AR_EXTRAS
|
78
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
# == About camping/fastcgi.rb
|
2
|
+
#
|
3
|
+
# Camping works very well with FastCGI, since your application is only loaded
|
4
|
+
# once -- when FastCGI starts. In addition, this class lets you mount several
|
5
|
+
# Camping apps under a single FastCGI process, to help save memory costs.
|
6
|
+
#
|
7
|
+
# So where do you use the Camping::FastCGI class? Use it in your application's
|
8
|
+
# postamble and then you can point your web server directly at your application.
|
9
|
+
# See Camping::FastCGI docs for more.
|
10
|
+
require 'camping'
|
11
|
+
require 'fcgi'
|
12
|
+
|
13
|
+
module Camping
|
14
|
+
# Camping::FastCGI is a small class for hooking one or more Camping apps up to
|
15
|
+
# FastCGI. Generally, you'll use this class in your application's postamble.
|
16
|
+
#
|
17
|
+
# == The Smallest Example
|
18
|
+
#
|
19
|
+
# if __FILE__ == $0
|
20
|
+
# require 'camping/fastcgi'
|
21
|
+
# Camping::FastCGI.start(YourApp)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# This example is stripped down to the basics. The postamble has no database
|
25
|
+
# connection. It just loads this class and calls Camping::FastCGI.start.
|
26
|
+
#
|
27
|
+
# Now, in Lighttpd or Apache, you can point to your app's file, which will
|
28
|
+
# be executed, only to discover that your app now speaks the FastCGI protocol.
|
29
|
+
#
|
30
|
+
# Here's a sample lighttpd.conf (tested with Lighttpd 1.4.11) to serve as example:
|
31
|
+
#
|
32
|
+
# server.port = 3044
|
33
|
+
# server.bind = "127.0.0.1"
|
34
|
+
# server.modules = ( "mod_fastcgi" )
|
35
|
+
# server.document-root = "/var/www/camping/blog/"
|
36
|
+
# server.errorlog = "/var/www/camping/blog/error.log"
|
37
|
+
#
|
38
|
+
# #### fastcgi module
|
39
|
+
# fastcgi.server = ( "/" => (
|
40
|
+
# "localhost" => (
|
41
|
+
# "socket" => "/tmp/camping-blog.socket",
|
42
|
+
# "bin-path" => "/var/www/camping/blog/blog.rb",
|
43
|
+
# "check-local" => "disable",
|
44
|
+
# "max-procs" => 1 ) ) )
|
45
|
+
#
|
46
|
+
# The file <tt>/var/www/camping/blog/blog.rb</tt> is the Camping app with
|
47
|
+
# the postamble.
|
48
|
+
#
|
49
|
+
# == Mounting Many Apps
|
50
|
+
#
|
51
|
+
# require 'camping/fastcgi'
|
52
|
+
# fast = Camping::FastCGI.new
|
53
|
+
# fast.mount("/blog", Blog)
|
54
|
+
# fast.mount("/tepee", Tepee)
|
55
|
+
# fast.mount("/", Index)
|
56
|
+
# fast.start
|
57
|
+
#
|
58
|
+
class FastCGI
|
59
|
+
CHUNK_SIZE=(4 * 1024)
|
60
|
+
|
61
|
+
attr_reader :mounts
|
62
|
+
|
63
|
+
# Creates a Camping::FastCGI class with empty mounts.
|
64
|
+
def initialize
|
65
|
+
@mounts = {}
|
66
|
+
end
|
67
|
+
# Mounts a Camping application. The +dir+ being the name of the directory
|
68
|
+
# to serve as the application's root. The +app+ is a Camping class.
|
69
|
+
def mount(dir, app)
|
70
|
+
dir.gsub!(/\/{2,}/, '/')
|
71
|
+
dir.gsub!(/\/+$/, '')
|
72
|
+
@mounts[dir] = app
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Starts the FastCGI main loop.
|
77
|
+
def start(&blk)
|
78
|
+
FCGI.each do |req|
|
79
|
+
camp_do(req, &blk)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# A simple single-app starter mechanism
|
84
|
+
#
|
85
|
+
# Camping::FastCGI.start(Blog)
|
86
|
+
#
|
87
|
+
def self.start(app)
|
88
|
+
cf = Camping::FastCGI.new
|
89
|
+
cf.mount("/", app)
|
90
|
+
cf.start
|
91
|
+
end
|
92
|
+
|
93
|
+
# Serve an entire directory of Camping apps. (See
|
94
|
+
# http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.)
|
95
|
+
#
|
96
|
+
# Use this method inside your FastCGI dispatcher:
|
97
|
+
#
|
98
|
+
# #!/usr/local/bin/ruby
|
99
|
+
# require 'rubygems'
|
100
|
+
# require 'camping/fastcgi'
|
101
|
+
# Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => "/path/to/db"
|
102
|
+
# Camping::FastCGI.serve("/home/why/cvs/camping/examples")
|
103
|
+
#
|
104
|
+
def self.serve(path, index=nil)
|
105
|
+
require 'camping/reloader'
|
106
|
+
if File.directory? path
|
107
|
+
fast = Camping::FastCGI.new
|
108
|
+
script_load = proc do |script|
|
109
|
+
app = Camping::Reloader.new(script)
|
110
|
+
fast.mount("/#{app.mount}", app)
|
111
|
+
app
|
112
|
+
end
|
113
|
+
Dir[File.join(path, '*.rb')].each &script_load
|
114
|
+
fast.mount("/", index) if index
|
115
|
+
|
116
|
+
fast.start do |dir, app|
|
117
|
+
Dir[File.join(path, dir, '*.rb')].each do |script|
|
118
|
+
smount = "/" + File.basename(script, '.rb')
|
119
|
+
script_load[script] unless fast.mounts.has_key? smount
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
start(Camping::Reloader.new(path))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def camp_do(req)
|
130
|
+
root, path, dir, app = "/"
|
131
|
+
if ENV['FORCE_ROOT'] and ENV['FORCE_ROOT'].to_i == 1
|
132
|
+
path = req.env['SCRIPT_NAME']
|
133
|
+
else
|
134
|
+
root = req.env['SCRIPT_NAME']
|
135
|
+
path = req.env['PATH_INFO']
|
136
|
+
end
|
137
|
+
|
138
|
+
dir, app = @mounts.max { |a,b| match(path, a[0]) <=> match(path, b[0]) }
|
139
|
+
unless dir and app
|
140
|
+
dir, app = '/', Camping
|
141
|
+
end
|
142
|
+
yield dir, app if block_given?
|
143
|
+
|
144
|
+
req.env['SERVER_SCRIPT_NAME'] = req.env['SCRIPT_NAME']
|
145
|
+
req.env['SERVER_PATH_INFO'] = req.env['PATH_INFO']
|
146
|
+
req.env['SCRIPT_NAME'] = File.join(root, dir)
|
147
|
+
req.env['PATH_INFO'] = path.gsub(/^#{dir}/, '')
|
148
|
+
|
149
|
+
controller = app.run(SeekStream.new(req.in), req.env)
|
150
|
+
sendfile = nil
|
151
|
+
headers = {}
|
152
|
+
controller.headers.each do |k, v|
|
153
|
+
if k =~ /^X-SENDFILE$/i and !ENV['SERVER_X_SENDFILE']
|
154
|
+
sendfile = v
|
155
|
+
else
|
156
|
+
headers[k] = v
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
body = controller.body
|
161
|
+
controller.body = ""
|
162
|
+
controller.headers = headers
|
163
|
+
|
164
|
+
req.out << controller.to_s
|
165
|
+
if sendfile
|
166
|
+
File.open(sendfile, "rb") do |f|
|
167
|
+
while chunk = f.read(CHUNK_SIZE) and chunk.length > 0
|
168
|
+
req.out << chunk
|
169
|
+
end
|
170
|
+
end
|
171
|
+
elsif body.respond_to? :read
|
172
|
+
while chunk = body.read(CHUNK_SIZE) and chunk.length > 0
|
173
|
+
req.out << chunk
|
174
|
+
end
|
175
|
+
body.close if body.respond_to? :close
|
176
|
+
else
|
177
|
+
req.out << body.to_s
|
178
|
+
end
|
179
|
+
rescue Exception => e
|
180
|
+
req.out << server_error(root, path, exc, req)
|
181
|
+
ensure
|
182
|
+
req.finish
|
183
|
+
end
|
184
|
+
|
185
|
+
def server_error(root, path, exc, req)
|
186
|
+
"Content-Type: text/html\r\n\r\n" +
|
187
|
+
"<h1>Camping Problem!</h1>" +
|
188
|
+
"<h2><strong>#{root}</strong>#{path}</h2>" +
|
189
|
+
"<h3>#{exc.class} #{esc exc.message}</h3>" +
|
190
|
+
"<ul>" + exc.backtrace.map { |bt| "<li>#{esc bt}</li>" }.join + "</ul>" +
|
191
|
+
"<hr /><p>#{req.env.inspect}</p>"
|
192
|
+
end
|
193
|
+
|
194
|
+
def match(path, mount)
|
195
|
+
m = path.match(/^#{Regexp::quote mount}(\/|$)/)
|
196
|
+
if m; m.end(0)
|
197
|
+
else -1
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def esc(str)
|
202
|
+
str.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
|
203
|
+
end
|
204
|
+
|
205
|
+
class SeekStream
|
206
|
+
def initialize(stream)
|
207
|
+
@last_read = ""
|
208
|
+
@stream = stream
|
209
|
+
@buffer = ""
|
210
|
+
end
|
211
|
+
def eof?
|
212
|
+
@buffer.empty? && @stream.eof?
|
213
|
+
end
|
214
|
+
def each
|
215
|
+
while true
|
216
|
+
pull(1024) until eof? or @buffer.index("\n")
|
217
|
+
return nil if eof?
|
218
|
+
yield @buffer.slice!(0..(@buffer.index("\n") || -1))
|
219
|
+
end
|
220
|
+
end
|
221
|
+
def pull(len)
|
222
|
+
@buffer += @stream.read(len).to_s
|
223
|
+
end
|
224
|
+
def read(len = 16384)
|
225
|
+
pull(len)
|
226
|
+
@last_read =
|
227
|
+
if eof?
|
228
|
+
nil
|
229
|
+
else
|
230
|
+
@buffer.slice!(0...len)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
def seek(len, typ)
|
234
|
+
raise NotImplementedError, "only IO::SEEK_CUR is supported with SeekStream" if typ != IO::SEEK_CUR
|
235
|
+
raise NotImplementedError, "only rewinding is supported with SeekStream" if len > 0
|
236
|
+
raise NotImplementedError, "rewinding #{-len} past the buffer #{@last_read.size} start not supported with SeekStream" if -len > @last_read.size
|
237
|
+
@buffer = @last_read[len..-1] + @buffer
|
238
|
+
@last_read = ""
|
239
|
+
self
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|