rack 2.0.6 → 2.2.3

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +694 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -148
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +38 -10
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack.rb +67 -73
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +1 -1
  15. data/lib/rack/auth/basic.rb +7 -4
  16. data/lib/rack/auth/digest/md5.rb +13 -11
  17. data/lib/rack/auth/digest/nonce.rb +6 -3
  18. data/lib/rack/auth/digest/params.rb +4 -2
  19. data/lib/rack/auth/digest/request.rb +5 -3
  20. data/lib/rack/body_proxy.rb +15 -14
  21. data/lib/rack/builder.rb +116 -23
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +68 -20
  24. data/lib/rack/common_logger.rb +33 -25
  25. data/lib/rack/conditional_get.rb +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +8 -7
  28. data/lib/rack/content_type.rb +5 -4
  29. data/lib/rack/core_ext/regexp.rb +14 -0
  30. data/lib/rack/deflater.rb +59 -34
  31. data/lib/rack/directory.rb +84 -64
  32. data/lib/rack/etag.rb +5 -4
  33. data/lib/rack/events.rb +19 -20
  34. data/lib/rack/file.rb +4 -173
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler.rb +7 -2
  37. data/lib/rack/handler/cgi.rb +2 -3
  38. data/lib/rack/handler/fastcgi.rb +4 -4
  39. data/lib/rack/handler/lsws.rb +3 -3
  40. data/lib/rack/handler/scgi.rb +9 -8
  41. data/lib/rack/handler/thin.rb +3 -3
  42. data/lib/rack/handler/webrick.rb +15 -6
  43. data/lib/rack/head.rb +1 -1
  44. data/lib/rack/lint.rb +71 -25
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +2 -1
  47. data/lib/rack/logger.rb +2 -0
  48. data/lib/rack/media_type.rb +10 -5
  49. data/lib/rack/method_override.rb +4 -2
  50. data/lib/rack/mime.rb +9 -1
  51. data/lib/rack/mock.rb +97 -20
  52. data/lib/rack/multipart.rb +6 -4
  53. data/lib/rack/multipart/generator.rb +17 -13
  54. data/lib/rack/multipart/parser.rb +57 -62
  55. data/lib/rack/multipart/uploaded_file.rb +15 -7
  56. data/lib/rack/null_logger.rb +2 -0
  57. data/lib/rack/query_parser.rb +53 -28
  58. data/lib/rack/recursive.rb +7 -5
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +220 -61
  61. data/lib/rack/response.rb +127 -44
  62. data/lib/rack/rewindable_input.rb +4 -3
  63. data/lib/rack/runtime.rb +6 -4
  64. data/lib/rack/sendfile.rb +13 -9
  65. data/lib/rack/server.rb +95 -24
  66. data/lib/rack/session/abstract/id.rb +100 -22
  67. data/lib/rack/session/cookie.rb +22 -14
  68. data/lib/rack/session/memcache.rb +4 -87
  69. data/lib/rack/session/pool.rb +18 -9
  70. data/lib/rack/show_exceptions.rb +21 -17
  71. data/lib/rack/show_status.rb +9 -9
  72. data/lib/rack/static.rb +23 -11
  73. data/lib/rack/tempfile_reaper.rb +1 -1
  74. data/lib/rack/urlmap.rb +12 -6
  75. data/lib/rack/utils.rb +102 -109
  76. data/lib/rack/version.rb +29 -0
  77. data/rack.gemspec +40 -28
  78. metadata +39 -181
  79. data/HISTORY.md +0 -505
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -95
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -515
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -722
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1398
  166. data/test/spec_response.rb +0 -510
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -320
  175. data/test/spec_session_pool.rb +0 -210
  176. data/test/spec_show_exceptions.rb +0 -93
  177. data/test/spec_show_status.rb +0 -104
  178. data/test/spec_static.rb +0 -184
  179. data/test/spec_tempfile_reaper.rb +0 -64
  180. data/test/spec_thin.rb +0 -96
  181. data/test/spec_urlmap.rb +0 -237
  182. data/test/spec_utils.rb +0 -742
  183. data/test/spec_version.rb +0 -11
  184. data/test/spec_webrick.rb +0 -206
  185. data/test/static/another/index.html +0 -1
  186. data/test/static/foo.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ostruct'
