rack 1.6.11 → 2.2.0

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +675 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +157 -163
  6. data/Rakefile +38 -32
  7. data/{SPEC → SPEC.rdoc} +41 -13
  8. data/bin/rackup +1 -0
  9. data/contrib/rack_logo.svg +164 -111
  10. data/example/lobster.ru +2 -0
  11. data/example/protectedlobster.rb +4 -2
  12. data/example/protectedlobster.ru +3 -1
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +6 -2
  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 +5 -4
  19. data/lib/rack/auth/digest/request.rb +6 -4
  20. data/lib/rack/body_proxy.rb +21 -15
  21. data/lib/rack/builder.rb +119 -26
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +70 -22
  24. data/lib/rack/common_logger.rb +80 -0
  25. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +9 -8
  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 +60 -70
  31. data/lib/rack/directory.rb +117 -85
  32. data/lib/rack/etag.rb +9 -7
  33. data/lib/rack/events.rb +153 -0
  34. data/lib/rack/file.rb +4 -149
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler/cgi.rb +17 -19
  37. data/lib/rack/handler/fastcgi.rb +17 -18
  38. data/lib/rack/handler/lsws.rb +14 -14
  39. data/lib/rack/handler/scgi.rb +22 -21
  40. data/lib/rack/handler/thin.rb +20 -11
  41. data/lib/rack/handler/webrick.rb +39 -32
  42. data/lib/rack/handler.rb +9 -26
  43. data/lib/rack/head.rb +16 -18
  44. data/lib/rack/lint.rb +110 -64
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +17 -11
  47. data/lib/rack/logger.rb +4 -2
  48. data/lib/rack/media_type.rb +43 -0
  49. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  50. data/lib/rack/mime.rb +27 -6
  51. data/lib/rack/mock.rb +124 -65
  52. data/lib/rack/multipart/generator.rb +20 -16
  53. data/lib/rack/multipart/parser.rb +273 -162
  54. data/lib/rack/multipart/uploaded_file.rb +15 -8
  55. data/lib/rack/multipart.rb +39 -8
  56. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  57. data/lib/rack/query_parser.rb +217 -0
  58. data/lib/rack/recursive.rb +11 -9
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +543 -305
  61. data/lib/rack/response.rb +244 -88
  62. data/lib/rack/rewindable_input.rb +5 -15
  63. data/lib/rack/runtime.rb +12 -18
  64. data/lib/rack/sendfile.rb +17 -15
  65. data/lib/rack/server.rb +125 -47
  66. data/lib/rack/session/abstract/id.rb +216 -93
  67. data/lib/rack/session/cookie.rb +47 -31
  68. data/lib/rack/session/memcache.rb +4 -87
  69. data/lib/rack/session/pool.rb +26 -17
  70. data/lib/rack/show_exceptions.rb +390 -0
  71. data/lib/rack/{showstatus.rb → show_status.rb} +8 -8
  72. data/lib/rack/static.rb +48 -11
  73. data/lib/rack/tempfile_reaper.rb +3 -3
  74. data/lib/rack/urlmap.rb +26 -19
  75. data/lib/rack/utils.rb +208 -294
  76. data/lib/rack/version.rb +29 -0
  77. data/lib/rack.rb +76 -33
  78. data/rack.gemspec +43 -30
  79. metadata +62 -183
  80. data/HISTORY.md +0 -375
  81. data/KNOWN-ISSUES +0 -44
  82. data/lib/rack/backports/uri/common_18.rb +0 -56
  83. data/lib/rack/backports/uri/common_192.rb +0 -52
  84. data/lib/rack/backports/uri/common_193.rb +0 -29
  85. data/lib/rack/commonlogger.rb +0 -72
  86. data/lib/rack/handler/evented_mongrel.rb +0 -8
  87. data/lib/rack/handler/mongrel.rb +0 -106
  88. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  89. data/lib/rack/showexceptions.rb +0 -387
  90. data/lib/rack/utils/okjson.rb +0 -600
  91. data/test/builder/anything.rb +0 -5
  92. data/test/builder/comment.ru +0 -4
  93. data/test/builder/end.ru +0 -5
  94. data/test/builder/line.ru +0 -1
  95. data/test/builder/options.ru +0 -2
  96. data/test/cgi/assets/folder/test.js +0 -1
  97. data/test/cgi/assets/fonts/font.eot +0 -1
  98. data/test/cgi/assets/images/image.png +0 -1
  99. data/test/cgi/assets/index.html +0 -1
  100. data/test/cgi/assets/javascripts/app.js +0 -1
  101. data/test/cgi/assets/stylesheets/app.css +0 -1
  102. data/test/cgi/lighttpd.conf +0 -26
  103. data/test/cgi/rackup_stub.rb +0 -6
  104. data/test/cgi/sample_rackup.ru +0 -5
  105. data/test/cgi/test +0 -9
  106. data/test/cgi/test+directory/test+file +0 -1
  107. data/test/cgi/test.fcgi +0 -8
  108. data/test/cgi/test.ru +0 -5
  109. data/test/gemloader.rb +0 -10
  110. data/test/multipart/bad_robots +0 -259
  111. data/test/multipart/binary +0 -0
  112. data/test/multipart/content_type_and_no_filename +0 -6
  113. data/test/multipart/empty +0 -10
  114. data/test/multipart/fail_16384_nofile +0 -814
  115. data/test/multipart/file1.txt +0 -1
  116. data/test/multipart/filename_and_modification_param +0 -7
  117. data/test/multipart/filename_and_no_name +0 -6
  118. data/test/multipart/filename_with_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  120. data/test/multipart/filename_with_null_byte +0 -7
  121. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  122. data/test/multipart/filename_with_unescaped_percentages +0 -6
  123. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  124. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  125. data/test/multipart/filename_with_unescaped_quotes +0 -6
  126. data/test/multipart/ie +0 -6
  127. data/test/multipart/invalid_character +0 -6
  128. data/test/multipart/mixed_files +0 -21
  129. data/test/multipart/nested +0 -10
  130. data/test/multipart/none +0 -9
  131. data/test/multipart/semicolon +0 -6
  132. data/test/multipart/text +0 -15
  133. data/test/multipart/three_files_three_fields +0 -31
  134. data/test/multipart/webkit +0 -32
  135. data/test/rackup/config.ru +0 -31
  136. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  137. data/test/spec_auth_basic.rb +0 -81
  138. data/test/spec_auth_digest.rb +0 -259
  139. data/test/spec_body_proxy.rb +0 -85
  140. data/test/spec_builder.rb +0 -223
  141. data/test/spec_cascade.rb +0 -61
  142. data/test/spec_cgi.rb +0 -102
  143. data/test/spec_chunked.rb +0 -101
  144. data/test/spec_commonlogger.rb +0 -93
  145. data/test/spec_conditionalget.rb +0 -102
  146. data/test/spec_config.rb +0 -22
  147. data/test/spec_content_length.rb +0 -85
  148. data/test/spec_content_type.rb +0 -45
  149. data/test/spec_deflater.rb +0 -339
  150. data/test/spec_directory.rb +0 -88
  151. data/test/spec_etag.rb +0 -107
  152. data/test/spec_fastcgi.rb +0 -107
  153. data/test/spec_file.rb +0 -221
  154. data/test/spec_handler.rb +0 -72
  155. data/test/spec_head.rb +0 -45
  156. data/test/spec_lint.rb +0 -550
  157. data/test/spec_lobster.rb +0 -58
  158. data/test/spec_lock.rb +0 -164
  159. data/test/spec_logger.rb +0 -23
  160. data/test/spec_methodoverride.rb +0 -111
  161. data/test/spec_mime.rb +0 -51
  162. data/test/spec_mock.rb +0 -297
  163. data/test/spec_mongrel.rb +0 -182
  164. data/test/spec_multipart.rb +0 -600
  165. data/test/spec_nulllogger.rb +0 -20
  166. data/test/spec_recursive.rb +0 -72
  167. data/test/spec_request.rb +0 -1232
  168. data/test/spec_response.rb +0 -407
  169. data/test/spec_rewindable_input.rb +0 -118
  170. data/test/spec_runtime.rb +0 -49
  171. data/test/spec_sendfile.rb +0 -130
  172. data/test/spec_server.rb +0 -167
  173. data/test/spec_session_abstract_id.rb +0 -53
  174. data/test/spec_session_cookie.rb +0 -410
  175. data/test/spec_session_memcache.rb +0 -321
  176. data/test/spec_session_pool.rb +0 -209
  177. data/test/spec_showexceptions.rb +0 -98
  178. data/test/spec_showstatus.rb +0 -103
  179. data/test/spec_static.rb +0 -145
  180. data/test/spec_tempfile_reaper.rb +0 -63
  181. data/test/spec_thin.rb +0 -91
  182. data/test/spec_urlmap.rb +0 -236
  183. data/test/spec_utils.rb +0 -647
  184. data/test/spec_version.rb +0 -17
  185. data/test/spec_webrick.rb +0 -184
  186. data/test/static/another/index.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
