rack 2.1.4.4 → 2.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +713 -10
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +109 -38
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +35 -6
  7. data/lib/rack/auth/abstract/request.rb +0 -2
  8. data/lib/rack/auth/basic.rb +4 -5
  9. data/lib/rack/auth/digest/md5.rb +4 -4
  10. data/lib/rack/auth/digest/nonce.rb +2 -3
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +77 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +27 -19
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +45 -35
  21. data/lib/rack/directory.rb +77 -60
  22. data/lib/rack/etag.rb +4 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -1
  25. data/lib/rack/files.rb +96 -56
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +211 -179
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/media_type.rb +17 -7
  37. data/lib/rack/method_override.rb +1 -1
  38. data/lib/rack/mock.rb +54 -7
  39. data/lib/rack/multipart/generator.rb +11 -6
  40. data/lib/rack/multipart/parser.rb +17 -17
  41. data/lib/rack/multipart/uploaded_file.rb +13 -7
  42. data/lib/rack/multipart.rb +1 -1
  43. data/lib/rack/query_parser.rb +62 -16
  44. data/lib/rack/recursive.rb +1 -1
  45. data/lib/rack/reloader.rb +1 -3
  46. data/lib/rack/request.rb +184 -78
  47. data/lib/rack/response.rb +62 -19
  48. data/lib/rack/rewindable_input.rb +0 -1
  49. data/lib/rack/runtime.rb +3 -3
  50. data/lib/rack/sendfile.rb +1 -4
  51. data/lib/rack/server.rb +9 -8
  52. data/lib/rack/session/abstract/id.rb +21 -18
  53. data/lib/rack/session/cookie.rb +4 -6
  54. data/lib/rack/session/pool.rb +7 -2
  55. data/lib/rack/show_exceptions.rb +6 -8
  56. data/lib/rack/show_status.rb +5 -7
  57. data/lib/rack/static.rb +15 -7
  58. data/lib/rack/tempfile_reaper.rb +0 -2
  59. data/lib/rack/urlmap.rb +2 -5
  60. data/lib/rack/utils.rb +69 -58
  61. data/lib/rack/version.rb +29 -0
  62. data/lib/rack.rb +7 -16
  63. data/rack.gemspec +31 -29
  64. metadata +12 -16
@@ -3,10 +3,8 @@
3
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
4
4
  # bugrep: Andreas Zehnder
5
5
 
6
- require 'rack'
6
+ require_relative '../../../rack'
7
7
  require 'time'
8
- require 'rack/request'
9
- require 'rack/response'
10
8
  require 'securerandom'
11
9
  require 'digest/sha2'
12
10
 
@@ -44,18 +42,6 @@ module Rack
44
42
  # SessionHash is responsible to lazily load the session from store.
45
43
 
46
44
  class SessionHash
47
- using Module.new {
48
- refine Hash do
49
- def transform_keys(&block)
50
- hash = {}
51
- each do |key, value|
52
- hash[block.call(key)] = value
53
- end
54
- hash
55
- end
56
- end
57
- } unless {}.respond_to?(:transform_keys)
58
-
59
45
  include Enumerable
60
46
  attr_writer :id
61
47
 
@@ -98,6 +84,11 @@ module Rack
98
84
  @data[key.to_s]
99
85
  end
100
86
 
87
+ def dig(key, *keys)
88
+ load_for_read!
89
+ @data.dig(key.to_s, *keys)
90
+ end
91
+
101
92
  def fetch(key, default = Unspecified, &block)
102
93
  load_for_read!
103
94
  if default == Unspecified
@@ -201,14 +192,19 @@ module Rack
201
192
  end
202
193
 
203
194
  def stringify_keys(other)
204
- other.to_hash.transform_keys(&:to_s)
195
+ # Use transform_keys after dropping Ruby 2.4 support
196
+ hash = {}
197
+ other.to_hash.each do |key, value|
198
+ hash[key.to_s] = value
199
+ end
200
+ hash
205
201
  end
206
202
  end
207
203
 
208
204
  # ID sets up a basic framework for implementing an id based sessioning
209
205
  # service. Cookies sent to the client for maintaining sessions will only
