rack 2.1.4.2 → 2.2.0
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 +600 -13
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +83 -39
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +3 -3
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +25 -21
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +2 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +15 -11
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +58 -15
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/mock.rb +22 -4
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +8 -17
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/multipart.rb +5 -4
- data/lib/rack/query_parser.rb +7 -8
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +172 -76
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +0 -3
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +20 -18
- data/lib/rack/session/cookie.rb +2 -3
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/show_exceptions.rb +2 -4
- data/lib/rack/show_status.rb +1 -3
- data/lib/rack/static.rb +13 -6
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +1 -4
- data/lib/rack/utils.rb +64 -65
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +7 -16
- data/rack.gemspec +31 -29
- metadata +14 -15
    
        data/lib/rack/files.rb
    CHANGED
    
    | @@ -1,10 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'time'
         | 
| 4 | 
            -
            require 'rack/utils'
         | 
| 5 | 
            -
            require 'rack/mime'
         | 
| 6 | 
            -
            require 'rack/request'
         | 
| 7 | 
            -
            require 'rack/head'
         | 
| 8 4 |  | 
| 9 5 | 
             
            module Rack
         | 
| 10 6 | 
             
              # Rack::Files serves files below the +root+ directory given, according to the
         | 
| @@ -18,6 +14,15 @@ module Rack | |
| 18 14 | 
             
              class Files
         | 
| 19 15 | 
             
                ALLOWED_VERBS = %w[GET HEAD OPTIONS]
         | 
| 20 16 | 
             
                ALLOW_HEADER = ALLOWED_VERBS.join(', ')
         | 
| 17 | 
            +
                MULTIPART_BOUNDARY = 'AaB03x'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # @todo remove in 3.0
         | 
| 20 | 
            +
                def self.method_added(name)
         | 
| 21 | 
            +
                  if name == :response_body
         | 
| 22 | 
            +
                    raise "#{self.class}\#response_body is no longer supported."
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                  super
         | 
| 25 | 
            +
                end
         | 
| 21 26 |  | 
| 22 27 | 
             
                attr_reader :root
         | 
| 23 28 |  | 
| @@ -48,7 +53,11 @@ module Rack | |
| 48 53 | 
             
                  available = begin
         | 
| 49 54 | 
             
                    ::File.file?(path) && ::File.readable?(path)
         | 
| 50 55 | 
             
                  rescue SystemCallError
         | 
| 56 | 
            +
                    # Not sure in what conditions this exception can occur, but this
         | 
| 57 | 
            +
                    # is a safe way to handle such an error.
         | 
| 58 | 
            +
                    # :nocov:
         | 
| 51 59 | 
             
                    false
         | 
| 60 | 
            +
                    # :nocov:
         | 
| 52 61 | 
             
                  end
         | 
| 53 62 |  | 
| 54 63 | 
             
                  if available
         | 
| @@ -70,75 +79,116 @@ module Rack | |
| 70 79 | 
             
                  headers[CONTENT_TYPE] = mime_type if mime_type
         | 
| 71 80 |  | 
| 72 81 | 
             
                  # Set custom headers
         | 
| 73 | 
            -
                   | 
| 74 | 
            -
             | 
| 75 | 
            -
                  response = [ 200, headers ]
         | 
| 82 | 
            +
                  headers.merge!(@headers) if @headers
         | 
| 76 83 |  | 
| 84 | 
            +
                  status = 200
         | 
| 77 85 | 
             
                  size = filesize path
         | 
| 78 86 |  | 
| 79 | 
            -
                  range = nil
         | 
| 80 87 | 
             
                  ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
         | 
| 81 | 
            -
                  if ranges.nil? | 
| 82 | 
            -
                    # No ranges | 
| 83 | 
            -
                     | 
| 84 | 
            -
                    response[0] = 200
         | 
| 85 | 
            -
                    range = 0..size - 1
         | 
| 88 | 
            +
                  if ranges.nil?
         | 
| 89 | 
            +
                    # No ranges:
         | 
| 90 | 
            +
                    ranges = [0..size - 1]
         | 
| 86 91 | 
             
                  elsif ranges.empty?
         | 
| 87 92 | 
             
                    # Unsatisfiable. Return error, and file size:
         | 
| 88 93 | 
             
                    response = fail(416, "Byte range unsatisfiable")
         | 
| 89 94 | 
             
                    response[1]["Content-Range"] = "bytes */#{size}"
         | 
| 90 95 | 
             
                    return response
         | 
