rack 1.6.13 → 2.1.4.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +105 -141
  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} +14 -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 +55 -52
  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 +292 -161
  53. data/lib/rack/multipart/uploaded_file.rb +3 -2
  54. data/lib/rack/multipart.rb +38 -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 +139 -94
  66. data/lib/rack/session/cookie.rb +34 -26
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +12 -10
  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 +203 -277
  75. data/lib/rack.rb +76 -24
  76. data/rack.gemspec +25 -14
  77. metadata +62 -183
  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 -358
  173. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  174. data/test/spec_session_pool.rb +0 -246
  175. data/test/spec_showexceptions.rb +0 -98
  176. data/test/spec_showstatus.rb +0 -103
  177. data/test/spec_static.rb +0 -145
  178. data/test/spec_tempfile_reaper.rb +0 -63
  179. data/test/spec_thin.rb +0 -91
  180. data/test/spec_urlmap.rb +0 -236
  181. data/test/spec_utils.rb +0 -647
  182. data/test/spec_version.rb +0 -17
  183. data/test/spec_webrick.rb +0 -184
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
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,81 @@ 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
- attr_accessor :multipart_part_limit
66
- end
62
+ attr_accessor :multipart_total_part_limit
67
63
 
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
64
+ attr_accessor :multipart_file_limit
71
65
 
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
66
+ # multipart_part_limit is the original name of multipart_file_limit, but
67
+ # the limit only counts parts with filenames.
68
+ alias multipart_part_limit multipart_file_limit
69
+ alias multipart_part_limit= multipart_file_limit=
70
+ end
75
71
 
76
- # The maximum number of parts a request can contain. Accepting too many part
77
- # can lead to the server running out of file handles.
72
+ # The maximum number of file parts a request can contain. Accepting too
73
+ # many parts can lead to the server running out of file handles.
78
74
  # 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
75
+ self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
91
76
 
92
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
93
- next if p.empty?
94
- k, v = p.split('=', 2).map(&unescaper)
77
+ # The maximum total number of parts a request can contain. Accepting too
78
+ # many can lead to excessive memory use and parsing time.
79
+ self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
95
80
 
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
106
-
107
- return params.to_params_hash
81
+ def self.param_depth_limit
82
+ default_query_parser.param_depth_limit
108
83
  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
84
 
119
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
120
- k, v = p.split('=', 2).map { |s| unescape(s) }
85
+ def self.param_depth_limit=(v)
86
+ self.default_query_parser = self.default_query_parser.new_depth_limit(v)
87
+ end
121
88
 
122
- normalize_params(params, k, v)
123
- end
89
+ def self.key_space_limit
90
+ default_query_parser.key_space_limit
91
+ end
124
92
 
125
- return params.to_params_hash
126
- rescue ArgumentError => e
127
- raise InvalidParameterError, e.message
93
+ def self.key_space_limit=(v)
94
+ self.default_query_parser = self.default_query_parser.new_space_limit(v)
128
95
  end
129
- module_function :parse_nested_query
130
96
 
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)
97
+ if defined?(Process::CLOCK_MONOTONIC)
98
+ def clock_time
99
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
100
+ end
101
+ else
102
+ def clock_time
103
+ Time.now.to_f
164
104
  end
105
+ end
106
+ module_function :clock_time
165
107
 
166
- return params
108
+ def parse_query(qs, d = nil, &unescaper)
109
+ Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
167
110
  end
168
- module_function :normalize_params
111
+ module_function :parse_query
169
112
 
170
- def params_hash_type?(obj)
171
- obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
113
+ def parse_nested_query(qs, d = nil)
114
+ Rack::Utils.default_query_parser.parse_nested_query(qs, d)
172
115
  end
173
- module_function :params_hash_type?
116
+ module_function :parse_nested_query
174
117
 
175
118
  def build_query(params)
176
119
  params.map { |k, v|
@@ -192,7 +135,7 @@ module Rack
192
135
  when Hash
193
136
  value.map { |k, v|
194
137
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
195
- }.reject(&:empty?).join('&')
138
+ }.delete_if(&:empty?).join('&')
196
139
  when nil
197
140
  prefix
198
141
  else
@@ -206,7 +149,7 @@ module Rack
206
149
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
207
150
  value, parameters = part.split(/\s*;\s*/, 2)
208
151
  quality = 1.0
209
- if md = /\Aq=([\d.]+)/.match(parameters)
152
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
210
153
  quality = md[1].to_f
211
154
  end
212
155
  [value, quality]
@@ -236,13 +179,8 @@ module Rack
236
179
  '"' => "&quot;",
