Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki

This commit is contained in:
Jacques Distler 2009-12-18 20:20:17 -06:00
commit f50d7189f7
113 changed files with 5759 additions and 199 deletions

18
vendor/plugins/rack/COPYING vendored Normal file
View file

@ -0,0 +1,18 @@
Copyright (c) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

18
vendor/plugins/rack/KNOWN-ISSUES vendored Normal file
View file

@ -0,0 +1,18 @@
= Known issues with Rack and Web servers
* Lighttpd sets wrong SCRIPT_NAME and PATH_INFO if you mount your
FastCGI app at "/". This can be fixed by using this middleware:
class LighttpdScriptNameFix
def initialize(app)
@app = app
end
def call(env)
env["PATH_INFO"] = env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s
env["SCRIPT_NAME"] = ""
@app.call(env)
end
end
Of course, use this only when your app runs at "/".

364
vendor/plugins/rack/README vendored Normal file
View file

@ -0,0 +1,364 @@
= Rack, a modular Ruby webserver interface
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
middleware) into a single method call.
The exact details of this are described in the Rack specification,
which all Rack applications should conform to.
== Specification changes in this release
With Rack 1.0, the Rack specification (found in SPEC) changed in the
following backward-incompatible ways. This was done to properly
support Ruby 1.9 and to deprecate some problematic techniques:
* Rack::VERSION has been pushed to [1,0].
* Header values must be Strings now, split on "\n".
* rack.input must be rewindable and support reading into a buffer,
wrap with Rack::RewindableInput if it isn't.
* Content-Length can be missing, in this case chunked transfer
encoding is used.
* Bodies can now additionally respond to #to_path with a filename to
be served.
* String bodies are deprecated and will not work with Ruby 1.9, use an
Array with a single String instead.
* rack.session is now specified.
== Supported web servers
The included *handlers* connect all kinds of web servers to Rack:
* Mongrel
* EventedMongrel
* SwiftipliedMongrel
* WEBrick
* FCGI
* CGI
* SCGI
* LiteSpeed
* Thin
These web servers include Rack handlers in their distributions:
* Ebb
* Fuzed
* Phusion Passenger (which is mod_rack for Apache and for nginx)
* Unicorn
Any valid Rack app will run the same on all these handlers, without
changing anything.
== Supported web frameworks
The included *adapters* connect Rack with existing Ruby web frameworks:
* Camping
These frameworks include Rack adapters in their distributions:
* Camping
* Coset
* Halcyon
* Mack
* Maveric
* Merb
* Racktools::SimpleApplication
* Ramaze
* Ruby on Rails
* Rum
* Sinatra
* Sin
* Vintage
* Waves
* Wee
Current links to these projects can be found at
http://wiki.ramaze.net/Home#other-frameworks
== Available middleware
Between the server and the framework, Rack can be customized to your
applications needs using middleware, for example:
* Rack::URLMap, to route to multiple applications inside the same process.
* Rack::CommonLogger, for creating Apache-style logfiles.
* Rack::ShowException, for catching unhandled exceptions and
presenting them in a nice and helpful way with clickable backtrace.
* Rack::File, for serving static files.
* ...many others!
All these components use the same interface, which is described in
detail in the Rack specification. These optional components can be
used in any way you wish.
== Convenience
If you want to develop outside of existing frameworks, implement your
own ones, or develop middleware, Rack provides many helpers to create
Rack applications quickly and without doing the same web stuff all
over:
* Rack::Request, which also provides query string parsing and
multipart handling.
* Rack::Response, for convenient generation of HTTP replies and
cookie handling.
* Rack::MockRequest and Rack::MockResponse for efficient and quick
testing of Rack application without real HTTP round-trips.
== rack-contrib
The plethora of useful middleware created the need for a project that
collects fresh Rack middleware. rack-contrib includes a variety of
add-on components for Rack and it is easy to contribute new modules.
* http://github.com/rack/rack-contrib
== rackup
rackup is a useful tool for running Rack applications, which uses the
Rack::Builder DSL to configure middleware and build up applications
easily.
rackup automatically figures out the environment it is run in, and
runs your application as FastCGI, CGI, or standalone with Mongrel or
WEBrick---all from the same configuration.
== Quick start
Try the lobster!
Either with the embedded WEBrick starter:
ruby -Ilib lib/rack/lobster.rb
Or with rackup:
bin/rackup -Ilib example/lobster.ru
By default, the lobster is found at http://localhost:9292.
== Installing with RubyGems
A Gem of Rack is available. You can install it with:
gem install rack
I also provide a local mirror of the gems (and development snapshots)
at my site:
gem install rack --source http://chneukirchen.org/releases/gems/
== Running the tests
Testing Rack requires the test/spec testing framework:
gem install test-spec
There are two rake-based test tasks:
rake test tests all the fast tests (no Handlers or Adapters)
rake fulltest runs all the tests
The fast testsuite has no dependencies outside of the core Ruby
installation and test-spec.
To run the test suite completely, you need:
* camping
* fcgi
* memcache-client
* mongrel
* ruby-openid
* thin
The full set of tests test FCGI access with lighttpd (on port
9203) so you will need lighttpd installed as well as the FCGI
libraries and the fcgi gem:
Download and install lighttpd:
http://www.lighttpd.net/download
Installing the FCGI libraries:
curl -O http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
tar xzvf fcgi-2.4.0.tar.gz
cd fcgi-2.4.0
./configure --prefix=/usr/local
make
sudo make install
cd ..
Installing the Ruby fcgi gem:
gem install fcgi
Furthermore, to test Memcache sessions, you need memcached (will be
run on port 11211) and memcache-client installed.
== History
* March 3rd, 2007: First public release 0.1.
* May 16th, 2007: Second public release 0.2.
* HTTP Basic authentication.
* Cookie Sessions.
* Static file handler.
* Improved Rack::Request.
* Improved Rack::Response.
* Added Rack::ShowStatus, for better default error messages.
* Bug fixes in the Camping adapter.
* Removed Rails adapter, was too alpha.
* February 26th, 2008: Third public release 0.3.
* LiteSpeed handler, by Adrian Madrid.
* SCGI handler, by Jeremy Evans.
* Pool sessions, by blink.
* OpenID authentication, by blink.
* :Port and :File options for opening FastCGI sockets, by blink.
* Last-Modified HTTP header for Rack::File, by blink.
* Rack::Builder#use now accepts blocks, by Corey Jewett.
(See example/protectedlobster.ru)
* HTTP status 201 can contain a Content-Type and a body now.
* Many bugfixes, especially related to Cookie handling.
* August 21st, 2008: Fourth public release 0.4.
* New middleware, Rack::Deflater, by Christoffer Sawicki.
* OpenID authentication now needs ruby-openid 2.
* New Memcache sessions, by blink.
* Explicit EventedMongrel handler, by Joshua Peek <josh@joshpeek.com>
* Rack::Reloader is not loaded in rackup development mode.
* rackup can daemonize with -D.
* Many bugfixes, especially for pool sessions, URLMap, thread safety
and tempfile handling.
* Improved tests.
* Rack moved to Git.
* January 6th, 2009: Fifth public release 0.9.
* Rack is now managed by the Rack Core Team.
* Rack::Lint is stricter and follows the HTTP RFCs more closely.
* Added ConditionalGet middleware.
* Added ContentLength middleware.
* Added Deflater middleware.
* Added Head middleware.
* Added MethodOverride middleware.
* Rack::Mime now provides popular MIME-types and their extension.
* Mongrel Header now streams.
* Added Thin handler.
* Official support for swiftiplied Mongrel.
* Secure cookies.
* Made HeaderHash case-preserving.
* Many bugfixes and small improvements.
* January 9th, 2009: Sixth public release 0.9.1.
* Fix directory traversal exploits in Rack::File and Rack::Directory.
* April 25th, 2009: Seventh public release 1.0.0.
* SPEC change: Rack::VERSION has been pushed to [1,0].
* SPEC change: header values must be Strings now, split on "\n".
* SPEC change: Content-Length can be missing, in this case chunked transfer
encoding is used.
* SPEC change: rack.input must be rewindable and support reading into
a buffer, wrap with Rack::RewindableInput if it isn't.
* SPEC change: rack.session is now specified.
* SPEC change: Bodies can now additionally respond to #to_path with
a filename to be served.
* NOTE: String bodies break in 1.9, use an Array consisting of a
single String instead.
* New middleware Rack::Lock.
* New middleware Rack::ContentType.
* Rack::Reloader has been rewritten.
* Major update to Rack::Auth::OpenID.
* Support for nested parameter parsing in Rack::Response.
* Support for redirects in Rack::Response.
* HttpOnly cookie support in Rack::Response.
* The Rakefile has been rewritten.
* Many bugfixes and small improvements.
* October 18th, 2009: Eighth public release 1.0.1.
* Bump remainder of rack.versions.
* Support the pure Ruby FCGI implementation.
* Fix for form names containing "=": split first then unescape components
* Fixes the handling of the filename parameter with semicolons in names.
* Add anchor to nested params parsing regexp to prevent stack overflows
* Use more compatible gzip write api instead of "<<".
* Make sure that Reloader doesn't break when executed via ruby -e
* Make sure WEBrick respects the :Host option
* Many Ruby 1.9 fixes.
== Contact
Please mail bugs, suggestions and patches to
<mailto:rack-devel@googlegroups.com>.
Mailing list archives are available at
<http://groups.google.com/group/rack-devel>.
There is a bug tracker at <http://rack.lighthouseapp.com/>.
Git repository (send Git patches to the mailing list):
* http://github.com/rack/rack
* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git
You are also welcome to join the #rack channel on irc.freenode.net.
== Thanks
The Rack Core Team, consisting of
* Christian Neukirchen (chneukirchen)
* James Tucker (raggi)
* Josh Peek (josh)
* Michael Fellinger (manveru)
* Ryan Tomayko (rtomayko)
* Scytrin dai Kinthra (scytrin)
would like to thank:
* Adrian Madrid, for the LiteSpeed handler.
* Christoffer Sawicki, for the first Rails adapter and Rack::Deflater.
* Tim Fletcher, for the HTTP authentication code.
* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes.
* Armin Ronacher, for the logo and racktools.
* Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd,
Tom Robinson, Phil Hagelberg, and S. Brent Faulkner for bug fixing
and other improvements.
* Brian Candler, for Rack::ContentType.
* Graham Batty, for improved handler loading.
* Stephen Bannasch, for bug reports and documentation.
* Gary Wright, for proposing a better Rack::Response interface.
* Jonathan Buch, for improvements regarding Rack::Response.
* Armin Röhrl, for tracking down bugs in the Cookie generator.
* Alexander Kellett for testing the Gem and reviewing the announcement.
* Marcus Rückert, for help with configuring and debugging lighttpd.
* The WSGI team for the well-done and documented work they've done and
Rack builds up on.
* All bug reporters and patch contributers not mentioned above.
== Copyright
Copyright (C) 2007, 2008, 2009 Christian Neukirchen <http://purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== Links
Rack:: <http://rack.rubyforge.org/>
Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
Official Rack repositories:: <http://github.com/rack>
rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
Christian Neukirchen:: <http://chneukirchen.org/>

164
vendor/plugins/rack/Rakefile vendored Normal file
View file

