rack 3.0.15 → 3.2.6

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +368 -6
  3. data/CONTRIBUTING.md +11 -9
  4. data/README.md +103 -28
  5. data/SPEC.rdoc +206 -288
  6. data/lib/rack/auth/abstract/request.rb +2 -0
  7. data/lib/rack/auth/basic.rb +1 -2
  8. data/lib/rack/bad_request.rb +8 -0
  9. data/lib/rack/builder.rb +29 -10
  10. data/lib/rack/cascade.rb +0 -3
  11. data/lib/rack/conditional_get.rb +4 -3
  12. data/lib/rack/constants.rb +4 -0
  13. data/lib/rack/directory.rb +6 -3
  14. data/lib/rack/events.rb +21 -6
  15. data/lib/rack/files.rb +1 -1
  16. data/lib/rack/head.rb +2 -3
  17. data/lib/rack/headers.rb +86 -2
  18. data/lib/rack/lint.rb +482 -425
  19. data/lib/rack/media_type.rb +14 -10
  20. data/lib/rack/mime.rb +6 -5
  21. data/lib/rack/mock_request.rb +10 -15
  22. data/lib/rack/mock_response.rb +50 -20
  23. data/lib/rack/multipart/parser.rb +255 -76
  24. data/lib/rack/multipart/uploaded_file.rb +42 -5
  25. data/lib/rack/multipart.rb +34 -1
  26. data/lib/rack/query_parser.rb +86 -78
  27. data/lib/rack/request.rb +78 -65
  28. data/lib/rack/response.rb +28 -20
  29. data/lib/rack/rewindable_input.rb +4 -1
  30. data/lib/rack/sendfile.rb +51 -21
  31. data/lib/rack/show_exceptions.rb +10 -4
  32. data/lib/rack/show_status.rb +0 -2
  33. data/lib/rack/static.rb +7 -3
  34. data/lib/rack/utils.rb +175 -119
  35. data/lib/rack/version.rb +3 -20
  36. data/lib/rack.rb +1 -4
  37. metadata +6 -12
  38. data/lib/rack/auth/digest/md5.rb +0 -1
  39. data/lib/rack/auth/digest/nonce.rb +0 -1
  40. data/lib/rack/auth/digest/params.rb +0 -1
  41. data/lib/rack/auth/digest/request.rb +0 -1
  42. data/lib/rack/auth/digest.rb +0 -256
  43. data/lib/rack/chunked.rb +0 -120
  44. data/lib/rack/file.rb +0 -9
  45. data/lib/rack/logger.rb +0 -22
data/lib/rack/sendfile.rb CHANGED
@@ -16,21 +16,21 @@ module Rack
16
16
  # delivery code.
17
17
  #
18
18
  # In order to take advantage of this middleware, the response body must
19
- # respond to +to_path+ and the request must include an x-sendfile-type
19
+ # respond to +to_path+ and the request must include an `x-sendfile-type`
20
20
  # header. Rack::Files and other components implement +to_path+ so there's
21
- # rarely anything you need to do in your application. The x-sendfile-type
21
+ # rarely anything you need to do in your application. The `x-sendfile-type`
22
22
  # header is typically set in your web servers configuration. The following
23
23
  # sections attempt to document
24
24
  #
25
25
  # === Nginx
26
26
  #
27
- # Nginx supports the x-accel-redirect header. This is similar to x-sendfile
27
+ # Nginx supports the `x-accel-redirect` header. This is similar to `x-sendfile`
28
28
  # but requires parts of the filesystem to be mapped into a private URL
29
29
  # hierarchy.
30
30
  #
31
31
  # The following example shows the Nginx configuration required to create
32
- # a private "/files/" area, enable x-accel-redirect, and pass the special
33
- # x-sendfile-type and x-accel-mapping headers to the backend:
32
+ # a private "/files/" area, enable `x-accel-redirect`, and pass the special
33
+ # `x-accel-mapping` header to the backend:
34
34
  #
35
35
  # location ~ /files/(.*) {
36
36
  # internal;
@@ -44,24 +44,29 @@ module Rack
44
44
  # proxy_set_header X-Real-IP $remote_addr;
45
45
  # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
46
46
  #
47
- # proxy_set_header x-sendfile-type x-accel-redirect;
48
47
  # proxy_set_header x-accel-mapping /var/www/=/files/;