237
180
  "/" => "&#x2F;"
238
181
  }
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
182
+
183
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
246
184
 
247
185
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
248
186
  def escape_html(string)
@@ -254,137 +192,139 @@ module Rack
254
192
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
255
193
 
256
194
  expanded_accept_encoding =
257
- accept_encoding.map { |m, q|
195
+ accept_encoding.each_with_object([]) do |(m, q), list|
258
196
  if m == "*"
259
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
197
+ (available_encodings - accept_encoding.map(&:first))
198
+ .each { |m2| list << [m2, q] }
260
199
  else
261
- [[m, q]]
200
+ list << [m, q]
262
201
  end
263
- }.inject([]) { |mem, list|
264
- mem + list
265
- }
202
+ end
266
203
 
267
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
204
+ encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
268
205
 
269
206
  unless encoding_candidates.include?("identity")
270
207
  encoding_candidates.push("identity")
271
208
  end
272
209
 
273
- expanded_accept_encoding.each { |m, q|
210
+ expanded_accept_encoding.each do |m, q|
274
211
  encoding_candidates.delete(m) if q == 0.0
275
- }
212
+ end
276
213
 
277
- return (encoding_candidates & available_encodings)[0]
214
+ (encoding_candidates & available_encodings)[0]
278
215
  end
279
216
  module_function :select_best_encoding
280
217
 
281
- def set_cookie_header!(header, key, value)
218
+ def parse_cookies(env)
219
+ parse_cookies_header env[HTTP_COOKIE]
220
+ end
221
+ module_function :parse_cookies
222
+
223
+ def parse_cookies_header(header)
224
+ # According to RFC 2109:
225
+ # If multiple cookies satisfy the criteria above, they are ordered in
226
+ # the Cookie header such that those with more specific Path attributes
227
+ # precede those with less specific. Ordering with respect to other
228
+ # attributes (e.g., Domain) is unspecified.
229
+ return {} unless header
230
+ header.split(/[;,] */n).each_with_object({}) do |cookie, cookies|
231
+ next if cookie.empty?
232
+ key, value = cookie.split('=', 2)
233
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
234
+ end
235
+ end
236
+ module_function :parse_cookies_header
237
+
238
+ def add_cookie_to_header(header, key, value)
282
239
  case value
283
240
  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]
241
+ domain = "; domain=#{value[:domain]}" if value[:domain]
242
+ path = "; path=#{value[:path]}" if value[:path]
243
+ max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
244
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
312
245
  secure = "; secure" if value[:secure]
313
246
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
314
247
  same_site =
315
248
  case value[:same_site]
316
249
  when false, nil
317
250
  nil
251
+ when :none, 'None', :None
252
+ '; SameSite=None'
318
253
  when :lax, 'Lax', :Lax
319
- '; SameSite=Lax'.freeze
254
+ '; SameSite=Lax'
320
255
  when true, :strict, 'Strict', :Strict
321
- '; SameSite=Strict'.freeze
256
+ '; SameSite=Strict'
322
257
  else
323
258
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
324
259
  end
325
260
  value = value[:value]
326
261
  end
327
262
  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
263
 
332
- case header["Set-Cookie"]
264
+ cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
265
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
266
+
267
+ case header
333
268
  when nil, ''
334
- header["Set-Cookie"] = cookie
269
+ cookie
335
270
  when String
336
- header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
271
+ [header, cookie].join("\n")
337
272
  when Array
338
- header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
273
+ (header + [cookie]).join("\n")
274
+ else
275
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
339
276
  end
277
+ end
278
+ module_function :add_cookie_to_header
340
279
 
280
+ def set_cookie_header!(header, key, value)
281
+ header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
341
282
  nil
342
283
  end
343
284
  module_function :set_cookie_header!
344
285
 
345
- def delete_cookie_header!(header, key, value = {})
346
- case header["Set-Cookie"]
286
+ def make_delete_cookie_header(header, key, value)
287
+ case header
347
288
  when nil, ''
348
289
  cookies = []
349
290
  when String
350
- cookies = header["Set-Cookie"].split("\n")
291
+ cookies = header.split("\n")
351
292
  when Array
352
- cookies = header["Set-Cookie"]
293
+ cookies = header
353
294
  end
354
295
 
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
- }
296
+ regexp = if value[:domain]
297
+ /\A#{escape(key)}=.*domain=#{value[:domain]}/
298
+ elsif value[:path]
299
+ /\A#{escape(key)}=.*path=#{value[:path]}/
300
+ else
301
+ /\A#{escape(key)}=/
302
+ end
364
303
 