@ -0,0 +1,164 @@
# Rakefile for Rack. -*-ruby-*-
require 'rake/rdoctask'
require 'rake/testtask'
desc "Run all the tests"
task :default => [:test]
desc "Make an archive as .tar.gz"
task :dist => [:chmod, :changelog, :rdoc, "SPEC", "rack.gemspec"] do
FileUtils.touch("RDOX")
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
sh "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX SPEC ChangeLog doc rack.gemspec"
sh "gzip -f -9 #{release}.tar"
end
desc "Make an official release"
task :officialrelease do
puts "Official build for #{release}..."
sh "rm -rf stage"
sh "git clone --shared . stage"
sh "cd stage && rake officialrelease_really"
sh "mv stage/#{release}.tar.gz stage/#{release}.gem ."
end
task :officialrelease_really => [:fulltest, "RDOX", "SPEC", :dist, :gem] do
sh "sha1sum #{release}.tar.gz #{release}.gem"
end
def version
abort "You need to pass VERSION=... to build packages." unless ENV["VERSION"]
ENV["VERSION"]
end
def release
"rack-#{version}"
end
def manifest
`git ls-files`.split("\n")
end
desc "Make binaries executable"
task :chmod do
Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
Dir["test/cgi/test*"].each { |binary| File.chmod(0775, binary) }
end
desc "Generate a ChangeLog"
task :changelog do
File.open("ChangeLog", "w") { |out|
`git log -z`.split("\0").map { |chunk|
author = chunk[/Author: (.*)/, 1].strip
date = chunk[/Date: (.*)/, 1].strip
desc, detail = $'.strip.split("\n", 2)
detail ||= ""
detail = detail.gsub(/.*darcs-hash:.*/, '')
detail.rstrip!
out.puts "#{date} #{author}"
out.puts " * #{desc.strip}"
out.puts detail unless detail.empty?
out.puts
}
}
end
desc "Generate RDox"
task "RDOX" do
sh "specrb -Ilib:test -a --rdox >RDOX"
end
desc "Generate Rack Specification"
task "SPEC" do
File.open("SPEC", "wb") { |file|
IO.foreach("lib/rack/lint.rb") { |line|
if line =~ /## (.*)/
file.puts $1
end
}
}
end
desc "Run all the fast tests"
task :test do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|Rack::Auth::OpenID)"'}"
end
desc "Run all the tests"
task :fulltest => [:chmod] do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
end
begin
require 'rubygems'
rescue LoadError
# Too bad.
else
task "rack.gemspec" do
spec = Gem::Specification.new do |s|
s.name = "rack"
s.version = version
s.platform = Gem::Platform::RUBY
s.summary = "a modular Ruby webserver interface"
s.description = <<-EOF
Rack provides 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
middleware) into a single method call.
Also see http://rack.rubyforge.org.
EOF
s.files = manifest + %w(SPEC RDOX rack.gemspec)
s.bindir = 'bin'
s.executables << 'rackup'
s.require_path = 'lib'
s.has_rdoc = true
s.extra_rdoc_files = ['README', 'SPEC', 'RDOX', 'KNOWN-ISSUES']
s.test_files = Dir['test/{test,spec}_*.rb']
s.author = 'Christian Neukirchen'
s.email = 'chneukirchen@gmail.com'
s.homepage = 'http://rack.rubyforge.org'
s.rubyforge_project = 'rack'
s.add_development_dependency 'test-spec'
s.add_development_dependency 'camping'
s.add_development_dependency 'fcgi'
s.add_development_dependency 'memcache-client'
s.add_development_dependency 'mongrel'
s.add_development_dependency 'ruby-openid', '~> 2.0.0'
s.add_development_dependency 'thin'
end
File.open("rack.gemspec", "w") { |f| f << spec.to_ruby }
end
task :gem => ["rack.gemspec", "SPEC"] do
FileUtils.touch("RDOX")
sh "gem build rack.gemspec"
end
end
desc "Generate RDoc documentation"
task :rdoc do
sh(*%w{rdoc --line-numbers --main README
--title 'Rack\ Documentation' --charset utf-8 -U -o doc} +
%w{README KNOWN-ISSUES SPEC RDOX} +
Dir["lib/**/*.rb"])
end
task :pushsite => [:rdoc] do
sh "cd site && git gc"
sh "rsync -avz doc/ chneukirchen@rack.rubyforge.org:/var/www/gforge-projects/rack/doc/"
sh "rsync -avz site/ chneukirchen@rack.rubyforge.org:/var/www/gforge-projects/rack/"
sh "cd site && git push"
end

176
vendor/plugins/rack/bin/rackup vendored Executable file
View file

@ -0,0 +1,176 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$LOAD_PATH.unshift File.expand_path("#{__FILE__}/../../lib")
autoload :Rack, 'rack'
require 'optparse'
automatic = false
server = nil
env = "development"
daemonize = false
pid = nil
options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
# Don't evaluate CGI ISINDEX parameters.
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
ARGV.clear if ENV.include?("REQUEST_METHOD")
opts = OptionParser.new("", 24, ' ') { |opts|
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
opts.separator ""
opts.separator "Ruby options:"
lineno = 1
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
eval line, TOPLEVEL_BINDING, "-e", lineno
lineno += 1
}
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
$DEBUG = true
}
opts.on("-w", "--warn", "turn warnings on for your script") {
$-w = true
}
opts.on("-I", "--include PATH",
"specify $LOAD_PATH (may be used more than once)") { |path|
$LOAD_PATH.unshift(*path.split(":"))
}
opts.on("-r", "--require LIBRARY",
"require the library, before executing your script") { |library|
require library
}
opts.separator ""
opts.separator "Rack options:"
opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
server = s
}
opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
options[:Host] = host
}
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
options[:Port] = port
}
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
env = e
}
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
daemonize = d ? true : false
}
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
pid = File.expand_path(f)
}
opts.separator ""
opts.separator "Common options:"
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
opts.on_tail("--version", "Show version") do
puts "Rack #{Rack.version}"
exit
end
opts.parse! ARGV
}
require 'pp' if $DEBUG
config = ARGV[0] || "config.ru"
if !File.exist? config
abort "configuration #{config} not found"
end
if config =~ /\.ru$/
cfgfile = File.read(config)
if cfgfile[/^#\\(.*)/]
opts.parse! $1.split(/\s+/)
end
inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
nil, config
else
require config
inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
end
unless server = Rack::Handler.get(server)
# Guess.
if ENV.include?("PHP_FCGI_CHILDREN")
server = Rack::Handler::FastCGI
# We already speak FastCGI
options.delete :File
options.delete :Port
elsif ENV.include?("REQUEST_METHOD")
server = Rack::Handler::CGI
else
begin
server = Rack::Handler::Mongrel
rescue LoadError => e
server = Rack::Handler::WEBrick
end
end
end
p server if $DEBUG
case env
when "development"
app = Rack::Builder.new {
use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
use Rack::ShowExceptions
use Rack::Lint
run inner_app
}.to_app
when "deployment"
app = Rack::Builder.new {
use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
run inner_app
}.to_app
when "none"
app = inner_app
end
if $DEBUG
pp app
pp inner_app
end
if daemonize
if RUBY_VERSION < "1.9"
exit if fork
Process.setsid
exit if fork
Dir.chdir "/"
File.umask 0000
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
STDERR.reopen "/dev/null", "a"
else
Process.daemon
end
if pid
File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
at_exit { File.delete(pid) if File.exist?(pid) }
end
end
server.run app, options

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,4 @@
require 'rack/lobster'
use Rack::ShowExceptions
run Rack::Lobster.new

View file

@ -0,0 +1,14 @@
require 'rack'
require 'rack/lobster'
lobster = Rack::Lobster.new
protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password|
'secret' == password
end
protected_lobster.realm = 'Lobster 2.0'
pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster))
Rack::Handler::WEBrick.run pretty_protected_lobster, :Port => 9292

View file

@ -0,0 +1,8 @@
require 'rack/lobster'
use Rack::ShowExceptions
use Rack::Auth::Basic, "Lobster 2.0" do |username, password|
'secret' == password
end
run Rack::Lobster.new

View file

@ -60,7 +60,7 @@ module Rack
@writer = block
gzip =::Zlib::GzipWriter.new(self)
gzip.mtime = @mtime
@body.each { |part| gzip << part }
@body.each { |part| gzip.write(part) }
@body.close if @body.respond_to?(:close)
gzip.close
@writer = nil

View file

@ -3,13 +3,15 @@ require 'socket'
require 'rack/content_length'
require 'rack/rewindable_input'
class FCGI::Stream
alias _rack_read_without_buffer read
if defined? FCGI::Stream
class FCGI::Stream
alias _rack_read_without_buffer read
def read(n, buffer=nil)
buf = _rack_read_without_buffer n
buffer.replace(buf.to_s) if buffer
buf
def read(n, buffer=nil)
buf = _rack_read_without_buffer n
buffer.replace(buf.to_s) if buffer
buf
end
end
end
@ -31,7 +33,7 @@ module Rack
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
rack_input = RewindableInput.new(request.in)
env.update({"rack.version" => [1,0],

View file

@ -15,14 +15,19 @@ module Rack
env = ENV.to_hash
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new($stdin.read.to_s),
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
})
rack_input = RewindableInput.new($stdin.read.to_s)
env.update(
"rack.version" => [1,0],
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
)
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"

View file

@ -10,7 +10,7 @@ module Rack
server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
options[:Port] || 8080)
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
# Use is similar to #run, replacing the app argument with a hash of
# Use is similar to #run, replacing the app argument with a hash of
# { path=>app, ... } or an instance of Rack::URLMap.
if options[:map]
if app.is_a? Hash
@ -45,8 +45,11 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
rack_input = request.body || StringIO.new('')
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0],
"rack.input" => request.body || StringIO.new(""),
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,

View file

@ -7,14 +7,14 @@ module Rack
module Handler
class SCGI < ::SCGI::Processor
attr_accessor :app
def self.run(app, options=nil)
new(options.merge(:app=>app,
:host=>options[:Host],
:port=>options[:Port],
:socket=>options[:Socket])).listen
end
def initialize(settings = {})
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
@log = Object.new
@ -22,7 +22,7 @@ module Rack
def @log.error(*args); end
super(settings)
end
def process_request(request, input_body, socket)
env = {}.replace(request)
env.delete "HTTP_CONTENT_TYPE"
@ -32,10 +32,13 @@ module Rack
env["PATH_INFO"] = env["REQUEST_PATH"]
env["QUERY_STRING"] ||= ""
env["SCRIPT_NAME"] = ""
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new(input_body),
"rack.errors" => $stderr,
rack_input = StringIO.new(input_body)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0],
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,

View file

@ -6,6 +6,7 @@ module Rack
module Handler
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host]
server = ::WEBrick::HTTPServer.new(options)
server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { server.shutdown }
@ -23,8 +24,11 @@ module Rack
env = req.meta_vars
env.delete_if { |k, v| v.nil? }
rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new(req.body.to_s),
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,

View file

@ -233,8 +233,17 @@ module Rack
## === The Input Stream
##
## The input stream is an IO-like object which contains the raw HTTP
## POST data. If it is a file then it must be opened in binary mode.
## POST data.
def check_input(input)
## When applicable, its external encoding must be "ASCII-8BIT" and it
## must be opened in binary mode, for Ruby 1.9 compatibility.
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
input.external_encoding.name == "ASCII-8BIT"
} if input.respond_to?(:external_encoding)
assert("rack.input #{input} is not opened in binary mode") {
input.binmode?
} if input.respond_to?(:binmode?)
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
[:gets, :each, :read, :rewind].each { |method|
assert("rack.input #{input} does not respond to ##{method}") {
@ -291,9 +300,9 @@ module Rack
args[1].kind_of?(String)
}
end
v = @input.read(*args)
assert("rack.input#read didn't return nil or a String") {
v.nil? or v.instance_of? String
}
@ -302,7 +311,7 @@ module Rack
!v.nil?
}
end
v
end
@ -316,7 +325,7 @@ module Rack
yield line
}
end
## * +rewind+ must be called without arguments. It rewinds the input
## stream back to the beginning. It must not raise Errno::ESPIPE:
## that is, it may not be a pipe or a socket. Therefore, handler