2
4
  require 'erb'
3
- require 'rack/request'
4
- require 'rack/utils'
5
5
 
6
6
  module Rack
7
7
  # Rack::ShowExceptions catches all exceptions raised from the app it
@@ -55,7 +55,7 @@ module Rack
55
55
  private :accepts_html?
56
56
 
57
57
  def dump_exception(exception)
58
- string = "#{exception.class}: #{exception.message}\n"
58
+ string = "#{exception.class}: #{exception.message}\n".dup
59
59
  string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
60
60
  string
61
61
  end
@@ -63,12 +63,12 @@ module Rack
63
63
  def pretty(env, exception)
64
64
  req = Rack::Request.new(env)
65
65
 
66
- # This double assignment is to prevent an "unused variable" warning on
67
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
66
+ # This double assignment is to prevent an "unused variable" warning.
67
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
68
68
  path = path = (req.script_name + req.path_info).squeeze("/")
69
69
 
70
- # This double assignment is to prevent an "unused variable" warning on
71
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
70
+ # This double assignment is to prevent an "unused variable" warning.
71
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
72
72
  frames = frames = exception.backtrace.map { |line|
73
73
  frame = OpenStruct.new
74
74
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
@@ -77,13 +77,13 @@ module Rack
77
77
  frame.function = $4
78
78
 
79
79
  begin
80
- lineno = frame.lineno-1
80
+ lineno = frame.lineno - 1
81
81
  lines = ::File.readlines(frame.filename)
82
- frame.pre_context_lineno = [lineno-CONTEXT, 0].max
82
+ frame.pre_context_lineno = [lineno - CONTEXT, 0].max
83
83
  frame.pre_context = lines[frame.pre_context_lineno...lineno]
84
84
  frame.context_line = lines[lineno].chomp
85
- frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
86
- frame.post_context = lines[lineno+1..frame.post_context_lineno]
85
+ frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
86
+ frame.post_context = lines[lineno + 1..frame.post_context_lineno]
87
87
  rescue
88
88
  end
89
89
 
@@ -93,7 +93,11 @@ module Rack
93
93
  end
94
94
  }.compact
95
95
 
96
- TEMPLATE.result(binding)
96
+ template.result(binding)
97
+ end
98
+
99
+ def template
100
+ TEMPLATE
97
101
  end
98
102
 
99
103
  def h(obj) # :nodoc:
@@ -107,8 +111,8 @@ module Rack
107
111
 
108
112
  # :stopdoc:
109
113
 
110
- # adapted from Django <djangoproject.com>
111
- # Copyright (c) 2005, the Lawrence Journal-World
114
+ # adapted from Django <www.djangoproject.com>
115
+ # Copyright (c) Django Software Foundation and individual contributors.
112
116
  # Used under the modified BSD license:
113
117
  # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
114
118
  TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, ''))
@@ -307,7 +311,7 @@ module Rack
307
311
  <% end %>
308
312
 
309
313
  <h3 id="post-info">POST</h3>
310
- <% if req.POST and not req.POST.empty? %>
314
+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
311
315
  <table class="req">
312
316
  <thead>
313
317
  <tr>
@@ -325,7 +329,7 @@ module Rack
325
329
  </tbody>
326
330
  </table>
327
331
  <% else %>
328
- <p>No POST data.</p>
332
+ <p><%= no_post_data || "No POST data" %>.</p>
329
333
  <% end %>
330
334
 
331
335
 
@@ -363,7 +367,7 @@ module Rack
363
367
  <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
364
368
  <tr>
365
369
  <td><%=h key %></td>
366
- <td class="code"><div><%=h val %></div></td>
370
+ <td class="code"><div><%=h val.inspect %></div></td>
367
371
  </tr>