| 91 | 
            -
                   | 
| 92 | 
            -
                    # Partial content | 
| 93 | 
            -
                     | 
| 94 | 
            -
             | 
| 95 | 
            -
                     | 
| 96 | 
            -
             | 
| 96 | 
            +
                  elsif ranges.size >= 1
         | 
| 97 | 
            +
                    # Partial content
         | 
| 98 | 
            +
                    partial_content = true
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    if ranges.size == 1
         | 
| 101 | 
            +
                      range = ranges[0]
         | 
| 102 | 
            +
                      headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
         | 
| 103 | 
            +
                    else
         | 
| 104 | 
            +
                      headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    status = 206
         | 
| 108 | 
            +
                    body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
         | 
| 109 | 
            +
                    size = body.bytesize
         | 
| 97 110 | 
             
                  end
         | 
| 98 111 |  | 
| 99 | 
            -
                   | 
| 112 | 
            +
                  headers[CONTENT_LENGTH] = size.to_s
         | 
| 100 113 |  | 
| 101 | 
            -
                   | 
| 102 | 
            -
             | 
| 103 | 
            -
                   | 
| 114 | 
            +
                  if request.head?
         | 
| 115 | 
            +
                    body = []
         | 
| 116 | 
            +
                  elsif !partial_content
         | 
| 117 | 
            +
                    body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  [status, headers, body]
         | 
| 104 121 | 
             
                end
         | 
| 105 122 |  | 
| 106 | 
            -
                class  | 
| 107 | 
            -
                  attr_reader :path, : | 
| 108 | 
            -
                  alias :to_path :path
         | 
| 123 | 
            +
                class BaseIterator
         | 
| 124 | 
            +
                  attr_reader :path, :ranges, :options
         | 
| 109 125 |  | 
| 110 | 
            -
                  def initialize | 
| 111 | 
            -
                    @path | 
| 112 | 
            -
                    @ | 
| 126 | 
            +
                  def initialize(path, ranges, options)
         | 
| 127 | 
            +
                    @path = path
         | 
| 128 | 
            +
                    @ranges = ranges
         | 
| 129 | 
            +
                    @options = options
         | 
| 113 130 | 
             
                  end
         | 
| 114 131 |  | 
| 115 132 | 
             
                  def each
         | 
| 116 133 | 
             
                    ::File.open(path, "rb") do |file|
         | 
| 117 | 
            -
                       | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
                         | 
| 121 | 
            -
             | 
| 122 | 
            -
                         | 
| 123 | 
            -
             | 
| 124 | 
            -
                        yield part
         | 
| 134 | 
            +
                      ranges.each do |range|
         | 
| 135 | 
            +
                        yield multipart_heading(range) if multipart?
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                        each_range_part(file, range) do |part|
         | 
| 138 | 
            +
                          yield part
         | 
| 139 | 
            +
                        end
         | 
| 125 140 | 
             
                      end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                      yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
         | 
| 126 143 | 
             
                    end
         | 
| 127 144 | 
             
                  end
         | 
| 128 145 |  | 
| 146 | 
            +
                  def bytesize
         | 
| 147 | 
            +
                    size = ranges.inject(0) do |sum, range|
         | 
| 148 | 
            +
                      sum += multipart_heading(range).bytesize if multipart?
         | 
| 149 | 
            +
                      sum += range.size
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
                    size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
         | 
| 152 | 
            +
                    size
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 129 155 | 
             
                  def close; end
         | 
| 130 | 
            -
                end
         | 
| 131 156 |  | 
| 132 | 
            -
             | 
| 157 | 
            +
                  private
         | 
| 133 158 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 159 | 
            +
                  def multipart?
         | 
| 160 | 
            +
                    ranges.size > 1
         | 
| 161 | 
            +
                  end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  def multipart_heading(range)
         | 
| 164 | 
            +
            <<-EOF
         | 
| 165 | 
            +
            \r
         | 
| 166 | 
            +
            --#{MULTIPART_BOUNDARY}\r
         | 
| 167 | 
            +
            Content-Type: #{options[:mime_type]}\r
         | 
| 168 | 
            +
            Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
         | 
| 169 | 
            +
            \r
         | 
| 170 | 
            +
            EOF
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  def each_range_part(file, range)
         | 
| 174 | 
            +
                    file.seek(range.begin)
         | 
| 175 | 
            +
                    remaining_len = range.end - range.begin + 1
         | 
| 176 | 
            +
                    while remaining_len > 0
         | 