210
- # contain an id reference. Only #find_session and #write_session are
211
- # required to be overwritten.
206
+ # contain an id reference. Only #find_session, #write_session and
207
+ # #delete_session are required to be overwritten.
212
208
  #
213
209
  # All parameters are optional.
214
210
  # * :key determines the name of the cookie, by default it is
@@ -256,6 +252,7 @@ module Rack
256
252
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
257
253
  @key = @default_options.delete(:key)
258
254
  @cookie_only = @default_options.delete(:cookie_only)
255
+ @same_site = @default_options.delete(:same_site)
259
256
  initialize_sid
260
257
  end
261
258
 
@@ -397,6 +394,12 @@ module Rack
397
394
  cookie[:value] = cookie_value(data)
398
395
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
399
396
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
397
+
398
+ if @same_site.respond_to? :call
399
+ cookie[:same_site] = @same_site.call(req, res)
400
+ else
401
+ cookie[:same_site] = @same_site
402
+ end
400
403
  set_cookie(req, res, cookie.merge!(options))
401
404
  end
402
405
  end
@@ -2,11 +2,9 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'zlib'
5
- require 'rack/request'
6
- require 'rack/response'
7
- require 'rack/session/abstract/id'
5
+ require_relative 'abstract/id'
8
6
  require 'json'
9
- require 'base64'
7
+ require 'delegate'
10
8
 
11
9
  module Rack
12
10
 
@@ -52,11 +50,11 @@ module Rack
52
50
  # Encode session cookies as Base64
53
51
  class Base64
54
52
  def encode(str)
55
- ::Base64.strict_encode64(str)
53
+ [str].pack("m0")
56
54
  end
57
55
 
58
56
  def decode(str)
59
- ::Base64.decode64(str)
57
+ str.unpack("m").first
60
58
  end
61
59
 
62
60
  # Encode session cookies as Marshaled Base64 data
@@ -5,7 +5,7 @@
5
5
  # apeiros, for session id generation, expiry setup, and threadiness
6
6
  # sergio, threadiness and bugreps
7
7
 
8
- require 'rack/session/abstract/id'
8
+ require_relative 'abstract/id'
9
9
  require 'thread'
10
10
 
11
11
  module Rack
@@ -55,6 +55,7 @@ module Rack
55
55
 
56
56
  def write_session(req, session_id, new_session, options)
57
57
  with_lock(req) do
58
+ return false unless get_session_with_fallback(session_id)
58
59
  @pool.store session_id.private_id, new_session
59
60
  session_id
60
61
  end
@@ -64,7 +65,11 @@ module Rack
64
65
  with_lock(req) do
65
66
  @pool.delete(session_id.public_id)
66
67
  @pool.delete(session_id.private_id)
67
- generate_sid unless options[:drop]
68
+ unless options[:drop]
69
+ sid = generate_sid
70
+ @pool.store(sid.private_id, {})
71
+ sid
72
+ end
68
73
  end
69
74
  end
70
75
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'ostruct'
4
4
  require 'erb'
5
- require 'rack/request'
6
- require 'rack/utils'
7
5
 
8
6
  module Rack
9
7
  # Rack::ShowExceptions catches all exceptions raised from the app it
@@ -65,12 +63,12 @@ module Rack
65
63
  def pretty(env, exception)
66
64
  req = Rack::Request.new(env)
67
65
 
68
- # This double assignment is to prevent an "unused variable" warning on
69
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
66
+ # This double assignment is to prevent an "unused variable" warning.
67
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
70
68
  path = path = (req.script_name + req.path_info).squeeze("/")
71
69
 
