rack-contrib 1.1.0 → 1.2.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.
Potentially problematic release.
This version of rack-contrib might be problematic. Click here for more details.
- data/AUTHORS +1 -0
- data/COPYING +1 -0
- data/README.rdoc +18 -1
- data/lib/rack/contrib.rb +4 -1
- data/lib/rack/contrib/accept_format.rb +1 -1
- data/lib/rack/contrib/common_cookies.rb +2 -4
- data/lib/rack/contrib/jsonp.rb +51 -5
- data/lib/rack/contrib/locale.rb +27 -16
- data/lib/rack/contrib/mailexceptions.rb +15 -33
- data/lib/rack/contrib/post_body_content_type_parser.rb +3 -3
- data/lib/rack/contrib/printout.rb +25 -0
- data/lib/rack/contrib/profiler.rb +21 -31
- data/lib/rack/contrib/static_cache.rb +2 -2
- data/lib/rack/contrib/try_static.rb +2 -2
- data/rack-contrib.gemspec +3 -2
- data/test/spec_rack_jsonp.rb +108 -1
- data/test/spec_rack_mailexceptions.rb +10 -12
- data/test/spec_rack_post_body_content_type_parser.rb +21 -13
- data/test/spec_rack_profiler.rb +8 -3
- data/test/spec_rack_static_cache.rb +13 -0
- data/test/spec_rack_try_static.rb +19 -7
- metadata +76 -90
data/AUTHORS
CHANGED
data/COPYING
CHANGED
data/README.rdoc
CHANGED
@@ -48,6 +48,7 @@ interface:
|
|
48
48
|
* Rack::ResponseHeaders - Manipulates response headers object at runtime
|
49
49
|
* Rack::SimpleEndpoint - Creates simple endpoints with routing rules, similar to Sinatra actions
|
50
50
|
* Rack::TryStatic - Tries to match request to a static file
|
51
|
+
* Rack::Printout - Prints the environment and the response per request
|
51
52
|
|
52
53
|
=== Use
|
53
54
|
|
@@ -73,9 +74,25 @@ components included. The following example shows what a simple rackup
|
|
73
74
|
|
74
75
|
run theapp
|
75
76
|
|
77
|
+
=== Testing
|
78
|
+
|
79
|
+
To contribute to the project, begin by cloning the repo and installing the necessary gems:
|
80
|
+
|
81
|
+
gem install json rack ruby-prof test-spec test-unit
|
82
|
+
|
83
|
+
To run the entire test suite, run
|
84
|
+
rake test
|
85
|
+
|
86
|
+
To run a specific component's tests run
|
87
|
+
specrb -Ilib:test -w test/spec_rack_thecomponent.rb
|
88
|
+
|
89
|
+
This works on ruby 1.8.7 but has problems under ruby 1.9.x.
|
90
|
+
|
91
|
+
TODO: instructions for 1.9.x and include bundler
|
92
|
+
|
76
93
|
=== Links
|
77
94
|
|
78
95
|
rack-contrib on GitHub:: <http://github.com/rack/rack-contrib>
|
79
96
|
Rack:: <http://rack.rubyforge.org/>
|
80
|
-
Rack On GitHub:: <http://github.
|
97
|
+
Rack On GitHub:: <http://github.com/rack/rack>
|
81
98
|
rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
|
data/lib/rack/contrib.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rack'
|
|
3
3
|
module Rack
|
4
4
|
module Contrib
|
5
5
|
def self.release
|
6
|
-
"1.0
|
6
|
+
"1.2.0"
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
@@ -14,6 +14,7 @@ module Rack
|
|
14
14
|
autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
|
15
15
|
autoload :Deflect, "rack/contrib/deflect"
|
16
16
|
autoload :ExpectationCascade, "rack/contrib/expectation_cascade"
|
17
|
+
autoload :HostMeta, "rack/contrib/host_meta"
|
17
18
|
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
18
19
|
autoload :JSONP, "rack/contrib/jsonp"
|
19
20
|
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
@@ -36,4 +37,6 @@ module Rack
|
|
36
37
|
autoload :ResponseCache, "rack/contrib/response_cache"
|
37
38
|
autoload :RelativeRedirect, "rack/contrib/relative_redirect"
|
38
39
|
autoload :StaticCache, "rack/contrib/static_cache"
|
40
|
+
autoload :TryStatic, "rack/contrib/try_static"
|
41
|
+
autoload :Printout, "rack/contrib/printout"
|
39
42
|
end
|
@@ -37,7 +37,7 @@ module Rack
|
|
37
37
|
if ::File.extname(req.path_info).empty?
|
38
38
|
accept = env['HTTP_ACCEPT'].to_s.scan(/[^;,\s]*\/[^;,\s]*/)[0].to_s
|
39
39
|
extension = Rack::Mime::MIME_TYPES.invert[accept] || @ext
|
40
|
-
req.path_info = req.path_info
|
40
|
+
req.path_info = req.path_info.chomp('/') << "#{extension}"
|
41
41
|
end
|
42
42
|
|
43
43
|
@app.call(env)
|
data/lib/rack/contrib/jsonp.rb
CHANGED
@@ -7,6 +7,21 @@ module Rack
|
|
7
7
|
class JSONP
|
8
8
|
include Rack::Utils
|
9
9
|
|
10
|
+
VALID_JS_VAR = /[a-zA-Z_$][\w$]*/
|
11
|
+
VALID_CALLBACK = /\A#{VALID_JS_VAR}(?:\.?#{VALID_JS_VAR})*\z/
|
12
|
+
|
13
|
+
# These hold the Unicode characters \u2028 and \u2029.
|
14
|
+
#
|
15
|
+
# They are defined in constants for Ruby 1.8 compatibility.
|
16
|
+
#
|
17
|
+
# In 1.8
|
18
|
+
# "\u2028" # => "u2028"
|
19
|
+
# "\u2029" # => "u2029"
|
20
|
+
# In 1.9
|
21
|
+
# "\342\200\250" # => "\u2028"
|
22
|
+
# "\342\200\251" # => "\u2029"
|
23
|
+
U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"]
|
24
|
+
|
10
25
|
def initialize(app)
|
11
26
|
@app = app
|
12
27
|
end
|
@@ -18,13 +33,17 @@ module Rack
|
|
18
33
|
# Changes nothing if no <tt>callback</tt> param is specified.
|
19
34
|
#
|
20
35
|
def call(env)
|
36
|
+
request = Rack::Request.new(env)
|
37
|
+
|
21
38
|
status, headers, response = @app.call(env)
|
22
39
|
|
23
40
|
headers = HeaderHash.new(headers)
|
24
|
-
request = Rack::Request.new(env)
|
25
41
|
|
26
42
|
if is_json?(headers) && has_callback?(request)
|
27
|
-
|
43
|
+
callback = request.params['callback']
|
44
|
+
return bad_request unless valid_callback?(callback)
|
45
|
+
|
46
|
+
response = pad(callback, response)
|
28
47
|
|
29
48
|
# No longer json, its javascript!
|
30
49
|
headers['Content-Type'] = headers['Content-Type'].gsub('json', 'javascript')
|
@@ -35,6 +54,7 @@ module Rack
|
|
35
54
|
headers['Content-Length'] = length.to_s
|
36
55
|
end
|
37
56
|
end
|
57
|
+
|
38
58
|
[status, headers, response]
|
39
59
|
end
|
40
60
|
|
@@ -45,7 +65,16 @@ module Rack
|
|
45
65
|
end
|
46
66
|
|
47
67
|
def has_callback?(request)
|
48
|
-
request.params.include?('callback')
|
68
|
+
request.params.include?('callback') and not request.params['callback'].empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
# See:
|
72
|
+
# http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names
|
73
|
+
#
|
74
|
+
# NOTE: Supports dots (.) since callbacks are often in objects:
|
75
|
+
#
|
76
|
+
def valid_callback?(callback)
|
77
|
+
callback =~ VALID_CALLBACK
|
49
78
|
end
|
50
79
|
|
51
80
|
# Pads the response with the appropriate callback format according to the
|
@@ -56,8 +85,25 @@ module Rack
|
|
56
85
|
# since JSON is returned as a full string.
|
57
86
|
#
|
58
87
|
def pad(callback, response, body = "")
|
59
|
-
response.each
|
60
|
-
|
88
|
+
response.each do |s|
|
89
|
+
# U+2028 and U+2029 are allowed inside strings in JSON (as all literal
|
90
|
+
# Unicode characters) but JavaScript defines them as newline
|
91
|
+
# seperators. Because no literal newlines are allowed in a string, this
|
92
|
+
# causes a ParseError in the browser. We work around this issue by
|
93
|
+
# replacing them with the escaped version. This should be safe because
|
94
|
+
# according to the JSON spec, these characters are *only* valid inside
|
95
|
+
# a string and should therefore not be present any other places.
|
96
|
+
body << s.to_s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
|
97
|
+
end
|
98
|
+
|
99
|
+
# https://github.com/rack/rack-contrib/issues/46
|
100
|
+
response.close if response.respond_to?(:close)
|
101
|
+
|
102
|
+
["/**/#{callback}(#{body})"]
|
103
|
+
end
|
104
|
+
|
105
|
+
def bad_request(body = "Bad Request")
|
106
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.size.to_s }, [body] ]
|
61
107
|
end
|
62
108
|
|
63
109
|
end
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -8,24 +8,35 @@ module Rack
|
|
8
8
|
|
9
9
|
def call(env)
|
10
10
|
old_locale = I18n.locale
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
locale =
|
20
|
-
else
|
21
|
-
locale = I18n.default_locale
|
11
|
+
|
12
|
+
begin
|
13
|
+
locale = accept_locale(env) || I18n.default_locale
|
14
|
+
locale = env['rack.locale'] = I18n.locale = locale.to_s
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
headers['Content-Language'] = locale
|
17
|
+
[status, headers, body]
|
18
|
+
ensure
|
19
|
+
I18n.locale = old_locale
|
22
20
|
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
26
|
+
def accept_locale(env)
|
27
|
+
accept_langs = env["HTTP_ACCEPT_LANGUAGE"]
|
28
|
+
return if accept_langs.nil?
|
29
|
+
|
30
|
+
languages_and_qvalues = accept_langs.split(",").map { |l|
|
31
|
+
l += ';q=1.0' unless l =~ /;q=\d+(?:\.\d+)?$/
|
32
|
+
l.split(';q=')
|
33
|
+
}
|
34
|
+
|
35
|
+
lang = languages_and_qvalues.sort_by { |(locale, qvalue)|
|
36
|
+
qvalue.to_f
|
37
|
+
}.last.first
|
23
38
|
|
24
|
-
|
25
|
-
status, headers, body = @app.call(env)
|
26
|
-
headers['Content-Language'] = locale
|
27
|
-
I18n.locale = old_locale
|
28
|
-
[status, headers, body]
|
39
|
+
lang == '*' ? nil : lang
|
29
40
|
end
|
30
41
|
end
|
31
42
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'net/smtp'
|
2
|
-
require '
|
2
|
+
require 'mail'
|
3
3
|
require 'erb'
|
4
4
|
|
5
5
|
module Rack
|
@@ -14,10 +14,10 @@ module Rack
|
|
14
14
|
@app = app
|
15
15
|
@config = {
|
16
16
|
:to => nil,
|
17
|
-
:from => ENV['USER'] || 'rack',
|
17
|
+
:from => ENV['USER'] || 'rack@localhost',
|
18
18
|
:subject => '[exception] %s',
|
19
19
|
:smtp => {
|
20
|
-
:
|
20
|
+
:address => 'localhost',
|
21
21
|
:domain => 'localhost',
|
22
22
|
:port => 25,
|
23
23
|
:authentication => :login,
|
@@ -34,8 +34,6 @@ module Rack
|
|
34
34
|
begin
|
35
35
|
@app.call(env)
|
36
36
|
rescue => boom
|
37
|
-
# TODO don't allow exceptions from send_notification to
|
38
|
-
# propogate
|
39
37
|
send_notification boom, env
|
40
38
|
raise
|
41
39
|
end
|
@@ -53,40 +51,24 @@ module Rack
|
|
53
51
|
|
54
52
|
private
|
55
53
|
def generate_mail(exception, env)
|
56
|
-
mail =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
mail.charset = 'UTF-8'
|
63
|
-
mail.body = @template.result(binding)
|
64
|
-
mail
|
54
|
+
mail = Mail.new({
|
55
|
+
:from => config[:from],
|
56
|
+
:to => config[:to],
|
57
|
+
:subject => config[:subject] % [exception.to_s],
|
58
|
+
:body => @template.result(binding)
|
59
|
+
})
|
65
60
|
end
|
66
61
|
|
67
62
|
def send_notification(exception, env)
|
68
63
|
mail = generate_mail(exception, env)
|
69
64
|
smtp = config[:smtp]
|
65
|
+
# for backward compability, replace the :server key with :address
|
66
|
+
address = smtp.delete :server
|
67
|
+
smtp[:address] = address if address
|
68
|
+
mail.delivery_method :smtp, smtp
|
69
|
+
mail.deliver!
|
70
70
|
env['mail.sent'] = true
|
71
|
-
|
72
|
-
|
73
|
-
server = service.new(smtp[:server], smtp[:port])
|
74
|
-
|
75
|
-
if smtp[:enable_starttls_auto] == :auto
|
76
|
-
server.enable_starttls_auto
|
77
|
-
elsif smtp[:enable_starttls_auto]
|
78
|
-
server.enable_starttls
|
79
|
-
end
|
80
|
-
|
81
|
-
server.start smtp[:domain], smtp[:user_name], smtp[:password], smtp[:authentication]
|
82
|
-
|
83
|
-
mail.to.each do |recipient|
|
84
|
-
server.send_message mail.to_s, mail.from, recipient
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def service
|
89
|
-
Net::SMTP
|
71
|
+
mail
|
90
72
|
end
|
91
73
|
|
92
74
|
def extract_body(env)
|
@@ -29,9 +29,9 @@ module Rack
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def call(env)
|
32
|
-
|
33
|
-
|
34
|
-
env.update(FORM_HASH => JSON.parse(
|
32
|
+
if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0
|
33
|
+
env[POST_BODY].rewind # somebody might try to read this stream
|
34
|
+
env.update(FORM_HASH => JSON.parse(body), FORM_INPUT => env[POST_BODY])
|
35
35
|
end
|
36
36
|
@app.call(env)
|
37
37
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack
|
2
|
+
#prints the environment and request for simple debugging
|
3
|
+
class Printout
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
# See http://rack.rubyforge.org/doc/SPEC.html for details
|
10
|
+
puts "**********\n Environment\n **************"
|
11
|
+
puts env.inspect
|
12
|
+
|
13
|
+
puts "**********\n Response\n **************"
|
14
|
+
response = @app.call(env)
|
15
|
+
puts response.inspect
|
16
|
+
|
17
|
+
puts "**********\n Response contents\n **************"
|
18
|
+
response[2].each do |chunk|
|
19
|
+
puts chunk
|
20
|
+
end
|
21
|
+
puts "\n \n"
|
22
|
+
return response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -6,30 +6,22 @@ module Rack
|
|
6
6
|
#
|
7
7
|
# Pass the :printer option to pick a different result format.
|
8
8
|
class Profiler
|
9
|
-
MODES = %w(
|
10
|
-
|
11
|
-
wall_time
|
12
|
-
cpu_time
|
13
|
-
allocations
|
14
|
-
memory
|
15
|
-
gc_runs
|
16
|
-
gc_time
|
17
|
-
)
|
9
|
+
MODES = %w(process_time wall_time cpu_time
|
10
|
+
allocations memory gc_runs gc_time)
|
18
11
|
|
19
|
-
DEFAULT_PRINTER =
|
20
|
-
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
12
|
+
DEFAULT_PRINTER = :call_stack
|
21
13
|
|
22
|
-
|
23
|
-
RubyProf::FlatPrinter
|
24
|
-
RubyProf::GraphPrinter
|
25
|
-
RubyProf::GraphHtmlPrinter => 'text/html'
|
26
|
-
|
14
|
+
CONTENT_TYPES = Hash.new('application/octet-stream').merge(
|
15
|
+
'RubyProf::FlatPrinter' => 'text/plain',
|
16
|
+
'RubyProf::GraphPrinter' => 'text/plain',
|
17
|
+
'RubyProf::GraphHtmlPrinter' => 'text/html',
|
18
|
+
'RubyProf::CallStackPrinter' => 'text/html')
|
27
19
|
|
28
|
-
# Accepts a :printer => [:call_tree|:graph_html|:graph|:flat]
|
29
|
-
# defaulting to :
|
20
|
+
# Accepts a :printer => [:call_stack|:call_tree|:graph_html|:graph|:flat]
|
21
|
+
# option defaulting to :call_stack.
|
30
22
|
def initialize(app, options = {})
|
31
23
|
@app = app
|
32
|
-
@printer = parse_printer(options[:printer])
|
24
|
+
@printer = parse_printer(options[:printer] || DEFAULT_PRINTER)
|
33
25
|
@times = (options[:times] || 1).to_i
|
34
26
|
end
|
35
27
|
|
@@ -43,10 +35,10 @@ module Rack
|
|
43
35
|
|
44
36
|
private
|
45
37
|
def profiling?(env)
|
46
|
-
unless RubyProf.running?
|
38
|
+
unless ::RubyProf.running?
|
47
39
|
request = Rack::Request.new(env.clone)
|
48
40
|
if mode = request.params.delete('profile')
|
49
|
-
if RubyProf.const_defined?(mode.upcase)
|
41
|
+
if ::RubyProf.const_defined?(mode.upcase)
|
50
42
|
mode
|
51
43
|
else
|
52
44
|
env['rack.errors'].write "Invalid RubyProf measure_mode: " +
|
@@ -58,10 +50,10 @@ module Rack
|
|
58
50
|
end
|
59
51
|
|
60
52
|
def profile(env, mode)
|
61
|
-
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
53
|
+
::RubyProf.measure_mode = ::RubyProf.const_get(mode.upcase)
|
62
54
|
|
63
55
|
GC.enable_stats if GC.respond_to?(:enable_stats)
|
64
|
-
result = RubyProf.profile do
|
56
|
+
result = ::RubyProf.profile do
|
65
57
|
@times.times { @app.call(env) }
|
66
58
|
end
|
67
59
|
GC.disable_stats if GC.respond_to?(:disable_stats)
|
@@ -77,8 +69,8 @@ module Rack
|
|
77
69
|
end
|
78
70
|
|
79
71
|
def headers(printer, env, mode)
|
80
|
-
headers = { 'Content-Type' =>
|
81
|
-
if printer == RubyProf::CallTreePrinter
|
72
|
+
headers = { 'Content-Type' => CONTENT_TYPES[printer.name] }
|
73
|
+
if printer == ::RubyProf::CallTreePrinter
|
82
74
|
filename = ::File.basename(env['PATH_INFO'])
|
83
75
|
headers['Content-Disposition'] =
|
84
76
|
%(attachment; filename="#{filename}.#{mode}.tree")
|
@@ -87,16 +79,14 @@ module Rack
|
|
87
79
|
end
|
88
80
|
|
89
81
|
def parse_printer(printer)
|
90
|
-
if printer.
|
91
|
-
DEFAULT_PRINTER
|
92
|
-
elsif printer.is_a?(Class)
|
82
|
+
if printer.is_a?(Class)
|
93
83
|
printer
|
94
84
|
else
|
95
85
|
name = "#{camel_case(printer)}Printer"
|
96
|
-
if RubyProf.const_defined?(name)
|
97
|
-
RubyProf.const_get(name)
|
86
|
+
if ::RubyProf.const_defined?(name)
|
87
|
+
::RubyProf.const_get(name)
|
98
88
|
else
|
99
|
-
|
89
|
+
::RubyProf::FlatPrinter
|
100
90
|
end
|
101
91
|
end
|
102
92
|
end
|
@@ -26,7 +26,7 @@ module Rack
|
|
26
26
|
#
|
27
27
|
# Examples:
|
28
28
|
# use Rack::StaticCache, :urls => ["/images", "/css", "/js", "/documents*"], :root => "statics"
|
29
|
-
# will serve all requests beginning with /images, /
|
29
|
+
# will serve all requests beginning with /images, /css or /js from the
|
30
30
|
# directory "statics/images", "statics/css", "statics/js".
|
31
31
|
# All the files from these directories will have modified headers to enable client/proxy caching,
|
32
32
|
# except the files from the directory "documents". Append a * (star) at the end of the pattern
|
@@ -87,7 +87,7 @@ module Rack
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def duration_in_seconds
|
90
|
-
60 * 60 * 24 * 365 * @cache_duration
|
90
|
+
(60 * 60 * 24 * 365 * @cache_duration).to_i
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
data/rack-contrib.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rack-contrib'
|
6
|
-
s.version = '1.
|
7
|
-
s.
|
6
|
+
s.version = '1.2.0'
|
7
|
+
s.licenses = ['MIT']
|
8
8
|
|
9
9
|
s.description = "Contributed Rack Middleware and Utilities"
|
10
10
|
s.summary = "Contributed Rack Middleware and Utilities"
|
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
|
|
41
41
|
lib/rack/contrib/not_found.rb
|
42
42
|
lib/rack/contrib/post_body_content_type_parser.rb
|
43
43
|
lib/rack/contrib/proctitle.rb
|
44
|
+
lib/rack/contrib/printout.rb
|
44
45
|
lib/rack/contrib/profiler.rb
|
45
46
|
lib/rack/contrib/relative_redirect.rb
|
46
47
|
lib/rack/contrib/response_cache.rb
|
data/test/spec_rack_jsonp.rb
CHANGED
@@ -51,6 +51,113 @@ context "Rack::JSONP" do
|
|
51
51
|
headers = Rack::JSONP.new(app).call(request)[1]
|
52
52
|
headers['Content-Type'].should.equal('application/javascript')
|
53
53
|
end
|
54
|
+
|
55
|
+
specify "should not allow literal U+2028 or U+2029" do
|
56
|
+
test_body = unless "\u2028" == 'u2028'
|
57
|
+
"{\"bar\":\"\u2028 and \u2029\"}"
|
58
|
+
else
|
59
|
+
"{\"bar\":\"\342\200\250 and \342\200\251\"}"
|
60
|
+
end
|
61
|
+
callback = 'foo'
|
62
|
+
app = lambda { |env| [200, {'Content-Type' => 'application/json'}, [test_body]] }
|
63
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
64
|
+
body = Rack::JSONP.new(app).call(request).last
|
65
|
+
unless "\u2028" == 'u2028'
|
66
|
+
body.join.should.not.match(/\u2028|\u2029/)
|
67
|
+
else
|
68
|
+
body.join.should.not.match(/\342\200\250|\342\200\251/)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "but is empty" do
|
73
|
+
specify "should " do
|
74
|
+
test_body = '{"bar":"foo"}'
|
75
|
+
callback = ''
|
76
|
+
app = lambda { |env| [200, {'Content-Type' => 'application/json'}, [test_body]] }
|
77
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
78
|
+
body = Rack::JSONP.new(app).call(request).last
|
79
|
+
body.should.equal ['{"bar":"foo"}']
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'but is invalid' do
|
84
|
+
context 'with content-type application/json' do
|
85
|
+
specify 'should return "Bad Request"' do
|
86
|
+
test_body = '{"bar":"foo"}'
|
87
|
+
callback = '*'
|
88
|
+
content_type = 'application/json'
|
89
|
+
app = lambda { |env| [200, {'Content-Type' => content_type}, [test_body]] }
|
90
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
91
|
+
body = Rack::JSONP.new(app).call(request).last
|
92
|
+
body.should.equal ['Bad Request']
|
93
|
+
end
|
94
|
+
|
95
|
+
specify 'should return set the response code to 400' do
|
96
|
+
test_body = '{"bar":"foo"}'
|
97
|
+
callback = '*'
|
98
|
+
content_type = 'application/json'
|
99
|
+
app = lambda { |env| [200, {'Content-Type' => content_type}, [test_body]] }
|
100
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
101
|
+
response_code = Rack::JSONP.new(app).call(request).first
|
102
|
+
response_code.should.equal 400
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with content-type text/plain' do
|
107
|
+
specify 'should return "Good Request"' do
|
108
|
+
test_body = 'Good Request'
|
109
|
+
callback = '*'
|
110
|
+
content_type = 'text/plain'
|
111
|
+
app = lambda { |env| [200, {'Content-Type' => content_type}, [test_body]] }
|
112
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
113
|
+
body = Rack::JSONP.new(app).call(request).last
|
114
|
+
body.should.equal ['Good Request']
|
115
|
+
end
|
116
|
+
|
117
|
+
specify 'should not change the response code from 200' do
|
118
|
+
test_body = '{"bar":"foo"}'
|
119
|
+
callback = '*'
|
120
|
+
content_type = 'text/plain'
|
121
|
+
app = lambda { |env| [200, {'Content-Type' => content_type}, [test_body]] }
|
122
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
123
|
+
response_code = Rack::JSONP.new(app).call(request).first
|
124
|
+
response_code.should.equal 200
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "with XSS vulnerability attempts" do
|
130
|
+
def request(callback, body = '{"bar":"foo"}')
|
131
|
+
app = lambda { |env| [200, {'Content-Type' => 'application/json'}, [body]] }
|
132
|
+
request = Rack::MockRequest.env_for("/", :params => "foo=bar&callback=#{callback}")
|
133
|
+
Rack::JSONP.new(app).call(request)
|
134
|
+
end
|
135
|
+
|
136
|
+
def assert_bad_request(response)
|
137
|
+
response.should.not.equal nil
|
138
|
+
status, headers, body = response
|
139
|
+
status.should.equal 400
|
140
|
+
body.should.equal ["Bad Request"]
|
141
|
+
end
|
142
|
+
|
143
|
+
specify "should return bad request for callback with invalid characters" do
|
144
|
+
assert_bad_request(request("foo<bar>baz()$"))
|
145
|
+
end
|
146
|
+
|
147
|
+
specify "should return bad request for callbacks with <script> tags" do
|
148
|
+
assert_bad_request(request("foo<script>alert(1)</script>"))
|
149
|
+
end
|
150
|
+
|
151
|
+
specify "should return bad requests for callbacks with multiple statements" do
|
152
|
+
assert_bad_request(request("foo%3balert(1)//")) # would render: "foo;alert(1)//"
|
153
|
+
end
|
154
|
+
|
155
|
+
specify "should not return a bad request for callbacks with dots in the callback" do
|
156
|
+
status, headers, body = request(callback = "foo.bar.baz", test_body = '{"foo":"bar"}')
|
157
|
+
status.should.equal 200
|
158
|
+
body.should.equal ["#{callback}(#{test_body})"]
|
159
|
+
end
|
160
|
+
end
|
54
161
|
|
55
162
|
end
|
56
163
|
|
@@ -78,4 +185,4 @@ context "Rack::JSONP" do
|
|
78
185
|
body.should.equal [test_body]
|
79
186
|
end
|
80
187
|
|
81
|
-
end
|
188
|
+
end
|
@@ -2,10 +2,8 @@ require 'test/spec'
|
|
2
2
|
require 'rack/mock'
|
3
3
|
|
4
4
|
begin
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
|
8
|
-
require File.dirname(__FILE__) + '/mail_settings.rb'
|
5
|
+
require './lib/rack/contrib/mailexceptions'
|
6
|
+
require './test/mail_settings.rb'
|
9
7
|
|
10
8
|
class TestError < RuntimeError
|
11
9
|
end
|
@@ -57,13 +55,13 @@ begin
|
|
57
55
|
mail.smtp @smtp_settings
|
58
56
|
end
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
58
|
+
mail = mailer.send(:generate_mail, test_exception, @env)
|
59
|
+
mail.to.should.equal ['foo@example.org']
|
60
|
+
mail.from.should.equal ['bar@example.org']
|
61
|
+
mail.subject.should.equal '[ERROR] Suffering Succotash!'
|
62
|
+
mail.body.should.not.be.nil
|
63
|
+
mail.body.should.be =~ /FOO:\s+"BAR"/
|
64
|
+
mail.body.should.be =~ /^\s*THE BODY\s*$/
|
67
65
|
end
|
68
66
|
|
69
67
|
specify 'catches exceptions raised from app, sends mail, and re-raises' do
|
@@ -167,5 +165,5 @@ begin
|
|
167
165
|
end
|
168
166
|
end
|
169
167
|
rescue LoadError => boom
|
170
|
-
STDERR.puts "WARN: Skipping Rack::MailExceptions tests (
|
168
|
+
STDERR.puts "WARN: Skipping Rack::MailExceptions tests (mail not installed)"
|
171
169
|
end
|
@@ -6,26 +6,34 @@ begin
|
|
6
6
|
|
7
7
|
context "Rack::PostBodyContentTypeParser" do
|
8
8
|
|
9
|
-
specify "should
|
10
|
-
|
11
|
-
|
12
|
-
body = Rack::PostBodyContentTypeParser.new(app).call(env).last
|
13
|
-
body['body'].should.equal "asdf"
|
14
|
-
body['status'].should.equal "12"
|
9
|
+
specify "should parse 'application/json' requests" do
|
10
|
+
params = params_for_request '{"key":"value"}', "application/json"
|
11
|
+
params['key'].should.equal "value"
|
15
12
|
end
|
16
13
|
|
17
|
-
specify "should
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
specify "should parse 'application/json; charset=utf-8' requests" do
|
15
|
+
params = params_for_request '{"key":"value"}', "application/json; charset=utf-8"
|
16
|
+
params['key'].should.equal "value"
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "should parse 'application/json' requests with empty body" do
|
20
|
+
params = params_for_request "", "application/json"
|
21
|
+
params.should.equal({})
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "shouldn't affect form-urlencoded requests" do
|
25
|
+
params = params_for_request("key=value", "application/x-www-form-urlencoded")
|
26
|
+
params['key'].should.equal "value"
|
22
27
|
end
|
23
28
|
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
Rack::MockRequest.env_for
|
31
|
+
def params_for_request(body, content_type)
|
32
|
+
env = Rack::MockRequest.env_for "/", {:method => "POST", :input => body, "CONTENT_TYPE" => content_type}
|
33
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
|
34
|
+
Rack::PostBodyContentTypeParser.new(app).call(env).last
|
28
35
|
end
|
36
|
+
|
29
37
|
rescue LoadError => e
|
30
38
|
# Missing dependency JSON, skipping tests.
|
31
39
|
STDERR.puts "WARN: Skipping Rack::PostBodyContentTypeParser tests (json not installed)"
|
data/test/spec_rack_profiler.rb
CHANGED
@@ -8,14 +8,19 @@ begin
|
|
8
8
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'Oh hai der'] }
|
9
9
|
request = Rack::MockRequest.env_for("/", :params => "profile=process_time")
|
10
10
|
|
11
|
-
specify 'printer defaults to RubyProf::
|
11
|
+
specify 'printer defaults to RubyProf::CallStackPrinter' do
|
12
12
|
profiler = Rack::Profiler.new(nil)
|
13
|
-
profiler.instance_variable_get('@printer').should.equal RubyProf::
|
13
|
+
profiler.instance_variable_get('@printer').should.equal RubyProf::CallStackPrinter
|
14
14
|
profiler.instance_variable_get('@times').should.equal 1
|
15
15
|
end
|
16
16
|
|
17
|
+
specify 'CallStackPrinter has Content-Type test/html' do
|
18
|
+
headers = Rack::Profiler.new(app, :printer => :call_stack).call(request)[1]
|
19
|
+
headers.should.equal "Content-Type"=>"text/html"
|
20
|
+
end
|
21
|
+
|
17
22
|
specify 'CallTreePrinter has correct headers' do
|
18
|
-
headers = Rack::Profiler.new(app).call(request)[1]
|
23
|
+
headers = Rack::Profiler.new(app, :printer => :call_tree).call(request)[1]
|
19
24
|
headers.should.equal "Content-Disposition"=>"attachment; filename=\"/.process_time.tree\"", "Content-Type"=>"application/octet-stream"
|
20
25
|
end
|
21
26
|
|
@@ -59,6 +59,14 @@ describe "Rack::StaticCache" do
|
|
59
59
|
res.headers['Expires'].should =~ Regexp.new("#{next_next_year}")
|
60
60
|
end
|
61
61
|
|
62
|
+
it "should round max-age if duration is part of a year" do
|
63
|
+
one_week_duration_app_request
|
64
|
+
res = @request.get("/statics/test")
|
65
|
+
res.should.be.ok
|
66
|
+
res.body.should =~ /rubyrack/
|
67
|
+
res.headers['Cache-Control'].should == "max-age=606461, public"
|
68
|
+
end
|
69
|
+
|
62
70
|
it "should return 404s if requested with version number but versioning is disabled" do
|
63
71
|
configured_app_request
|
64
72
|
res = @request.get("/statics/test-0.0.1")
|
@@ -79,6 +87,11 @@ describe "Rack::StaticCache" do
|
|
79
87
|
request
|
80
88
|
end
|
81
89
|
|
90
|
+
def one_week_duration_app_request
|
91
|
+
@options = {:urls => ["/statics"], :root => @root, :duration => 1.fdiv(52)}
|
92
|
+
request
|
93
|
+
end
|
94
|
+
|
82
95
|
def configured_app_request
|
83
96
|
@options = {:urls => ["/statics", "/documents*"], :root => @root, :versioning => false, :duration => 2}
|
84
97
|
request
|
@@ -4,23 +4,25 @@ require 'rack'
|
|
4
4
|
require 'rack/contrib/try_static'
|
5
5
|
require 'rack/mock'
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def build_options(opts)
|
8
|
+
{
|
9
9
|
:urls => %w[/],
|
10
10
|
:root => ::File.expand_path(::File.dirname(__FILE__)),
|
11
|
-
})
|
11
|
+
}.merge(opts)
|
12
|
+
end
|
12
13
|
|
14
|
+
def request(options = {})
|
13
15
|
@request =
|
14
16
|
Rack::MockRequest.new(
|
15
17
|
Rack::TryStatic.new(
|
16
|
-
lambda {[200, {}, ["Hello World"]]},
|
18
|
+
lambda { |_| [200, {}, ["Hello World"]]},
|
17
19
|
options))
|
18
20
|
end
|
19
21
|
|
20
22
|
describe "Rack::TryStatic" do
|
21
23
|
context 'when file cannot be found' do
|
22
24
|
it 'should call call app' do
|
23
|
-
res = request(:try => ['html']).get('/documents')
|
25
|
+
res = request(build_options(:try => ['html'])).get('/documents')
|
24
26
|
res.should.be.ok
|
25
27
|
res.body.should == "Hello World"
|
26
28
|
end
|
@@ -28,7 +30,7 @@ describe "Rack::TryStatic" do
|
|
28
30
|
|
29
31
|
context 'when file can be found' do
|
30
32
|
it 'should serve first found' do
|
31
|
-
res = request(:try => ['.html', '/index.html', '/index.htm']).get('/documents')
|
33
|
+
res = request(build_options(:try => ['.html', '/index.html', '/index.htm'])).get('/documents')
|
32
34
|
res.should.be.ok
|
33
35
|
res.body.strip.should == "index.html"
|
34
36
|
end
|
@@ -36,9 +38,19 @@ describe "Rack::TryStatic" do
|
|
36
38
|
|
37
39
|
context 'when path_info maps directly to file' do
|
38
40
|
it 'should serve existing' do
|
39
|
-
res = request(:try => ['/index.html']).get('/documents/existing.html')
|
41
|
+
res = request(build_options(:try => ['/index.html'])).get('/documents/existing.html')
|
40
42
|
res.should.be.ok
|
41
43
|
res.body.strip.should == "existing.html"
|
42
44
|
end
|
43
45
|
end
|
46
|
+
|
47
|
+
context 'when sharing options' do
|
48
|
+
it 'should not mutate given options' do
|
49
|
+
org_options = build_options :try => ['/index.html']
|
50
|
+
given_options = org_options.dup
|
51
|
+
request(given_options).get('/documents').should.be.ok
|
52
|
+
request(given_options).get('/documents').should.be.ok
|
53
|
+
given_options.should == org_options
|
54
|
+
end
|
55
|
+
end
|
44
56
|
end
|
metadata
CHANGED
@@ -1,95 +1,88 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-contrib
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 1
|
9
|
-
- 0
|
10
|
-
version: 1.1.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- rack-devel
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2014-10-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: rack
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 57
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 9
|
33
|
-
- 1
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
34
21
|
version: 0.9.1
|
35
22
|
type: :runtime
|
36
|
-
version_requirements: *id001
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: test-spec
|
39
23
|
prerelease: false
|
40
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.9.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: test-spec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
41
33
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
hash: 59
|
46
|
-
segments:
|
47
|
-
- 0
|
48
|
-
- 9
|
49
|
-
- 0
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
50
37
|
version: 0.9.0
|
51
38
|
type: :development
|
52
|
-
version_requirements: *id002
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: tmail
|
55
39
|
prerelease: false
|
56
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
41
|
none: false
|
58
|
-
requirements:
|
59
|
-
- -
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: tmail
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.2'
|
66
54
|
type: :development
|
67
|
-
version_requirements: *id003
|
68
|
-
- !ruby/object:Gem::Dependency
|
69
|
-
name: json
|
70
55
|
prerelease: false
|
71
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
57
|
none: false
|
73
|
-
requirements:
|
74
|
-
- -
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.2'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: json
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.1'
|
81
70
|
type: :development
|
82
|
-
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.1'
|
83
78
|
description: Contributed Rack Middleware and Utilities
|
84
79
|
email: rack-devel@googlegroups.com
|
85
80
|
executables: []
|
86
|
-
|
87
81
|
extensions: []
|
88
|
-
|
89
|
-
extra_rdoc_files:
|
82
|
+
extra_rdoc_files:
|
90
83
|
- README.rdoc
|
91
84
|
- COPYING
|
92
|
-
files:
|
85
|
+
files:
|
93
86
|
- AUTHORS
|
94
87
|
- COPYING
|
95
88
|
- README.rdoc
|
@@ -117,6 +110,7 @@ files:
|
|
117
110
|
- lib/rack/contrib/not_found.rb
|
118
111
|
- lib/rack/contrib/post_body_content_type_parser.rb
|
119
112
|
- lib/rack/contrib/proctitle.rb
|
113
|
+
- lib/rack/contrib/printout.rb
|
120
114
|
- lib/rack/contrib/profiler.rb
|
121
115
|
- lib/rack/contrib/relative_redirect.rb
|
122
116
|
- lib/rack/contrib/response_cache.rb
|
@@ -168,46 +162,38 @@ files:
|
|
168
162
|
- test/spec_rack_static_cache.rb
|
169
163
|
- test/spec_rack_try_static.rb
|
170
164
|
- test/statics/test
|
171
|
-
has_rdoc: true
|
172
165
|
homepage: http://github.com/rack/rack-contrib/
|
173
|
-
licenses:
|
174
|
-
|
166
|
+
licenses:
|
167
|
+
- MIT
|
175
168
|
post_install_message:
|
176
|
-
rdoc_options:
|
169
|
+
rdoc_options:
|
177
170
|
- --line-numbers
|
178
171
|
- --inline-source
|
179
172
|
- --title
|
180
173
|
- rack-contrib
|
181
174
|
- --main
|
182
175
|
- README
|
183
|
-
require_paths:
|
176
|
+
require_paths:
|
184
177
|
- lib
|
185
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
179
|
none: false
|
187
|
-
requirements:
|
188
|
-
- -
|
189
|
-
- !ruby/object:Gem::Version
|
190
|
-
|
191
|
-
|
192
|
-
- 0
|
193
|
-
version: "0"
|
194
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ! '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
185
|
none: false
|
196
|
-
requirements:
|
197
|
-
- -
|
198
|
-
- !ruby/object:Gem::Version
|
199
|
-
|
200
|
-
segments:
|
201
|
-
- 0
|
202
|
-
version: "0"
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
203
190
|
requirements: []
|
204
|
-
|
205
191
|
rubyforge_project:
|
206
|
-
rubygems_version: 1.
|
192
|
+
rubygems_version: 1.8.23
|
207
193
|
signing_key:
|
208
194
|
specification_version: 2
|
209
195
|
summary: Contributed Rack Middleware and Utilities
|
210
|
-
test_files:
|
196
|
+
test_files:
|
211
197
|
- test/spec_rack_accept_format.rb
|
212
198
|
- test/spec_rack_access.rb
|
213
199
|
- test/spec_rack_backstage.rb
|