[DRE-commits] [SCM] ruby-rack.git branch, master, updated. debian/1.3.1-1-8-ge579ca8
Paul van Tilburg
paul at luon.net
Tue Jan 3 21:55:33 UTC 2012
The following commit has been merged in the master branch:
commit e159324894bae9405eecb6464f86364b5750e779
Author: Paul van Tilburg <paul at luon.net>
Date: Tue Jan 3 11:07:20 2012 +0100
Imported Upstream version 1.4.0
diff --git a/README.rdoc b/README.rdoc
index 558f1c9..664f021 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -352,6 +352,46 @@ run on port 11211) and memcache-client installed.
* Rack::MockResponse calls close on the body object
* Fix a DOS vector from MRI stdlib backport
+* July 16, 2011: Sixteenth public release 1.3.2
+ * Fix for Rails and rack-test, Rack::Utils#escape calls to_s
+
+* Not Yet Released: Seventeenth public release 1.3.3
+ * Fix bug with broken query parameters in Rack::ShowExceptions
+ * Rack::Request#cookies no longer swallows exceptions on broken input
+ * Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine
+ * Rack::ConditionalGet handles broken If-Modified-Since helpers
+
+* September 16, 2011: Eighteenth public release 1.2.4
+ * Fix a bug with MRI regex engine to prevent XSS by malformed unicode
+
+* October 1, 2011: Nineteenth public release 1.3.4
+ * Backport security fix from 1.9.3, also fixes some roundtrip issues in URI
+ * Small documentation update
+ * Fix an issue where BodyProxy could cause an infinite recursion
+ * Add some supporting files for travis-ci
+
+* October 17, 2011: Twentieth public release 1.3.5
+ * Fix annoying warnings caused by the backport in 1.3.4
+
+* December 28th, 2011: Twenty fourth public release 1.4.0
+ * Ruby 1.8.6 support has officially been dropped. Not all tests pass.
+ * Raise sane error messages for broken config.ru
+ * Allow combining run and map in a config.ru
+ * Rack::ContentType will not set Content-Type for responses without a body
+ * Status code 205 does not send a response body
+ * Rack::Response::Helpers will not rely on instance variables
+ * Rack::Utils.build_query no longer outputs '=' for nil query values
+ * Various mime types added
+ * Rack::MockRequest now supports HEAD
+ * Rack::Directory now supports files that contain RFC3986 reserved chars
+ * Rack::File now only supports GET and HEAD requests
+ * Rack::Server#start now passes the block to Rack::Handler::<h>#run
+ * Rack::Static now supports an index option
+ * Added the Teapot status code
+ * rackup now defaults to Thin instead of Mongrel (if installed)
+ * Support added for HTTP_X_FORWARDED_SCHEME
+ * Numerous bug fixes, including many fixes for new and alternate rubies
+
== Contact
Please post bugs, suggestions and patches to
diff --git a/Rakefile b/Rakefile
index 8899877..562b767 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,6 +3,18 @@
desc "Run all the tests"
task :default => [:test]
+desc "Install gem dependencies"
+task :deps do
+ require 'rubygems'
+ spec = Gem::Specification.load('rack.gemspec')
+ spec.dependencies.each do |dep|
+ reqs = dep.requirements_list
+ reqs = (["-v"] * reqs.size).zip(reqs).flatten
+ # Use system over sh, because we want to ignore errors!
+ system "gem", "install", '--conservative', dep.name, *reqs
+ end
+end
+
desc "Make an archive as .tar.gz"
task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
@@ -64,15 +76,18 @@ file "SPEC" => 'lib/rack/lint.rb' do
}
end
-desc "Run all the fast tests"
+desc "Run all the fast + platform agnostic tests"
task :test => 'SPEC' do
opts = ENV['TEST'] || '-a'
specopts = ENV['TESTOPTS'] ||
- "-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server)'"
+ "-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server|Rack::Handler)'"
- sh "bacon -I./lib:./test -w #{opts} #{specopts}"
+ sh "bacon -I./lib:./test #{opts} #{specopts}"
end
+desc "Run all the tests we run on CI"
+task :ci => :test
+
desc "Run all the tests"
task :fulltest => %w[SPEC chmod] do
opts = ENV['TEST'] || '-a'
diff --git a/SPEC b/SPEC
index 525b045..d802e75 100644
--- a/SPEC
+++ b/SPEC
@@ -146,11 +146,11 @@ consisting of lines (for multiple header values, e.g. multiple
The lines must not contain characters below 037.
=== The Content-Type
There must be a <tt>Content-Type</tt>, except when the
-+Status+ is 1xx, 204 or 304, in which case there must be none
++Status+ is 1xx, 204, 205 or 304, in which case there must be none
given.
=== The Content-Length
There must not be a <tt>Content-Length</tt> header when the
-+Status+ is 1xx, 204 or 304.
++Status+ is 1xx, 204, 205 or 304.
=== The Body
The Body must respond to +each+
and must only yield String values.
diff --git a/lib/rack/backports/uri/common.rb b/lib/rack/backports/uri/common_18.rb
similarity index 94%
rename from lib/rack/backports/uri/common.rb
rename to lib/rack/backports/uri/common_18.rb
index aaa7904..4a4d6be 100644
--- a/lib/rack/backports/uri/common.rb
+++ b/lib/rack/backports/uri/common_18.rb
@@ -10,7 +10,7 @@ module URI
TBLENCWWWCOMP_ = {} # :nodoc:
TBLDECWWWCOMP_ = {} # :nodoc:
- # Encode given +str+ to URL-encoded form data.
+ # Encode given +s+ to URL-encoded form data.
#
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
# (ASCII space) to + and converts others to %XX.
@@ -19,7 +19,8 @@ module URI
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
#
# See URI.decode_www_form_component, URI.encode_www_form
- def self.encode_www_form_component(str)
+ def self.encode_www_form_component(s)
+ str = s.to_s
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
@@ -37,7 +38,6 @@ module URI
rescue
end
end
- str = str.to_s
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
end
end
diff --git a/lib/rack/backports/uri/common_192.rb b/lib/rack/backports/uri/common_192.rb
new file mode 100644
index 0000000..4adeb90
--- /dev/null
+++ b/lib/rack/backports/uri/common_192.rb
@@ -0,0 +1,55 @@
+# :stopdoc:
+
+# Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2
+#
+# https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb
+#
+# Issue:
+# http://redmine.ruby-lang.org/issues/5149
+#
+# Relevant Fixes:
+# https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b
+# https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b
+#
+# This should probably be removed once there is a Ruby 1.9.2 patch level that
+# includes this fix.
+
+require 'uri/common'
+
+module URI
+ def self.decode_www_form(str, enc=Encoding::UTF_8)
+ return [] if str.empty?
+ unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
+ raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
+ end
+ ary = []
+ $&.scan(/([^=;&]+)=([^;&]*)/) do
+ ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
+ end
+ ary
+ end
+
+ def self.decode_www_form_component(str, enc=Encoding::UTF_8)
+ if TBLDECWWWCOMP_.empty?
+ tbl = {}
+ 256.times do |i|
+ h, l = i>>4, i&15
+ tbl['%%%X%X' % [h, l]] = i.chr
+ tbl['%%%x%X' % [h, l]] = i.chr
+ tbl['%%%X%x' % [h, l]] = i.chr
+ tbl['%%%x%x' % [h, l]] = i.chr
+ end
+ tbl['+'] = ' '
+ begin
+ TBLDECWWWCOMP_.replace(tbl)
+ TBLDECWWWCOMP_.freeze
+ rescue
+ end
+ end
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
+ str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
+ end
+
+ remove_const :WFKV_
+ WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
+end
diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb
index 921af6b..6362c5f 100644
--- a/lib/rack/body_proxy.rb
+++ b/lib/rack/body_proxy.rb
@@ -9,13 +9,10 @@ module Rack
end
def close
- raise IOError, "closed stream" if @closed
- begin
- @body.close if @body.respond_to? :close
- ensure
- @block.call
- @closed = true
- end
+ return if @closed
+ @closed = true
+ @body.close if @body.respond_to? :close
+ @block.call
end
def closed?
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index db8e2d8..be1970e 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -20,7 +20,7 @@ module Rack
#
# app = Rack::Builder.app do
# use Rack::CommonLogger
- # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
+ # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
# end
#
# run app
@@ -36,7 +36,7 @@ module Rack
if cfgfile[/^#\\(.*)/] && opts
options = opts.parse! $1.split(/\s+/)
end
- cfgfile.sub!(/^__END__\n.*/, '')
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
TOPLEVEL_BINDING, config
else
@@ -46,13 +46,13 @@ module Rack
return app, options
end
- def initialize(&block)
- @ins = []
+ def initialize(default_app = nil,&block)
+ @use, @map, @run = [], nil, default_app
instance_eval(&block) if block_given?
end
- def self.app(&block)
- self.new(&block).to_app
+ def self.app(default_app = nil, &block)
+ self.new(default_app, &block).to_app
end
# Specifies a middleware to use in a stack.
@@ -75,7 +75,11 @@ module Rack
# The +call+ method in this example sets an additional environment key which then can be
# referenced in the application if required.
def use(middleware, *args, &block)
- @ins << lambda { |app| middleware.new(app, *args, &block) }
+ if @map
+ mapping, @map = @map, nil
+ @use << proc { |app| generate_map app, mapping }
+ end
+ @use << proc { |app| middleware.new(app, *args, &block) }
end
# Takes an argument that is an object that responds to #call and returns a Rack response.
@@ -93,7 +97,7 @@ module Rack
#
# run Heartbeat
def run(app)
- @ins << app #lambda { |nothing| app }
+ @run = app
end
# Creates a route within the application.
@@ -116,22 +120,26 @@ module Rack
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
#
def map(path, &block)
- if @ins.last.kind_of? Hash
- @ins.last[path] = self.class.new(&block).to_app
- else
- @ins << {}
- map(path, &block)
- end
+ @map ||= {}
+ @map[path] = block
end
def to_app
- @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
- inner_app = @ins.last
- @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
+ app = @map ? generate_map(@run, @map) : @run
+ fail "missing run or map statement" unless app
+ @use.reverse.inject(app) { |a,e| e[a] }
end
def call(env)
to_app.call(env)
end
+
+ private
+
+ def generate_map(default_app, mapping)
+ mapped = default_app ? {'/' => default_app} : {}
+ mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
+ URLMap.new(mapped)
+ end
end
end
diff --git a/lib/rack/cascade.rb b/lib/rack/cascade.rb
index 14c3e54..4b9ba35 100644
--- a/lib/rack/cascade.rb
+++ b/lib/rack/cascade.rb
@@ -4,7 +4,7 @@ module Rack
# status codes).
class Cascade
- NotFound = [404, {}, []]
+ NotFound = [404, {"Content-Type" => "text/plain"}, []]
attr_reader :apps
diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb
index 60acf25..a400756 100644
--- a/lib/rack/chunked.rb
+++ b/lib/rack/chunked.rb
@@ -23,6 +23,8 @@ module Rack
@body.each do |chunk|
size = bytesize(chunk)
next if size == 0
+
+ chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
yield [size.to_s(16), term, chunk, term].join
end
yield TAIL
diff --git a/lib/rack/conditionalget.rb b/lib/rack/conditionalget.rb
index 136d187..dc580eb 100644
--- a/lib/rack/conditionalget.rb
+++ b/lib/rack/conditionalget.rb
@@ -56,6 +56,7 @@ module Rack
def modified_since?(modified_since, headers)
last_modified = to_rfc2822(headers['Last-Modified']) and
+ modified_since and
modified_since >= last_modified
end
diff --git a/lib/rack/content_type.rb b/lib/rack/content_type.rb
index 874c28c..dd96e95 100644
--- a/lib/rack/content_type.rb
+++ b/lib/rack/content_type.rb
@@ -9,6 +9,8 @@ module Rack
#
# When no content type argument is provided, "text/html" is assumed.
class ContentType
+ include Rack::Utils
+
def initialize(app, content_type = "text/html")
@app, @content_type = app, content_type
end
@@ -16,7 +18,11 @@ module Rack
def call(env)
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
- headers['Content-Type'] ||= @content_type
+
+ unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
+ headers['Content-Type'] ||= @content_type
+ end
+
[status, headers, body]
end
end
diff --git a/lib/rack/directory.rb b/lib/rack/directory.rb
index 927ac0c..d57652a 100644
--- a/lib/rack/directory.rb
+++ b/lib/rack/directory.rb
@@ -80,13 +80,17 @@ table { width:100%%; }
@files = [['../','Parent Directory','','','']]
glob = F.join(@path, '*')
+ url_head = ([@script_name] + @path_info.split('/')).map do |part|
+ Rack::Utils.escape part
+ end
+
Dir[glob].sort.each do |node|
stat = stat(node)
next unless stat
basename = F.basename(node)
ext = F.extname(node)
- url = F.join(@script_name, @path_info, basename)
+ url = F.join(*url_head + [Rack::Utils.escape(basename)])
size = stat.size
type = stat.directory? ? 'directory' : Mime.mime_type(ext)
size = stat.directory? ? '-' : filesize_format(size)
diff --git a/lib/rack/file.rb b/lib/rack/file.rb
index a034ea9..8973195 100644
--- a/lib/rack/file.rb
+++ b/lib/rack/file.rb
@@ -13,6 +13,7 @@ module Rack
class File
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
+ ALLOWED_VERBS = %w[GET HEAD]
attr_accessor :root
attr_accessor :path
@@ -32,10 +33,24 @@ module Rack
F = ::File
def _call(env)
+ unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
+ return fail(403, "Forbidden")
+ end
+
@path_info = Utils.unescape(env["PATH_INFO"])
parts = @path_info.split SEPS
- return fail(403, "Forbidden") if parts.include? ".."
+ parts.inject(0) do |depth, part|
+ case part
+ when '', '.'
+ depth
+ when '..'
+ return fail(403, "Forbidden") if depth - 1 < 0
+ depth - 1
+ else
+ depth + 1
+ end
+ end
@path = F.join(@root, *parts)
@@ -53,22 +68,24 @@ module Rack
end
def serving(env)
- # NOTE:
- # We check via File::size? whether this file provides size info
- # via stat (e.g. /proc files often don't), otherwise we have to
- # figure it out by reading the whole file into memory.
- size = F.size?(@path) || Utils.bytesize(F.read(@path))
-
+ last_modified = F.mtime(@path).httpdate
+ return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
response = [
200,
{
- "Last-Modified" => F.mtime(@path).httpdate,
+ "Last-Modified" => last_modified,
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain')
},
- self
+ env["REQUEST_METHOD"] == "HEAD" ? [] : self
]
response[1].merge! 'Cache-Control' => @cache_control if @cache_control
+ # NOTE:
+ # We check via File::size? whether this file provides size info
+ # via stat (e.g. /proc files often don't), otherwise we have to
+ # figure it out by reading the whole file into memory.
+ size = F.size?(@path) || Utils.bytesize(F.read(@path))
+
ranges = Rack::Utils.byte_ranges(env, size)
if ranges.nil? || ranges.length > 1
# No ranges, or multiple ranges (which we don't support):
diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb
index a2649df..70316be 100644
--- a/lib/rack/handler.rb
+++ b/lib/rack/handler.rb
@@ -1,7 +1,7 @@
module Rack
# *Handlers* connect web servers with Rack.
#
- # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
+ # Rack includes Handlers for Thin, WEBrick, FastCGI, CGI, SCGI
# and LiteSpeed.
#
# Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
@@ -38,7 +38,7 @@ module Rack
Rack::Handler::CGI
else
begin
- Rack::Handler::Mongrel
+ Rack::Handler::Thin
rescue LoadError
Rack::Handler::WEBrick
end
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index d21ea21..2448f22 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -464,7 +464,7 @@ module Rack
def check_content_type(status, headers)
headers.each { |key, value|
## There must be a <tt>Content-Type</tt>, except when the
- ## +Status+ is 1xx, 204 or 304, in which case there must be none
+ ## +Status+ is 1xx, 204, 205 or 304, in which case there must be none
## given.
if key.downcase == "content-type"
assert("Content-Type header found in #{status} response, not allowed") {
@@ -483,7 +483,7 @@ module Rack
headers.each { |key, value|
if key.downcase == 'content-length'
## There must not be a <tt>Content-Length</tt> header when the
- ## +Status+ is 1xx, 204 or 304.
+ ## +Status+ is 1xx, 204, 205 or 304.
assert("Content-Length header found in #{status} response, not allowed") {
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
}
diff --git a/lib/rack/methodoverride.rb b/lib/rack/methodoverride.rb
index e1bca56..48d6afd 100644
--- a/lib/rack/methodoverride.rb
+++ b/lib/rack/methodoverride.rb
@@ -11,10 +11,7 @@ module Rack
def call(env)
if env["REQUEST_METHOD"] == "POST"
- req = Request.new(env)
- method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
- env[HTTP_METHOD_OVERRIDE_HEADER]
- method = method.to_s.upcase
+ method = method_override(env)
if HTTP_METHODS.include?(method)
env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
env["REQUEST_METHOD"] = method
@@ -23,5 +20,14 @@ module Rack
@app.call(env)
end
+
+ def method_override(env)
+ req = Request.new(env)
+ method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
+ env[HTTP_METHOD_OVERRIDE_HEADER]
+ method.to_s.upcase
+ rescue EOFError
+ ""
+ end
end
end
diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb
index 38c58f9..d2e908c 100644
--- a/lib/rack/mime.rb
+++ b/lib/rack/mime.rb
@@ -28,183 +28,621 @@ module Rack
# list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
# Rack::Mime::MIME_TYPES.merge!(list)
#
+ # N.B. On Ubuntu the mime.types file does not include the leading period, so
+ # users may need to modify the data before merging into the hash.
+ #
# To add the list mongrel provides, use:
#
# require 'mongrel/handlers'
# Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
MIME_TYPES = {
- ".3gp" => "video/3gpp",
- ".a" => "application/octet-stream",
- ".ai" => "application/postscript",
- ".aif" => "audio/x-aiff",
- ".aiff" => "audio/x-aiff",
- ".asc" => "application/pgp-signature",
- ".asf" => "video/x-ms-asf",
- ".asm" => "text/x-asm",
- ".asx" => "video/x-ms-asf",
- ".atom" => "application/atom+xml",
- ".au" => "audio/basic",
- ".avi" => "video/x-msvideo",
- ".bat" => "application/x-msdownload",
- ".bin" => "application/octet-stream",
- ".bmp" => "image/bmp",
- ".bz2" => "application/x-bzip2",
- ".c" => "text/x-c",
- ".cab" => "application/vnd.ms-cab-compressed",
- ".cc" => "text/x-c",
- ".chm" => "application/vnd.ms-htmlhelp",
- ".class" => "application/octet-stream",
- ".com" => "application/x-msdownload",
- ".conf" => "text/plain",
- ".cpp" => "text/x-c",
- ".crt" => "application/x-x509-ca-cert",
- ".css" => "text/css",
- ".csv" => "text/csv",
- ".cxx" => "text/x-c",
- ".deb" => "application/x-debian-package",
- ".der" => "application/x-x509-ca-cert",
- ".diff" => "text/x-diff",
- ".djv" => "image/vnd.djvu",
- ".djvu" => "image/vnd.djvu",
- ".dll" => "application/x-msdownload",
- ".dmg" => "application/octet-stream",
- ".doc" => "application/msword",
- ".dot" => "application/msword",
- ".dtd" => "application/xml-dtd",
- ".dvi" => "application/x-dvi",
- ".ear" => "application/java-archive",
- ".eml" => "message/rfc822",
- ".eps" => "application/postscript",
- ".exe" => "application/x-msdownload",
- ".f" => "text/x-fortran",
- ".f77" => "text/x-fortran",
- ".f90" => "text/x-fortran",
- ".flv" => "video/x-flv",
- ".for" => "text/x-fortran",
- ".gem" => "application/octet-stream",
- ".gemspec" => "text/x-script.ruby",
- ".gif" => "image/gif",
- ".gz" => "application/x-gzip",
- ".h" => "text/x-c",
- ".htc" => "text/x-component",
- ".hh" => "text/x-c",
- ".htm" => "text/html",
- ".html" => "text/html",
- ".ico" => "image/vnd.microsoft.icon",
- ".ics" => "text/calendar",
- ".ifb" => "text/calendar",
- ".iso" => "application/octet-stream",
- ".jar" => "application/java-archive",
- ".java" => "text/x-java-source",
- ".jnlp" => "application/x-java-jnlp-file",
- ".jpeg" => "image/jpeg",
- ".jpg" => "image/jpeg",
- ".js" => "application/javascript",
- ".json" => "application/json",
- ".log" => "text/plain",
- ".m3u" => "audio/x-mpegurl",
- ".m4v" => "video/mp4",
- ".man" => "text/troff",
- ".manifest"=> "text/cache-manifest",
- ".mathml" => "application/mathml+xml",
- ".mbox" => "application/mbox",
- ".mdoc" => "text/troff",
- ".me" => "text/troff",
- ".mid" => "audio/midi",
- ".midi" => "audio/midi",
- ".mime" => "message/rfc822",
- ".mml" => "application/mathml+xml",
- ".mng" => "video/x-mng",
- ".mov" => "video/quicktime",
- ".mp3" => "audio/mpeg",
- ".mp4" => "video/mp4",
- ".mp4v" => "video/mp4",
- ".mpeg" => "video/mpeg",
- ".mpg" => "video/mpeg",
- ".ms" => "text/troff",
- ".msi" => "application/x-msdownload",
- ".odp" => "application/vnd.oasis.opendocument.presentation",
- ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
- ".odt" => "application/vnd.oasis.opendocument.text",
- ".ogg" => "application/ogg",
- ".ogv" => "video/ogg",
- ".p" => "text/x-pascal",
- ".pas" => "text/x-pascal",
- ".pbm" => "image/x-portable-bitmap",
- ".pdf" => "application/pdf",
- ".pem" => "application/x-x509-ca-cert",
- ".pgm" => "image/x-portable-graymap",
- ".pgp" => "application/pgp-encrypted",
- ".pkg" => "application/octet-stream",
- ".pl" => "text/x-script.perl",
- ".pm" => "text/x-script.perl-module",
- ".png" => "image/png",
- ".pnm" => "image/x-portable-anymap",
- ".ppm" => "image/x-portable-pixmap",
- ".pps" => "application/vnd.ms-powerpoint",
- ".ppt" => "application/vnd.ms-powerpoint",
- ".ps" => "application/postscript",
- ".psd" => "image/vnd.adobe.photoshop",
- ".py" => "text/x-script.python",
- ".qt" => "video/quicktime",
- ".ra" => "audio/x-pn-realaudio",
- ".rake" => "text/x-script.ruby",
- ".ram" => "audio/x-pn-realaudio",
- ".rar" => "application/x-rar-compressed",
- ".rb" => "text/x-script.ruby",
- ".rdf" => "application/rdf+xml",
- ".roff" => "text/troff",
- ".rpm" => "application/x-redhat-package-manager",
- ".rss" => "application/rss+xml",
- ".rtf" => "application/rtf",
- ".ru" => "text/x-script.ruby",
- ".s" => "text/x-asm",
- ".sgm" => "text/sgml",
- ".sgml" => "text/sgml",
- ".sh" => "application/x-sh",
- ".sig" => "application/pgp-signature",
- ".snd" => "audio/basic",
- ".so" => "application/octet-stream",
- ".svg" => "image/svg+xml",
- ".svgz" => "image/svg+xml",
- ".swf" => "application/x-shockwave-flash",
- ".t" => "text/troff",
- ".tar" => "application/x-tar",
- ".tbz" => "application/x-bzip-compressed-tar",
- ".tcl" => "application/x-tcl",
- ".tex" => "application/x-tex",
- ".texi" => "application/x-texinfo",
- ".texinfo" => "application/x-texinfo",
- ".text" => "text/plain",
- ".tif" => "image/tiff",
- ".tiff" => "image/tiff",
- ".torrent" => "application/x-bittorrent",
- ".tr" => "text/troff",
- ".ttf" => "application/octet-stream",
- ".txt" => "text/plain",
- ".vcf" => "text/x-vcard",
- ".vcs" => "text/x-vcalendar",
- ".vrml" => "model/vrml",
- ".war" => "application/java-archive",
- ".wav" => "audio/x-wav",
- ".webm" => "video/webm",
- ".wma" => "audio/x-ms-wma",
- ".wmv" => "video/x-ms-wmv",
- ".wmx" => "video/x-ms-wmx",
- ".woff" => "application/octet-stream",
- ".wrl" => "model/vrml",
- ".wsdl" => "application/wsdl+xml",
- ".xbm" => "image/x-xbitmap",
- ".xhtml" => "application/xhtml+xml",
- ".xls" => "application/vnd.ms-excel",
- ".xml" => "application/xml",
- ".xpm" => "image/x-xpixmap",
- ".xsl" => "application/xml",
- ".xslt" => "application/xslt+xml",
- ".yaml" => "text/yaml",
- ".yml" => "text/yaml",
- ".zip" => "application/zip",
+ ".123" => "application/vnd.lotus-1-2-3",
+ ".3dml" => "text/vnd.in3d.3dml",
+ ".3g2" => "video/3gpp2",
+ ".3gp" => "video/3gpp",
+ ".a" => "application/octet-stream",
+ ".acc" => "application/vnd.americandynamics.acc",
+ ".ace" => "application/x-ace-compressed",
+ ".acu" => "application/vnd.acucobol",
+ ".aep" => "application/vnd.audiograph",
+ ".afp" => "application/vnd.ibm.modcap",
+ ".ai" => "application/postscript",
+ ".aif" => "audio/x-aiff",
+ ".aiff" => "audio/x-aiff",
+ ".ami" => "application/vnd.amiga.ami",
+ ".appcache" => "text/cache-manifest",
+ ".apr" => "application/vnd.lotus-approach",
+ ".asc" => "application/pgp-signature",
+ ".asf" => "video/x-ms-asf",
+ ".asm" => "text/x-asm",
+ ".aso" => "application/vnd.accpac.simply.aso",
+ ".asx" => "video/x-ms-asf",
+ ".atc" => "application/vnd.acucorp",
+ ".atom" => "application/atom+xml",
+ ".atomcat" => "application/atomcat+xml",
+ ".atomsvc" => "application/atomsvc+xml",
+ ".atx" => "application/vnd.antix.game-component",
+ ".au" => "audio/basic",
+ ".avi" => "video/x-msvideo",
+ ".bat" => "application/x-msdownload",
+ ".bcpio" => "application/x-bcpio",
+ ".bdm" => "application/vnd.syncml.dm+wbxml",
+ ".bh2" => "application/vnd.fujitsu.oasysprs",
+ ".bin" => "application/octet-stream",
+ ".bmi" => "application/vnd.bmi",
+ ".bmp" => "image/bmp",
+ ".box" => "application/vnd.previewsystems.box",
+ ".btif" => "image/prs.btif",
+ ".bz" => "application/x-bzip",
+ ".bz2" => "application/x-bzip2",
+ ".c" => "text/x-c",
+ ".c4g" => "application/vnd.clonk.c4group",
+ ".cab" => "application/vnd.ms-cab-compressed",
+ ".cc" => "text/x-c",
+ ".ccxml" => "application/ccxml+xml",
+ ".cdbcmsg" => "application/vnd.contact.cmsg",
+ ".cdkey" => "application/vnd.mediastation.cdkey",
+ ".cdx" => "chemical/x-cdx",
+ ".cdxml" => "application/vnd.chemdraw+xml",
+ ".cdy" => "application/vnd.cinderella",
+ ".cer" => "application/pkix-cert",
+ ".cgm" => "image/cgm",
+ ".chat" => "application/x-chat",
+ ".chm" => "application/vnd.ms-htmlhelp",
+ ".chrt" => "application/vnd.kde.kchart",
+ ".cif" => "chemical/x-cif",
+ ".cii" => "application/vnd.anser-web-certificate-issue-initiation",
+ ".cil" => "application/vnd.ms-artgalry",
+ ".cla" => "application/vnd.claymore",
+ ".class" => "application/octet-stream",
+ ".clkk" => "application/vnd.crick.clicker.keyboard",
+ ".clkp" => "application/vnd.crick.clicker.palette",
+ ".clkt" => "application/vnd.crick.clicker.template",
+ ".clkw" => "application/vnd.crick.clicker.wordbank",
+ ".clkx" => "application/vnd.crick.clicker",
+ ".clp" => "application/x-msclip",
+ ".cmc" => "application/vnd.cosmocaller",
+ ".cmdf" => "chemical/x-cmdf",
+ ".cml" => "chemical/x-cml",
+ ".cmp" => "application/vnd.yellowriver-custom-menu",
+ ".cmx" => "image/x-cmx",
+ ".com" => "application/x-msdownload",
+ ".conf" => "text/plain",
+ ".cpio" => "application/x-cpio",
+ ".cpp" => "text/x-c",
+ ".cpt" => "application/mac-compactpro",
+ ".crd" => "application/x-mscardfile",
+ ".crl" => "application/pkix-crl",
+ ".crt" => "application/x-x509-ca-cert",
+ ".csh" => "application/x-csh",
+ ".csml" => "chemical/x-csml",
+ ".csp" => "application/vnd.commonspace",
+ ".css" => "text/css",
+ ".csv" => "text/csv",
+ ".curl" => "application/vnd.curl",
+ ".cww" => "application/prs.cww",
+ ".cxx" => "text/x-c",
+ ".daf" => "application/vnd.mobius.daf",
+ ".davmount" => "application/davmount+xml",
+ ".dcr" => "application/x-director",
+ ".dd2" => "application/vnd.oma.dd2+xml",
+ ".ddd" => "application/vnd.fujixerox.ddd",
+ ".deb" => "application/x-debian-package",
+ ".der" => "application/x-x509-ca-cert",
+ ".dfac" => "application/vnd.dreamfactory",
+ ".diff" => "text/x-diff",
+ ".dis" => "application/vnd.mobius.dis",
+ ".djv" => "image/vnd.djvu",
+ ".djvu" => "image/vnd.djvu",
+ ".dll" => "application/x-msdownload",
+ ".dmg" => "application/octet-stream",
+ ".dna" => "application/vnd.dna",
+ ".doc" => "application/msword",
+ ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ ".dot" => "application/msword",
+ ".dp" => "application/vnd.osgi.dp",
+ ".dpg" => "application/vnd.dpgraph",
+ ".dsc" => "text/prs.lines.tag",
+ ".dtd" => "application/xml-dtd",
+ ".dts" => "audio/vnd.dts",
+ ".dtshd" => "audio/vnd.dts.hd",
+ ".dv" => "video/x-dv",
+ ".dvi" => "application/x-dvi",
+ ".dwf" => "model/vnd.dwf",
+ ".dwg" => "image/vnd.dwg",
+ ".dxf" => "image/vnd.dxf",
+ ".dxp" => "application/vnd.spotfire.dxp",
+ ".ear" => "application/java-archive",
+ ".ecelp4800" => "audio/vnd.nuera.ecelp4800",
+ ".ecelp7470" => "audio/vnd.nuera.ecelp7470",
+ ".ecelp9600" => "audio/vnd.nuera.ecelp9600",
+ ".ecma" => "application/ecmascript",
+ ".edm" => "application/vnd.novadigm.edm",
+ ".edx" => "application/vnd.novadigm.edx",
+ ".efif" => "application/vnd.picsel",
+ ".ei6" => "application/vnd.pg.osasli",
+ ".eml" => "message/rfc822",
+ ".eol" => "audio/vnd.digital-winds",
+ ".eot" => "application/vnd.ms-fontobject",
+ ".eps" => "application/postscript",
+ ".es3" => "application/vnd.eszigno3+xml",
+ ".esf" => "application/vnd.epson.esf",
+ ".etx" => "text/x-setext",
+ ".exe" => "application/x-msdownload",
+ ".ext" => "application/vnd.novadigm.ext",
+ ".ez" => "application/andrew-inset",
+ ".ez2" => "application/vnd.ezpix-album",
+ ".ez3" => "application/vnd.ezpix-package",
+ ".f" => "text/x-fortran",
+ ".f77" => "text/x-fortran",
+ ".f90" => "text/x-fortran",
+ ".fbs" => "image/vnd.fastbidsheet",
+ ".fdf" => "application/vnd.fdf",
+ ".fe_launch" => "application/vnd.denovo.fcselayout-link",
+ ".fg5" => "application/vnd.fujitsu.oasysgp",
+ ".fli" => "video/x-fli",
+ ".flo" => "application/vnd.micrografx.flo",
+ ".flv" => "video/x-flv",
+ ".flw" => "application/vnd.kde.kivio",
+ ".flx" => "text/vnd.fmi.flexstor",
+ ".fly" => "text/vnd.fly",
+ ".fm" => "application/vnd.framemaker",
+ ".fnc" => "application/vnd.frogans.fnc",
+ ".for" => "text/x-fortran",
+ ".fpx" => "image/vnd.fpx",
+ ".fsc" => "application/vnd.fsc.weblaunch",
+ ".fst" => "image/vnd.fst",
+ ".ftc" => "application/vnd.fluxtime.clip",
+ ".fti" => "application/vnd.anser-web-funds-transfer-initiation",
+ ".fvt" => "video/vnd.fvt",
+ ".fzs" => "application/vnd.fuzzysheet",
+ ".g3" => "image/g3fax",
+ ".gac" => "application/vnd.groove-account",
+ ".gdl" => "model/vnd.gdl",
+ ".gem" => "application/octet-stream",
+ ".gemspec" => "text/x-script.ruby",
+ ".ghf" => "application/vnd.groove-help",
+ ".gif" => "image/gif",
+ ".gim" => "application/vnd.groove-identity-message",
+ ".gmx" => "application/vnd.gmx",
+ ".gph" => "application/vnd.flographit",
+ ".gqf" => "application/vnd.grafeq",
+ ".gram" => "application/srgs",
+ ".grv" => "application/vnd.groove-injector",
+ ".grxml" => "application/srgs+xml",
+ ".gtar" => "application/x-gtar",
+ ".gtm" => "application/vnd.groove-tool-message",
+ ".gtw" => "model/vnd.gtw",
+ ".gv" => "text/vnd.graphviz",
+ ".gz" => "application/x-gzip",
+ ".h" => "text/x-c",
+ ".h261" => "video/h261",
+ ".h263" => "video/h263",
+ ".h264" => "video/h264",
+ ".hbci" => "application/vnd.hbci",
+ ".hdf" => "application/x-hdf",
+ ".hh" => "text/x-c",
+ ".hlp" => "application/winhlp",
+ ".hpgl" => "application/vnd.hp-hpgl",
+ ".hpid" => "application/vnd.hp-hpid",
+ ".hps" => "application/vnd.hp-hps",
+ ".hqx" => "application/mac-binhex40",
+ ".htc" => "text/x-component",
+ ".htke" => "application/vnd.kenameaapp",
+ ".htm" => "text/html",
+ ".html" => "text/html",
+ ".hvd" => "application/vnd.yamaha.hv-dic",
+ ".hvp" => "application/vnd.yamaha.hv-voice",
+ ".hvs" => "application/vnd.yamaha.hv-script",
+ ".icc" => "application/vnd.iccprofile",
+ ".ice" => "x-conference/x-cooltalk",
+ ".ico" => "image/vnd.microsoft.icon",
+ ".ics" => "text/calendar",
+ ".ief" => "image/ief",
+ ".ifb" => "text/calendar",
+ ".ifm" => "application/vnd.shana.informed.formdata",
+ ".igl" => "application/vnd.igloader",
+ ".igs" => "model/iges",
+ ".igx" => "application/vnd.micrografx.igx",
+ ".iif" => "application/vnd.shana.informed.interchange",
+ ".imp" => "application/vnd.accpac.simply.imp",
+ ".ims" => "application/vnd.ms-ims",
+ ".ipk" => "application/vnd.shana.informed.package",
+ ".irm" => "application/vnd.ibm.rights-management",
+ ".irp" => "application/vnd.irepository.package+xml",
+ ".iso" => "application/octet-stream",
+ ".itp" => "application/vnd.shana.informed.formtemplate",
+ ".ivp" => "application/vnd.immervision-ivp",
+ ".ivu" => "application/vnd.immervision-ivu",
+ ".jad" => "text/vnd.sun.j2me.app-descriptor",
+ ".jam" => "application/vnd.jam",
+ ".jar" => "application/java-archive",
+ ".java" => "text/x-java-source",
+ ".jisp" => "application/vnd.jisp",
+ ".jlt" => "application/vnd.hp-jlyt",
+ ".jnlp" => "application/x-java-jnlp-file",
+ ".joda" => "application/vnd.joost.joda-archive",
+ ".jp2" => "image/jp2",
+ ".jpeg" => "image/jpeg",
+ ".jpg" => "image/jpeg",
+ ".jpgv" => "video/jpeg",
+ ".jpm" => "video/jpm",
+ ".js" => "application/javascript",
+ ".json" => "application/json",
+ ".karbon" => "application/vnd.kde.karbon",
+ ".kfo" => "application/vnd.kde.kformula",
+ ".kia" => "application/vnd.kidspiration",
+ ".kml" => "application/vnd.google-earth.kml+xml",
+ ".kmz" => "application/vnd.google-earth.kmz",
+ ".kne" => "application/vnd.kinar",
+ ".kon" => "application/vnd.kde.kontour",
+ ".kpr" => "application/vnd.kde.kpresenter",
+ ".ksp" => "application/vnd.kde.kspread",
+ ".ktz" => "application/vnd.kahootz",
+ ".kwd" => "application/vnd.kde.kword",
+ ".latex" => "application/x-latex",
+ ".lbd" => "application/vnd.llamagraphics.life-balance.desktop",
+ ".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml",
+ ".les" => "application/vnd.hhe.lesson-player",
+ ".link66" => "application/vnd.route66.link66+xml",
+ ".log" => "text/plain",
+ ".lostxml" => "application/lost+xml",
+ ".lrm" => "application/vnd.ms-lrm",
+ ".ltf" => "application/vnd.frogans.ltf",
+ ".lvp" => "audio/vnd.lucent.voice",
+ ".lwp" => "application/vnd.lotus-wordpro",
+ ".m3u" => "audio/x-mpegurl",
+ ".m4a" => "audio/mp4a-latm",
+ ".m4v" => "video/mp4",
+ ".ma" => "application/mathematica",
+ ".mag" => "application/vnd.ecowin.chart",
+ ".man" => "text/troff",
+ ".manifest" => "text/cache-manifest",
+ ".mathml" => "application/mathml+xml",
+ ".mbk" => "application/vnd.mobius.mbk",
+ ".mbox" => "application/mbox",
+ ".mc1" => "application/vnd.medcalcdata",
+ ".mcd" => "application/vnd.mcd",
+ ".mdb" => "application/x-msaccess",
+ ".mdi" => "image/vnd.ms-modi",
+ ".mdoc" => "text/troff",
+ ".me" => "text/troff",
+ ".mfm" => "application/vnd.mfmp",
+ ".mgz" => "application/vnd.proteus.magazine",
+ ".mid" => "audio/midi",
+ ".midi" => "audio/midi",
+ ".mif" => "application/vnd.mif",
+ ".mime" => "message/rfc822",
+ ".mj2" => "video/mj2",
+ ".mlp" => "application/vnd.dolby.mlp",
+ ".mmd" => "application/vnd.chipnuts.karaoke-mmd",
+ ".mmf" => "application/vnd.smaf",
+ ".mml" => "application/mathml+xml",
+ ".mmr" => "image/vnd.fujixerox.edmics-mmr",
+ ".mng" => "video/x-mng",
+ ".mny" => "application/x-msmoney",
+ ".mov" => "video/quicktime",
+ ".movie" => "video/x-sgi-movie",
+ ".mp3" => "audio/mpeg",
+ ".mp4" => "video/mp4",
+ ".mp4a" => "audio/mp4",
+ ".mp4s" => "application/mp4",
+ ".mp4v" => "video/mp4",
+ ".mpc" => "application/vnd.mophun.certificate",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mpga" => "audio/mpeg",
+ ".mpkg" => "application/vnd.apple.installer+xml",
+ ".mpm" => "application/vnd.blueice.multipass",
+ ".mpn" => "application/vnd.mophun.application",
+ ".mpp" => "application/vnd.ms-project",
+ ".mpy" => "application/vnd.ibm.minipay",
+ ".mqy" => "application/vnd.mobius.mqy",
+ ".mrc" => "application/marc",
+ ".ms" => "text/troff",
+ ".mscml" => "application/mediaservercontrol+xml",
+ ".mseq" => "application/vnd.mseq",
+ ".msf" => "application/vnd.epson.msf",
+ ".msh" => "model/mesh",
+ ".msi" => "application/x-msdownload",
+ ".msl" => "application/vnd.mobius.msl",
+ ".msty" => "application/vnd.muvee.style",
+ ".mts" => "model/vnd.mts",
+ ".mus" => "application/vnd.musician",
+ ".mvb" => "application/x-msmediaview",
+ ".mwf" => "application/vnd.mfer",
+ ".mxf" => "application/mxf",
+ ".mxl" => "application/vnd.recordare.musicxml",
+ ".mxml" => "application/xv+xml",
+ ".mxs" => "application/vnd.triscape.mxs",
+ ".mxu" => "video/vnd.mpegurl",
+ ".n" => "application/vnd.nokia.n-gage.symbian.install",
+ ".nc" => "application/x-netcdf",
+ ".ngdat" => "application/vnd.nokia.n-gage.data",
+ ".nlu" => "application/vnd.neurolanguage.nlu",
+ ".nml" => "application/vnd.enliven",
+ ".nnd" => "application/vnd.noblenet-directory",
+ ".nns" => "application/vnd.noblenet-sealer",
+ ".nnw" => "application/vnd.noblenet-web",
+ ".npx" => "image/vnd.net-fpx",
+ ".nsf" => "application/vnd.lotus-notes",
+ ".oa2" => "application/vnd.fujitsu.oasys2",
+ ".oa3" => "application/vnd.fujitsu.oasys3",
+ ".oas" => "application/vnd.fujitsu.oasys",
+ ".obd" => "application/x-msbinder",
+ ".oda" => "application/oda",
+ ".odc" => "application/vnd.oasis.opendocument.chart",
+ ".odf" => "application/vnd.oasis.opendocument.formula",
+ ".odg" => "application/vnd.oasis.opendocument.graphics",
+ ".odi" => "application/vnd.oasis.opendocument.image",
+ ".odp" => "application/vnd.oasis.opendocument.presentation",
+ ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
+ ".odt" => "application/vnd.oasis.opendocument.text",
+ ".oga" => "audio/ogg",
+ ".ogg" => "application/ogg",
+ ".ogv" => "video/ogg",
+ ".ogx" => "application/ogg",
+ ".org" => "application/vnd.lotus-organizer",
+ ".otc" => "application/vnd.oasis.opendocument.chart-template",
+ ".otf" => "application/vnd.oasis.opendocument.formula-template",
+ ".otg" => "application/vnd.oasis.opendocument.graphics-template",
+ ".oth" => "application/vnd.oasis.opendocument.text-web",
+ ".oti" => "application/vnd.oasis.opendocument.image-template",
+ ".otm" => "application/vnd.oasis.opendocument.text-master",
+ ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template",
+ ".ott" => "application/vnd.oasis.opendocument.text-template",
+ ".oxt" => "application/vnd.openofficeorg.extension",
+ ".p" => "text/x-pascal",
+ ".p10" => "application/pkcs10",
+ ".p12" => "application/x-pkcs12",
+ ".p7b" => "application/x-pkcs7-certificates",
+ ".p7m" => "application/pkcs7-mime",
+ ".p7r" => "application/x-pkcs7-certreqresp",
+ ".p7s" => "application/pkcs7-signature",
+ ".pas" => "text/x-pascal",
+ ".pbd" => "application/vnd.powerbuilder6",
+ ".pbm" => "image/x-portable-bitmap",
+ ".pcl" => "application/vnd.hp-pcl",
+ ".pclxl" => "application/vnd.hp-pclxl",
+ ".pcx" => "image/x-pcx",
+ ".pdb" => "chemical/x-pdb",
+ ".pdf" => "application/pdf",
+ ".pem" => "application/x-x509-ca-cert",
+ ".pfr" => "application/font-tdpfr",
+ ".pgm" => "image/x-portable-graymap",
+ ".pgn" => "application/x-chess-pgn",
+ ".pgp" => "application/pgp-encrypted",
+ ".pic" => "image/x-pict",
+ ".pict" => "image/pict",
+ ".pkg" => "application/octet-stream",
+ ".pki" => "application/pkixcmp",
+ ".pkipath" => "application/pkix-pkipath",
+ ".pl" => "text/x-script.perl",
+ ".plb" => "application/vnd.3gpp.pic-bw-large",
+ ".plc" => "application/vnd.mobius.plc",
+ ".plf" => "application/vnd.pocketlearn",
+ ".pls" => "application/pls+xml",
+ ".pm" => "text/x-script.perl-module",
+ ".pml" => "application/vnd.ctc-posml",
+ ".png" => "image/png",
+ ".pnm" => "image/x-portable-anymap",
+ ".pntg" => "image/x-macpaint",
+ ".portpkg" => "application/vnd.macports.portpkg",
+ ".ppd" => "application/vnd.cups-ppd",
+ ".ppm" => "image/x-portable-pixmap",
+ ".pps" => "application/vnd.ms-powerpoint",
+ ".ppt" => "application/vnd.ms-powerpoint",
+ ".prc" => "application/vnd.palm",
+ ".pre" => "application/vnd.lotus-freelance",
+ ".prf" => "application/pics-rules",
+ ".ps" => "application/postscript",
+ ".psb" => "application/vnd.3gpp.pic-bw-small",
+ ".psd" => "image/vnd.adobe.photoshop",
+ ".ptid" => "application/vnd.pvi.ptid1",
+ ".pub" => "application/x-mspublisher",
+ ".pvb" => "application/vnd.3gpp.pic-bw-var",
+ ".pwn" => "application/vnd.3m.post-it-notes",
+ ".py" => "text/x-script.python",
+ ".pya" => "audio/vnd.ms-playready.media.pya",
+ ".pyv" => "video/vnd.ms-playready.media.pyv",
+ ".qam" => "application/vnd.epson.quickanime",
+ ".qbo" => "application/vnd.intu.qbo",
+ ".qfx" => "application/vnd.intu.qfx",
+ ".qps" => "application/vnd.publishare-delta-tree",
+ ".qt" => "video/quicktime",
+ ".qtif" => "image/x-quicktime",
+ ".qxd" => "application/vnd.quark.quarkxpress",
+ ".ra" => "audio/x-pn-realaudio",
+ ".rake" => "text/x-script.ruby",
+ ".ram" => "audio/x-pn-realaudio",
+ ".rar" => "application/x-rar-compressed",
+ ".ras" => "image/x-cmu-raster",
+ ".rb" => "text/x-script.ruby",
+ ".rcprofile" => "application/vnd.ipunplugged.rcprofile",
+ ".rdf" => "application/rdf+xml",
+ ".rdz" => "application/vnd.data-vision.rdz",
+ ".rep" => "application/vnd.businessobjects",
+ ".rgb" => "image/x-rgb",
+ ".rif" => "application/reginfo+xml",
+ ".rl" => "application/resource-lists+xml",
+ ".rlc" => "image/vnd.fujixerox.edmics-rlc",
+ ".rld" => "application/resource-lists-diff+xml",
+ ".rm" => "application/vnd.rn-realmedia",
+ ".rmp" => "audio/x-pn-realaudio-plugin",
+ ".rms" => "application/vnd.jcp.javame.midlet-rms",
+ ".rnc" => "application/relax-ng-compact-syntax",
+ ".roff" => "text/troff",
+ ".rpm" => "application/x-redhat-package-manager",
+ ".rpss" => "application/vnd.nokia.radio-presets",
+ ".rpst" => "application/vnd.nokia.radio-preset",
+ ".rq" => "application/sparql-query",
+ ".rs" => "application/rls-services+xml",
+ ".rsd" => "application/rsd+xml",
+ ".rss" => "application/rss+xml",
+ ".rtf" => "application/rtf",
+ ".rtx" => "text/richtext",
+ ".ru" => "text/x-script.ruby",
+ ".s" => "text/x-asm",
+ ".saf" => "application/vnd.yamaha.smaf-audio",
+ ".sbml" => "application/sbml+xml",
+ ".sc" => "application/vnd.ibm.secure-container",
+ ".scd" => "application/x-msschedule",
+ ".scm" => "application/vnd.lotus-screencam",
+ ".scq" => "application/scvp-cv-request",
+ ".scs" => "application/scvp-cv-response",
+ ".sdkm" => "application/vnd.solent.sdkm+xml",
+ ".sdp" => "application/sdp",
+ ".see" => "application/vnd.seemail",
+ ".sema" => "application/vnd.sema",
+ ".semd" => "application/vnd.semd",
+ ".semf" => "application/vnd.semf",
+ ".setpay" => "application/set-payment-initiation",
+ ".setreg" => "application/set-registration-initiation",
+ ".sfd" => "application/vnd.hydrostatix.sof-data",
+ ".sfs" => "application/vnd.spotfire.sfs",
+ ".sgm" => "text/sgml",
+ ".sgml" => "text/sgml",
+ ".sh" => "application/x-sh",
+ ".shar" => "application/x-shar",
+ ".shf" => "application/shf+xml",
+ ".sig" => "application/pgp-signature",
+ ".sit" => "application/x-stuffit",
+ ".sitx" => "application/x-stuffitx",
+ ".skp" => "application/vnd.koan",
+ ".slt" => "application/vnd.epson.salt",
+ ".smi" => "application/smil+xml",
+ ".snd" => "audio/basic",
+ ".so" => "application/octet-stream",
+ ".spf" => "application/vnd.yamaha.smaf-phrase",
+ ".spl" => "application/x-futuresplash",
+ ".spot" => "text/vnd.in3d.spot",
+ ".spp" => "application/scvp-vp-response",
+ ".spq" => "application/scvp-vp-request",
+ ".src" => "application/x-wais-source",
+ ".srx" => "application/sparql-results+xml",
+ ".sse" => "application/vnd.kodak-descriptor",
+ ".ssf" => "application/vnd.epson.ssf",
+ ".ssml" => "application/ssml+xml",
+ ".stf" => "application/vnd.wt.stf",
+ ".stk" => "application/hyperstudio",
+ ".str" => "application/vnd.pg.format",
+ ".sus" => "application/vnd.sus-calendar",
+ ".sv4cpio" => "application/x-sv4cpio",
+ ".sv4crc" => "application/x-sv4crc",
+ ".svd" => "application/vnd.svd",
+ ".svg" => "image/svg+xml",
+ ".svgz" => "image/svg+xml",
+ ".swf" => "application/x-shockwave-flash",
+ ".swi" => "application/vnd.arastra.swi",
+ ".t" => "text/troff",
+ ".tao" => "application/vnd.tao.intent-module-archive",
+ ".tar" => "application/x-tar",
+ ".tbz" => "application/x-bzip-compressed-tar",
+ ".tcap" => "application/vnd.3gpp2.tcap",
+ ".tcl" => "application/x-tcl",
+ ".tex" => "application/x-tex",
+ ".texi" => "application/x-texinfo",
+ ".texinfo" => "application/x-texinfo",
+ ".text" => "text/plain",
+ ".tif" => "image/tiff",
+ ".tiff" => "image/tiff",
+ ".tmo" => "application/vnd.tmobile-livetv",
+ ".torrent" => "application/x-bittorrent",
+ ".tpl" => "application/vnd.groove-tool-template",
+ ".tpt" => "application/vnd.trid.tpt",
+ ".tr" => "text/troff",
+ ".tra" => "application/vnd.trueapp",
+ ".trm" => "application/x-msterminal",
+ ".tsv" => "text/tab-separated-values",
+ ".ttf" => "application/octet-stream",
+ ".twd" => "application/vnd.simtech-mindmapper",
+ ".txd" => "application/vnd.genomatix.tuxedo",
+ ".txf" => "application/vnd.mobius.txf",
+ ".txt" => "text/plain",
+ ".ufd" => "application/vnd.ufdl",
+ ".umj" => "application/vnd.umajin",
+ ".unityweb" => "application/vnd.unity",
+ ".uoml" => "application/vnd.uoml+xml",
+ ".uri" => "text/uri-list",
+ ".ustar" => "application/x-ustar",
+ ".utz" => "application/vnd.uiq.theme",
+ ".uu" => "text/x-uuencode",
+ ".vcd" => "application/x-cdlink",
+ ".vcf" => "text/x-vcard",
+ ".vcg" => "application/vnd.groove-vcard",
+ ".vcs" => "text/x-vcalendar",
+ ".vcx" => "application/vnd.vcx",
+ ".vis" => "application/vnd.visionary",
+ ".viv" => "video/vnd.vivo",
+ ".vrml" => "model/vrml",
+ ".vsd" => "application/vnd.visio",
+ ".vsf" => "application/vnd.vsf",
+ ".vtu" => "model/vnd.vtu",
+ ".vxml" => "application/voicexml+xml",
+ ".war" => "application/java-archive",
+ ".wav" => "audio/x-wav",
+ ".wax" => "audio/x-ms-wax",
+ ".wbmp" => "image/vnd.wap.wbmp",
+ ".wbs" => "application/vnd.criticaltools.wbs+xml",
+ ".wbxml" => "application/vnd.wap.wbxml",
+ ".webm" => "video/webm",
+ ".wm" => "video/x-ms-wm",
+ ".wma" => "audio/x-ms-wma",
+ ".wmd" => "application/x-ms-wmd",
+ ".wmf" => "application/x-msmetafile",
+ ".wml" => "text/vnd.wap.wml",
+ ".wmlc" => "application/vnd.wap.wmlc",
+ ".wmls" => "text/vnd.wap.wmlscript",
+ ".wmlsc" => "application/vnd.wap.wmlscriptc",
+ ".wmv" => "video/x-ms-wmv",
+ ".wmx" => "video/x-ms-wmx",
+ ".wmz" => "application/x-ms-wmz",
+ ".woff" => "application/octet-stream",
+ ".wpd" => "application/vnd.wordperfect",
+ ".wpl" => "application/vnd.ms-wpl",
+ ".wps" => "application/vnd.ms-works",
+ ".wqd" => "application/vnd.wqd",
+ ".wri" => "application/x-mswrite",
+ ".wrl" => "model/vrml",
+ ".wsdl" => "application/wsdl+xml",
+ ".wspolicy" => "application/wspolicy+xml",
+ ".wtb" => "application/vnd.webturbo",
+ ".wvx" => "video/x-ms-wvx",
+ ".x3d" => "application/vnd.hzn-3d-crossword",
+ ".xar" => "application/vnd.xara",
+ ".xbd" => "application/vnd.fujixerox.docuworks.binder",
+ ".xbm" => "image/x-xbitmap",
+ ".xdm" => "application/vnd.syncml.dm+xml",
+ ".xdp" => "application/vnd.adobe.xdp+xml",
+ ".xdw" => "application/vnd.fujixerox.docuworks",
+ ".xenc" => "application/xenc+xml",
+ ".xer" => "application/patch-ops-error+xml",
+ ".xfdf" => "application/vnd.adobe.xfdf",
+ ".xfdl" => "application/vnd.xfdl",
+ ".xhtml" => "application/xhtml+xml",
+ ".xif" => "image/vnd.xiff",
+ ".xls" => "application/vnd.ms-excel",
+ ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ ".xml" => "application/xml",
+ ".xo" => "application/vnd.olpc-sugar",
+ ".xop" => "application/xop+xml",
+ ".xpm" => "image/x-xpixmap",
+ ".xpr" => "application/vnd.is-xpr",
+ ".xps" => "application/vnd.ms-xpsdocument",
+ ".xpw" => "application/vnd.intercon.formnet",
+ ".xsl" => "application/xml",
+ ".xslt" => "application/xslt+xml",
+ ".xsm" => "application/vnd.syncml+xml",
+ ".xspf" => "application/xspf+xml",
+ ".xul" => "application/vnd.mozilla.xul+xml",
+ ".xwd" => "image/x-xwindowdump",
+ ".xyz" => "chemical/x-xyz",
+ ".yaml" => "text/yaml",
+ ".yml" => "text/yaml",
+ ".zaz" => "application/vnd.zzazz.deck+xml",
+ ".zip" => "application/zip",
+ ".zmm" => "application/vnd.handheld-entertainment+xml",
}
end
end
diff --git a/lib/rack/mock.rb b/lib/rack/mock.rb
index 008d9c4..75cf364 100644
--- a/lib/rack/mock.rb
+++ b/lib/rack/mock.rb
@@ -57,6 +57,7 @@ module Rack
def post(uri, opts={}) request("POST", uri, opts) end
def put(uri, opts={}) request("PUT", uri, opts) end
def delete(uri, opts={}) request("DELETE", uri, opts) end
+ def head(uri, opts={}) request("HEAD", uri, opts) end
def request(method="GET", uri="", opts={})
env = self.class.env_for(uri, opts.merge(:method => method))
@@ -182,7 +183,7 @@ module Rack
end
def empty?
- [201, 204, 304].include? status
+ [201, 204, 205, 304].include? status
end
end
end
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index 6eee64e..2b55cf9 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -14,6 +14,9 @@ module Rack
fast_forward_to_first_boundary
+ max_key_space = Utils.key_space_limit
+ bytes = 0
+
loop do
head, filename, content_type, name, body =
get_current_head_and_filename_and_content_type_and_name_and_body
@@ -28,6 +31,13 @@ module Rack
filename, data = get_data(filename, body, content_type, name, head)
+ if name
+ bytes += name.size
+ if bytes > max_key_space
+ raise RangeError, "exceeded available parameter key space"
+ end
+ end
+
Utils.normalize_params(@params, name, data) unless data.nil?
# break if we're at the end of a buffer, but not if it is the end of a field
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index 59987bf..fbbf00b 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -72,6 +72,8 @@ module Rack
'https'
elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
'https'
+ elsif @env['HTTP_X_FORWARDED_SCHEME']
+ @env['HTTP_X_FORWARDED_SCHEME']
elsif @env['HTTP_X_FORWARDED_PROTO']
@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
else
@@ -113,15 +115,32 @@ module Rack
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
def path_info=(s); @env["PATH_INFO"] = s.to_s end
+
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
def delete?; request_method == "DELETE" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type GET
def get?; request_method == "GET" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
def head?; request_method == "HEAD" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
def options?; request_method == "OPTIONS" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
def patch?; request_method == "PATCH" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type POST
def post?; request_method == "POST" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
def put?; request_method == "PUT" end
+
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
def trace?; request_method == "TRACE" end
+
# The set of form-data media-types. Requests that do not indicate
# one of the media types presents in this list will not be eligible
# for form-data / param parsing.
@@ -230,22 +249,23 @@ module Rack
end
def cookies
- return {} unless @env["HTTP_COOKIE"]
-
- if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
- @env["rack.request.cookie_hash"]
- else
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
- # According to RFC 2109:
- # If multiple cookies satisfy the criteria above, they are ordered in
- # the Cookie header such that those with more specific Path attributes
- # precede those with less specific. Ordering with respect to other
- # attributes (e.g., Domain) is unspecified.
- @env["rack.request.cookie_hash"] =
- Hash[*Utils.parse_query(@env["rack.request.cookie_string"], ';,').map {|k,v|
- [k, Array === v ? v.first : v]
- }.flatten]
- end
+ hash = @env["rack.request.cookie_hash"] ||= {}
+ string = @env["HTTP_COOKIE"]
+
+ return hash if string == @env["rack.request.cookie_string"]
+ hash.clear
+
+ # According to RFC 2109:
+ # If multiple cookies satisfy the criteria above, they are ordered in
+ # the Cookie header such that those with more specific Path attributes
+ # precede those with less specific. Ordering with respect to other
+ # attributes (e.g., Domain) is unspecified.
+ Utils.parse_query(string, ';,').each { |k,v| hash[k] = Array === v ? v.first : v }
+ @env["rack.request.cookie_string"] = string
+ hash
+ rescue => error
+ error.message.replace "cannot parse Cookie header: #{error.message}"
+ raise
end
def xhr?
@@ -278,23 +298,35 @@ module Rack
end
def accept_encoding
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
- m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
-
- if m
- [m[1], (m[2] || 1.0).to_f]
- else
- raise "Invalid value for Accept-Encoding: #{part.inspect}"
+ @env["HTTP_ACCEPT_ENCODING"].to_s.split(/\s*,\s*/).map do |part|
+ encoding, parameters = part.split(/\s*;\s*/, 2)
+ quality = 1.0
+ if parameters and /\Aq=([\d.]+)/ =~ parameters
+ quality = $1.to_f
end
+ [encoding, quality]
end
end
+ def trusted_proxy?(ip)
+ ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$/i
+ end
+
def ip
- if addr = @env['HTTP_X_FORWARDED_FOR']
- (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
- else
- @env['REMOTE_ADDR']
+ remote_addrs = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
+ remote_addrs.reject! { |addr| trusted_proxy?(addr) }
+
+ return remote_addrs.first if remote_addrs.any?
+
+ forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
+
+ if client_ip = @env['HTTP_CLIENT_IP']
+ # If forwarded_ips doesn't include the client_ip, it might be an
+ # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
+ return client_ip if forwarded_ips.include?(client_ip)
end
+
+ return forwarded_ips.reject { |ip| trusted_proxy?(ip) }.last || @env["REMOTE_ADDR"]
end
protected
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index bcc31e3..12180d8 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -19,7 +19,7 @@ module Rack
class Response
attr_accessor :length
- def initialize(body=[], status=200, header={}, &block)
+ def initialize(body=[], status=200, header={})
@status = status.to_i
@header = Utils::HeaderHash.new("Content-Type" => "text/html").
merge(header)
@@ -71,7 +71,7 @@ module Rack
def finish(&block)
@block = block
- if [204, 304].include?(status.to_i)
+ if [204, 205, 304].include?(status.to_i)
header.delete "Content-Type"
header.delete "Content-Length"
[status.to_i, header, []]
@@ -112,19 +112,21 @@ module Rack
alias headers header
module Helpers
- def invalid?; @status < 100 || @status >= 600; end
+ def invalid?; status < 100 || status >= 600; end
- def informational?; @status >= 100 && @status < 200; end
- def successful?; @status >= 200 && @status < 300; end
- def redirection?; @status >= 300 && @status < 400; end
- def client_error?; @status >= 400 && @status < 500; end
- def server_error?; @status >= 500 && @status < 600; end
+ def informational?; status >= 100 && status < 200; end
+ def successful?; status >= 200 && status < 300; end
+ def redirection?; status >= 300 && status < 400; end
+ def client_error?; status >= 400 && status < 500; end
+ def server_error?; status >= 500 && status < 600; end
- def ok?; @status == 200; end
- def forbidden?; @status == 403; end
- def not_found?; @status == 404; end
+ def ok?; status == 200; end
+ def bad_request?; status == 400; end
+ def forbidden?; status == 403; end
+ def not_found?; status == 404; end
+ def unprocessable?; status == 422; end
- def redirect?; [301, 302, 303, 307].include? @status; end
+ def redirect?; [301, 302, 303, 307].include? status; end
# Headers
attr_reader :headers, :original_headers
diff --git a/lib/rack/sendfile.rb b/lib/rack/sendfile.rb
index 367d689..c82145a 100644
--- a/lib/rack/sendfile.rb
+++ b/lib/rack/sendfile.rb
@@ -46,10 +46,11 @@ module Rack
# proxy_pass http://127.0.0.1:8080/;
# }
#
- # Note that the X-Sendfile-Type header must be set exactly as shown above. The
- # X-Accel-Mapping header should specify the internal URI path, followed by an
- # equals sign (=), followed name of the location in the file system that it maps
- # to. The middleware performs a simple substitution on the resulting path.
+ # Note that the X-Sendfile-Type header must be set exactly as shown above.
+ # The X-Accel-Mapping header should specify the location on the file system,
+ # followed by an equals sign (=), followed name of the private URL pattern
+ # that it maps to. The middleware performs a simple substitution on the
+ # resulting path.
#
# See Also: http://wiki.codemongers.com/NginxXSendfile
#
@@ -80,7 +81,7 @@ module Rack
#
# X-Sendfile is supported under Apache 2.x using a separate module:
#
- # http://tn123.ath.cx/mod_xsendfile/
+ # https://tn123.org/mod_xsendfile/
#
# Once the module is compiled and installed, you can enable it using
# XSendFile config directive:
@@ -104,10 +105,11 @@ module Rack
when 'X-Accel-Redirect'
path = F.expand_path(body.to_path)
if url = map_accel_path(env, path)
+ headers['Content-Length'] = '0'
headers[type] = url
body = []
else
- env['rack.errors'] << "X-Accel-Mapping header missing"
+ env['rack.errors'].puts "X-Accel-Mapping header missing"
end
when 'X-Sendfile', 'X-Lighttpd-Send-File'
path = F.expand_path(body.to_path)
@@ -116,7 +118,7 @@ module Rack
body = []
when '', nil
else
- env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
+ env['rack.errors'].puts "Unknown x-sendfile variation: '#{variation}'.\n"
end
end
[status, headers, body]
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
index 3c6584d..a8546bf 100644
--- a/lib/rack/server.rb
+++ b/lib/rack/server.rb
@@ -226,7 +226,7 @@ module Rack
self.class.middleware
end
- def start
+ def start &blk
if options[:warn]
$-w = true
end
@@ -262,7 +262,7 @@ module Rack
end
end
- server.run wrapped_app, options
+ server.run wrapped_app, options, &blk
end
def server
diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb
index 16481ab..641e3c0 100644
--- a/lib/rack/session/abstract/id.rb
+++ b/lib/rack/session/abstract/id.rb
@@ -95,8 +95,11 @@ module Rack
end
def inspect
- load_for_read!
- super
+ if loaded?
+ super
+ else
+ "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
+ end
end
def exists?
@@ -108,6 +111,11 @@ module Rack
@loaded
end
+ def empty?
+ load_for_read!
+ super
+ end
+
private
def load_for_read!
@@ -144,7 +152,9 @@ module Rack
# 'rack.session'
# * :path, :domain, :expire_after, :secure, and :httponly set the related
# cookie options as by Rack::Response#add_cookie
- # * :defer will not set a cookie in the response.
+ # * :skip will not a set a cookie in the response nor update the session state
+ # * :defer will not set a cookie in the response but still update the session
+ # state if it is used with a backend
# * :renew (implementation dependent) will prompt the generation of a new
# session id, and migration of data to be referenced at the new id. If
# :defer is set, it will be overridden and the cookie will be set.
@@ -260,21 +270,30 @@ module Rack
end
# Session should be commited if it was loaded, any of specific options like :renew, :drop
- # or :expire_after was given and the security permissions match.
+ # or :expire_after was given and the security permissions match. Skips if skip is given.
def commit_session?(env, session, options)
- (loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
+ if options[:skip]
+ false
+ else
+ has_session = loaded_session?(session) || forced_session_update?(session, options)
+ has_session && security_matches?(env, options)
+ end
end
def loaded_session?(session)
!session.is_a?(SessionHash) || session.loaded?
end
+ def forced_session_update?(session, options)
+ force_options?(options) && session && !session.empty?
+ end
+
def force_options?(options)
options.values_at(:renew, :drop, :defer, :expire_after).any?
end
- def secure_session?(env, options)
+ def security_matches?(env, options)
return true unless options[:secure]
request = Rack::Request.new(env)
request.ssl?
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb
index 0cf9b8d..1d87384 100644
--- a/lib/rack/session/cookie.rb
+++ b/lib/rack/session/cookie.rb
@@ -14,6 +14,7 @@ module Rack
# Both methods must take a string and return a string.
#
# When the secret key is set, cookie data is checked for data integrity.
+ # The old secret key is also accepted and allows graceful secret rotation.
#
# Example:
#
@@ -21,14 +22,15 @@ module Rack
# :domain => 'foo.com',
# :path => '/',
# :expire_after => 2592000,
- # :secret => 'change_me'
+ # :secret => 'change_me',
+ # :old_secret => 'also_change_me'
#
# All parameters are optional.
#
# Example of a cookie with no encoding:
#
# Rack::Session::Cookie.new(application, {
- # :coder => Racke::Session::Cookie::Identity.new
+ # :coder => Rack::Session::Cookie::Identity.new
# })
#
# Example of a cookie with custom encoding:
@@ -80,6 +82,7 @@ module Rack
def initialize(app, options={})
@secret = options[:secret]
+ @old_secret = options[:old_secret]
@coder = options[:coder] ||= Base64::Marshal.new
super(app, options.merge!(:cookie_only => true))
end
@@ -101,9 +104,11 @@ module Rack
request = Rack::Request.new(env)
session_data = request.cookies[@key]
- if @secret && session_data
+ if (@secret || @old_secret) && session_data
session_data, digest = session_data.split("--")
- session_data = nil unless digest == generate_hmac(session_data)
+ if (digest != generate_hmac(session_data, @secret)) && (digest != generate_hmac(session_data, @old_secret))
+ session_data = nil
+ end
end
coder.decode(session_data) || {}
@@ -127,7 +132,7 @@ module Rack
session_data = coder.encode(session)
if @secret
- session_data = "#{session_data}--#{generate_hmac(session_data)}"
+ session_data = "#{session_data}--#{generate_hmac(session_data, @secret)}"
end
if session_data.size > (4096 - @key.size)
@@ -143,8 +148,8 @@ module Rack
generate_sid unless options[:drop]
end
- def generate_hmac(data)
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
+ def generate_hmac(data, secret)
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
end
end
diff --git a/lib/rack/showexceptions.rb b/lib/rack/showexceptions.rb
index 00ddf45..c91ca07 100644
--- a/lib/rack/showexceptions.rb
+++ b/lib/rack/showexceptions.rb
@@ -277,7 +277,7 @@ TEMPLATE = <<'HTML'
<h2>Request information</h2>
<h3 id="get-info">GET</h3>
- <% unless req.GET.empty? %>
+ <% if req.GET and not req.GET.empty? %>
<table class="req">
<thead>
<tr>
@@ -299,7 +299,7 @@ TEMPLATE = <<'HTML'
<% end %>
<h3 id="post-info">POST</h3>
- <% unless req.POST.empty? %>
+ <% if req.POST and not req.POST.empty? %>
<table class="req">
<thead>
<tr>
diff --git a/lib/rack/static.rb b/lib/rack/static.rb
index f3cac7b..1b843a0 100644
--- a/lib/rack/static.rb
+++ b/lib/rack/static.rb
@@ -22,6 +22,12 @@ module Rack
#
# use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
#
+ # Serve all requests normally from the folder "public" in the current
+ # directory but uses index.html as default route for "/"
+ #
+ # use Rack::Static, :urls => [""], :root => 'public', :index =>
+ # 'public/index.html'
+ #
# Set a fixed Cache-Control header for all served files:
#
# use Rack::Static, :root => 'public', :cache_control => 'public'
@@ -32,22 +38,29 @@ module Rack
def initialize(app, options={})
@app = app
@urls = options[:urls] || ["/favicon.ico"]
+ @index = options[:index] || "index.html"
root = options[:root] || Dir.pwd
cache_control = options[:cache_control]
@file_server = Rack::File.new(root, cache_control)
end
+ def overwrite_file_path(path)
+ @urls.kind_of?(Hash) && @urls.key?(path) || @index && path == '/'
+ end
+
+ def route_file(path)
+ @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
+ end
+
+ def can_serve(path)
+ route_file(path) || overwrite_file_path(path)
+ end
+
def call(env)
path = env["PATH_INFO"]
- unless @urls.kind_of? Hash
- can_serve = @urls.any? { |url| path.index(url) == 0 }
- else
- can_serve = @urls.key? path
- end
-
- if can_serve
- env["PATH_INFO"] = @urls[path] if @urls.kind_of? Hash
+ if can_serve(path)
+ env["PATH_INFO"] = (path == '/' ? @index : @urls[path]) if overwrite_file_path(path)
@file_server.call(env)
else
@app.call(env)
diff --git a/lib/rack/urlmap.rb b/lib/rack/urlmap.rb
index 4d6b8c6..d3b95a5 100644
--- a/lib/rack/urlmap.rb
+++ b/lib/rack/urlmap.rb
@@ -19,9 +19,6 @@ module Rack
end
def remap(map)
- longest_path_first = lambda do |(host, location, _, _)|
- [host ? -host.size : NEGATIVE_INFINITY, -location.size]
- end
@mapping = map.map { |location, app|
if location =~ %r{\Ahttps?://(.*?)(/.*)}
host, location = $1, $2
@@ -32,28 +29,46 @@ module Rack
unless location[0] == ?/
raise ArgumentError, "paths need to start with /"
end
+
location = location.chomp('/')
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
[host, location, match, app]
- }.sort_by(&longest_path_first)
+ }.sort_by do |(host, location, _, _)|
+ [host ? -host.size : NEGATIVE_INFINITY, -location.size]
+ end
end
def call(env)
path = env["PATH_INFO"]
script_name = env['SCRIPT_NAME']
- hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
- @mapping.each { |host, location, match, app|
- next unless (hHost == host || sName == host \
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
- next unless path.to_s =~ match && rest = $1
- next unless rest.empty? || rest[0] == ?/
- env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
+ hHost = env['HTTP_HOST']
+ sName = env['SERVER_NAME']
+ sPort = env['SERVER_PORT']
+
+ @mapping.each do |host, location, match, app|
+ unless hHost == host \
+ || sName == host \
+ || (!host && (hHost == sName || hHost == sName+':'+sPort))
+ next
+ end
+
+ next unless m = match.match(path.to_s)
+
+ rest = m[1]
+ next unless !rest || rest.empty? || rest[0] == ?/
+
+ env['SCRIPT_NAME'] = (script_name + location)
+ env['PATH_INFO'] = rest
+
return app.call(env)
- }
+ end
+
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
+
ensure
- env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
+ env['PATH_INFO'] = path
+ env['SCRIPT_NAME'] = script_name
end
end
end
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 9e726c8..7bceb45 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -6,9 +6,10 @@ require 'rack/multipart'
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
-if (major == 1 && minor < 9) || (major == 1 && minor == 9 && patch < 2)
- # pull in backports
- require 'rack/backports/uri/common'
+if major == 1 && minor < 9
+ require 'rack/backports/uri/common_18'
+elsif major == 1 && minor == 9 && patch < 3
+ require 'rack/backports/uri/common_192'
else
require 'uri/common'
end
@@ -18,7 +19,7 @@ module Rack
# applications adopted from all kinds of Ruby libraries.
module Utils
- # URI escapes a string. (CGI style space to +)
+ # URI escapes. (CGI style space to +)
def escape(s)
URI.encode_www_form_component(s)
end
@@ -31,14 +32,29 @@ module Rack
end
module_function :escape_path
- # Unescapes a URI escaped string.
- def unescape(s)
- URI.decode_www_form_component(s)
+ # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
+ # target encoding of the string returned, and it defaults to UTF-8
+ if defined?(::Encoding)
+ def unescape(s, encoding = Encoding::UTF_8)
+ URI.decode_www_form_component(s, encoding)
+ end
+ else
+ def unescape(s, encoding = nil)
+ URI.decode_www_form_component(s, encoding)
+ end
end
module_function :unescape
DEFAULT_SEP = /[&;] */n
+ class << self
+ attr_accessor :key_space_limit
+ end
+
+ # The default number of bytes to allow parameter keys to take up.
+ # This helps prevent a rogue client from flooding a Request.
+ self.key_space_limit = 65536
+
# Stolen from Mongrel, with some small modifications:
# Parses a query string by breaking it up at the '&'
# and ';' characters. You can also use this to parse
@@ -47,8 +63,19 @@ module Rack
def parse_query(qs, d = nil)
params = {}
+ max_key_space = Utils.key_space_limit
+ bytes = 0
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) }
+
+ if k
+ bytes += k.size
+ if bytes > max_key_space
+ raise RangeError, "exceeded available parameter key space"
+ end
+ end
+
if cur = params[k]
if cur.class == Array
params[k] << v
@@ -67,8 +94,19 @@ module Rack
def parse_nested_query(qs, d = nil)
params = {}
+ max_key_space = Utils.key_space_limit
+ bytes = 0
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |s| unescape(s) }
+
+ if k
+ bytes += k.size
+ if bytes > max_key_space
+ raise RangeError, "exceeded available parameter key space"
+ end
+ end
+
normalize_params(params, k, v)
end
@@ -113,7 +151,7 @@ module Rack
if v.class == Array
build_query(v.map { |x| [k, x] })
else
- "#{escape(k)}=#{escape(v)}"
+ v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
end
}.join("&")
end
@@ -146,7 +184,13 @@ module Rack
'"' => """,
"/" => "/"
}
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
+ if //.respond_to?(:encoding)
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
+ else
+ # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
+ # TODO doesn't apply to jruby, so a better condition above might be preferable?
+ ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
+ end
# Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string)
@@ -228,6 +272,8 @@ module Rack
cookies.reject! { |cookie|
if value[:domain]
cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
+ elsif value[:path]
+ cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
else
cookie =~ /\A#{escape(key)}=/
end
@@ -443,6 +489,7 @@ module Rack
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
+ 418 => "I'm a Teapot",
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
@@ -459,7 +506,7 @@ module Rack
}
# Responses with HTTP status codes that should not have an entity body
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
+ STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
diff --git a/metadata.yml b/metadata.yml
index 2261efc..3234b9b 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,13 +1,13 @@
--- !ruby/object:Gem::Specification
name: rack
version: !ruby/object:Gem::Version
- hash: 25
+ hash: 7
prerelease:
segments:
- 1
- - 3
- - 1
- version: 1.3.1
+ - 4
+ - 0
+ version: 1.4.0
platform: ruby
authors:
- Christian Neukirchen
@@ -15,7 +15,7 @@ autorequire:
bindir: bin
cert_chain: []
-date: 2011-07-13 00:00:00 Z
+date: 2011-12-28 00:00:00 Z
dependencies:
- !ruby/object:Gem::Dependency
name: bacon
@@ -46,7 +46,7 @@ dependencies:
type: :development
version_requirements: *id002
- !ruby/object:Gem::Dependency
- name: fcgi
+ name: ruby-fcgi
prerelease: false
requirement: &id003 !ruby/object:Gem::Requirement
none: false
@@ -81,10 +81,14 @@ dependencies:
requirements:
- - ">="
- !ruby/object:Gem::Version
- hash: 3
+ hash: 1923831981
segments:
+ - 1
+ - 2
- 0
- version: "0"
+ - pre
+ - 2
+ version: 1.2.0.pre2
type: :development
version_requirements: *id005
- !ruby/object:Gem::Dependency
@@ -102,7 +106,7 @@ dependencies:
type: :development
version_requirements: *id006
description: |
- Rack provides minimal, modular and adaptable interface for developing
+ Rack provides a minimal, modular and adaptable interface for developing
web applications in Ruby. By wrapping HTTP requests and responses in
the simplest way possible, it unifies and distills the API for web
servers, web frameworks, and software in between (the so-called
@@ -131,7 +135,8 @@ files:
- lib/rack/auth/digest/nonce.rb
- lib/rack/auth/digest/params.rb
- lib/rack/auth/digest/request.rb
-- lib/rack/backports/uri/common.rb
+- lib/rack/backports/uri/common_18.rb
+- lib/rack/backports/uri/common_192.rb
- lib/rack/body_proxy.rb
- lib/rack/builder.rb
- lib/rack/cascade.rb
@@ -194,6 +199,7 @@ files:
- test/cgi/rackup_stub.rb
- test/cgi/sample_rackup.ru
- test/cgi/test
+- test/cgi/test+directory/test+file
- test/cgi/test.fcgi
- test/cgi/test.ru
- test/gemloader.rb
@@ -263,6 +269,7 @@ files:
- test/spec_urlmap.rb
- test/spec_utils.rb
- test/spec_webrick.rb
+- test/static/index.html
- test/testrequest.rb
- test/unregistered_handler/rack/handler/unregistered.rb
- test/unregistered_handler/rack/handler/unregistered_long_one.rb
@@ -301,7 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
requirements: []
rubyforge_project: rack
-rubygems_version: 1.8.5
+rubygems_version: 1.8.12
signing_key:
specification_version: 3
summary: a modular Ruby webserver interface
diff --git a/rack.gemspec b/rack.gemspec
index 971a99c..0d6fa07 100644
--- a/rack.gemspec
+++ b/rack.gemspec
@@ -1,11 +1,11 @@
Gem::Specification.new do |s|
s.name = "rack"
- s.version = "1.3.1"
+ s.version = "1.4.0"
s.platform = Gem::Platform::RUBY
s.summary = "a modular Ruby webserver interface"
s.description = <<-EOF
-Rack provides minimal, modular and adaptable interface for developing
+Rack provides a minimal, modular and adaptable interface for developing
web applications in Ruby. By wrapping HTTP requests and responses in
the simplest way possible, it unifies and distills the API for web
servers, web frameworks, and software in between (the so-called
@@ -30,8 +30,8 @@ EOF
s.add_development_dependency 'bacon'
s.add_development_dependency 'rake'
- s.add_development_dependency 'fcgi'
+ s.add_development_dependency 'ruby-fcgi'
s.add_development_dependency 'memcache-client'
- s.add_development_dependency 'mongrel'
+ s.add_development_dependency 'mongrel', '>= 1.2.0.pre2'
s.add_development_dependency 'thin'
end
diff --git a/test/builder/end.ru b/test/builder/end.ru
index 8eea56e..7f36d8c 100644
--- a/test/builder/end.ru
+++ b/test/builder/end.ru
@@ -1,3 +1,5 @@
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
__END__
Should not be evaluated
+Neither should
+This
diff --git a/test/cgi/lighttpd.conf b/test/cgi/lighttpd.conf
index 721b76a..c195f78 100755
--- a/test/cgi/lighttpd.conf
+++ b/test/cgi/lighttpd.conf
@@ -2,6 +2,7 @@ server.modules = ("mod_fastcgi", "mod_cgi")
server.document-root = "."
server.errorlog = var.CWD + "/lighttpd.errors"
server.port = 9203
+server.bind = "127.0.0.1"
server.event-handler = "select"
diff --git a/test/cgi/sample_rackup.ru b/test/cgi/sample_rackup.ru
index 86d99e6..a73df81 100755
--- a/test/cgi/sample_rackup.ru
+++ b/test/cgi/sample_rackup.ru
@@ -2,4 +2,4 @@
require '../testrequest'
-run TestRequest.new
+run Rack::Lint.new(TestRequest.new)
diff --git a/test/cgi/test+directory/test+file b/test/cgi/test+directory/test+file
new file mode 100644
index 0000000..f4273fb
--- /dev/null
+++ b/test/cgi/test+directory/test+file
@@ -0,0 +1 @@
+this file has plusses!
diff --git a/test/cgi/test.ru b/test/cgi/test.ru
index bd3ee72..7913ef7 100755
--- a/test/cgi/test.ru
+++ b/test/cgi/test.ru
@@ -2,4 +2,4 @@
# -*- ruby -*-
require '../testrequest'
-run TestRequest.new
+run Rack::Lint.new(TestRequest.new)
diff --git a/test/gemloader.rb b/test/gemloader.rb
index 382e8b8..22be697 100644
--- a/test/gemloader.rb
+++ b/test/gemloader.rb
@@ -2,5 +2,9 @@ require 'rubygems'
project = 'rack'
gemspec = File.expand_path("#{project}.gemspec", Dir.pwd)
Gem::Specification.load(gemspec).dependencies.each do |dep|
- gem dep.name, *dep.requirement.as_list
-end
\ No newline at end of file
+ begin
+ gem dep.name, *dep.requirement.as_list
+ rescue Gem::LoadError
+ warn "Cannot load #{dep.name} #{dep.requirement.to_s}"
+ end
+end
diff --git a/test/spec_auth_basic.rb b/test/spec_auth_basic.rb
index 5ef2643..145f6b9 100644
--- a/test/spec_auth_basic.rb
+++ b/test/spec_auth_basic.rb
@@ -1,4 +1,5 @@
require 'rack/auth/basic'
+require 'rack/lint'
require 'rack/mock'
describe Rack::Auth::Basic do
@@ -7,7 +8,9 @@ describe Rack::Auth::Basic do
end
def unprotected_app
- lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
+ Rack::Lint.new lambda { |env|
+ [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
+ }
end
def protected_app
diff --git a/test/spec_auth_digest.rb b/test/spec_auth_digest.rb
index c85d717..1828923 100644
--- a/test/spec_auth_digest.rb
+++ b/test/spec_auth_digest.rb
@@ -1,4 +1,5 @@
require 'rack/auth/digest/md5'
+require 'rack/lint'
require 'rack/mock'
describe Rack::Auth::Digest::MD5 do
@@ -7,10 +8,10 @@ describe Rack::Auth::Digest::MD5 do
end
def unprotected_app
- lambda do |env|
+ Rack::Lint.new lambda { |env|
friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"]
[ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
- end
+ }
end
def protected_app
diff --git a/test/spec_body_proxy.rb b/test/spec_body_proxy.rb
index 5c5a080..64bd65f 100644
--- a/test/spec_body_proxy.rb
+++ b/test/spec_body_proxy.rb
@@ -35,9 +35,14 @@ describe Rack::BodyProxy do
should 'not close more than one time' do
count = 0
proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 }
+ 2.times { proxy.close }
+ count.should.equal 1
+ end
+
+ should 'be closed when the callback is triggered' do
+ closed = false
+ proxy = Rack::BodyProxy.new([]) { closed = proxy.closed? }
proxy.close
- lambda {
- proxy.close
- }.should.raise(IOError)
+ closed.should.equal true
end
end
diff --git a/test/spec_builder.rb b/test/spec_builder.rb
index f7501ae..eb497df 100644
--- a/test/spec_builder.rb
+++ b/test/spec_builder.rb
@@ -1,4 +1,5 @@
require 'rack/builder'
+require 'rack/lint'
require 'rack/mock'
require 'rack/showexceptions'
require 'rack/urlmap'
@@ -18,38 +19,46 @@ class NothingMiddleware
end
describe Rack::Builder do
+ def builder(&block)
+ Rack::Lint.new Rack::Builder.new(&block)
+ end
+
+ def builder_to_app(&block)
+ Rack::Lint.new Rack::Builder.new(&block).to_app
+ end
+
it "supports mapping" do
- app = Rack::Builder.new do
+ app = builder_to_app do
map '/' do |outer_env|
- run lambda { |inner_env| [200, {}, ['root']] }
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
end
map '/sub' do
- run lambda { |inner_env| [200, {}, ['sub']] }
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
end
- end.to_app
+ end
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
end
it "doesn't dupe env even when mapping" do
- app = Rack::Builder.new do
+ app = builder_to_app do
use NothingMiddleware
map '/' do |outer_env|
run lambda { |inner_env|
inner_env['new_key'] = 'new_value'
- [200, {}, ['root']]
+ [200, {"Content-Type" => "text/plain"}, ['root']]
}
end
- end.to_app
+ end
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
NothingMiddleware.env['new_key'].should.equal 'new_value'
end
it "chains apps by default" do
- app = Rack::Builder.new do
+ app = builder_to_app do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
- end.to_app
+ end
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
@@ -57,7 +66,7 @@ describe Rack::Builder do
end
it "has implicit #to_app" do
- app = Rack::Builder.new do
+ app = builder do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end
@@ -68,13 +77,13 @@ describe Rack::Builder do
end
it "supports blocks on use" do
- app = Rack::Builder.new do
+ app = builder do
use Rack::ShowExceptions
use Rack::Auth::Basic do |username, password|
'secret' == password
end
- run lambda { |env| [200, {}, ['Hi Boss']] }
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
end
response = Rack::MockRequest.new(app).get("/")
@@ -89,7 +98,7 @@ describe Rack::Builder do
end
it "has explicit #to_app" do
- app = Rack::Builder.app do
+ app = builder do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end
@@ -99,8 +108,30 @@ describe Rack::Builder do
Rack::MockRequest.new(app).get("/").should.be.server_error
end
+ it "can mix map and run for endpoints" do
+ app = builder do
+ map '/sub' do
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
+ end
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
+ end
+
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
+ end
+
+ it "accepts middleware-only map blocks" do
+ app = builder do
+ map('/foo') { use Rack::ShowExceptions }
+ run lambda { |env| raise "bzzzt" }
+ end
+
+ proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
+ Rack::MockRequest.new(app).get("/foo").should.be.server_error
+ end
+
should "initialize apps once" do
- app = Rack::Builder.new do
+ app = builder do
class AppClass
def initialize
@called = 0
@@ -120,6 +151,23 @@ describe Rack::Builder do
Rack::MockRequest.new(app).get("/").should.be.server_error
end
+ it "allows use after run" do
+ app = builder do
+ run lambda { |env| raise "bzzzt" }
+ use Rack::ShowExceptions
+ end
+
+ Rack::MockRequest.new(app).get("/").should.be.server_error
+ Rack::MockRequest.new(app).get("/").should.be.server_error
+ Rack::MockRequest.new(app).get("/").should.be.server_error
+ end
+
+ it 'complains about a missing run' do
+ proc do
+ Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
+ end.should.raise(RuntimeError)
+ end
+
describe "parse_file" do
def config_file(name)
File.join(File.dirname(__FILE__), 'builder', name)
@@ -133,6 +181,7 @@ describe Rack::Builder do
it "removes __END__ before evaluating app" do
app, options = Rack::Builder.parse_file config_file('end.ru')
+ options = nil # ignored, prevents warning
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
end
diff --git a/test/spec_cascade.rb b/test/spec_cascade.rb
index c2c4920..cfd1164 100644
--- a/test/spec_cascade.rb
+++ b/test/spec_cascade.rb
@@ -1,9 +1,14 @@
require 'rack/cascade'
require 'rack/file'
+require 'rack/lint'
require 'rack/urlmap'
require 'rack/mock'
describe Rack::Cascade do
+ def cascade(*args)
+ Rack::Lint.new Rack::Cascade.new(*args)
+ end
+
docroot = File.expand_path(File.dirname(__FILE__))
app1 = Rack::File.new(docroot)
@@ -13,20 +18,20 @@ describe Rack::Cascade do
[200, { "Content-Type" => "text/plain"}, [""]]})
should "dispatch onward on 404 by default" do
- cascade = Rack::Cascade.new([app1, app2, app3])
+ cascade = cascade([app1, app2, app3])
Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
Rack::MockRequest.new(cascade).get("/foo").should.be.ok
Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
- Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
+ Rack::MockRequest.new(cascade).get("/cgi/../..").should.be.forbidden
end
should "dispatch onward on whatever is passed" do
- cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
+ cascade = cascade([app1, app2, app3], [404, 403])
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
end
should "return 404 if empty" do
- Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
+ Rack::MockRequest.new(cascade([])).get('/').should.be.not_found
end
should "append new app" do
@@ -37,7 +42,7 @@ describe Rack::Cascade do
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
cascade << app1
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
- Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
+ Rack::MockRequest.new(cascade).get('/cgi/../..').should.be.forbidden
Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
cascade << app3
Rack::MockRequest.new(cascade).get('/foo').should.be.ok
diff --git a/test/spec_cgi.rb b/test/spec_cgi.rb
index 345a49a..ebfee02 100644
--- a/test/spec_cgi.rb
+++ b/test/spec_cgi.rb
@@ -5,7 +5,7 @@ require 'rack/handler/cgi'
describe Rack::Handler::CGI do
extend TestRequest::Helpers
- @host = '0.0.0.0'
+ @host = '127.0.0.1'
@port = 9203
if `which lighttpd` && !$?.success?
diff --git a/test/spec_chunked.rb b/test/spec_chunked.rb
index 04c22fd..1aa5130 100644
--- a/test/spec_chunked.rb
+++ b/test/spec_chunked.rb
@@ -1,31 +1,56 @@
require 'rack/chunked'
+require 'rack/lint'
require 'rack/mock'
describe Rack::Chunked do
+ Enumerator = ::Enumerable::Enumerator unless defined?(Enumerator)
+
+ def chunked(app)
+ proc do |env|
+ app = Rack::Chunked.new(app)
+ Rack::Lint.new(app).call(env).tap do |response|
+ # we want to use body like an array, but it only has #each
+ response[2] = Enumerator.new(response[2]).to_a
+ end
+ end
+ end
+
before do
@env = Rack::MockRequest.
env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
end
should 'chunk responses with no Content-Length' do
- app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
- response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
response.headers.should.not.include 'Content-Length'
response.headers['Transfer-Encoding'].should.equal 'chunked'
response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
end
should 'chunks empty bodies properly' do
- app = lambda { |env| [200, {}, []] }
- response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] }
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
response.headers.should.not.include 'Content-Length'
response.headers['Transfer-Encoding'].should.equal 'chunked'
response.body.should.equal "0\r\n\r\n"
end
+ should 'chunks encoded bodies properly' do
+ body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] }
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
+ response.headers.should.not.include 'Content-Length'
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
+ response.body.encoding.to_s.should == "ASCII-8BIT"
+ response.body.should.equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n"
+ end if RUBY_VERSION >= "1.9"
+
should 'not modify response when Content-Length header present' do
- app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
- status, headers, body = Rack::Chunked.new(app).call(@env)
+ app = lambda { |env|
+ [200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']]
+ }
+ status, headers, body = chunked(app).call(@env)
status.should.equal 200
headers.should.not.include 'Transfer-Encoding'
headers.should.include 'Content-Length'
@@ -33,26 +58,28 @@ describe Rack::Chunked do
end
should 'not modify response when client is HTTP/1.0' do
- app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
@env['HTTP_VERSION'] = 'HTTP/1.0'
- status, headers, body = Rack::Chunked.new(app).call(@env)
+ status, headers, body = chunked(app).call(@env)
status.should.equal 200
headers.should.not.include 'Transfer-Encoding'
body.join.should.equal 'Hello World!'
end
should 'not modify response when Transfer-Encoding header already present' do
- app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
- status, headers, body = Rack::Chunked.new(app).call(@env)
+ app = lambda { |env|
+ [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
+ }
+ status, headers, body = chunked(app).call(@env)
status.should.equal 200
headers['Transfer-Encoding'].should.equal 'identity'
body.join.should.equal 'Hello World!'
end
- [100, 204, 304].each do |status_code|
+ [100, 204, 205, 304].each do |status_code|
should "not modify response when status code is #{status_code}" do
app = lambda { |env| [status_code, {}, []] }
- status, headers, _ = Rack::Chunked.new(app).call(@env)
+ status, headers, _ = chunked(app).call(@env)
status.should.equal status_code
headers.should.not.include 'Transfer-Encoding'
end
diff --git a/test/spec_commonlogger.rb b/test/spec_commonlogger.rb
index 9172750..d88e19c 100644
--- a/test/spec_commonlogger.rb
+++ b/test/spec_commonlogger.rb
@@ -1,19 +1,20 @@
require 'rack/commonlogger'
+require 'rack/lint'
require 'rack/mock'
describe Rack::CommonLogger do
obj = 'foobar'
length = obj.size
- app = lambda { |env|
+ app = Rack::Lint.new lambda { |env|
[200,
{"Content-Type" => "text/html", "Content-Length" => length.to_s},
[obj]]}
- app_without_length = lambda { |env|
+ app_without_length = Rack::Lint.new lambda { |env|
[200,
{"Content-Type" => "text/html"},
[]]}
- app_with_zero_length = lambda { |env|
+ app_with_zero_length = Rack::Lint.new lambda { |env|
[200,
{"Content-Type" => "text/html", "Content-Length" => "0"},
[]]}
diff --git a/test/spec_conditionalget.rb b/test/spec_conditionalget.rb
index f9e9cec..8b365eb 100644
--- a/test/spec_conditionalget.rb
+++ b/test/spec_conditionalget.rb
@@ -3,9 +3,13 @@ require 'rack/conditionalget'
require 'rack/mock'
describe Rack::ConditionalGet do
+ def conditional_get(app)
+ Rack::Lint.new Rack::ConditionalGet.new(app)
+ end
+
should "set a 304 status and truncate body when If-Modified-Since hits" do
timestamp = Time.now.httpdate
- app = Rack::ConditionalGet.new(lambda { |env|
+ app = conditional_get(lambda { |env|
[200, {'Last-Modified'=>timestamp}, ['TEST']] })
response = Rack::MockRequest.new(app).
@@ -16,7 +20,7 @@ describe Rack::ConditionalGet do
end
should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
- app = Rack::ConditionalGet.new(lambda { |env|
+ app = conditional_get(lambda { |env|
[200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
response = Rack::MockRequest.new(app).
@@ -27,7 +31,7 @@ describe Rack::ConditionalGet do
end
should "set a 304 status and truncate body when If-None-Match hits" do
- app = Rack::ConditionalGet.new(lambda { |env|
+ app = conditional_get(lambda { |env|
[200, {'Etag'=>'1234'}, ['TEST']] })
response = Rack::MockRequest.new(app).
@@ -39,8 +43,8 @@ describe Rack::ConditionalGet do
should "not set a 304 status if If-Modified-Since hits but Etag does not" do
timestamp = Time.now.httpdate
- app = Rack::ConditionalGet.new(lambda { |env|
- [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
+ app = conditional_get(lambda { |env|
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
@@ -51,7 +55,7 @@ describe Rack::ConditionalGet do
should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
timestamp = Time.now.httpdate
- app = Rack::ConditionalGet.new(lambda { |env|
+ app = conditional_get(lambda { |env|
[200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
response = Rack::MockRequest.new(app).
@@ -62,8 +66,8 @@ describe Rack::ConditionalGet do
end
should "not affect non-GET/HEAD requests" do
- app = Rack::ConditionalGet.new(lambda { |env|
- [200, {'Etag'=>'1234'}, ['TEST']] })
+ app = conditional_get(lambda { |env|
+ [200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
response = Rack::MockRequest.new(app).
post("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -73,8 +77,8 @@ describe Rack::ConditionalGet do
end
should "not affect non-200 requests" do
- app = Rack::ConditionalGet.new(lambda { |env|
- [302, {'Etag'=>'1234'}, ['TEST']] })
+ app = conditional_get(lambda { |env|
+ [302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -83,4 +87,16 @@ describe Rack::ConditionalGet do
response.body.should.equal 'TEST'
end
+ should "not affect requests with malformed HTTP_IF_NONE_MATCH" do
+ bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
+ app = conditional_get(lambda { |env|
+ [200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] })
+
+ response = Rack::MockRequest.new(app).
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp)
+
+ response.status.should.equal 200
+ response.body.should.equal 'TEST'
+ end
+
end
diff --git a/test/spec_content_length.rb b/test/spec_content_length.rb
index 5e480ae..db7944f 100644
--- a/test/spec_content_length.rb
+++ b/test/spec_content_length.rb
@@ -52,7 +52,7 @@ describe Rack::ContentLength do
end.new(%w[one two three])
app = lambda { |env| [200, {}, body] }
- response = Rack::ContentLength.new(app).call({})
+ Rack::ContentLength.new(app).call({})
body.closed.should.equal true
end
diff --git a/test/spec_content_type.rb b/test/spec_content_type.rb
index 44b0ed1..2ba1927 100644
--- a/test/spec_content_type.rb
+++ b/test/spec_content_type.rb
@@ -26,4 +26,10 @@ describe Rack::ContentType do
headers.to_a.select { |k,v| k.downcase == "content-type" }.
should.equal [["CONTENT-Type","foo/bar"]]
end
+
+ should "not set Content-Type on 304 responses" do
+ app = lambda { |env| [304, {}, []] }
+ response = Rack::ContentType.new(app, "text/html").call({})
+ response[1]['Content-Type'].should.equal nil
+ end
end
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb
index 0c9d060..5747560 100644
--- a/test/spec_deflater.rb
+++ b/test/spec_deflater.rb
@@ -51,7 +51,7 @@ describe Rack::Deflater do
response[2].each { |part| buf << inflater.inflate(part) }
buf << inflater.finish
buf.delete_if { |part| part.empty? }
- buf.should.equal(%w(foo bar))
+ buf.join.should.equal("foobar")
end
# TODO: This is really just a special case of the above...
@@ -104,7 +104,7 @@ describe Rack::Deflater do
response[2].each { |part| buf << inflater.inflate(part) }
buf << inflater.finish
buf.delete_if { |part| part.empty? }
- buf.should.equal(%w(foo bar))
+ buf.join.should.equal("foobar")
end
should "be able to fallback to no deflation" do
diff --git a/test/spec_directory.rb b/test/spec_directory.rb
index a45ba23..81b8c55 100644
--- a/test/spec_directory.rb
+++ b/test/spec_directory.rb
@@ -54,4 +54,16 @@ describe Rack::Directory do
res.should.be.not_found
end
+
+ should "uri escape path parts" do # #265, properly escape file names
+ mr = Rack::MockRequest.new(Rack::Lint.new(app))
+
+ res = mr.get("/cgi/test%2bdirectory")
+
+ res.should.be.ok
+ res.body.should =~ %r[/cgi/test%2Bdirectory/test%2Bfile]
+
+ res = mr.get("/cgi/test%2bdirectory/test%2bfile")
+ res.should.be.ok
+ end
end
diff --git a/test/spec_fastcgi.rb b/test/spec_fastcgi.rb
index 93df4d1..5897f35 100644
--- a/test/spec_fastcgi.rb
+++ b/test/spec_fastcgi.rb
@@ -5,7 +5,7 @@ require 'rack/handler/fastcgi'
describe Rack::Handler::FastCGI do
extend TestRequest::Helpers
- @host = '0.0.0.0'
+ @host = '127.0.0.1'
@port = 9203
if `which lighttpd` && !$?.success?
diff --git a/test/spec_file.rb b/test/spec_file.rb
index 121e5a2..2a4d214 100644
--- a/test/spec_file.rb
+++ b/test/spec_file.rb
@@ -22,6 +22,23 @@ describe Rack::File do
res["Last-Modified"].should.equal File.mtime(path).httpdate
end
+ should "return 304 if file isn't modified since last serve" do
+ path = File.join(DOCROOT, "/cgi/test")
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
+
+ res.status.should.equal 304
+ res.body.should.be.empty
+ end
+
+ should "return the file if it's modified since last serve" do
+ path = File.join(DOCROOT, "/cgi/test")
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
+
+ res.should.be.ok
+ end
+
should "serve files with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/%74%65%73%74") # "/cgi/test"
@@ -30,9 +47,23 @@ describe Rack::File do
res.should =~ /ruby/
end
- should "not allow directory traversal" do
+ should "allow safe directory traversal" do
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+
+ res = req.get('/cgi/../cgi/test')
+ res.should.be.successful
+
+ res = req.get('.')
+ res.should.be.not_found
+
+ res = req.get("test/..")
+ res.should.be.not_found
+ end
+
+ should "not allow unsafe directory traversal" do
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
- res = req.get("/cgi/../test")
+
+ res = req.get("/../README")
res.should.be.forbidden
res = req.get("../test")
@@ -40,9 +71,6 @@ describe Rack::File do
res = req.get("..")
res.should.be.forbidden
-
- res = req.get("test/..")
- res.should.be.forbidden
end
should "allow files with .. in their name" do
@@ -57,13 +85,20 @@ describe Rack::File do
res.should.be.not_found
end
- should "not allow directory traversal with encoded periods" do
+ should "not allow unsafe directory traversal with encoded periods" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/%2E%2E/README")
res.should.be.forbidden
end
+ should "allow safe directory traversal with encoded periods" do
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ get("/cgi/%2E%2E/cgi/test")
+
+ res.should.be.successful
+ end
+
should "404 if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/blubb")
@@ -113,10 +148,25 @@ describe Rack::File do
env = Rack::MockRequest.env_for("/cgi/test")
status, heads, _ = Rack::File.new(DOCROOT, 'public, max-age=38').call(env)
- path = File.join(DOCROOT, "/cgi/test")
-
status.should.equal 200
heads['Cache-Control'].should.equal 'public, max-age=38'
end
+ should "only support GET and HEAD requests" do
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+
+ forbidden = %w[post put delete]
+ forbidden.each do |method|
+
+ res = req.send(method, "/cgi/test")
+ res.should.be.forbidden
+ end
+
+ allowed = %w[get head]
+ allowed.each do |method|
+ res = req.send(method, "/cgi/test")
+ res.should.be.successful
+ end
+ end
+
end
diff --git a/test/spec_lint.rb b/test/spec_lint.rb
index eda0dfc..6eda182 100644
--- a/test/spec_lint.rb
+++ b/test/spec_lint.rb
@@ -241,7 +241,7 @@ describe Rack::Lint do
}.should.raise(Rack::Lint::LintError).
message.should.match(/No Content-Type/)
- [100, 101, 204, 304].each do |status|
+ [100, 101, 204, 205, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
@@ -252,7 +252,7 @@ describe Rack::Lint do
end
should "notice content-length errors" do
- [100, 101, 204, 304].each do |status|
+ [100, 101, 204, 205, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-length" => "0"}, []]
diff --git a/test/spec_methodoverride.rb b/test/spec_methodoverride.rb
index 82c6cbd..171025a 100644
--- a/test/spec_methodoverride.rb
+++ b/test/spec_methodoverride.rb
@@ -55,4 +55,19 @@ describe Rack::MethodOverride do
req.env["rack.methodoverride.original_method"].should.equal "POST"
end
+
+ should "not modify REQUEST_METHOD when given invalid multipart form data" do
+ input = <<EOF
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+EOF
+ env = Rack::MockRequest.env_for("/",
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ :method => "POST", :input => input)
+ app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
+ req = app.call(env)
+
+ req.env["REQUEST_METHOD"].should.equal "POST"
+ end
end
diff --git a/test/spec_mock.rb b/test/spec_mock.rb
index c8191a4..cd144c6 100644
--- a/test/spec_mock.rb
+++ b/test/spec_mock.rb
@@ -42,7 +42,7 @@ describe Rack::MockRequest do
env["mock.postdata"].should.be.empty
end
- should "allow GET/POST/PUT/DELETE" do
+ should "allow GET/POST/PUT/DELETE/HEAD" do
res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
@@ -59,6 +59,10 @@ describe Rack::MockRequest do
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "DELETE"
+ res = Rack::MockRequest.new(app).head("", :input => "foo")
+ env = YAML.load(res.body)
+ env["REQUEST_METHOD"].should.equal "HEAD"
+
Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
should.equal "OPTIONS"
end
@@ -175,7 +179,8 @@ describe Rack::MockRequest do
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/foo"
env["CONTENT_TYPE"].should.equal "multipart/form-data; boundary=AaB03x"
- env["mock.postdata"].length.should.equal 206
+ # The gsub accounts for differences in YAMLs affect on the data.
+ env["mock.postdata"].gsub("\r", "").length.should.equal 206
end
should "behave valid according to the Rack spec" do
@@ -188,7 +193,7 @@ describe Rack::MockRequest do
should "call close on the original body object" do
called = false
body = Rack::BodyProxy.new(['hi']) { called = true }
- capp = proc { |e| [200, {'Content-Type' => 'text/plain '}, body] }
+ capp = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] }
called.should.equal false
Rack::MockRequest.new(capp).get('/', :lint => true)
called.should.equal true
diff --git a/test/spec_mongrel.rb b/test/spec_mongrel.rb
index 5361b5b..6160327 100644
--- a/test/spec_mongrel.rb
+++ b/test/spec_mongrel.rb
@@ -11,7 +11,7 @@ $tcp_cork_opts = nil
describe Rack::Handler::Mongrel do
extend TestRequest::Helpers
- @server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
+ @server = Mongrel::HttpServer.new(@host='127.0.0.1', @port=9201)
@server.register('/test',
Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
@server.register('/stream',
@@ -31,7 +31,7 @@ describe Rack::Handler::Mongrel do
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9201"
- response["SERVER_NAME"].should.equal "0.0.0.0"
+ response["SERVER_NAME"].should.equal "127.0.0.1"
end
should "have rack headers" do
@@ -84,7 +84,7 @@ describe Rack::Handler::Mongrel do
should "provide a .run" do
block_ran = false
Thread.new {
- Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
+ Rack::Handler::Mongrel.run(lambda {}, {:Host => '127.0.0.1', :Port => 9211}) { |server|
server.should.be.kind_of Mongrel::HttpServer
block_ran = true
}
@@ -97,7 +97,7 @@ describe Rack::Handler::Mongrel do
block_ran = false
Thread.new {
map = {'/'=>lambda{},'/foo'=>lambda{}}
- Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
+ Rack::Handler::Mongrel.run(map, :map => true, :Host => '127.0.0.1', :Port => 9221) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.equal 2
server.classifier.uris.should.not.include '/arf'
@@ -114,7 +114,7 @@ describe Rack::Handler::Mongrel do
block_ran = false
Thread.new {
map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
- Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
+ Rack::Handler::Mongrel.run(map, {:map => true, :Host => '127.0.0.1', :Port => 9231}) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.equal 2
server.classifier.uris.should.not.include '/arf'
@@ -134,12 +134,12 @@ describe Rack::Handler::Mongrel do
'/' => lambda{},
'/foo' => lambda{},
'/bar' => lambda{},
- 'http://localhost/' => lambda{},
- 'http://localhost/bar' => lambda{},
+ 'http://127.0.0.1/' => lambda{},
+ 'http://127.0.0.1/bar' => lambda{},
'http://falsehost/arf' => lambda{},
'http://falsehost/qux' => lambda{}
})
- opt = {:map => true, :Port => 9241, :Host => 'localhost'}
+ opt = {:map => true, :Port => 9241, :Host => '127.0.0.1'}
Rack::Handler::Mongrel.run(map, opt) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.should.include '/'
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index fcd825c..1dc2f4d 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -30,6 +30,17 @@ describe Rack::Multipart do
params["text"].should.equal "contents"
end
+ should "raise RangeError if the key space is exhausted" do
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
+
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+ begin
+ lambda { Rack::Multipart.parse_multipart(env) }.should.raise(RangeError)
+ ensure
+ Rack::Utils.key_space_limit = old
+ end
+ end
+
should "parse multipart form webkit style" do
env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
@@ -284,4 +295,24 @@ describe Rack::Multipart do
message.should.equal "value must be a Hash"
end
+ it "can parse fields with a content type" do
+ data = <<-EOF
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r
+Content-Disposition: form-data; name="description"\r
+Content-Type: text/plain"\r
+\r
+Very very blue\r
+--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r
+EOF
+ options = {
+ "CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
+ "CONTENT_LENGTH" => data.length.to_s,
+ :input => StringIO.new(data)
+ }
+ env = Rack::MockRequest.env_for("/", options)
+ params = Rack::Utils::Multipart.parse_multipart(env)
+
+ params.should.equal({"description"=>"Very very blue"})
+ end
+
end
diff --git a/test/spec_request.rb b/test/spec_request.rb
index 5c089b5..d20585c 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -125,6 +125,18 @@ describe Rack::Request do
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
+ should "limit the keys from the GET query string" do
+ env = Rack::MockRequest.env_for("/?foo=bar")
+
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+ begin
+ req = Rack::Request.new(env)
+ lambda { req.GET }.should.raise(RangeError)
+ ensure
+ Rack::Utils.key_space_limit = old
+ end
+ end
+
should "not unify GET and POST when calling params" do
mr = Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST',
@@ -157,6 +169,20 @@ describe Rack::Request do
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
+ should "limit the keys from the POST form data" do
+ env = Rack::MockRequest.env_for("",
+ "REQUEST_METHOD" => 'POST',
+ :input => "foo=bar&quux=bla")
+
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
+ begin
+ req = Rack::Request.new(env)
+ lambda { req.POST }.should.raise(RangeError)
+ ensure
+ Rack::Utils.key_space_limit = old
+ end
+ end
+
should "parse POST data with explicit content type regardless of method" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
@@ -333,13 +359,17 @@ describe Rack::Request do
request.scheme.should.equal "https"
request.should.be.ssl?
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https'))
+ request.scheme.should.equal "https"
+ request.should.be.ssl?
+
request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https'))
request.scheme.should.equal "https"
request.should.be.ssl?
request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http'))
request.scheme.should.equal "https"
- request.should.be.ssl
+ request.should.be.ssl?
end
should "parse cookies" do
@@ -351,6 +381,28 @@ describe Rack::Request do
req.cookies.should.equal({})
end
+ should "always return the same hash object" do
+ req = Rack::Request.new \
+ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
+ hash = req.cookies
+ req.env.delete("HTTP_COOKIE")
+ req.cookies.should.equal(hash)
+ req.env["HTTP_COOKIE"] = "zoo=m"
+ req.cookies.should.equal(hash)
+ end
+
+ should "modify the cookies hash in place" do
+ req = Rack::Request.new(Rack::MockRequest.env_for(""))
+ req.cookies.should.equal({})
+ req.cookies['foo'] = 'bar'
+ req.cookies.should.equal 'foo' => 'bar'
+ end
+
+ should "raise any errors on every request" do
+ req = Rack::Request.new Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%")
+ 2.times { proc { req.cookies }.should.raise(ArgumentError) }
+ end
+
should "parse cookies according to RFC 2109" do
req = Rack::Request.new \
Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
@@ -735,38 +787,98 @@ EOF
parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]])
parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ])
- lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
+ parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]])
+ parser.call("gzip ; deflate").should.equal([["gzip", 1.0]])
end
+ ip_app = lambda { |env|
+ request = Rack::Request.new(env)
+ response = Rack::Response.new
+ response.write request.ip
+ response.finish
+ }
+
should 'provide ip information' do
- app = lambda { |env|
- request = Rack::Request.new(env)
- response = Rack::Response.new
- response.write request.ip
- response.finish
- }
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
- mock = Rack::MockRequest.new(Rack::Lint.new(app))
- res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
- res.body.should.equal '123.123.123.123'
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4'
+ res.body.should.equal '1.2.3.4'
- res = mock.get '/',
- 'REMOTE_ADDR' => '123.123.123.123',
- 'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
+ res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329'
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
+
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
+ res.body.should.equal '1.2.3.4'
+ end
+
+ should 'deals with proxies' do
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
- res.body.should.equal '234.234.234.234'
+ res = mock.get '/',
+ 'REMOTE_ADDR' => '1.2.3.4',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ res.body.should.equal '1.2.3.4'
res = mock.get '/',
- 'REMOTE_ADDR' => '123.123.123.123',
- 'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
+ res.body.should.equal '3.4.5.6'
- res.body.should.equal '234.234.234.234'
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
+ res.body.should.equal 'unknown'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1'
+ res.body.should.equal 'unknown'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1'
+ res.body.should.equal 'unknown'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
+ res.body.should.equal '3.4.5.6'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11'
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1'
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11'
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000'
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
res = mock.get '/',
- 'REMOTE_ADDR' => '123.123.123.123',
- 'HTTP_X_FORWARDED_FOR' => 'unknown,234.234.234.234,212.212.212.212'
+ 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1',
+ 'HTTP_CLIENT_IP' => '1.1.1.1'
+ res.body.should.equal '1.1.1.1'
+
+ # Spoofing attempt
+ res = mock.get '/',
+ 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
+ 'HTTP_CLIENT_IP' => '2.2.2.2'
+ res.body.should.equal '1.1.1.1'
+
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
+ res.body.should.equal '9.9.9.9'
- res.body.should.equal '234.234.234.234'
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329'
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
end
class MyRequest < Rack::Request
diff --git a/test/spec_response.rb b/test/spec_response.rb
index c92fa5b..07dd012 100644
--- a/test/spec_response.rb
+++ b/test/spec_response.rb
@@ -109,6 +109,18 @@ describe Rack::Response do
"foo=; domain=sample.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
end
+ it "can delete cookies with the same name with different paths" do
+ response = Rack::Response.new
+ response.set_cookie "foo", {:value => "bar", :path => "/"}
+ response.set_cookie "foo", {:value => "bar", :path => "/path"}
+ response["Set-Cookie"].should.equal ["foo=bar; path=/",
+ "foo=bar; path=/path"].join("\n")
+
+ response.delete_cookie "foo", :path => "/path"
+ response["Set-Cookie"].should.equal ["foo=bar; path=/",
+ "foo=; path=/path; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
+ end
+
it "can do redirects" do
response = Rack::Response.new
response.redirect "/foo"
@@ -196,11 +208,21 @@ describe Rack::Response do
res.should.be.successful
res.should.be.ok
+ res.status = 400
+ res.should.not.be.successful
+ res.should.be.client_error
+ res.should.be.bad_request
+
res.status = 404
res.should.not.be.successful
res.should.be.client_error
res.should.be.not_found
+ res.status = 422
+ res.should.not.be.successful
+ res.should.be.client_error
+ res.should.be.unprocessable
+
res.status = 501
res.should.not.be.successful
res.should.be.server_error
diff --git a/test/spec_sendfile.rb b/test/spec_sendfile.rb
index 84517e7..de4ae7a 100644
--- a/test/spec_sendfile.rb
+++ b/test/spec_sendfile.rb
@@ -40,6 +40,7 @@ describe Rack::Sendfile do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
response.should.be.ok
response.body.should.be.empty
+ response.headers['Content-Length'].should == '0'
response.headers['X-Sendfile'].should.equal '/tmp/hello.txt'
end
end
@@ -48,6 +49,7 @@ describe Rack::Sendfile do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
response.should.be.ok
response.body.should.be.empty
+ response.headers['Content-Length'].should == '0'
response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt'
end
end
@@ -60,6 +62,7 @@ describe Rack::Sendfile do
request headers do |response|
response.should.be.ok
response.body.should.be.empty
+ response.headers['Content-Length'].should == '0'
response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt'
end
end
diff --git a/test/spec_server.rb b/test/spec_server.rb
index 9befacb..a1a51d9 100644
--- a/test/spec_server.rb
+++ b/test/spec_server.rb
@@ -61,8 +61,8 @@ describe Rack::Server do
:daemonize => false,
:server => 'webrick'
)
- t = Thread.new { server.start }
- until t.status == 'sleep'; t.join(0.01) end
+ t = Thread.new { server.start { |s| Thread.current[:server] = s } }
+ t.join(0.01) until t[:server] && t[:server].status != :Stop
body = open("http://127.0.0.1:#{server.options[:Port]}/") { |f| f.read }
body.should.eql('success')
diff --git a/test/spec_session_cookie.rb b/test/spec_session_cookie.rb
index 9fa8b49..514bb3b 100644
--- a/test/spec_session_cookie.rb
+++ b/test/spec_session_cookie.rb
@@ -11,7 +11,7 @@ describe Rack::Session::Cookie do
end
session_id = lambda do |env|
- Rack::Response.new(env["rack.session"].inspect).to_a
+ Rack::Response.new(env["rack.session"].to_hash.inspect).to_a
end
session_option = lambda do |opt|
@@ -99,18 +99,23 @@ describe Rack::Session::Cookie do
end
only_session_id = lambda do |env|
- Rack::Response.new(env["rack.session"]["session_id"]).to_a
+ Rack::Response.new(env["rack.session"]["session_id"].to_s).to_a
end
it "renew session id" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
res = Rack::MockRequest.new(Rack::Session::Cookie.new(only_session_id)).
get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+
+ res.body.should.not.equal ""
old_session_id = res.body
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(renewer)).
get("/", "HTTP_COOKIE" => res["Set-Cookie"])
res = Rack::MockRequest.new(Rack::Session::Cookie.new(only_session_id)).
get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+
+ res.body.should.not.equal ""
res.body.should.not.equal old_session_id
end
@@ -144,6 +149,18 @@ describe Rack::Session::Cookie do
res.body.should.equal '{"counter"=>3}'
end
+ it "loads from a cookie wih accept-only integrity hash for graceful key rotation" do
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
+ cookie = res["Set-Cookie"]
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test2', :old_secret => 'test')).
+ get("/", "HTTP_COOKIE" => cookie)
+ res.body.should.equal '{"counter"=>2}'
+ cookie = res["Set-Cookie"]
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test3', :old_secret => 'test2')).
+ get("/", "HTTP_COOKIE" => cookie)
+ res.body.should.equal '{"counter"=>3}'
+ end
+
it "ignores tampered with session cookies" do
app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
response1 = Rack::MockRequest.new(app).get("/")
@@ -158,6 +175,38 @@ describe Rack::Session::Cookie do
response2.body.should.equal '{"counter"=>1}'
end
+ describe "1.9 bugs relating to inspecting yet-to-be-loaded from cookie data: Rack::Session::Abstract::SessionHash" do
+
+ it "can handle Rack::Lint middleware" do
+ app = Rack::Session::Cookie.new(incrementor)
+ res = Rack::MockRequest.new(app).get("/")
+
+ app = Rack::Session::Cookie.new(Rack::Lint.new(session_id))
+ res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+ res.body.should.not.be.nil
+ end
+
+ it "can handle a middleware that inspects the env" do
+ class TestEnvInspector
+ def initialize(app)
+ @app = app
+ end
+ def call(env)
+ env.inspect
+ @app.call(env)
+ end
+ end
+
+ app = Rack::Session::Cookie.new(incrementor)
+ res = Rack::MockRequest.new(app).get("/")
+
+ app = Rack::Session::Cookie.new(TestEnvInspector.new(session_id))
+ res = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => res["Set-Cookie"])
+ res.body.should.not.be.nil
+ end
+
+ end
+
it "returns the session id in the session hash" do
app = Rack::Session::Cookie.new(incrementor)
res = Rack::MockRequest.new(app).get("/")
@@ -192,10 +241,16 @@ describe Rack::Session::Cookie do
it "returns even if not read/written if :expire_after is set" do
app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
- res = Rack::MockRequest.new(app).get("/")
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
res["Set-Cookie"].should.not.be.nil
end
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
+ app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
+ res = Rack::MockRequest.new(app).get("/")
+ res["Set-Cookie"].should.be.nil
+ end
+
it "exposes :secret in env['rack.session.option']" do
app = Rack::Session::Cookie.new(session_option[:secret], :secret => "foo")
res = Rack::MockRequest.new(app).get("/")
diff --git a/test/spec_session_memcache.rb b/test/spec_session_memcache.rb
index 7c81b9a..0ae4bdf 100644
--- a/test/spec_session_memcache.rb
+++ b/test/spec_session_memcache.rb
@@ -23,6 +23,10 @@ begin
env['rack.session.options'][:defer] = true
incrementor.call(env)
end
+ skip_session = proc do |env|
+ env['rack.session.options'][:skip] = true
+ incrementor.call(env)
+ end
# test memcache connection
Rack::Session::Memcache.new(incrementor)
@@ -168,14 +172,40 @@ begin
res4.body.should.equal '{"counter"=>1}'
end
- it "omits cookie with :defer option" do
+ it "omits cookie with :defer option but still updates the state" do
pool = Rack::Session::Memcache.new(incrementor)
+ count = Rack::Utils::Context.new(pool, incrementor)
defer = Rack::Utils::Context.new(pool, defer_session)
dreq = Rack::MockRequest.new(defer)
+ creq = Rack::MockRequest.new(count)
res0 = dreq.get("/")
res0["Set-Cookie"].should.equal nil
res0.body.should.equal '{"counter"=>1}'
+
+ res0 = creq.get("/")
+ res1 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+ res1.body.should.equal '{"counter"=>2}'
+ res2 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+ res2.body.should.equal '{"counter"=>3}'
+ end
+
+ it "omits cookie and state update with :skip option" do
+ pool = Rack::Session::Memcache.new(incrementor)
+ count = Rack::Utils::Context.new(pool, incrementor)
+ skip = Rack::Utils::Context.new(pool, skip_session)
+ sreq = Rack::MockRequest.new(skip)
+ creq = Rack::MockRequest.new(count)
+
+ res0 = sreq.get("/")
+ res0["Set-Cookie"].should.equal nil
+ res0.body.should.equal '{"counter"=>1}'
+
+ res0 = creq.get("/")
+ res1 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+ res1.body.should.equal '{"counter"=>2}'
+ res2 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
+ res2.body.should.equal '{"counter"=>2}'
end
it "updates deep hashes correctly" do
diff --git a/test/spec_session_pool.rb b/test/spec_session_pool.rb
index b498806..8c34236 100644
--- a/test/spec_session_pool.rb
+++ b/test/spec_session_pool.rb
@@ -181,20 +181,26 @@ describe Rack::Session::Pool do
end
it "does not return a cookie if cookie was not read/written" do
- app = Rack::Session::Cookie.new(nothing)
+ app = Rack::Session::Pool.new(nothing)
res = Rack::MockRequest.new(app).get("/")
res["Set-Cookie"].should.be.nil
end
it "does not return a cookie if cookie was not written (only read)" do
- app = Rack::Session::Cookie.new(session_id)
+ app = Rack::Session::Pool.new(session_id)
res = Rack::MockRequest.new(app).get("/")
res["Set-Cookie"].should.be.nil
end
it "returns even if not read/written if :expire_after is set" do
- app = Rack::Session::Cookie.new(nothing, :expire_after => 3600)
- res = Rack::MockRequest.new(app).get("/")
+ app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
res["Set-Cookie"].should.not.be.nil
end
+
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
+ app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
+ res = Rack::MockRequest.new(app).get("/")
+ res["Set-Cookie"].should.be.nil
+ end
end
diff --git a/test/spec_static.rb b/test/spec_static.rb
index 651c9f4..21b2b6a 100644
--- a/test/spec_static.rb
+++ b/test/spec_static.rb
@@ -11,9 +11,11 @@ describe Rack::Static do
root = File.expand_path(File.dirname(__FILE__))
OPTIONS = {:urls => ["/cgi"], :root => root}
+ STATIC_OPTIONS = {:urls => [""], :root => root, :index => 'static/index.html'}
HASH_OPTIONS = {:urls => {"/cgi/sekret" => 'cgi/test'}, :root => root}
@request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS))
+ @static_request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, STATIC_OPTIONS))
@hash_request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, HASH_OPTIONS))
it "serves files" do
@@ -33,6 +35,12 @@ describe Rack::Static do
res.body.should == "Hello World"
end
+ it "calls index file when requesting root" do
+ res = @static_request.get("/")
+ res.should.be.ok
+ res.body.should =~ /index!/
+ end
+
it "serves hidden files" do
res = @hash_request.get("/cgi/sekret")
res.should.be.ok
diff --git a/test/spec_thin.rb b/test/spec_thin.rb
index 9403964..f762d11 100644
--- a/test/spec_thin.rb
+++ b/test/spec_thin.rb
@@ -11,7 +11,7 @@ describe Rack::Handler::Thin do
Thin::Logging.silent = true
@thread = Thread.new do
- Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server|
+ Rack::Handler::Thin.run(@app, :Host => @host='127.0.0.1', :Port => @port=9204) do |server|
@server = server
end
end
@@ -30,7 +30,7 @@ describe Rack::Handler::Thin do
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9204"
- response["SERVER_NAME"].should.equal "0.0.0.0"
+ response["SERVER_NAME"].should.equal "127.0.0.1"
end
should "have rack headers" do
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index f4fe4e8..a787763 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -3,6 +3,24 @@ require 'rack/utils'
require 'rack/mock'
describe Rack::Utils do
+ def kcodeu
+ one8 = RUBY_VERSION.to_f < 1.9
+ default_kcode, $KCODE = $KCODE, 'U' if one8
+ yield
+ ensure
+ $KCODE = default_kcode if one8
+ end
+
+ should "round trip binary data" do
+ r = [218, 0].pack 'CC'
+ if defined?(::Encoding)
+ z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY)
+ else
+ z = Rack::Utils.unescape(Rack::Utils.escape(r))
+ end
+ r.should.equal z
+ end
+
should "escape correctly" do
Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
Rack::Utils.escape("a space").should.equal "a+space"
@@ -21,23 +39,27 @@ describe Rack::Utils do
if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
should "escape correctly for multibyte characters if $KCODE is set to 'U'" do
- default_kcode, $KCODE = $KCODE, 'U'
-
- matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
- matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
- Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
- matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
- matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
- Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
-
- $KCODE = default_kcode
+ kcodeu do
+ matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
+ matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
+ Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
+ matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
+ matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
+ Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
+ end
end
should "unescape multibyte characters correctly if $KCODE is set to 'U'" do
- default_kcode, $KCODE = $KCODE, 'U'
- Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
- "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
- $KCODE = default_kcode
+ kcodeu do
+ Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
+ "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
+ end
+ end
+ end
+
+ should "escape objects that responds to to_s" do
+ kcodeu do
+ Rack::Utils.escape(:id).should.equal "id"
end
end
@@ -46,6 +68,16 @@ describe Rack::Utils do
Rack::Utils.escape("ø".encode("ISO-8859-1")).should.equal "%F8"
end
end
+
+ should "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do
+ lambda {
+ timeout(1) do
+ lambda {
+ URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %"
+ }.should.raise(ArgumentError)
+ end
+ }.should.not.raise(Timeout::Error)
+ end
should "escape path spaces with %20" do
Rack::Utils.escape_path("foo bar").should.equal "foo%20bar"
@@ -185,22 +217,22 @@ describe Rack::Utils do
# unordered hash. Test that build_nested_query performs the inverse
# function of parse_nested_query.
[{"foo" => nil, "bar" => ""},
- {"foo" => "bar", "baz" => ""},
- {"foo" => ["1", "2"]},
- {"foo" => "bar", "baz" => ["1", "2", "3"]},
- {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
- {"foo" => ["1", "2"]},
- {"foo" => "bar", "baz" => ["1", "2", "3"]},
- {"x" => {"y" => {"z" => "1"}}},
- {"x" => {"y" => {"z" => ["1"]}}},
- {"x" => {"y" => {"z" => ["1", "2"]}}},
- {"x" => {"y" => [{"z" => "1"}]}},
- {"x" => {"y" => [{"z" => ["1"]}]}},
- {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
- {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
- {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
- {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
- {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
+ {"foo" => "bar", "baz" => ""},
+ {"foo" => ["1", "2"]},
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
+ {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
+ {"foo" => ["1", "2"]},
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
+ {"x" => {"y" => {"z" => "1"}}},
+ {"x" => {"y" => {"z" => ["1"]}}},
+ {"x" => {"y" => {"z" => ["1", "2"]}}},
+ {"x" => {"y" => [{"z" => "1"}]}},
+ {"x" => {"y" => [{"z" => ["1"]}]}},
+ {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
+ {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
+ {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
+ {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
].each { |params|
qs = Rack::Utils.build_nested_query(params)
Rack::Utils.parse_nested_query(qs).should.equal params
@@ -211,7 +243,18 @@ describe Rack::Utils do
message.should.equal "value must be a Hash"
end
- should "should escape html entities [&><'\"/]" do
+ should "parse query strings that have a non-existent value" do
+ key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
+ Rack::Utils.parse_query(key).should.equal Rack::Utils.unescape(key) => nil
+ end
+
+ should "build query strings without = with non-existent values" do
+ key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
+ key = Rack::Utils.unescape(key)
+ Rack::Utils.build_query(key => nil).should.equal Rack::Utils.escape(key)
+ end
+
+ should "escape html entities [&><'\"/]" do
Rack::Utils.escape_html("foo").should.equal "foo"
Rack::Utils.escape_html("f&o").should.equal "f&o"
Rack::Utils.escape_html("f<o").should.equal "f<o"
@@ -222,6 +265,27 @@ describe Rack::Utils do
Rack::Utils.escape_html("<foo></foo>").should.equal "<foo></foo>"
end
+ should "escape html entities even on MRI when it's bugged" do
+ test_escape = lambda do
+ kcodeu do
+ Rack::Utils.escape_html("\300<").should.equal "\300<"
+ end
+ end
+
+ if RUBY_VERSION.to_f < 1.9
+ test_escape.call
+ else
+ test_escape.should.raise(ArgumentError)
+ end
+ end
+
+ if "".respond_to?(:encode)
+ should "escape html entities in unicode strings" do
+ # the following will cause warnings if the regex is poorly encoded:
+ Rack::Utils.escape_html("☃").should.equal "☃"
+ end
+ end
+
should "figure out which encodings are acceptable" do
helper = lambda do |a, b|
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
diff --git a/test/spec_webrick.rb b/test/spec_webrick.rb
index e3af233..d121c69 100644
--- a/test/spec_webrick.rb
+++ b/test/spec_webrick.rb
@@ -6,7 +6,7 @@ Thread.abort_on_exception = true
describe Rack::Handler::WEBrick do
extend TestRequest::Helpers
- @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0',
+ @server = WEBrick::HTTPServer.new(:Host => @host='127.0.0.1',
:Port => @port=9202,
:Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
:AccessLog => [])
@@ -28,7 +28,7 @@ describe Rack::Handler::WEBrick do
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9202"
- response["SERVER_NAME"].should.equal "0.0.0.0"
+ response["SERVER_NAME"].should.equal "127.0.0.1"
end
should "have rack headers" do
@@ -106,7 +106,9 @@ describe Rack::Handler::WEBrick do
block_ran = false
catch(:done) {
Rack::Handler::WEBrick.run(lambda {},
- {:Port => 9210,
+ {
+ :Host => '127.0.0.1',
+ :Port => 9210,
:Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
:AccessLog => []}) { |server|
block_ran = true
diff --git a/test/static/index.html b/test/static/index.html
new file mode 100644
index 0000000..fc2d9ad
--- /dev/null
+++ b/test/static/index.html
@@ -0,0 +1 @@
+index!
--
ruby-rack.git
More information about the Pkg-ruby-extras-commits
mailing list