49
48
  #
50
49
  # proxy_pass http://127.0.0.1:8080/;
51
50
  # }
52
51
  #
53
- # Note that the x-sendfile-type header must be set exactly as shown above.
54
- # The x-accel-mapping header should specify the location on the file system,
52
+ # The `x-accel-mapping` header should specify the location on the file system,
55
53
  # followed by an equals sign (=), followed name of the private URL pattern
56
- # that it maps to. The middleware performs a simple substitution on the
54
+ # that it maps to. The middleware performs a case-insensitive substitution on the
57
55
  # resulting path.
58
56
  #
57
+ # To enable `x-accel-redirect`, you must configure the middleware explicitly:
58
+ #
59
+ # use Rack::Sendfile, "x-accel-redirect"
60
+ #
61
+ # For security reasons, the `x-sendfile-type` header from requests is ignored.
62
+ # The sendfile variation must be set via the middleware constructor.
63
+ #
59
64
  # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
60
65
  #
61
66
  # === lighttpd
62
67
  #
63
- # Lighttpd has supported some variation of the x-sendfile header for some
64
- # time, although only recent version support x-sendfile in a reverse proxy
68
+ # Lighttpd has supported some variation of the `x-sendfile` header for some
69
+ # time, although only recent version support `x-sendfile` in a reverse proxy
65
70
  # configuration.
66
71
  #
67
72
  # $HTTP["host"] == "example.com" {
@@ -83,7 +88,7 @@ module Rack
83
88
  #
84
89
  # === Apache
85
90
  #
86
- # x-sendfile is supported under Apache 2.x using a separate module:
91
+ # `x-sendfile` is supported under Apache 2.x using a separate module:
87
92
  #
88
93
  # https://tn123.org/mod_xsendfile/
89
94
  #
@@ -97,16 +102,28 @@ module Rack
97
102
  # === Mapping parameter
98
103
  #
99
104
  # The third parameter allows for an overriding extension of the
100
- # x-accel-mapping header. Mappings should be provided in tuples of internal to
105
+ # `x-accel-mapping` header. Mappings should be provided in tuples of internal to
101
106
  # external. The internal values may contain regular expression syntax, they
102
107
  # will be matched with case indifference.
108
+ #
109
+ # When `x-accel-redirect` is explicitly enabled via the variation parameter,
110
+ # and no application-level mappings are provided, the middleware will read
111
+ # the `x-accel-mapping` header from the proxy. This allows nginx to control
112
+ # the path mapping without requiring application-level configuration.
113
+ #
114
+ # === Security
115
+ #
116
+ # For security reasons, the `x-sendfile-type` header from HTTP requests is
117
+ # ignored. The sendfile variation must be explicitly configured via the
118
+ # middleware constructor to prevent information disclosure vulnerabilities
119
+ # where attackers could bypass proxy restrictions.
103
120
 
104
121
  class Sendfile
105
122
  def initialize(app, variation = nil, mappings = [])
106
123
  @app = app
107
124
  @variation = variation
108
125
  @mappings = mappings.map do |internal, external|