| 177 | 
            +
                      part = file.read([8192, remaining_len].min)
         | 
| 178 | 
            +
                      break unless part
         | 
| 179 | 
            +
                      remaining_len -= part.length
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                      yield part
         | 
| 182 | 
            +
                    end
         | 
| 139 183 | 
             
                  end
         | 
| 140 184 | 
             
                end
         | 
| 141 185 |  | 
| 186 | 
            +
                class Iterator < BaseIterator
         | 
| 187 | 
            +
                  alias :to_path :path
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                private
         | 
| 191 | 
            +
             | 
| 142 192 | 
             
                def fail(status, body, headers = {})
         | 
| 143 193 | 
             
                  body += "\n"
         | 
| 144 194 |  | 
| @@ -154,25 +204,15 @@ module Rack | |
| 154 204 | 
             
                end
         | 
| 155 205 |  | 
| 156 206 | 
             
                # The MIME type for the contents of the file located at @path
         | 
| 157 | 
            -
                def mime_type | 
| 207 | 
            +
                def mime_type(path, default_mime)
         | 
| 158 208 | 
             
                  Mime.mime_type(::File.extname(path), default_mime)
         | 
| 159 209 | 
             
                end
         | 
| 160 210 |  | 
| 161 | 
            -
                def filesize | 
| 162 | 
            -
                  # If response_body is present, use its size.
         | 
| 163 | 
            -
                  return response_body.bytesize if response_body
         | 
| 164 | 
            -
             | 
| 211 | 
            +
                def filesize(path)
         | 
| 165 212 | 
             
                  #   We check via File::size? whether this file provides size info
         | 
| 166 213 | 
             
                  #   via stat (e.g. /proc files often don't), otherwise we have to
         | 
| 167 214 | 
             
                  #   figure it out by reading the whole file into memory.
         | 
| 168 215 | 
             
                  ::File.size?(path) || ::File.read(path).bytesize
         | 
| 169 216 | 
             
                end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
                # By default, the response body for file requests is nil.
         | 
| 172 | 
            -
                # In this case, the response body will be generated later
         | 
| 173 | 
            -
                # from the file at @path
         | 
| 174 | 
            -
                def response_body
         | 
| 175 | 
            -
                  nil
         | 
| 176 | 
            -
                end
         | 
| 177 217 | 
             
              end
         | 
| 178 218 | 
             
            end
         | 
    
        data/lib/rack/handler/cgi.rb
    CHANGED
    
    
    
        data/lib/rack/handler/fastcgi.rb
    CHANGED
    
    | @@ -2,8 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'fcgi'
         | 
| 4 4 | 
             
            require 'socket'
         | 
| 5 | 
            -
            require 'rack/content_length'
         | 
| 6 | 
            -
            require 'rack/rewindable_input'
         | 
| 7 5 |  | 
| 8 6 | 
             
            if defined? FCGI::Stream
         | 
| 9 7 | 
             
              class FCGI::Stream
         | 
| @@ -20,7 +18,7 @@ end | |
| 20 18 | 
             
            module Rack
         | 
| 21 19 | 
             
              module Handler
         | 
| 22 20 | 
             
                class FastCGI
         | 
