rack 2.2.7 → 3.1.8
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.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +341 -78
 - data/CONTRIBUTING.md +63 -55
 - data/MIT-LICENSE +1 -1
 - data/README.md +328 -0
 - data/SPEC.rdoc +213 -136
 - data/lib/rack/auth/abstract/handler.rb +3 -1
 - data/lib/rack/auth/abstract/request.rb +3 -1
 - data/lib/rack/auth/basic.rb +1 -4
 - data/lib/rack/bad_request.rb +8 -0
 - data/lib/rack/body_proxy.rb +21 -3
 - data/lib/rack/builder.rb +102 -69
 - data/lib/rack/cascade.rb +2 -3
 - data/lib/rack/common_logger.rb +23 -18
 - data/lib/rack/conditional_get.rb +18 -15
 - data/lib/rack/constants.rb +67 -0
 - data/lib/rack/content_length.rb +12 -16
 - data/lib/rack/content_type.rb +8 -5
 - data/lib/rack/deflater.rb +40 -26
 - data/lib/rack/directory.rb +9 -3
 - data/lib/rack/etag.rb +14 -23
 - data/lib/rack/events.rb +4 -0
 - data/lib/rack/files.rb +15 -17
 - data/lib/rack/head.rb +9 -8
 - data/lib/rack/headers.rb +238 -0
 - data/lib/rack/lint.rb +866 -681
 - data/lib/rack/lock.rb +2 -5
 - data/lib/rack/logger.rb +3 -0
 - data/lib/rack/media_type.rb +9 -4
 - data/lib/rack/method_override.rb +5 -1
 - data/lib/rack/mime.rb +14 -5
 - data/lib/rack/mock.rb +1 -271
 - data/lib/rack/mock_request.rb +161 -0
 - data/lib/rack/mock_response.rb +124 -0
 - data/lib/rack/multipart/generator.rb +7 -5
 - data/lib/rack/multipart/parser.rb +217 -91
 - data/lib/rack/multipart/uploaded_file.rb +4 -0
 - data/lib/rack/multipart.rb +53 -40
 - data/lib/rack/null_logger.rb +9 -0
 - data/lib/rack/query_parser.rb +81 -102
 - data/lib/rack/recursive.rb +2 -0
 - data/lib/rack/reloader.rb +0 -2
 - data/lib/rack/request.rb +260 -123
 - data/lib/rack/response.rb +151 -66
 - data/lib/rack/rewindable_input.rb +24 -5
 - data/lib/rack/runtime.rb +7 -6
 - data/lib/rack/sendfile.rb +30 -25
 - data/lib/rack/show_exceptions.rb +21 -4
 - data/lib/rack/show_status.rb +17 -7
 - data/lib/rack/static.rb +8 -8
 - data/lib/rack/tempfile_reaper.rb +15 -4
 - data/lib/rack/urlmap.rb +3 -1
 - data/lib/rack/utils.rb +240 -237
 - data/lib/rack/version.rb +1 -9
 - data/lib/rack.rb +13 -89
 - metadata +15 -41
 - data/README.rdoc +0 -320
 - data/Rakefile +0 -130
 - data/bin/rackup +0 -5
 - data/contrib/rack.png +0 -0
 - data/contrib/rack.svg +0 -150
 - data/contrib/rack_logo.svg +0 -164
 - data/contrib/rdoc.css +0 -412
 - data/example/lobster.ru +0 -6
 - data/example/protectedlobster.rb +0 -16
 - data/example/protectedlobster.ru +0 -10
 - data/lib/rack/auth/digest/md5.rb +0 -131
 - data/lib/rack/auth/digest/nonce.rb +0 -54
 - data/lib/rack/auth/digest/params.rb +0 -54
 - data/lib/rack/auth/digest/request.rb +0 -43
 - data/lib/rack/chunked.rb +0 -117
 - data/lib/rack/core_ext/regexp.rb +0 -14
 - data/lib/rack/file.rb +0 -7
 - data/lib/rack/handler/cgi.rb +0 -59
 - data/lib/rack/handler/fastcgi.rb +0 -100
 - data/lib/rack/handler/lsws.rb +0 -61
 - data/lib/rack/handler/scgi.rb +0 -71
 - data/lib/rack/handler/thin.rb +0 -36
 - data/lib/rack/handler/webrick.rb +0 -129
 - data/lib/rack/handler.rb +0 -104
 - data/lib/rack/lobster.rb +0 -70
 - data/lib/rack/server.rb +0 -466
 - data/lib/rack/session/abstract/id.rb +0 -523
 - data/lib/rack/session/cookie.rb +0 -203
 - data/lib/rack/session/memcache.rb +0 -10
 - data/lib/rack/session/pool.rb +0 -85
 - data/rack.gemspec +0 -46
 
    
        data/lib/rack/utils.rb
    CHANGED
    
    | 
         @@ -6,31 +6,33 @@ require 'fileutils' 
     | 
|
| 
       6 
6 
     | 
    
         
             
            require 'set'
         
     | 
| 
       7 
7 
     | 
    
         
             
            require 'tempfile'
         
     | 
| 
       8 
8 
     | 
    
         
             
            require 'time'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'erb'
         
     | 
| 
       9 
10 
     | 
    
         | 
| 
       10 
11 
     | 
    
         
             
            require_relative 'query_parser'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative 'mime'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require_relative 'headers'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require_relative 'constants'
         
     | 
| 
       11 
15 
     | 
    
         | 
| 
       12 
16 
     | 
    
         
             
            module Rack
         
     | 
| 
       13 
17 
     | 
    
         
             
              # Rack::Utils contains a grab-bag of useful methods for writing web
         
     | 
| 
       14 
18 
     | 
    
         
             
              # applications adopted from all kinds of Ruby libraries.
         
     | 
| 
       15 
19 
     | 
    
         | 
| 
       16 
20 
     | 
    
         
             
              module Utils
         
     | 
