rack 2.0.8 → 2.1.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +77 -117
  5. data/Rakefile +25 -18
  6. data/SPEC +3 -4
  7. data/bin/rackup +1 -0
  8. data/example/lobster.ru +2 -0
  9. data/example/protectedlobster.rb +3 -1
  10. data/example/protectedlobster.ru +2 -0
  11. data/lib/rack.rb +63 -60
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +2 -0
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +2 -0
  19. data/lib/rack/body_proxy.rb +3 -6
  20. data/lib/rack/builder.rb +39 -15
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +29 -6
  23. data/lib/rack/common_logger.rb +9 -8
  24. data/lib/rack/conditional_get.rb +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +3 -1
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +32 -17
  30. data/lib/rack/directory.rb +17 -14
  31. data/lib/rack/etag.rb +3 -1
  32. data/lib/rack/events.rb +5 -3
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler.rb +7 -2
  36. data/lib/rack/handler/cgi.rb +3 -1
  37. data/lib/rack/handler/fastcgi.rb +4 -2
  38. data/lib/rack/handler/lsws.rb +3 -1
  39. data/lib/rack/handler/scgi.rb +9 -6
  40. data/lib/rack/handler/thin.rb +3 -1
  41. data/lib/rack/handler/webrick.rb +4 -2
  42. data/lib/rack/head.rb +2 -0
  43. data/lib/rack/lint.rb +14 -11
  44. data/lib/rack/lobster.rb +7 -5
  45. data/lib/rack/lock.rb +2 -0
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +4 -2
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +74 -15
  51. data/lib/rack/multipart.rb +5 -3
  52. data/lib/rack/multipart/generator.rb +6 -7
  53. data/lib/rack/multipart/parser.rb +51 -45
  54. data/lib/rack/multipart/uploaded_file.rb +2 -0
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +51 -25
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +79 -26
  60. data/lib/rack/response.rb +71 -31
  61. data/lib/rack/rewindable_input.rb +4 -2
  62. data/lib/rack/runtime.rb +4 -2
  63. data/lib/rack/sendfile.rb +15 -8
  64. data/lib/rack/server.rb +88 -18
  65. data/lib/rack/session/abstract/id.rb +32 -22
  66. data/lib/rack/session/cookie.rb +10 -9
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +4 -2
  69. data/lib/rack/show_exceptions.rb +15 -9
  70. data/lib/rack/show_status.rb +4 -2
  71. data/lib/rack/static.rb +15 -10
  72. data/lib/rack/tempfile_reaper.rb +2 -0
  73. data/lib/rack/urlmap.rb +11 -2
  74. data/lib/rack/utils.rb +55 -70
  75. data/rack.gemspec +17 -7
  76. metadata +29 -169
  77. data/HISTORY.md +0 -505
  78. data/test/builder/an_underscore_app.rb +0 -5
  79. data/test/builder/anything.rb +0 -5
  80. data/test/builder/comment.ru +0 -4
  81. data/test/builder/end.ru +0 -5
  82. data/test/builder/line.ru +0 -1
  83. data/test/builder/options.ru +0 -2
  84. data/test/cgi/assets/folder/test.js +0 -1
  85. data/test/cgi/assets/fonts/font.eot +0 -1
  86. data/test/cgi/assets/images/image.png +0 -1
  87. data/test/cgi/assets/index.html +0 -1
  88. data/test/cgi/assets/javascripts/app.js +0 -1
  89. data/test/cgi/assets/stylesheets/app.css +0 -1
  90. data/test/cgi/lighttpd.conf +0 -26
  91. data/test/cgi/rackup_stub.rb +0 -6
  92. data/test/cgi/sample_rackup.ru +0 -5
  93. data/test/cgi/test +0 -9
  94. data/test/cgi/test+directory/test+file +0 -1
  95. data/test/cgi/test.fcgi +0 -9
  96. data/test/cgi/test.gz +0 -0
  97. data/test/cgi/test.ru +0 -5
  98. data/test/gemloader.rb +0 -10
  99. data/test/helper.rb +0 -34
  100. data/test/multipart/bad_robots +0 -259
  101. data/test/multipart/binary +0 -0
  102. data/test/multipart/content_type_and_no_filename +0 -6
  103. data/test/multipart/empty +0 -10
  104. data/test/multipart/fail_16384_nofile +0 -814
  105. data/test/multipart/file1.txt +0 -1
  106. data/test/multipart/filename_and_modification_param +0 -7
  107. data/test/multipart/filename_and_no_name +0 -6
  108. data/test/multipart/filename_with_encoded_words +0 -7
  109. data/test/multipart/filename_with_escaped_quotes +0 -6
  110. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  111. data/test/multipart/filename_with_null_byte +0 -7
  112. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  113. data/test/multipart/filename_with_single_quote +0 -7
  114. data/test/multipart/filename_with_unescaped_percentages +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  116. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  117. data/test/multipart/filename_with_unescaped_quotes +0 -6
  118. data/test/multipart/ie +0 -6
  119. data/test/multipart/invalid_character +0 -6
  120. data/test/multipart/mixed_files +0 -21
  121. data/test/multipart/nested +0 -10
  122. data/test/multipart/none +0 -9
  123. data/test/multipart/quoted +0 -15
  124. data/test/multipart/rack-logo.png +0 -0
  125. data/test/multipart/semicolon +0 -6
  126. data/test/multipart/text +0 -15
  127. data/test/multipart/three_files_three_fields +0 -31
  128. data/test/multipart/unity3d_wwwform +0 -11
  129. data/test/multipart/webkit +0 -32
  130. data/test/rackup/config.ru +0 -31
  131. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  132. data/test/spec_auth_basic.rb +0 -89
  133. data/test/spec_auth_digest.rb +0 -260
  134. data/test/spec_body_proxy.rb +0 -85
  135. data/test/spec_builder.rb +0 -233
  136. data/test/spec_cascade.rb +0 -63
  137. data/test/spec_cgi.rb +0 -84
  138. data/test/spec_chunked.rb +0 -103
  139. data/test/spec_common_logger.rb +0 -95
  140. data/test/spec_conditional_get.rb +0 -103
  141. data/test/spec_config.rb +0 -23
  142. data/test/spec_content_length.rb +0 -86
  143. data/test/spec_content_type.rb +0 -46
  144. data/test/spec_deflater.rb +0 -375
  145. data/test/spec_directory.rb +0 -148
  146. data/test/spec_etag.rb +0 -108
  147. data/test/spec_events.rb +0 -133
  148. data/test/spec_fastcgi.rb +0 -85
  149. data/test/spec_file.rb +0 -264
  150. data/test/spec_handler.rb +0 -57
  151. data/test/spec_head.rb +0 -46
  152. data/test/spec_lint.rb +0 -515
  153. data/test/spec_lobster.rb +0 -59
  154. data/test/spec_lock.rb +0 -204
  155. data/test/spec_logger.rb +0 -24
  156. data/test/spec_media_type.rb +0 -42
  157. data/test/spec_method_override.rb +0 -110
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -359
  160. data/test/spec_multipart.rb +0 -722
  161. data/test/spec_null_logger.rb +0 -21
  162. data/test/spec_recursive.rb +0 -75
  163. data/test/spec_request.rb +0 -1407
  164. data/test/spec_response.rb +0 -510
  165. data/test/spec_rewindable_input.rb +0 -128
  166. data/test/spec_runtime.rb +0 -50
  167. data/test/spec_sendfile.rb +0 -125
  168. data/test/spec_server.rb +0 -193
  169. data/test/spec_session_abstract_id.rb +0 -31
  170. data/test/spec_session_abstract_session_hash.rb +0 -45
  171. data/test/spec_session_cookie.rb +0 -442
  172. data/test/spec_session_memcache.rb +0 -357
  173. data/test/spec_session_pool.rb +0 -247
  174. data/test/spec_show_exceptions.rb +0 -93
  175. data/test/spec_show_status.rb +0 -104
  176. data/test/spec_static.rb +0 -184
  177. data/test/spec_tempfile_reaper.rb +0 -64
  178. data/test/spec_thin.rb +0 -96
  179. data/test/spec_urlmap.rb +0 -237
  180. data/test/spec_utils.rb +0 -742
  181. data/test/spec_version.rb +0 -11
  182. data/test/spec_webrick.rb +0 -206
  183. data/test/static/another/index.html +0 -1
  184. data/test/static/foo.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,99 +1,10 @@
