rack 1.6.11 → 2.1.4

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