| 23 | 
            -
                  def self.run(app, options | 
| 21 | 
            +
                  def self.run(app, **options)
         | 
| 24 22 | 
             
                    if options[:File]
         | 
| 25 23 | 
             
                      STDIN.reopen(UNIXServer.new(options[:File]))
         | 
| 26 24 | 
             
                    elsif options[:Port]
         | 
    
        data/lib/rack/handler/lsws.rb
    CHANGED
    
    | @@ -1,13 +1,11 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'lsapi'
         | 
| 4 | 
            -
            require 'rack/content_length'
         | 
| 5 | 
            -
            require 'rack/rewindable_input'
         | 
| 6 4 |  | 
| 7 5 | 
             
            module Rack
         | 
| 8 6 | 
             
              module Handler
         | 
| 9 7 | 
             
                class LSWS
         | 
| 10 | 
            -
                  def self.run(app, options | 
| 8 | 
            +
                  def self.run(app, **options)
         | 
| 11 9 | 
             
                    while LSAPI.accept != nil
         | 
| 12 10 | 
             
                      serve app
         | 
| 13 11 | 
             
                    end
         | 
    
        data/lib/rack/handler/scgi.rb
    CHANGED
    
    | @@ -2,15 +2,13 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'scgi'
         | 
| 4 4 | 
             
            require 'stringio'
         | 
| 5 | 
            -
            require 'rack/content_length'
         | 
| 6 | 
            -
            require 'rack/chunked'
         | 
| 7 5 |  | 
| 8 6 | 
             
            module Rack
         | 
| 9 7 | 
             
              module Handler
         | 
| 10 8 | 
             
                class SCGI < ::SCGI::Processor
         | 
| 11 9 | 
             
                  attr_accessor :app
         | 
| 12 10 |  | 
| 13 | 
            -
                  def self.run(app, options | 
| 11 | 
            +
                  def self.run(app, **options)
         | 
| 14 12 | 
             
                    options[:Socket] = UNIXServer.new(options[:File]) if options[:File]
         | 
| 15 13 | 
             
                    new(options.merge(app: app,
         | 
| 16 14 | 
             
                                      host: options[:Host],
         | 
    
        data/lib/rack/handler/thin.rb
    CHANGED
    
    | @@ -4,24 +4,28 @@ require "thin" | |
| 4 4 | 
             
            require "thin/server"
         | 
| 5 5 | 
             
            require "thin/logging"
         | 
| 6 6 | 
             
            require "thin/backends/tcp_server"
         | 
| 7 | 
            -
            require "rack/content_length"
         | 
| 8 | 
            -
            require "rack/chunked"
         | 
| 9 7 |  | 
| 10 8 | 
             
            module Rack
         | 
| 11 9 | 
             
              module Handler
         | 
| 12 10 | 
             
                class Thin
         | 
| 13 | 
            -
                  def self.run(app, options | 
| 11 | 
            +
                  def self.run(app, **options)
         | 
| 14 12 | 
             
                    environment  = ENV['RACK_ENV'] || 'development'
         | 
| 15 13 | 
             
                    default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
         | 
| 16 14 |  | 
| 17 | 
            -
                     | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 15 | 
            +
                    if block_given?
         | 
| 16 | 
            +
                      host = options.delete(:Host) || default_host
         | 
| 17 | 
            +
                      port = options.delete(:Port) || 8080
         | 
| 18 | 
            +
                      args = [host, port, app, options]
         | 
| 19 | 
            +
                      # Thin versions below 0.8.0 do not support additional options
         | 
| 20 | 
            +
                      args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8
         | 
| 21 | 
            +
                      server = ::Thin::Server.new(*args)
         | 
| 22 | 
            +
                      yield server
         | 
| 23 | 
            +
                      server.start
         | 
| 24 | 
            +
                    else
         | 
| 25 | 
            +
                      options[:address] = options[:Host] || default_host
         | 
| 26 | 
            +
                      options[:port] = options[:Port] || 8080
         | 
| 27 | 
            +
                      ::Thin::Controllers::Controller.new(options).start
         | 
| 28 | 
            +
                    end
         | 
| 25 29 | 
             
                  end
         | 
| 26 30 |  | 
| 27 31 | 
             
                  def self.valid_options
         | 
    
        data/lib/rack/handler/webrick.rb
    CHANGED
    
    | @@ -2,7 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'webrick'
         | 
| 4 4 | 
             
            require 'stringio'
         | 
| 5 | 
            -
            require 'rack/content_length'
         | 
| 6 5 |  | 
| 7 6 | 
             
            # This monkey patch allows for applications to perform their own chunking
         | 
| 8 7 | 
             
            # through WEBrick::HTTPResponse if rack is set to true.
         | 
| @@ -24,12 +23,18 @@ end | |
| 24 23 | 
             
            module Rack
         | 
| 25 24 | 
             
              module Handler
         | 
| 26 25 | 
             
                class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
         | 
| 27 | 
            -
                  def self.run(app, options | 
| 26 | 
            +
                  def self.run(app, **options)
         | 
| 28 27 | 
             
                    environment  = ENV['RACK_ENV'] || 'development'
         | 
| 29 28 | 
             
                    default_host = environment == 'development' ? 'localhost' : nil
         | 
| 30 29 |  | 
| 31 | 
            -
                    options[:BindAddress]  | 
| 30 | 
            +
                    if !options[:BindAddress] || options[:Host]
         | 
| 31 | 
            +
                      options[:BindAddress] = options.delete(:Host) || default_host
         | 
| 32 | 
            +
                    end
         | 
| 32 33 | 
             
                    options[:Port] ||= 8080
         | 
| 34 | 
            +
                    if options[:SSLEnable]
         | 
| 35 | 
            +
                      require 'webrick/https'
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 33 38 | 
             
                    @server = ::WEBrick::HTTPServer.new(options)
         | 
| 34 39 | 
             
                    @server.mount "/", Rack::Handler::WEBrick, app
         | 
| 35 40 | 
             
                    yield @server  if block_given?
         | 
| @@ -47,8 +52,10 @@ module Rack | |
| 47 52 | 
             
                  end
         | 
| 48 53 |  | 
| 49 54 | 
             
                  def self.shutdown
         | 
| 50 | 
            -
                    @server | 
| 51 | 
            -
             | 
| 55 | 
            +
                    if @server
         | 
| 56 | 
            +
                      @server.shutdown
         | 
| 57 | 
            +
                      @server = nil
         | 
| 58 | 
            +
                    end
         | 
| 52 59 | 
             
                  end
         | 
| 53 60 |  | 
| 54 61 | 
             
                  def initialize(server, app)
         | 
    
        data/lib/rack/head.rb
    CHANGED
    
    
    
        data/lib/rack/lint.rb
    CHANGED
    
    | @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'rack/utils'
         | 
| 4 3 | 
             
            require 'forwardable'
         | 
| 5 4 |  | 
| 6 5 | 
             
            module Rack
         | 
| @@ -48,13 +47,24 @@ module Rack | |
| 48 47 | 
             
                  env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
         | 
| 49 48 |  | 
| 50 49 | 
             
                  ## and returns an Array of exactly three values:
         | 
| 51 | 
            -
                   | 
| 50 | 
            +
                  ary = @app.call(env)
         | 
| 51 | 
            +
                  assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
         | 
| 52 | 
            +
                    ary.kind_of? Array
         | 
| 53 | 
            +
                  }
         | 
| 54 | 
            +
                  assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
         | 
| 55 | 
            +
                    ary.size == 3
         | 
| 56 | 
            +
                  }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  status, headers, @body = ary
         | 
| 52 59 | 
             
                  ## The *status*,
         | 
| 53 60 | 
             
                  check_status status
         | 
| 54 61 | 
             
                  ## the *headers*,
         | 
| 55 62 | 
             
                  check_headers headers
         | 
| 56 63 |  | 
| 57 | 
            -
                  check_hijack_response headers, env
         | 
| 64 | 
            +
                  hijack_proc = check_hijack_response headers, env
         | 
| 65 | 
            +
                  if hijack_proc && headers.is_a?(Hash)
         | 
| 66 | 
            +
                    headers[RACK_HIJACK] = hijack_proc
         | 
| 67 | 
            +
                  end
         | 
| 58 68 |  | 
| 59 69 | 
             
                  ## and the *body*.
         | 
| 60 70 | 
             
                  check_content_type status, headers
         | 
| @@ -65,12 +75,15 @@ module Rack | |
| 65 75 |  | 
| 66 76 | 
             
                ## == The Environment
         | 
| 67 77 | 
             
                def check_env(env)
         | 
| 68 | 
            -
                  ## The environment must be an instance of Hash that includes
         | 
| 78 | 
            +
                  ## The environment must be an unfrozen instance of Hash that includes
         | 
| 69 79 | 
             
                  ## CGI-like headers.  The application is free to modify the
         | 
| 70 80 | 
             
                  ## environment.
         | 
| 71 81 | 
             
                  assert("env #{env.inspect} is not a Hash, but #{env.class}") {
         | 
| 72 82 | 
             
                    env.kind_of? Hash
         | 
| 73 83 | 
             
                  }
         | 
| 84 | 
            +
                  assert("env should not be frozen, but is") {
         | 
| 85 | 
            +
                    !env.frozen?
         | 
| 86 | 
            +
                  }
         | 
| 74 87 |  | 
| 75 88 | 
             
                  ##
         | 
| 76 89 | 
             
                  ## The environment is required to include these variables
         | 
| @@ -104,17 +117,19 @@ module Rack | |
| 104 117 | 
             
                  ##                         follows the <tt>?</tt>, if any. May be
         | 
| 105 118 | 
             
                  ##                         empty, but is always required!
         | 
| 106 119 |  | 
| 107 | 
            -
                  ## <tt>SERVER_NAME</tt | 
| 108 | 
            -
                  ##                        When combined with <tt>SCRIPT_NAME</tt> and
         | 
| 120 | 
            +
                  ## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
         | 
| 109 121 | 
             
                  ##                        <tt>PATH_INFO</tt>, these variables can be
         | 
| 110 122 | 
             
                  ##                        used to complete the URL. Note, however,
         | 
| 111 123 | 
             
                  ##                        that <tt>HTTP_HOST</tt>, if present,
         | 
| 112 124 | 
             
                  ##                        should be used in preference to
         | 
| 113 125 | 
             
                  ##                        <tt>SERVER_NAME</tt> for reconstructing
         | 
| 114 126 | 
             
                  ##                        the request URL.
         | 
| 115 | 
            -
                  ##                        <tt>SERVER_NAME</tt>  | 
| 116 | 
            -
                  ##                         | 
| 117 | 
            -
             | 
| 127 | 
            +
                  ##                        <tt>SERVER_NAME</tt> can never be an empty
         | 
| 128 | 
            +
                  ##                        string, and so is always required.
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  ## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
         | 
| 131 | 
            +
                  ##                        server is running on. Should be specified if
         | 
| 132 | 
            +
                  ##                        the server is running on a non-standard port.
         | 
| 118 133 |  | 
| 119 134 | 
             
                  ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
         | 
| 120 135 | 
             
                  ##                            client-supplied HTTP request
         | 
| @@ -198,6 +213,11 @@ module Rack | |
| 198 213 | 
             
                    assert("session #{session.inspect} must respond to clear") {
         | 
| 199 214 | 
             
                      session.respond_to?(:clear)
         | 
| 200 215 | 
             
                    }
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                    ##                         to_hash (returning unfrozen Hash instance);
         | 
| 218 | 
            +
                    assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
         | 
| 219 | 
            +
                      session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
         | 
| 220 | 
            +
                    }
         | 
| 201 221 | 
             
                  end
         | 
| 202 222 |  | 
| 203 223 | 
             
                  ## <tt>rack.logger</tt>:: A common object interface for logging messages.
         | 
| @@ -253,13 +273,28 @@ module Rack | |
| 253 273 | 
             
                  ## accepted specifications and must not be used otherwise.
         | 
| 254 274 | 
             
                  ##
         | 
| 255 275 |  | 
| 256 | 
            -
                  %w[REQUEST_METHOD SERVER_NAME  | 
| 257 | 
            -
                     QUERY_STRING
         | 
| 276 | 
            +
                  %w[REQUEST_METHOD SERVER_NAME QUERY_STRING
         | 
| 258 277 | 
             
                     rack.version rack.input rack.errors
         | 
| 259 278 | 
             
                     rack.multithread rack.multiprocess rack.run_once].each { |header|
         | 
| 260 279 | 
             
                    assert("env missing required key #{header}") { env.include? header }
         | 
| 261 280 | 
             
                  }
         | 
| 262 281 |  | 
| 282 | 
            +
                  ## The <tt>SERVER_PORT</tt> must be an Integer if set.
         | 
| 283 | 
            +
                  assert("env[SERVER_PORT] is not an Integer") do
         | 
| 284 | 
            +
                    server_port = env["SERVER_PORT"]
         | 
| 285 | 
            +
                    server_port.nil? || (Integer(server_port) rescue false)
         | 
| 286 | 
            +
                  end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                  ## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
         | 
| 289 | 
            +
                  assert("#{env[SERVER_NAME]} must be a valid authority") do
         | 
| 290 | 
            +
                    URI.parse("http://#{env[SERVER_NAME]}/") rescue false
         | 
| 291 | 
            +
                  end
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                  ## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
         | 
| 294 | 
            +
                  assert("#{env[HTTP_HOST]} must be a valid authority") do
         | 
| 295 | 
            +
                    URI.parse("http://#{env[HTTP_HOST]}/") rescue false
         | 
| 296 | 
            +
                  end
         | 
| 297 | 
            +
             | 
| 263 298 | 
             
                  ## The environment must not contain the keys
         | 
| 264 299 | 
             
                  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
         | 
| 265 300 | 
             
                  ## (use the versions without <tt>HTTP_</tt>).
         | 
| @@ -270,11 +305,17 @@ module Rack | |
| 270 305 | 
             
                  }
         | 
| 271 306 |  | 
| 272 307 | 
             
                  ## The CGI keys (named without a period) must have String values.
         | 
| 308 | 
            +
                  ## If the string values for CGI keys contain non-ASCII characters,
         | 
| 309 | 
            +
                  ## they should use ASCII-8BIT encoding.
         | 
| 273 310 | 
             
                  env.each { |key, value|
         | 
| 274 311 | 
             
                    next  if key.include? "."   # Skip extensions
         | 
| 275 312 | 
             
                    assert("env variable #{key} has non-string value #{value.inspect}") {
         | 
| 276 313 | 
             
                      value.kind_of? String
         | 
| 277 314 | 
             
                    }
         | 
| 315 | 
            +
                    next if value.encoding == Encoding::ASCII_8BIT
         | 
| 316 | 
            +
                    assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
         | 
| 317 | 
            +
                      value.b !~ /[\x80-\xff]/n
         | 
| 318 | 
            +
                    }
         | 
| 278 319 | 
             
                  }
         | 
| 279 320 |  | 
| 280 321 | 
             
                  ## There are the following restrictions:
         | 
| @@ -296,7 +337,7 @@ module Rack | |
| 296 337 | 
             
                  check_hijack env
         | 
| 297 338 |  | 
| 298 339 | 
             
                  ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
         | 
| 299 | 
            -
                  assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD] | 
| 340 | 
            +
                  assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
         | 
| 300 341 | 
             
                    env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
         | 
| 301 342 | 
             
                  }
         | 
| 302 343 |  | 
| @@ -337,7 +378,7 @@ module Rack | |
| 337 378 | 
             
                  ## When applicable, its external encoding must be "ASCII-8BIT" and it
         | 
| 338 379 | 
             
                  ## must be opened in binary mode, for Ruby 1.9 compatibility.
         | 
| 339 380 | 
             
                  assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
         | 
| 340 | 
            -
                    input.external_encoding | 
| 381 | 
            +
                    input.external_encoding == Encoding::ASCII_8BIT
         | 
| 341 382 | 
             
                  } if input.respond_to?(:external_encoding)
         | 
