rack 1.6.13 → 2.2.3

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 (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 +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 +6 -3
  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 +553 -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 +141 -93
  67. data/lib/rack/session/cookie.rb +35 -29
  68. data/lib/rack/session/memcache.rb +4 -93
  69. data/lib/rack/session/pool.rb +13 -11
  70. data/lib/rack/show_exceptions.rb +390 -0
  71. data/lib/rack/{showstatus.rb → show_status.rb} +12 -12
  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 +212 -294
  76. data/lib/rack/version.rb +29 -0
  77. data/lib/rack.rb +76 -33
  78. data/rack.gemspec +43 -30
  79. metadata +65 -187
  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 -358
  176. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  177. data/test/spec_session_pool.rb +0 -246
  178. data/test/spec_showexceptions.rb +0 -98
  179. data/test/spec_showstatus.rb +0 -103
  180. data/test/spec_static.rb +0 -145
  181. data/test/spec_tempfile_reaper.rb +0 -63
  182. data/test/spec_thin.rb +0 -91
  183. data/test/spec_urlmap.rb +0 -236
  184. data/test/spec_utils.rb +0 -647
  185. data/test/spec_version.rb +0 -17
  186. data/test/spec_webrick.rb +0 -184
  187. data/test/static/another/index.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
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,159 @@ 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
+ 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
221
+ end
222
+
223
+ def add_cookie_to_header(header, key, value)
282
224
  case value
283
225
  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]
226
+ domain = "; domain=#{value[:domain]}" if value[:domain]
227
+ path = "; path=#{value[:path]}" if value[:path]
228
+ max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
229
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
312
230
  secure = "; secure" if value[:secure]
313
231
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
314
232
  same_site =
315
233
  case value[:same_site]
316
234
  when false, nil
317
235
  nil
236
+ when :none, 'None', :None
237
+ '; SameSite=None'
318
238
  when :lax, 'Lax', :Lax
319
- '; SameSite=Lax'.freeze
239
+ '; SameSite=Lax'
320
240
  when true, :strict, 'Strict', :Strict
321
- '; SameSite=Strict'.freeze
241
+ '; SameSite=Strict'
322
242
  else
323
243
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
324
244
  end
325
245
  value = value[:value]
326
246
  end
327
247
  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
248
 
332
- case header["Set-Cookie"]
249
+ cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
250
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
251
+
252
+ case header
333
253
  when nil, ''
334
- header["Set-Cookie"] = cookie
254
+ cookie
335
255
  when String
336
- header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
256
+ [header, cookie].join("\n")
337
257
  when Array
338
- header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
258
+ (header + [cookie]).join("\n")
259
+ else
260
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
339
261
  end
262
+ end
340
263
 
264
+ def set_cookie_header!(header, key, value)
265
+ header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
341
266
  nil
342
267
  end
343
- module_function :set_cookie_header!
344
268
 
345
- def delete_cookie_header!(header, key, value = {})
346
- case header["Set-Cookie"]
269
+ def make_delete_cookie_header(header, key, value)
270
+ case header
347
271
  when nil, ''
348
272
  cookies = []
349
273
  when String
350
- cookies = header["Set-Cookie"].split("\n")
274
+ cookies = header.split("\n")
351
275
  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))
276
+ cookies = header
277
+ end
278
+
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 }
295
+
296
+ cookies.join("\n")
297
+ end
371
298
 
299
+ def delete_cookie_header!(header, key, value = {})
300
+ header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
372
301
  nil
373
302
  end
374
- module_function :delete_cookie_header!
375
303
 
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
304
+ # Adds a cookie that will *remove* a cookie from the client. Hence the
305
+ # strange method name.
306
+ def add_remove_cookie_to_header(header, key, value = {})
307
+ new_header = make_delete_cookie_header(header, key, value)
308
+
309
+ add_cookie_to_header(new_header, key,
310
+ { value: '', path: nil, domain: nil,
311
+ max_age: '0',
312
+ expires: Time.at(0) }.merge(value))
313
+
386
314
  end
387
- module_function :bytesize
388
315
 
