rack 2.0.9 → 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 (191) 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 +54 -56
  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 +34 -21
  67. data/lib/rack/session/cookie.rb +11 -12
  68. data/lib/rack/session/memcache.rb +4 -93
  69. data/lib/rack/session/pool.rb +5 -3
  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 +101 -110
  76. data/lib/rack/version.rb +29 -0
  77. data/rack.gemspec +40 -28
  78. metadata +39 -182
  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 -1407
  166. data/test/spec_response.rb +0 -528
  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 -357
  175. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  176. data/test/spec_session_pool.rb +0 -247
  177. data/test/spec_show_exceptions.rb +0 -93
  178. data/test/spec_show_status.rb +0 -104
  179. data/test/spec_static.rb +0 -184
  180. data/test/spec_tempfile_reaper.rb +0 -64
  181. data/test/spec_thin.rb +0 -96
  182. data/test/spec_urlmap.rb +0 -237
  183. data/test/spec_utils.rb +0 -742
  184. data/test/spec_version.rb +0 -11
  185. data/test/spec_webrick.rb +0 -206
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/foo.html +0 -1
  188. data/test/static/index.html +0 -1
  189. data/test/testrequest.rb +0 -78
  190. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  191. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -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,31 +226,7 @@ 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 =
@@ -253,11 +234,11 @@ module Rack
253
234
  when false, nil
254
235
  nil
255
236
  when :none, 'None', :None
256
- '; SameSite=None'.freeze
237
+ '; SameSite=None'
257
238
  when :lax, 'Lax', :Lax
258
- '; SameSite=Lax'.freeze
239
+ '; SameSite=Lax'
259
240
  when true, :strict, 'Strict', :Strict
260
- '; SameSite=Strict'.freeze
241
+ '; SameSite=Strict'
261
242
  else
262
243
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
263
244
  end
@@ -279,13 +260,11 @@ module Rack
279
260
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
280
261
  end
281
262
  end
282
- module_function :add_cookie_to_header
283
263
 
284
264
  def set_cookie_header!(header, key, value)
285
265
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
286
266
  nil
287
267
  end
288
- module_function :set_cookie_header!
289
268
 
290
269
  def make_delete_cookie_header(header, key, value)
291
270
  case header
@@ -297,25 +276,30 @@ module Rack
297
276
  cookies = header
298
277
  end
299
278
 
300
- cookies.reject! { |cookie|
301
- if value[:domain]
302
- cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
303
- elsif value[:path]
304
- cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
305
- else
306
- cookie =~ /\A#{escape(key)}=/
307
- end
308
- }
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 }
309
295
 
310
296
  cookies.join("\n")
311
297
  end
312
- module_function :make_delete_cookie_header
313
298
 
314
299
  def delete_cookie_header!(header, key, value = {})
315
300
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
316
301
  nil
317
302
  end
318
- module_function :delete_cookie_header!
319
303
 
320
304
  # Adds a cookie that will *remove* a cookie from the client. Hence the
321
305
  # strange method name.
@@ -323,17 +307,15 @@ module Rack
323
307
  new_header = make_delete_cookie_header(header, key, value)
324
308
 
325
309
  add_cookie_to_header(new_header, key,
326
- {:value => '', :path => nil, :domain => nil,
327
- :max_age => '0',
328
- :expires => Time.at(0) }.merge(value))
310
+ { value: '', path: nil, domain: nil,
311
+ max_age: '0',
312
+ expires: Time.at(0) }.merge(value))
329
313
 
330
314
  end
331
- module_function :add_remove_cookie_to_header
332
315
 
333
316
  def rfc2822(time)
334
317
  time.rfc2822
335
318
  end
336
- module_function :rfc2822
337
319
 
338
320
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
339
321
  # of '% %b %Y'.
@@ -349,7 +331,6 @@ module Rack
349
331
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
350
332
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
351
333
  end
352
- module_function :rfc2109
353
334
 
354
335
  # Parses the "Range:" header, if present, into an array of Range objects.
355
336
  # Returns nil if the header is missing or syntactically invalid.
@@ -358,7 +339,6 @@ module Rack
358
339
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
359
340
  get_byte_ranges env['HTTP_RANGE'], size
360
341
  end
361
- module_function :byte_ranges
362
342
 
363
343
  def get_byte_ranges(http_range, size)
364
344
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -366,7 +346,7 @@ module Rack
366
346
  ranges = []
367
347
  $1.split(/,\s*/).each do |range_spec|
368
348
  return nil unless range_spec =~ /(\d*)-(\d*)/
369
- r0,r1 = $1, $2
349
+ r0, r1 = $1, $2
370
350
  if r0.empty?
371
351
  return nil if r1.empty?
372
352
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -380,14 +360,13 @@ module Rack
380
360
  else
381
361
  r1 = r1.to_i
382
362
  return nil if r1 < r0 # backwards range is syntactically invalid
383
- r1 = size-1 if r1 >= size
363
+ r1 = size - 1 if r1 >= size
384
364
  end