View file

@ -73,17 +73,14 @@ module Rack
# Return the Rack environment used for a request to +uri+.
def self.env_for(uri="", opts={})
uri = URI(uri)
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
env = DEFAULT_ENV.dup
env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
env["REQUEST_METHOD"] = opts[:method] || "GET"
env["SERVER_NAME"] = uri.host || "example.org"
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
env["QUERY_STRING"] = uri.query.to_s
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
env["rack.url_scheme"] = uri.scheme || "http"
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
env["SCRIPT_NAME"] = opts[:script_name] || ""
@ -93,34 +90,16 @@ module Rack
env["rack.errors"] = StringIO.new
end
if params = opts[:params]
if env["REQUEST_METHOD"] == "GET"
params = Utils.parse_nested_query(params) if params.is_a?(String)
params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
env["QUERY_STRING"] = Utils.build_nested_query(params)
elsif !opts.has_key?(:input)
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
if params.is_a?(Hash)
if data = Utils::Multipart.build_multipart(params)
opts[:input] = data
opts["CONTENT_LENGTH"] ||= data.length.to_s
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
else
opts[:input] = Utils.build_nested_query(params)
end
else
opts[:input] = params
end
end
end
opts[:input] ||= ""
if String === opts[:input]
env["rack.input"] = StringIO.new(opts[:input])
rack_input = StringIO.new(opts[:input])
else
env["rack.input"] = opts[:input]
rack_input = opts[:input]
end
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env['rack.input'] = rack_input
env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
opts.each { |field, value|
@ -149,7 +128,7 @@ module Rack
@body = ""
body.each { |part| @body << part }
@errors = errors.string if errors.respond_to?(:string)
@errors = errors.string
end
# Status

View file

@ -70,7 +70,7 @@ module Rack
next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
found, stat = figure_path(file, paths)
next unless found and stat and mtime = stat.mtime
next unless found && stat && mtime = stat.mtime
@cache[file] = found
@ -87,7 +87,7 @@ module Rack
found, stat = safe_stat(found)
return found, stat if found
paths.each do |possible_path|
paths.find do |possible_path|
path = ::File.join(possible_path, file)
found, stat = safe_stat(path)
return ::File.expand_path(found), stat if found

View file

@ -17,14 +17,6 @@ module Rack
# The environment of the request.
attr_reader :env
def self.new(env, *args)
if self == Rack::Request
env["rack.request"] ||= super
else
super
end
end
def initialize(env)
@env = env
end
@ -73,7 +65,7 @@ module Rack
def host
# Remove port number.
(@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
(@env["HTTP_HOST"] || @env["SERVER_NAME"]).to_s.gsub(/:\d+\z/, '')
end
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
@ -100,7 +92,7 @@ module Rack
PARSEABLE_DATA_MEDIA_TYPES = [
'multipart/related',
'multipart/mixed'
]
]
# Determine whether the request body contains form-data by checking
# the request media_type against registered form-data media-types:
@ -222,11 +214,11 @@ module Rack
url
end
def path
script_name + path_info
end
def fullpath
query_string.empty? ? path : "#{path}?#{query_string}"
end

View file

@ -13,7 +13,7 @@ module Rack
# version since it's faster. (Stolen from Camping).
def escape(s)
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
'%'+$1.unpack('H2'*$1.size).join('%').upcase
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
}.tr(' ', '+')
end
module_function :escape
@ -26,16 +26,18 @@ module Rack
end
module_function :unescape
DEFAULT_SEP = /[&;] */n
# 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
# cookies by changing the characters used in the second
# parameter (which defaults to '&;').
def parse_query(qs, d = '&;')
def parse_query(qs, d = nil)
params = {}
(qs || '').split(/[#{d}] */n).each do |p|
k, v = unescape(p).split('=', 2)
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) }
if cur = params[k]
if cur.class == Array
@ -52,10 +54,10 @@ module Rack
end
module_function :parse_query
def parse_nested_query(qs, d = '&;')
def parse_nested_query(qs, d = nil)
params = {}
(qs || '').split(/[#{d}] */n).each do |p|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = unescape(p).split('=', 2)
normalize_params(params, k, v)
end
@ -101,31 +103,12 @@ module Rack
if v.class == Array
build_query(v.map { |x| [k, x] })
else
escape(k) + "=" + escape(v)
"#{escape(k)}=#{escape(v)}"
end
}.join("&")
end
module_function :build_query
def build_nested_query(value, prefix = nil)
case value
when Array
value.map { |v|
build_nested_query(v, "#{prefix}[]")
}.join("&")
when Hash
value.map { |k, v|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
}.join("&")
when String
raise ArgumentError, "value must be a Hash" if prefix.nil?
"#{prefix}=#{escape(value)}"
else
prefix
end
end
module_function :build_nested_query
# Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string)
string.to_s.gsub("&", "&amp;").
@ -310,35 +293,7 @@ module Rack
# Usually, Rack::Request#POST takes care of calling this.
module Multipart
class UploadedFile
# The filename, *not* including the path, of the "uploaded" file
attr_reader :original_filename
# The content type of the "uploaded" file
attr_accessor :content_type
def initialize(path, content_type = "text/plain", binary = false)
raise "#{path} file does not exist" unless ::File.exist?(path)
@content_type = content_type
@original_filename = ::File.basename(path)
@tempfile = Tempfile.new(@original_filename)
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
@tempfile.binmode if binary
FileUtils.copy_file(path, @tempfile.path)
end
def path
@tempfile.path
end
alias_method :local_path, :path
def method_missing(method_name, *args, &block) #:nodoc:
@tempfile.__send__(method_name, *args, &block)
end
end
EOL = "\r\n"
MULTIPART_BOUNDARY = "AaB03x"
def self.parse_multipart(env)
unless env['CONTENT_TYPE'] =~
@ -375,7 +330,7 @@ module Rack
head = buf.slice!(0, i+2) # First \r\n
buf.slice!(0, 2) # Second \r\n
filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1]
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
@ -423,7 +378,7 @@ module Rack
:name => name, :tempfile => body, :head => head}
elsif !filename && content_type
body.rewind
# Generic multipart cases, not coming from a form
data = {:type => content_type,
:name => name, :tempfile => body, :head => head}
@ -441,76 +396,6 @@ module Rack
params
end
end
def self.build_multipart(params, first = true)
if first
unless params.is_a?(Hash)
raise ArgumentError, "value must be a Hash"
end
multipart = false
query = lambda { |value|
case value
when Array
value.each(&query)
when Hash
value.values.each(&query)
when UploadedFile
multipart = true
end
}
params.values.each(&query)
return nil unless multipart
end
flattened_params = Hash.new
params.each do |key, value|
k = first ? key.to_s : "[#{key}]"
case value
when Array
value.map { |v|
build_multipart(v, false).each { |subkey, subvalue|
flattened_params["#{k}[]#{subkey}"] = subvalue
}
}
when Hash
build_multipart(value, false).each { |subkey, subvalue|
flattened_params[k + subkey] = subvalue
}
else
flattened_params[k] = value
end
end
if first
flattened_params.map { |name, file|
if file.respond_to?(:original_filename)
::File.open(file.path, "rb") do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF
--#{MULTIPART_BOUNDARY}\r
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
Content-Type: #{file.content_type}\r
Content-Length: #{::File.stat(file.path).size}\r
\r
#{f.read}\r
EOF
end
else
<<-EOF
--#{MULTIPART_BOUNDARY}\r
Content-Disposition: form-data; name="#{name}"\r
\r
#{file}\r
EOF
end
}.join + "--#{MULTIPART_BOUNDARY}--\r"
else
flattened_params
end
end
end
end
end

View file

@ -0,0 +1,20 @@
server.modules = ("mod_fastcgi", "mod_cgi")
server.document-root = "."
server.errorlog = "lighttpd.errors"
server.port = 9203
server.event-handler = "select"
cgi.assign = ("/test" => "",
# ".ru" => ""
)
fastcgi.server = ("test.fcgi" => ("localhost" =>
("min-procs" => 1,
"socket" => "/tmp/rack-test-fcgi",
"bin-path" => "test.fcgi")),
"test.ru" => ("localhost" =>
("min-procs" => 1,
"socket" => "/tmp/rack-test-ru-fcgi",
"bin-path" => "test.ru")),
)

9
vendor/plugins/rack/test/cgi/test vendored Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$: << File.join(File.dirname(__FILE__), "..", "..", "lib")
require 'rack'
require '../testrequest'
Rack::Handler::CGI.run(Rack::Lint.new(TestRequest.new))

8
vendor/plugins/rack/test/cgi/test.fcgi vendored Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$:.unshift '../../lib'
require 'rack'
require '../testrequest'
Rack::Handler::FastCGI.run(Rack::Lint.new(TestRequest.new))

7
vendor/plugins/rack/test/cgi/test.ru vendored Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env ../../bin/rackup
#\ -E deployment -I ../../lib
# -*- ruby -*-
require '../testrequest'
run TestRequest.new

Binary file not shown.

View file

@ -0,0 +1,10 @@
--AaB03x
Content-Disposition: form-data; name="submit-name"
Larry
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
--AaB03x--

6
vendor/plugins/rack/test/multipart/ie vendored Normal file
View file

@ -0,0 +1,6 @@
--AaB03x
Content-Disposition: form-data; name="files"; filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"
Content-Type: text/plain
contents
--AaB03x--

View file

@ -0,0 +1,10 @@
--AaB03x
Content-Disposition: form-data; name="foo[submit-name]"
Larry
--AaB03x
Content-Disposition: form-data; name="foo[files]"; filename="file1.txt"
Content-Type: text/plain
contents
--AaB03x--

View file

@ -0,0 +1,9 @@
--AaB03x
Content-Disposition: form-data; name="submit-name"
Larry
--AaB03x
Content-Disposition: form-data; name="files"; filename=""
--AaB03x--

View file

@ -0,0 +1,6 @@
--AaB03x
Content-Disposition: form-data; name="files"; filename="fi;le1.txt"
Content-Type: text/plain
contents
--AaB03x--

10
vendor/plugins/rack/test/multipart/text vendored Normal file
View file

@ -0,0 +1,10 @@
--AaB03x
Content-Disposition: form-data; name="submit-name"
Larry
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
contents
--AaB03x--

View file

@ -0,0 +1,73 @@
require 'test/spec'
require 'rack/auth/basic'
require 'rack/mock'
context 'Rack::Auth::Basic' do
def realm
'WallysWorld'
end
def unprotected_app
lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
end
def protected_app
app = Rack::Auth::Basic.new(unprotected_app) { |username, password| 'Boss' == username }
app.realm = realm
app
end
setup do
@request = Rack::MockRequest.new(protected_app)
end
def request_with_basic_auth(username, password, &block)
request 'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:#{password}"].pack("m*"), &block
end
def request(headers = {})
yield @request.get('/', headers)
end
def assert_basic_auth_challenge(response)
response.should.be.a.client_error
response.status.should.equal 401
response.should.include 'WWW-Authenticate'
response.headers['WWW-Authenticate'].should =~ /Basic realm="#{Regexp.escape(realm)}"/
response.body.should.be.empty
end
specify 'should challenge correctly when no credentials are specified' do
request do |response|
assert_basic_auth_challenge response
end
end
specify 'should rechallenge if incorrect credentials are specified' do
request_with_basic_auth 'joe', 'password' do |response|
assert_basic_auth_challenge response
end
end
specify 'should return application output if correct credentials are specified' do
request_with_basic_auth 'Boss', 'password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Boss'
end
end
specify 'should return 400 Bad Request if different auth scheme used' do
request 'HTTP_AUTHORIZATION' => 'Digest params' do |response|
response.should.be.a.client_error
response.status.should.equal 400
response.should.not.include 'WWW-Authenticate'
end
end
specify 'realm as optional constructor arg' do
app = Rack::Auth::Basic.new(unprotected_app, realm) { true }
assert_equal realm, app.realm
end
end