data/lib/rack/utils.rb CHANGED
@@ -1,176 +1,103 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'uri'
2
5
  require 'fileutils'
3
6
  require 'set'
4
7
  require 'tempfile'
5
- require 'rack/multipart'
6
8
  require 'time'
7
9
 
8
- major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
9
-
10
- if major == 1 && minor < 9
11
- require 'rack/backports/uri/common_18'
12
- elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
13
- require 'rack/backports/uri/common_192'
14
- elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
15
- require 'rack/backports/uri/common_193'
16
- else
17
- require 'uri/common'
18
- end
10
+ require_relative 'query_parser'
19
11
 
20
12
  module Rack
21
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
22
14
  # applications adopted from all kinds of Ruby libraries.
23
15
 
24
16
  module Utils
25
- # ParameterTypeError is the error that is raised when incoming structural
26
- # parameters (parsed by parse_nested_query) contain conflicting types.
27
- class ParameterTypeError < TypeError; end
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
28
18
 
29
- # InvalidParameterError is the error that is raised when incoming structural
30
- # parameters (parsed by parse_nested_query) contain invalid format or byte
31
- # sequence.
32
- class InvalidParameterError < ArgumentError; end
19
+ ParameterTypeError = QueryParser::ParameterTypeError
20
+ InvalidParameterError = QueryParser::InvalidParameterError
21
+ DEFAULT_SEP = QueryParser::DEFAULT_SEP
22
+ COMMON_SEP = QueryParser::COMMON_SEP
23
+ KeySpaceConstrainedParams = QueryParser::Params
24
+
25
+ class << self
26
+ attr_accessor :default_query_parser
27
+ end
28
+ # The default number of bytes to allow parameter keys to take up.
29
+ # This helps prevent a rogue client from flooding a Request.
30
+ self.default_query_parser = QueryParser.make_default(65536, 100)
31
+
32
+ module_function
33
33
 