368
372
  <% } %>
369
373
  </tbody>
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
- require 'rack/request'
3
- require 'rack/utils'
4
4
 
5
5
  module Rack
6
6
  # Rack::ShowStatus catches all empty responses and replaces them
@@ -18,19 +18,19 @@ module Rack
18
18
 
19
19
  def call(env)
20
20
  status, headers, body = @app.call(env)
21
- headers = Utils::HeaderHash.new(headers)
21
+ headers = Utils::HeaderHash[headers]
22
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
23
23
 
24
24
  # client or server error, or explicit message
25
25
  if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
26
- # This double assignment is to prevent an "unused variable" warning on
27
- # 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.
28
28
  req = req = Rack::Request.new(env)
29
29
 
30
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
31
31
 
32
- # This double assignment is to prevent an "unused variable" warning on
33
- # 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.
34
34
  detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
35
35
 
36
36
  body = @template.result(binding)
@@ -52,8 +52,8 @@ module Rack
52
52
 
53
53
  # :stopdoc:
54
54
 
55
- # adapted from Django <djangoproject.com>
56
- # Copyright (c) 2005, the Lawrence Journal-World
55
+ # adapted from Django <www.djangoproject.com>
56
+ # Copyright (c) Django Software Foundation and individual contributors.
57
57
  # Used under the modified BSD license:
58
58
  # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
59
59
  TEMPLATE = <<'HTML'
@@ -1,11 +1,10 @@
1
- require "rack/file"
2
- require "rack/utils"
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rack
5
4
 
6
5
  # The Rack::Static middleware intercepts requests for static files
7
6
  # (javascript files, images, stylesheets, etc) based on the url prefixes or
8
- # route mappings passed in the options, and serves them using a Rack::File
7
+ # route mappings passed in the options, and serves them using a Rack::Files
9
8
  # object. This allows a Rack stack to serve both static and dynamic content.
10
9
  #
11
10
  # Examples:
@@ -15,6 +14,11 @@ module Rack
15
14
  #
16
15
  # use Rack::Static, :urls => ["/media"]
17
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
+ #
18
22
  # Serve all requests beginning with /css or /images from the folder "public"
19
23
  # in the current directory (ie public/css/* and public/images/*):
20
24
  #
@@ -82,24 +86,26 @@ module Rack
82
86
  # ]
83
87
  #
84
88
  class Static
89
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
85
90
 
86
- def initialize(app, options={})
91
+ def initialize(app, options = {})
87
92
  @app = app
88
93
  @urls = options[:urls] || ["/favicon.ico"]
89
94
  @index = options[:index]
90
95
  @gzip = options[:gzip]
96
+ @cascade = options[:cascade]
91
97
  root = options[:root] || Dir.pwd
92
98
 
93
99
  # HTTP Headers
94
100
  @header_rules = options[:header_rules] || []
95
101
  # Allow for legacy :cache_control option while prioritizing global header_rules setting