View file

@ -0,0 +1,226 @@
require 'test/spec'
require 'rack/auth/digest/md5'
require 'rack/mock'
context 'Rack::Auth::Digest::MD5' do
def realm
'WallysWorld'
end
def unprotected_app
lambda do |env|
[ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
end
end
def protected_app
app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
{ 'Alice' => 'correct-password' }[username]
end
app.realm = realm
app.opaque = 'this-should-be-secret'
app
end
def protected_app_with_hashed_passwords
app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil
end
app.realm = realm
app.opaque = 'this-should-be-secret'
app.passwords_hashed = true
app
end
def partially_protected_app
Rack::URLMap.new({
'/' => unprotected_app,
'/protected' => protected_app
})
end
def protected_app_with_method_override
Rack::MethodOverride.new(protected_app)
end
setup do
@request = Rack::MockRequest.new(protected_app)
end
def request(method, path, headers = {}, &block)
response = @request.request(method, path, headers)
block.call(response) if block
return response
end
class MockDigestRequest
def initialize(params)
@params = params
end
def method_missing(sym)
if @params.has_key? k = sym.to_s
return @params[k]
end
super
end
def method
@params['method']
end
def response(password)
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
end
end
def request_with_digest_auth(method, path, username, password, options = {}, &block)
request_options = {}
request_options[:input] = options.delete(:input) if options.include? :input
response = request(method, path, request_options)
return response unless response.status == 401
if wait = options.delete(:wait)
sleep wait
end
challenge = response['WWW-Authenticate'].split(' ', 2).last
params = Rack::Auth::Digest::Params.parse(challenge)
params['username'] = username
params['nc'] = '00000001'
params['cnonce'] = 'nonsensenonce'
params['uri'] = path
params['method'] = method
params.update options
params['response'] = MockDigestRequest.new(params).response(password)
request(method, path, request_options.merge('HTTP_AUTHORIZATION' => "Digest #{params}"), &block)
end
def assert_digest_auth_challenge(response)
response.should.be.a.client_error
response.status.should.equal 401
response.should.include 'WWW-Authenticate'
response.headers['WWW-Authenticate'].should =~ /^Digest /
response.body.should.be.empty
end
def assert_bad_request(response)
response.should.be.a.client_error
response.status.should.equal 400
response.should.not.include 'WWW-Authenticate'
end
specify 'should challenge when no credentials are specified' do
request 'GET', '/' do |response|
assert_digest_auth_challenge response
end
end
specify 'should return application output if correct credentials given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should return application output if correct credentials given (hashed passwords)' do
@request = Rack::MockRequest.new(protected_app_with_hashed_passwords)
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should rechallenge if incorrect username given' do
request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response|
assert_digest_auth_challenge response
end
end
specify 'should rechallenge if incorrect password given' do
request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response|
assert_digest_auth_challenge response
end
end
specify 'should rechallenge with stale parameter if nonce is stale' do
begin
Rack::Auth::Digest::Nonce.time_limit = 1
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 2 do |response|
assert_digest_auth_challenge response
response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/
end
ensure
Rack::Auth::Digest::Nonce.time_limit = nil
end
end
specify 'should return 400 Bad Request if incorrect qop given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
assert_bad_request response
end
end
specify 'should return 400 Bad Request if incorrect uri given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
assert_bad_request response
end
end
specify 'should return 400 Bad Request if different auth scheme used' do
request 'GET', '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
assert_bad_request response
end
end
specify 'should not require credentials for unprotected path' do
@request = Rack::MockRequest.new(partially_protected_app)
request 'GET', '/' do |response|
response.should.be.ok
end
end
specify 'should challenge when no credentials are specified for protected path' do
@request = Rack::MockRequest.new(partially_protected_app)
request 'GET', '/protected' do |response|
assert_digest_auth_challenge response
end
end
specify 'should return application output if correct credentials given for protected path' do
@request = Rack::MockRequest.new(partially_protected_app)
request_with_digest_auth 'GET', '/protected', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should return application output if correct credentials given for POST' do
request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should return application output if correct credentials given for PUT (using method override of POST)' do
@request = Rack::MockRequest.new(protected_app_with_method_override)
request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'realm as optional constructor arg' do
app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true }
assert_equal realm, app.realm
end
end

View file

@ -0,0 +1,84 @@
require 'test/spec'
begin
# requires the ruby-openid gem
require 'rack/auth/openid'
context "Rack::Auth::OpenID" do
OID = Rack::Auth::OpenID
host = 'host'
subd = 'sub.host'
wild = '*.host'
path = 'path'
long = 'path/long'
scheme = 'http://'
realm = scheme+host+'/'+path
specify 'realm uri should be valid' do
lambda{OID.new('/'+path)}.should.raise ArgumentError
lambda{OID.new('/'+long)}.should.raise ArgumentError
lambda{OID.new(scheme+host)}.should.not.raise
lambda{OID.new(scheme+host+'/')}.should.not.raise
lambda{OID.new(scheme+host+'/'+path)}.should.not.raise
lambda{OID.new(scheme+subd)}.should.not.raise
lambda{OID.new(scheme+subd+'/')}.should.not.raise
lambda{OID.new(scheme+subd+'/'+path)}.should.not.raise
end
specify 'should be able to check if a uri is within the realm' do
end
specify 'return_to should be valid' do
uri = '/'+path
lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
uri = '/'+long
lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
uri = scheme+host
lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
uri = scheme+host+'/'+path
lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
uri = scheme+subd+'/'+path
lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
uri = scheme+host+'/'+long
lambda{OID.new(realm, :return_to=>uri)}.should.not.raise
uri = scheme+subd+'/'+long
lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError
end
specify 'extensions should have required constants defined' do
badext = Rack::Auth::OpenID::BadExtension
ext = Object.new
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext = Module.new
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Request = nil
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Response = nil
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::NS_URI = nil
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
end
specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do
$-w, w = nil, $-w # yuck
badext = Rack::Auth::OpenID::BadExtension
ext = Module.new
ext::Request = nil
ext::Response = nil
ext::NS_URI = nil
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Request = Class.new()
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Response = Class.new()
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Request = Class.new(::OpenID::Extension)
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
ext::Response = Class.new(::OpenID::Extension)
lambda{OID.new(realm).add_extension(ext)}.should.raise(badext)
$-w = w
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Auth::OpenID tests (ruby-openid 2 is required). `gem install ruby-openid` and try again."
end

View file

@ -0,0 +1,84 @@
require 'test/spec'
require 'rack/builder'
require 'rack/mock'
require 'rack/showexceptions'
require 'rack/auth/basic'
context "Rack::Builder" do
specify "chains apps by default" do
app = Rack::Builder.new do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end.to_app
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
specify "has implicit #to_app" do
app = Rack::Builder.new do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
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
specify "supports blocks on use" do
app = Rack::Builder.new do
use Rack::ShowExceptions
use Rack::Auth::Basic do |username, password|
'secret' == password
end
run lambda { |env| [200, {}, ['Hi Boss']] }
end
response = Rack::MockRequest.new(app).get("/")
response.should.be.client_error
response.status.should.equal 401
# with auth...
response = Rack::MockRequest.new(app).get("/",
'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Boss'
end
specify "has explicit #to_app" do
app = Rack::Builder.app do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
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
specify "apps are initialized once" do
app = Rack::Builder.new do
class AppClass
def initialize
@called = 0
end
def call(env)
raise "bzzzt" if @called > 0
@called += 1
[200, {'Content-Type' => 'text/plain'}, ['OK']]
end
end
use Rack::ShowExceptions
run AppClass.new
end
Rack::MockRequest.new(app).get("/").status.should.equal 200
Rack::MockRequest.new(app).get("/").should.be.server_error
end
end

View file

@ -0,0 +1,51 @@
require 'test/spec'
require 'stringio'
require 'uri'
begin
require 'rack/mock'
$-w, w = nil, $-w # yuck
require 'camping'
require 'rack/adapter/camping'
Camping.goes :CampApp
module CampApp
module Controllers
class HW < R('/')
def get
@headers["X-Served-By"] = URI("http://rack.rubyforge.org")
"Camping works!"
end
def post
"Data: #{input.foo}"
end
end
end
end
$-w = w
context "Rack::Adapter::Camping" do
specify "works with GET" do
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
get("/")
res.should.be.ok
res["Content-Type"].should.equal "text/html"
res["X-Served-By"].should.equal "http://rack.rubyforge.org"
res.body.should.equal "Camping works!"
end
specify "works with POST" do
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
post("/", :input => "foo=bar")
res.should.be.ok
res.body.should.equal "Data: bar"
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Adapter::Camping tests (Camping is required). `gem install camping` and try again."
end

View file

@ -0,0 +1,50 @@
require 'test/spec'
require 'rack/cascade'
require 'rack/mock'
require 'rack/urlmap'
require 'rack/file'
context "Rack::Cascade" do
docroot = File.expand_path(File.dirname(__FILE__))
app1 = Rack::File.new(docroot)
app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
app3 = Rack::URLMap.new("/foo" => lambda { |env|
[200, { "Content-Type" => "text/plain"}, [""]]})
specify "should dispatch onward on 404 by default" do
cascade = Rack::Cascade.new([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
end
specify "should dispatch onward on whatever is passed" do
cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
end
specify "should fail if empty" do
lambda { Rack::MockRequest.new(Rack::Cascade.new([])).get("/") }.
should.raise(ArgumentError)
end
specify "should append new app" do
cascade = Rack::Cascade.new([], [404, 403])
lambda { Rack::MockRequest.new(cascade).get('/cgi/test') }.
should.raise(ArgumentError)
cascade << app2
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found
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('/foo').should.be.not_found
cascade << app3
Rack::MockRequest.new(cascade).get('/foo').should.be.ok
end
end

View file

@ -0,0 +1,89 @@
require 'test/spec'
require 'testrequest'
context "Rack::Handler::CGI" do
include TestRequest::Helpers
setup do
@host = '0.0.0.0'
@port = 9203
end
# Keep this first.
specify "startup" do
$pid = fork {
Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
exec "lighttpd -D -f lighttpd.conf"
}
end
specify "should respond" do
sleep 1
lambda {
GET("/test")
}.should.not.raise
end
specify "should be a lighttpd" do
GET("/test")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /lighttpd/
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal @port.to_s
response["SERVER_NAME"].should =~ @host
end
specify "should have rack headers" do
GET("/test")
response["rack.version"].should.equal [1,0]
response["rack.multithread"].should.be false
response["rack.multiprocess"].should.be true
response["rack.run_once"].should.be true
end
specify "should have CGI headers on GET" do
GET("/test")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.equal "/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
# Keep this last.
specify "shutdown" do
Process.kill 15, $pid
Process.wait($pid).should.equal $pid
end
end

View file

@ -0,0 +1,62 @@
require 'rack/mock'
require 'rack/chunked'
require 'rack/utils'
context "Rack::Chunked" do
before do
@env = Rack::MockRequest.
env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
end
specify 'chunks responses with no Content-Length' do
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
response = Rack::MockResponse.new(*Rack::Chunked.new(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
specify 'chunks empty bodies properly' do
app = lambda { |env| [200, {}, []] }
response = Rack::MockResponse.new(*Rack::Chunked.new(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
specify 'does 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)
status.should.equal 200
headers.should.not.include 'Transfer-Encoding'
headers.should.include 'Content-Length'
body.join.should.equal 'Hello World!'
end
specify 'does not modify response when client is HTTP/1.0' do
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
@env['HTTP_VERSION'] = 'HTTP/1.0'
status, headers, body = Rack::Chunked.new(app).call(@env)
status.should.equal 200
headers.should.not.include 'Transfer-Encoding'
body.join.should.equal 'Hello World!'
end
specify 'does 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)
status.should.equal 200
headers['Transfer-Encoding'].should.equal 'identity'
body.join.should.equal 'Hello World!'
end
[100, 204, 304].each do |status_code|
specify "does not modify response when status code is #{status_code}" do
app = lambda { |env| [status_code, {}, []] }
status, headers, body = Rack::Chunked.new(app).call(@env)
status.should.equal status_code
headers.should.not.include 'Transfer-Encoding'
end
end
end

View file

@ -0,0 +1,32 @@
require 'test/spec'
require 'stringio'
require 'rack/commonlogger'
require 'rack/lobster'
require 'rack/mock'
context "Rack::CommonLogger" do
app = lambda { |env|
[200,
{"Content-Type" => "text/html"},
["foo"]]}
specify "should log to rack.errors by default" do
log = StringIO.new
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
res.errors.should.not.be.empty
res.errors.should =~ /GET /
res.errors.should =~ / 200 / # status
res.errors.should =~ / 3 / # length
end
specify "should log to anything with <<" do
log = ""
res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
log.should =~ /GET /
log.should =~ / 200 / # status
log.should =~ / 3 / # length
end
end

View file

@ -0,0 +1,41 @@
require 'test/spec'
require 'time'
require 'rack/mock'
require 'rack/conditionalget'
context "Rack::ConditionalGet" do
specify "should set a 304 status and truncate body when If-Modified-Since hits" do
timestamp = Time.now.httpdate
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Last-Modified'=>timestamp}, ['TEST']] })
response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
response.status.should.equal 304
response.body.should.be.empty
end
specify "should set a 304 status and truncate body when If-None-Match hits" do
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, ['TEST']] })
response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
response.status.should.equal 304
response.body.should.be.empty
end
specify "should not affect non-GET/HEAD requests" do
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, ['TEST']] })
response = Rack::MockRequest.new(app).
post("/", 'HTTP_IF_NONE_MATCH' => '1234')
response.status.should.equal 200
response.body.should.equal 'TEST'
end
end

View file

@ -0,0 +1,43 @@
require 'rack/mock'
require 'rack/content_length'
context "Rack::ContentLength" do
specify "sets Content-Length on String bodies if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '13'
end
specify "sets Content-Length on Array bodies if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '13'
end
specify "does not set Content-Length on variable length bodies" do
body = lambda { "Hello World!" }
def body.each ; yield call ; end
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.be.nil
end
specify "does not change Content-Length if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '1'
end
specify "does not set Content-Length on 304 responses" do
app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal nil
end
specify "does not set Content-Length when Transfer-Encoding is chunked" do
app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] }
response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal nil
end
end

