actionpack 4.1.7 → 4.2.11

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

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +404 -451
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +11 -4
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +7 -1
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching/fragments.rb +7 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/log_subscriber.rb +26 -26
  14. data/lib/action_controller/metal/conditional_get.rb +37 -12
  15. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  16. data/lib/action_controller/metal/exceptions.rb +1 -1
  17. data/lib/action_controller/metal/force_ssl.rb +1 -1
  18. data/lib/action_controller/metal/head.rb +7 -3
  19. data/lib/action_controller/metal/http_authentication.rb +20 -10
  20. data/lib/action_controller/metal/instrumentation.rb +8 -5
  21. data/lib/action_controller/metal/live.rb +57 -6
  22. data/lib/action_controller/metal/mime_responds.rb +25 -246
  23. data/lib/action_controller/metal/params_wrapper.rb +5 -5
  24. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  25. data/lib/action_controller/metal/redirecting.rb +14 -8
  26. data/lib/action_controller/metal/renderers.rb +29 -11
  27. data/lib/action_controller/metal/rendering.rb +2 -6
  28. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  29. data/lib/action_controller/metal/streaming.rb +1 -1
  30. data/lib/action_controller/metal/strong_parameters.rb +129 -14
  31. data/lib/action_controller/metal/url_for.rb +11 -12
  32. data/lib/action_controller/metal.rb +12 -11
  33. data/lib/action_controller/model_naming.rb +1 -1
  34. data/lib/action_controller/railtie.rb +4 -0
  35. data/lib/action_controller/test_case.rb +119 -75
  36. data/lib/action_controller.rb +1 -1
  37. data/lib/action_dispatch/http/cache.rb +5 -4
  38. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  39. data/lib/action_dispatch/http/headers.rb +43 -9
  40. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  41. data/lib/action_dispatch/http/mime_type.rb +18 -4
  42. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  43. data/lib/action_dispatch/http/parameters.rb +11 -26
  44. data/lib/action_dispatch/http/request.rb +37 -11
  45. data/lib/action_dispatch/http/response.rb +74 -23
  46. data/lib/action_dispatch/http/upload.rb +9 -8
  47. data/lib/action_dispatch/http/url.rb +89 -70
  48. data/lib/action_dispatch/journey/formatter.rb +34 -18
  49. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  50. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  51. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  52. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  53. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  54. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  55. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  56. data/lib/action_dispatch/journey/parser.rb +52 -60
  57. data/lib/action_dispatch/journey/parser.y +11 -10
  58. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  59. data/lib/action_dispatch/journey/route.rb +4 -19
  60. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  61. data/lib/action_dispatch/journey/router/utils.rb +1 -1
  62. data/lib/action_dispatch/journey/router.rb +53 -77
  63. data/lib/action_dispatch/journey/routes.rb +4 -0
  64. data/lib/action_dispatch/journey/scanner.rb +5 -5
  65. data/lib/action_dispatch/journey/visitors.rb +81 -92
  66. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  67. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  68. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +34 -34
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  72. data/lib/action_dispatch/middleware/flash.rb +13 -7
  73. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  74. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  75. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  76. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +1 -1
  80. data/lib/action_dispatch/middleware/static.rb +75 -39
  81. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  82. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  83. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  84. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  90. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  92. data/lib/action_dispatch/railtie.rb +2 -0
  93. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  94. data/lib/action_dispatch/routing/inspector.rb +5 -12
  95. data/lib/action_dispatch/routing/mapper.rb +414 -283
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  97. data/lib/action_dispatch/routing/redirection.rb +10 -12
  98. data/lib/action_dispatch/routing/route_set.rb +300 -173
  99. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  100. data/lib/action_dispatch/routing/url_for.rb +17 -5
  101. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  103. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  104. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  105. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  106. data/lib/action_dispatch/testing/assertions.rb +11 -7
  107. data/lib/action_dispatch/testing/integration.rb +28 -20
  108. data/lib/action_dispatch/testing/test_request.rb +1 -1
  109. data/lib/action_dispatch/testing/test_response.rb +1 -5
  110. data/lib/action_pack/gem_version.rb +3 -3
  111. metadata +55 -13
  112. data/lib/action_controller/metal/responder.rb +0 -297