385
365
  end
386
366
  ranges << (r0..r1) if r0 <= r1
387
367
  end
388
368
  ranges
389
369
  end
390
- module_function :get_byte_ranges
391
370
 
392
371
  # Constant time string comparison.
393
372
  #
@@ -401,10 +380,9 @@ module Rack
401
380
  l = a.unpack("C*")
402
381
 
403
382
  r, i = 0, -1
404
- b.each_byte { |v| r |= v ^ l[i+=1] }
383
+ b.each_byte { |v| r |= v ^ l[i += 1] }
405
384
  r == 0
406
385
  end
407
- module_function :secure_compare
408
386
 
409
387
  # Context allows the use of a compatible middleware at different points
410
388
  # in a request handling stack. A compatible middleware must define
@@ -427,19 +405,25 @@ module Rack
427
405
  self.class.new(@for, app)
428
406
  end
429
407
 
430
- def context(env, app=@app)
408
+ def context(env, app = @app)
431
409
  recontext(app).call(env)
432
410
  end
433
411
  end
434
412
 
435
413
  # A case-insensitive Hash that preserves the original case of a
436
414
  # header when set.
437
- class HeaderHash < Hash
438
- def self.new(hash={})
439
- 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
440
424
  end
441
425
 
442
- def initialize(hash={})
426
+ def initialize(hash = {})
443
427
  super()
444
428
  @names = {}
445
429
  hash.each { |k, v| self[k] = v }
@@ -451,6 +435,12 @@ module Rack
451
435
  @names = other.names.dup
452
436
  end
453
437
 
438
+ # on clear, we need to clear @names hash
439
+ def clear
440
+ super
441
+ @names.clear
442
+ end
443
+
454
444
  def each
455
445
  super do |k, v|
456
446
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -459,7 +449,7 @@ module Rack
459
449
 
460
450
  def to_hash
461
451
  hash = {}
462
- each { |k,v| hash[k] = v }
452
+ each { |k, v| hash[k] = v }
463
453
  hash
464
454
  end
465
455
 
@@ -512,13 +502,14 @@ module Rack
512
502
 
513
503
  # Every standard HTTP code mapped to the appropriate message.
514
504
  # Generated with:
515
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
516
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
517
- # 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,"'
518
508
  HTTP_STATUS_CODES = {
519
509
  100 => 'Continue',
520
510
  101 => 'Switching Protocols',
521
511
  102 => 'Processing',
512
+ 103 => 'Early Hints',
522
513
  200 => 'OK',
523
514
  201 => 'Created',
524
515
  202 => 'Accepted',
@@ -535,6 +526,7 @@ module Rack
535
526
  303 => 'See Other',
536
527
  304 => 'Not Modified',
537
528
  305 => 'Use Proxy',
529
+ 306 => '(Unused)',
538
530
  307 => 'Temporary Redirect',
539
531
  308 => 'Permanent Redirect',
540
532
  400 => 'Bad Request',
@@ -559,6 +551,7 @@ module Rack
559
551
  422 => 'Unprocessable Entity',
560
552
  423 => 'Locked',
561
553
  424 => 'Failed Dependency',
554
+ 425 => 'Too Early',
562
555
  426 => 'Upgrade Required',
563
556
  428 => 'Precondition Required',
564
557
  429 => 'Too Many Requests',
@@ -573,12 +566,13 @@ module Rack
573
566
  506 => 'Variant Also Negotiates',
574
567
  507 => 'Insufficient Storage',
575
568
  508 => 'Loop Detected',
569
+ 509 => 'Bandwidth Limit Exceeded',
576
570
  510 => 'Not Extended',
577
571
  511 => 'Network Authentication Required'
578
572
  }
579
573
 
580
574
  # Responses with HTTP status codes that should not have an entity body
581
- 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])]
582
576
 
583
577
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
584
578
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -586,12 +580,11 @@ module Rack
586
580
 
587
581
  def status_code(status)
588
582
  if status.is_a?(Symbol)
589
- SYMBOL_TO_STATUS_CODE[status] || 500
583
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
590
584
  else
591
585
  status.to_i
592
586
  end
593
587
  end
594
- module_function :status_code
595
588
 
596
589
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
597
590
 
@@ -605,18 +598,16 @@ module Rack
605
598
  part == '..' ? clean.pop : clean << part
606
599
  end
607
600
 
608
- clean.unshift '/' if parts.empty? || parts.first.empty?
609
-
610
- ::File.join(*clean)
601
+ clean_path = clean.join(::File::SEPARATOR)
602
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
603
+ clean_path
611
604
  end
612
- module_function :clean_path_info
613
605
 
614
- NULL_BYTE = "\0".freeze
606
+ NULL_BYTE = "\0"
615
607
 
616
608
  def valid_path?(path)
617
609
  path.valid_encoding? && !path.include?(NULL_BYTE)
618
610
  end
619
- module_function :valid_path?
620
611
 
621
612
  end
622
613
  end