View file

@ -0,0 +1,30 @@
require 'rack/mock'
require 'rack/content_type'
context "Rack::ContentType" do
specify "sets Content-Type to default text/html if none is set" do
app = lambda { |env| [200, {}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({})
headers['Content-Type'].should.equal 'text/html'
end
specify "sets Content-Type to chosen default if none is set" do
app = lambda { |env| [200, {}, "Hello, World!"] }
status, headers, body =
Rack::ContentType.new(app, 'application/octet-stream').call({})
headers['Content-Type'].should.equal 'application/octet-stream'
end
specify "does not change Content-Type if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({})
headers['Content-Type'].should.equal 'foo/bar'
end
specify "case insensitive detection of Content-Type" do
app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({})
headers.to_a.select { |k,v| k.downcase == "content-type" }.
should.equal [["CONTENT-Type","foo/bar"]]
end
end

View file

@ -0,0 +1,127 @@
require 'test/spec'
require 'rack/mock'
require 'rack/deflater'
require 'stringio'
require 'time' # for Time#httpdate
context "Rack::Deflater" do
def build_response(status, body, accept_encoding, headers = {})
body = [body] if body.respond_to? :to_str
app = lambda { |env| [status, {}, body] }
request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
response = Rack::Deflater.new(app).call(request)
return response
end
specify "should be able to deflate bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "deflate")
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "deflate",
"Vary" => "Accept-Encoding"
})
buf = ''
response[2].each { |part| buf << part }
buf.should.equal("K\313\317OJ,\002\000")
end
# TODO: This is really just a special case of the above...
specify "should be able to deflate String bodies" do
response = build_response(200, "Hello world!", "deflate")
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "deflate",
"Vary" => "Accept-Encoding"
})
buf = ''
response[2].each { |part| buf << part }
buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
end
specify "should be able to gzip bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "gzip")
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "gzip",
"Vary" => "Accept-Encoding",
})
buf = ''
response[2].each { |part| buf << part }
io = StringIO.new(buf)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("foobar")
gz.close
end
specify "should be able to fallback to no deflation" do
response = build_response(200, "Hello world!", "superzip")
response[0].should.equal(200)
response[1].should.equal({ "Vary" => "Accept-Encoding" })
response[2].should.equal(["Hello world!"])
end
specify "should be able to skip when there is no response entity body" do
response = build_response(304, [], "gzip")
response[0].should.equal(304)
response[1].should.equal({})
response[2].should.equal([])
end
specify "should handle the lack of an acceptable encoding" do
response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
response1[0].should.equal(406)
response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
response2[0].should.equal(406)
response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
end
specify "should handle gzip response with Last-Modified header" do
last_modified = Time.now.httpdate
app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] }
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
response = Rack::Deflater.new(app).call(request)
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "gzip",
"Vary" => "Accept-Encoding",
"Last-Modified" => last_modified
})
buf = ''
response[2].each { |part| buf << part }
io = StringIO.new(buf)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("Hello World!")
gz.close
end
specify "should do nothing when no-transform Cache-Control directive present" do
app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
response = Rack::Deflater.new(app).call(request)
response[0].should.equal(200)
response[1].should.not.include "Content-Encoding"
response[2].join.should.equal("Hello World!")
end
end

View file

@ -0,0 +1,61 @@
require 'test/spec'
require 'rack/directory'
require 'rack/lint'
require 'rack/mock'
context "Rack::Directory" do
DOCROOT = File.expand_path(File.dirname(__FILE__))
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
app = Rack::Directory.new DOCROOT, FILE_CATCH
specify "serves directory indices" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/")
res.should.be.ok
res.should =~ /<html><head>/
end
specify "passes to app if file found" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/test")
res.should.be.ok
res.should =~ /passed!/
end
specify "serves uri with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/%63%67%69/") # "/cgi/test"
res.should.be.ok
res.should =~ /<html><head>/
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/%74%65%73%74") # "/cgi/test"
res.should.be.ok
res.should =~ /passed!/
end
specify "does not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/../test")
res.should.be.forbidden
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/%2E%2E/test")
res.should.be.forbidden
end
specify "404s if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/blubb")
res.should.be.not_found
end
end

View file

@ -0,0 +1,89 @@
require 'test/spec'
require 'testrequest'
context "Rack::Handler::FastCGI" do
include TestRequest::Helpers
setup do
@host = '0.0.0.0'
@port = 9203
end
# Keep this first.
specify "startup" do
$pid = fork {
Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
exec "lighttpd -D -f lighttpd.conf"
}
end
specify "should respond" do
sleep 1
lambda {
GET("/test.fcgi")
}.should.not.raise
end
specify "should be a lighttpd" do
GET("/test.fcgi")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /lighttpd/
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal @port.to_s
response["SERVER_NAME"].should =~ @host
end
specify "should have rack headers" do
GET("/test.fcgi")
response["rack.version"].should.equal [1,0]
response["rack.multithread"].should.be false
response["rack.multiprocess"].should.be true
response["rack.run_once"].should.be false
end
specify "should have CGI headers on GET" do
GET("/test.fcgi")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test.fcgi"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test.fcgi/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test.fcgi"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.equal "/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["SCRIPT_NAME"].should.equal "/test.fcgi"
response["REQUEST_PATH"].should.equal "/"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test.fcgi?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
# Keep this last.
specify "shutdown" do
Process.kill 15, $pid
Process.wait($pid).should.equal $pid
end
end

View file

@ -0,0 +1,75 @@
require 'test/spec'
require 'rack/file'
require 'rack/lint'
require 'rack/mock'
context "Rack::File" do
DOCROOT = File.expand_path(File.dirname(__FILE__))
specify "serves files" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test")
res.should.be.ok
res.should =~ /ruby/
end
specify "sets Last-Modified header" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test")
path = File.join(DOCROOT, "/cgi/test")
res.should.be.ok
res["Last-Modified"].should.equal File.mtime(path).httpdate
end
specify "serves 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"
res.should.be.ok
res.should =~ /ruby/
end
specify "does not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/../test")
res.should.be.forbidden
end
specify "does not allow 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
specify "404s if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/blubb")
res.should.be.not_found
end
specify "detects SystemCallErrors" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi")
res.should.be.not_found
end
specify "returns bodies that respond to #to_path" do
env = Rack::MockRequest.env_for("/cgi/test")
status, headers, body = Rack::File.new(DOCROOT).call(env)
path = File.join(DOCROOT, "/cgi/test")
status.should.equal 200
body.should.respond_to :to_path
body.to_path.should.equal path
end
end

View file

@ -0,0 +1,43 @@
require 'test/spec'
require 'rack/handler'
class Rack::Handler::Lobster; end
class RockLobster; end
context "Rack::Handler" do
specify "has registered default handlers" do
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
end
specify "handler that doesn't exist should raise a NameError" do
lambda {
Rack::Handler.get('boom')
}.should.raise(NameError)
end
specify "should get unregistered, but already required, handler by name" do
Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
end
specify "should register custom handler" do
Rack::Handler.register('rock_lobster', 'RockLobster')
Rack::Handler.get('rock_lobster').should.equal RockLobster
end
specify "should not need registration for properly coded handlers even if not already required" do
begin
$:.push "test/unregistered_handler"
Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
lambda {
Rack::Handler.get('UnRegistered')
}.should.raise(NameError)
Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
ensure
$:.delete "test/unregistered_handler"
end
end
end

View file

@ -0,0 +1,30 @@
require 'rack/head'
require 'rack/mock'
context "Rack::Head" do
def test_response(headers = {})
app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
request = Rack::MockRequest.env_for("/", headers)
response = Rack::Head.new(app).call(request)
return response
end
specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
%w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
resp = test_response("REQUEST_METHOD" => type)
resp[0].should.equal(200)
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
resp[2].should.equal(["foo"])
end
end
specify "removes body from HEAD requests" do
resp = test_response("REQUEST_METHOD" => "HEAD")
resp[0].should.equal(200)
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
resp[2].should.equal([])
end
end

View file

