rack 2.1.0 → 2.2.2

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

Potentially problematic release.


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

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -6
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +83 -39
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +26 -1
  7. data/lib/rack.rb +7 -16
  8. data/lib/rack/auth/abstract/request.rb +0 -2
  9. data/lib/rack/auth/basic.rb +3 -3
  10. data/lib/rack/auth/digest/md5.rb +4 -4
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +78 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +25 -18
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +49 -35
  21. data/lib/rack/directory.rb +77 -60
  22. data/lib/rack/etag.rb +2 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -2
  25. data/lib/rack/files.rb +97 -57
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +57 -14
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/mock.rb +22 -4
  37. data/lib/rack/multipart.rb +1 -1
  38. data/lib/rack/multipart/generator.rb +11 -6
  39. data/lib/rack/multipart/parser.rb +10 -18
  40. data/lib/rack/multipart/uploaded_file.rb +13 -7
  41. data/lib/rack/query_parser.rb +7 -8
  42. data/lib/rack/recursive.rb +1 -1
  43. data/lib/rack/reloader.rb +1 -3
  44. data/lib/rack/request.rb +182 -76
  45. data/lib/rack/response.rb +62 -19
  46. data/lib/rack/rewindable_input.rb +0 -1
  47. data/lib/rack/runtime.rb +3 -3
  48. data/lib/rack/sendfile.rb +0 -3
  49. data/lib/rack/server.rb +9 -10
  50. data/lib/rack/session/abstract/id.rb +23 -28
  51. data/lib/rack/session/cookie.rb +1 -3
  52. data/lib/rack/session/pool.rb +1 -1
  53. data/lib/rack/show_exceptions.rb +6 -8
  54. data/lib/rack/show_status.rb +5 -7
  55. data/lib/rack/static.rb +13 -6
  56. data/lib/rack/tempfile_reaper.rb +0 -2
  57. data/lib/rack/urlmap.rb +1 -4
  58. data/lib/rack/utils.rb +58 -54
  59. data/lib/rack/version.rb +29 -0
  60. data/rack.gemspec +31 -29
  61. metadata +11 -12
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'rack/request'
5
- require 'rack/utils'
6
4
 
7
5
  module Rack
8
6
  # Rack::ShowStatus catches all empty responses and replaces them
@@ -20,19 +18,19 @@ module Rack
20
18
 
21
19
  def call(env)
22
20
  status, headers, body = @app.call(env)
23
- headers = Utils::HeaderHash.new(headers)
21
+ headers = Utils::HeaderHash[headers]
24
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
25
23
 
26
24
  # client or server error, or explicit message
27
25
  if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
28
- # This double assignment is to prevent an "unused variable" warning on
29
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
26
+ # This double assignment is to prevent an "unused variable" warning.
27
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
30
28
  req = req = Rack::Request.new(env)
31
29
 
32
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
33
31
 
34
- # This double assignment is to prevent an "unused variable" warning on
35
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
32
+ # This double assignment is to prevent an "unused variable" warning.
33
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
36
34
  detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
37
35
 
38
36
  body = @template.result(binding)
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/files"
4
- require "rack/utils"
5
-
6
- require_relative 'core_ext/regexp'
7
-
8
3
  module Rack
9
4
 
10
5
  # The Rack::Static middleware intercepts requests for static files
@@ -19,6 +14,11 @@ module Rack
19
14
  #
20
15
  # use Rack::Static, :urls => ["/media"]
21
16
  #
17
+ # Same as previous, but instead of returning 404 for missing files under
18
+ # /media, call the next middleware:
19
+ #
20
+ # use Rack::Static, :urls => ["/media"], :cascade => true
21
+ #
22
22
  # Serve all requests beginning with /css or /images from the folder "public"
23
23
  # in the current directory (ie public/css/* and public/images/*):
24
24
  #
@@ -86,13 +86,14 @@ module Rack
86
86
  # ]
87
87
  #
88
88
  class Static
89
- using ::Rack::RegexpExtensions
89
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
90
90
 
91
91
  def initialize(app, options = {})
92
92
  @app = app
93
93
  @urls = options[:urls] || ["/favicon.ico"]
94
94
  @index = options[:index]
95
95
  @gzip = options[:gzip]
96
+ @cascade = options[:cascade]
96
97
  root = options[:root] || Dir.pwd
97
98
 
98
99
  # HTTP Headers
@@ -133,6 +134,8 @@ module Rack
133
134
 