365
- header["Set-Cookie"] = cookies.join("\n")
304
+ cookies.reject! { |cookie| regexp.match? cookie }
366
305
 
367
- set_cookie_header!(header, key,
368
- {:value => '', :path => nil, :domain => nil,
369
- :max_age => '0',
370
- :expires => Time.at(0) }.merge(value))
306
+ cookies.join("\n")
307
+ end
308
+ module_function :make_delete_cookie_header
371
309
 
310
+ def delete_cookie_header!(header, key, value = {})
311
+ header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
372
312
  nil
373
313
  end
374
314
  module_function :delete_cookie_header!
375
315
 
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
316
+ # Adds a cookie that will *remove* a cookie from the client. Hence the
317
+ # strange method name.
318
+ def add_remove_cookie_to_header(header, key, value = {})
319
+ new_header = make_delete_cookie_header(header, key, value)
320
+
321
+ add_cookie_to_header(new_header, key,
322
+ { value: '', path: nil, domain: nil,
323
+ max_age: '0',
324
+ expires: Time.at(0) }.merge(value))
325
+
386
326
  end
387
- module_function :bytesize
327
+ module_function :add_remove_cookie_to_header
388
328
 
389
329
  def rfc2822(time)
390
330
  time.rfc2822
@@ -411,34 +351,40 @@ module Rack
411
351
  # Returns nil if the header is missing or syntactically invalid.
412
352
  # Returns an empty array if none of the ranges are satisfiable.
413
353
  def byte_ranges(env, size)
354
+ warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
355
+ get_byte_ranges env['HTTP_RANGE'], size
356
+ end
357
+ module_function :byte_ranges
358
+
359
+ def get_byte_ranges(http_range, size)
414
360
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
415
- http_range = env['HTTP_RANGE']
416
361
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
417
362
  ranges = []
418
363
  $1.split(/,\s*/).each do |range_spec|
419
- return nil unless range_spec =~ /(\d*)-(\d*)/
420
- r0,r1 = $1, $2
421
- if r0.empty?
422
- return nil if r1.empty?
364
+ return nil unless range_spec.include?('-')
365
+ range = range_spec.split('-')
366
+ r0, r1 = range[0], range[1]
367
+ if r0.nil? || r0.empty?
368
+ return nil if r1.nil?
423
369
  # suffix-byte-range-spec, represents trailing suffix of file
424
370
  r0 = size - r1.to_i
425
371
  r0 = 0 if r0 < 0
426
372
  r1 = size - 1
427
373
  else
428
374
  r0 = r0.to_i
429
- if r1.empty?
375
+ if r1.nil?
430
376
  r1 = size - 1
431
377
  else
432
378
  r1 = r1.to_i
433
379
  return nil if r1 < r0 # backwards range is syntactically invalid
434
- r1 = size-1 if r1 >= size
380
+ r1 = size - 1 if r1 >= size
435
381
  end
436
382
  end
437
383
  ranges << (r0..r1) if r0 <= r1
438
384
  end
439
385
  ranges
440
386
  end
441
- module_function :byte_ranges
387
+ module_function :get_byte_ranges
442
388
 
443
389
  # Constant time string comparison.
444
390
  #
@@ -447,12 +393,12 @@ module Rack
447
393
  # on variable length plaintext strings because it could leak length info
448
394
  # via timing attacks.
449
395
  def secure_compare(a, b)
450
- return false unless bytesize(a) == bytesize(b)
396
+ return false unless a.bytesize == b.bytesize
451
397
 
452
398
  l = a.unpack("C*")
453
399
 
454
400
  r, i = 0, -1
455
- b.each_byte { |v| r |= v ^ l[i+=1] }
401
+ b.each_byte { |v| r |= v ^ l[i += 1] }
456
402
  r == 0
457
403
  end
458
404
  module_function :secure_compare
@@ -478,24 +424,28 @@ module Rack
478
424
  self.class.new(@for, app)
479
425
  end
480
426
 
481
- def context(env, app=@app)
427
+ def context(env, app = @app)
482
428
  recontext(app).call(env)
483
429
  end
484
430
  end
485
431
 
486
432
  # A case-insensitive Hash that preserves the original case of a
487
433
  # 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={})
434
+ #
435
+ # @api private
436
+ class HeaderHash < Hash # :nodoc:
437
+ def initialize(hash = {})
494
438
  super()
495
439
  @names = {}
496
440
  hash.each { |k, v| self[k] = v }
497
441
  end
498
442
 
443
+ # on dup/clone, we need to duplicate @names hash
444
+ def initialize_copy(other)
445
+ super
446
+ @names = other.names.dup
447
+ end
448
+
499
449
  def each