@ -0,0 +1,521 @@
require 'test/spec'
require 'stringio'
require 'rack/lint'
require 'rack/mock'
context "Rack::Lint" do
def env(*args)
Rack::MockRequest.env_for("/", *args)
end
specify "passes valid request" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
}).call(env({}))
}.should.not.raise
end
specify "notices fatal errors" do
lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
message.should.match(/No env given/)
end
specify "notices environment errors" do
lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
message.should.match(/not a Hash/)
lambda {
e = env
e.delete("REQUEST_METHOD")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/missing required key REQUEST_METHOD/)
lambda {
e = env
e.delete("SERVER_NAME")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/missing required key SERVER_NAME/)
lambda {
Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/contains HTTP_CONTENT_TYPE/)
lambda {
Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/contains HTTP_CONTENT_LENGTH/)
lambda {
Rack::Lint.new(nil).call(env("FOO" => Object.new))
}.should.raise(Rack::Lint::LintError).
message.should.match(/non-string value/)
lambda {
Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be an Array/)
lambda {
Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/url_scheme unknown/)
lambda {
Rack::Lint.new(nil).call(env("rack.session" => []))
}.should.raise(Rack::Lint::LintError).
message.should.equal("session [] must respond to store and []=")
lambda {
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/REQUEST_METHOD/)
lambda {
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must start with/)
lambda {
Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must start with/)
lambda {
Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Invalid CONTENT_LENGTH/)
lambda {
e = env
e.delete("PATH_INFO")
e.delete("SCRIPT_NAME")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/One of .* must be set/)
lambda {
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/cannot be .* make it ''/)
end
specify "notices input errors" do
lambda {
Rack::Lint.new(nil).call(env("rack.input" => ""))
}.should.raise(Rack::Lint::LintError).
message.should.match(/does not respond to #gets/)
lambda {
input = Object.new
def input.binmode?
false
end
Rack::Lint.new(nil).call(env("rack.input" => input))
}.should.raise(Rack::Lint::LintError).
message.should.match(/is not opened in binary mode/)
lambda {
input = Object.new
def input.external_encoding
result = Object.new
def result.name
"US-ASCII"
end
result
end
Rack::Lint.new(nil).call(env("rack.input" => input))
}.should.raise(Rack::Lint::LintError).
message.should.match(/does not have ASCII-8BIT as its external encoding/)
end
specify "notices error errors" do
lambda {
Rack::Lint.new(nil).call(env("rack.errors" => ""))
}.should.raise(Rack::Lint::LintError).
message.should.match(/does not respond to #puts/)
end
specify "notices status errors" do
lambda {
Rack::Lint.new(lambda { |env|
["cc", {}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be >=100 seen as integer/)
lambda {
Rack::Lint.new(lambda { |env|
[42, {}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be >=100 seen as integer/)
end
specify "notices header errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, Object.new, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
lambda {
Rack::Lint.new(lambda { |env|
[200, {true=>false}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("header key must be a string, was TrueClass")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Status" => "404"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not contain Status/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-Type:" => "text/plain"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not contain :/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-" => "text/plain"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not end/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"..%%quark%%.." => "text/plain"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("invalid header name: ..%%quark%%..")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo" => Object.new}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo" => [1, 2, 3]}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo-Bar" => "text\000plain"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/invalid header/)
# line ends (010) should be allowed in header values.
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
}).call(env({}))
}.should.not.raise(Rack::Lint::LintError)
end
specify "notices content-type errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/No Content-Type/)
[100, 101, 204, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Type header found/)
end
end
specify "notices content-length errors" do
[100, 101, 204, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header found/)
end
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header was 1, but should be 0/)
end
specify "notices body errors" do
lambda {
status, header, body = Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
}).call(env({}))
body.each { |part| }
}.should.raise(Rack::Lint::LintError).
message.should.match(/yielded non-string/)
end
specify "notices input handling errors" do
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].gets("\r\n")
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/gets called with arguments/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(1, 2, 3)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with too many arguments/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read("foo")
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with non-integer and non-nil length/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(-1)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with a negative length/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil, nil)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with non-String buffer/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil, 1)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with non-String buffer/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].rewind(0)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/rewind called with arguments/)
weirdio = Object.new
class << weirdio
def gets
42
end
def read
23
end
def each
yield 23
yield 42
end
def rewind
raise Errno::ESPIPE, "Errno::ESPIPE"
end
end
eof_weirdio = Object.new
class << eof_weirdio
def gets
nil
end
def read(*args)
nil
end
def each
end
def rewind
end
end
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].gets
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/gets didn't return a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].each { |x| }
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/each didn't yield a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read didn't return nil or a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env("rack.input" => eof_weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read\(nil\) returned nil on EOF/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].rewind
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/rewind raised Errno::ESPIPE/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].close
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/close must not be called/)
end
specify "notices error handling errors" do
lambda {
Rack::Lint.new(lambda { |env|
env["rack.errors"].write(42)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/write not called with a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.errors"].close
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/close must not be called/)
end
specify "notices HEAD errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
}).call(env({"REQUEST_METHOD" => "HEAD"}))
}.should.not.raise
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
}).call(env({"REQUEST_METHOD" => "HEAD"}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/body was given for HEAD/)
end
specify "passes valid read calls" do
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(0)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(1)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil, '')
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read(1, '')
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")}))
}.should.not.raise(Rack::Lint::LintError)
end
end
context "Rack::Lint::InputWrapper" do
specify "delegates :size to underlying IO object" do
class IOMock
def size
101
end
end
wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
wrapper.size.should == 101
end
specify "delegates :rewind to underlying IO object" do
io = StringIO.new("123")
wrapper = Rack::Lint::InputWrapper.new(io)
wrapper.read.should.equal "123"
wrapper.read.should.equal ""
wrapper.rewind
wrapper.read.should.equal "123"
end
end

View file

@ -0,0 +1,45 @@
require 'test/spec'
require 'rack/lobster'
require 'rack/mock'
context "Rack::Lobster::LambdaLobster" do
specify "should be a single lambda" do
Rack::Lobster::LambdaLobster.should.be.kind_of Proc
end
specify "should look like a lobster" do
res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/")
res.should.be.ok
res.body.should.include "(,(,,(,,,("
res.body.should.include "?flip"
end
specify "should be flippable" do
res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/?flip")
res.should.be.ok
res.body.should.include "(,,,(,,(,("
end
end
context "Rack::Lobster" do
specify "should look like a lobster" do
res = Rack::MockRequest.new(Rack::Lobster.new).get("/")
res.should.be.ok
res.body.should.include "(,(,,(,,,("
res.body.should.include "?flip"
res.body.should.include "crash"
end
specify "should be flippable" do
res = Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=left")
res.should.be.ok
res.body.should.include "(,,,(,,(,("
end
specify "should provide crashing for testing purposes" do
lambda {
Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=crash")
}.should.raise
end
end

View file

@ -0,0 +1,38 @@
require 'test/spec'
require 'rack/mock'
require 'rack/lock'
context "Rack::Lock" do
class Lock
attr_reader :synchronized
def initialize
@synchronized = false
end
def synchronize
@synchronized = true
yield
end
end
specify "should call synchronize on lock" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
app = Rack::Lock.new(lambda { |env| }, lock)
lock.synchronized.should.equal false
app.call(env)
lock.synchronized.should.equal true
end
specify "should set multithread flag to false" do
app = Rack::Lock.new(lambda { |env| env['rack.multithread'] })
app.call(Rack::MockRequest.env_for("/")).should.equal false
end
specify "should reset original multithread flag when exiting lock" do
app = Rack::Lock.new(lambda { |env| env })
app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true
end
end

View file

@ -0,0 +1,60 @@
require 'test/spec'
require 'rack/mock'
require 'rack/methodoverride'
require 'stringio'
context "Rack::MethodOverride" do
specify "should not affect GET requests" do
env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "GET"
end
specify "_method parameter should modify REQUEST_METHOD for POST requests" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "PUT"
end
specify "X-HTTP-Method-Override header should modify REQUEST_METHOD for POST requests" do
env = Rack::MockRequest.env_for("/",
:method => "POST",
"HTTP_X_HTTP_METHOD_OVERRIDE" => "PUT"
)
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "PUT"
end
specify "should not modify REQUEST_METHOD if the method is unknown" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "POST"
end
specify "should not modify REQUEST_METHOD when _method is nil" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "POST"
end
specify "should store the original REQUEST_METHOD prior to overriding" do
env = Rack::MockRequest.env_for("/",
:method => "POST",
:input => "_method=options")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
req = app.call(env)
req.env["rack.methodoverride.original_method"].should.equal "POST"
end
end

View file

