rack 1.4.7 → 1.5.0.beta.1
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.rdoc +2 -30
- data/Rakefile +1 -0
- data/SPEC +68 -4
- data/example/protectedlobster.rb +1 -1
- data/lib/rack.rb +2 -14
- data/lib/rack/auth/abstract/request.rb +1 -5
- data/lib/rack/builder.rb +8 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/config.rb +5 -0
- data/lib/rack/deflater.rb +2 -1
- data/lib/rack/file.rb +25 -28
- data/lib/rack/handler.rb +18 -5
- data/lib/rack/handler/mongrel.rb +1 -1
- data/lib/rack/handler/scgi.rb +1 -1
- data/lib/rack/handler/thin.rb +6 -3
- data/lib/rack/handler/webrick.rb +1 -0
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +132 -7
- data/lib/rack/lobster.rb +3 -3
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/methodoverride.rb +0 -2
- data/lib/rack/mime.rb +29 -0
- data/lib/rack/multipart/parser.rb +0 -9
- data/lib/rack/request.rb +66 -25
- data/lib/rack/response.rb +1 -2
- data/lib/rack/sendfile.rb +18 -4
- data/lib/rack/server.rb +20 -12
- data/lib/rack/session/abstract/id.rb +60 -59
- data/lib/rack/session/cookie.rb +11 -16
- data/lib/rack/utils.rb +97 -85
- data/rack.gemspec +1 -6
- data/test/spec_builder.rb +7 -0
- data/test/spec_cgi.rb +1 -1
- data/test/spec_chunked.rb +3 -5
- data/test/spec_content_length.rb +3 -6
- data/test/spec_deflater.rb +26 -9
- data/test/spec_fastcgi.rb +1 -1
- data/test/spec_file.rb +24 -11
- data/test/spec_head.rb +3 -8
- data/test/spec_lint.rb +6 -6
- data/test/spec_lock.rb +4 -7
- data/test/spec_methodoverride.rb +4 -1
- data/test/spec_mime.rb +51 -0
- data/test/spec_mongrel.rb +1 -1
- data/test/spec_multipart.rb +15 -49
- data/test/spec_nulllogger.rb +3 -6
- data/test/spec_request.rb +112 -18
- data/test/spec_response.rb +8 -8
- data/test/spec_sendfile.rb +52 -13
- data/test/spec_server.rb +6 -0
- data/test/spec_session_abstract_id.rb +11 -1
- data/test/spec_session_cookie.rb +140 -153
- data/test/spec_thin.rb +6 -1
- data/test/spec_utils.rb +23 -17
- data/test/spec_webrick.rb +1 -1
- metadata +37 -83
- checksums.yaml +0 -7
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
- data/test/spec_auth.rb +0 -57
data/lib/rack/response.rb
CHANGED
@@ -21,8 +21,7 @@ module Rack
|
|
21
21
|
|
22
22
|
def initialize(body=[], status=200, header={})
|
23
23
|
@status = status.to_i
|
24
|
-
@header = Utils::HeaderHash.new(
|
25
|
-
merge(header)
|
24
|
+
@header = Utils::HeaderHash.new.merge(header)
|
26
25
|
|
27
26
|
@chunked = "chunked" == @header['Transfer-Encoding']
|
28
27
|
@writer = lambda { |x| @body << x }
|
data/lib/rack/sendfile.rb
CHANGED
@@ -89,13 +89,23 @@ module Rack
|
|
89
89
|
# RequestHeader Set X-Sendfile-Type X-Sendfile
|
90
90
|
# ProxyPassReverse / http://localhost:8001/
|
91
91
|
# XSendFile on
|
92
|
+
#
|
93
|
+
# === Mapping parameter
|
94
|
+
#
|
95
|
+
# The third parameter allows for an overriding extension of the
|
96
|
+
# X-Accel-Mapping header. Mappings should be provided in tuples of internal to
|
97
|
+
# external. The internal values may contain regular expression syntax, they
|
98
|
+
# will be matched with case indifference.
|
92
99
|
|
93
100
|
class Sendfile
|
94
101
|
F = ::File
|
95
102
|
|
96
|
-
def initialize(app, variation=nil)
|
103
|
+
def initialize(app, variation=nil, mappings=[])
|
97
104
|
@app = app
|
98
105
|
@variation = variation
|
106
|
+
@mappings = mappings.map do |internal, external|
|
107
|
+
[/^#{internal}/i, external]
|
108
|
+
end
|
99
109
|
end
|
100
110
|
|
101
111
|
def call(env)
|
@@ -107,6 +117,7 @@ module Rack
|
|
107
117
|
if url = map_accel_path(env, path)
|
108
118
|
headers['Content-Length'] = '0'
|
109
119
|
headers[type] = url
|
120
|
+
body.close if body.respond_to?(:close)
|
110
121
|
body = []
|
111
122
|
else
|
112
123
|
env['rack.errors'].puts "X-Accel-Mapping header missing"
|
@@ -115,6 +126,7 @@ module Rack
|
|
115
126
|
path = F.expand_path(body.to_path)
|
116
127
|
headers['Content-Length'] = '0'
|
117
128
|
headers[type] = path
|
129
|
+
body.close if body.respond_to?(:close)
|
118
130
|
body = []
|
119
131
|
when '', nil
|
120
132
|
else
|
@@ -131,10 +143,12 @@ module Rack
|
|
131
143
|
env['HTTP_X_SENDFILE_TYPE']
|
132
144
|
end
|
133
145
|
|
134
|
-
def map_accel_path(env,
|
135
|
-
if mapping =
|
146
|
+
def map_accel_path(env, path)
|
147
|
+
if mapping = @mappings.find { |internal,_| internal =~ path }
|
148
|
+
path.sub(*mapping)
|
149
|
+
elsif mapping = env['HTTP_X_ACCEL_MAPPING']
|
136
150
|
internal, external = mapping.split('=', 2).map{ |p| p.strip }
|
137
|
-
|
151
|
+
path.sub(/^#{internal}/i, external)
|
138
152
|
end
|
139
153
|
end
|
140
154
|
end
|
data/lib/rack/server.rb
CHANGED
@@ -17,6 +17,10 @@ module Rack
|
|
17
17
|
lineno += 1
|
18
18
|
}
|
19
19
|
|
20
|
+
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
|
21
|
+
options[:builder] = line
|
22
|
+
}
|
23
|
+
|
20
24
|
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
21
25
|
options[:debug] = true
|
22
26
|
}
|
@@ -36,7 +40,7 @@ module Rack
|
|
36
40
|
|
37
41
|
opts.separator ""
|
38
42
|
opts.separator "Rack options:"
|
39
|
-
opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
|
43
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
|
40
44
|
options[:server] = s
|
41
45
|
}
|
42
46
|
|
@@ -192,15 +196,7 @@ module Rack
|
|
192
196
|
end
|
193
197
|
|
194
198
|
def app
|
195
|
-
@app ||=
|
196
|
-
if !::File.exist? options[:config]
|
197
|
-
abort "configuration #{options[:config]} not found"
|
198
|
-
end
|
199
|
-
|
200
|
-
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
|
201
|
-
self.options.merge! options
|
202
|
-
app
|
203
|
-
end
|
199
|
+
@app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
|
204
200
|
end
|
205
201
|
|
206
202
|
def self.logging_middleware
|
@@ -273,6 +269,20 @@ module Rack
|
|
273
269
|
end
|
274
270
|
|
275
271
|
private
|
272
|
+
def build_app_and_options_from_config
|
273
|
+
if !::File.exist? options[:config]
|
274
|
+
abort "configuration #{options[:config]} not found"
|
275
|
+
end
|
276
|
+
|
277
|
+
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
|
278
|
+
self.options.merge! options
|
279
|
+
app
|
280
|
+
end
|
281
|
+
|
282
|
+
def build_app_from_string
|
283
|
+
Rack::Builder.new_from_string(self.options[:builder])
|
284
|
+
end
|
285
|
+
|
276
286
|
def parse_options(args)
|
277
287
|
options = default_options
|
278
288
|
|
@@ -337,8 +347,6 @@ module Rack
|
|
337
347
|
return :exited unless ::File.exist?(options[:pid])
|
338
348
|
|
339
349
|
pid = ::File.read(options[:pid]).to_i
|
340
|
-
return :dead if pid == 0
|
341
|
-
|
342
350
|
Process.kill(0, pid)
|
343
351
|
:running
|
344
352
|
rescue Errno::ESRCH
|
@@ -18,85 +18,85 @@ module Rack
|
|
18
18
|
ENV_SESSION_KEY = 'rack.session'.freeze
|
19
19
|
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
20
20
|
|
21
|
-
#
|
21
|
+
# SessionHash is responsible to lazily load the session from store.
|
22
|
+
|
23
|
+
class SessionHash
|
24
|
+
include Enumerable
|
25
|
+
attr_writer :id
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
@by = by
|
27
|
+
def initialize(store, env)
|
28
|
+
@store = store
|
26
29
|
@env = env
|
27
|
-
@
|
28
|
-
merge!(default_options)
|
30
|
+
@loaded = false
|
29
31
|
end
|
30
32
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
33
|
+
def id
|
34
|
+
return @id if @loaded or instance_variable_defined?(:@id)
|
35
|
+
@id = @store.send(:extract_session_id, @env)
|
34
36
|
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
def session_id_not_loaded?
|
39
|
-
!(@session_id_loaded || key?(:id))
|
38
|
+
def options
|
39
|
+
@env[ENV_SESSION_OPTIONS_KEY]
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
|
44
|
-
@
|
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
|
42
|
+
def each(&block)
|
43
|
+
load_for_read!
|
44
|
+
@data.each(&block)
|
56
45
|
end
|
57
46
|
|
58
47
|
def [](key)
|
59
48
|
load_for_read!
|
60
|
-
|
49
|
+
@data[key.to_s]
|
61
50
|
end
|
51
|
+
alias :fetch :[]
|
62
52
|
|
63
53
|
def has_key?(key)
|
64
54
|
load_for_read!
|
65
|
-
|
55
|
+
@data.has_key?(key.to_s)
|
66
56
|
end
|
67
57
|
alias :key? :has_key?
|
68
58
|
alias :include? :has_key?
|
69
59
|
|
70
60
|
def []=(key, value)
|
71
61
|
load_for_write!
|
72
|
-
|
62
|
+
@data[key.to_s] = value
|
73
63
|
end
|
64
|
+
alias :store :[]=
|
74
65
|
|
75
66
|
def clear
|
76
67
|
load_for_write!
|
77
|
-
|
68
|
+
@data.clear
|
69
|
+
end
|
70
|
+
|
71
|
+
def destroy
|
72
|
+
clear
|
73
|
+
@id = @store.send(:destroy_session, @env, id, options)
|
78
74
|
end
|
79
75
|
|
80
76
|
def to_hash
|
81
77
|
load_for_read!
|
82
|
-
|
83
|
-
h.delete_if { |k,v| v.nil? }
|
84
|
-
h
|
78
|
+
@data.dup
|
85
79
|
end
|
86
80
|
|
87
81
|
def update(hash)
|
88
82
|
load_for_write!
|
89
|
-
|
83
|
+
@data.update(stringify_keys(hash))
|
84
|
+
end
|
85
|
+
alias :merge! :update
|
86
|
+
|
87
|
+
def replace(hash)
|
88
|
+
load_for_write!
|
89
|
+
@data.replace(stringify_keys(hash))
|
90
90
|
end
|
91
91
|
|
92
92
|
def delete(key)
|
93
93
|
load_for_write!
|
94
|
-
|
94
|
+
@data.delete(key.to_s)
|
95
95
|
end
|
96
96
|
|
97
97
|
def inspect
|
98
98
|
if loaded?
|
99
|
-
|
99
|
+
@data.inspect
|
100
100
|
else
|
101
101
|
"#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
|
102
102
|
end
|
@@ -104,7 +104,8 @@ module Rack
|
|
104
104
|
|
105
105
|
def exists?
|
106
106
|
return @exists if instance_variable_defined?(:@exists)
|
107
|
-
@
|
107
|
+
@data = {}
|
108
|
+
@exists = @store.send(:session_exists?, @env)
|
108
109
|
end
|
109
110
|
|
110
111
|
def loaded?
|
@@ -113,12 +114,7 @@ module Rack
|
|
113
114
|
|
114
115
|
def empty?
|
115
116
|
load_for_read!
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
def merge!(hash)
|
120
|
-
load_for_write!
|
121
|
-
super
|
117
|
+
@data.empty?
|
122
118
|
end
|
123
119
|
|
124
120
|
private
|
@@ -132,9 +128,8 @@ module Rack
|
|
132
128
|
end
|
133
129
|
|
134
130
|
def load!
|
135
|
-
id, session = @
|
136
|
-
@
|
137
|
-
replace(stringify_keys(session))
|
131
|
+
@id, session = @store.send(:load_session, @env)
|
132
|
+
@data = stringify_keys(session)
|
138
133
|
@loaded = true
|
139
134
|
end
|
140
135
|
|
@@ -225,7 +220,7 @@ module Rack
|
|
225
220
|
|
226
221
|
def generate_sid(secure = @sid_secure)
|
227
222
|
if secure
|
228
|
-
|
223
|
+
secure.hex(@sid_length)
|
229
224
|
else
|
230
225
|
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
|
231
226
|
end
|
@@ -238,8 +233,8 @@ module Rack
|
|
238
233
|
|
239
234
|
def prepare_session(env)
|
240
235
|
session_was = env[ENV_SESSION_KEY]
|
241
|
-
env[ENV_SESSION_KEY] =
|
242
|
-
env[ENV_SESSION_OPTIONS_KEY] =
|
236
|
+
env[ENV_SESSION_KEY] = session_class.new(self, env)
|
237
|
+
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
243
238
|
env[ENV_SESSION_KEY].merge! session_was if session_was
|
244
239
|
end
|
245
240
|
|
@@ -261,10 +256,10 @@ module Rack
|
|
261
256
|
sid
|
262
257
|
end
|
263
258
|
|
264
|
-
# Returns the current session id from the
|
259
|
+
# Returns the current session id from the SessionHash.
|
265
260
|
|
266
261
|
def current_session_id(env)
|
267
|
-
env[
|
262
|
+
env[ENV_SESSION_KEY].id
|
268
263
|
end
|
269
264
|
|
270
265
|
# Check if the session exists or not.
|
@@ -287,7 +282,7 @@ module Rack
|
|
287
282
|
end
|
288
283
|
|
289
284
|
def loaded_session?(session)
|
290
|
-
!session.is_a?(
|
285
|
+
!session.is_a?(session_class) || session.loaded?
|
291
286
|
end
|
292
287
|
|
293
288
|
def forced_session_update?(session, options)
|
@@ -310,21 +305,21 @@ module Rack
|
|
310
305
|
# response with the session's id.
|
311
306
|
|
312
307
|
def commit_session(env, status, headers, body)
|
313
|
-
session = env[
|
314
|
-
options =
|
308
|
+
session = env[ENV_SESSION_KEY]
|
309
|
+
options = session.options
|
315
310
|
|
316
311
|
if options[:drop] || options[:renew]
|
317
|
-
session_id = destroy_session(env,
|
312
|
+
session_id = destroy_session(env, session.id || generate_sid, options)
|
318
313
|
return [status, headers, body] unless session_id
|
319
314
|
end
|
320
315
|
|
321
316
|
return [status, headers, body] unless commit_session?(env, session, options)
|
322
317
|
|
323
318
|
session.send(:load!) unless loaded_session?(session)
|
324
|
-
|
325
|
-
|
319
|
+
session_id ||= session.id
|
320
|
+
session_data = session.to_hash.delete_if { |k,v| v.nil? }
|
326
321
|
|
327
|
-
if not data = set_session(env, session_id,
|
322
|
+
if not data = set_session(env, session_id, session_data, options)
|
328
323
|
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
329
324
|
elsif options[:defer] and not options[:renew]
|
330
325
|
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
|
@@ -348,6 +343,12 @@ module Rack
|
|
348
343
|
end
|
349
344
|
end
|
350
345
|
|
346
|
+
# Allow subclasses to prepare_session for different Session classes
|
347
|
+
|
348
|
+
def session_class
|
349
|
+
SessionHash
|
350
|
+
end
|
351
|
+
|
351
352
|
# All thread safety and session retrival proceedures should occur here.
|
352
353
|
# Should return [session_id, session].
|
353
354
|
# If nil is provided as the session id, generation of a new valid id
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -61,6 +61,7 @@ module Rack
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def decode(str)
|
64
|
+
return unless str
|
64
65
|
::Marshal.load(super(str)) rescue nil
|
65
66
|
end
|
66
67
|
end
|
@@ -97,7 +98,7 @@ module Rack
|
|
97
98
|
|
98
99
|
private
|
99
100
|
|
100
|
-
def
|
101
|
+
def get_session(env, sid)
|
101
102
|
data = unpacked_cookie_data(env)
|
102
103
|
data = persistent_session_id!(data)
|
103
104
|
[data["session_id"], data]
|
@@ -114,14 +115,7 @@ module Rack
|
|
114
115
|
|
115
116
|
if @secrets.size > 0 && session_data
|
116
117
|
session_data, digest = session_data.split("--")
|
117
|
-
|
118
|
-
if session_data && digest
|
119
|
-
ok = @secrets.any? do |secret|
|
120
|
-
secret && Rack::Utils.secure_compare(digest, generate_hmac(session_data, secret))
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
session_data = nil unless ok
|
118
|
+
session_data = nil unless digest_match?(session_data, digest)
|
125
119
|
end
|
126
120
|
|
127
121
|
coder.decode(session_data) || {}
|
@@ -134,18 +128,12 @@ module Rack
|
|
134
128
|
data
|
135
129
|
end
|
136
130
|
|
137
|
-
# Overwrite set cookie to bypass content equality and always stream the cookie.
|
138
|
-
|
139
|
-
def set_cookie(env, headers, cookie)
|
140
|
-
Utils.set_cookie_header!(headers, @key, cookie)
|
141
|
-
end
|
142
|
-
|
143
131
|
def set_session(env, session_id, session, options)
|
144
132
|
session = session.merge("session_id" => session_id)
|
145
133
|
session_data = coder.encode(session)
|
146
134
|
|
147
135
|
if @secrets.first
|
148
|
-
session_data
|
136
|
+
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
|
149
137
|
end
|
150
138
|
|
151
139
|
if session_data.size > (4096 - @key.size)
|
@@ -161,6 +149,13 @@ module Rack
|
|
161
149
|
generate_sid unless options[:drop]
|
162
150
|
end
|
163
151
|
|
152
|
+
def digest_match?(data, digest)
|
153
|
+
return unless data && digest
|
154
|
+
@secrets.any? do |secret|
|
155
|
+
digest == generate_hmac(data, secret)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
164
159
|
def generate_hmac(data, secret)
|
165
160
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
|
166
161
|
end
|
data/lib/rack/utils.rb
CHANGED
@@ -51,23 +51,12 @@ module Rack
|
|
51
51
|
|
52
52
|
class << self
|
53
53
|
attr_accessor :key_space_limit
|
54
|
-
attr_accessor :param_depth_limit
|
55
|
-
attr_accessor :multipart_part_limit
|
56
54
|
end
|
57
55
|
|
58
56
|
# The default number of bytes to allow parameter keys to take up.
|
59
57
|
# This helps prevent a rogue client from flooding a Request.
|
60
58
|
self.key_space_limit = 65536
|
61
59
|
|
62
|
-
# Default depth at which the parameter parser will raise an exception for
|
63
|
-
# being too deep. This helps prevent SystemStackErrors
|
64
|
-
self.param_depth_limit = 100
|
65
|
-
#
|
66
|
-
# The maximum number of parts a request can contain. Accepting to many part
|
67
|
-
# can lead to the server running out of file handles.
|
68
|
-
# Set to `0` for no limit.
|
69
|
-
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
70
|
-
|
71
60
|
# Stolen from Mongrel, with some small modifications:
|
72
61
|
# Parses a query string by breaking it up at the '&'
|
73
62
|
# and ';' characters. You can also use this to parse
|
@@ -111,9 +100,7 @@ module Rack
|
|
111
100
|
end
|
112
101
|
module_function :parse_nested_query
|
113
102
|
|
114
|
-
def normalize_params(params, name, v = nil
|
115
|
-
raise RangeError if depth <= 0
|
116
|
-
|
103
|
+
def normalize_params(params, name, v = nil)
|
117
104
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
118
105
|
k = $1 || ''
|
119
106
|
after = $' || ''
|
@@ -131,14 +118,14 @@ module Rack
|
|
131
118
|
params[k] ||= []
|
132
119
|
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
133
120
|
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
|
134
|
-
normalize_params(params[k].last, child_key, v
|
121
|
+
normalize_params(params[k].last, child_key, v)
|
135
122
|
else
|
136
|
-
params[k] << normalize_params(params.class.new, child_key, v
|
123
|
+
params[k] << normalize_params(params.class.new, child_key, v)
|
137
124
|
end
|
138
125
|
else
|
139
126
|
params[k] ||= params.class.new
|
140
127
|
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
141
|
-
params[k] = normalize_params(params[k], after, v
|
128
|
+
params[k] = normalize_params(params[k], after, v)
|
142
129
|
end
|
143
130
|
|
144
131
|
return params
|
@@ -180,6 +167,31 @@ module Rack
|
|
180
167
|
end
|
181
168
|
module_function :build_nested_query
|
182
169
|
|
170
|
+
def q_values(q_value_header)
|
171
|
+
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
172
|
+
value, parameters = part.split(/\s*;\s*/, 2)
|
173
|
+
quality = 1.0
|
174
|
+
if md = /\Aq=([\d.]+)/.match(parameters)
|
175
|
+
quality = md[1].to_f
|
176
|
+
end
|
177
|
+
[value, quality]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
module_function :q_values
|
181
|
+
|
182
|
+
def best_q_match(q_value_header, available_mimes)
|
183
|
+
values = q_values(q_value_header)
|
184
|
+
|
185
|
+
values.map do |req_mime, quality|
|
186
|
+
match = available_mimes.first { |am| Rack::Mime.match?(am, req_mime) }
|
187
|
+
next unless match
|
188
|
+
[match, quality]
|
189
|
+
end.compact.sort_by do |match, quality|
|
190
|
+
(match.split('/', 2).count('*') * -10) + quality
|
191
|
+
end.last.first
|
192
|
+
end
|
193
|
+
module_function :best_q_match
|
194
|
+
|
183
195
|
ESCAPE_HTML = {
|
184
196
|
"&" => "&",
|
185
197
|
"<" => "<",
|
@@ -237,6 +249,7 @@ module Rack
|
|
237
249
|
when Hash
|
238
250
|
domain = "; domain=" + value[:domain] if value[:domain]
|
239
251
|
path = "; path=" + value[:path] if value[:path]
|
252
|
+
max_age = "; max-age=" + value[:max_age] if value[:max_age]
|
240
253
|
# According to RFC 2109, we need dashes here.
|
241
254
|
# N.B.: cgi.rb uses spaces...
|
242
255
|
expires = "; expires=" +
|
@@ -248,7 +261,7 @@ module Rack
|
|
248
261
|
value = [value] unless Array === value
|
249
262
|
cookie = escape(key) + "=" +
|
250
263
|
value.map { |v| escape v }.join("&") +
|
251
|
-
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
264
|
+
"#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}"
|
252
265
|
|
253
266
|
case header["Set-Cookie"]
|
254
267
|
when nil, ''
|
@@ -287,6 +300,7 @@ module Rack
|
|
287
300
|
|
288
301
|
set_cookie_header!(header, key,
|
289
302
|
{:value => '', :path => nil, :domain => nil,
|
303
|
+
:max_age => '0',
|
290
304
|
:expires => Time.at(0) }.merge(value))
|
291
305
|
|
292
306
|
nil
|
@@ -355,18 +369,6 @@ module Rack
|
|
355
369
|
end
|
356
370
|
module_function :byte_ranges
|
357
371
|
|
358
|
-
# Constant time string comparison.
|
359
|
-
def secure_compare(a, b)
|
360
|
-
return false unless bytesize(a) == bytesize(b)
|
361
|
-
|
362
|
-
l = a.unpack("C*")
|
363
|
-
|
364
|
-
r, i = 0, -1
|
365
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
366
|
-
r == 0
|
367
|
-
end
|
368
|
-
module_function :secure_compare
|
369
|
-
|
370
372
|
# Context allows the use of a compatible middleware at different points
|
371
373
|
# in a request handling stack. A compatible middleware must define
|
372
374
|
# #context which should take the arguments env and app. The first of which
|
@@ -498,62 +500,72 @@ module Rack
|
|
498
500
|
|
499
501
|
# Every standard HTTP code mapped to the appropriate message.
|
500
502
|
# Generated with:
|
501
|
-
#
|
502
|
-
#
|
503
|
-
#
|
503
|
+
# irb -ropen-uri -rnokogiri
|
504
|
+
# > Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each{|r|
|
505
|
+
# puts "#{r.css('value').text} => '#{r.css('description').text}'"}
|
504
506
|
HTTP_STATUS_CODES = {
|
505
|
-
100
|
506
|
-
101
|
507
|
-
102
|
508
|
-
200
|
509
|
-
201
|
510
|
-
202
|
511
|
-
203
|
512
|
-
204
|
513
|
-
205
|
514
|
-
206
|
515
|
-
207
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
507
|
+
100 => 'Continue',
|
508
|
+
101 => 'Switching Protocols',
|
509
|
+
102 => 'Processing',
|
510
|
+
200 => 'OK',
|
511
|
+
201 => 'Created',
|
512
|
+
202 => 'Accepted',
|
513
|
+
203 => 'Non-Authoritative Information',
|
514
|
+
204 => 'No Content',
|
515
|
+
205 => 'Reset Content',
|
516
|
+
206 => 'Partial Content',
|
517
|
+
207 => 'Multi-Status',
|
518
|
+
208 => 'Already Reported',
|
519
|
+
226 => 'IM Used',
|
520
|
+
300 => 'Multiple Choices',
|
521
|
+
301 => 'Moved Permanently',
|
522
|
+
302 => 'Found',
|
523
|
+
303 => 'See Other',
|
524
|
+
304 => 'Not Modified',
|
525
|
+
305 => 'Use Proxy',
|
526
|
+
306 => 'Reserved',
|
527
|
+
307 => 'Temporary Redirect',
|
528
|
+
308 => 'Permanent Redirect',
|
529
|
+
400 => 'Bad Request',
|
530
|
+
401 => 'Unauthorized',
|
531
|
+
402 => 'Payment Required',
|
532
|
+
403 => 'Forbidden',
|
533
|
+
404 => 'Not Found',
|
534
|
+
405 => 'Method Not Allowed',
|
535
|
+
406 => 'Not Acceptable',
|
536
|
+
407 => 'Proxy Authentication Required',
|
537
|
+
408 => 'Request Timeout',
|
538
|
+
409 => 'Conflict',
|
539
|
+
410 => 'Gone',
|
540
|
+
411 => 'Length Required',
|
541
|
+
412 => 'Precondition Failed',
|
542
|
+
413 => 'Request Entity Too Large',
|
543
|
+
414 => 'Request-URI Too Long',
|
544
|
+
415 => 'Unsupported Media Type',
|
545
|
+
416 => 'Requested Range Not Satisfiable',
|
546
|
+
417 => 'Expectation Failed',
|
547
|
+
422 => 'Unprocessable Entity',
|
548
|
+
423 => 'Locked',
|
549
|
+
424 => 'Failed Dependency',
|
550
|
+
425 => 'Reserved for WebDAV advanced collections expired proposal',
|
551
|
+
426 => 'Upgrade Required',
|
552
|
+
427 => 'Unassigned',
|
553
|
+
428 => 'Precondition Required',
|
554
|
+
429 => 'Too Many Requests',
|
555
|
+
430 => 'Unassigned',
|
556
|
+
431 => 'Request Header Fields Too Large',
|
557
|
+
500 => 'Internal Server Error',
|
558
|
+
501 => 'Not Implemented',
|
559
|
+
502 => 'Bad Gateway',
|
560
|
+
503 => 'Service Unavailable',
|
561
|
+
504 => 'Gateway Timeout',
|
562
|
+
505 => 'HTTP Version Not Supported',
|
563
|
+
506 => 'Variant Also Negotiates (Experimental)',
|
564
|
+
507 => 'Insufficient Storage',
|
565
|
+
508 => 'Loop Detected',
|
566
|
+
509 => 'Unassigned',
|
567
|
+
510 => 'Not Extended',
|
568
|
+
511 => 'Network Authentication Required'
|
557
569
|
}
|
558
570
|
|
559
571
|
# Responses with HTTP status codes that should not have an entity body
|