34
34
  # URI escapes. (CGI style space to +)
35
35
  def escape(s)
36
36
  URI.encode_www_form_component(s)
37
37
  end
38
- module_function :escape
39
38
 
40
39
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
41
40
  # true URI escaping.
42
41
  def escape_path(s)
43
- escape(s).gsub('+', '%20')
42
+ ::URI::DEFAULT_PARSER.escape s
43
+ end
44
+
45
+ # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
46
+ # unescaping query parameters or form components.
47
+ def unescape_path(s)
48
+ ::URI::DEFAULT_PARSER.unescape s
44
49
  end
45
- module_function :escape_path
46
50
 
47
51
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
48
52
  # target encoding of the string returned, and it defaults to UTF-8
49
- if defined?(::Encoding)
50
- def unescape(s, encoding = Encoding::UTF_8)
51
- URI.decode_www_form_component(s, encoding)
52
- end
53
- else
54
- def unescape(s, encoding = nil)
55
- URI.decode_www_form_component(s, encoding)
56
- end
53
+ def unescape(s, encoding = Encoding::UTF_8)
54
+ URI.decode_www_form_component(s, encoding)
57
55
  end
58
- module_function :unescape
59
-
60
- DEFAULT_SEP = /[&;] */n
61
56
 
62
57
  class << self
63
- attr_accessor :key_space_limit
64
- attr_accessor :param_depth_limit
65
58
  attr_accessor :multipart_part_limit
66
59
  end
67
60
 
68
- # The default number of bytes to allow parameter keys to take up.
69
- # This helps prevent a rogue client from flooding a Request.
70
- self.key_space_limit = 65536
71
-
72
- # Default depth at which the parameter parser will raise an exception for
73
- # being too deep. This helps prevent SystemStackErrors
74
- self.param_depth_limit = 100
75
-
76
61
  # The maximum number of parts a request can contain. Accepting too many part
77
62
  # can lead to the server running out of file handles.
78
63
  # Set to `0` for no limit.