134
135
  if response[0] == 404
135
136
  response = nil
137
+ elsif response[0] == 304
138
+ # Do nothing, leave headers as is
136
139
  else
137
140
  if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
138
141
  response[1][CONTENT_TYPE] = mime_type
@@ -144,6 +147,10 @@ module Rack
144
147
  path = env[PATH_INFO]
145
148
  response ||= @file_server.call(env)
146
149
 
150
+ if @cascade && response[0] == 404
151
+ return @app.call(env)
152
+ end
153
+
147
154
  headers = response[1]
148
155
  applicable_rules(path).each do |rule, new_headers|
149
156
  new_headers.each { |field, content| headers[field] = content }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/body_proxy'
4
-
5
3
  module Rack
6
4
 
7
5
  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
@@ -16,9 +16,6 @@ module Rack
16
16
  # first, since they are most specific.
17
17
 
18
18
  class URLMap
19
- NEGATIVE_INFINITY = -1.0 / 0.0
20
- INFINITY = 1.0 / 0.0
21
-
22
19
  def initialize(map = {})
23
20
  remap(map)
24
21
  end
@@ -42,7 +39,7 @@ module Rack
42
39
 
43
40
  [host, location, match, app]
44
41
  }.sort_by do |(host, location, _, _)|
45
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
46
43
  end
47
44
  end
48
45
 
@@ -5,17 +5,16 @@ require 'uri'
5
5
  require 'fileutils'
6
6
  require 'set'
7
7
  require 'tempfile'
8
- require 'rack/query_parser'
9
8
  require 'time'
10
9
 
11
- require_relative 'core_ext/regexp'
10
+ require_relative 'query_parser'
12
11
 
13
12
  module Rack
14
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
15
14
  # applications adopted from all kinds of Ruby libraries.
16
15
 
17
16
  module Utils
18
- using ::Rack::RegexpExtensions
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
19
18
 
20
19
  ParameterTypeError = QueryParser::ParameterTypeError
21
20
  InvalidParameterError = QueryParser::InvalidParameterError
@@ -30,33 +29,30 @@ module Rack
30
29
  # This helps prevent a rogue client from flooding a Request.
31
30
  self.default_query_parser = QueryParser.make_default(65536, 100)
32
31
 
32
+ module_function
33
+
33
34
  # URI escapes. (CGI style space to +)
34
35
  def escape(s)
35
36
  URI.encode_www_form_component(s)
36
37
  end
37
- module_function :escape
38
38
 
39
39
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
40
40
  # true URI escaping.
41
41
  def escape_path(s)
42
42
  ::URI::DEFAULT_PARSER.escape s
43
43
  end
44
- module_function :escape_path
45
44
 
46
45
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
47
46
  # unescaping query parameters or form components.
48
47
  def unescape_path(s)
49
48
  ::URI::DEFAULT_PARSER.unescape s
50
49
  end
51
- module_function :unescape_path
52
-
53
50
 
54
51
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
55
52
  # target encoding of the string returned, and it defaults to UTF-8
56
53
  def unescape(s, encoding = Encoding::UTF_8)
57
54
  URI.decode_www_form_component(s, encoding)
58
55
  end
59
- module_function :unescape
60
56
 
61
57
  class << self
62
58
  attr_accessor :multipart_part_limit
@@ -88,21 +84,20 @@ module Rack
88
84
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
89
85
  end
90
86
  else
87
+ # :nocov:
91
88
  def clock_time
92
89
  Time.now.to_f
93
90
  end
91
+ # :nocov:
94
92
  end
95
- module_function :clock_time
96
93
 
97
94
  def parse_query(qs, d = nil, &unescaper)
98
95
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
99
96
  end
100
- module_function :parse_query
101
97
 
102
98
  def parse_nested_query(qs, d = nil)
103
99
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
104
100
  end
105
- module_function :parse_nested_query
106
101
 
107
102
  def build_query(params)
108
103
  params.map { |k, v|
@@ -113,7 +108,6 @@ module Rack
113
108
  end
114
109
  }.join("&")
115
110
  end
116
- module_function :build_query
117
111
 
118
112
  def build_nested_query(value, prefix = nil)
119
113
  case value
@@ -132,7 +126,6 @@ module Rack
132
126
  "#{prefix}=#{escape(value)}"
133
127
  end
134
128
  end
135
- module_function :build_nested_query
136
129
 
137
130
  def q_values(q_value_header)
