rack 1.2.8 → 1.3.0.beta
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- data/README +9 -177
- data/Rakefile +2 -1
- data/SPEC +2 -2
- data/lib/rack.rb +2 -13
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/digest/md5.rb +6 -2
- data/lib/rack/auth/digest/params.rb +5 -7
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/backports/uri/common.rb +64 -0
- data/lib/rack/builder.rb +60 -3
- data/lib/rack/chunked.rb +29 -22
- data/lib/rack/conditionalget.rb +35 -16
- data/lib/rack/content_length.rb +3 -3
- data/lib/rack/deflater.rb +5 -2
- data/lib/rack/etag.rb +38 -10
- data/lib/rack/file.rb +76 -43
- data/lib/rack/handler.rb +13 -7
- data/lib/rack/handler/cgi.rb +0 -2
- data/lib/rack/handler/fastcgi.rb +13 -4
- data/lib/rack/handler/lsws.rb +0 -2
- data/lib/rack/handler/mongrel.rb +12 -2
- data/lib/rack/handler/scgi.rb +9 -1
- data/lib/rack/handler/thin.rb +7 -1
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/lint.rb +2 -2
- data/lib/rack/lock.rb +29 -3
- data/lib/rack/methodoverride.rb +1 -1
- data/lib/rack/mime.rb +2 -2
- data/lib/rack/mock.rb +28 -33
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +164 -0
- data/lib/rack/multipart/uploaded_file.rb +30 -0
- data/lib/rack/request.rb +55 -19
- data/lib/rack/response.rb +10 -8
- data/lib/rack/sendfile.rb +14 -18
- data/lib/rack/server.rb +55 -8
- data/lib/rack/session/abstract/id.rb +233 -22
- data/lib/rack/session/cookie.rb +99 -46
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +22 -43
- data/lib/rack/showexceptions.rb +40 -11
- data/lib/rack/showstatus.rb +9 -2
- data/lib/rack/static.rb +29 -9
- data/lib/rack/urlmap.rb +6 -1
- data/lib/rack/utils.rb +67 -326
- data/rack.gemspec +2 -3
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +3 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/lighttpd.conf +1 -1
- data/test/cgi/lighttpd.errors +412 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/text +5 -0
- data/test/multipart/webkit +32 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_auth_digest.rb +20 -5
- data/test/spec_builder.rb +29 -0
- data/test/spec_cgi.rb +11 -0
- data/test/spec_chunked.rb +1 -1
- data/test/spec_commonlogger.rb +1 -1
- data/test/spec_conditionalget.rb +47 -0
- data/test/spec_content_length.rb +0 -6
- data/test/spec_content_type.rb +5 -5
- data/test/spec_deflater.rb +46 -2
- data/test/spec_etag.rb +68 -1
- data/test/spec_fastcgi.rb +11 -0
- data/test/spec_file.rb +54 -3
- data/test/spec_handler.rb +23 -5
- data/test/spec_lint.rb +2 -2
- data/test/spec_lock.rb +111 -5
- data/test/spec_methodoverride.rb +2 -2
- data/test/spec_mock.rb +3 -3
- data/test/spec_mongrel.rb +1 -2
- data/test/spec_multipart.rb +279 -0
- data/test/spec_request.rb +222 -38
- data/test/spec_response.rb +9 -3
- data/test/spec_server.rb +74 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +97 -15
- data/test/spec_session_memcache.rb +60 -50
- data/test/spec_session_pool.rb +63 -40
- data/test/spec_showexceptions.rb +64 -0
- data/test/spec_static.rb +23 -0
- data/test/spec_utils.rb +65 -351
- data/test/spec_webrick.rb +23 -4
- metadata +35 -15
- data/test/spec_auth.rb +0 -57
data/lib/rack/server.rb
CHANGED
@@ -47,6 +47,12 @@ module Rack
|
|
47
47
|
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
48
48
|
options[:Port] = port
|
49
49
|
}
|
50
|
+
|
51
|
+
opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
|
52
|
+
name, value = name.split('=', 2)
|
53
|
+
value = true if value.nil?
|
54
|
+
options[name.to_sym] = value
|
55
|
+
}
|
50
56
|
|
51
57
|
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
52
58
|
options[:environment] = e
|
@@ -57,26 +63,57 @@ module Rack
|
|
57
63
|
}
|
58
64
|
|
59
65
|
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
|
60
|
-
options[:pid] = f
|
66
|
+
options[:pid] = ::File.expand_path(f)
|
61
67
|
}
|
62
68
|
|
63
69
|
opts.separator ""
|
64
70
|
opts.separator "Common options:"
|
65
71
|
|
66
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
72
|
+
opts.on_tail("-h", "-?", "--help", "Show this message") do
|
67
73
|
puts opts
|
74
|
+
puts handler_opts(options)
|
75
|
+
|
68
76
|
exit
|
69
77
|
end
|
70
78
|
|
71
79
|
opts.on_tail("--version", "Show version") do
|
72
|
-
puts "Rack #{Rack.version}"
|
80
|
+
puts "Rack #{Rack.version} (Release: #{Rack.release})"
|
73
81
|
exit
|
74
82
|
end
|
75
83
|
end
|
76
|
-
|
84
|
+
|
85
|
+
begin
|
86
|
+
opt_parser.parse! args
|
87
|
+
rescue OptionParser::InvalidOption => e
|
88
|
+
warn e.message
|
89
|
+
abort opt_parser.to_s
|
90
|
+
end
|
91
|
+
|
77
92
|
options[:config] = args.last if args.last
|
78
93
|
options
|
79
94
|
end
|
95
|
+
|
96
|
+
def handler_opts(options)
|
97
|
+
begin
|
98
|
+
info = []
|
99
|
+
server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
|
100
|
+
if server && server.respond_to?(:valid_options)
|
101
|
+
info << ""
|
102
|
+
info << "Server-specific options for #{server.name}:"
|
103
|
+
|
104
|
+
has_options = false
|
105
|
+
server.valid_options.each do |name, description|
|
106
|
+
next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
|
107
|
+
info << " -O %-21s %s" % [name, description]
|
108
|
+
has_options = true
|
109
|
+
end
|
110
|
+
return "" if !has_options
|
111
|
+
end
|
112
|
+
info.join("\n")
|
113
|
+
rescue NameError
|
114
|
+
return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
|
115
|
+
end
|
116
|
+
end
|
80
117
|
end
|
81
118
|
|
82
119
|
# Start a new rack server (like running rackup). This will parse ARGV and
|
@@ -136,6 +173,7 @@ module Rack
|
|
136
173
|
# require the given libraries
|
137
174
|
def initialize(options = nil)
|
138
175
|
@options = options
|
176
|
+
@app = options[:app] if options && options[:app]
|
139
177
|
end
|
140
178
|
|
141
179
|
def options
|
@@ -144,7 +182,7 @@ module Rack
|
|
144
182
|
|
145
183
|
def default_options
|
146
184
|
{
|
147
|
-
:environment => "development",
|
185
|
+
:environment => ENV['RACK_ENV'] || "development",
|
148
186
|
:pid => nil,
|
149
187
|
:Port => 9292,
|
150
188
|
:Host => "0.0.0.0",
|
@@ -165,10 +203,20 @@ module Rack
|
|
165
203
|
end
|
166
204
|
end
|
167
205
|
|
206
|
+
def self.logging_middleware
|
207
|
+
lambda { |server|
|
208
|
+
server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
168
212
|
def self.middleware
|
169
213
|
@middleware ||= begin
|
170
214
|
m = Hash.new {|h,k| h[k] = []}
|
171
|
-
m["deployment"].concat
|
215
|
+
m["deployment"].concat [
|
216
|
+
[Rack::ContentLength],
|
217
|
+
[Rack::Chunked],
|
218
|
+
logging_middleware
|
219
|
+
]
|
172
220
|
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
|
173
221
|
m
|
174
222
|
end
|
@@ -229,7 +277,7 @@ module Rack
|
|
229
277
|
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
|
230
278
|
args.clear if ENV.include?("REQUEST_METHOD")
|
231
279
|
|
232
|
-
options.merge! opt_parser.parse!
|
280
|
+
options.merge! opt_parser.parse!(args)
|
233
281
|
options[:config] = ::File.expand_path(options[:config])
|
234
282
|
ENV["RACK_ENV"] = options[:environment]
|
235
283
|
options
|
@@ -259,7 +307,6 @@ module Rack
|
|
259
307
|
Process.setsid
|
260
308
|
exit if fork
|
261
309
|
Dir.chdir "/"
|
262
|
-
::File.umask 0000
|
263
310
|
STDIN.reopen "/dev/null"
|
264
311
|
STDOUT.reopen "/dev/null", "a"
|
265
312
|
STDERR.reopen "/dev/null", "a"
|
@@ -4,12 +4,135 @@
|
|
4
4
|
require 'time'
|
5
5
|
require 'rack/request'
|
6
6
|
require 'rack/response'
|
7
|
+
begin
|
8
|
+
require 'securerandom'
|
9
|
+
rescue LoadError
|
10
|
+
# We just won't get securerandom
|
11
|
+
end
|
7
12
|
|
8
13
|
module Rack
|
9
14
|
|
10
15
|
module Session
|
11
16
|
|
12
17
|
module Abstract
|
18
|
+
ENV_SESSION_KEY = 'rack.session'.freeze
|
19
|
+
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
20
|
+
|
21
|
+
# Thin wrapper around Hash that allows us to lazily load session id into session_options.
|
22
|
+
|
23
|
+
class OptionsHash < Hash #:nodoc:
|
24
|
+
def initialize(by, env, default_options)
|
25
|
+
@by = by
|
26
|
+
@env = env
|
27
|
+
@session_id_loaded = false
|
28
|
+
merge!(default_options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
load_session_id! if key == :id && session_id_not_loaded?
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def session_id_not_loaded?
|
39
|
+
!key?(:id) && !@session_id_loaded
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_session_id!
|
43
|
+
self[:id] = @by.send(:extract_session_id, @env)
|
44
|
+
@session_id_loaded = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# SessionHash is responsible to lazily load the session from store.
|
49
|
+
|
50
|
+
class SessionHash < Hash
|
51
|
+
def initialize(by, env)
|
52
|
+
super()
|
53
|
+
@by = by
|
54
|
+
@env = env
|
55
|
+
@loaded = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
load_for_read!
|
60
|
+
super(key.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_key?(key)
|
64
|
+
load_for_read!
|
65
|
+
super(key.to_s)
|
66
|
+
end
|
67
|
+
alias :key? :has_key?
|
68
|
+
alias :include? :has_key?
|
69
|
+
|
70
|
+
def []=(key, value)
|
71
|
+
load_for_write!
|
72
|
+
super(key.to_s, value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def clear
|
76
|
+
load_for_write!
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_hash
|
81
|
+
load_for_read!
|
82
|
+
h = {}.replace(self)
|
83
|
+
h.delete_if { |k,v| v.nil? }
|
84
|
+
h
|
85
|
+
end
|
86
|
+
|
87
|
+
def update(hash)
|
88
|
+
load_for_write!
|
89
|
+
super(stringify_keys(hash))
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete(key)
|
93
|
+
load_for_write!
|
94
|
+
super(key.to_s)
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
load_for_read!
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def exists?
|
103
|
+
return @exists if instance_variable_defined?(:@exists)
|
104
|
+
@exists = @by.send(:session_exists?, @env)
|
105
|
+
end
|
106
|
+
|
107
|
+
def loaded?
|
108
|
+
@loaded
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def load_for_read!
|
114
|
+
load! if !loaded? && exists?
|
115
|
+
end
|
116
|
+
|
117
|
+
def load_for_write!
|
118
|
+
load! unless loaded?
|
119
|
+
end
|
120
|
+
|
121
|
+
def load!
|
122
|
+
id, session = @by.send(:load_session, @env)
|
123
|
+
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
124
|
+
replace(stringify_keys(session))
|
125
|
+
@loaded = true
|
126
|
+
end
|
127
|
+
|
128
|
+
def stringify_keys(other)
|
129
|
+
hash = {}
|
130
|
+
other.each do |key, value|
|
131
|
+
hash[key.to_s] = value
|
132
|
+
end
|
133
|
+
hash
|
134
|
+
end
|
135
|
+
end
|
13
136
|
|
14
137
|
# ID sets up a basic framework for implementing an id based sessioning
|
15
138
|
# service. Cookies sent to the client for maintaining sessions will only
|
@@ -34,9 +157,13 @@ module Rack
|
|
34
157
|
# recommended to change its value.
|
35
158
|
#
|
36
159
|
# Is Rack::Utils::Context compatible.
|
160
|
+
#
|
161
|
+
# Not included by default; you must require 'rack/session/abstract/id'
|
162
|
+
# to use.
|
37
163
|
|
38
164
|
class ID
|
39
165
|
DEFAULT_OPTIONS = {
|
166
|
+
:key => 'rack.session',
|
40
167
|
:path => '/',
|
41
168
|
:domain => nil,
|
42
169
|
:expire_after => nil,
|
@@ -44,14 +171,19 @@ module Rack
|
|
44
171
|
:httponly => true,
|
45
172
|
:defer => false,
|
46
173
|
:renew => false,
|
47
|
-
:sidbits => 128
|
174
|
+
:sidbits => 128,
|
175
|
+
:cookie_only => true,
|
176
|
+
:secure_random => begin ::SecureRandom rescue false end
|
48
177
|
}
|
49
178
|
|
50
179
|
attr_reader :key, :default_options
|
180
|
+
|
51
181
|
def initialize(app, options={})
|
52
182
|
@app = app
|
53
|
-
@key = options[:key] || "rack.session"
|
54
183
|
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
184
|
+
@key = options[:key] || "rack.session"
|
185
|
+
@cookie_only = @default_options.delete(:cookie_only)
|
186
|
+
initialize_sid
|
55
187
|
end
|
56
188
|
|
57
189
|
def call(env)
|
@@ -59,40 +191,91 @@ module Rack
|
|
59
191
|
end
|
60
192
|
|
61
193
|
def context(env, app=@app)
|
62
|
-
|
194
|
+
prepare_session(env)
|
63
195
|
status, headers, body = app.call(env)
|
64
196
|
commit_session(env, status, headers, body)
|
65
197
|
end
|
66
198
|
|
67
199
|
private
|
68
200
|
|
201
|
+
def initialize_sid
|
202
|
+
sidbits = @default_options.delete(:sidbits)
|
203
|
+
@sid_secure = @default_options.delete(:secure_random)
|
204
|
+
@sid_template = "%0#{sidbits / 4}x"
|
205
|
+
@sid_rand_width = (2**sidbits - 1)
|
206
|
+
end
|
207
|
+
|
69
208
|
# Generate a new session id using Ruby #rand. The size of the
|
70
209
|
# session id is controlled by the :sidbits option.
|
71
210
|
# Monkey patch this to use custom methods for session id generation.
|
72
211
|
|
73
212
|
def generate_sid
|
74
|
-
|
75
|
-
|
213
|
+
r = if @sid_secure
|
214
|
+
SecureRandom.random_number(@sid_rand_width)
|
215
|
+
else
|
216
|
+
Kernel.rand(@sid_rand_width)
|
217
|
+
end
|
218
|
+
@sid_template % r
|
219
|
+
end
|
220
|
+
|
221
|
+
# Sets the lazy session at 'rack.session' and places options and session
|
222
|
+
# metadata into 'rack.session.options'.
|
223
|
+
|
224
|
+
def prepare_session(env)
|
225
|
+
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
226
|
+
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
76
227
|
end
|
77
228
|
|
78
229
|
# Extracts the session id from provided cookies and passes it and the
|
79
|
-
# environment to #get_session.
|
80
|
-
# 'rack.session', and places options and session metadata into
|
81
|
-
# 'rack.session.options'.
|
230
|
+
# environment to #get_session.
|
82
231
|
|
83
232
|
def load_session(env)
|
233
|
+
sid = current_session_id(env)
|
234
|
+
sid, session = get_session(env, sid)
|
235
|
+
[sid, session || {}]
|
236
|
+
end
|
237
|
+
|
238
|
+
# Extract session id from request object.
|
239
|
+
|
240
|
+
def extract_session_id(env)
|
84
241
|
request = Rack::Request.new(env)
|
85
|
-
|
242
|
+
sid = request.cookies[@key]
|
243
|
+
sid ||= request.params[@key] unless @cookie_only
|
244
|
+
sid
|
245
|
+
end
|
86
246
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
247
|
+
# Returns the current session id from the OptionsHash.
|
248
|
+
|
249
|
+
def current_session_id(env)
|
250
|
+
env[ENV_SESSION_OPTIONS_KEY][:id]
|
251
|
+
end
|
252
|
+
|
253
|
+
# Check if the session exists or not.
|
254
|
+
|
255
|
+
def session_exists?(env)
|
256
|
+
value = current_session_id(env)
|
257
|
+
value && !value.empty?
|
258
|
+
end
|
259
|
+
|
260
|
+
# Session should be commited if it was loaded, any of specific options like :renew, :drop
|
261
|
+
# or :expire_after was given and the security permissions match.
|
262
|
+
|
263
|
+
def commit_session?(env, session, options)
|
264
|
+
(loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
|
265
|
+
end
|
266
|
+
|
267
|
+
def loaded_session?(session)
|
268
|
+
!session.is_a?(SessionHash) || session.loaded?
|
269
|
+
end
|
93
270
|
|
94
|
-
|
95
|
-
|
271
|
+
def force_options?(options)
|
272
|
+
options.values_at(:renew, :drop, :defer, :expire_after).any?
|
273
|
+
end
|
274
|
+
|
275
|
+
def secure_session?(env, options)
|
276
|
+
return true unless options[:secure]
|
277
|
+
request = Rack::Request.new(env)
|
278
|
+
request.ssl?
|
96
279
|
end
|
97
280
|
|
98
281
|
# Acquires the session from the environment and the session id from
|
@@ -103,22 +286,42 @@ module Rack
|
|
103
286
|
def commit_session(env, status, headers, body)
|
104
287
|
session = env['rack.session']
|
105
288
|
options = env['rack.session.options']
|
106
|
-
session_id = options[:id]
|
107
289
|
|
108
|
-
if
|
290
|
+
if options[:drop] || options[:renew]
|
291
|
+
session_id = destroy_session(env, options[:id] || generate_sid, options)
|
292
|
+
return [status, headers, body] unless session_id
|
293
|
+
end
|
294
|
+
|
295
|
+
return [status, headers, body] unless commit_session?(env, session, options)
|
296
|
+
|
297
|
+
session.send(:load!) unless loaded_session?(session)
|
298
|
+
session = session.to_hash
|
299
|
+
session_id ||= options[:id] || generate_sid
|
300
|
+
|
301
|
+
if not data = set_session(env, session_id, session, options)
|
109
302
|
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
110
303
|
elsif options[:defer] and not options[:renew]
|
111
304
|
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
|
112
305
|
else
|
113
306
|
cookie = Hash.new
|
114
|
-
cookie[:value] =
|
115
|
-
cookie[:expires] = Time.now + options[:expire_after]
|
116
|
-
|
307
|
+
cookie[:value] = data
|
308
|
+
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
309
|
+
set_cookie(env, headers, cookie.merge!(options))
|
117
310
|
end
|
118
311
|
|
119
312
|
[status, headers, body]
|
120
313
|
end
|
121
314
|
|
315
|
+
# Sets the cookie back to the client with session id. We skip the cookie
|
316
|
+
# setting if the value didn't change (sid is the same) or expires was given.
|
317
|
+
|
318
|
+
def set_cookie(env, headers, cookie)
|
319
|
+
request = Rack::Request.new(env)
|
320
|
+
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
321
|
+
Utils.set_cookie_header!(headers, @key, cookie)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
122
325
|
# All thread safety and session retrival proceedures should occur here.
|
123
326
|
# Should return [session_id, session].
|
124
327
|
# If nil is provided as the session id, generation of a new valid id
|
@@ -131,9 +334,17 @@ module Rack
|
|
131
334
|
# All thread safety and session storage proceedures should occur here.
|
132
335
|
# Should return true or false dependant on whether or not the session
|
133
336
|
# was saved or not.
|
337
|
+
|
134
338
|
def set_session(env, sid, session, options)
|
135
339
|
raise '#set_session not implemented.'
|
136
340
|
end
|
341
|
+
|
342
|
+
# All thread safety and session destroy proceedures should occur here.
|
343
|
+
# Should return a new session id or nil if options[:drop]
|
344
|
+
|
345
|
+
def destroy_session(env, sid, options)
|
346
|
+
raise '#destroy_session not implemented'
|
347
|
+
end
|
137
348
|
end
|
138
349
|
end
|
139
350
|
end
|