72
- # This double assignment is to prevent an "unused variable" warning on
73
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
70
+ # This double assignment is to prevent an "unused variable" warning.
71
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
74
72
  frames = frames = exception.backtrace.map { |line|
75
73
  frame = OpenStruct.new
76
74
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
@@ -313,7 +311,7 @@ module Rack
313
311
  <% end %>
314
312
 
315
313
  <h3 id="post-info">POST</h3>
316
- <% if req.POST and not req.POST.empty? %>
314
+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
317
315
  <table class="req">
318
316
  <thead>
319
317
  <tr>
@@ -331,7 +329,7 @@ module Rack
331
329
  </tbody>
332
330
  </table>
333
331
  <% else %>
334
- <p>No POST data.</p>
332
+ <p><%= no_post_data || "No POST data" %>.</p>
335
333
  <% end %>
336
334
 
337
335
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'rack/request'
5
- require 'rack/utils'
6
4
 
7
5
  module Rack
8
6
  # Rack::ShowStatus catches all empty responses and replaces them
@@ -20,19 +18,19 @@ module Rack
20
18
 
21
19
  def call(env)
22
20
  status, headers, body = @app.call(env)
23
- headers = Utils::HeaderHash.new(headers)
21
+ headers = Utils::HeaderHash[headers]
24
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
25
23
 
26
24
  # client or server error, or explicit message
27
25
  if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
28
- # This double assignment is to prevent an "unused variable" warning on
29
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
26
+ # This double assignment is to prevent an "unused variable" warning.
27
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
30
28
  req = req = Rack::Request.new(env)
31
29
 
32
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
33
31
 
34
- # This double assignment is to prevent an "unused variable" warning on
35
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
32
+ # This double assignment is to prevent an "unused variable" warning.
33
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
36
34
  detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
37
35
 
38
36
  body = @template.result(binding)
data/lib/rack/static.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/files"
4
- require "rack/utils"
5
-
6
- require_relative 'core_ext/regexp'
7
-
8
3
  module Rack
9
4
 
10
5
  # The Rack::Static middleware intercepts requests for static files
@@ -19,6 +14,11 @@ module Rack
19
14
  #
20
15
  # use Rack::Static, :urls => ["/media"]
21
16
  #
17
+ # Same as previous, but instead of returning 404 for missing files under
18
+ # /media, call the next middleware:
19
+ #
20
+ # use Rack::Static, :urls => ["/media"], :cascade => true
21
+ #
22
22
  # Serve all requests beginning with /css or /images from the folder "public"
23
23
  # in the current directory (ie public/css/* and public/images/*):
24
24
  #
@@ -86,13 +86,14 @@ module Rack
86
86
  # ]
87
87
  #
88
88
  class Static
89
- using ::Rack::RegexpExtensions
89
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
90
90
 
91
91
  def initialize(app, options = {})
92
92
  @app = app
93
93
  @urls = options[:urls] || ["/favicon.ico"]
94
94
  @index = options[:index]
95
95
  @gzip = options[:gzip]
96
+ @cascade = options[:cascade]
96
97
  root = options[:root] || Dir.pwd
97
98
 
98
99
  # HTTP Headers
@@ -121,8 +122,9 @@ module Rack
121
122
 
122
123
  def call(env)
123
124
  path = env[PATH_INFO]
125
+ actual_path = Utils.clean_path_info(Utils.unescape_path(path))
124
126
 
125
- if can_serve(path)
127
+ if can_serve(actual_path)
126
128
  if overwrite_file_path(path)
127
129
  env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
128
130
  elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
@@ -133,6 +135,8 @@ module Rack
133
135
 
134
136
  if response[0] == 404
135
137
  response = nil
138
+ elsif response[0] == 304
139
+ # Do nothing, leave headers as is
136
140
  else
137
141
  if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
138
142
  response[1][CONTENT_TYPE] = mime_type
@@ -144,6 +148,10 @@ module Rack
144
148
  path = env[PATH_INFO]
145
149
  response ||= @file_server.call(env)
146
150
 
151
+ if @cascade && response[0] == 404
152
+ return @app.call(env)
153
+ end
154
+
147
155
  headers = response[1]
148
156
  applicable_rules(path).each do |rule, new_headers|
149
157
  new_headers.each { |field, content| headers[field] = content }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/body_proxy'
4
-
5
3
  module Rack
6
4
 
7
5
  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
data/lib/rack/urlmap.rb CHANGED
@@ -16,9 +16,6 @@ module Rack
16
16
  # first, since they are most specific.
17
17
 
18
18
  class URLMap
19
- NEGATIVE_INFINITY = -1.0 / 0.0
20
- INFINITY = 1.0 / 0.0
21
-
22
19
  def initialize(map = {})
23
20
  remap(map)
24
21
  end
@@ -38,11 +35,11 @@ module Rack
38
35
  end
39
36
 
40
37
  location = location.chomp('/')
41
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
38
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
42
39
 
43
40
  [host, location, match, app]
44
41
  }.sort_by do |(host, location, _, _)|