79
- # FIXME: RACK_MULTIPART_LIMIT was introduced by mistake and it will be removed in 1.7.0
80
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
81
-
82
- # Stolen from Mongrel, with some small modifications:
83
- # Parses a query string by breaking it up at the '&'
84
- # and ';' characters. You can also use this to parse
85
- # cookies by changing the characters used in the second
86
- # parameter (which defaults to '&;').
87
- def parse_query(qs, d = nil, &unescaper)
88
- unescaper ||= method(:unescape)
89
-
90
- params = KeySpaceConstrainedParams.new
91
-
92
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
93
- next if p.empty?
94
- k, v = p.split('=', 2).map(&unescaper)
64
+ self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
95
65
 
96
- if cur = params[k]
97
- if cur.class == Array
98
- params[k] << v
99
- else
100
- params[k] = [cur, v]
101
- end
102
- else
103
- params[k] = v
104
- end
105
- end
66
+ def self.param_depth_limit
67
+ default_query_parser.param_depth_limit
68
+ end
106
69
 
107
- return params.to_params_hash
70
+ def self.param_depth_limit=(v)
71
+ self.default_query_parser = self.default_query_parser.new_depth_limit(v)
108
72
  end
109
- module_function :parse_query
110
73
 
111
- # parse_nested_query expands a query string into structural types. Supported
112
- # types are Arrays, Hashes and basic value types. It is possible to supply
113
- # query strings with parameters of conflicting types, in this case a
114
- # ParameterTypeError is raised. Users are encouraged to return a 400 in this
115
- # case.
116
- def parse_nested_query(qs, d = nil)
117
- params = KeySpaceConstrainedParams.new
74
+ def self.key_space_limit
75
+ default_query_parser.key_space_limit
76
+ end
118
77
 
119
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
120
- k, v = p.split('=', 2).map { |s| unescape(s) }
78
+ def self.key_space_limit=(v)
79
+ self.default_query_parser = self.default_query_parser.new_space_limit(v)
80
+ end
121
81
 
122
- normalize_params(params, k, v)
82
+ if defined?(Process::CLOCK_MONOTONIC)
83
+ def clock_time
84
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
123
85
  end
124
-
125
- return params.to_params_hash
126
- rescue ArgumentError => e
127
- raise InvalidParameterError, e.message
128
- end
129
- module_function :parse_nested_query
130
-
131
- # normalize_params recursively expands parameters into structural types. If
132
- # the structural types represented by two different parameter names are in
133
- # conflict, a ParameterTypeError is raised.
134
- def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
135
- raise RangeError if depth <= 0
136
-
137
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
138
- k = $1 || ''
139
- after = $' || ''
140
-
141
- return if k.empty?
142
-
143
- if after == ""
144
- params[k] = v
145
- elsif after == "["
146
- params[name] = v
147
- elsif after == "[]"
148
- params[k] ||= []
149
- raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
150
- params[k] << v
151
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
152
- child_key = $1
153
- params[k] ||= []
154
- raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
155
- if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
156
- normalize_params(params[k].last, child_key, v, depth - 1)
157
- else
158
- params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
159
- end
160
- else
161
- params[k] ||= params.class.new
162
- raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
163
- params[k] = normalize_params(params[k], after, v, depth - 1)
86
+ else
87
+ # :nocov:
88
+ def clock_time
89
+ Time.now.to_f
164
90
  end
91
+ # :nocov:
92
+ end
165
93
 
166
- return params
94
+ def parse_query(qs, d = nil, &unescaper)
95
+ Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
167
96
  end
168
- module_function :normalize_params
169
97
 
170
- def params_hash_type?(obj)
171
- obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
98
+ def parse_nested_query(qs, d = nil)
99
+ Rack::Utils.default_query_parser.parse_nested_query(qs, d)
172
100
  end
173
- module_function :params_hash_type?
174
101
 
175
102
  def build_query(params)
176
103
  params.map { |k, v|
@@ -181,7 +108,6 @@ module Rack
181
108
  end
182
109
  }.join("&")
183
110
  end
184
- module_function :build_query
185
111
 
186
112
  def build_nested_query(value, prefix = nil)
187
113
  case value
@@ -192,7 +118,7 @@ module Rack
192
118
  when Hash
193
119
  value.map { |k, v|
194
120
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
195
- }.reject(&:empty?).join('&')
121
+ }.delete_if(&:empty?).join('&')
196
122
  when nil
197
123
  prefix
198
124
  else
@@ -200,20 +126,22 @@ module Rack
200
126
  "#{prefix}=#{escape(value)}"
201
127
  end
202
128
  end
203
- module_function :build_nested_query
204
129
 