@@ -1,3 +1,5 @@
1
+ require 'ipaddr'
2
+
1
3
  module ActionDispatch
2
4
  # This middleware calculates the IP address of the remote client that is
3
5
  # making the request. It does this by checking various headers that could
@@ -11,7 +13,7 @@ module ActionDispatch
11
13
  # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
12
14
  # requires. Some Rack servers simply drop preceding headers, and only report
13
15
  # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
14
- # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
16
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
15
17
  # then you should test your Rack server to make sure your data is good.
16
18
  #
17
19
  # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
@@ -28,14 +30,14 @@ module ActionDispatch
28
30
  # guaranteed by the IP specification to be private addresses. Those will
29
31
  # not be the ultimate client IP in production, and so are discarded. See
30
32
  # http://en.wikipedia.org/wiki/Private_network for details.
31
- TRUSTED_PROXIES = %r{
32
- ^127\.0\.0\.1$ | # localhost IPv4
33
- ^::1$ | # localhost IPv6
34
- ^fc00: | # private IPv6 range fc00
35
- ^10\. | # private IPv4 range 10.x.x.x
36
- ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
37
- ^192\.168\. # private IPv4 range 192.168.x.x
38
- }x
33
+ TRUSTED_PROXIES = [
34
+ "127.0.0.1", # localhost IPv4
35
+ "::1", # localhost IPv6
36
+ "fc00::/7", # private IPv6 range fc00::/7
37
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
38
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
39
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
40
+ ].map { |proxy| IPAddr.new(proxy) }
39
41
 
40
42
  attr_reader :check_ip, :proxies
41
43
 
@@ -47,24 +49,24 @@ module ActionDispatch
47
49
  # clients (like WAP devices), or behind proxies that set headers in an
48
50
  # incorrect or confusing way (like AWS ELB).
49
51
  #
50
- # The +custom_proxies+ argument can take a regex, which will be used
51
- # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
52
- # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
53
- # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
54
- # servers after it. If your proxies aren't removed, pass them in via the
55
- # +custom_proxies+ parameter. That way, the middleware will ignore those
56
- # IP addresses, and return the one that you want.
52
+ # The +custom_proxies+ argument can take an Array of string, IPAddr, or
53
+ # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
54
+ # single string, IPAddr, or Regexp object is provided, it will be used in
55
+ # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
56
+ # want in the middle (or at the beginning) of the X-Forwarded-For list,
57
+ # with your proxy servers after it. If your proxies aren't removed, pass
58
+ # them in via the +custom_proxies+ parameter. That way, the middleware will
59
+ # ignore those IP addresses, and return the one that you want.
57
60
  def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
58
61
  @app = app
59
62
  @check_ip = check_ip_spoofing
60
- @proxies = case custom_proxies
61
- when Regexp
62
- custom_proxies
63
- when nil
64
- TRUSTED_PROXIES
65
- else
66
- Regexp.union(TRUSTED_PROXIES, custom_proxies)
67
- end
63
+ @proxies = if custom_proxies.blank?
64
+ TRUSTED_PROXIES
65
+ elsif custom_proxies.respond_to?(:any?)
66
+ custom_proxies
67
+ else
68
+ Array(custom_proxies) + TRUSTED_PROXIES
69
+ end
68
70
  end
69
71
 
70
72
  # Since the IP address may not be needed, we store the object here
@@ -80,32 +82,6 @@ module ActionDispatch
80
82
  # into an actual IP address. If the ActionDispatch::Request#remote_ip method
81
83
  # is called, this class will calculate the value and then memoize it.
82
84
  class GetIp