138
131
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
@@ -144,8 +137,11 @@ module Rack
144
137
  [value, quality]
145
138
  end
146
139
  end
147
- module_function :q_values
148
140
 
141
+ # Return best accept value to use, based on the algorithm
142
+ # in RFC 2616 Section 14. If there are multiple best
143
+ # matches (same specificity and quality), the value returned
144
+ # is arbitrary.
149
145
  def best_q_match(q_value_header, available_mimes)
150
146
  values = q_values(q_value_header)
151
147
 
@@ -158,7 +154,6 @@ module Rack
158
154
  end.last
159
155
  matches && matches.first
160
156
  end
161
- module_function :best_q_match
162
157
 
163
158
  ESCAPE_HTML = {
164
159
  "&" => "&amp;",
@@ -175,22 +170,27 @@ module Rack
175
170
  def escape_html(string)
176
171
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
177
172
  end
178
- module_function :escape_html
179
173
 
180
174
  def select_best_encoding(available_encodings, accept_encoding)
181
175
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
182
176
 
183
- expanded_accept_encoding =
184
- accept_encoding.each_with_object([]) do |(m, q), list|
185
- if m == "*"
186
- (available_encodings - accept_encoding.map(&:first))
187
- .each { |m2| list << [m2, q] }
188
- else
189
- list << [m, q]
177
+ expanded_accept_encoding = []
178
+
179
+ accept_encoding.each do |m, q|
180
+ preference = available_encodings.index(m) || available_encodings.size
181
+
182
+ if m == "*"
183
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
184
+ expanded_accept_encoding << [m2, q, preference]
190
185
  end
186
+ else
187
+ expanded_accept_encoding << [m, q, preference]
191
188
  end
189
+ end
192
190
 
193
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
191
+ encoding_candidates = expanded_accept_encoding
192
+ .sort_by { |_, q, p| [-q, p] }
193
+ .map!(&:first)
194
194
 
195
195
  unless encoding_candidates.include?("identity")
196
196
  encoding_candidates.push("identity")
@@ -202,23 +202,19 @@ module Rack
202
202
 
203
203
  (encoding_candidates & available_encodings)[0]
204
204
  end
205
- module_function :select_best_encoding
206
205
 
207
206
  def parse_cookies(env)
208
207
  parse_cookies_header env[HTTP_COOKIE]
209
208
  end
210
- module_function :parse_cookies
211
209
 
212
210
  def parse_cookies_header(header)
213
- # According to RFC 2109:
214
- # If multiple cookies satisfy the criteria above, they are ordered in
215
- # the Cookie header such that those with more specific Path attributes
216
- # precede those with less specific. Ordering with respect to other
217
- # attributes (e.g., Domain) is unspecified.
218
- cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
211
+ # According to RFC 6265:
212
+ # The syntax for cookie headers only supports semicolons
213
+ # User Agent -> Server ==
214
+ # Cookie: SID=31d4d96e407aad42; lang=en-US
215
+ cookies = parse_query(header, ';') { |s| unescape(s) rescue s }
219
216
  cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
220
217
  end
221
- module_function :parse_cookies_header
222
218
 
223
219
  def add_cookie_to_header(header, key, value)
224
220
  case value
@@ -260,13 +256,11 @@ module Rack
260
256
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
261
257
  end
262
258
  end
263
- module_function :add_cookie_to_header
264
259
 
265
260
  def set_cookie_header!(header, key, value)
266
261
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
267
262
  nil
268
263
  end
269
- module_function :set_cookie_header!
270
264
 
271
265
  def make_delete_cookie_header(header, key, value)
272
266
  case header
@@ -278,25 +272,30 @@ module Rack
278
272
  cookies = header
279
273
  end
280
274
 
281
- regexp = if value[:domain]
282
- /\A#{escape(key)}=.*domain=#{value[:domain]}/
283
- elsif value[:path]
284
- /\A#{escape(key)}=.*path=#{value[:path]}/
275
+ key = escape(key)
276
+ domain = value[:domain]
277
+ path = value[:path]
278
+ regexp = if domain
279
+ if path
280
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
281
+ else
282
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
283
+ end
284
+ elsif path
285
+ /\A#{key}=.*path=#{path}(?:;|$)/
285
286
  else
286
- /\A#{escape(key)}=/
287
+ /\A#{key}=/
287
288
  end
288
289
 
289
290
  cookies.reject! { |cookie| regexp.match? cookie }
290
291
 
291
292
  cookies.join("\n")
292
293
  end
293
- module_function :make_delete_cookie_header
294
294
 
295
295
  def delete_cookie_header!(header, key, value = {})
296
296
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
297
297
  nil
298
298
  end
299
- module_function :delete_cookie_header!
300
299
 
301
300
  # Adds a cookie that will *remove* a cookie from the client. Hence the
302
301
  # strange method name.
@@ -309,12 +308,10 @@ module Rack
309
308
  expires: Time.at(0) }.merge(value))