1
- # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rack/session/abstract/id'
4
- require 'memcache'
3
+ require 'rack/session/dalli'
5
4
 
6
5
  module Rack
7
6
  module Session
8
- # Rack::Session::Memcache provides simple cookie based session management.
9
- # Session data is stored in memcached. The corresponding session key is
10
- # maintained in the cookie.
11
- # You may treat Session::Memcache as you would Session::Pool with the
12
- # following caveats.
13
- #
14
- # * Setting :expire_after to 0 would note to the Memcache server to hang
15
- # onto the session data until it would drop it according to it's own
16
- # specifications. However, the cookie sent to the client would expire
17
- # immediately.
18
- #
19
- # Note that memcache does drop data before it may be listed to expire. For
20
- # a full description of behaviour, please see memcache's documentation.
21
-
22
- class Memcache < Abstract::PersistedSecure
23
- attr_reader :mutex, :pool
24
-
25
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
26
- :namespace => 'rack:session',
27
- :memcache_server => 'localhost:11211'
28
-
29
- def initialize(app, options={})
30
- super
31
-
32
- @mutex = Mutex.new
33
- mserv = @default_options[:memcache_server]
34
- mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
35
-
36
- @pool = options[:cache] || MemCache.new(mserv, mopts)
37
- unless @pool.active? and @pool.servers.any?(&:alive?)
38
- raise 'No memcache servers'
39
- end
40
- end
41
-
42
- def generate_sid
43
- loop do
44
- sid = super
45
- break sid unless @pool.get(sid.private_id, true)
46
- end
47
- end
48
-
49
- def find_session(req, sid)
50
- with_lock(req) do
51
- unless sid and session = get_session_with_fallback(sid)
52
- sid, session = generate_sid, {}
53
- unless /^STORED/ =~ @pool.add(sid.private_id, session)
54
- raise "Session collision on '#{sid.inspect}'"
55
- end
56
- end
57
- [sid, session]
58
- end
59
- end
60
-
61
- def write_session(req, session_id, new_session, options)
62
- expiry = options[:expire_after]
63
- expiry = expiry.nil? ? 0 : expiry + 1
64
-
65
- with_lock(req) do
66
- @pool.set session_id.private_id, new_session, expiry
67
- session_id
68
- end
69
- end
70
-
71
- def delete_session(req, session_id, options)
72
- with_lock(req) do
73
- @pool.delete(session_id.public_id)
74
- @pool.delete(session_id.private_id)
75
- generate_sid unless options[:drop]
76
- end
77
- end
78
-
79
- def with_lock(req)
80
- @mutex.lock if req.multithread?
81
- yield
82
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
83
- if $VERBOSE
84
- warn "#{self} is unable to find memcached server."
85
- warn $!.inspect
86
- end
87
- raise
88
- ensure
89
- @mutex.unlock if @mutex.locked?
90
- end
91
-
92
- private
93
-
94
- def get_session_with_fallback(sid)
95
- @pool.get(sid.private_id) || @pool.get(sid.public_id)
96
- end
97
- end
7
+ warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead."
8
+ Memcache = Dalli
98
9
  end