45
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
46
43
  end
47
44
  end
48
45
 
data/lib/rack/utils.rb CHANGED
@@ -5,17 +5,16 @@ require 'uri'
5
5
  require 'fileutils'
6
6
  require 'set'
7
7
  require 'tempfile'
8
- require 'rack/query_parser'
9
8
  require 'time'
10
9
 
11
- require_relative 'core_ext/regexp'
10
+ require_relative 'query_parser'
12
11
 
13
12
  module Rack
14
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
15
14
  # applications adopted from all kinds of Ruby libraries.
16
15
 
17
16
  module Utils
18
- using ::Rack::RegexpExtensions
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
19
18
 
20
19
  ParameterTypeError = QueryParser::ParameterTypeError
21
20
  InvalidParameterError = QueryParser::InvalidParameterError
@@ -23,6 +22,10 @@ module Rack
23
22
  COMMON_SEP = QueryParser::COMMON_SEP
24
23
  KeySpaceConstrainedParams = QueryParser::Params
25
24
 
25
+ RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
26
+ RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
27
+ RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
28
+
26
29
  class << self
27
30
  attr_accessor :default_query_parser
28
31
  end
@@ -30,33 +33,30 @@ module Rack
30
33
  # This helps prevent a rogue client from flooding a Request.
31
34
  self.default_query_parser = QueryParser.make_default(65536, 100)
32
35
 
36
+ module_function
37
+
33
38
  # URI escapes. (CGI style space to +)
34
39
  def escape(s)
35
40
  URI.encode_www_form_component(s)
36
41
  end
37
- module_function :escape
38
42
 
39
43
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
40
44
  # true URI escaping.
41
45
  def escape_path(s)
42
- ::URI::DEFAULT_PARSER.escape s
46
+ RFC2396_PARSER.escape s
43
47
  end
44
- module_function :escape_path
45
48
 
46
49
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
47
50
  # unescaping query parameters or form components.
48
51
  def unescape_path(s)
49
- ::URI::DEFAULT_PARSER.unescape s
52
+ RFC2396_PARSER.unescape s
50
53
  end
51
- module_function :unescape_path
52
-
53
54
 
54
55
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
55
56
  # target encoding of the string returned, and it defaults to UTF-8
56
57
  def unescape(s, encoding = Encoding::UTF_8)
57
58
  URI.decode_www_form_component(s, encoding)
58
59
  end
59
- module_function :unescape
60
60
 
61
61
  class << self
62
62
  attr_accessor :multipart_total_part_limit
@@ -99,21 +99,20 @@ module Rack
99
99
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
100
100
  end
101
101
  else
102
+ # :nocov:
102
103
  def clock_time
103
104
  Time.now.to_f
104
105
  end
106
+ # :nocov:
105
107
  end
106
- module_function :clock_time
107
108
 
108
109
  def parse_query(qs, d = nil, &unescaper)
109
110
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
110
111
  end
111
- module_function :parse_query
112
112
 
113
113
  def parse_nested_query(qs, d = nil)
114
114
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
115
115
  end
116
- module_function :parse_nested_query
117
116
 
118
117
  def build_query(params)
119
118
  params.map { |k, v|
@@ -124,7 +123,6 @@ module Rack
124
123
  end
125
124
  }.join("&")
126
125
  end
127
- module_function :build_query
128
126
 
129
127
  def build_nested_query(value, prefix = nil)
130
128
  case value
@@ -143,7 +141,6 @@ module Rack
143
141
  "#{prefix}=#{escape(value)}"
144
142
  end
145
143
  end
146
- module_function :build_nested_query
147
144
 
148
145
  def q_values(q_value_header)
149
146
  q_value_header.to_s.split(',').map do |part|
@@ -155,8 +152,11 @@ module Rack
155
152
  [value, quality]