389
316
  def rfc2822(time)
390
317
  time.rfc2822
391
318
  end
392
- module_function :rfc2822
393
319
 
394
320
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
395
321
  # of '% %b %Y'.
@@ -405,19 +331,22 @@ module Rack
405
331
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
406
332
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
407
333
  end
408
- module_function :rfc2109
409
334
 
410
335
  # Parses the "Range:" header, if present, into an array of Range objects.
411
336
  # Returns nil if the header is missing or syntactically invalid.
412
337
  # Returns an empty array if none of the ranges are satisfiable.
413
338
  def byte_ranges(env, size)
339
+ warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
340
+ get_byte_ranges env['HTTP_RANGE'], size
341
+ end
342
+
343
+ def get_byte_ranges(http_range, size)
414
344
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
415
- http_range = env['HTTP_RANGE']
416
345
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
417
346
  ranges = []
418
347
  $1.split(/,\s*/).each do |range_spec|
419
348
  return nil unless range_spec =~ /(\d*)-(\d*)/
420
- r0,r1 = $1, $2
349
+ r0, r1 = $1, $2
421
350
  if r0.empty?
422
351
  return nil if r1.empty?
423
352
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -431,14 +360,13 @@ module Rack
431
360
  else
432
361
  r1 = r1.to_i
433
362
  return nil if r1 < r0 # backwards range is syntactically invalid
434
- r1 = size-1 if r1 >= size
363
+ r1 = size - 1 if r1 >= size
435
364
  end
436
365
  end
437
366
  ranges << (r0..r1) if r0 <= r1
438
367
  end
439
368
  ranges
440
369
  end
441
- module_function :byte_ranges
442
370
 
443
371
  # Constant time string comparison.
444
372
  #
@@ -447,15 +375,14 @@ module Rack
447
375
  # on variable length plaintext strings because it could leak length info
448
376
  # via timing attacks.
449
377
  def secure_compare(a, b)
450
- return false unless bytesize(a) == bytesize(b)
378
+ return false unless a.bytesize == b.bytesize
451
379
 
452
380
  l = a.unpack("C*")
453
381
 
454
382
  r, i = 0, -1
455
- b.each_byte { |v| r |= v ^ l[i+=1] }
383
+ b.each_byte { |v| r |= v ^ l[i += 1] }
456
384
  r == 0
457
385
  end
458
- module_function :secure_compare
459
386
 
460
387
  # Context allows the use of a compatible middleware at different points
461
388
  # in a request handling stack. A compatible middleware must define
@@ -478,24 +405,42 @@ module Rack
478
405
  self.class.new(@for, app)
479
406
  end
480
407
 
481
- def context(env, app=@app)
408
+ def context(env, app = @app)
482
409
  recontext(app).call(env)
483
410
  end
484
411
  end
485
412
 
486
413
  # A case-insensitive Hash that preserves the original case of a
487
414
  # header when set.
488
- class HeaderHash < Hash
489
- def self.new(hash={})
490
- 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
491
424
  end
492
425
 
493
- def initialize(hash={})
426
+ def initialize(hash = {})
494
427
  super()
495
428
  @names = {}
496
429
  hash.each { |k, v| self[k] = v }
497
430
  end
498
431
 
432
+ # on dup/clone, we need to duplicate @names hash
433
+ def initialize_copy(other)
434
+ super
435
+ @names = other.names.dup
436
+ end
437
+
438
+ # on clear, we need to clear @names hash
439
+ def clear
440
+ super
441
+ @names.clear
442
+ end
443
+
499
444
  def each
500
445
  super do |k, v|
501
446
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -504,7 +449,7 @@ module Rack
504
449
 
505
450
  def to_hash
506
451
  hash = {}
507
- each { |k,v| hash[k] = v }
452
+ each { |k, v| hash[k] = v }
508
453
  hash
509
454
  end
510
455
 
@@ -513,21 +458,20 @@ module Rack
513
458
  end
514
459
 
515
460
  def []=(k, v)
516
- canonical = k.downcase
461
+ canonical = k.downcase.freeze
517
462
  delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