109
- [/^#{internal}/i, external]
126
+ [/\A#{internal}/i, external]
110
127
  end
111
128
  end
112
129
 
@@ -145,22 +162,35 @@ module Rack
145
162
  end
146
163
 
147
164
  private
165
+
148
166
  def variation(env)
149
- @variation ||
150
- env['sendfile.type'] ||
151
- env['HTTP_X_SENDFILE_TYPE']
167
+ # Note: HTTP_X_SENDFILE_TYPE is intentionally NOT read for security reasons.
168
+ # Attackers could use this header to enable x-accel-redirect and bypass proxy restrictions.
169
+ @variation || env['sendfile.type']
170
+ end
171
+
172
+ def x_accel_mapping(env)
173
+ # Only allow header when:
174
+ # 1. `x-accel-redirect` is explicitly enabled via constructor.
175
+ # 2. No application-level mappings are configured.
176
+ return nil unless @variation =~ /x-accel-redirect/i
177
+ return nil if @mappings.any?
178
+
179
+ env['HTTP_X_ACCEL_MAPPING']
152
180
  end
153
181
 
154
182
  def map_accel_path(env, path)
155
183
  if mapping = @mappings.find { |internal, _| internal =~ path }
156
- path.sub(*mapping)
157
- elsif mapping = env['HTTP_X_ACCEL_MAPPING']
184
+ return path.sub(*mapping)
185
+ elsif mapping = x_accel_mapping(env)
186
+ # Safe to use header: explicit config + no app mappings:
158
187
  mapping.split(',').map(&:strip).each do |m|
159
188
  internal, external = m.split('=', 2).map(&:strip)
160
- new_path = path.sub(/^#{internal}/i, external)
189
+ new_path = path.sub(/\A#{Regexp.escape(internal)}/i, external)
161
190
  return new_path unless path == new_path
162
191
  end
163
- path
192
+
193
+ return path
164
194
  end
165
195
  end
166
196
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
4
3
  require 'erb'
5
4
 
6
5
  require_relative 'constants'
@@ -19,6 +18,11 @@ module Rack
19
18
  class ShowExceptions
20
19
  CONTEXT = 7
21
20
 
21
+ Frame = Struct.new(:filename, :lineno, :function,
22
+ :pre_context_lineno, :pre_context,
23
+ :context_line, :post_context_lineno,
24
+ :post_context)
25
+
22
26
  def initialize(app)
23
27
  @app = app
24
28
  end
@@ -61,8 +65,12 @@ module Rack
61
65
  def dump_exception(exception)
62
66
  if exception.respond_to?(:detailed_message)
63
67
  message = exception.detailed_message(highlight: false)
68
+ # :nocov:
69
+ # Ruby 3.2 added Exception#detailed_message, so the else
70
+ # branch cannot be hit on the current Ruby version.
64
71
  else
65
72
  message = exception.message
73
+ # :nocov:
66
74
  end
67
75
  string = "#{exception.class}: #{message}\n".dup
68
76
  string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
@@ -79,7 +87,7 @@ module Rack
79
87
  # This double assignment is to prevent an "unused variable" warning.
80
88
  # Yes, it is dumb, but I don't like Ruby yelling at me.
81
89
  frames = frames = exception.backtrace.map { |line|
82
- frame = OpenStruct.new
90
+ frame = Frame.new
83
91
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
84
92
  frame.filename = $1
85
93
  frame.lineno = $2.to_i
@@ -397,7 +405,5 @@ module Rack
397
405
  </body>
398
406
  </html>
399
407
  HTML
400
-
401
- # :startdoc:
402
408
  end
403
409
  end
@@ -117,7 +117,5 @@ TEMPLATE = <<'HTML'
117
117
  </body>
118
118
  </html>
119
119
  HTML
120
-
121
- # :startdoc:
122
120
  end
123
121
  end
data/lib/rack/static.rb CHANGED
@@ -93,6 +93,9 @@ module Rack
93
93
  def initialize(app, options = {})
94
94
  @app = app
95
95
  @urls = options[:urls] || ["/favicon.ico"]
96
+ if @urls.kind_of?(Array)
97
+ @urls = @urls.map { |url| [url, url.end_with?('/') ? url : "#{url}/".freeze].freeze }.freeze
98
+ end
96
99
  @index = options[:index]
97
100
  @gzip = options[:gzip]
98
101
  @cascade = options[:cascade]
@@ -115,7 +118,7 @@ module Rack
115
118
  end
116
119
 
117
120
  def route_file(path)
118
- @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
121
+ @urls.kind_of?(Array) && @urls.any? { |url, url_slash| path == url || path.start_with?(url_slash) }
119
122
  end
120
123
 
121
124
  def can_serve(path)
@@ -165,6 +168,8 @@ module Rack
165
168
 
166
169
  # Convert HTTP header rules to HTTP headers
167
170
  def applicable_rules(path)
171
+ path = ::Rack::Utils.unescape_path(path)
172
+
168
173
  @header_rules.find_all do |rule, new_headers|
169
174
  case rule
170
175
  when :all
@@ -172,10 +177,9 @@ module Rack
172
177
  when :fonts
173
178
  /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
174
179
  when String
175
- path = ::Rack::Utils.unescape(path)
176
180
  path.start_with?(rule) || path.start_with?('/' + rule)
177
181
  when Array
178
- /\.(#{rule.join('|')})\z/.match?(path)
182
+ /\.#{Regexp.union(rule)}\z/.match?(path)
179
183
  when Regexp
180
184
  rule.match?(path)
181
185
  else