| 342 383 | 
             
                  assert("rack.input #{input} is not opened in binary mode") {
         | 
| 343 384 | 
             
                    input.binmode?
         | 
| @@ -569,7 +610,7 @@ module Rack | |
| 569 610 |  | 
| 570 611 | 
             
                  # this check uses headers like a hash, but the spec only requires
         | 
| 571 612 | 
             
                  # headers respond to #each
         | 
| 572 | 
            -
                  headers = Rack::Utils::HeaderHash | 
| 613 | 
            +
                  headers = Rack::Utils::HeaderHash[headers]
         | 
| 573 614 |  | 
| 574 615 | 
             
                  ## In order to do this, an application may set the special header
         | 
| 575 616 | 
             
                  ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
         | 
| @@ -593,7 +634,7 @@ module Rack | |
| 593 634 | 
             
                      headers[RACK_HIJACK].respond_to? :call
         | 
| 594 635 | 
             
                    }
         | 
| 595 636 | 
             
                    original_hijack = headers[RACK_HIJACK]
         | 
| 596 | 
            -
                     | 
| 637 | 
            +
                    proc do |io|
         | 
| 597 638 | 
             
                      original_hijack.call HijackWrapper.new(io)
         | 
| 598 639 | 
             
                    end
         | 
| 599 640 | 
             
                  else
         | 