518
- @names[k] = @names[canonical] = k
463
+ @names[canonical] = k
519
464
  super k, v
520
465
  end
521
466
 
522
467
  def delete(k)
523
468
  canonical = k.downcase
524
469
  result = super @names.delete(canonical)
525
- @names.delete_if { |name,| name.downcase == canonical }
526
470
  result
527
471
  end
528
472
 
529
473
  def include?(k)
530
- @names.include?(k) || @names.include?(k.downcase)
474
+ super || @names.include?(k.downcase)
531
475
  end
532
476
 
533
477
  alias_method :has_key?, :include?
@@ -549,56 +493,23 @@ module Rack
549
493
  other.each { |k, v| self[k] = v }
550
494
  self
551
495
  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
496
 
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
497
+ protected
498
+ def names
499
+ @names
588
500
  end
589
- hash
590
- end
591
501
  end
592
502
 
593
503
  # Every standard HTTP code mapped to the appropriate message.
594
504
  # 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,"'
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,"'
598
508
  HTTP_STATUS_CODES = {
599
509
  100 => 'Continue',
600
510
  101 => 'Switching Protocols',
601
511
  102 => 'Processing',
512
+ 103 => 'Early Hints',
602
513
  200 => 'OK',
603
514
  201 => 'Created',
604
515
  202 => 'Accepted',
@@ -615,6 +526,7 @@ module Rack
615
526
  303 => 'See Other',
616
527
  304 => 'Not Modified',
617
528
  305 => 'Use Proxy',
529
+ 306 => '(Unused)',
618
530
  307 => 'Temporary Redirect',
619
531
  308 => 'Permanent Redirect',
620
532
  400 => 'Bad Request',
@@ -635,13 +547,16 @@ module Rack
635
547
  415 => 'Unsupported Media Type',
636
548
  416 => 'Range Not Satisfiable',
637
549
  417 => 'Expectation Failed',
550
+ 421 => 'Misdirected Request',
638
551
  422 => 'Unprocessable Entity',
639
552
  423 => 'Locked',
640
553
  424 => 'Failed Dependency',
554
+ 425 => 'Too Early',
641
555
  426 => 'Upgrade Required',
642
556
  428 => 'Precondition Required',
643
557
  429 => 'Too Many Requests',
644
558
  431 => 'Request Header Fields Too Large',
559
+ 451 => 'Unavailable for Legal Reasons',
645
560
  500 => 'Internal Server Error',
646
561
  501 => 'Not Implemented',
647
562
  502 => 'Bad Gateway',
@@ -651,12 +566,13 @@ module Rack
651
566
  506 => 'Variant Also Negotiates',
652
567
  507 => 'Insufficient Storage',
653
568
  508 => 'Loop Detected',
569
+ 509 => 'Bandwidth Limit Exceeded',
654
570
  510 => 'Not Extended',
655
571
  511 => 'Network Authentication Required'
656
572
  }
657
573
 
658
574
  # 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)
575
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
660
576
 
661
577
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
662
578
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -664,14 +580,11 @@ module Rack
664
580
 
665
581
  def status_code(status)
666
582
  if status.is_a?(Symbol)
667
- SYMBOL_TO_STATUS_CODE[status] || 500
583
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
668
584
  else
669
585
  status.to_i
670
586
  end
671
587
  end
672
- module_function :status_code
673
-
674
- Multipart = Rack::Multipart
675
588
 
676
589
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
677
590
 
@@ -685,11 +598,16 @@ module Rack
685
598
  part == '..' ? clean.pop : clean << part
686
599
  end
687
600
 
688
- clean.unshift '/' if parts.empty? || parts.first.empty?
601
+ clean_path = clean.join(::File::SEPARATOR)
602
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
603
+ clean_path
604
+ end
605
+
606
+ NULL_BYTE = "\0"
689
607
 
690
- ::File.join(*clean)
608
+ def valid_path?(path)
609
+ path.valid_encoding? && !path.include?(NULL_BYTE)
691
610
  end
692
- module_function :clean_path_info
693
611
 
694
612
  end
695
613
  end