99
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
4
  # THANKS:
3
5
  # apeiros, for session id generation, expiry setup, and threadiness
@@ -26,9 +28,9 @@ module Rack
26
28
 
27
29
  class Pool < Abstract::PersistedSecure
28
30
  attr_reader :mutex, :pool
29
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
31
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge drop: false
30
32
 
31
- def initialize(app, options={})
33
+ def initialize(app, options = {})
32
34
  super
33
35
  @pool = Hash.new
34
36
  @mutex = Mutex.new
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ostruct'
2
4
  require 'erb'
3
5
  require 'rack/request'
@@ -55,7 +57,7 @@ module Rack
55
57
  private :accepts_html?
56
58
 
57
59
  def dump_exception(exception)
58
- string = "#{exception.class}: #{exception.message}\n"
60
+ string = "#{exception.class}: #{exception.message}\n".dup
59
61
  string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
60
62
  string
61
63
  end
@@ -77,13 +79,13 @@ module Rack
77
79
  frame.function = $4
78
80
 
79
81
  begin
80
- lineno = frame.lineno-1
82
+ lineno = frame.lineno - 1
81
83
  lines = ::File.readlines(frame.filename)
82
- frame.pre_context_lineno = [lineno-CONTEXT, 0].max
84
+ frame.pre_context_lineno = [lineno - CONTEXT, 0].max
83
85
  frame.pre_context = lines[frame.pre_context_lineno...lineno]
84
86
  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]
87
+ frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
88
+ frame.post_context = lines[lineno + 1..frame.post_context_lineno]
87
89
  rescue
88
90
  end
89
91
 
@@ -93,7 +95,11 @@ module Rack
93
95
  end
94
96
  }.compact
95
97
 