96
- @header_rules.unshift([:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
102
+ @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
97
103
 
98
- @file_server = Rack::File.new(root)
104
+ @file_server = Rack::Files.new(root)
99
105
  end
100
106
 
101
107
  def add_index_root?(path)
102
- @index && path =~ /\/$/
108
+ @index && route_file(path) && path.end_with?('/')
103
109
  end
104
110
 
105
111
  def overwrite_file_path(path)
@@ -120,7 +126,7 @@ module Rack
120
126
  if can_serve(path)
121
127
  if overwrite_file_path(path)
122
128
  env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
123
- elsif @gzip && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
129
+ elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
124
130
  path = env[PATH_INFO]
125
131
  env[PATH_INFO] += '.gz'
126
132
  response = @file_server.call(env)
@@ -128,6 +134,8 @@ module Rack
128
134
 
129
135
  if response[0] == 404
130
136
  response = nil
137
+ elsif response[0] == 304
138
+ # Do nothing, leave headers as is
131
139
  else
132
140
  if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
133
141
  response[1][CONTENT_TYPE] = mime_type
@@ -139,6 +147,10 @@ module Rack
139
147
  path = env[PATH_INFO]
140
148
  response ||= @file_server.call(env)
141
149
 
150
+ if @cascade && response[0] == 404
151
+ return @app.call(env)
152
+ end
153
+
142
154
  headers = response[1]
143
155
  applicable_rules(path).each do |rule, new_headers|
144
156
  new_headers.each { |field, content| headers[field] = content }
@@ -157,14 +169,14 @@ module Rack
157
169
  when :all
158
170
  true
159
171
  when :fonts
160
- path =~ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/
172
+ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
161
173
  when String
162
174
  path = ::Rack::Utils.unescape(path)
163
175
  path.start_with?(rule) || path.start_with?('/' + rule)
164
176
  when Array
165
- path =~ /\.(#{rule.join('|')})\z/
177
+ /\.(#{rule.join('|')})\z/.match?(path)
166
178
  when Regexp
167
- path =~ rule
179
+ rule.match?(path)
168
180
  else
169
181
  false
170
182
  end
@@ -1,4 +1,4 @@
1
- require 'rack/body_proxy'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
4
 
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
1
5
  module Rack
2
6
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
7
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -12,17 +16,16 @@ module Rack
12
16
  # first, since they are most specific.
13
17
 
14
18
  class URLMap
15
- NEGATIVE_INFINITY = -1.0 / 0.0
16
- INFINITY = 1.0 / 0.0
17
-
18
19
  def initialize(map = {})
19
20
  remap(map)
20
21
  end
21
22
 
22
23
  def remap(map)
24
+ @known_hosts = Set[]
23
25
  @mapping = map.map { |location, app|
24
26
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
25
27
  host, location = $1, $2
28
+ @known_hosts << host
26
29
  else
27
30
  host = nil
28
31
  end
@@ -36,7 +39,7 @@ module Rack
36
39
 
37
40
  [host, location, match, app]
38
41
  }.sort_by do |(host, location, _, _)|
39
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
40
43
  end
41
44
  end
42
45
 
@@ -50,10 +53,13 @@ module Rack
50
53
  is_same_server = casecmp?(http_host, server_name) ||
51
54
  casecmp?(http_host, "#{server_name}:#{server_port}")
52
55
 
56
+ is_host_known = @known_hosts.include? http_host
57
+
53
58
  @mapping.each do |host, location, match, app|
54
59
  unless casecmp?(http_host, host) \
55
60
  || casecmp?(server_name, host) \
56
- || (!host && is_same_server)
61
+ || (!host && is_same_server) \
62
+ || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
57
63
  next
58
64
  end
59
65
 
@@ -68,7 +74,7 @@ module Rack
68
74
  return app.call(env)
69
75
  end
70
76
 
71
- [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
77
+ [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
72
78
 
73
79
  ensure
74
80
  env[PATH_INFO] = path
@@ -1,16 +1,21 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'uri'
3
5
  require 'fileutils'
4
6
  require 'set'
5
7
  require 'tempfile'
6
- require 'rack/query_parser'
7
8
  require 'time'
8
9
 
10
+ require_relative 'query_parser'
11
+
9
12
  module Rack
10
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
11
14
  # applications adopted from all kinds of Ruby libraries.
12
15
 
13
16
  module Utils
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
18
+
14
19
  ParameterTypeError = QueryParser::ParameterTypeError
15
20
  InvalidParameterError = QueryParser::InvalidParameterError
16
21
  DEFAULT_SEP = QueryParser::DEFAULT_SEP
@@ -24,33 +29,30 @@ module Rack
24
29
  # This helps prevent a rogue client from flooding a Request.
25
30
  self.default_query_parser = QueryParser.make_default(65536, 100)
26
31
 
32
+ module_function
33
+
27
34
  # URI escapes. (CGI style space to +)
28
35
  def escape(s)
29
36
  URI.encode_www_form_component(s)
30
37
  end
31
- module_function :escape
32
38
 
33
39
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
34
40
  # true URI escaping.
35
41
  def escape_path(s)
36
42
  ::URI::DEFAULT_PARSER.escape s
37
43
  end
38
- module_function :escape_path
39
44
 
40
45
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
41
46
  # unescaping query parameters or form components.
42
47
  def unescape_path(s)
43
48
  ::URI::DEFAULT_PARSER.unescape s
44
49
  end
45
- module_function :unescape_path
46
-
47
50
 
48
51
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
49
52
  # target encoding of the string returned, and it defaults to UTF-8
50
53
  def unescape(s, encoding = Encoding::UTF_8)
51
54
  URI.decode_www_form_component(s, encoding)
52
55
  end
53
- module_function :unescape
54
56
 
55
57
  class << self
56
58
  attr_accessor :multipart_part_limit
@@ -82,21 +84,20 @@ module Rack
82
84
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
85
  end
84
86
  else
87
+ # :nocov:
85
88
  def clock_time
86
89
  Time.now.to_f
87
90
  end
91
+ # :nocov:
88
92
  end
89
- module_function :clock_time
90
93
 
91
94
  def parse_query(qs, d = nil, &unescaper)
92
95
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
93
96
  end
94
- module_function :parse_query
95
97
 
96
98
  def parse_nested_query(qs, d = nil)
97
99
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
98
100
  end
99
- module_function :parse_nested_query
100
101
 
101
102
  def build_query(params)
102
103
  params.map { |k, v|
@@ -107,7 +108,6 @@ module Rack
107
108
  end
108
109
  }.join("&")
109
110
  end
110
- module_function :build_query
111
111
 
112
112
  def build_nested_query(value, prefix = nil)
113
113
  case value
@@ -118,7 +118,7 @@ module Rack
118
118
  when Hash
119
119
  value.map { |k, v|
120
120
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121
- }.reject(&:empty?).join('&')
121
+ }.delete_if(&:empty?).join('&')
122
122
  when nil
123
123
  prefix
124
124
  else
@@ -126,20 +126,22 @@ module Rack
126
126
  "#{prefix}=#{escape(value)}"
127
127
  end
128
128
  end
129
- module_function :build_nested_query
130
129
 
131
130
  def q_values(q_value_header)
132
131
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
133
132
  value, parameters = part.split(/\s*;\s*/, 2)
134
133
  quality = 1.0
135
- if md = /\Aq=([\d.]+)/.match(parameters)
134
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
136
135
  quality = md[1].to_f
137
136
  end
138
137
  [value, quality]
139
138
  end
140
139
  end
141
- module_function :q_values
142
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.
143
145
  def best_q_match(q_value_header, available_mimes)
144
146
  values = q_values(q_value_header)
145
147
 
@@ -152,7 +154,6 @@ module Rack
152
154
  end.last
153
155
  matches && matches.first
154
156
  end
155
- module_function :best_q_match
156
157
 
157
158
  ESCAPE_HTML = {
158
159
  "&" => "&amp;",
@@ -169,51 +170,55 @@ module Rack
169
170
  def escape_html(string)
170
171
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
171
172
  end
172
- module_function :escape_html
173
173
 
174
174
  def select_best_encoding(available_encodings, accept_encoding)
175
175
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
176
176
 
177
- expanded_accept_encoding =
178
- accept_encoding.map { |m, q|
179
- if m == "*"
180
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
181
- else
182
- [[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]
183
185
  end
184
- }.inject([]) { |mem, list|
185
- mem + list
186
- }
186
+ else
187
+ expanded_accept_encoding << [m, q, preference]
188
+ end
189
+ end
187
190
 
188
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
191
+ encoding_candidates = expanded_accept_encoding
192
+ .sort_by { |_, q, p| [-q, p] }
193
+ .map!(&:first)
189
194
 
190
195
  unless encoding_candidates.include?("identity")
191
196
  encoding_candidates.push("identity")
192
197
  end
193
198
 
194
- expanded_accept_encoding.each { |m, q|
199
+ expanded_accept_encoding.each do |m, q|
195
200
  encoding_candidates.delete(m) if q == 0.0
196
- }
201
+ end
197
202
 
198
- return (encoding_candidates & available_encodings)[0]
203
+ (encoding_candidates & available_encodings)[0]
199
204
  end
200
- module_function :select_best_encoding
201
205
 
202
206
  def parse_cookies(env)
203
207
  parse_cookies_header env[HTTP_COOKIE]
204
208
  end
205
- module_function :parse_cookies
206
209
 
207
210
  def parse_cookies_header(header)
208
- # According to RFC 2109:
209
- # If multiple cookies satisfy the criteria above, they are ordered in
210
- # the Cookie header such that those with more specific Path attributes
211
- # precede those with less specific. Ordering with respect to other
212
- # attributes (e.g., Domain) is unspecified.
213
- cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
214
- cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
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
+ return {} unless header
216
+ header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
217
+ next if cookie.empty?
218
+ key, value = cookie.split('=', 2)
219
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
220
+ end
215
221
  end
216
- module_function :parse_cookies_header
217
222
 
218
223
  def add_cookie_to_header(header, key, value)
219
224
  case value
@@ -221,41 +226,19 @@ module Rack
221
226
  domain = "; domain=#{value[:domain]}" if value[:domain]
222
227
  path = "; path=#{value[:path]}" if value[:path]
223
228
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
224
- # There is an RFC mess in the area of date formatting for Cookies. Not
225
- # only are there contradicting RFCs and examples within RFC text, but
226
- # there are also numerous conflicting names of fields and partially
227
- # cross-applicable specifications.
228
- #
229
- # These are best described in RFC 2616 3.3.1. This RFC text also
230
- # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
231
- # fixed length format with space-date delimited fields.
232
- #
233
- # See also RFC 1123 section 5.2.14.
234
- #
235
- # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
236
- # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
237
- # the space delimited format. These formats are compliant with RFC 2822.
238
- #
239
- # For reference, all involved RFCs are:
240
- # RFC 822
241
- # RFC 1123
242
- # RFC 2109
243
- # RFC 2616
244
- # RFC 2822
245
- # RFC 2965
246
- # RFC 6265
247
- expires = "; expires=" +
248
- rfc2822(value[:expires].clone.gmtime) if value[:expires]
229
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
249
230
  secure = "; secure" if value[:secure]
250
231
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
251
232
  same_site =
252
233
  case value[:same_site]
253
234
  when false, nil
254
235
  nil
236
+ when :none, 'None', :None
237
+ '; SameSite=None'
255
238
  when :lax, 'Lax', :Lax
256
- '; SameSite=Lax'.freeze
239
+ '; SameSite=Lax'
257
240
  when true, :strict, 'Strict', :Strict
258
- '; SameSite=Strict'.freeze
241
+ '; SameSite=Strict'
259
242
  else
260
243
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
261
244
  end
@@ -277,13 +260,11 @@ module Rack
277
260
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
278
261
  end
279
262
  end
280
- module_function :add_cookie_to_header
281
263
 
282
264
  def set_cookie_header!(header, key, value)
283
265
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
284
266
  nil
285
267
  end
286
- module_function :set_cookie_header!
287
268
 
288
269
  def make_delete_cookie_header(header, key, value)
289
270
  case header
@@ -295,25 +276,30 @@ module Rack
295
276
  cookies = header
296
277
  end
297
278
 
298
- cookies.reject! { |cookie|
299
- if value[:domain]
300
- cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
301
- elsif value[:path]
302
- cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
303
- else
304
- cookie =~ /\A#{escape(key)}=/
305
- end
306
- }
279
+ key = escape(key)
280
+ domain = value[:domain]
281
+ path = value[:path]
282
+ regexp = if domain
283
+ if path
284
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
285
+ else
286
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
287
+ end
288
+ elsif path
289
+ /\A#{key}=.*path=#{path}(?:;|$)/
290
+ else
291
+ /\A#{key}=/
292
+ end
293
+
294
+ cookies.reject! { |cookie| regexp.match? cookie }
307
295
 
308
296
  cookies.join("\n")
309
297
  end
310
- module_function :make_delete_cookie_header
311
298
 
312
299
  def delete_cookie_header!(header, key, value = {})
313
300
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
314
301
  nil
315
302
  end
316
- module_function :delete_cookie_header!
317
303
 
318
304
  # Adds a cookie that will *remove* a cookie from the client. Hence the
319
305
  # strange method name.
@@ -321,17 +307,15 @@ module Rack
321
307
  new_header = make_delete_cookie_header(header, key, value)
322
308
 
323
309
  add_cookie_to_header(new_header, key,
324
- {:value => '', :path => nil, :domain => nil,
325
- :max_age => '0',
326
- :expires => Time.at(0) }.merge(value))
310
+ { value: '', path: nil, domain: nil,
311
+ max_age: '0',
312
+ expires: Time.at(0) }.merge(value))
327
313
 
328
314
  end
329
- module_function :add_remove_cookie_to_header
330
315
 
331
316
  def rfc2822(time)
332
317
  time.rfc2822
333
318
  end
334
- module_function :rfc2822
335
319
 
336
320
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
337
321
  # of '% %b %Y'.
@@ -347,7 +331,6 @@ module Rack
347
331
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
348
332
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
349
333
  end
350
- module_function :rfc2109
351
334
 
352
335
  # Parses the "Range:" header, if present, into an array of Range objects.
353
336
  # Returns nil if the header is missing or syntactically invalid.
@@ -356,7 +339,6 @@ module Rack
356
339
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
357
340
  get_byte_ranges env['HTTP_RANGE'], size
358
341
  end
359
- module_function :byte_ranges
360
342
 
361
343
  def get_byte_ranges(http_range, size)
362
344
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -364,7 +346,7 @@ module Rack
364
346
  ranges = []
365
347
  $1.split(/,\s*/).each do |range_spec|
366
348
  return nil unless range_spec =~ /(\d*)-(\d*)/
367
- r0,r1 = $1, $2
349
+ r0, r1 = $1, $2
368
350
  if r0.empty?
369
351
  return nil if r1.empty?
370
352
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -378,14 +360,13 @@ module Rack
378
360
  else
379
361
  r1 = r1.to_i
380
362
  return nil if r1 < r0 # backwards range is syntactically invalid
381
- r1 = size-1 if r1 >= size
363
+ r1 = size - 1 if r1 >= size
382
364
  end
383
365
  end
384
366
  ranges << (r0..r1) if r0 <= r1
385
367
  end
386
368
  ranges
387
369
  end
388
- module_function :get_byte_ranges
389
370
 
390
371
  # Constant time string comparison.
391
372
  #
@@ -399,10 +380,9 @@ module Rack
399
380
  l = a.unpack("C*")
400
381
 
401
382
  r, i = 0, -1
402
- b.each_byte { |v| r |= v ^ l[i+=1] }
383
+ b.each_byte { |v| r |= v ^ l[i += 1] }
403
384
  r == 0
404
385
  end
405
- module_function :secure_compare
406
386
 
407
387
  # Context allows the use of a compatible middleware at different points
408
388
  # in a request handling stack. A compatible middleware must define
@@ -425,19 +405,25 @@ module Rack
425
405
  self.class.new(@for, app)
426
406
  end
427
407
 
428
- def context(env, app=@app)
408
+ def context(env, app = @app)
429
409
  recontext(app).call(env)
430
410
  end
431
411
  end
432
412
 
433
413
  # A case-insensitive Hash that preserves the original case of a
434
414
  # header when set.
435
- class HeaderHash < Hash
436
- def self.new(hash={})
437
- HeaderHash === hash ? hash : super(hash)
415
+ #
416
+ # @api private
417
+ class HeaderHash < Hash # :nodoc:
418
+ def self.[](headers)
419
+ if headers.is_a?(HeaderHash) && !headers.frozen?
420
+ return headers
421
+ else
422
+ return self.new(headers)
423
+ end
438
424
  end
439
425
 
440
- def initialize(hash={})
426
+ def initialize(hash = {})
441
427
  super()
442
428
  @names = {}
443
429
  hash.each { |k, v| self[k] = v }
@@ -449,6 +435,12 @@ module Rack
449
435
  @names = other.names.dup
450
436
  end
451
437
 
438
+ # on clear, we need to clear @names hash
439
+ def clear
440
+ super
441
+ @names.clear
442
+ end
443
+
452
444
  def each
453
445
  super do |k, v|
454
446
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -457,7 +449,7 @@ module Rack
457
449
 
458
450
  def to_hash
459
451
  hash = {}
460
- each { |k,v| hash[k] = v }
452
+ each { |k, v| hash[k] = v }
461
453
  hash
462
454
  end
463
455
 
@@ -510,13 +502,14 @@ module Rack
510
502
 
511
503
  # Every standard HTTP code mapped to the appropriate message.
512
504
  # Generated with:
513
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
514
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
515
- # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
505
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
506
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
507
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
516
508
  HTTP_STATUS_CODES = {
517
509
  100 => 'Continue',
518
510
  101 => 'Switching Protocols',
519
511
  102 => 'Processing',
512
+ 103 => 'Early Hints',
520
513
  200 => 'OK',
521
514
  201 => 'Created',
522
515
  202 => 'Accepted',
@@ -533,6 +526,7 @@ module Rack
533
526
  303 => 'See Other',
534
527
  304 => 'Not Modified',
535
528
  305 => 'Use Proxy',
529
+ 306 => '(Unused)',
536
530
  307 => 'Temporary Redirect',
537
531
  308 => 'Permanent Redirect',
538
532
  400 => 'Bad Request',
@@ -557,6 +551,7 @@ module Rack
557
551
  422 => 'Unprocessable Entity',
558
552
  423 => 'Locked',
559
553
  424 => 'Failed Dependency',
554
+ 425 => 'Too Early',
560
555
  426 => 'Upgrade Required',
561
556
  428 => 'Precondition Required',
562
557
  429 => 'Too Many Requests',
@@ -571,12 +566,13 @@ module Rack
571
566
  506 => 'Variant Also Negotiates',
572
567
  507 => 'Insufficient Storage',
573
568
  508 => 'Loop Detected',
569
+ 509 => 'Bandwidth Limit Exceeded',
574
570
  510 => 'Not Extended',
575
571
  511 => 'Network Authentication Required'
576
572
  }
577
573
 
578
574
  # Responses with HTTP status codes that should not have an entity body
579
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
575
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
580
576
 
581
577
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
582
578
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -584,12 +580,11 @@ module Rack
584
580
 
585
581
  def status_code(status)
586
582
  if status.is_a?(Symbol)
587
- SYMBOL_TO_STATUS_CODE[status] || 500
583
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
588
584
  else
589
585
  status.to_i
590
586
  end
591
587
  end
592
- module_function :status_code
593
588
 
594
589
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
595
590
 
@@ -603,18 +598,16 @@ module Rack
603
598
  part == '..' ? clean.pop : clean << part
604
599
  end
605
600
 
606
- clean.unshift '/' if parts.empty? || parts.first.empty?
607
-
608
- ::File.join(*clean)
601
+ clean_path = clean.join(::File::SEPARATOR)
602
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
603
+ clean_path
609
604
  end
610
- module_function :clean_path_info
611
605
 
612
- NULL_BYTE = "\0".freeze
606
+ NULL_BYTE = "\0"
613
607
 
614
608
  def valid_path?(path)
615
609
  path.valid_encoding? && !path.include?(NULL_BYTE)
616
610
  end
617
- module_function :valid_path?
618
611
 
619
612
  end
620
613
  end