205
130
  def q_values(q_value_header)
206
131
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
207
132
  value, parameters = part.split(/\s*;\s*/, 2)
208
133
  quality = 1.0
209
- if md = /\Aq=([\d.]+)/.match(parameters)
134
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
210
135
  quality = md[1].to_f
211
136
  end
212
137
  [value, quality]
213
138
  end
214
139
  end
215
- module_function :q_values
216
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.
217
145
  def best_q_match(q_value_header, available_mimes)
218
146
  values = q_values(q_value_header)
219
147
 
@@ -226,7 +154,6 @@ module Rack
226
154
  end.last
227
155
  matches && matches.first
228
156
  end
229
- module_function :best_q_match
230
157
 
231
158
  ESCAPE_HTML = {
232
159
  "&" => "&amp;",
@@ -236,160 +163,155 @@ module Rack
236
163
  '"' => "&quot;",
237
164
  "/" => "&#x2F;"
238
165
  }
239
- if //.respond_to?(:encoding)
240
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
241
- else
242
- # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
243
- # TODO doesn't apply to jruby, so a better condition above might be preferable?
244
- ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
245
- end
166
+
167
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
246
168
 
247
169
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
248
170
  def escape_html(string)
249
171
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
250
172
  end
251
- module_function :escape_html
252
173
 
253
174
  def select_best_encoding(available_encodings, accept_encoding)
254
175
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
255
176
 
256
- expanded_accept_encoding =
257
- accept_encoding.map { |m, q|
258
- if m == "*"
259
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
260
- else
261
- [[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]
262
185
  end
263
- }.inject([]) { |mem, list|
264
- mem + list
265
- }
186
+ else
187
+ expanded_accept_encoding << [m, q, preference]
188
+ end
189
+ end
266
190
 
267
- 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)
268
194
 
269
195
  unless encoding_candidates.include?("identity")
270
196
  encoding_candidates.push("identity")
271
197
  end
272
198
 
273
- expanded_accept_encoding.each { |m, q|
199
+ expanded_accept_encoding.each do |m, q|
274
200
  encoding_candidates.delete(m) if q == 0.0
275
- }
201
+ end
276
202
 
277
- return (encoding_candidates & available_encodings)[0]
203
+ (encoding_candidates & available_encodings)[0]
278
204
  end
279
- module_function :select_best_encoding
280
205
 
281
- def set_cookie_header!(header, key, value)
206
+ def parse_cookies(env)
207
+ parse_cookies_header env[HTTP_COOKIE]
208
+ end
209
+
210
+ def parse_cookies_header(header)
211
+ # According to RFC 6265:
212
+ # The syntax for cookie headers only supports semicolons
213
+ # User Agent -> Server ==
214
+ # Cookie: SID=31d4d96e407aad42; lang=en-US
215
+ cookies = parse_query(header, ';') { |s| unescape(s) rescue s }
216
+ cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
217
+ end
218
+
219
+ def add_cookie_to_header(header, key, value)
282
220
  case value
283
221
  when Hash
284
- domain = "; domain=" + value[:domain] if value[:domain]
285
- path = "; path=" + value[:path] if value[:path]
286
- max_age = "; max-age=" + value[:max_age].to_s if value[:max_age]
287
- # There is an RFC mess in the area of date formatting for Cookies. Not
288
- # only are there contradicting RFCs and examples within RFC text, but
289
- # there are also numerous conflicting names of fields and partially
290
- # cross-applicable specifications.
291
- #
292
- # These are best described in RFC 2616 3.3.1. This RFC text also
293
- # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
294
- # fixed length format with space-date delimeted fields.
295
- #
296
- # See also RFC 1123 section 5.2.14.
297
- #
298
- # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
299
- # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
300
- # the space delimited format. These formats are compliant with RFC 2822.
301
- #
302
- # For reference, all involved RFCs are:
303
- # RFC 822
304
- # RFC 1123
305
- # RFC 2109
306
- # RFC 2616
307
- # RFC 2822
308
- # RFC 2965
309
- # RFC 6265
310
- expires = "; expires=" +
311
- rfc2822(value[:expires].clone.gmtime) if value[:expires]
222
+ domain = "; domain=#{value[:domain]}" if value[:domain]
223
+ path = "; path=#{value[:path]}" if value[:path]
224
+ max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
225
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
312
226
  secure = "; secure" if value[:secure]
313
227
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
314
228
  same_site =