96
- TEMPLATE.result(binding)
98
+ template.result(binding)
99
+ end
100
+
101
+ def template
102
+ TEMPLATE
97
103
  end
98
104
 
99
105
  def h(obj) # :nodoc:
@@ -107,8 +113,8 @@ module Rack
107
113
 
108
114
  # :stopdoc:
109
115
 
110
- # adapted from Django <djangoproject.com>
111
- # Copyright (c) 2005, the Lawrence Journal-World
116
+ # adapted from Django <www.djangoproject.com>
117
+ # Copyright (c) Django Software Foundation and individual contributors.
112
118
  # Used under the modified BSD license:
113
119
  # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
114
120
  TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, ''))
@@ -363,7 +369,7 @@ module Rack
363
369
  <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
364
370
  <tr>
365
371
  <td><%=h key %></td>
366
- <td class="code"><div><%=h val %></div></td>
372
+ <td class="code"><div><%=h val.inspect %></div></td>
367
373
  </tr>
368
374
  <% } %>
369
375
  </tbody>
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
  require 'rack/request'
3
5
  require 'rack/utils'
@@ -52,8 +54,8 @@ module Rack
52
54
 
53
55
  # :stopdoc:
54
56
 
55
- # adapted from Django <djangoproject.com>
56
- # Copyright (c) 2005, the Lawrence Journal-World
57
+ # adapted from Django <www.djangoproject.com>
58
+ # Copyright (c) Django Software Foundation and individual contributors.
57
59
  # Used under the modified BSD license:
58
60
  # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
59
61
  TEMPLATE = <<'HTML'
@@ -1,11 +1,15 @@
1
- require "rack/file"
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/files"
2
4
  require "rack/utils"
3
5
 
6
+ require_relative 'core_ext/regexp'
7
+
4
8
  module Rack
5
9
 
6
10
  # The Rack::Static middleware intercepts requests for static files
7
11
  # (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
12
+ # route mappings passed in the options, and serves them using a Rack::Files
9
13
  # object. This allows a Rack stack to serve both static and dynamic content.
10
14
  #
11
15
  # Examples:
@@ -82,8 +86,9 @@ module Rack
82
86
  # ]
83
87
  #
84
88
  class Static
89
+ using ::Rack::RegexpExtensions
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]
@@ -93,13 +98,13 @@ module Rack
93
98
  # HTTP Headers
94
99
  @header_rules = options[:header_rules] || []
95
100
  # 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]
101
+ @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
97
102
 
98
- @file_server = Rack::File.new(root)
103
+ @file_server = Rack::Files.new(root)
99
104
  end
100
105
 
101
106
  def add_index_root?(path)
102
- @index && path =~ /\/$/
107
+ @index && route_file(path) && path.end_with?('/')
103
108
  end
104
109
 
105
110
  def overwrite_file_path(path)
@@ -120,7 +125,7 @@ module Rack
120
125
  if can_serve(path)
121
126
  if overwrite_file_path(path)
122
127
  env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
123
- elsif @gzip && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
128
+ elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
124
129
  path = env[PATH_INFO]
125
130
  env[PATH_INFO] += '.gz'
126
131
  response = @file_server.call(env)
@@ -157,14 +162,14 @@ module Rack
157
162
  when :all
158
163
  true
159
164
  when :fonts
160
- path =~ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/
165
+ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
161
166
  when String
162
167
  path = ::Rack::Utils.unescape(path)
163
168
  path.start_with?(rule) || path.start_with?('/' + rule)
164
169
  when Array