@ -0,0 +1,157 @@
require 'yaml'
require 'rack/mock'
require 'rack/request'
require 'rack/response'
app = lambda { |env|
req = Rack::Request.new(env)
env["mock.postdata"] = env["rack.input"].read
if req.GET["error"]
env["rack.errors"].puts req.GET["error"]
env["rack.errors"].flush
end
Rack::Response.new(env.to_yaml,
req.GET["status"] || 200,
"Content-Type" => "text/yaml").finish
}
context "Rack::MockRequest" do
specify "should return a MockResponse" do
res = Rack::MockRequest.new(app).get("")
res.should.be.kind_of Rack::MockResponse
end
specify "should be able to only return the environment" do
env = Rack::MockRequest.env_for("")
env.should.be.kind_of Hash
env.should.include "rack.version"
end
specify "should provide sensible defaults" do
res = Rack::MockRequest.new(app).request
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["SERVER_NAME"].should.equal "example.org"
env["SERVER_PORT"].should.equal "80"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/"
env["SCRIPT_NAME"].should.equal ""
env["rack.url_scheme"].should.equal "http"
env["mock.postdata"].should.be.empty
end
specify "should allow GET/POST/PUT/DELETE" do
res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
res = Rack::MockRequest.new(app).post("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST"
res = Rack::MockRequest.new(app).put("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "PUT"
res = Rack::MockRequest.new(app).delete("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "DELETE"
Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
should.equal "OPTIONS"
end
specify "should set content length" do
env = Rack::MockRequest.env_for("/", :input => "foo")
env["CONTENT_LENGTH"].should.equal "3"
end
specify "should allow posting" do
res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body)
env["mock.postdata"].should.equal "foo"
res = Rack::MockRequest.new(app).post("", :input => StringIO.new("foo"))
env = YAML.load(res.body)
env["mock.postdata"].should.equal "foo"
end
specify "should use all parts of an URL" do
res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar")
res.should.be.kind_of Rack::MockResponse
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["SERVER_NAME"].should.equal "bla.example.org"
env["SERVER_PORT"].should.equal "9292"
env["QUERY_STRING"].should.equal "bar"
env["PATH_INFO"].should.equal "/meh/foo"
env["rack.url_scheme"].should.equal "https"
end
specify "should behave valid according to the Rack spec" do
lambda {
res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar", :lint => true)
}.should.not.raise(Rack::Lint::LintError)
end
end
context "Rack::MockResponse" do
specify "should provide access to the HTTP status" do
res = Rack::MockRequest.new(app).get("")
res.should.be.successful
res.should.be.ok
res = Rack::MockRequest.new(app).get("/?status=404")
res.should.not.be.successful
res.should.be.client_error
res.should.be.not_found
res = Rack::MockRequest.new(app).get("/?status=501")
res.should.not.be.successful
res.should.be.server_error
res = Rack::MockRequest.new(app).get("/?status=307")
res.should.be.redirect
res = Rack::MockRequest.new(app).get("/?status=201", :lint => true)
res.should.be.empty
end
specify "should provide access to the HTTP headers" do
res = Rack::MockRequest.new(app).get("")
res.should.include "Content-Type"
res.headers["Content-Type"].should.equal "text/yaml"
res.original_headers["Content-Type"].should.equal "text/yaml"
res["Content-Type"].should.equal "text/yaml"
res.content_type.should.equal "text/yaml"
res.content_length.should.be 401 # needs change often.
res.location.should.be.nil
end
specify "should provide access to the HTTP body" do
res = Rack::MockRequest.new(app).get("")
res.body.should =~ /rack/
res.should =~ /rack/
res.should.match(/rack/)
res.should.satisfy { |r| r.match(/rack/) }
end
specify "should provide access to the Rack errors" do
res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true)
res.should.be.ok
res.errors.should.not.be.empty
res.errors.should.include "foo"
end
specify "should optionally make Rack errors fatal" do
lambda {
Rack::MockRequest.new(app).get("/?error=foo", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning)
end
end

View file

@ -0,0 +1,189 @@
require 'test/spec'
begin
require 'rack/handler/mongrel'
require 'rack/urlmap'
require 'rack/lint'
require 'testrequest'
require 'timeout'
Thread.abort_on_exception = true
$tcp_defer_accept_opts = nil
$tcp_cork_opts = nil
context "Rack::Handler::Mongrel" do
include TestRequest::Helpers
setup do
server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
server.register('/test',
Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
server.register('/stream',
Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest)))
@acc = server.run
end
specify "should respond" do
lambda {
GET("/test")
}.should.not.raise
end
specify "should be a Mongrel" do
GET("/test")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /Mongrel/
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"
end
specify "should have rack headers" do
GET("/test")
response["rack.version"].should.equal [1,0]
response["rack.multithread"].should.be true
response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false
end
specify "should have CGI headers on GET" do
GET("/test")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test"
response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test/foo"
response["PATH_INFO"].should.equal "/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
specify "should provide a .run" do
block_ran = false
Thread.new {
Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
server.should.be.kind_of Mongrel::HttpServer
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a hash" do
block_ran = false
Thread.new {
map = {'/'=>lambda{},'/foo'=>lambda{}}
Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/foo'
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a urlmap" do
block_ran = false
Thread.new {
map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/bar'
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a urlmap restricting by host" do
block_ran = false
Thread.new {
map = Rack::URLMap.new({
'/' => lambda{},
'/foo' => lambda{},
'/bar' => lambda{},
'http://localhost/' => lambda{},
'http://localhost/bar' => lambda{},
'http://falsehost/arf' => lambda{},
'http://falsehost/qux' => lambda{}
})
opt = {:map => true, :Port => 9241, :Host => 'localhost'}
Rack::Handler::Mongrel.run(map, opt) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.should.include '/'
server.classifier.handler_map['/'].size.should.be 2
server.classifier.uris.should.include '/foo'
server.classifier.handler_map['/foo'].size.should.be 1
server.classifier.uris.should.include '/bar'
server.classifier.handler_map['/bar'].size.should.be 2
server.classifier.uris.should.not.include '/qux'
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.size.should.be 3
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should stream #each part of the response" do
body = ''
begin
Timeout.timeout(1) do
Net::HTTP.start(@host, @port) do |http|
get = Net::HTTP::Get.new('/stream')
http.request(get) do |response|
response.read_body { |part| body << part }
end
end
end
rescue Timeout::Error
end
body.should.not.be.empty
end
teardown do
@acc.raise Mongrel::StopServer
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again."
end

View file

@ -0,0 +1,77 @@
require 'test/spec'
require 'rack/recursive'
require 'rack/urlmap'
require 'rack/response'
require 'rack/mock'
context "Rack::Recursive" do
setup do
@app1 = lambda { |env|
res = Rack::Response.new
res["X-Path-Info"] = env["PATH_INFO"]
res["X-Query-String"] = env["QUERY_STRING"]
res.finish do |res|
res.write "App1"
end
}
@app2 = lambda { |env|
Rack::Response.new.finish do |res|
res.write "App2"
_, _, body = env['rack.recursive.include'].call(env, "/app1")
body.each { |b|
res.write b
}
end
}
@app3 = lambda { |env|
raise Rack::ForwardRequest.new("/app1")
}
@app4 = lambda { |env|
raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh")
}
end
specify "should allow for subrequests" do
res = Rack::MockRequest.new(Rack::Recursive.new(
Rack::URLMap.new("/app1" => @app1,
"/app2" => @app2))).
get("/app2")
res.should.be.ok
res.body.should.equal "App2App1"
end
specify "should raise error on requests not below the app" do
app = Rack::URLMap.new("/app1" => @app1,
"/app" => Rack::Recursive.new(
Rack::URLMap.new("/1" => @app1,
"/2" => @app2)))
lambda {
Rack::MockRequest.new(app).get("/app/2")
}.should.raise(ArgumentError).
message.should =~ /can only include below/
end
specify "should support forwarding" do
app = Rack::Recursive.new(Rack::URLMap.new("/app1" => @app1,
"/app3" => @app3,
"/app4" => @app4))
res = Rack::MockRequest.new(app).get("/app3")
res.should.be.ok
res.body.should.equal "App1"
res = Rack::MockRequest.new(app).get("/app4")
res.should.be.ok
res.body.should.equal "App1"
res["X-Path-Info"].should.equal "/quux"
res["X-Query-String"].should.equal "meh"
end
end

View file

@ -0,0 +1,504 @@
require 'test/spec'
require 'stringio'
require 'rack/request'
require 'rack/mock'
context "Rack::Request" do
specify "wraps the rack variables" do
req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/"))
req.body.should.respond_to? :gets
req.scheme.should.equal "http"
req.request_method.should.equal "GET"
req.should.be.get
req.should.not.be.post
req.should.not.be.put
req.should.not.be.delete
req.should.not.be.head
req.script_name.should.equal ""
req.path_info.should.equal "/"
req.query_string.should.equal ""
req.host.should.equal "example.com"
req.port.should.equal 8080
req.content_length.should.equal "0"
req.content_type.should.be.nil
end
specify "can figure out the correct host" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
req.host.should.equal "www2.example.org"
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org:9292")
req.host.should.equal "example.org"
env = Rack::MockRequest.env_for("/")
env.delete("SERVER_NAME")
req = Rack::Request.new(env)
req.host.should.equal ""
end
specify "can parse the query string" do
req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla"))
req.query_string.should.equal "foo=bar&quux=bla"
req.GET.should.equal "foo" => "bar", "quux" => "bla"
req.POST.should.be.empty
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can parse POST data" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla")
req.content_type.should.be.nil
req.media_type.should.be.nil
req.query_string.should.equal "foo=quux"
req.GET.should.equal "foo" => "quux"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can parse POST data with explicit content type" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
:input => "foo=bar&quux=bla")
req.content_type.should.equal 'application/x-www-form-urlencoded;foo=bar'
req.media_type.should.equal 'application/x-www-form-urlencoded'
req.media_type_params['foo'].should.equal 'bar'
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "does not parse POST data when media type is not form-data" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux",
"CONTENT_TYPE" => 'text/plain;charset=utf-8',
:input => "foo=bar&quux=bla")
req.content_type.should.equal 'text/plain;charset=utf-8'
req.media_type.should.equal 'text/plain'
req.media_type_params['charset'].should.equal 'utf-8'
req.POST.should.be.empty
req.params.should.equal "foo" => "quux"
req.body.read.should.equal "foo=bar&quux=bla"
end
specify "rewinds input after parsing POST data" do
input = StringIO.new("foo=bar&quux=bla")
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
:input => input)
req.params.should.equal "foo" => "bar", "quux" => "bla"
input.read.should.equal "foo=bar&quux=bla"
end
specify "cleans up Safari's ajax POST body" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", :input => "foo=bar&quux=bla\0")
req.POST.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can get value by key from params with #[]" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=quux")
req['foo'].should.equal 'quux'
req[:foo].should.equal 'quux'
end
specify "can set value to key on params with #[]=" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=duh")
req['foo'].should.equal 'duh'
req[:foo].should.equal 'duh'
req.params.should.equal 'foo' => 'duh'
req['foo'] = 'bar'
req.params.should.equal 'foo' => 'bar'
req['foo'].should.equal 'bar'
req[:foo].should.equal 'bar'
req[:foo] = 'jaz'
req.params.should.equal 'foo' => 'jaz'
req['foo'].should.equal 'jaz'
req[:foo].should.equal 'jaz'
end
specify "values_at answers values by keys in order given" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful")
req.values_at('foo').should.equal ['baz']
req.values_at('foo', 'wun').should.equal ['baz', 'der']
req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der']
end
specify "referrer should be extracted correct" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path")
req.referer.should.equal "/some/path"
req = Rack::Request.new \
Rack::MockRequest.env_for("/")
req.referer.should.equal "/"
end
specify "can cache, but invalidates the cache" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla")
req.GET.should.equal "foo" => "quux"
req.GET.should.equal "foo" => "quux"
req.env["QUERY_STRING"] = "bla=foo"
req.GET.should.equal "bla" => "foo"
req.GET.should.equal "bla" => "foo"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.env["rack.input"] = StringIO.new("foo=bla&quux=bar")
req.POST.should.equal "foo" => "bla", "quux" => "bar"
req.POST.should.equal "foo" => "bla", "quux" => "bar"
end
specify "can figure out if called via XHR" do
req = Rack::Request.new(Rack::MockRequest.env_for(""))
req.should.not.be.xhr
req = Rack::Request.new \
Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
req.should.be.xhr
end
specify "can parse cookies" do
req = Rack::Request.new \
Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
req.cookies.should.equal "foo" => "bar", "quux" => "h&m"
req.cookies.should.equal "foo" => "bar", "quux" => "h&m"
req.env.delete("HTTP_COOKIE")
req.cookies.should.equal({})
end
specify "parses cookies according to RFC 2109" do
req = Rack::Request.new \
Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
req.cookies.should.equal 'foo' => 'bar'
end
specify "provides setters" do
req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
req.script_name.should.equal ""
req.script_name = "/foo"
req.script_name.should.equal "/foo"
e["SCRIPT_NAME"].should.equal "/foo"
req.path_info.should.equal "/"
req.path_info = "/foo"
req.path_info.should.equal "/foo"
e["PATH_INFO"].should.equal "/foo"
end
specify "provides the original env" do
req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
req.env.should.be e
end
specify "can restore the URL" do
Rack::Request.new(Rack::MockRequest.env_for("")).url.
should.equal "http://example.org/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url.
should.equal "http://example.org/foo/"
Rack::Request.new(Rack::MockRequest.env_for("/foo")).url.
should.equal "http://example.org/foo"
Rack::Request.new(Rack::MockRequest.env_for("?foo")).url.
should.equal "http://example.org/?foo"
Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).url.
should.equal "http://example.org:8080/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).url.
should.equal "https://example.org/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url.
should.equal "https://example.com:8080/foo?foo"
end
specify "can restore the full path" do
Rack::Request.new(Rack::MockRequest.env_for("")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath.
should.equal "/foo/"
Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath.
should.equal "/foo"
Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath.
should.equal "/?foo"
Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath.
should.equal "/foo?foo"
end
specify "can handle multiple media type parameters" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam')
req.should.not.be.form_data
req.media_type_params.should.include 'foo'
req.media_type_params['foo'].should.equal 'BAR'
req.media_type_params.should.include 'baz'
req.media_type_params['baz'].should.equal 'bizzle dizzle'
req.media_type_params.should.not.include 'BLING'
req.media_type_params.should.include 'bling'
req.media_type_params['bling'].should.equal 'bam'
end
specify "can parse multipart form data" do
# Adapted from RFC 1867.
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="reply"\r
\r
yes\r
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
Content-Type: image/jpeg\r
Content-Transfer-Encoding: base64\r
\r
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
--AaB03x--\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
req.POST.should.include "fileupload"
req.POST.should.include "reply"
req.should.be.form_data
req.content_length.should.equal input.size
req.media_type.should.equal 'multipart/form-data'
req.media_type_params.should.include 'boundary'
req.media_type_params['boundary'].should.equal 'AaB03x'
req.POST["reply"].should.equal "yes"
f = req.POST["fileupload"]
f.should.be.kind_of Hash
f[:type].should.equal "image/jpeg"
f[:filename].should.equal "dj.jpg"
f.should.include :tempfile
f[:tempfile].size.should.equal 76
end
specify "can parse big multipart form data" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
#{"x"*32768}\r
--AaB03x\r
content-disposition: form-data; name="mean"; filename="mean"\r
\r
--AaB03xha\r
--AaB03x--\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
req.POST["huge"][:tempfile].size.should.equal 32768
req.POST["mean"][:tempfile].size.should.equal 10
req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
end
specify "can detect invalid multipart form data" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
lambda { req.POST }.should.raise(EOFError)
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
lambda { req.POST }.should.raise(EOFError)
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
lambda { req.POST }.should.raise(EOFError)
end
specify "shouldn't try to interpret binary as utf8" do
begin
original_kcode = $KCODE
$KCODE='UTF8'
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="junk.a"\r
content-type: application/octet-stream\r
\r
#{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
--AaB03x--\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
lambda{req.POST}.should.not.raise(EOFError)
req.POST["fileupload"][:tempfile].size.should.equal 4
ensure
$KCODE = original_kcode
end
end
specify "should work around buggy 1.8.* Tempfile equality" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\r
--AaB03x--
EOF
rack_input = Tempfile.new("rackspec")
rack_input.write(input)
rack_input.rewind
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => rack_input)
lambda {req.POST}.should.not.raise
lambda {req.POST}.should.blaming("input re-processed!").not.raise
end
specify "does conform to the Rack spec" do
app = lambda { |env|
content = Rack::Request.new(env).POST["file"].inspect
size = content.respond_to?(:bytesize) ? content.bytesize : content.size
[200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]]
}
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="reply"\r
\r
yes\r
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
Content-Type: image/jpeg\r
Content-Transfer-Encoding: base64\r
\r
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
--AaB03x--\r
EOF
res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input)
res.should.be.ok
end
specify "should parse Accept-Encoding correctly" do
parser = lambda do |x|
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding
end
parser.call(nil).should.equal([])
parser.call("compress, gzip").should.equal([["compress", 1.0], ["gzip", 1.0]])
parser.call("").should.equal([])
parser.call("*").should.equal([["*", 1.0]])
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)
end
specify '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(app))
res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
res.body.should.equal '123.123.123.123'
res = mock.get '/',
'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
res.body.should.equal '234.234.234.234'
res = mock.get '/',
'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
res.body.should.equal '212.212.212.212'
end
class MyRequest < Rack::Request
def params
{:foo => "bar"}
end
end
specify "should allow subclass request to be instantiated after parent request" do
env = Rack::MockRequest.env_for("/?foo=bar")
req1 = Rack::Request.new(env)
req1.GET.should.equal "foo" => "bar"
req1.params.should.equal "foo" => "bar"
req2 = MyRequest.new(env)
req2.GET.should.equal "foo" => "bar"
req2.params.should.equal :foo => "bar"
end
specify "should allow parent request to be instantiated after subclass request" do
env = Rack::MockRequest.env_for("/?foo=bar")
req1 = MyRequest.new(env)
req1.GET.should.equal "foo" => "bar"
req1.params.should.equal :foo => "bar"
req2 = Rack::Request.new(env)
req2.GET.should.equal "foo" => "bar"
req2.params.should.equal "foo" => "bar"
end
end

