rack 1.5.5 → 1.6.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.
- checksums.yaml +4 -4
- data/KNOWN-ISSUES +14 -0
- data/README.rdoc +10 -6
- data/Rakefile +3 -4
- data/SPEC +59 -23
- data/lib/rack.rb +2 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +1 -1
- data/lib/rack/builder.rb +19 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/chunked.rb +12 -1
- data/lib/rack/commonlogger.rb +13 -5
- data/lib/rack/conditionalget.rb +14 -2
- data/lib/rack/content_length.rb +5 -1
- data/lib/rack/deflater.rb +52 -13
- data/lib/rack/directory.rb +8 -2
- data/lib/rack/etag.rb +14 -6
- data/lib/rack/file.rb +10 -14
- data/lib/rack/handler.rb +2 -0
- data/lib/rack/handler/fastcgi.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -2
- data/lib/rack/handler/scgi.rb +4 -1
- data/lib/rack/handler/thin.rb +8 -2
- data/lib/rack/handler/webrick.rb +46 -6
- data/lib/rack/head.rb +7 -2
- data/lib/rack/lint.rb +73 -25
- data/lib/rack/lobster.rb +8 -3
- data/lib/rack/methodoverride.rb +14 -3
- data/lib/rack/mime.rb +1 -15
- data/lib/rack/mock.rb +15 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/multipart/parser.rb +107 -53
- data/lib/rack/multipart/uploaded_file.rb +2 -2
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/request.rb +38 -24
- data/lib/rack/response.rb +5 -0
- data/lib/rack/sendfile.rb +10 -5
- data/lib/rack/server.rb +45 -17
- data/lib/rack/session/abstract/id.rb +7 -6
- data/lib/rack/session/cookie.rb +17 -7
- data/lib/rack/session/memcache.rb +4 -4
- data/lib/rack/session/pool.rb +3 -6
- data/lib/rack/showexceptions.rb +20 -11
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/static.rb +27 -30
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +17 -3
- data/lib/rack/utils.rb +78 -47
- data/lib/rack/utils/okjson.rb +90 -91
- data/rack.gemspec +3 -3
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/spec_builder.rb +13 -4
- data/test/spec_chunked.rb +16 -0
- data/test/spec_commonlogger.rb +36 -0
- data/test/spec_content_length.rb +3 -1
- data/test/spec_deflater.rb +283 -148
- data/test/spec_etag.rb +11 -2
- data/test/spec_file.rb +11 -3
- data/test/spec_head.rb +2 -0
- data/test/spec_lobster.rb +1 -1
- data/test/spec_mock.rb +8 -0
- data/test/spec_multipart.rb +111 -49
- data/test/spec_request.rb +109 -25
- data/test/spec_response.rb +30 -0
- data/test/spec_server.rb +20 -5
- data/test/spec_session_cookie.rb +45 -2
- data/test/spec_session_memcache.rb +1 -1
- data/test/spec_showexceptions.rb +29 -36
- data/test/spec_showstatus.rb +19 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/spec_urlmap.rb +23 -0
- data/test/spec_utils.rb +60 -10
- data/test/spec_webrick.rb +41 -0
- metadata +12 -9
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
@@ -47,7 +47,7 @@ module Rack
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def get_session(env, sid)
|
50
|
-
with_lock(env
|
50
|
+
with_lock(env) do
|
51
51
|
unless sid and session = @pool.get(sid)
|
52
52
|
sid, session = generate_sid, {}
|
53
53
|
unless /^STORED/ =~ @pool.add(sid, session)
|
@@ -62,7 +62,7 @@ module Rack
|
|
62
62
|
expiry = options[:expire_after]
|
63
63
|
expiry = expiry.nil? ? 0 : expiry + 1
|
64
64
|
|
65
|
-
with_lock(env
|
65
|
+
with_lock(env) do
|
66
66
|
@pool.set session_id, new_session, expiry
|
67
67
|
session_id
|
68
68
|
end
|
@@ -75,7 +75,7 @@ module Rack
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
def with_lock(env
|
78
|
+
def with_lock(env)
|
79
79
|
@mutex.lock if env['rack.multithread']
|
80
80
|
yield
|
81
81
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
@@ -83,7 +83,7 @@ module Rack
|
|
83
83
|
warn "#{self} is unable to find memcached server."
|
84
84
|
warn $!.inspect
|
85
85
|
end
|
86
|
-
|
86
|
+
raise
|
87
87
|
ensure
|
88
88
|
@mutex.unlock if @mutex.locked?
|
89
89
|
end
|
data/lib/rack/session/pool.rb
CHANGED
@@ -42,7 +42,7 @@ module Rack
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def get_session(env, sid)
|
45
|
-
with_lock(env
|
45
|
+
with_lock(env) do
|
46
46
|
unless sid and session = @pool[sid]
|
47
47
|
sid, session = generate_sid, {}
|
48
48
|
@pool.store sid, session
|
@@ -52,7 +52,7 @@ module Rack
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def set_session(env, session_id, new_session, options)
|
55
|
-
with_lock(env
|
55
|
+
with_lock(env) do
|
56
56
|
@pool.store session_id, new_session
|
57
57
|
session_id
|
58
58
|
end
|
@@ -65,15 +65,12 @@ module Rack
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def with_lock(env
|
68
|
+
def with_lock(env)
|
69
69
|
@mutex.lock if env['rack.multithread']
|
70
70
|
yield
|
71
|
-
rescue
|
72
|
-
default
|
73
71
|
ensure
|
74
72
|
@mutex.unlock if @mutex.locked?
|
75
73
|
end
|
76
|
-
|
77
74
|
end
|
78
75
|
end
|
79
76
|
end
|
data/lib/rack/showexceptions.rb
CHANGED
@@ -28,23 +28,32 @@ module Rack
|
|
28
28
|
env["rack.errors"].puts(exception_string)
|
29
29
|
env["rack.errors"].flush
|
30
30
|
|
31
|
-
if
|
32
|
-
content_type = "text/plain"
|
33
|
-
body = [exception_string]
|
34
|
-
else
|
31
|
+
if accepts_html?(env)
|
35
32
|
content_type = "text/html"
|
36
33
|
body = pretty(env, e)
|
34
|
+
else
|
35
|
+
content_type = "text/plain"
|
36
|
+
body = exception_string
|
37
37
|
end
|
38
38
|
|
39
|
-
[
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
[
|
40
|
+
500,
|
41
|
+
{
|
42
|
+
"Content-Type" => content_type,
|
43
|
+
"Content-Length" => Rack::Utils.bytesize(body).to_s,
|
44
|
+
},
|
45
|
+
[body],
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
def prefers_plaintext?(env)
|
50
|
+
!accepts_html(env)
|
43
51
|
end
|
44
52
|
|
45
|
-
def
|
46
|
-
|
53
|
+
def accepts_html?(env)
|
54
|
+
Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
|
47
55
|
end
|
56
|
+
private :accepts_html?
|
48
57
|
|
49
58
|
def dump_exception(exception)
|
50
59
|
string = "#{exception.class}: #{exception.message}\n"
|
@@ -85,7 +94,7 @@ module Rack
|
|
85
94
|
end
|
86
95
|
}.compact
|
87
96
|
|
88
|
-
|
97
|
+
@template.result(binding)
|
89
98
|
end
|
90
99
|
|
91
100
|
def h(obj) # :nodoc:
|
data/lib/rack/showstatus.rb
CHANGED
data/lib/rack/static.rb
CHANGED
@@ -90,9 +90,8 @@ module Rack
|
|
90
90
|
@header_rules = options[:header_rules] || []
|
91
91
|
# Allow for legacy :cache_control option while prioritizing global header_rules setting
|
92
92
|
@header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
|
93
|
-
@headers = {}
|
94
93
|
|
95
|
-
@file_server = Rack::File.new(root
|
94
|
+
@file_server = Rack::File.new(root)
|
96
95
|
end
|
97
96
|
|
98
97
|
def overwrite_file_path(path)
|
@@ -112,42 +111,40 @@ module Rack
|
|
112
111
|
|
113
112
|
if can_serve(path)
|
114
113
|
env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
path = env["PATH_INFO"]
|
115
|
+
response = @file_server.call(env)
|
116
|
+
|
117
|
+
headers = response[1]
|
118
|
+
applicable_rules(path).each do |rule, new_headers|
|
119
|
+
new_headers.each { |field, content| headers[field] = content }
|
120
|
+
end
|
121
|
+
|
122
|
+
response
|
118
123
|
else
|
119
124
|
@app.call(env)
|
120
125
|
end
|
121
126
|
end
|
122
127
|
|
123
128
|
# Convert HTTP header rules to HTTP headers
|
124
|
-
def
|
125
|
-
@header_rules.
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
set_headers(headers) if @path.match(/\.(#{extensions})\z/)
|
142
|
-
when Regexp # Flexible Regexp
|
143
|
-
set_headers(headers) if @path.match(rule)
|
144
|
-
else
|
129
|
+
def applicable_rules(path)
|
130
|
+
@header_rules.find_all do |rule, new_headers|
|
131
|
+
case rule
|
132
|
+
when :all
|
133
|
+
true
|
134
|
+
when :fonts
|
135
|
+
path =~ /\.(?:ttf|otf|eot|woff|svg)\z/
|
136
|
+
when String
|
137
|
+
path = ::Rack::Utils.unescape(path)
|
138
|
+
path.start_with?(rule) || path.start_with?('/' + rule)
|
139
|
+
when Array
|
140
|
+
path =~ /\.(#{rule.join('|')})\z/
|
141
|
+
when Regexp
|
142
|
+
path =~ rule
|
143
|
+
else
|
144
|
+
false
|
145
|
+
end
|
145
146
|
end
|
146
147
|
end
|
147
148
|
|
148
|
-
def set_headers(headers)
|
149
|
-
headers.each { |field, content| @headers[field] = content }
|
150
|
-
end
|
151
|
-
|
152
149
|
end
|
153
150
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rack/body_proxy'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
# Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
|
6
|
+
# Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter
|
7
|
+
# https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ
|
8
|
+
class TempfileReaper
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
env['rack.tempfiles'] ||= []
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
body_proxy = BodyProxy.new(body) do
|
17
|
+
env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
|
18
|
+
end
|
19
|
+
[status, headers, body_proxy]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/rack/urlmap.rb
CHANGED
@@ -48,9 +48,10 @@ module Rack
|
|
48
48
|
sPort = env['SERVER_PORT']
|
49
49
|
|
50
50
|
@mapping.each do |host, location, match, app|
|
51
|
-
unless hHost
|
52
|
-
|| sName
|
53
|
-
|| (!host && (hHost
|
51
|
+
unless casecmp?(hHost, host) \
|
52
|
+
|| casecmp?(sName, host) \
|
53
|
+
|| (!host && (casecmp?(hHost, sName) ||
|
54
|
+
casecmp?(hHost, sName+':'+sPort)))
|
54
55
|
next
|
55
56
|
end
|
56
57
|
|
@@ -71,6 +72,19 @@ module Rack
|
|
71
72
|
env['PATH_INFO'] = path
|
72
73
|
env['SCRIPT_NAME'] = script_name
|
73
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def casecmp?(v1, v2)
|
78
|
+
# if both nil, or they're the same string
|
79
|
+
return true if v1 == v2
|
80
|
+
|
81
|
+
# if either are nil... (but they're not the same)
|
82
|
+
return false if v1.nil?
|
83
|
+
return false if v2.nil?
|
84
|
+
|
85
|
+
# otherwise check they're not case-insensitive the same
|
86
|
+
v1.casecmp(v2).zero?
|
87
|
+
end
|
74
88
|
end
|
75
89
|
end
|
76
90
|
|
data/lib/rack/utils.rb
CHANGED
@@ -9,7 +9,7 @@ major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
|
9
9
|
|
10
10
|
if major == 1 && minor < 9
|
11
11
|
require 'rack/backports/uri/common_18'
|
12
|
-
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <=
|
12
|
+
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
|
13
13
|
require 'rack/backports/uri/common_192'
|
14
14
|
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
|
15
15
|
require 'rack/backports/uri/common_193'
|
@@ -22,6 +22,15 @@ module Rack
|
|
22
22
|
# applications adopted from all kinds of Ruby libraries.
|
23
23
|
|
24
24
|
module Utils
|
25
|
+
# ParameterTypeError is the error that is raised when incoming structural
|
26
|
+
# parameters (parsed by parse_nested_query) contain conflicting types.
|
27
|
+
class ParameterTypeError < TypeError; end
|
28
|
+
|
29
|
+
# InvalidParameterError is the error that is raised when incoming structural
|
30
|
+
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
31
|
+
# sequence.
|
32
|
+
class InvalidParameterError < ArgumentError; end
|
33
|
+
|
25
34
|
# URI escapes. (CGI style space to +)
|
26
35
|
def escape(s)
|
27
36
|
URI.encode_www_form_component(s)
|
@@ -52,23 +61,12 @@ module Rack
|
|
52
61
|
|
53
62
|
class << self
|
54
63
|
attr_accessor :key_space_limit
|
55
|
-
attr_accessor :param_depth_limit
|
56
|
-
attr_accessor :multipart_part_limit
|
57
64
|
end
|
58
65
|
|
59
66
|
# The default number of bytes to allow parameter keys to take up.
|
60
67
|
# This helps prevent a rogue client from flooding a Request.
|
61
68
|
self.key_space_limit = 65536
|
62
69
|
|
63
|
-
# Default depth at which the parameter parser will raise an exception for
|
64
|
-
# being too deep. This helps prevent SystemStackErrors
|
65
|
-
self.param_depth_limit = 100
|
66
|
-
#
|
67
|
-
# The maximum number of parts a request can contain. Accepting to many part
|
68
|
-
# can lead to the server running out of file handles.
|
69
|
-
# Set to `0` for no limit.
|
70
|
-
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
71
|
-
|
72
70
|
# Stolen from Mongrel, with some small modifications:
|
73
71
|
# Parses a query string by breaking it up at the '&'
|
74
72
|
# and ';' characters. You can also use this to parse
|
@@ -98,6 +96,11 @@ module Rack
|
|
98
96
|
end
|
99
97
|
module_function :parse_query
|
100
98
|
|
99
|
+
# parse_nested_query expands a query string into structural types. Supported
|
100
|
+
# types are Arrays, Hashes and basic value types. It is possible to supply
|
101
|
+
# query strings with parameters of conflicting types, in this case a
|
102
|
+
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
103
|
+
# case.
|
101
104
|
def parse_nested_query(qs, d = nil)
|
102
105
|
params = KeySpaceConstrainedParams.new
|
103
106
|
|
@@ -108,12 +111,15 @@ module Rack
|
|
108
111
|
end
|
109
112
|
|
110
113
|
return params.to_params_hash
|
114
|
+
rescue ArgumentError => e
|
115
|
+
raise InvalidParameterError, e.message
|
111
116
|
end
|
112
117
|
module_function :parse_nested_query
|
113
118
|
|
114
|
-
|
115
|
-
|
116
|
-
|
119
|
+
# normalize_params recursively expands parameters into structural types. If
|
120
|
+
# the structural types represented by two different parameter names are in
|
121
|
+
# conflict, a ParameterTypeError is raised.
|
122
|
+
def normalize_params(params, name, v = nil)
|
117
123
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
118
124
|
k = $1 || ''
|
119
125
|
after = $' || ''
|
@@ -122,23 +128,25 @@ module Rack
|
|
122
128
|
|
123
129
|
if after == ""
|
124
130
|
params[k] = v
|
131
|
+
elsif after == "["
|
132
|
+
params[name] = v
|
125
133
|
elsif after == "[]"
|
126
134
|
params[k] ||= []
|
127
|
-
raise
|
135
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
128
136
|
params[k] << v
|
129
137
|
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
130
138
|
child_key = $1
|
131
139
|
params[k] ||= []
|
132
|
-
raise
|
140
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
133
141
|
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
|
134
|
-
normalize_params(params[k].last, child_key, v
|
142
|
+
normalize_params(params[k].last, child_key, v)
|
135
143
|
else
|
136
|
-
params[k] << normalize_params(params.class.new, child_key, v
|
144
|
+
params[k] << normalize_params(params.class.new, child_key, v)
|
137
145
|
end
|
138
146
|
else
|
139
147
|
params[k] ||= params.class.new
|
140
|
-
raise
|
141
|
-
params[k] = normalize_params(params[k], after, v
|
148
|
+
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
149
|
+
params[k] = normalize_params(params[k], after, v)
|
142
150
|
end
|
143
151
|
|
144
152
|
return params
|
@@ -170,12 +178,12 @@ module Rack
|
|
170
178
|
when Hash
|
171
179
|
value.map { |k, v|
|
172
180
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
173
|
-
}.join(
|
174
|
-
when
|
181
|
+
}.reject(&:empty?).join('&')
|
182
|
+
when nil
|
183
|
+
prefix
|
184
|
+
else
|
175
185
|
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
176
186
|
"#{prefix}=#{escape(value)}"
|
177
|
-
else
|
178
|
-
prefix
|
179
187
|
end
|
180
188
|
end
|
181
189
|
module_function :build_nested_query
|
@@ -195,13 +203,14 @@ module Rack
|
|
195
203
|
def best_q_match(q_value_header, available_mimes)
|
196
204
|
values = q_values(q_value_header)
|
197
205
|
|
198
|
-
values.map do |req_mime, quality|
|
199
|
-
match = available_mimes.
|
206
|
+
matches = values.map do |req_mime, quality|
|
207
|
+
match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
|
200
208
|
next unless match
|
201
209
|
[match, quality]
|
202
210
|
end.compact.sort_by do |match, quality|
|
203
211
|
(match.split('/', 2).count('*') * -10) + quality
|
204
|
-
end.last
|
212
|
+
end.last
|
213
|
+
matches && matches.first
|
205
214
|
end
|
206
215
|
module_function :best_q_match
|
207
216
|
|
@@ -216,7 +225,7 @@ module Rack
|
|
216
225
|
if //.respond_to?(:encoding)
|
217
226
|
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
218
227
|
else
|
219
|
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS
|
228
|
+
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
|
220
229
|
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
221
230
|
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
222
231
|
end
|
@@ -247,10 +256,8 @@ module Rack
|
|
247
256
|
encoding_candidates.push("identity")
|
248
257
|
end
|
249
258
|
|
250
|
-
expanded_accept_encoding.
|
251
|
-
q == 0.0
|
252
|
-
}.each { |m, _|
|
253
|
-
encoding_candidates.delete(m)
|
259
|
+
expanded_accept_encoding.each { |m, q|
|
260
|
+
encoding_candidates.delete(m) if q == 0.0
|
254
261
|
}
|
255
262
|
|
256
263
|
return (encoding_candidates & available_encodings)[0]
|
@@ -260,9 +267,9 @@ module Rack
|
|
260
267
|
def set_cookie_header!(header, key, value)
|
261
268
|
case value
|
262
269
|
when Hash
|
263
|
-
domain = "; domain=" + value[:domain]
|
264
|
-
path = "; path=" + value[:path]
|
265
|
-
max_age = "; max-age=" + value[:max_age] if value[:max_age]
|
270
|
+
domain = "; domain=" + value[:domain] if value[:domain]
|
271
|
+
path = "; path=" + value[:path] if value[:path]
|
272
|
+
max_age = "; max-age=" + value[:max_age].to_s if value[:max_age]
|
266
273
|
# There is an RFC mess in the area of date formatting for Cookies. Not
|
267
274
|
# only are there contradicting RFCs and examples within RFC text, but
|
268
275
|
# there are also numerous conflicting names of fields and partially
|
@@ -289,7 +296,7 @@ module Rack
|
|
289
296
|
expires = "; expires=" +
|
290
297
|
rfc2822(value[:expires].clone.gmtime) if value[:expires]
|
291
298
|
secure = "; secure" if value[:secure]
|
292
|
-
httponly = "; HttpOnly" if value[:httponly]
|
299
|
+
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
293
300
|
value = value[:value]
|
294
301
|
end
|
295
302
|
value = [value] unless Array === value
|
@@ -363,7 +370,7 @@ module Rack
|
|
363
370
|
# of '% %b %Y'.
|
364
371
|
# It assumes that the time is in GMT to comply to the RFC 2109.
|
365
372
|
#
|
366
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is
|
373
|
+
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
367
374
|
# that I'm certain someone implemented only that option.
|
368
375
|
# Do not use %a and %b from Time.strptime, it would use localized names for
|
369
376
|
# weekday and month.
|
@@ -409,6 +416,11 @@ module Rack
|
|
409
416
|
module_function :byte_ranges
|
410
417
|
|
411
418
|
# Constant time string comparison.
|
419
|
+
#
|
420
|
+
# NOTE: the values compared should be of fixed length, such as strings
|
421
|
+
# that have aready been processed by HMAC. This should not be used
|
422
|
+
# on variable length plaintext strings because it could leak length info
|
423
|
+
# via timing attacks.
|
412
424
|
def secure_compare(a, b)
|
413
425
|
return false unless bytesize(a) == bytesize(b)
|
414
426
|
|
@@ -540,7 +552,11 @@ module Rack
|
|
540
552
|
hash.keys.each do |key|
|
541
553
|
value = hash[key]
|
542
554
|
if value.kind_of?(self.class)
|
543
|
-
|
555
|
+
if value.object_id == self.object_id
|
556
|
+
hash[key] = hash
|
557
|
+
else
|
558
|
+
hash[key] = value.to_params_hash
|
559
|
+
end
|
544
560
|
elsif value.kind_of?(Array)
|
545
561
|
value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
|
546
562
|
end
|
@@ -551,9 +567,9 @@ module Rack
|
|
551
567
|
|
552
568
|
# Every standard HTTP code mapped to the appropriate message.
|
553
569
|
# Generated with:
|
554
|
-
#
|
555
|
-
#
|
556
|
-
#
|
570
|
+
# ruby -ropen-uri -rnokogiri -e "Nokogiri::XML(open(
|
571
|
+
# 'http://www.iana.org/assignments/http-status-codes/http-status-codes.xml')).css('record').each{|r|
|
572
|
+
# name = r.css('description').text; puts %Q[#{r.css('value').text} => '#{name}',] unless name == 'Unassigned' }"
|
557
573
|
HTTP_STATUS_CODES = {
|
558
574
|
100 => 'Continue',
|
559
575
|
101 => 'Switching Protocols',
|
@@ -595,15 +611,13 @@ module Rack
|
|
595
611
|
415 => 'Unsupported Media Type',
|
596
612
|
416 => 'Requested Range Not Satisfiable',
|
597
613
|
417 => 'Expectation Failed',
|
614
|
+
418 => 'I\'m a teapot',
|
598
615
|
422 => 'Unprocessable Entity',
|
599
616
|
423 => 'Locked',
|
600
617
|
424 => 'Failed Dependency',
|
601
|
-
425 => 'Reserved for WebDAV advanced collections expired proposal',
|
602
618
|
426 => 'Upgrade Required',
|
603
|
-
427 => 'Unassigned',
|
604
619
|
428 => 'Precondition Required',
|
605
620
|
429 => 'Too Many Requests',
|
606
|
-
430 => 'Unassigned',
|
607
621
|
431 => 'Request Header Fields Too Large',
|
608
622
|
500 => 'Internal Server Error',
|
609
623
|
501 => 'Not Implemented',
|
@@ -614,7 +628,6 @@ module Rack
|
|
614
628
|
506 => 'Variant Also Negotiates (Experimental)',
|
615
629
|
507 => 'Insufficient Storage',
|
616
630
|
508 => 'Loop Detected',
|
617
|
-
509 => 'Unassigned',
|
618
631
|
510 => 'Not Extended',
|
619
632
|
511 => 'Network Authentication Required'
|
620
633
|
}
|
@@ -623,7 +636,7 @@ module Rack
|
|
623
636
|
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
|
624
637
|
|
625
638
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
626
|
-
[message.downcase.gsub(/\s
|
639
|
+
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
627
640
|
}.flatten]
|
628
641
|
|
629
642
|
def status_code(status)
|
@@ -637,5 +650,23 @@ module Rack
|
|
637
650
|
|
638
651
|
Multipart = Rack::Multipart
|
639
652
|
|
653
|
+
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
654
|
+
|
655
|
+
def clean_path_info(path_info)
|
656
|
+
parts = path_info.split PATH_SEPS
|
657
|
+
|
658
|
+
clean = []
|
659
|
+
|
660
|
+
parts.each do |part|
|
661
|
+
next if part.empty? || part == '.'
|
662
|
+
part == '..' ? clean.pop : clean << part
|
663
|
+
end
|
664
|
+
|
665
|
+
clean.unshift '/' if parts.empty? || parts.first.empty?
|
666
|
+
|
667
|
+
::File.join(*clean)
|
668
|
+
end
|
669
|
+
module_function :clean_path_info
|
670
|
+
|
640
671
|
end
|
641
672
|
end
|