165
- path =~ /\.(#{rule.join('|')})\z/
170
+ /\.(#{rule.join('|')})\z/.match?(path)
166
171
  when Regexp
167
- path =~ rule
172
+ rule.match?(path)
168
173
  else
169
174
  false
170
175
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/body_proxy'
2
4
 
3
5
  module Rack
@@ -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
@@ -20,9 +24,11 @@ module Rack
20
24
  end
21
25
 
22
26
  def remap(map)
27
+ @known_hosts = Set[]
23
28
  @mapping = map.map { |location, app|
24
29
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
25
30
  host, location = $1, $2
31
+ @known_hosts << host
26
32
  else
27
33
  host = nil
28
34
  end
@@ -50,10 +56,13 @@ module Rack
50
56
  is_same_server = casecmp?(http_host, server_name) ||
51
57
  casecmp?(http_host, "#{server_name}:#{server_port}")
52
58
 
59
+ is_host_known = @known_hosts.include? http_host
60
+
53
61
  @mapping.each do |host, location, match, app|
54
62
  unless casecmp?(http_host, host) \
55
63
  || casecmp?(server_name, host) \
56
- || (!host && is_same_server)
64
+ || (!host && is_same_server) \
65
+ || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
57
66
  next
58
67
  end
59
68
 
@@ -68,7 +77,7 @@ module Rack
68
77
  return app.call(env)
69
78
  end
70
79
 
71
- [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
80
+ [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
72
81
 
73
82
  ensure
74
83
  env[PATH_INFO] = path
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'uri'
3
5
  require 'fileutils'
4
6
  require 'set'
@@ -6,11 +8,15 @@ require 'tempfile'
6
8
  require 'rack/query_parser'
7
9
  require 'time'
8
10
 
11
+ require_relative 'core_ext/regexp'
12
+
9
13
  module Rack
10
14
  # Rack::Utils contains a grab-bag of useful methods for writing web
11
15
  # applications adopted from all kinds of Ruby libraries.
12
16
 
13
17
  module Utils
18
+ using ::Rack::RegexpExtensions
19
+
14
20
  ParameterTypeError = QueryParser::ParameterTypeError
15
21
  InvalidParameterError = QueryParser::InvalidParameterError
16
22
  DEFAULT_SEP = QueryParser::DEFAULT_SEP
@@ -118,7 +124,7 @@ module Rack
118
124
  when Hash
119
125
  value.map { |k, v|
120
126
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121
- }.reject(&:empty?).join('&')
127
+ }.delete_if(&:empty?).join('&')
122
128
  when nil
123
129
  prefix
124
130
  else
@@ -132,7 +138,7 @@ module Rack
132
138
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
133
139
  value, parameters = part.split(/\s*;\s*/, 2)
134
140
  quality = 1.0
135
- if md = /\Aq=([\d.]+)/.match(parameters)
141
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
136
142
  quality = md[1].to_f
137
143
  end
138
144
  [value, quality]
@@ -175,27 +181,26 @@ module Rack
175
181
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
176
182
 
177
183
  expanded_accept_encoding =
178
- accept_encoding.map { |m, q|
184
+ accept_encoding.each_with_object([]) do |(m, q), list|
179
185
  if m == "*"
180
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
186
+ (available_encodings - accept_encoding.map(&:first))
187
+ .each { |m2| list << [m2, q] }
181
188
  else
182
- [[m, q]]
189
+ list << [m, q]
183
190
  end
184
- }.inject([]) { |mem, list|
185
- mem + list
186
- }
191
+ end
187
192
 
188
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
193
+ encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.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
205
  module_function :select_best_encoding
201
206
 
@@ -211,7 +216,7 @@ module Rack
211
216
  # precede those with less specific. Ordering with respect to other
212
217
  # attributes (e.g., Domain) is unspecified.
213
218
  cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
214
- cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
219
+ cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
215
220
  end
216
221
  module_function :parse_cookies_header
217
222
 
@@ -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
@@ -295,15 +278,15 @@ module Rack
295
278
  cookies = header
296
279
  end
297
280
 
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
- }
281
+ regexp = if value[:domain]
282
+ /\A#{escape(key)}=.*domain=#{value[:domain]}/
283
+ elsif value[:path]
284
+ /\A#{escape(key)}=.*path=#{value[:path]}/
285
+ else
286
+ /\A#{escape(key)}=/
287
+ end
288
+
289
+ cookies.reject! { |cookie| regexp.match? cookie }
307
290
 
308
291
  cookies.join("\n")
309
292
  end
@@ -321,9 +304,9 @@ module Rack
321
304
  new_header = make_delete_cookie_header(header, key, value)
322
305
 
323
306
  add_cookie_to_header(new_header, key,
324
- {:value => '', :path => nil, :domain => nil,
325
- :max_age => '0',
326
- :expires => Time.at(0) }.merge(value))
307
+ { value: '', path: nil, domain: nil,
308
+ max_age: '0',
309
+ expires: Time.at(0) }.merge(value))
327
310
 
328
311
  end
329
312
  module_function :add_remove_cookie_to_header
@@ -364,7 +347,7 @@ module Rack
364
347
  ranges = []
365
348
  $1.split(/,\s*/).each do |range_spec|
366
349
  return nil unless range_spec =~ /(\d*)-(\d*)/
367
- r0,r1 = $1, $2
350
+ r0, r1 = $1, $2
368
351
  if r0.empty?
369
352
  return nil if r1.empty?
370
353
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -378,7 +361,7 @@ module Rack
378
361
  else
379
362
  r1 = r1.to_i
380
363
  return nil if r1 < r0 # backwards range is syntactically invalid
381
- r1 = size-1 if r1 >= size
364
+ r1 = size - 1 if r1 >= size
382
365
  end
383
366
  end
384
367
  ranges << (r0..r1) if r0 <= r1
@@ -399,7 +382,7 @@ module Rack
399
382
  l = a.unpack("C*")
400
383
 
401
384
  r, i = 0, -1
402
- b.each_byte { |v| r |= v ^ l[i+=1] }
385
+ b.each_byte { |v| r |= v ^ l[i += 1] }
403
386
  r == 0
404
387
  end
405
388
  module_function :secure_compare
@@ -425,19 +408,17 @@ module Rack
425
408
  self.class.new(@for, app)
426
409
  end
427
410
 
428
- def context(env, app=@app)
411
+ def context(env, app = @app)
429
412
  recontext(app).call(env)
430
413
  end
431
414
  end
432
415
 
433
416
  # A case-insensitive Hash that preserves the original case of a
434
417
  # header when set.
435
- class HeaderHash < Hash
436
- def self.new(hash={})
437
- HeaderHash === hash ? hash : super(hash)
438
- end
439
-
440
- def initialize(hash={})
418
+ #
419
+ # @api private
420
+ class HeaderHash < Hash # :nodoc:
421
+ def initialize(hash = {})
441
422
  super()
442
423
  @names = {}
443
424
  hash.each { |k, v| self[k] = v }
@@ -457,7 +438,7 @@ module Rack
457
438
 
458
439
  def to_hash
459
440
  hash = {}
460
- each { |k,v| hash[k] = v }
441
+ each { |k, v| hash[k] = v }
461
442
  hash
462
443
  end
463
444
 
@@ -510,13 +491,14 @@ module Rack
510
491
 
511
492
  # Every standard HTTP code mapped to the appropriate message.
512
493
  # 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,"'
494
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
495
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
496
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
516
497
  HTTP_STATUS_CODES = {
517
498
  100 => 'Continue',
518
499
  101 => 'Switching Protocols',
519
500
  102 => 'Processing',
501
+ 103 => 'Early Hints',
520
502
  200 => 'OK',
521
503
  201 => 'Created',
522
504
  202 => 'Accepted',
@@ -533,6 +515,7 @@ module Rack
533
515
  303 => 'See Other',
534
516
  304 => 'Not Modified',
535
517
  305 => 'Use Proxy',
518
+ 306 => '(Unused)',
536
519
  307 => 'Temporary Redirect',
537
520
  308 => 'Permanent Redirect',
538
521
  400 => 'Bad Request',
@@ -557,6 +540,7 @@ module Rack
557
540
  422 => 'Unprocessable Entity',
558
541
  423 => 'Locked',
559
542
  424 => 'Failed Dependency',
543
+ 425 => 'Too Early',
560
544
  426 => 'Upgrade Required',
561
545
  428 => 'Precondition Required',
562
546
  429 => 'Too Many Requests',
@@ -571,12 +555,13 @@ module Rack
571
555
  506 => 'Variant Also Negotiates',
572
556
  507 => 'Insufficient Storage',
573
557
  508 => 'Loop Detected',
558
+ 509 => 'Bandwidth Limit Exceeded',
574
559
  510 => 'Not Extended',
575
560
  511 => 'Network Authentication Required'
576
561
  }
577
562
 
578
563
  # 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)
564
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
580
565
 
581
566
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
582
567
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -584,7 +569,7 @@ module Rack
584
569
 
585
570
  def status_code(status)
586
571
  if status.is_a?(Symbol)
587
- SYMBOL_TO_STATUS_CODE[status] || 500
572
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
588
573
  else
589
574
  status.to_i
590
575
  end
@@ -605,11 +590,11 @@ module Rack
605
590
 
606
591
  clean.unshift '/' if parts.empty? || parts.first.empty?
607
592
 
608
- ::File.join(*clean)
593
+ ::File.join clean
609
594
  end
610
595
  module_function :clean_path_info
611
596
 
612
- NULL_BYTE = "\0".freeze
597
+ NULL_BYTE = "\0"
613
598
 
614
599
  def valid_path?(path)
615
600
  path.valid_encoding? && !path.include?(NULL_BYTE)