500
450
  super do |k, v|
501
451
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -504,7 +454,7 @@ module Rack
504
454
 
505
455
  def to_hash
506
456
  hash = {}
507
- each { |k,v| hash[k] = v }
457
+ each { |k, v| hash[k] = v }
508
458
  hash
509
459
  end
510
460
 
@@ -513,21 +463,20 @@ module Rack
513
463
  end
514
464
 
515
465
  def []=(k, v)
516
- canonical = k.downcase
466
+ canonical = k.downcase.freeze
517
467
  delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
518
- @names[k] = @names[canonical] = k
468
+ @names[canonical] = k
519
469
  super k, v
520
470
  end
521
471
 
522
472
  def delete(k)
523
473
  canonical = k.downcase
524
474
  result = super @names.delete(canonical)
525
- @names.delete_if { |name,| name.downcase == canonical }
526
475
  result
527
476
  end
528
477
 
529
478
  def include?(k)
530
- @names.include?(k) || @names.include?(k.downcase)
479
+ super || @names.include?(k.downcase)
531
480
  end
532
481
 
533
482
  alias_method :has_key?, :include?
@@ -549,56 +498,23 @@ module Rack
549
498
  other.each { |k, v| self[k] = v }
550
499
  self
551
500
  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
501
 
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
502
+ protected
503
+ def names
504
+ @names
588
505
  end
589
- hash
590
- end
591
506
  end
592
507
 
593
508
  # Every standard HTTP code mapped to the appropriate message.
594
509
  # 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,"'
510
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
511
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
512
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
598
513
  HTTP_STATUS_CODES = {
599
514
  100 => 'Continue',
600
515
  101 => 'Switching Protocols',
601
516
  102 => 'Processing',
517
+ 103 => 'Early Hints',
602
518
  200 => 'OK',
603
519
  201 => 'Created',
604
520
  202 => 'Accepted',
@@ -615,6 +531,7 @@ module Rack
615
531
  303 => 'See Other',
616
532
  304 => 'Not Modified',
617
533
  305 => 'Use Proxy',
534
+ 306 => '(Unused)',
618
535
  307 => 'Temporary Redirect',
619
536
  308 => 'Permanent Redirect',
620
537
  400 => 'Bad Request',
@@ -635,13 +552,16 @@ module Rack
635
552
  415 => 'Unsupported Media Type',
636
553
  416 => 'Range Not Satisfiable',
637
554
  417 => 'Expectation Failed',
555
+ 421 => 'Misdirected Request',
638
556
  422 => 'Unprocessable Entity',
639
557
  423 => 'Locked',
640
558
  424 => 'Failed Dependency',
559
+ 425 => 'Too Early',
641
560
  426 => 'Upgrade Required',
642
561
  428 => 'Precondition Required',
643
562
  429 => 'Too Many Requests',
644
563
  431 => 'Request Header Fields Too Large',
564
+ 451 => 'Unavailable for Legal Reasons',
645
565
  500 => 'Internal Server Error',
646
566
  501 => 'Not Implemented',
647
567
  502 => 'Bad Gateway',
@@ -651,12 +571,13 @@ module Rack
651
571
  506 => 'Variant Also Negotiates',
652
572
  507 => 'Insufficient Storage',
653
573
  508 => 'Loop Detected',
574
+ 509 => 'Bandwidth Limit Exceeded',
654
575
  510 => 'Not Extended',
655
576
  511 => 'Network Authentication Required'
656
577
  }
657
578
 
658
579
  # 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)
580
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
660
581
 
661
582
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
662
583
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -664,15 +585,13 @@ module Rack
664
585
 
665
586
  def status_code(status)
666
587
  if status.is_a?(Symbol)
667
- SYMBOL_TO_STATUS_CODE[status] || 500
588
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
668
589
  else
669
590
  status.to_i
670
591
  end
671
592
  end
672
593
  module_function :status_code
673
594
 
674
- Multipart = Rack::Multipart
675
-
676
595
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
677
596
 
678
597
  def clean_path_info(path_info)
@@ -687,9 +606,16 @@ module Rack
687
606
 
688
607
  clean.unshift '/' if parts.empty? || parts.first.empty?
689
608
 
690
- ::File.join(*clean)
609
+ ::File.join clean
691
610
  end
692
611
  module_function :clean_path_info
693
612
 
613
+ NULL_BYTE = "\0"
614
+
615
+ def valid_path?(path)
616
+ path.valid_encoding? && !path.include?(NULL_BYTE)
617
+ end
618
+ module_function :valid_path?
619
+
694
620
  end
695
621
  end