| @@ -603,6 +644,8 @@ module Rack | |
| 603 644 | 
             
                    assert('rack.hijack header must not be present if server does not support hijacking') {
         | 
| 604 645 | 
             
                      headers[RACK_HIJACK].nil?
         | 
| 605 646 | 
             
                    }
         | 
| 647 | 
            +
             | 
| 648 | 
            +
                    nil
         | 
| 606 649 | 
             
                  end
         | 
| 607 650 | 
             
                end
         | 
| 608 651 | 
             
                ## ==== Conventions
         | 
    
        data/lib/rack/lobster.rb
    CHANGED
    
    | @@ -2,9 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'zlib'
         | 
| 4 4 |  | 
| 5 | 
            -
            require 'rack/request'
         | 
| 6 | 
            -
            require 'rack/response'
         | 
| 7 | 
            -
             | 
| 8 5 | 
             
            module Rack
         | 
| 9 6 | 
             
              # Paste has a Pony, Rack has a Lobster!
         | 
| 10 7 | 
             
              class Lobster
         | 
| @@ -64,9 +61,10 @@ module Rack | |
| 64 61 | 
             
            end
         | 
| 65 62 |  | 
| 66 63 | 
             
            if $0 == __FILE__
         | 
| 67 | 
            -
               | 
| 68 | 
            -
               | 