315
229
  case value[:same_site]
316
230
  when false, nil
317
231
  nil
232
+ when :none, 'None', :None
233
+ '; SameSite=None'
318
234
  when :lax, 'Lax', :Lax
319
- '; SameSite=Lax'.freeze
235
+ '; SameSite=Lax'
320
236
  when true, :strict, 'Strict', :Strict
321
- '; SameSite=Strict'.freeze
237
+ '; SameSite=Strict'
322
238
  else
323
239
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
324
240
  end
325
241
  value = value[:value]
326
242
  end
327
243
  value = [value] unless Array === value
328
- cookie = escape(key) + "=" +
329
- value.map { |v| escape v }.join("&") +
330
- "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
331
244
 
332
- case header["Set-Cookie"]
245
+ cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
246
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
247
+
248
+ case header
333
249
  when nil, ''
334
- header["Set-Cookie"] = cookie
250
+ cookie
335
251
  when String
336
- header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
252
+ [header, cookie].join("\n")
337
253
  when Array
338
- header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
254
+ (header + [cookie]).join("\n")
255
+ else
256
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
339
257
  end
258
+ end
340
259
 
260
+ def set_cookie_header!(header, key, value)
261
+ header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
341
262
  nil
342
263
  end
343
- module_function :set_cookie_header!
344
264
 
345
- def delete_cookie_header!(header, key, value = {})
346
- case header["Set-Cookie"]
265
+ def make_delete_cookie_header(header, key, value)
266
+ case header
347
267
  when nil, ''
348
268
  cookies = []
349
269
  when String
350
- cookies = header["Set-Cookie"].split("\n")
270
+ cookies = header.split("\n")
351
271
  when Array
352
- cookies = header["Set-Cookie"]
353
- end
354
-
355
- cookies.reject! { |cookie|
356
- if value[:domain]
357
- cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
358
- elsif value[:path]
359
- cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
360
- else
361
- cookie =~ /\A#{escape(key)}=/
362
- end
363
- }
364
-
365
- header["Set-Cookie"] = cookies.join("\n")
366
-
367
- set_cookie_header!(header, key,
368
- {:value => '', :path => nil, :domain => nil,
369
- :max_age => '0',
370
- :expires => Time.at(0) }.merge(value))
272
+ cookies = header
273
+ end
274
+
275
+ key = escape(key)
276
+ domain = value[:domain]
277
+ path = value[:path]
278
+ regexp = if domain
279
+ if path
280
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
281
+ else
282
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
283
+ end
284
+ elsif path
285
+ /\A#{key}=.*path=#{path}(?:;|$)/
286
+ else
287
+ /\A#{key}=/
288
+ end
289
+
290
+ cookies.reject! { |cookie| regexp.match? cookie }
291
+
292
+ cookies.join("\n")
293
+ end
371
294
 
295
+ def delete_cookie_header!(header, key, value = {})
296
+ header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
372
297
  nil
373
298
  end
374
- module_function :delete_cookie_header!
375
299
 
376
- # Return the bytesize of String; uses String#size under Ruby 1.8 and
377
- # String#bytesize under 1.9.
378
- if ''.respond_to?(:bytesize)
379
- def bytesize(string)
380
- string.bytesize
381
- end
382
- else
383
- def bytesize(string)
384
- string.size
385
- end
300
+ # Adds a cookie that will *remove* a cookie from the client. Hence the
301
+ # strange method name.
302
+ def add_remove_cookie_to_header(header, key, value = {})
303
+ new_header = make_delete_cookie_header(header, key, value)
304
+
305
+ add_cookie_to_header(new_header, key,
306
+ { value: '', path: nil, domain: nil,
307
+ max_age: '0',
308
+ expires: Time.at(0) }.merge(value))
309
+
386
310
  end
387
- module_function :bytesize
388
311
 
389
312
  def rfc2822(time)
390
313
  time.rfc2822
391
314
  end
392
- module_function :rfc2822
393
315
 
394
316
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
395
317
  # of '% %b %Y'.
@@ -405,19 +327,22 @@ module Rack
405
327
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
406
328
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
407
329
  end
408
- module_function :rfc2109
409
330
 
410
331
  # Parses the "Range:" header, if present, into an array of Range objects.
411
332
  # Returns nil if the header is missing or syntactically invalid.
412
333
  # Returns an empty array if none of the ranges are satisfiable.
413
334
  def byte_ranges(env, size)