83
-
84
- # This constant contains a regular expression that validates every known
85
- # form of IP v4 and v6 address, with or without abbreviations, adapted
86
- # from {this gist}[https://gist.github.com/gazay/1289635].
87
- VALID_IP = %r{
88
- (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
89
- (^(
90
- (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
91
- (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
92
- (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
93
- (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
94
- (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
95
- (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
96
- (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
97
- (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
98
- (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
99
- (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
100
- (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
101
- (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
102
- (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
103
- ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
104
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
105
- (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
106
- )$)
107
- }x
108
-
109
85
  def initialize(env, middleware)
110
86
  @env = env
111
87
  @check_ip = middleware.check_ip
@@ -118,7 +94,7 @@ module ActionDispatch
118
94
  #
119
95
  # REMOTE_ADDR will be correct if the request is made directly against the
120
96
  # Ruby process, on e.g. Heroku. When the request is proxied by another
121
- # server like HAProxy or Nginx, the IP address that made the original
97
+ # server like HAProxy or NGINX, the IP address that made the original
122
98
  # request will be put in an X-Forwarded-For header. If there are multiple
123
99
  # proxies, that header may contain a list of IPs. Other proxy services
124
100
  # set the Client-Ip header instead, so we check that too.
@@ -173,12 +149,22 @@ module ActionDispatch
173
149
  def ips_from(header)
174
150
  # Split the comma-separated list into an array of strings
175
151
  ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
176
- # Only return IPs that are valid according to the regex
177
- ips.select{ |ip| ip =~ VALID_IP }
152
+ ips.select do |ip|
153
+ begin
154
+ # Only return IPs that are valid according to the IPAddr#new method
155
+ range = IPAddr.new(ip).to_range
156
+ # we want to make sure nobody is sneaking a netmask in
157
+ range.begin == range.end
158
+ rescue ArgumentError
159
+ nil
160
+ end
161
+ end
178
162
  end
179
163
 
180
164
  def filter_proxies(ips)
181
- ips.reject { |ip| ip =~ @proxies }
165
+ ips.reject do |ip|
166
+ @proxies.any? { |proxy| proxy === ip }
167
+ end
182
168
  end
183
169
 
184
170
  end
@@ -5,7 +5,7 @@ module ActionDispatch
5
5
  # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
6
6
  # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
7
7
  #
8
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
8
+ # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
9
9
  # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
10
10
  # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
11
11
  #
@@ -49,7 +49,7 @@ module ActionDispatch
49
49
  # reasonably sure that your upgrade is otherwise complete. Additionally,
50
50
  # you should take care to make sure you are not relying on the ability to
51
51
  # decode signed cookies generated by your app in external applications or
52
- # Javascript before upgrading.
52
+ # JavaScript before upgrading.
53
53
  #
54
54
  # Note that changing the secret key will invalidate all existing sessions!
55
55
  class CookieStore < Rack::Session::Abstract::ID
@@ -42,6 +42,7 @@ module ActionDispatch
42
42
  wrapper = ExceptionWrapper.new(env, exception)
43
43
  status = wrapper.status_code
44
44
  env["action_dispatch.exception"] = wrapper.exception
45
+ env["action_dispatch.original_path"] = env["PATH_INFO"]
45
46
  env["PATH_INFO"] = "/#{status}"
46
47
  response = @exceptions_app.call(env)
47
48
  response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
@@ -22,7 +22,7 @@ module ActionDispatch
22
22
 
23
23
  if request.ssl?
24
24
  status, headers, body = @app.call(env)
25
- headers = hsts_headers.merge(headers)
25
+ headers.reverse_merge!(hsts_headers)
26
26
  flag_cookies_as_secure!(headers)
27
27
  [status, headers, body]
28
28
  else
@@ -2,69 +2,105 @@ require 'rack/utils'
2
2
  require 'active_support/core_ext/uri'
3
3
 
4
4
  module ActionDispatch
5
+ # This middleware returns a file's contents from disk in the body response.
6
+ # When initialized it can accept an optional 'Cache-Control' header which
7
+ # will be set when a response containing a file's contents is delivered.
8
+ #
9
+ # This middleware will render the file specified in `env["PATH_INFO"]`
10
+ # where the base path is in the +root+ directory. For example if the +root+
11
+ # is set to `public/` then a request with `env["PATH_INFO"]` of
12
+ # `assets/application.js` will return a response with contents of a file
13
+ # located at `public/assets/application.js` if the file exists. If the file
14
+ # does not exist a 404 "File not Found" response will be returned.
5
15
  class FileHandler
6
16
  def initialize(root, cache_control)
7
17
  @root = root.chomp('/')
8
18
  @compiled_root = /^#{Regexp.escape(root)}/
9
- headers = cache_control && { 'Cache-Control' => cache_control }
10
- @file_server = ::Rack::File.new(@root, headers)
19
+ headers = cache_control && { 'Cache-Control' => cache_control }
20
+ @file_server = ::Rack::File.new(@root, headers)
11
21
  end
12
22
 
13
23
  def match?(path)
14
- path = unescape_path(path)
15
- return false unless path.valid_encoding?
16
-
17
- full_path = path.empty? ? @root : File.join(@root,
18
- clean_path_info(escape_glob_chars(path)))
19
- paths = "#{full_path}#{ext}"
20
-
21
- matches = Dir[paths]
22
- match = matches.detect { |m| File.file?(m) }
23
- if match
24
- match.sub!(@compiled_root, '')
25
- ::Rack::Utils.escape(match)
24
+ path = URI.parser.unescape(path)
25
+ return false unless valid_path?(path)
26
+
27
+ paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v|
28
+ Rack::Utils.clean_path_info v
29
+ }
30
+
31
+ if match = paths.detect { |p|
32
+ path = File.join(@root, p.force_encoding('UTF-8'))
33
+ begin
34
+ File.file?(path) && File.readable?(path)
35
+ rescue SystemCallError
36
+ false
37
+ end
38
+
39
+ }
40
+ return ::Rack::Utils.escape(match)
26
41
  end
27
42
  end
28
43
 
29
44
  def call(env)
30
- @file_server.call(env)
31
- end
32
-
33
- def ext
34
- @ext ||= begin
35
- ext = ::ActionController::Base.default_static_extension
36
- "{,#{ext},/index#{ext}}"
45
+ path = env['PATH_INFO']
46
+ gzip_path = gzip_file_path(path)
47
+
48
+ if gzip_path && gzip_encoding_accepted?(env)
49
+ env['PATH_INFO'] = gzip_path
50
+ status, headers, body = @file_server.call(env)
51
+ if status == 304
52
+ return [status, headers, body]
53
+ end
54
+ headers['Content-Encoding'] = 'gzip'
55
+ headers['Content-Type'] = content_type(path)
56
+ else
57
+ status, headers, body = @file_server.call(env)
37
58
  end
38
- end
39
59
 
40
- def unescape_path(path)
41
- URI.parser.unescape(path)
42
- end
60
+ headers['Vary'] = 'Accept-Encoding' if gzip_path
43
61
 
44
- def escape_glob_chars(path)
45
- path.gsub(/[*?{}\[\]]/, "\\\\\\&")
62
+ return [status, headers, body]
63
+ ensure
64
+ env['PATH_INFO'] = path
46
65
  end
47
66
 
48
67
  private
68
+ def ext
69
+ ::ActionController::Base.default_static_extension
70
+ end
49
71
 
50
- PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
51
-
52
- def clean_path_info(path_info)
53
- parts = path_info.split PATH_SEPS
54
-
55
- clean = []
72
+ def content_type(path)
73
+ ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
74
+ end
56
75
 
57
- parts.each do |part|
58
- next if part.empty? || part == '.'
59
- part == '..' ? clean.pop : clean << part
76
+ def gzip_encoding_accepted?(env)
77
+ env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
60
78
  end
61
79
 
62
- clean.unshift '/' if parts.empty? || parts.first.empty?
80
+ def gzip_file_path(path)
81
+ can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
82
+ gzip_path = "#{path}.gz"
83
+ if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path)))
84
+ gzip_path
85
+ else
86
+ false
87
+ end
88
+ end
63
89
 
64
- ::File.join(*clean)
65
- end
90
+ def valid_path?(path)
91
+ path.valid_encoding? && !path.include?("\0")
92
+ end
66
93
  end
67
94
 
95
+ # This middleware will attempt to return the contents of a file's body from
96
+ # disk in the response. If a file is not found on disk, the request will be
97
+ # delegated to the application stack. This middleware is commonly initialized
98
+ # to serve assets from a server's `public/` directory.
99
+ #
100
+ # This middleware verifies the path to ensure that only files
101
+ # living in the root directory can be rendered. A request cannot
102
+ # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
103
+ # requests will result in a file being returned.
68
104
  class Static
69
105
  def initialize(app, path, cache_control=nil)
70
106
  @app = app
@@ -1,25 +1,27 @@
1
- <% if @source_extract %>
2
- <div class="source">
3
- <div class="info">
4
- Extracted source (around line <strong>#<%= @line_number %></strong>):
5
- </div>
6
- <div class="data">
7
- <table cellpadding="0" cellspacing="0" class="lines">
8
- <tr>
9
- <td>
10
- <pre class="line_numbers">
11
- <% @source_extract.keys.each do |line_number| %>
1
+ <% @source_extracts.each_with_index do |source_extract, index| %>
2
+ <% if source_extract[:code] %>
3
+ <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
4
+ <div class="info">
5
+ Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
6
+ </div>
7
+ <div class="data">
8
+ <table cellpadding="0" cellspacing="0" class="lines">
9
+ <tr>
10
+ <td>
11
+ <pre class="line_numbers">
12
+ <% source_extract[:code].each_key do |line_number| %>
12
13
  <span><%= line_number -%></span>
13
- <% end %>
14
- </pre>
15
- </td>
14
+ <% end %>
15
+ </pre>
16
+ </td>
16
17
  <td width="100%">
17
18
  <pre>
18
- <% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%>
19
+ <% source_extract[:code].each do |line, source| -%><div class="line<%= " active" if line == source_extract[:line_number] -%>"><%= source -%></div><% end -%>
19
20
  </pre>
20
21
  </td>
21
- </tr>
22
- </table>
23
- </div>
24
- </div>
22
+ </tr>
23
+ </table>
24
+ </div>
25
+ </div>
26
+ <% end %>
25
27
  <% end %>
@@ -1,9 +1,4 @@
1
- <%
2
- traces = { "Application Trace" => @application_trace,
3
- "Framework Trace" => @framework_trace,
4
- "Full Trace" => @full_trace }
5
- names = traces.keys
6
- %>
1
+ <% names = @traces.keys %>
7
2
 
8
3
  <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
9
4
 
@@ -16,9 +11,42 @@
16
11
  <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
17
12
  <% end %>
18
13
 
19
- <% traces.each do |name, trace| %>
20
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
21
- <pre><code><%= trace.join "\n" %></code></pre>
14
+ <% @traces.each do |name, trace| %>
15
+ <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
16
+ <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
22
17
  </div>
23
18
  <% end %>
19
+
20
+ <script type="text/javascript">
21
+ var traceFrames = document.getElementsByClassName('trace-frames');
22
+ var selectedFrame, currentSource = document.getElementById('frame-source-0');
23
+
24
+ // Add click listeners for all stack frames
25
+ for (var i = 0; i < traceFrames.length; i++) {
26
+ traceFrames[i].addEventListener('click', function(e) {
27
+ e.preventDefault();
28
+ var target = e.target;
29
+ var frame_id = target.dataset.frameId;
30
+
31
+ if (selectedFrame) {
32
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
33
+ }
34
+
35
+ target.className += " selected";
36
+ selectedFrame = target;
37
+
38
+ // Change the extracted source code
39
+ changeSourceExtract(frame_id);
40
+ });
41
+
42
+ function changeSourceExtract(frame_id) {
43
+ var el = document.getElementById('frame-source-' + frame_id);
44
+ if (currentSource && el) {
45
+ currentSource.className += " hidden";
46
+ el.className = el.className.replace(" hidden", "");
47
+ currentSource = el;
48
+ }
49
+ }
50
+ }
51
+ </script>
24
52
  </div>
@@ -1,15 +1,9 @@
1
- <%
2
- traces = { "Application Trace" => @application_trace,
3
- "Framework Trace" => @framework_trace,
4
- "Full Trace" => @full_trace }
5
- %>
6
-
7
1
  Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
8
2
 
9
- <% traces.each do |name, trace| %>
3
+ <% @traces.each do |name, trace| %>
10
4
  <% if trace.any? %>
11
5
  <%= name %>
12
- <%= trace.join("\n") %>
6
+ <%= trace.map { |t| t[:trace] }.join("\n") %>
13
7
 
14
8
  <% end %>
15
9
  <% end %>