rack-contrib 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-contrib might be problematic. Click here for more details.

data/AUTHORS CHANGED
@@ -24,3 +24,4 @@ TJ Holowaychuk <tj@vision-media.ca>
24
24
  anupom syam <anupom.syam@gmail.com>
25
25
  ichverstehe <ichverstehe@gmail.com>
26
26
  kubicek <jiri@kubicek.cz>
27
+ Jordi Polo <mumismo@gmail.com>
data/COPYING CHANGED
@@ -1,3 +1,4 @@
1
+ The MIT License (MIT)
1
2
  Copyright (c) 2008 The Committers
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -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.org/rack/rack>
97
+ Rack On GitHub:: <http://github.com/rack/rack>
81
98
  rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
@@ -3,7 +3,7 @@ require 'rack'
3
3
  module Rack
4
4
  module Contrib
5
5
  def self.release
6
- "1.0.1"
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+"#{extension}"
40
+ req.path_info = req.path_info.chomp('/') << "#{extension}"
41
41
  end
42
42
 
43
43
  @app.call(env)
@@ -19,10 +19,8 @@ module Rack
19
19
  private
20
20
 
21
21
  def domain
22
- @domain ||= begin
23
- @host =~ DOMAIN_REGEXP
24
- ".#{$1}.#{$2}"
25
- end
22
+ @host =~ DOMAIN_REGEXP
23
+ ".#{$1}.#{$2}"
26
24
  end
27
25
 
28
26
  def share_cookie(headers)
@@ -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
- response = pad(request.params.delete('callback'), response)
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{ |s| body << s.to_s }
60
- ["#{callback}(#{body})"]
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
@@ -8,24 +8,35 @@ module Rack
8
8
 
9
9
  def call(env)
10
10
  old_locale = I18n.locale
11
- locale = nil
12
-
13
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
14
- if lang = env["HTTP_ACCEPT_LANGUAGE"]
15
- lang = lang.split(",").map { |l|
16
- l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
17
- l.split(';q=')
18
- }.first
19
- locale = lang.first.split("-").first
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
- locale = env['rack.locale'] = I18n.locale = locale.to_s
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 'tmail'
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
- :server => 'localhost',
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 = TMail::Mail.new
57
- mail.to = Array(config[:to])
58
- mail.from = config[:from]
59
- mail.subject = config[:subject] % [exception.to_s]
60
- mail.date = Time.now
61
- mail.set_content_type 'text/plain'
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
- return if smtp[:server] == 'example.com'
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
- case env[CONTENT_TYPE]
33
- when APPLICATION_JSON
34
- env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
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
- process_time
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 = RubyProf::CallTreePrinter
20
- DEFAULT_CONTENT_TYPE = 'application/octet-stream'
12
+ DEFAULT_PRINTER = :call_stack
21
13
 
22
- PRINTER_CONTENT_TYPE = {
23
- RubyProf::FlatPrinter => 'text/plain',
24
- RubyProf::GraphPrinter => 'text/plain',
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] option
29
- # defaulting to :call_tree.
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' => PRINTER_CONTENT_TYPE[printer] || DEFAULT_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.nil?
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
- DEFAULT_PRINTER
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, /csss or /js from the
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
@@ -17,9 +17,9 @@ module Rack
17
17
 
18
18
  def initialize(app, options)
19
19
  @app = app
20
- @try = ['', *options.delete(:try)]
20
+ @try = ['', *options[:try]]
21
21
  @static = ::Rack::Static.new(
22
- lambda { [404, {}, []] },
22
+ lambda { |_| [404, {}, []] },
23
23
  options)
24
24
  end
25
25
 
@@ -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.1.0'
7
- s.date = '2010-10-19'
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
@@ -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 'tmail'
6
- require 'rack/contrib/mailexceptions'
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
- tmail = mailer.send(:generate_mail, test_exception, @env)
61
- tmail.to.should.equal ['foo@example.org']
62
- tmail.from.should.equal ['bar@example.org']
63
- tmail.subject.should.equal '[ERROR] Suffering Succotash!'
64
- tmail.body.should.not.be.nil
65
- tmail.body.should.be =~ /FOO:\s+"BAR"/
66
- tmail.body.should.be =~ /^\s*THE BODY\s*$/
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 (tmail not installed)"
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 handle requests with POST body Content-Type of application/json" do
10
- app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
11
- env = env_for_post_with_headers('/', {'Content_Type'.upcase => 'application/json'}, {:body => "asdf", :status => "12"}.to_json)
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 change nothing when the POST body content type isn't application/json" do
18
- app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
19
- body = Rack::PostBodyContentTypeParser.new(app).call(Rack::MockRequest.env_for("/", {:method => 'POST', :params => "body=asdf&status=12"})).last
20
- body['body'].should.equal "asdf"
21
- body['status'].should.equal "12"
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 env_for_post_with_headers(path, headers, body)
27
- Rack::MockRequest.env_for(path, {:method => "POST", :input => body}.merge(headers))
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)"
@@ -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::CallTreePrinter' do
11
+ specify 'printer defaults to RubyProf::CallStackPrinter' do
12
12
  profiler = Rack::Profiler.new(nil)
13
- profiler.instance_variable_get('@printer').should.equal RubyProf::CallTreePrinter
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 request(options = {})
8
- options.merge!({
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
- hash: 19
5
- prerelease: false
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
- date: 2010-10-19 00:00:00 -07:00
19
- default_executable:
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
- prerelease: false
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
- requirement: &id002 !ruby/object:Gem::Requirement
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
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
57
41
  none: false
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- hash: 11
62
- segments:
63
- - 1
64
- - 2
65
- version: "1.2"
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
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
72
57
  none: false
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- hash: 13
77
- segments:
78
- - 1
79
- - 1
80
- version: "1.1"
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
- version_requirements: *id004
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
- hash: 3
191
- segments:
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
- hash: 3
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.3.7
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