| 64 | 
            +
              # :nocov:
         | 
| 65 | 
            +
              require_relative '../rack'
         | 
| 69 66 | 
             
              Rack::Server.start(
         | 
| 70 67 | 
             
                app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
         | 
| 71 68 | 
             
              )
         | 
| 69 | 
            +
              # :nocov:
         | 
| 72 70 | 
             
            end
         | 
    
        data/lib/rack/lock.rb
    CHANGED
    
    
    
        data/lib/rack/mock.rb
    CHANGED
    
    | @@ -2,10 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'uri'
         | 
| 4 4 | 
             
            require 'stringio'
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            require 'rack/lint'
         | 
| 7 | 
            -
            require 'rack/utils'
         | 
| 8 | 
            -
            require 'rack/response'
         | 
| 5 | 
            +
            require_relative '../rack'
         | 
| 9 6 | 
             
            require 'cgi/cookie'
         | 
| 10 7 |  | 
| 11 8 | 
             
            module Rack
         | 
| @@ -56,14 +53,24 @@ module Rack | |
| 56 53 | 
             
                  @app = app
         | 
| 57 54 | 
             
                end
         | 
| 58 55 |  | 
| 56 | 
            +
                # Make a GET request and return a MockResponse. See #request.
         | 
| 59 57 | 
             
                def get(uri, opts = {})     request(GET, uri, opts)     end
         | 