| 
       17 
     | 
    
         
            -
                (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
21 
     | 
    
         
             
                ParameterTypeError = QueryParser::ParameterTypeError
         
     | 
| 
       20 
22 
     | 
    
         
             
                InvalidParameterError = QueryParser::InvalidParameterError
         
     | 
| 
      
 23 
     | 
    
         
            +
                ParamsTooDeepError = QueryParser::ParamsTooDeepError
         
     | 
| 
       21 
24 
     | 
    
         
             
                DEFAULT_SEP = QueryParser::DEFAULT_SEP
         
     | 
| 
       22 
25 
     | 
    
         
             
                COMMON_SEP = QueryParser::COMMON_SEP
         
     | 
| 
       23 
26 
     | 
    
         
             
                KeySpaceConstrainedParams = QueryParser::Params
         
     | 
| 
       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 
     | 
    
         
            +
                URI_PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
         
     | 
| 
       27 
28 
     | 
    
         | 
| 
       28 
29 
     | 
    
         
             
                class << self
         
     | 
| 
       29 
30 
     | 
    
         
             
                  attr_accessor :default_query_parser
         
     | 
| 
       30 
31 
     | 
    
         
             
                end
         
     | 
| 
       31 
     | 
    
         
            -
                # The default  
     | 
| 
       32 
     | 
    
         
            -
                # This helps prevent a rogue client from  
     | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
      
 32 
     | 
    
         
            +
                # The default amount of nesting to allowed by hash parameters.
         
     | 
| 
      
 33 
     | 
    
         
            +
                # This helps prevent a rogue client from triggering a possible stack overflow
         
     | 
| 
      
 34 
     | 
    
         
            +
                # when parsing parameters.
         
     | 
| 
      
 35 
     | 
    
         
            +
                self.default_query_parser = QueryParser.make_default(32)
         
     | 
| 
       34 
36 
     | 
    
         | 
| 
       35 
37 
     | 
    
         
             
                module_function
         
     | 
| 
       36 
38 
     | 
    
         | 
| 
         @@ -42,13 +44,13 @@ module Rack 
     | 
|
| 
       42 
44 
     | 
    
         
             
                # Like URI escaping, but with %20 instead of +. Strictly speaking this is
         
     | 
| 
       43 
45 
     | 
    
         
             
                # true URI escaping.
         
     | 
| 
       44 
46 
     | 
    
         
             
                def escape_path(s)
         
     | 
| 
       45 
     | 
    
         
            -
                   
     | 
| 
      
 47 
     | 
    
         
            +
                  URI_PARSER.escape s
         
     | 
| 
       46 
48 
     | 
    
         
             
                end
         
     | 
| 
       47 
49 
     | 
    
         | 
| 
       48 
50 
     | 
    
         
             
                # Unescapes the **path** component of a URI.  See Rack::Utils.unescape for
         
     | 
| 
       49 
51 
     | 
    
         
             
                # unescaping query parameters or form components.
         
     | 
| 
       50 
52 
     | 
    
         
             
                def unescape_path(s)
         
     | 
| 
       51 
     | 
    
         
            -
                   
     | 
| 
      
 53 
     | 
    
         
            +
                  URI_PARSER.unescape s
         
     | 
| 
       52 
54 
     | 
    
         
             
                end
         
     | 
| 
       53 
55 
     | 
    
         | 
| 
       54 
56 
     | 
    
         
             
                # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
         
     | 
| 
         @@ -85,14 +87,6 @@ module Rack 
     | 
|
| 
       85 
87 
     | 
    
         
             
                  self.default_query_parser = self.default_query_parser.new_depth_limit(v)
         
     | 
| 
       86 
88 
     | 
    
         
             
                end
         
     | 
| 
       87 
89 
     | 
    
         | 
| 
       88 
     | 
    
         
            -
                def self.key_space_limit
         
     | 
| 
       89 
     | 
    
         
            -
                  default_query_parser.key_space_limit
         
     | 
| 
       90 
     | 
    
         
            -
                end
         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
                def self.key_space_limit=(v)
         
     | 
| 
       93 
     | 
    
         
            -
                  self.default_query_parser = self.default_query_parser.new_space_limit(v)
         
     | 
| 
       94 
     | 
    
         
            -
                end
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
90 
     | 
    
         
             
                if defined?(Process::CLOCK_MONOTONIC)
         
     | 
| 
       97 
91 
     | 
    
         
             
                  def clock_time
         
     | 
| 
       98 
92 
     | 
    
         
             
                    Process.clock_gettime(Process::CLOCK_MONOTONIC)
         
     | 
| 
         @@ -131,19 +125,19 @@ module Rack 
     | 
|
| 
       131 
125 
     | 
    
         
             
                    }.join("&")
         
     | 
| 
       132 
126 
     | 
    
         
             
                  when Hash
         
     | 
| 
       133 