View file

@ -0,0 +1,218 @@
require 'test/spec'
require 'set'
require 'rack/response'
context "Rack::Response" do
specify "has sensible default values" do
response = Rack::Response.new
status, header, body = response.finish
status.should.equal 200
header.should.equal "Content-Type" => "text/html"
body.each { |part|
part.should.equal ""
}
response = Rack::Response.new
status, header, body = *response
status.should.equal 200
header.should.equal "Content-Type" => "text/html"
body.each { |part|
part.should.equal ""
}
end
specify "can be written to" do
response = Rack::Response.new
status, header, body = response.finish do
response.write "foo"
response.write "bar"
response.write "baz"
end
parts = []
body.each { |part| parts << part }
parts.should.equal ["foo", "bar", "baz"]
end
specify "can set and read headers" do
response = Rack::Response.new
response["Content-Type"].should.equal "text/html"
response["Content-Type"] = "text/plain"
response["Content-Type"].should.equal "text/plain"
end
specify "can set cookies" do
response = Rack::Response.new
response.set_cookie "foo", "bar"
response["Set-Cookie"].should.equal "foo=bar"
response.set_cookie "foo2", "bar2"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
response.set_cookie "foo3", "bar3"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
end
specify "formats the Cookie expiration date accordingly to RFC 2109" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
response["Set-Cookie"].should.match(
/expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
end
specify "can set secure cookies" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :secure => true}
response["Set-Cookie"].should.equal "foo=bar; secure"
end
specify "can set http only cookies" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :httponly => true}
response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
end
specify "can delete cookies" do
response = Rack::Response.new
response.set_cookie "foo", "bar"
response.set_cookie "foo2", "bar2"
response.delete_cookie "foo"
response["Set-Cookie"].should.equal ["foo2=bar2",
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
end
specify "can do redirects" do
response = Rack::Response.new
response.redirect "/foo"
status, header, body = response.finish
status.should.equal 302
header["Location"].should.equal "/foo"
response = Rack::Response.new
response.redirect "/foo", 307
status, header, body = response.finish
status.should.equal 307
end
specify "has a useful constructor" do
r = Rack::Response.new("foo")
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foo"
r = Rack::Response.new(["foo", "bar"])
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foobar"
r = Rack::Response.new(["foo", "bar"].to_set)
r.write "foo"
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foobarfoo"
r = Rack::Response.new([], 500)
r.status.should.equal 500
end
specify "has a constructor that can take a block" do
r = Rack::Response.new { |res|
res.status = 404
res.write "foo"
}
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foo"
status.should.equal 404
end
specify "doesn't return invalid responses" do
r = Rack::Response.new(["foo", "bar"], 204)
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.be.empty
header["Content-Type"].should.equal nil
lambda {
Rack::Response.new(Object.new)
}.should.raise(TypeError).
message.should =~ /stringable or iterable required/
end
specify "knows if it's empty" do
r = Rack::Response.new
r.should.be.empty
r.write "foo"
r.should.not.be.empty
r = Rack::Response.new
r.should.be.empty
r.finish
r.should.be.empty
r = Rack::Response.new
r.should.be.empty
r.finish { }
r.should.not.be.empty
end
specify "should provide access to the HTTP status" do
res = Rack::Response.new
res.status = 200
res.should.be.successful
res.should.be.ok
res.status = 404
res.should.not.be.successful
res.should.be.client_error
res.should.be.not_found
res.status = 501
res.should.not.be.successful
res.should.be.server_error
res.status = 307
res.should.be.redirect
end
specify "should provide access to the HTTP headers" do
res = Rack::Response.new
res["Content-Type"] = "text/yaml"
res.should.include "Content-Type"
res.headers["Content-Type"].should.equal "text/yaml"
res["Content-Type"].should.equal "text/yaml"
res.content_type.should.equal "text/yaml"
res.content_length.should.be.nil
res.location.should.be.nil
end
specify "does not add or change Content-Length when #finish()ing" do
res = Rack::Response.new
res.status = 200
res.finish
res.headers["Content-Length"].should.be.nil
res = Rack::Response.new
res.status = 200
res.headers["Content-Length"] = "10"
res.finish
res.headers["Content-Length"].should.equal "10"
end
specify "updates Content-Length when body appended to using #write" do
res = Rack::Response.new
res.status = 200
res.headers["Content-Length"].should.be.nil
res.write "Hi"
res.headers["Content-Length"].should.equal "2"
res.write " there"
res.headers["Content-Length"].should.equal "8"
end
end

View file

@ -0,0 +1,118 @@
require 'test/spec'
require 'stringio'
require 'rack/rewindable_input'
shared_context "a rewindable IO object" do
setup do
@rio = Rack::RewindableInput.new(@io)
end
teardown do
@rio.close
end
specify "should be able to handle to read()" do
@rio.read.should.equal "hello world"
end
specify "should be able to handle to read(nil)" do
@rio.read(nil).should.equal "hello world"
end
specify "should be able to handle to read(length)" do
@rio.read(1).should.equal "h"
end
specify "should be able to handle to read(length, buffer)" do
buffer = ""
result = @rio.read(1, buffer)
result.should.equal "h"
result.object_id.should.equal buffer.object_id
end
specify "should be able to handle to read(nil, buffer)" do
buffer = ""
result = @rio.read(nil, buffer)
result.should.equal "hello world"
result.object_id.should.equal buffer.object_id
end
specify "should rewind to the beginning when #rewind is called" do
@rio.read(1)
@rio.rewind
@rio.read.should.equal "hello world"
end
specify "should be able to handle gets" do
@rio.gets.should == "hello world"
end
specify "should be able to handle each" do
array = []
@rio.each do |data|
array << data
end
array.should.equal(["hello world"])
end
specify "should not buffer into a Tempfile if no data has been read yet" do
@rio.instance_variable_get(:@rewindable_io).should.be.nil
end
specify "should buffer into a Tempfile when data has been consumed for the first time" do
@rio.read(1)
tempfile = @rio.instance_variable_get(:@rewindable_io)
tempfile.should.not.be.nil
@rio.read(1)
tempfile2 = @rio.instance_variable_get(:@rewindable_io)
tempfile2.should.equal tempfile
end
specify "should close the underlying tempfile upon calling #close" do
@rio.read(1)
tempfile = @rio.instance_variable_get(:@rewindable_io)
@rio.close
tempfile.should.be.closed
end
specify "should be possibel to call #close when no data has been buffered yet" do
@rio.close
end
specify "should be possible to call #close multiple times" do
@rio.close
@rio.close
end
end
context "Rack::RewindableInput" do
context "given an IO object that is already rewindable" do
setup do
@io = StringIO.new("hello world")
end
it_should_behave_like "a rewindable IO object"
end
context "given an IO object that is not rewindable" do
setup do
@io = StringIO.new("hello world")
@io.instance_eval do
undef :rewind
end
end
it_should_behave_like "a rewindable IO object"
end
context "given an IO object whose rewind method raises Errno::ESPIPE" do
setup do
@io = StringIO.new("hello world")
def @io.rewind
raise Errno::ESPIPE, "You can't rewind this!"
end
end
it_should_behave_like "a rewindable IO object"
end
end

View file

@ -0,0 +1,82 @@
require 'test/spec'
require 'rack/session/cookie'
require 'rack/mock'
require 'rack/response'
context "Rack::Session::Cookie" do
incrementor = lambda { |env|
env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a
}
specify "creates a new cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
res["Set-Cookie"].should.match("rack.session=")
res.body.should.equal '{"counter"=>1}'
end
specify "loads from a cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "survives broken cookies" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
res.body.should.equal '{"counter"=>1}'
end
bigcookie = lambda { |env|
env["rack.session"]["cookie"] = "big" * 3000
Rack::Response.new(env["rack.session"].inspect).to_a
}
specify "barks on too big cookies" do
lambda {
Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
get("/", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning)
end
specify "creates a new cookie with integrity hash" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
if RUBY_VERSION < "1.9"
res["Set-Cookie"].should.match("rack.session=BAh7BiIMY291bnRlcmkG%0A--1439b4d37b9d4b04c603848382f712d6fcd31088")
else
res["Set-Cookie"].should.match("rack.session=BAh7BkkiDGNvdW50ZXIGOg1lbmNvZGluZyINVVMtQVNDSUlpBg%3D%3D%0A--d7a6637b94d2728194a96c18484e1f7ed9074a83")
end
end
specify "loads from a cookie wih integrity hash" 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 => '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 => 'test')).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "ignores tampered with session cookies" do
app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
response1 = Rack::MockRequest.new(app).get("/")
_, digest = response1["Set-Cookie"].split("--")
tampered_with_cookie = "hackerman-was-here" + "--" + digest
response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" =>
tampered_with_cookie)
# The tampered-with cookie is ignored, so we get back an identical Set-Cookie
response2["Set-Cookie"].should.equal(response1["Set-Cookie"])
end
end

Some files were not shown because too many files have changed in this diff Show more