310
309
 
311
310
  end
312
- module_function :add_remove_cookie_to_header
313
311
 
314
312
  def rfc2822(time)
315
313
  time.rfc2822
316
314
  end
317
- module_function :rfc2822
318
315
 
319
316
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
320
317
  # of '% %b %Y'.
@@ -330,7 +327,6 @@ module Rack
330
327
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
331
328
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
332
329
  end
333
- module_function :rfc2109
334
330
 
335
331
  # Parses the "Range:" header, if present, into an array of Range objects.
336
332
  # Returns nil if the header is missing or syntactically invalid.
@@ -339,7 +335,6 @@ module Rack
339
335
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
340
336
  get_byte_ranges env['HTTP_RANGE'], size
341
337
  end
342
- module_function :byte_ranges
343
338
 
344
339
  def get_byte_ranges(http_range, size)
345
340
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -368,7 +363,6 @@ module Rack
368
363
  end
369
364
  ranges
370
365
  end
371
- module_function :get_byte_ranges
372
366
 
373
367
  # Constant time string comparison.
374
368
  #
@@ -385,7 +379,6 @@ module Rack
385
379
  b.each_byte { |v| r |= v ^ l[i += 1] }
386
380
  r == 0
387
381
  end
388
- module_function :secure_compare
389
382
 
390
383
  # Context allows the use of a compatible middleware at different points
391
384
  # in a request handling stack. A compatible middleware must define
@@ -418,6 +411,14 @@ module Rack
418
411
  #
419
412
  # @api private
420
413
  class HeaderHash < Hash # :nodoc:
414
+ def self.[](headers)
415
+ if headers.is_a?(HeaderHash) && !headers.frozen?
416
+ return headers
417
+ else
418
+ return self.new(headers)
419
+ end
420
+ end
421
+
421
422
  def initialize(hash = {})
422
423
  super()
423
424
  @names = {}
@@ -430,6 +431,12 @@ module Rack
430
431
  @names = other.names.dup
431
432
  end
432
433
 
434
+ # on clear, we need to clear @names hash
435
+ def clear
436
+ super
437
+ @names.clear
438
+ end
439
+
433
440
  def each
434
441
  super do |k, v|
435
442
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -574,7 +581,6 @@ module Rack
574
581
  status.to_i
575
582
  end
576
583
  end
577
- module_function :status_code
578
584
 
579
585
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
580
586
 
@@ -588,18 +594,16 @@ module Rack
588
594
  part == '..' ? clean.pop : clean << part
589
595
  end
590
596
 
591
- clean.unshift '/' if parts.empty? || parts.first.empty?
592
-
593
- ::File.join clean
597
+ clean_path = clean.join(::File::SEPARATOR)
598
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
599
+ clean_path
594
600
  end
595
- module_function :clean_path_info
596
601
 
597
602
  NULL_BYTE = "\0"
598
603
 
599
604
  def valid_path?(path)
600
605
  path.valid_encoding? && !path.include?(NULL_BYTE)
601
606
  end
602
- module_function :valid_path?
603
607
 
604
608
  end
605
609
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
4
+ #
5
+ # Rack is freely distributable under the terms of an MIT-style license.
6
+ # See MIT-LICENSE or https://opensource.org/licenses/MIT.
7
+
8
+ # The Rack main module, serving as a namespace for all core Rack
9
+ # modules and classes.
10
+ #
11
+ # All modules meant for use in your application are <tt>autoload</tt>ed here,
12
+ # so it should be enough just to <tt>require 'rack'</tt> in your code.
13
+
14
+ module Rack
15
+ # The Rack protocol version number implemented.
16
+ VERSION = [1, 3]
17
+
18
+ # Return the Rack protocol version as a dotted string.
19
+ def self.version
20
+ VERSION.join(".")
21
+ end
22
+
23
+ RELEASE = "2.2.2"
24
+
25
+ # Return the Rack release as a dotted string.
26
+ def self.release
27
+ RELEASE
28
+ end
29
+ end