| 58 | 
            +
                # Make a POST request and return a MockResponse. See #request.
         | 
| 60 59 | 
             
                def post(uri, opts = {})    request(POST, uri, opts)    end
         | 
| 60 | 
            +
                # Make a PUT request and return a MockResponse. See #request.
         | 
| 61 61 | 
             
                def put(uri, opts = {})     request(PUT, uri, opts)     end
         | 
| 62 | 
            +
                # Make a PATCH request and return a MockResponse. See #request.
         | 
| 62 63 | 
             
                def patch(uri, opts = {})   request(PATCH, uri, opts)   end
         | 
| 64 | 
            +
                # Make a DELETE request and return a MockResponse. See #request.
         | 
| 63 65 | 
             
                def delete(uri, opts = {})  request(DELETE, uri, opts)  end
         | 
| 66 | 
            +
                # Make a HEAD request and return a MockResponse. See #request.
         | 
| 64 67 | 
             
                def head(uri, opts = {})    request(HEAD, uri, opts)    end
         | 
| 68 | 
            +
                # Make an OPTIONS request and return a MockResponse. See #request.
         | 
| 65 69 | 
             
                def options(uri, opts = {}) request(OPTIONS, uri, opts) end
         | 
| 66 70 |  | 
| 71 | 
            +
                # Make a request using the given request method for the given
         | 
| 72 | 
            +
                # uri to the rack application and return a MockResponse.
         | 
| 73 | 
            +
                # Options given are passed to MockRequest.env_for.
         | 
| 67 74 | 
             
                def request(method = GET, uri = "", opts = {})
         | 
| 68 75 | 
             
                  env = self.class.env_for(uri, opts.merge(method: method))
         | 
| 69 76 |  | 
| @@ -88,6 +95,13 @@ module Rack | |
| 88 95 | 
             
                end
         | 
| 89 96 |  | 
| 90 97 | 
             
                # Return the Rack environment used for a request to +uri+.
         | 
| 98 | 
            +
                # All options that are strings are added to the returned environment.
         | 
| 99 | 
            +
                # Options:
         | 
| 100 | 
            +
                # :fatal :: Whether to raise an exception if request outputs to rack.errors
         | 
| 101 | 
            +
                # :input :: The rack.input to set
         | 
| 102 | 
            +
                # :method :: The HTTP request method to use
         | 
| 103 | 
            +
                # :params :: The params to use
         | 
| 104 | 
            +
                # :script_name :: The SCRIPT_NAME to set
         | 
| 91 105 | 
             
                def self.env_for(uri = "", opts = {})
         | 
| 92 106 | 
             
                  uri = parse_uri_rfc2396(uri)
         | 
| 93 107 | 
             
                  uri.path = "/#{uri.path}" unless uri.path[0] == ?/
         | 
| @@ -157,6 +171,10 @@ module Rack | |
| 157 171 | 
             
              # MockRequest.
         | 
| 158 172 |  | 
| 159 173 | 
             
              class MockResponse < Rack::Response
         | 
| 174 | 
            +
                class << self
         | 
| 175 | 
            +
                  alias [] new
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 160 178 | 
             
                # Headers
         | 
| 161 179 | 
             
                attr_reader :original_headers, :cookies
         | 
| 162 180 |  |