156
153
  end
157
154
  end
158
- module_function :q_values
159
155
 
156
+ # Return best accept value to use, based on the algorithm
157
+ # in RFC 2616 Section 14. If there are multiple best
158
+ # matches (same specificity and quality), the value returned
159
+ # is arbitrary.
160
160
  def best_q_match(q_value_header, available_mimes)
161
161
  values = q_values(q_value_header)
162
162
 
@@ -169,7 +169,6 @@ module Rack
169
169
  end.last
170
170
  matches && matches.first
171
171
  end
172
- module_function :best_q_match
173
172
 
174
173
  ESCAPE_HTML = {
175
174
  "&" => "&amp;",
@@ -186,22 +185,27 @@ module Rack
186
185
  def escape_html(string)
187
186
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
188
187
  end
189
- module_function :escape_html
190
188
 
191
189
  def select_best_encoding(available_encodings, accept_encoding)
192
190
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
193
191
 
194
- expanded_accept_encoding =
195
- accept_encoding.each_with_object([]) do |(m, q), list|
196
- if m == "*"
197
- (available_encodings - accept_encoding.map(&:first))
198
- .each { |m2| list << [m2, q] }
199
- else
200
- list << [m, q]
192
+ expanded_accept_encoding = []
193
+
194
+ accept_encoding.each do |m, q|
195
+ preference = available_encodings.index(m) || available_encodings.size
196
+
197
+ if m == "*"
198
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
199
+ expanded_accept_encoding << [m2, q, preference]
201
200
  end
201
+ else
202
+ expanded_accept_encoding << [m, q, preference]
202
203
  end
204
+ end
203
205
 
204
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
206
+ encoding_candidates = expanded_accept_encoding
207
+ .sort_by { |_, q, p| [-q, p] }
208
+ .map!(&:first)
205
209
 
206
210
  unless encoding_candidates.include?("identity")
207
211
  encoding_candidates.push("identity")
@@ -213,27 +217,23 @@ module Rack
213
217
 
214
218
  (encoding_candidates & available_encodings)[0]
215
219
  end
216
- module_function :select_best_encoding
217
220
 
218
221
  def parse_cookies(env)
219
222
  parse_cookies_header env[HTTP_COOKIE]
220
223
  end
221
- module_function :parse_cookies
222
224
 
223
225
  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.
226
+ # According to RFC 6265:
227
+ # The syntax for cookie headers only supports semicolons
228
+ # User Agent -> Server ==
229
+ # Cookie: SID=31d4d96e407aad42; lang=en-US
229
230
  return {} unless header
230
- header.split(/[;,] */n).each_with_object({}) do |cookie, cookies|
231
+ header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
231
232
  next if cookie.empty?
232
233
  key, value = cookie.split('=', 2)
233
234
  cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
234
235
  end
235
236
  end
236
- module_function :parse_cookies_header
237
237
 
238
238
  def add_cookie_to_header(header, key, value)
239
239
  case value
@@ -275,13 +275,11 @@ module Rack
275
275
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
276
276
  end
277
277
  end
278
- module_function :add_cookie_to_header
279
278
 
280
279
  def set_cookie_header!(header, key, value)
281
280
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
282
281
  nil
283
282
  end
284
- module_function :set_cookie_header!
285
283
 
286
284
  def make_delete_cookie_header(header, key, value)
287
285
  case header
@@ -293,25 +291,30 @@ module Rack
293
291
  cookies = header
294
292
  end
295
293
 
296
- regexp = if value[:domain]
297
- /\A#{escape(key)}=.*domain=#{value[:domain]}/
298
- elsif value[:path]
299
- /\A#{escape(key)}=.*path=#{value[:path]}/
294
+ key = escape(key)
295
+ domain = value[:domain]
296
+ path = value[:path]
297
+ regexp = if domain
298
+ if path
299
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
300
+ else
301
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
302
+ end
303
+ elsif path
304
+ /\A#{key}=.*path=#{path}(?:;|$)/
300
305
  else
301
- /\A#{escape(key)}=/
306
+ /\A#{key}=/
302
307
  end
303
308
 
304
309
  cookies.reject! { |cookie| regexp.match? cookie }
305
310
 
306
311
  cookies.join("\n")
307
312
  end
308
- module_function :make_delete_cookie_header
309
313
 
310
314
  def delete_cookie_header!(header, key, value = {})
311
315
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
312
316
  nil
313
317
  end
314
- module_function :delete_cookie_header!
315
318
 
316
319
  # Adds a cookie that will *remove* a cookie from the client. Hence the
317
320
  # strange method name.
@@ -324,12 +327,10 @@ module Rack
324
327
  expires: Time.at(0) }.merge(value))