127 
     | 
    
         
             
                    value.map { |k, v|
         
     | 
| 
       134 
     | 
    
         
            -
                      build_nested_query(v, prefix ? "#{prefix}[#{ 
     | 
| 
      
 128 
     | 
    
         
            +
                      build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
         
     | 
| 
       135 
129 
     | 
    
         
             
                    }.delete_if(&:empty?).join('&')
         
     | 
| 
       136 
130 
     | 
    
         
             
                  when nil
         
     | 
| 
       137 
     | 
    
         
            -
                    prefix
         
     | 
| 
      
 131 
     | 
    
         
            +
                    escape(prefix)
         
     | 
| 
       138 
132 
     | 
    
         
             
                  else
         
     | 
| 
       139 
133 
     | 
    
         
             
                    raise ArgumentError, "value must be a Hash" if prefix.nil?
         
     | 
| 
       140 
     | 
    
         
            -
                    "#{prefix}=#{escape(value)}"
         
     | 
| 
      
 134 
     | 
    
         
            +
                    "#{escape(prefix)}=#{escape(value)}"
         
     | 
| 
       141 
135 
     | 
    
         
             
                  end
         
     | 
| 
       142 
136 
     | 
    
         
             
                end
         
     | 
| 
       143 
137 
     | 
    
         | 
| 
       144 
138 
     | 
    
         
             
                def q_values(q_value_header)
         
     | 
| 
       145 
     | 
    
         
            -
                  q_value_header.to_s.split( 
     | 
| 
       146 
     | 
    
         
            -
                    value, parameters = part.split( 
     | 
| 
      
 139 
     | 
    
         
            +
                  q_value_header.to_s.split(',').map do |part|
         
     | 
| 
      
 140 
     | 
    
         
            +
                    value, parameters = part.split(';', 2).map(&:strip)
         
     | 
| 
       147 
141 
     | 
    
         
             
                    quality = 1.0
         
     | 
| 
       148 
142 
     | 
    
         
             
                    if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
         
     | 
| 
       149 
143 
     | 
    
         
             
                      quality = md[1].to_f
         
     | 
| 
         @@ -152,6 +146,20 @@ module Rack 
     | 
|
| 
       152 
146 
     | 
    
         
             
                  end
         
     | 
| 
       153 
147 
     | 
    
         
             
                end
         
     | 
| 
       154 
148 
     | 
    
         | 
| 
      
 149 
     | 
    
         
            +
                def forwarded_values(forwarded_header)
         
     | 
| 
      
 150 
     | 
    
         
            +
                  return nil unless forwarded_header
         
     | 
| 
      
 151 
     | 
    
         
            +
                  forwarded_header = forwarded_header.to_s.gsub("\n", ";")
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                  forwarded_header.split(';').each_with_object({}) do |field, values|
         
     | 
| 
      
 154 
     | 
    
         
            +
                    field.split(',').each do |pair|
         
     | 
| 
      
 155 
     | 
    
         
            +
                      pair = pair.split('=').map(&:strip).join('=')
         
     | 
| 
      
 156 
     | 
    
         
            +
                      return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
         
     | 
| 
      
 157 
     | 
    
         
            +
                      (values[$1.downcase.to_sym] ||= []) << $2
         
     | 
| 
      
 158 
     | 
    
         
            +
                    end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
      
 160 
     | 
    
         
            +
                end
         
     | 
| 
      
 161 
     | 
    
         
            +
                module_function :forwarded_values
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
       155 
163 
     | 
    
         
             
                # Return best accept value to use, based on the algorithm
         
     | 
| 
       156 
164 
     | 
    
         
             
                # in RFC 2616 Section 14.  If there are multiple best
         
     | 
| 
       157 
165 
     | 
    
         
             
                # matches (same specificity and quality), the value returned
         
     | 
| 
         @@ -166,23 +174,19 @@ module Rack 
     | 
|
| 
       166 
174 
     | 
    
         
             
                  end.compact.sort_by do |match, quality|
         
     | 
| 
       167 
175 
     | 
    
         
             
                    (match.split('/', 2).count('*') * -10) + quality
         
     | 
| 
       168 
176 
     | 
    
         
             
                  end.last
         
     | 
| 
       169 
     | 
    
         
            -
                  matches 
     | 
| 
      
 177 
     | 
    
         
            +
                  matches&.first
         
     | 
| 
       170 
178 
     | 
    
         
             
                end
         
     | 
| 
       171 
179 
     | 
    
         | 
| 
       172 
     | 
    
         
            -
                 
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
             
     | 
| 
       175 
     | 
    
         
            -
                   
     | 
| 
       176 
     | 
    
         
            -
             
     | 
| 
       177 
     | 
    
         
            -
                  ' 
     | 
| 
       178 
     | 
    
         
            -
                   
     | 
| 
       179 
     | 
    
         
            -
             
     | 
| 
       180 
     | 
    
         
            -
             
     | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
                # Escape ampersands, brackets and quotes to their HTML/XML entities.
         
     | 
| 
       184 
     | 
    
         
            -
                def escape_html(string)
         
     | 
| 
       185 
     | 
    
         
            -
                  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
         
     | 
| 
      
 180 
     | 
    
         
            +
                # Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
         
     | 
| 
      
 181 
     | 
    
         
            +
                # doesn't get monkey-patched by rails
         
     | 
| 
      
 182 
     | 
    
         
            +
                if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
         
     | 
| 
      
 183 
     | 
    
         
            +
                  define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
         
     | 
| 
      
 184 
     | 
    
         
            +
                else
         
     | 
| 
      
 185 
     | 
    
         
            +
                  require 'cgi/escape'
         
     | 
| 
      
 186 
     | 
    
         
            +
                  # Escape ampersands, brackets and quotes to their HTML/XML entities.
         
     | 
| 
      
 187 
     | 
    
         
            +
                  def escape_html(string)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    CGI.escapeHTML(string.to_s)
         
     | 
| 
      
 189 
     | 
    
         
            +
                  end
         
     | 
| 
       186 
190 
     | 
    
         
             
                end
         
     | 
| 
       187 
191 
     | 
    
         | 
| 
       188 
192 
     | 
    
         
             
                def select_best_encoding(available_encodings, accept_encoding)
         
     | 
| 
         @@ -217,145 +221,199 @@ module Rack 
     | 
|
| 
       217 
221 
     | 
    
         
             
                  (encoding_candidates & available_encodings)[0]
         
     | 
| 
       218 
222 
     | 
    
         
             
                end
         
     | 
| 
       219 
223 
     | 
    
         | 
| 
       220 
     | 
    
         
            -
                 
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
       222 
     | 
    
         
            -
                 
     | 
| 
      
 224 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 225 
     | 
    
         
            +
                #   parse_cookies_header(value) -> hash
         
     | 
| 
      
 226 
     | 
    
         
            +
                #
         
     | 
| 
      
 227 
     | 
    
         
            +
                # Parse cookies from the provided header +value+ according to RFC6265. The
         
     | 
| 
      
 228 
     | 
    
         
            +
                # syntax for cookie headers only supports semicolons. Returns a map of
         
     | 
| 
      
 229 
     | 
    
         
            +
                # cookie +key+ to cookie +value+.
         
     | 
| 
      
 230 
     | 
    
         
            +
                #
         
     | 
| 
      
 231 
     | 
    
         
            +
                #   parse_cookies_header('myname=myvalue; max-age=0')
         
     | 
| 
      
 232 
     | 
    
         
            +
                #   # => {"myname"=>"myvalue", "max-age"=>"0"}
         
     | 
| 
      
 233 
     | 
    
         
            +
                #
         
     | 
| 
      
 234 
     | 
    
         
            +
                def parse_cookies_header(value)
         
     | 
| 
      
 235 
     | 
    
         
            +
                  return {} unless value
         
     | 
| 
       223 
236 
     | 
    
         | 
| 
       224 
     | 
    
         
            -
             
     | 
| 
       225 
     | 
    
         
            -
                  # According to RFC 6265:
         
     | 
| 
       226 
     | 
    
         
            -
                  # The syntax for cookie headers only supports semicolons
         
     | 
| 
       227 
     | 
    
         
            -
                  # User Agent -> Server ==
         
     | 
| 
       228 
     | 
    
         
            -
                  # Cookie: SID=31d4d96e407aad42; lang=en-US
         
     | 
| 
       229 
     | 
    
         
            -
                  return {} unless header
         
     | 
| 
       230 
     | 
    
         
            -
                  header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
         
     | 
| 
      
 237 
     | 
    
         
            +
                  value.split(/; */n).each_with_object({}) do |cookie, cookies|
         
     | 
| 
       231 
238 
     | 
    
         
             
                    next if cookie.empty?
         
     | 
| 
       232 
239 
     | 
    
         
             
                    key, value = cookie.split('=', 2)
         
     | 
| 
       233 
240 
     | 
    
         
             
                    cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
         
     | 
| 
       234 
241 
     | 
    
         
             
                  end
         
     | 
| 
       235 
242 
     | 
    
         
             
                end
         
     | 
| 
       236 
243 
     | 
    
         | 
| 
       237 
     | 
    
         
            -
                 
     | 
| 
      
 244 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 245 
     | 
    
         
            +
                #   parse_cookies(env) -> hash
         
     | 
| 
      
 246 
     | 
    
         
            +
                #
         
     | 
| 
      
 247 
     | 
    
         
            +
                # Parse cookies from the provided request environment using
         
     | 
| 
      
 248 
     | 
    
         
            +
                # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
         
     | 
| 
      
 249 
     | 
    
         
            +
                #
         
     | 
| 
      
 250 
     | 
    
         
            +
                #   parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
         
     | 
| 
      
 251 
     | 
    
         
            +
                #   # => {'myname' => 'myvalue'}
         
     | 
| 
      
 252 
     | 
    
         
            +
                #
         
     | 
| 
      
 253 
     | 
    
         
            +
                def parse_cookies(env)
         
     | 
| 
      
 254 
     | 
    
         
            +
                  parse_cookies_header env[HTTP_COOKIE]
         
     | 
| 
      
 255 
     | 
    
         
            +
                end
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                # A valid cookie key according to RFC2616.
         
     | 
| 
      
 258 
     | 
    
         
            +
                # A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
         
     | 
| 
      
 259 
     | 
    
         
            +
                VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
         
     | 
| 
      
 260 
     | 
    
         
            +
                private_constant :VALID_COOKIE_KEY
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
                private def escape_cookie_key(key)
         
     | 
| 
      
 263 
     | 
    
         
            +
                  if key =~ VALID_COOKIE_KEY
         
     | 
| 
      
 264 
     | 
    
         
            +
                    key
         
     | 
| 
      
 265 
     | 
    
         
            +
                  else
         
     | 
| 
      
 266 
     | 
    
         
            +
                    warn "Cookie key #{key.inspect} is not valid according to RFC2616; it will be escaped. This behaviour is deprecated and will be removed in a future version of Rack.", uplevel: 2
         
     | 
| 
      
 267 
     | 
    
         
            +
                    escape(key)
         
     | 
| 
      
 268 
     | 
    
         
            +
                  end
         
     | 
| 
      
 269 
     | 
    
         
            +
                end
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 272 
     | 
    
         
            +
                #   set_cookie_header(key, value) -> encoded string
         
     | 
| 
      
 273 
     | 
    
         
            +
                #
         
     | 
| 
      
 274 
     | 
    
         
            +
                # Generate an encoded string using the provided +key+ and +value+ suitable
         
     | 
| 
      
 275 
     | 
    
         
            +
                # for the +set-cookie+ header according to RFC6265. The +value+ may be an
         
     | 
| 
      
 276 
     | 
    
         
            +
                # instance of either +String+ or +Hash+.
         
     | 
| 
      
 277 
     | 
    
         
            +
                #
         
     | 
| 
      
 278 
     | 
    
         
            +
                # If the cookie +value+ is an instance of +Hash+, it considers the following
         
     | 
| 
      
 279 
     | 
    
         
            +
                # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
         
     | 
| 
      
 280 
     | 
    
         
            +
                # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
         
     | 
| 
      
 281 
     | 
    
         
            +
                # details about the interpretation of these fields, consult
         
     | 
| 
      
 282 
     | 
    
         
            +
                # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
         
     | 
| 
      
 283 
     | 
    
         
            +
                #
         
     | 
| 
      
 284 
     | 
    
         
            +
                # An extra cookie attribute +escape_key+ can be provided to control whether
         
     | 
| 
      
 285 
     | 
    
         
            +
                # or not the cookie key is URL encoded. If explicitly set to +false+, the
         
     | 
| 
      
 286 
     | 
    
         
            +
                # cookie key name will not be url encoded (escaped). The default is +true+.
         
     | 
| 
      
 287 
     | 
    
         
            +
                #
         
     | 
| 
      
 288 
     | 
    
         
            +
                #   set_cookie_header("myname", "myvalue")
         
     | 
| 
      
 289 
     | 
    
         
            +
                #   # => "myname=myvalue"
         
     | 
| 
      
 290 
     | 
    
         
            +
                #
         
     | 
| 
      
 291 
     | 
    
         
            +
                #   set_cookie_header("myname", {value: "myvalue", max_age: 10})
         
     | 
| 
      
 292 
     | 
    
         
            +
                #   # => "myname=myvalue; max-age=10"
         
     | 
| 
      
 293 
     | 
    
         
            +
                #
         
     | 
| 
      
 294 
     | 
    
         
            +
                def set_cookie_header(key, value)
         
     | 
| 
       238 
295 
     | 
    
         
             
                  case value
         
     | 
| 
       239 
296 
     | 
    
         
             
                  when Hash
         
     | 
| 
      
 297 
     | 
    
         
            +
                    key = escape_cookie_key(key) unless value[:escape_key] == false
         
     | 
| 
       240 
298 
     | 
    
         
             
                    domain  = "; domain=#{value[:domain]}"   if value[:domain]
         
     | 
| 
       241 
299 
     | 
    
         
             
                    path    = "; path=#{value[:path]}"       if value[:path]
         
     | 
| 
       242 
300 
     | 
    
         
             
                    max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
         
     | 
| 
       243 
301 
     | 
    
         
             
                    expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
         
     | 
| 
       244 
302 
     | 
    
         
             
                    secure = "; secure"  if value[:secure]
         
     | 
| 
       245 
     | 
    
         
            -
                    httponly = ";  
     | 
| 
      
 303 
     | 
    
         
            +
                    httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
         
     | 
| 
       246 
304 
     | 
    
         
             
                    same_site =
         
     | 
| 
       247 
305 
     | 
    
         
             
                      case value[:same_site]
         
     | 
| 
       248 
306 
     | 
    
         
             
                      when false, nil
         
     | 
| 
       249 
307 
     | 
    
         
             
                        nil
         
     | 
| 
       250 
308 
     | 
    
         
             
                      when :none, 'None', :None
         
     | 
| 
       251 
     | 
    
         
            -
                        ';  
     | 
| 
      
 309 
     | 
    
         
            +
                        '; samesite=none'
         
     | 
| 
       252 
310 
     | 
    
         
             
                      when :lax, 'Lax', :Lax
         
     | 
| 
       253 
     | 
    
         
            -
                        ';  
     | 
| 
      
 311 
     | 
    
         
            +
                        '; samesite=lax'
         
     | 
| 
       254 
312 
     | 
    
         
             
                      when true, :strict, 'Strict', :Strict
         
     | 
| 
       255 
     | 
    
         
            -
                        ';  
     | 
| 
      
 313 
     | 
    
         
            +
                        '; samesite=strict'
         
     | 
| 
       256 
314 
     | 
    
         
             
                      else
         
     | 
| 
       257 
     | 
    
         
            -
                        raise ArgumentError, "Invalid  
     | 
| 
      
 315 
     | 
    
         
            +
                        raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
         
     | 
| 
       258 
316 
     | 
    
         
             
                      end
         
     | 
| 
      
 317 
     | 
    
         
            +
                    partitioned = "; partitioned" if value[:partitioned]
         
     | 
| 
       259 
318 
     | 
    
         
             
                    value = value[:value]
         
     | 
| 
      
 319 
     | 
    
         
            +
                  else
         
     | 
| 
      
 320 
     | 
    
         
            +
                    key = escape_cookie_key(key)
         
     | 
| 
       260 
321 
     | 
    
         
             
                  end
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
       261 
323 
     | 
    
         
             
                  value = [value] unless Array === value
         
     | 
| 
       262 
324 
     | 
    
         | 
| 
       263 
     | 
    
         
            -
                   
     | 
| 
       264 
     | 
    
         
            -
                    "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
         
     | 
| 
      
 325 
     | 
    
         
            +
                  return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
         
     | 
| 
      
 326 
     | 
    
         
            +
                    "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
         
     | 
| 
      
 327 
     | 
    
         
            +
                end
         
     | 
| 
       265 
328 
     | 
    
         | 
| 
       266 
     | 
    
         
            -
             
     | 
| 
       267 
     | 
    
         
            -
             
     | 
| 
       268 
     | 
    
         
            -
             
     | 
| 
       269 
     | 
    
         
            -
             
     | 
| 
       270 
     | 
    
         
            -
             
     | 
| 
       271 
     | 
    
         
            -
             
     | 
| 
       272 
     | 
    
         
            -
             
     | 
| 
      
 329 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 330 
     | 
    
         
            +
                #   set_cookie_header!(headers, key, value) -> header value
         
     | 
| 
      
 331 
     | 
    
         
            +
                #
         
     | 
| 
      
 332 
     | 
    
         
            +
                # Append a cookie in the specified headers with the given cookie +key+ and
         
     | 
| 
      
 333 
     | 
    
         
            +
                # +value+ using set_cookie_header.
         
     | 
| 
      
 334 
     | 
    
         
            +
                #
         
     | 
| 
      
 335 
     | 
    
         
            +
                # If the headers already contains a +set-cookie+ key, it will be converted
         
     | 
| 
      
 336 
     | 
    
         
            +
                # to an +Array+ if not already, and appended to.
         
     | 
| 
      
 337 
     | 
    
         
            +
                def set_cookie_header!(headers, key, value)
         
     | 
| 
      
 338 
     | 
    
         
            +
                  if header = headers[SET_COOKIE]
         
     | 
| 
      
 339 
     | 
    
         
            +
                    if header.is_a?(Array)
         
     | 
| 
      
 340 
     | 
    
         
            +
                      header << set_cookie_header(key, value)
         
     | 
| 
      
 341 
     | 
    
         
            +
                    else
         
     | 
| 
      
 342 
     | 
    
         
            +
                      headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
         
     | 
| 
      
 343 
     | 
    
         
            +
                    end
         
     | 
| 
       273 
344 
     | 
    
         
             
                  else
         
     | 
| 
       274 
     | 
    
         
            -
                     
     | 
| 
      
 345 
     | 
    
         
            +
                    headers[SET_COOKIE] = set_cookie_header(key, value)
         
     | 
| 
       275 
346 
     | 
    
         
             
                  end
         
     | 
| 
       276 
347 
     | 
    
         
             
                end
         
     | 
| 
       277 
348 
     | 
    
         | 
| 
       278 
     | 
    
         
            -
                 
     | 
| 
       279 
     | 
    
         
            -
             
     | 
| 
       280 
     | 
    
         
            -
             
     | 
| 
      
 349 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 350 
     | 
    
         
            +
                #   delete_set_cookie_header(key, value = {}) -> encoded string
         
     | 
| 
      
 351 
     | 
    
         
            +
                #
         
     | 
| 
      
 352 
     | 
    
         
            +
                # Generate an encoded string based on the given +key+ and +value+ using
         
     | 
| 
      
 353 
     | 
    
         
            +
                # set_cookie_header for the purpose of causing the specified cookie to be
         
     | 
| 
      
 354 
     | 
    
         
            +
                # deleted. The +value+ may be an instance of +Hash+ and can include
         
     | 
| 
      
 355 
     | 
    
         
            +
                # attributes as outlined by set_cookie_header. The encoded cookie will have
         
     | 
| 
      
 356 
     | 
    
         
            +
                # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
         
     | 
| 
      
 357 
     | 
    
         
            +
                # +value+. When used with the +set-cookie+ header, it will cause the client
         
     | 
| 
      
 358 
     | 
    
         
            +
                # to *remove* any matching cookie.
         
     | 
| 
      
 359 
     | 
    
         
            +
                #
         
     | 
| 
      
 360 
     | 
    
         
            +
                #   delete_set_cookie_header("myname")
         
     | 
| 
      
 361 
     | 
    
         
            +
                #   # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
         
     | 
| 
      
 362 
     | 
    
         
            +
                #
         
     | 
| 
      
 363 
     | 
    
         
            +
                def delete_set_cookie_header(key, value = {})
         
     | 
| 
      
 364 
     | 
    
         
            +
                  set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
         
     | 
| 
       281 
365 
     | 
    
         
             
                end
         
     | 
| 
       282 
366 
     | 
    
         | 
| 
       283 
     | 
    
         
            -
                def  
     | 
| 
       284 
     | 
    
         
            -
                   
     | 
| 
       285 
     | 
    
         
            -
                  when nil, ''
         
     | 
| 
       286 
     | 
    
         
            -
                    cookies = []
         
     | 
| 
       287 
     | 
    
         
            -
                  when String
         
     | 
| 
       288 
     | 
    
         
            -
                    cookies = header.split("\n")
         
     | 
| 
       289 
     | 
    
         
            -
                  when Array
         
     | 
| 
       290 
     | 
    
         
            -
                    cookies = header
         
     | 
| 
       291 
     | 
    
         
            -
                  end
         
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
       293 
     | 
    
         
            -
                  key = escape(key)
         
     | 
| 
       294 
     | 
    
         
            -
                  domain = value[:domain]
         
     | 
| 
       295 
     | 
    
         
            -
                  path = value[:path]
         
     | 
| 
       296 
     | 
    
         
            -
                  regexp = if domain
         
     | 
| 
       297 
     | 
    
         
            -
                             if path
         
     | 
| 
       298 
     | 
    
         
            -
                               /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
         
     | 
| 
       299 
     | 
    
         
            -
                             else
         
     | 
| 
       300 
     | 
    
         
            -
                               /\A#{key}=.*domain=#{domain}(?:;|$)/
         
     | 
| 
       301 
     | 
    
         
            -
                             end
         
     | 
| 
       302 
     | 
    
         
            -
                           elsif path
         
     | 
| 
       303 
     | 
    
         
            -
                             /\A#{key}=.*path=#{path}(?:;|$)/
         
     | 
| 
       304 
     | 
    
         
            -
                           else
         
     | 
| 
       305 
     | 
    
         
            -
                             /\A#{key}=/
         
     | 
| 
       306 
     | 
    
         
            -
                           end
         
     | 
| 
       307 
     | 
    
         
            -
             
     | 
| 
       308 
     | 
    
         
            -
                  cookies.reject! { |cookie| regexp.match? cookie }
         
     | 
| 
      
 367 
     | 
    
         
            +
                def delete_cookie_header!(headers, key, value = {})
         
     | 
| 
      
 368 
     | 
    
         
            +
                  headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
         
     | 
| 
       309 
369 
     | 
    
         | 
| 
       310 
     | 
    
         
            -
                   
     | 
| 
      
 370 
     | 
    
         
            +
                  return nil
         
     | 
| 
       311 
371 
     | 
    
         
             
                end
         
     | 
| 
       312 
372 
     | 
    
         | 
| 
       313 
     | 
    
         
            -
                 
     | 
| 
       314 
     | 
    
         
            -
             
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
       316 
     | 
    
         
            -
                 
     | 
| 
       317 
     | 
    
         
            -
             
     | 
| 
       318 
     | 
    
         
            -
                #  
     | 
| 
       319 
     | 
    
         
            -
                # 
     | 
| 
       320 
     | 
    
         
            -
                 
     | 
| 
       321 
     | 
    
         
            -
             
     | 
| 
       322 
     | 
    
         
            -
             
     | 
| 
       323 
     | 
    
         
            -
             
     | 
| 
       324 
     | 
    
         
            -
             
     | 
| 
       325 
     | 
    
         
            -
             
     | 
| 
       326 
     | 
    
         
            -
             
     | 
| 
      
 373 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 374 
     | 
    
         
            +
                #   delete_set_cookie_header!(header, key, value = {}) -> header value
         
     | 
| 
      
 375 
     | 
    
         
            +
                #
         
     | 
| 
      
 376 
     | 
    
         
            +
                # Set an expired cookie in the specified headers with the given cookie
         
     | 
| 
      
 377 
     | 
    
         
            +
                # +key+ and +value+ using delete_set_cookie_header. This causes
         
     | 
| 
      
 378 
     | 
    
         
            +
                # the client to immediately delete the specified cookie.
         
     | 
| 
      
 379 
     | 
    
         
            +
                #
         
     | 
| 
      
 380 
     | 
    
         
            +
                #   delete_set_cookie_header!(nil, "mycookie")
         
     | 
| 
      
 381 
     | 
    
         
            +
                #   # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
         
     | 
| 
      
 382 
     | 
    
         
            +
                #
         
     | 
| 
      
 383 
     | 
    
         
            +
                # If the header is non-nil, it will be modified in place.
         
     | 
| 
      
 384 
     | 
    
         
            +
                #
         
     | 
| 
      
 385 
     | 
    
         
            +
                #   header = []
         
     | 
| 
      
 386 
     | 
    
         
            +
                #   delete_set_cookie_header!(header, "mycookie")
         
     | 
| 
      
 387 
     | 
    
         
            +
                #   # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
         
     | 
| 
      
 388 
     | 
    
         
            +
                #   header
         
     | 
| 
      
 389 
     | 
    
         
            +
                #   # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
         
     | 
| 
      
 390 
     | 
    
         
            +
                #
         
     | 
| 
      
 391 
     | 
    
         
            +
                def delete_set_cookie_header!(header, key, value = {})
         
     | 
| 
      
 392 
     | 
    
         
            +
                  if header
         
     | 
| 
      
 393 
     | 
    
         
            +
                    header = Array(header)
         
     | 
| 
      
 394 
     | 
    
         
            +
                    header << delete_set_cookie_header(key, value)
         
     | 
| 
      
 395 
     | 
    
         
            +
                  else
         
     | 
| 
      
 396 
     | 
    
         
            +
                    header = delete_set_cookie_header(key, value)
         
     | 
| 
      
 397 
     | 
    
         
            +
                  end
         
     | 
| 
       327 
398 
     | 
    
         | 
| 
      
 399 
     | 
    
         
            +
                  return header
         
     | 
| 
       328 
400 
     | 
    
         
             
                end
         
     | 
| 
       329 
401 
     | 
    
         | 
| 
       330 
402 
     | 
    
         
             
                def rfc2822(time)
         
     | 
| 
       331 
403 
     | 
    
         
             
                  time.rfc2822
         
     | 
| 
       332 
404 
     | 
    
         
             
                end
         
     | 
| 
       333 
405 
     | 
    
         | 
| 
       334 
     | 
    
         
            -
                # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
         
     | 
| 
       335 
     | 
    
         
            -
                # of '% %b %Y'.
         
     | 
| 
       336 
     | 
    
         
            -
                # It assumes that the time is in GMT to comply to the RFC 2109.
         
     | 
| 
       337 
     | 
    
         
            -
                #
         
     | 
| 
       338 
     | 
    
         
            -
                # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
         
     | 
| 
       339 
     | 
    
         
            -
                # that I'm certain someone implemented only that option.
         
     | 
| 
       340 
     | 
    
         
            -
                # Do not use %a and %b from Time.strptime, it would use localized names for
         
     | 
| 
       341 
     | 
    
         
            -
                # weekday and month.
         
     | 
| 
       342 
     | 
    
         
            -
                #
         
     | 
| 
       343 
     | 
    
         
            -
                def rfc2109(time)
         
     | 
| 
       344 
     | 
    
         
            -
                  wday = RFC2822_DAY_NAME[time.wday]
         
     | 
| 
       345 
     | 
    
         
            -
                  mon = RFC2822_MONTH_NAME[time.mon - 1]
         
     | 
| 
       346 
     | 
    
         
            -
                  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
         
     | 
| 
       347 
     | 
    
         
            -
                end
         
     | 
| 
       348 
     | 
    
         
            -
             
     | 
| 
       349 
406 
     | 
    
         
             
                # Parses the "Range:" header, if present, into an array of Range objects.
         
     | 
| 
       350 
407 
     | 
    
         
             
                # Returns nil if the header is missing or syntactically invalid.
         
     | 
| 
       351 
408 
     | 
    
         
             
                # Returns an empty array if none of the ranges are satisfiable.
         
     | 
| 
       352 
409 
     | 
    
         
             
                def byte_ranges(env, size)
         
     | 
| 
       353 
     | 
    
         
            -
                  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
         
     | 
| 
       354 
410 
     | 
    
         
             
                  get_byte_ranges env['HTTP_RANGE'], size
         
     | 
| 
       355 
411 
     | 
    
         
             
                end
         
     | 
| 
       356 
412 
     | 
    
         | 
| 
       357 
413 
     | 
    
         
             
                def get_byte_ranges(http_range, size)
         
     | 
| 
       358 
414 
     | 
    
         
             
                  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
         
     | 
| 
      
 415 
     | 
    
         
            +
                  # Ignore Range when file size is 0 to avoid a 416 error.
         
     | 
| 
      
 416 
     | 
    
         
            +
                  return nil if size.zero?
         
     | 
| 
       359 
417 
     | 
    
         
             
                  return nil unless http_range && http_range =~ /bytes=([^;]+)/
         
     | 
| 
       360 
418 
     | 
    
         
             
                  ranges = []
         
     | 
| 
       361 
419 
     | 
    
         
             
                  $1.split(/,\s*/).each do |range_spec|
         
     | 
| 
         @@ -380,23 +438,36 @@ module Rack 
     | 
|
| 
       380 
438 
     | 
    
         
             
                    end
         
     | 
| 
       381 
439 
     | 
    
         
             
                    ranges << (r0..r1)  if r0 <= r1
         
     | 
| 
       382 
440 
     | 
    
         
             
                  end
         
     | 
| 
      
 441 
     | 
    
         
            +
             
     | 
| 
      
 442 
     | 
    
         
            +
                  return [] if ranges.map(&:size).sum > size
         
     | 
| 
      
 443 
     | 
    
         
            +
             
     | 
| 
       383 
444 
     | 
    
         
             
                  ranges
         
     | 
| 
       384 
445 
     | 
    
         
             
                end
         
     | 
| 
       385 
446 
     | 
    
         | 
| 
       386 
     | 
    
         
            -
                #  
     | 
| 
       387 
     | 
    
         
            -
                 
     | 
| 
       388 
     | 
    
         
            -
             
     | 
| 
       389 
     | 
    
         
            -
             
     | 
| 
       390 
     | 
    
         
            -
             
     | 
| 
       391 
     | 
    
         
            -
             
     | 
| 
       392 
     | 
    
         
            -
             
     | 
| 
       393 
     | 
    
         
            -
                   
     | 
| 
      
 447 
     | 
    
         
            +
                # :nocov:
         
     | 
| 
      
 448 
     | 
    
         
            +
                if defined?(OpenSSL.fixed_length_secure_compare)
         
     | 
| 
      
 449 
     | 
    
         
            +
                  # Constant time string comparison.
         
     | 
| 
      
 450 
     | 
    
         
            +
                  #
         
     | 
| 
      
 451 
     | 
    
         
            +
                  # NOTE: the values compared should be of fixed length, such as strings
         
     | 
| 
      
 452 
     | 
    
         
            +
                  # that have already been processed by HMAC. This should not be used
         
     | 
| 
      
 453 
     | 
    
         
            +
                  # on variable length plaintext strings because it could leak length info
         
     | 
| 
      
 454 
     | 
    
         
            +
                  # via timing attacks.
         
     | 
| 
      
 455 
     | 
    
         
            +
                  def secure_compare(a, b)
         
     | 
| 
      
 456 
     | 
    
         
            +
                    return false unless a.bytesize == b.bytesize
         
     | 
| 
       394 
457 
     | 
    
         | 
| 
       395 
     | 
    
         
            -
             
     | 
| 
      
 458 
     | 
    
         
            +
                    OpenSSL.fixed_length_secure_compare(a, b)
         
     | 
| 
      
 459 
     | 
    
         
            +
                  end
         
     | 
| 
      
 460 
     | 
    
         
            +
                # :nocov:
         
     | 
| 
      
 461 
     | 
    
         
            +
                else
         
     | 
| 
      
 462 
     | 
    
         
            +
                  def secure_compare(a, b)
         
     | 
| 
      
 463 
     | 
    
         
            +
                    return false unless a.bytesize == b.bytesize
         
     | 
| 
      
 464 
     | 
    
         
            +
             
     | 
| 
      
 465 
     | 
    
         
            +
                    l = a.unpack("C*")
         
     | 
| 
       396 
466 
     | 
    
         | 
| 
       397 
     | 
    
         
            -
             
     | 
| 
       398 
     | 
    
         
            -
             
     | 
| 
       399 
     | 
    
         
            -
             
     | 
| 
      
 467 
     | 
    
         
            +
                    r, i = 0, -1
         
     | 
| 
      
 468 
     | 
    
         
            +
                    b.each_byte { |v| r |= v ^ l[i += 1] }
         
     | 
| 
      
 469 
     | 
    
         
            +
                    r == 0
         
     | 
| 
      
 470 
     | 
    
         
            +
                  end
         
     | 
| 
       400 
471 
     | 
    
         
             
                end
         
     | 
| 
       401 
472 
     | 
    
         | 
| 
       402 
473 
     | 
    
         
             
                # Context allows the use of a compatible middleware at different points
         
     | 
| 
         @@ -425,101 +496,12 @@ module Rack 
     | 
|
| 
       425 
496 
     | 
    
         
             
                  end
         
     | 
| 
       426 
497 
     | 
    
         
             
                end
         
     | 
| 
       427 
498 
     | 
    
         | 
| 
       428 
     | 
    
         
            -
                # A case-insensitive Hash that preserves the original case of a
         
     | 
| 
       429 
     | 
    
         
            -
                # header when set.
         
     | 
| 
       430 
     | 
    
         
            -
                #
         
     | 
| 
       431 
     | 
    
         
            -
                # @api private
         
     | 
| 
       432 
     | 
    
         
            -
                class HeaderHash < Hash # :nodoc:
         
     | 
| 
       433 
     | 
    
         
            -
                  def self.[](headers)
         
     | 
| 
       434 
     | 
    
         
            -
                    if headers.is_a?(HeaderHash) && !headers.frozen?
         
     | 
| 
       435 
     | 
    
         
            -
                      return headers
         
     | 
| 
       436 
     | 
    
         
            -
                    else
         
     | 
| 
       437 
     | 
    
         
            -
                      return self.new(headers)
         
     | 
| 
       438 
     | 
    
         
            -
                    end
         
     | 
| 
       439 
     | 
    
         
            -
                  end
         
     | 
| 
       440 
     | 
    
         
            -
             
     | 
| 
       441 
     | 
    
         
            -
                  def initialize(hash = {})
         
     | 
| 
       442 
     | 
    
         
            -
                    super()
         
     | 
| 
       443 
     | 
    
         
            -
                    @names = {}
         
     | 
| 
       444 
     | 
    
         
            -
                    hash.each { |k, v| self[k] = v }
         
     | 
| 
       445 
     | 
    
         
            -
                  end
         
     | 
| 
       446 
     | 
    
         
            -
             
     | 
| 
       447 
     | 
    
         
            -
                  # on dup/clone, we need to duplicate @names hash
         
     | 
| 
       448 
     | 
    
         
            -
                  def initialize_copy(other)
         
     | 
| 
       449 
     | 
    
         
            -
                    super
         
     | 
| 
       450 
     | 
    
         
            -
                    @names = other.names.dup
         
     | 
| 
       451 
     | 
    
         
            -
                  end
         
     | 
| 
       452 
     | 
    
         
            -
             
     | 
| 
       453 
     | 
    
         
            -
                  # on clear, we need to clear @names hash
         
     | 
| 
       454 
     | 
    
         
            -
                  def clear
         
     | 
| 
       455 
     | 
    
         
            -
                    super
         
     | 
| 
       456 
     | 
    
         
            -
                    @names.clear
         
     | 
| 
       457 
     | 
    
         
            -
                  end
         
     | 
| 
       458 
     | 
    
         
            -
             
     | 
| 
       459 
     | 
    
         
            -
                  def each
         
     | 
| 
       460 
     | 
    
         
            -
                    super do |k, v|
         
     | 
| 
       461 
     | 
    
         
            -
                      yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
         
     | 
| 
       462 
     | 
    
         
            -
                    end
         
     | 
| 
       463 
     | 
    
         
            -
                  end
         
     | 
| 
       464 
     | 
    
         
            -
             
     | 
| 
       465 
     | 
    
         
            -
                  def to_hash
         
     | 
| 
       466 
     | 
    
         
            -
                    hash = {}
         
     | 
| 
       467 
     | 
    
         
            -
                    each { |k, v| hash[k] = v }
         
     | 
| 
       468 
     | 
    
         
            -
                    hash
         
     | 
| 
       469 
     | 
    
         
            -
                  end
         
     | 
| 
       470 
     | 
    
         
            -
             
     | 
| 
       471 
     | 
    
         
            -
                  def [](k)
         
     | 
| 
       472 
     | 
    
         
            -
                    super(k) || super(@names[k.downcase])
         
     | 
| 
       473 
     | 
    
         
            -
                  end
         
     | 
| 
       474 
     | 
    
         
            -
             
     | 
| 
       475 
     | 
    
         
            -
                  def []=(k, v)
         
     | 
| 
       476 
     | 
    
         
            -
                    canonical = k.downcase.freeze
         
     | 
| 
       477 
     | 
    
         
            -
                    delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
         
     | 
| 
       478 
     | 
    
         
            -
                    @names[canonical] = k
         
     | 
| 
       479 
     | 
    
         
            -
                    super k, v
         
     | 
| 
       480 
     | 
    
         
            -
                  end
         
     | 
| 
       481 
     | 
    
         
            -
             
     | 
| 
       482 
     | 
    
         
            -
                  def delete(k)
         
     | 
| 
       483 
     | 
    
         
            -
                    canonical = k.downcase
         
     | 
| 
       484 
     | 
    
         
            -
                    result = super @names.delete(canonical)
         
     | 
| 
       485 
     | 
    
         
            -
                    result
         
     | 
| 
       486 
     | 
    
         
            -
                  end
         
     | 
| 
       487 
     | 
    
         
            -
             
     | 
| 
       488 
     | 
    
         
            -
                  def include?(k)
         
     | 
| 
       489 
     | 
    
         
            -
                    super || @names.include?(k.downcase)
         
     | 
| 
       490 
     | 
    
         
            -
                  end
         
     | 
| 
       491 
     | 
    
         
            -
             
     | 
| 
       492 
     | 
    
         
            -
                  alias_method :has_key?, :include?
         
     | 
| 
       493 
     | 
    
         
            -
                  alias_method :member?, :include?
         
     | 
| 
       494 
     | 
    
         
            -
                  alias_method :key?, :include?
         
     | 
| 
       495 
     | 
    
         
            -
             
     | 
| 
       496 
     | 
    
         
            -
                  def merge!(other)
         
     | 
| 
       497 
     | 
    
         
            -
                    other.each { |k, v| self[k] = v }
         
     | 
| 
       498 
     | 
    
         
            -
                    self
         
     | 
| 
       499 
     | 
    
         
            -
                  end
         
     | 
| 
       500 
     | 
    
         
            -
             
     | 
| 
       501 
     | 
    
         
            -
                  def merge(other)
         
     | 
| 
       502 
     | 
    
         
            -
                    hash = dup
         
     | 
| 
       503 
     | 
    
         
            -
                    hash.merge! other
         
     | 
| 
       504 
     | 
    
         
            -
                  end
         
     | 
| 
       505 
     | 
    
         
            -
             
     | 
| 
       506 
     | 
    
         
            -
                  def replace(other)
         
     | 
| 
       507 
     | 
    
         
            -
                    clear
         
     | 
| 
       508 
     | 
    
         
            -
                    other.each { |k, v| self[k] = v }
         
     | 
| 
       509 
     | 
    
         
            -
                    self
         
     | 
| 
       510 
     | 
    
         
            -
                  end
         
     | 
| 
       511 
     | 
    
         
            -
             
     | 
| 
       512 
     | 
    
         
            -
                  protected
         
     | 
| 
       513 
     | 
    
         
            -
                    def names
         
     | 
| 
       514 
     | 
    
         
            -
                      @names
         
     | 
| 
       515 
     | 
    
         
            -
                    end
         
     | 
| 
       516 
     | 
    
         
            -
                end
         
     | 
| 
       517 
     | 
    
         
            -
             
     | 
| 
       518 
499 
     | 
    
         
             
                # Every standard HTTP code mapped to the appropriate message.
         
     | 
| 
       519 
500 
     | 
    
         
             
                # Generated with:
         
     | 
| 
       520 
     | 
    
         
            -
                #   curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv  
     | 
| 
       521 
     | 
    
         
            -
                #     ruby - 
     | 
| 
       522 
     | 
    
         
            -
                # 
     | 
| 
      
 501 
     | 
    
         
            +
                #   curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
         
     | 
| 
      
 502 
     | 
    
         
            +
                #     | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
         
     | 
| 
      
 503 
     | 
    
         
            +
                #     .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
         
     | 
| 
      
 504 
     | 
    
         
            +
                #     .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
         
     | 
| 
       523 
505 
     | 
    
         
             
                HTTP_STATUS_CODES = {
         
     | 
| 
       524 
506 
     | 
    
         
             
                  100 => 'Continue',
         
     | 
| 
       525 
507 
     | 
    
         
             
                  101 => 'Switching Protocols',
         
     | 
| 
         @@ -541,7 +523,6 @@ module Rack 
     | 
|
| 
       541 
523 
     | 
    
         
             
                  303 => 'See Other',
         
     | 
| 
       542 
524 
     | 
    
         
             
                  304 => 'Not Modified',
         
     | 
| 
       543 
525 
     | 
    
         
             
                  305 => 'Use Proxy',
         
     | 
| 
       544 
     | 
    
         
            -
                  306 => '(Unused)',
         
     | 
| 
       545 
526 
     | 
    
         
             
                  307 => 'Temporary Redirect',
         
     | 
| 
       546 
527 
     | 
    
         
             
                  308 => 'Permanent Redirect',
         
     | 
| 
       547 
528 
     | 
    
         
             
                  400 => 'Bad Request',
         
     | 
| 
         @@ -557,13 +538,13 @@ module Rack 
     | 
|
| 
       557 
538 
     | 
    
         
             
                  410 => 'Gone',
         
     | 
| 
       558 
539 
     | 
    
         
             
                  411 => 'Length Required',
         
     | 
| 
       559 
540 
     | 
    
         
             
                  412 => 'Precondition Failed',
         
     | 
| 
       560 
     | 
    
         
            -
                  413 => ' 
     | 
| 
      
 541 
     | 
    
         
            +
                  413 => 'Content Too Large',
         
     | 
| 
       561 
542 
     | 
    
         
             
                  414 => 'URI Too Long',
         
     | 
| 
       562 
543 
     | 
    
         
             
                  415 => 'Unsupported Media Type',
         
     | 
| 
       563 
544 
     | 
    
         
             
                  416 => 'Range Not Satisfiable',
         
     | 
| 
       564 
545 
     | 
    
         
             
                  417 => 'Expectation Failed',
         
     | 
| 
       565 
546 
     | 
    
         
             
                  421 => 'Misdirected Request',
         
     | 
| 
       566 
     | 
    
         
            -
                  422 => 'Unprocessable  
     | 
| 
      
 547 
     | 
    
         
            +
                  422 => 'Unprocessable Content',
         
     | 
| 
       567 
548 
     | 
    
         
             
                  423 => 'Locked',
         
     | 
| 
       568 
549 
     | 
    
         
             
                  424 => 'Failed Dependency',
         
     | 
| 
       569 
550 
     | 
    
         
             
                  425 => 'Too Early',
         
     | 
| 
         @@ -571,7 +552,7 @@ module Rack 
     | 
|
| 
       571 
552 
     | 
    
         
             
                  428 => 'Precondition Required',
         
     | 
| 
       572 
553 
     | 
    
         
             
                  429 => 'Too Many Requests',
         
     | 
| 
       573 
554 
     | 
    
         
             
                  431 => 'Request Header Fields Too Large',
         
     | 
| 
       574 
     | 
    
         
            -
                  451 => 'Unavailable  
     | 
| 
      
 555 
     | 
    
         
            +
                  451 => 'Unavailable For Legal Reasons',
         
     | 
| 
       575 
556 
     | 
    
         
             
                  500 => 'Internal Server Error',
         
     | 
| 
       576 
557 
     | 
    
         
             
                  501 => 'Not Implemented',
         
     | 
| 
       577 
558 
     | 
    
         
             
                  502 => 'Bad Gateway',
         
     | 
| 
         @@ -581,8 +562,6 @@ module Rack 
     | 
|
| 
       581 
562 
     | 
    
         
             
                  506 => 'Variant Also Negotiates',
         
     | 
| 
       582 
563 
     | 
    
         
             
                  507 => 'Insufficient Storage',
         
     | 
| 
       583 
564 
     | 
    
         
             
                  508 => 'Loop Detected',
         
     | 
| 
       584 
     | 
    
         
            -
                  509 => 'Bandwidth Limit Exceeded',
         
     | 
| 
       585 
     | 
    
         
            -
                  510 => 'Not Extended',
         
     | 
| 
       586 
565 
     | 
    
         
             
                  511 => 'Network Authentication Required'
         
     | 
| 
       587 
566 
     | 
    
         
             
                }
         
     | 
| 
       588 
567 
     | 
    
         | 
| 
         @@ -590,12 +569,36 @@ module Rack 
     | 
|
| 
       590 
569 
     | 
    
         
             
                STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
         
     | 
| 
       591 
570 
     | 
    
         | 
| 
       592 
571 
     | 
    
         
             
                SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
         
     | 
| 
       593 
     | 
    
         
            -
                  [message.downcase.gsub(/\s 
     | 
| 
      
 572 
     | 
    
         
            +
                  [message.downcase.gsub(/\s|-/, '_').to_sym, code]
         
     | 
| 
       594 
573 
     | 
    
         
             
                }.flatten]
         
     | 
| 
       595 
574 
     | 
    
         | 
| 
      
 575 
     | 
    
         
            +
                OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
         
     | 
| 
      
 576 
     | 
    
         
            +
                  payload_too_large: 413,
         
     | 
| 
      
 577 
     | 
    
         
            +
                  unprocessable_entity: 422,
         
     | 
| 
      
 578 
     | 
    
         
            +
                  bandwidth_limit_exceeded: 509,
         
     | 
| 
      
 579 
     | 
    
         
            +
                  not_extended: 510
         
     | 
| 
      
 580 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 581 
     | 
    
         
            +
                private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
         
     | 
| 
      
 582 
     | 
    
         
            +
             
     | 
| 
      
 583 
     | 
    
         
            +
                OBSOLETE_SYMBOL_MAPPINGS = {
         
     | 
| 
      
 584 
     | 
    
         
            +
                  payload_too_large: :content_too_large,
         
     | 
| 
      
 585 
     | 
    
         
            +
                  unprocessable_entity: :unprocessable_content
         
     | 
| 
      
 586 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 587 
     | 
    
         
            +
                private_constant :OBSOLETE_SYMBOL_MAPPINGS
         
     | 
| 
      
 588 
     | 
    
         
            +
             
     | 
| 
       596 
589 
     | 
    
         
             
                def status_code(status)
         
     | 
| 
       597 
590 
     | 
    
         
             
                  if status.is_a?(Symbol)
         
     | 
| 
       598 
     | 
    
         
            -
                    SYMBOL_TO_STATUS_CODE.fetch(status)  
     | 
| 
      
 591 
     | 
    
         
            +
                    SYMBOL_TO_STATUS_CODE.fetch(status) do
         
     | 
| 
      
 592 
     | 
    
         
            +
                      fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
         
     | 
| 
      
 593 
     | 
    
         
            +
                      message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
         
     | 
| 
      
 594 
     | 
    
         
            +
                      if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
         
     | 
| 
      
 595 
     | 
    
         
            +
                        # message = "#{message} Please use #{canonical_symbol.inspect} instead."
         
     | 
| 
      
 596 
     | 
    
         
            +
                        # For now, let's not emit any warning when there is a mapping.
         
     | 
| 
      
 597 
     | 
    
         
            +
                      else
         
     | 
| 
      
 598 
     | 
    
         
            +
                        warn message, uplevel: 3
         
     | 
| 
      
 599 
     | 
    
         
            +
                      end
         
     | 
| 
      
 600 
     | 
    
         
            +
                      fallback_code
         
     | 
| 
      
 601 
     | 
    
         
            +
                    end
         
     | 
| 
       599 
602 
     | 
    
         
             
                  else
         
     | 
| 
       600 
603 
     | 
    
         
             
                    status.to_i
         
     | 
| 
       601 
604 
     | 
    
         
             
                  end
         
     |