335
+ warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
336
+ get_byte_ranges env['HTTP_RANGE'], size
337
+ end
338
+
339
+ def get_byte_ranges(http_range, size)
414
340
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
415
- http_range = env['HTTP_RANGE']
416
341
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
417
342
  ranges = []
418
343
  $1.split(/,\s*/).each do |range_spec|
419
344
  return nil unless range_spec =~ /(\d*)-(\d*)/
420
- r0,r1 = $1, $2
345
+ r0, r1 = $1, $2
421
346
  if r0.empty?
422
347
  return nil if r1.empty?
423
348
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -431,14 +356,13 @@ module Rack
431
356
  else
432
357
  r1 = r1.to_i
433
358
  return nil if r1 < r0 # backwards range is syntactically invalid
434
- r1 = size-1 if r1 >= size
359
+ r1 = size - 1 if r1 >= size
435
360
  end
436
361
  end
437
362
  ranges << (r0..r1) if r0 <= r1
438
363
  end
439
364
  ranges
440
365
  end
441
- module_function :byte_ranges
442
366
 
443
367
  # Constant time string comparison.
444
368
  #
@@ -447,15 +371,14 @@ module Rack
447
371
  # on variable length plaintext strings because it could leak length info
448
372
  # via timing attacks.
449
373
  def secure_compare(a, b)
450
- return false unless bytesize(a) == bytesize(b)
374
+ return false unless a.bytesize == b.bytesize
451
375
 
452
376
  l = a.unpack("C*")
453
377
 
454
378
  r, i = 0, -1
455
- b.each_byte { |v| r |= v ^ l[i+=1] }
379
+ b.each_byte { |v| r |= v ^ l[i += 1] }
456
380
  r == 0
457
381
  end
458
- module_function :secure_compare
459
382
 
460
383
  # Context allows the use of a compatible middleware at different points
461
384
  # in a request handling stack. A compatible middleware must define
@@ -478,24 +401,42 @@ module Rack
478
401
  self.class.new(@for, app)
479
402
  end
480
403
 
481
- def context(env, app=@app)
404
+ def context(env, app = @app)
482
405
  recontext(app).call(env)
483
406
  end
484
407
  end
485
408
 
486
409
  # A case-insensitive Hash that preserves the original case of a
487
410
  # header when set.
488
- class HeaderHash < Hash
489
- def self.new(hash={})
490
- HeaderHash === hash ? hash : super(hash)
411
+ #
412
+ # @api private
413
+ class HeaderHash < Hash # :nodoc:
414
+ def self.[](headers)
415
+ if headers.is_a?(HeaderHash) && !headers.frozen?
416
+ return headers
417
+ else
418
+ return self.new(headers)
419
+ end
491
420
  end
492
421
 
493
- def initialize(hash={})
422
+ def initialize(hash = {})
494
423
  super()
495
424
  @names = {}
496
425
  hash.each { |k, v| self[k] = v }
497
426
  end
498
427
 
428
+ # on dup/clone, we need to duplicate @names hash
429
+ def initialize_copy(other)
430
+ super
431
+ @names = other.names.dup
432
+ end
433
+
434
+ # on clear, we need to clear @names hash
435
+ def clear
436
+ super
437
+ @names.clear
438
+ end
439
+
499
440
  def each
500
441
  super do |k, v|
501
442
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -504,7 +445,7 @@ module Rack
504
445
 
505
446
  def to_hash
506
447
  hash = {}
507
- each { |k,v| hash[k] = v }
448
+ each { |k, v| hash[k] = v }
508
449
  hash
509
450
  end
510
451
 
@@ -513,21 +454,20 @@ module Rack
513
454
  end
514
455
 
515
456
  def []=(k, v)
516
- canonical = k.downcase
457
+ canonical = k.downcase.freeze
517
458
  delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
518
- @names[k] = @names[canonical] = k
459
+ @names[canonical] = k
519
460
  super k, v
520
461
  end
521
462
 
522
463
  def delete(k)
523
464
  canonical = k.downcase
524
465
  result = super @names.delete(canonical)
525
- @names.delete_if { |name,| name.downcase == canonical }
526
466
  result
527
467
  end
528
468
 
529
469
  def include?(k)
530
- @names.include?(k) || @names.include?(k.downcase)
470
+ super || @names.include?(k.downcase)
531
471
  end
532
472
 
533
473
  alias_method :has_key?, :include?