325
328
 
326
329
  end
327
- module_function :add_remove_cookie_to_header
328
330
 
329
331
  def rfc2822(time)
330
332
  time.rfc2822
331
333
  end
332
- module_function :rfc2822
333
334
 
334
335
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
335
336
  # of '% %b %Y'.
@@ -341,11 +342,10 @@ module Rack
341
342
  # weekday and month.
342
343
  #
343
344
  def rfc2109(time)
344
- wday = Time::RFC2822_DAY_NAME[time.wday]
345
- mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
345
+ wday = RFC2822_DAY_NAME[time.wday]
346
+ mon = RFC2822_MONTH_NAME[time.mon - 1]
346
347
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
347
348
  end
348
- module_function :rfc2109
349
349
 
350
350
  # Parses the "Range:" header, if present, into an array of Range objects.
351
351
  # Returns nil if the header is missing or syntactically invalid.
@@ -354,7 +354,6 @@ module Rack
354
354
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
355
355
  get_byte_ranges env['HTTP_RANGE'], size
356
356
  end
357
- module_function :byte_ranges
358
357
 
359
358
  def get_byte_ranges(http_range, size)
360
359
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -382,9 +381,11 @@ module Rack
382
381
  end
383
382
  ranges << (r0..r1) if r0 <= r1
384
383
  end
384
+
385
+ return [] if ranges.map(&:size).inject(0, :+) > size
386
+
385
387
  ranges
386
388
  end
387
- module_function :get_byte_ranges
388
389
 
389
390
  # Constant time string comparison.
390
391
  #
@@ -401,7 +402,6 @@ module Rack
401
402
  b.each_byte { |v| r |= v ^ l[i += 1] }
402
403
  r == 0
403
404
  end
404
- module_function :secure_compare
405
405
 
406
406
  # Context allows the use of a compatible middleware at different points
407
407
  # in a request handling stack. A compatible middleware must define
@@ -434,6 +434,14 @@ module Rack
434
434
  #
435
435
  # @api private
436
436
  class HeaderHash < Hash # :nodoc:
437
+ def self.[](headers)
438
+ if headers.is_a?(HeaderHash) && !headers.frozen?
439
+ return headers
440
+ else
441
+ return self.new(headers)
442
+ end
443
+ end
444
+
437
445
  def initialize(hash = {})
438
446
  super()
439
447
  @names = {}
@@ -446,6 +454,12 @@ module Rack
446
454
  @names = other.names.dup
447
455
  end
448
456
 
457
+ # on clear, we need to clear @names hash
458
+ def clear
459
+ super
460
+ @names.clear
461
+ end
462
+
449
463
  def each
450
464
  super do |k, v|
451
465
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -590,7 +604,6 @@ module Rack
590
604
  status.to_i
591
605
  end
592
606
  end
593
- module_function :status_code
594
607
 
595
608
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
596
609
 
@@ -604,18 +617,16 @@ module Rack
604
617
  part == '..' ? clean.pop : clean << part
605
618
  end
606
619
 
607
- clean.unshift '/' if parts.empty? || parts.first.empty?
608
-
609
- ::File.join clean
620
+ clean_path = clean.join(::File::SEPARATOR)
621
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
622
+ clean_path
610
623
  end
611
- module_function :clean_path_info
612
624
 
613
625
  NULL_BYTE = "\0"
614
626
 
615
627
  def valid_path?(path)
616
628
  path.valid_encoding? && !path.include?(NULL_BYTE)
617
629
  end
618
- module_function :valid_path?
619
630
 
620
631
  end
621
632
  end