@@ -549,56 +489,23 @@ module Rack
549
489
  other.each { |k, v| self[k] = v }
550
490
  self
551
491
  end
552
- end
553
-
554
- class KeySpaceConstrainedParams
555
- def initialize(limit = Utils.key_space_limit)
556
- @limit = limit
557
- @size = 0
558
- @params = {}
559
- end
560
492
 
561
- def [](key)
562
- @params[key]
563
- end
564
-
565
- def []=(key, value)
566
- @size += key.size if key && !@params.key?(key)
567
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
568
- @params[key] = value
569
- end
570
-
571
- def key?(key)
572
- @params.key?(key)
573
- end
574
-
575
- def to_params_hash
576
- hash = @params
577
- hash.keys.each do |key|
578
- value = hash[key]
579
- if value.kind_of?(self.class)
580
- if value.object_id == self.object_id
581
- hash[key] = hash
582
- else
583
- hash[key] = value.to_params_hash
584
- end
585
- elsif value.kind_of?(Array)
586
- value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
587
- end
493
+ protected
494
+ def names
495
+ @names
588
496
  end
589
- hash
590
- end
591
497
  end
592
498
 
593
499
  # Every standard HTTP code mapped to the appropriate message.
594
500
  # Generated with:
595
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
596
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
597
- # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
501
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
502
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
503
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
598
504
  HTTP_STATUS_CODES = {
599
505
  100 => 'Continue',
600
506
  101 => 'Switching Protocols',
601
507
  102 => 'Processing',
508
+ 103 => 'Early Hints',
602
509
  200 => 'OK',
603
510
  201 => 'Created',
604
511
  202 => 'Accepted',
@@ -615,6 +522,7 @@ module Rack
615
522
  303 => 'See Other',
616
523
  304 => 'Not Modified',
617
524
  305 => 'Use Proxy',
525
+ 306 => '(Unused)',
618
526
  307 => 'Temporary Redirect',
619
527
  308 => 'Permanent Redirect',
620
528
  400 => 'Bad Request',
@@ -635,13 +543,16 @@ module Rack
635
543
  415 => 'Unsupported Media Type',
636
544
  416 => 'Range Not Satisfiable',
637
545
  417 => 'Expectation Failed',
546
+ 421 => 'Misdirected Request',
638
547
  422 => 'Unprocessable Entity',
639
548
  423 => 'Locked',
640
549
  424 => 'Failed Dependency',
550
+ 425 => 'Too Early',
641
551
  426 => 'Upgrade Required',
642
552
  428 => 'Precondition Required',
643
553
  429 => 'Too Many Requests',
644
554
  431 => 'Request Header Fields Too Large',
555
+ 451 => 'Unavailable for Legal Reasons',
645
556
  500 => 'Internal Server Error',
646
557
  501 => 'Not Implemented',
647
558
  502 => 'Bad Gateway',
@@ -651,12 +562,13 @@ module Rack
651
562
  506 => 'Variant Also Negotiates',
652
563
  507 => 'Insufficient Storage',
653
564
  508 => 'Loop Detected',
565
+ 509 => 'Bandwidth Limit Exceeded',
654
566
  510 => 'Not Extended',
655
567
  511 => 'Network Authentication Required'
656
568
  }
657
569
 
658
570
  # Responses with HTTP status codes that should not have an entity body
659
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
571
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
660
572
 
661
573
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
662
574
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -664,14 +576,11 @@ module Rack
664
576
 
665
577
  def status_code(status)
666
578
  if status.is_a?(Symbol)
667
- SYMBOL_TO_STATUS_CODE[status] || 500
579
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
668
580
  else
669
581
  status.to_i
670
582
  end
671
583
  end
672
- module_function :status_code
673
-
674
- Multipart = Rack::Multipart
675
584
 
676
585
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
677
586
 
@@ -685,11 +594,16 @@ module Rack
685
594
  part == '..' ? clean.pop : clean << part
686
595
  end
687
596
 
688
- clean.unshift '/' if parts.empty? || parts.first.empty?
597
+ clean_path = clean.join(::File::SEPARATOR)
598
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
599
+ clean_path
600
+ end
601
+
602
+ NULL_BYTE = "\0"
689
603
 
690
- ::File.join(*clean)
604
+ def valid_path?(path)
605
+ path.valid_encoding? && !path.include?(NULL_BYTE)
691
606
  end
692
- module_function :clean_path_info
693
607
 
694
608
  end
695
609
  end