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

This commit is contained in:
Jacques Distler 2009-12-26 14:03:26 -06:00
commit 47439e97c5
74 changed files with 3080 additions and 608 deletions

View file

@ -18,7 +18,7 @@
onchange="proposeAddress();" />    onchange="proposeAddress();" />   
<label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>" <label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>"
onchange="cleanAddress();" /> onchange="cleanAddress();" />
<small><em>(Letters and digits only)</em></small> <em>(Letters and digits only)</em>
</div> </div>
<h2 style="margin-bottom: 3px">Specialize</h2> <h2 style="margin-bottom: 3px">Specialize</h2>

View file

@ -15,7 +15,7 @@
<input type="text" name="author" id="authorName" value="<%= @author %>" <input type="text" name="author" id="authorName" value="<%= @author %>"
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
<%- if @page -%> <%- if @page -%>
| <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <small>(unlocks page)</small> | <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <em>(unlocks page)</em>
<%- end -%> <%- end -%>
</p> </p>

View file

@ -4,8 +4,6 @@
@hide_navigation = true @hide_navigation = true
-%> -%>
<%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{params["msg"]}</small></p>" if params["msg"] %>
<div id="MarkupHelp"> <div id="MarkupHelp">
<%= render(:file => "#{@web.markup}_help") -%> <%= render(:file => "#{@web.markup}_help") -%>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%>

View file

@ -61,13 +61,6 @@ margin:0.2em 0 0.2em 0;
padding:0; padding:0;
} }
h1#pageName small {
color:#444;
font-size:35%;
line-height:1em;
padding:0;
}
#svg_logo { #svg_logo {
float:left; float:left;
margin:.5em .25em 0 -.625em; margin:.5em .25em 0 -.625em;

View file

@ -16,3 +16,6 @@
end end
Of course, use this only when your app runs at "/". Of course, use this only when your app runs at "/".
Since lighttpd 1.4.23, you also can use the "fix-root-scriptname" flag
in fastcgi.server.

View file

@ -11,21 +11,13 @@ which all Rack applications should conform to.
== Specification changes in this release == Specification changes in this release
With Rack 1.0, the Rack specification (found in SPEC) changed in the With Rack 1.1, the Rack specification (found in SPEC) changed in the
following backward-incompatible ways. This was done to properly following backward-incompatible ways.
support Ruby 1.9 and to deprecate some problematic techniques:
* Rack::VERSION has been pushed to [1,0]. * Rack::VERSION has been pushed to [1,1].
* Header values must be Strings now, split on "\n". * rack.logger is now specified.
* rack.input must be rewindable and support reading into a buffer, * The SPEC now allows subclasses of the required types.
wrap with Rack::RewindableInput if it isn't. * rack.input has to be opened in binary mode.
* 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 == Supported web servers
@ -43,8 +35,11 @@ The included *handlers* connect all kinds of web servers to Rack:
These web servers include Rack handlers in their distributions: These web servers include Rack handlers in their distributions:
* Ebb * Ebb
* Fuzed * Fuzed
* Glassfish v3
* Phusion Passenger (which is mod_rack for Apache and for nginx) * Phusion Passenger (which is mod_rack for Apache and for nginx)
* Rainbows!
* Unicorn * Unicorn
* Zbatery
Any valid Rack app will run the same on all these handlers, without Any valid Rack app will run the same on all these handlers, without
changing anything. changing anything.
@ -70,6 +65,7 @@ These frameworks include Rack adapters in their distributions:
* Vintage * Vintage
* Waves * Waves
* Wee * Wee
* ... and many others.
Current links to these projects can be found at Current links to these projects can be found at
http://wiki.ramaze.net/Home#other-frameworks http://wiki.ramaze.net/Home#other-frameworks
@ -136,7 +132,7 @@ By default, the lobster is found at http://localhost:9292.
== Installing with RubyGems == Installing with RubyGems
A Gem of Rack is available. You can install it with: A Gem of Rack is available at gemcutter.org. You can install it with:
gem install rack gem install rack
@ -165,7 +161,6 @@ To run the test suite completely, you need:
* fcgi * fcgi
* memcache-client * memcache-client
* mongrel * mongrel
* ruby-openid
* thin * thin
The full set of tests test FCGI access with lighttpd (on port The full set of tests test FCGI access with lighttpd (on port
@ -283,16 +278,49 @@ run on port 11211) and memcache-client installed.
* Make sure WEBrick respects the :Host option * Make sure WEBrick respects the :Host option
* Many Ruby 1.9 fixes. * Many Ruby 1.9 fixes.
* December ??th, 2009: Ninth public release 1.1.0.
* Moved Auth::OpenID to rack-contrib.
* SPEC change that relaxes Lint slightly to allow subclasses of the
required types
* SPEC change to document rack.input binary mode in greator detail
* SPEC define optional rack.logger specification
* File servers support X-Cascade header
* Imported Config middleware
* Imported ETag middleware
* Imported Runtime middleware
* Imported Sendfile middleware
* New Logger and NullLogger middlewares
* Added mime type for .ogv and .manifest.
* Don't squeeze PATH_INFO slashes
* Use Content-Type to determine POST params parsing
* Update Rack::Utils::HTTP_STATUS_CODES hash
* Add status code lookup utility
* Response should call #to_i on the status
* Add Request#user_agent
* Request#host knows about forwared host
* Return an empty string for Request#host if HTTP_HOST and
SERVER_NAME are both missing
* Allow MockRequest to accept hash params
* Optimizations to HeaderHash
* Refactored rackup into Rack::Server
* Added Utils.build_nested_query to complement Utils.parse_nested_query
* Added Utils::Multipart.build_multipart to complement
Utils::Multipart.parse_multipart
* Extracted set and delete cookie helpers into Utils so they can be
used outside Response
* Extract parse_query and parse_multipart in Request so subclasses
can change their behavior
* Enforce binary encoding in RewindableInput
* Set correct external_encoding for handlers that don't use RewindableInput
== Contact == Contact
Please mail bugs, suggestions and patches to Please post bugs, suggestions and patches to
<mailto:rack-devel@googlegroups.com>. the bug tracker at <http://rack.lighthouseapp.com/>.
Mailing list archives are available at Mailing list archives are available at
<http://groups.google.com/group/rack-devel>. <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): Git repository (send Git patches to the mailing list):
* http://github.com/rack/rack * http://github.com/rack/rack
* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git * http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git
@ -318,8 +346,14 @@ would like to thank:
* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes. * Luc Heinrich for the Cookie sessions, the static file handler and bugfixes.
* Armin Ronacher, for the logo and racktools. * Armin Ronacher, for the logo and racktools.
* Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, * Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd,
Tom Robinson, Phil Hagelberg, and S. Brent Faulkner for bug fixing Tom Robinson, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic,
and other improvements. Daniel Rodríguez Troitiño, Genki Takiuchi, Geoffrey Grosenbach,
Julien Sanchez, Kamal Fariz Mahyuddin, Masayoshi Takahashi, Patrick
Aljordm, Mig, and Kazuhiro Nishiyama for bug fixing and other
improvements.
* Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support
and API improvements.
* Yehuda Katz and Carl Lerche for refactoring rackup.
* Brian Candler, for Rack::ContentType. * Brian Candler, for Rack::ContentType.
* Graham Batty, for improved handler loading. * Graham Batty, for improved handler loading.
* Stephen Bannasch, for bug reports and documentation. * Stephen Bannasch, for bug reports and documentation.
@ -358,6 +392,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Rack:: <http://rack.rubyforge.org/> Rack:: <http://rack.rubyforge.org/>
Rack's Rubyforge project:: <http://rubyforge.org/projects/rack> Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
Official Rack repositories:: <http://github.com/rack> Official Rack repositories:: <http://github.com/rack>
Rack Lighthouse Bug Tracking:: <http://rack.lighthouseapp.com/>
rack-devel mailing list:: <http://groups.google.com/group/rack-devel> rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
Christian Neukirchen:: <http://chneukirchen.org/> Christian Neukirchen:: <http://chneukirchen.org/>

View file

@ -6,9 +6,8 @@ require 'rake/testtask'
desc "Run all the tests" desc "Run all the tests"
task :default => [:test] task :default => [:test]
desc "Make an archive as .tar.gz" desc "Make an archive as .tar.gz"
task :dist => [:chmod, :changelog, :rdoc, "SPEC", "rack.gemspec"] do task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do
FileUtils.touch("RDOX") FileUtils.touch("RDOX")
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar" 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 "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX SPEC ChangeLog doc rack.gemspec"
@ -28,21 +27,11 @@ task :officialrelease_really => [:fulltest, "RDOX", "SPEC", :dist, :gem] do
sh "sha1sum #{release}.tar.gz #{release}.gem" sh "sha1sum #{release}.tar.gz #{release}.gem"
end end
def version
abort "You need to pass VERSION=... to build packages." unless ENV["VERSION"]
ENV["VERSION"]
end
def release def release
"rack-#{version}" require File.dirname(__FILE__) + "/lib/rack"
"rack-#{Rack.release}"
end end
def manifest
`git ls-files`.split("\n")
end
desc "Make binaries executable" desc "Make binaries executable"
task :chmod do task :chmod do
Dir["bin/*"].each { |binary| File.chmod(0775, binary) } Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
@ -86,7 +75,7 @@ end
desc "Run all the fast tests" desc "Run all the fast tests"
task :test do task :test do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|Rack::Auth::OpenID)"'}" sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|rackup)"'}"
end end
desc "Run all the tests" desc "Run all the tests"
@ -94,59 +83,10 @@ task :fulltest => [:chmod] do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}" sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
end end
begin task :gem => ["SPEC"] do
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") FileUtils.touch("RDOX")
sh "gem build rack.gemspec" sh "gem build rack.gemspec"
end end
end
desc "Generate RDoc documentation" desc "Generate RDoc documentation"
task :rdoc do task :rdoc do

View file

@ -1,176 +1,2 @@
#!/usr/bin/env ruby require "rack"
# -*- ruby -*- Rack::Server.start
$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

View file

@ -3,10 +3,6 @@
# Rack is freely distributable under the terms of an MIT-style license. # Rack is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php. # See COPYING or http://www.opensource.org/licenses/mit-license.php.
path = File.expand_path(File.dirname(__FILE__))
$:.unshift(path) unless $:.include?(path)
# The Rack main module, serving as a namespace for all core Rack # The Rack main module, serving as a namespace for all core Rack
# modules and classes. # modules and classes.
# #
@ -15,7 +11,7 @@ $:.unshift(path) unless $:.include?(path)
module Rack module Rack
# The Rack protocol version number implemented. # The Rack protocol version number implemented.
VERSION = [1,0] VERSION = [1,1]
# Return the Rack protocol version as a dotted string. # Return the Rack protocol version as a dotted string.
def self.version def self.version
@ -24,7 +20,7 @@ module Rack
# Return the Rack release as a dotted string. # Return the Rack release as a dotted string.
def self.release def self.release
"1.0" "1.1"
end end
autoload :Builder, "rack/builder" autoload :Builder, "rack/builder"
@ -32,8 +28,10 @@ module Rack
autoload :Chunked, "rack/chunked" autoload :Chunked, "rack/chunked"
autoload :CommonLogger, "rack/commonlogger" autoload :CommonLogger, "rack/commonlogger"
autoload :ConditionalGet, "rack/conditionalget" autoload :ConditionalGet, "rack/conditionalget"
autoload :Config, "rack/config"
autoload :ContentLength, "rack/content_length" autoload :ContentLength, "rack/content_length"
autoload :ContentType, "rack/content_type" autoload :ContentType, "rack/content_type"
autoload :ETag, "rack/etag"
autoload :File, "rack/file" autoload :File, "rack/file"
autoload :Deflater, "rack/deflater" autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory" autoload :Directory, "rack/directory"
@ -42,10 +40,15 @@ module Rack
autoload :Head, "rack/head" autoload :Head, "rack/head"
autoload :Lint, "rack/lint" autoload :Lint, "rack/lint"
autoload :Lock, "rack/lock" autoload :Lock, "rack/lock"
autoload :Logger, "rack/logger"
autoload :MethodOverride, "rack/methodoverride" autoload :MethodOverride, "rack/methodoverride"
autoload :Mime, "rack/mime" autoload :Mime, "rack/mime"
autoload :NullLogger, "rack/nulllogger"
autoload :Recursive, "rack/recursive" autoload :Recursive, "rack/recursive"
autoload :Reloader, "rack/reloader" autoload :Reloader, "rack/reloader"
autoload :Runtime, "rack/runtime"
autoload :Sendfile, "rack/sendfile"
autoload :Server, "rack/server"
autoload :ShowExceptions, "rack/showexceptions" autoload :ShowExceptions, "rack/showexceptions"
autoload :ShowStatus, "rack/showstatus" autoload :ShowStatus, "rack/showstatus"
autoload :Static, "rack/static" autoload :Static, "rack/static"
@ -62,7 +65,6 @@ module Rack
autoload :Basic, "rack/auth/basic" autoload :Basic, "rack/auth/basic"
autoload :AbstractRequest, "rack/auth/abstract/request" autoload :AbstractRequest, "rack/auth/abstract/request"
autoload :AbstractHandler, "rack/auth/abstract/handler" autoload :AbstractHandler, "rack/auth/abstract/handler"
autoload :OpenID, "rack/auth/openid"
module Digest module Digest
autoload :MD5, "rack/auth/digest/md5" autoload :MD5, "rack/auth/digest/md5"
autoload :Nonce, "rack/auth/digest/nonce" autoload :Nonce, "rack/auth/digest/nonce"

View file

@ -24,6 +24,23 @@ module Rack
# You can use +map+ to construct a Rack::URLMap in a convenient way. # You can use +map+ to construct a Rack::URLMap in a convenient way.
class Builder class Builder
def self.parse_file(config, opts = Server::Options.new)
options = {}
if config =~ /\.ru$/
cfgfile = ::File.read(config)
if cfgfile[/^#\\(.*)/] && opts
options = opts.parse! $1.split(/\s+/)
end
cfgfile.sub!(/^__END__\n.*/, '')
app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
TOPLEVEL_BINDING, config
else
require config
app = Object.const_get(::File.basename(config, '.rb').capitalize)
end
return app, options
end
def initialize(&block) def initialize(&block)
@ins = [] @ins = []
instance_eval(&block) if block_given? instance_eval(&block) if block_given?

View file

@ -4,31 +4,36 @@ module Rack
# status codes). # status codes).
class Cascade class Cascade
NotFound = [404, {}, []]
attr_reader :apps attr_reader :apps
def initialize(apps, catch=404) def initialize(apps, catch=404)
@apps = apps @apps = []; @has_app = {}
@catch = [*catch] apps.each { |app| add app }
@catch = {}
[*catch].each { |status| @catch[status] = true }
end end
def call(env) def call(env)
status = headers = body = nil result = NotFound
raise ArgumentError, "empty cascade" if @apps.empty?
@apps.each { |app| @apps.each do |app|
begin result = app.call(env)
status, headers, body = app.call(env) break unless @catch.include?(result[0].to_i)
break unless @catch.include?(status.to_i)
end end
}
[status, headers, body] result
end end
def add app def add app
@has_app[app] = true
@apps << app @apps << app
end end
def include? app def include? app
@apps.include? app @has_app.include? app
end end
alias_method :<<, :add alias_method :<<, :add

View file

@ -19,7 +19,7 @@ module Rack
STATUS_WITH_NO_ENTITY_BODY.include?(status) || STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
headers['Content-Length'] || headers['Content-Length'] ||
headers['Transfer-Encoding'] headers['Transfer-Encoding']
[status, headers.to_hash, body] [status, headers, body]
else else
dup.chunk(status, headers, body) dup.chunk(status, headers, body)
end end
@ -29,7 +29,7 @@ module Rack
@body = body @body = body
headers.delete('Content-Length') headers.delete('Content-Length')
headers['Transfer-Encoding'] = 'chunked' headers['Transfer-Encoding'] = 'chunked'
[status, headers.to_hash, self] [status, headers, self]
end end
def each def each

View file

@ -2,60 +2,48 @@ module Rack
# Rack::CommonLogger forwards every request to an +app+ given, and # Rack::CommonLogger forwards every request to an +app+ given, and
# logs a line in the Apache common log format to the +logger+, or # logs a line in the Apache common log format to the +logger+, or
# rack.errors by default. # rack.errors by default.
class CommonLogger class CommonLogger
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
# lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
def initialize(app, logger=nil) def initialize(app, logger=nil)
@app = app @app = app
@logger = logger @logger = logger
end end
def call(env) def call(env)
dup._call(env) began_at = Time.now
status, header, body = @app.call(env)
header = Utils::HeaderHash.new(header)
log(env, status, header, began_at)
[status, header, body]
end end
def _call(env) private
@env = env
@logger ||= self def log(env, status, header, began_at)
@time = Time.now now = Time.now
@status, @header, @body = @app.call(env) length = extract_content_length(header)
[@status, @header, self]
logger = @logger || env['rack.errors']
logger.write FORMAT % [
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
env["REMOTE_USER"] || "-",
now.strftime("%d/%b/%Y %H:%M:%S"),
env["REQUEST_METHOD"],
env["PATH_INFO"],
env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
env["HTTP_VERSION"],
status.to_s[0..3],
length,
now - began_at ]
end end
def close def extract_content_length(headers)
@body.close if @body.respond_to? :close value = headers['Content-Length'] or return '-'
end value.to_s == '0' ? '-' : value
# By default, log to rack.errors.
def <<(str)
@env["rack.errors"].write(str)
@env["rack.errors"].flush
end
def each
length = 0
@body.each { |part|
length += part.size
yield part
}
@now = Time.now
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
# lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
@logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
[
@env['HTTP_X_FORWARDED_FOR'] || @env["REMOTE_ADDR"] || "-",
@env["REMOTE_USER"] || "-",
@now.strftime("%d/%b/%Y %H:%M:%S"),
@env["REQUEST_METHOD"],
@env["PATH_INFO"],
@env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"],
@env["HTTP_VERSION"],
@status.to_s[0..3],
(length.zero? ? "-" : length.to_s),
@now - @time
]
end end
end end
end end

15
vendor/plugins/rack/lib/rack/config.rb vendored Normal file
View file

@ -0,0 +1,15 @@
module Rack
# Rack::Config modifies the environment using the block given during
# initialization.
class Config
def initialize(app, &block)
@app = app
@block = block
end
def call(env)
@block.call(env)
@app.call(env)
end
end
end

View file

@ -17,7 +17,7 @@ module Rack
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers) headers = Utils::HeaderHash.new(headers)
headers['Content-Type'] ||= @content_type headers['Content-Type'] ||= @content_type
[status, headers.to_hash, body] [status, headers, body]
end end
end end
end end

View file

@ -71,7 +71,9 @@ table { width:100%%; }
body = "Forbidden\n" body = "Forbidden\n"
size = Rack::Utils.bytesize(body) size = Rack::Utils.bytesize(body)
return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]] return [403, {"Content-Type" => "text/plain",
"Content-Length" => size.to_s,
"X-Cascade" => "pass"}, [body]]
end end
def list_directory def list_directory
@ -123,7 +125,9 @@ table { width:100%%; }
def entity_not_found def entity_not_found
body = "Entity not found: #{@path_info}\n" body = "Entity not found: #{@path_info}\n"
size = Rack::Utils.bytesize(body) size = Rack::Utils.bytesize(body)
return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]] return [404, {"Content-Type" => "text/plain",
"Content-Length" => size.to_s,
"X-Cascade" => "pass"}, [body]]
end end
def each def each

23
vendor/plugins/rack/lib/rack/etag.rb vendored Normal file
View file

@ -0,0 +1,23 @@
require 'digest/md5'
module Rack
# Automatically sets the ETag header on all String bodies
class ETag
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if !headers.has_key?('ETag')
parts = []
body.each { |part| parts << part.to_s }
headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
[status, headers, parts]
else
[status, headers, body]
end
end
end
end

View file

@ -45,7 +45,8 @@ module Rack
def forbidden def forbidden
body = "Forbidden\n" body = "Forbidden\n"
[403, {"Content-Type" => "text/plain", [403, {"Content-Type" => "text/plain",
"Content-Length" => body.size.to_s}, "Content-Length" => body.size.to_s,
"X-Cascade" => "pass"},
[body]] [body]]
end end
@ -73,7 +74,8 @@ module Rack
def not_found def not_found
body = "File not found: #{@path_info}\n" body = "File not found: #{@path_info}\n"
[404, {"Content-Type" => "text/plain", [404, {"Content-Type" => "text/plain",
"Content-Length" => body.size.to_s}, "Content-Length" => body.size.to_s,
"X-Cascade" => "pass"},
[body]] [body]]
end end

View file

@ -22,6 +22,25 @@ module Rack
end end
end end
def self.default(options = {})
# Guess.
if ENV.include?("PHP_FCGI_CHILDREN")
# We already speak FastCGI
options.delete :File
options.delete :Port
Rack::Handler::FastCGI
elsif ENV.include?("REQUEST_METHOD")
Rack::Handler::CGI
else
begin
Rack::Handler::Mongrel
rescue LoadError => e
Rack::Handler::WEBrick
end
end
end
# Transforms server-name constants to their canonical form as filenames, # Transforms server-name constants to their canonical form as filenames,
# then tries to require them but silences the LoadError if not found # then tries to require them but silences the LoadError if not found
# #

View file

@ -15,7 +15,7 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [1,0], env.update({"rack.version" => [1,1],
"rack.input" => $stdin, "rack.input" => $stdin,
"rack.errors" => $stderr, "rack.errors" => $stderr,

View file

@ -36,7 +36,7 @@ module Rack
rack_input = RewindableInput.new(request.in) rack_input = RewindableInput.new(request.in)
env.update({"rack.version" => [1,0], env.update({"rack.version" => [1,1],
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => request.err, "rack.errors" => request.err,
@ -50,7 +50,6 @@ module Rack
env["QUERY_STRING"] ||= "" env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/" env["REQUEST_PATH"] ||= "/"
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == "" env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == "" env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""

View file

@ -1,5 +1,6 @@
require 'lsapi' require 'lsapi'
require 'rack/content_length' require 'rack/content_length'
require 'rack/rewindable_input'
module Rack module Rack
module Handler module Handler
@ -19,7 +20,7 @@ module Rack
rack_input = RewindableInput.new($stdin.read.to_s) rack_input = RewindableInput.new($stdin.read.to_s)
env.update( env.update(
"rack.version" => [1,0], "rack.version" => [1,1],
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
"rack.multithread" => false, "rack.multithread" => false,
@ -38,6 +39,8 @@ module Rack
ensure ensure
body.close if body.respond_to? :close body.close if body.respond_to? :close
end end
ensure
rack_input.close
end end
def self.send_headers(status, headers) def self.send_headers(status, headers)
print "Status: #{status}\r\n" print "Status: #{status}\r\n"

View file

@ -7,8 +7,12 @@ module Rack
module Handler module Handler
class Mongrel < ::Mongrel::HttpHandler class Mongrel < ::Mongrel::HttpHandler
def self.run(app, options={}) def self.run(app, options={})
server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0', server = ::Mongrel::HttpServer.new(
options[:Port] || 8080) options[:Host] || '0.0.0.0',
options[:Port] || 8080,
options[:num_processors] || 950,
options[:throttle] || 0,
options[:timeout] || 60)
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. # 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. # { path=>app, ... } or an instance of Rack::URLMap.
@ -48,7 +52,7 @@ module Rack
rack_input = request.body || StringIO.new('') rack_input = request.body || StringIO.new('')
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0], env.update({"rack.version" => [1,1],
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
@ -59,7 +63,6 @@ module Rack
"rack.url_scheme" => "http", "rack.url_scheme" => "http",
}) })
env["QUERY_STRING"] ||= "" env["QUERY_STRING"] ||= ""
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = @app.call(env) status, headers, body = @app.call(env)

View file

@ -36,7 +36,7 @@ module Rack
rack_input = StringIO.new(input_body) rack_input = StringIO.new(input_body)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0], env.update({"rack.version" => [1,1],
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
"rack.multithread" => true, "rack.multithread" => true,

View file

@ -27,7 +27,7 @@ module Rack
rack_input = StringIO.new(req.body.to_s) rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,0], env.update({"rack.version" => [1,1],
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
@ -41,9 +41,7 @@ module Rack
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["QUERY_STRING"] ||= "" env["QUERY_STRING"] ||= ""
env["REQUEST_PATH"] ||= "/" env["REQUEST_PATH"] ||= "/"
if env["PATH_INFO"] == "" unless env["PATH_INFO"] == ""
env.delete "PATH_INFO"
else
path, n = req.request_uri.path, env["SCRIPT_NAME"].length path, n = req.request_uri.path, env["SCRIPT_NAME"].length
env["PATH_INFO"] = path[n, path.length-n] env["PATH_INFO"] = path[n, path.length-n]
end end

View file

@ -61,7 +61,7 @@ module Rack
## subclassing allowed) that includes CGI-like headers. ## subclassing allowed) that includes CGI-like headers.
## The application is free to modify the environment. ## The application is free to modify the environment.
assert("env #{env.inspect} is not a Hash, but #{env.class}") { assert("env #{env.inspect} is not a Hash, but #{env.class}") {
env.instance_of? Hash env.kind_of? Hash
} }
## ##
@ -111,7 +111,7 @@ module Rack
## In addition to this, the Rack environment must include these ## In addition to this, the Rack environment must include these
## Rack-specific variables: ## Rack-specific variables:
## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack. ## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL. ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
## <tt>rack.input</tt>:: See below, the input stream. ## <tt>rack.input</tt>:: See below, the input stream.
## <tt>rack.errors</tt>:: See below, the error stream. ## <tt>rack.errors</tt>:: See below, the error stream.
@ -148,6 +148,35 @@ module Rack
} }
end end
## <tt>rack.logger</tt>:: A common object interface for logging messages.
## The object must implement:
if logger = env['rack.logger']
## info(message, &block)
assert("logger #{logger.inspect} must respond to info") {
logger.respond_to?(:info)
}
## debug(message, &block)
assert("logger #{logger.inspect} must respond to debug") {
logger.respond_to?(:debug)
}
## warn(message, &block)
assert("logger #{logger.inspect} must respond to warn") {
logger.respond_to?(:warn)
}
## error(message, &block)
assert("logger #{logger.inspect} must respond to error") {
logger.respond_to?(:error)
}
## fatal(message, &block)
assert("logger #{logger.inspect} must respond to fatal") {
logger.respond_to?(:fatal)
}
end
## The server or the application can store their own data in the ## The server or the application can store their own data in the
## environment, too. The keys must contain at least one dot, ## environment, too. The keys must contain at least one dot,
## and should be prefixed uniquely. The prefix <tt>rack.</tt> ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
@ -175,7 +204,7 @@ module Rack
env.each { |key, value| env.each { |key, value|
next if key.include? "." # Skip extensions next if key.include? "." # Skip extensions
assert("env variable #{key} has non-string value #{value.inspect}") { assert("env variable #{key} has non-string value #{value.inspect}") {
value.instance_of? String value.kind_of? String
} }
} }
@ -184,7 +213,7 @@ module Rack
## * <tt>rack.version</tt> must be an array of Integers. ## * <tt>rack.version</tt> must be an array of Integers.
assert("rack.version must be an Array, was #{env["rack.version"].class}") { assert("rack.version must be an Array, was #{env["rack.version"].class}") {
env["rack.version"].instance_of? Array env["rack.version"].kind_of? Array
} }
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+. ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") { assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
@ -269,7 +298,7 @@ module Rack
assert("rack.input#gets called with arguments") { args.size == 0 } assert("rack.input#gets called with arguments") { args.size == 0 }
v = @input.gets v = @input.gets
assert("rack.input#gets didn't return a String") { assert("rack.input#gets didn't return a String") {
v.nil? or v.instance_of? String v.nil? or v.kind_of? String
} }
v v
end end
@ -304,7 +333,7 @@ module Rack
v = @input.read(*args) v = @input.read(*args)
assert("rack.input#read didn't return nil or a String") { assert("rack.input#read didn't return nil or a String") {
v.nil? or v.instance_of? String v.nil? or v.kind_of? String
} }
if args[0].nil? if args[0].nil?
assert("rack.input#read(nil) returned nil on EOF") { assert("rack.input#read(nil) returned nil on EOF") {
@ -320,7 +349,7 @@ module Rack
assert("rack.input#each called with arguments") { args.size == 0 } assert("rack.input#each called with arguments") { args.size == 0 }
@input.each { |line| @input.each { |line|
assert("rack.input#each didn't yield a String") { assert("rack.input#each didn't yield a String") {
line.instance_of? String line.kind_of? String
} }
yield line yield line
} }
@ -373,7 +402,7 @@ module Rack
## * +write+ must be called with a single argument that is a String. ## * +write+ must be called with a single argument that is a String.
def write(str) def write(str)
assert("rack.errors#write not called with a String") { str.instance_of? String } assert("rack.errors#write not called with a String") { str.kind_of? String }
@error.write str @error.write str
end end
@ -407,7 +436,7 @@ module Rack
header.each { |key, value| header.each { |key, value|
## The header keys must be Strings. ## The header keys must be Strings.
assert("header key must be a string, was #{key.class}") { assert("header key must be a string, was #{key.class}") {
key.instance_of? String key.kind_of? String
} }
## The header must not contain a +Status+ key, ## The header must not contain a +Status+ key,
assert("header must not contain Status") { key.downcase != "status" } assert("header must not contain Status") { key.downcase != "status" }
@ -499,7 +528,7 @@ module Rack
@body.each { |part| @body.each { |part|
## and must only yield String values. ## and must only yield String values.
assert("Body yielded non-string value #{part.inspect}") { assert("Body yielded non-string value #{part.inspect}") {
part.instance_of? String part.kind_of? String
} }
yield part yield part
} }

20
vendor/plugins/rack/lib/rack/logger.rb vendored Normal file
View file

@ -0,0 +1,20 @@
require 'logger'
module Rack
# Sets up rack.logger to write to rack.errors stream
class Logger
def initialize(app, level = ::Logger::INFO)
@app, @level = app, level
end
def call(env)
logger = ::Logger.new(env['rack.errors'])
logger.level = @level
env['rack.logger'] = logger
@app.call(env)
ensure
logger.close
end
end
end

View file

@ -14,7 +14,7 @@ module Rack
# Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream') # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
def mime_type(ext, fallback='application/octet-stream') def mime_type(ext, fallback='application/octet-stream')
MIME_TYPES.fetch(ext, fallback) MIME_TYPES.fetch(ext.to_s.downcase, fallback)
end end
module_function :mime_type module_function :mime_type
@ -105,6 +105,7 @@ module Rack
".m3u" => "audio/x-mpegurl", ".m3u" => "audio/x-mpegurl",
".m4v" => "video/mp4", ".m4v" => "video/mp4",
".man" => "text/troff", ".man" => "text/troff",
".manifest"=> "text/cache-manifest",
".mathml" => "application/mathml+xml", ".mathml" => "application/mathml+xml",
".mbox" => "application/mbox", ".mbox" => "application/mbox",
".mdoc" => "text/troff", ".mdoc" => "text/troff",
@ -126,6 +127,7 @@ module Rack
".ods" => "application/vnd.oasis.opendocument.spreadsheet", ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
".odt" => "application/vnd.oasis.opendocument.text", ".odt" => "application/vnd.oasis.opendocument.text",
".ogg" => "application/ogg", ".ogg" => "application/ogg",
".ogv" => "video/ogg",
".p" => "text/x-pascal", ".p" => "text/x-pascal",
".pas" => "text/x-pascal", ".pas" => "text/x-pascal",
".pbm" => "image/x-portable-bitmap", ".pbm" => "image/x-portable-bitmap",

View file

@ -40,7 +40,7 @@ module Rack
end end
DEFAULT_ENV = { DEFAULT_ENV = {
"rack.version" => [1,0], "rack.version" => [1,1],
"rack.input" => StringIO.new, "rack.input" => StringIO.new,
"rack.errors" => StringIO.new, "rack.errors" => StringIO.new,
"rack.multithread" => true, "rack.multithread" => true,
@ -73,14 +73,17 @@ module Rack
# Return the Rack environment used for a request to +uri+. # Return the Rack environment used for a request to +uri+.
def self.env_for(uri="", opts={}) def self.env_for(uri="", opts={})
uri = URI(uri) uri = URI(uri)
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
env = DEFAULT_ENV.dup env = DEFAULT_ENV.dup
env["REQUEST_METHOD"] = opts[:method] || "GET" env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
env["SERVER_NAME"] = uri.host || "example.org" env["SERVER_NAME"] = uri.host || "example.org"
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80" env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
env["QUERY_STRING"] = uri.query.to_s env["QUERY_STRING"] = uri.query.to_s
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
env["rack.url_scheme"] = uri.scheme || "http" env["rack.url_scheme"] = uri.scheme || "http"
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
env["SCRIPT_NAME"] = opts[:script_name] || "" env["SCRIPT_NAME"] = opts[:script_name] || ""
@ -90,7 +93,30 @@ module Rack
env["rack.errors"] = StringIO.new env["rack.errors"] = StringIO.new
end end
opts[:input] ||= "" 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
empty_str = ""
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
opts[:input] ||= empty_str
if String === opts[:input] if String === opts[:input]
rack_input = StringIO.new(opts[:input]) rack_input = StringIO.new(opts[:input])
else else
@ -128,7 +154,7 @@ module Rack
@body = "" @body = ""
body.each { |part| @body << part } body.each { |part| @body << part }
@errors = errors.string @errors = errors.string if errors.respond_to?(:string)
end end
# Status # Status

View file

@ -0,0 +1,18 @@
module Rack
class NullLogger
def initialize(app)
@app = app
end
def call(env)
env['rack.logger'] = self
@app.call(env)
end
def info(progname = nil, &block); end
def debug(progname = nil, &block); end
def warn(progname = nil, &block); end
def error(progname = nil, &block); end
def fatal(progname = nil, &block); end
end
end

View file

@ -1,5 +1,6 @@
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
# All files in this distribution are subject to the terms of the Ruby license. # Rack::Reloader is subject to the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
require 'pathname' require 'pathname'
@ -92,6 +93,8 @@ module Rack
found, stat = safe_stat(path) found, stat = safe_stat(path)
return ::File.expand_path(found), stat if found return ::File.expand_path(found), stat if found
end end
return false, false
end end
def safe_stat(file) def safe_stat(file)

View file

@ -32,6 +32,7 @@ module Rack
def content_type; @env['CONTENT_TYPE'] end def content_type; @env['CONTENT_TYPE'] end
def session; @env['rack.session'] ||= {} end def session; @env['rack.session'] ||= {} end
def session_options; @env['rack.session.options'] ||= {} end def session_options; @env['rack.session.options'] ||= {} end
def logger; @env['rack.logger'] end
# The media type (type/subtype) portion of the CONTENT_TYPE header # The media type (type/subtype) portion of the CONTENT_TYPE header
# without any media type parameters. e.g., when CONTENT_TYPE is # without any media type parameters. e.g., when CONTENT_TYPE is
@ -63,9 +64,17 @@ module Rack
media_type_params['charset'] media_type_params['charset']
end end
def host_with_port
if forwarded = @env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
@env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
end
end
def host def host
# Remove port number. # Remove port number.
(@env["HTTP_HOST"] || @env["SERVER_NAME"]).to_s.gsub(/:\d+\z/, '') host_with_port.to_s.gsub(/:\d+\z/, '')
end end
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
@ -81,7 +90,6 @@ module Rack
# one of the media types presents in this list will not be eligible # one of the media types presents in this list will not be eligible
# for form-data / param parsing. # for form-data / param parsing.
FORM_DATA_MEDIA_TYPES = [ FORM_DATA_MEDIA_TYPES = [
nil,
'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded',
'multipart/form-data' 'multipart/form-data'
] ]
@ -95,12 +103,17 @@ module Rack
] ]
# Determine whether the request body contains form-data by checking # Determine whether the request body contains form-data by checking
# the request media_type against registered form-data media-types: # the request Content-Type for one of the media-types:
# "application/x-www-form-urlencoded" and "multipart/form-data". The # "application/x-www-form-urlencoded" or "multipart/form-data". The
# list of form-data media types can be modified through the # list of form-data media types can be modified through the
# +FORM_DATA_MEDIA_TYPES+ array. # +FORM_DATA_MEDIA_TYPES+ array.
#
# A request body is also assumed to contain form-data when no
# Content-Type header is provided and the request_method is POST.
def form_data? def form_data?
FORM_DATA_MEDIA_TYPES.include?(media_type) type = media_type
meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
(meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
end end
# Determine whether the request body contains data by checking # Determine whether the request body contains data by checking
@ -115,8 +128,7 @@ module Rack
@env["rack.request.query_hash"] @env["rack.request.query_hash"]
else else
@env["rack.request.query_string"] = query_string @env["rack.request.query_string"] = query_string
@env["rack.request.query_hash"] = @env["rack.request.query_hash"] = parse_query(query_string)
Utils.parse_nested_query(query_string)
end end
end end
@ -125,19 +137,20 @@ module Rack
# This method support both application/x-www-form-urlencoded and # This method support both application/x-www-form-urlencoded and
# multipart/form-data. # multipart/form-data.
def POST def POST
if @env["rack.request.form_input"].eql? @env["rack.input"] if @env["rack.input"].nil?
raise "Missing rack.input"
elsif @env["rack.request.form_input"].eql? @env["rack.input"]
@env["rack.request.form_hash"] @env["rack.request.form_hash"]
elsif form_data? || parseable_data? elsif form_data? || parseable_data?
@env["rack.request.form_input"] = @env["rack.input"] @env["rack.request.form_input"] = @env["rack.input"]
unless @env["rack.request.form_hash"] = unless @env["rack.request.form_hash"] = parse_multipart(env)
Utils::Multipart.parse_multipart(env)
form_vars = @env["rack.input"].read form_vars = @env["rack.input"].read
# Fix for Safari Ajax postings that always append \0 # Fix for Safari Ajax postings that always append \0
form_vars.sub!(/\0\z/, '') form_vars.sub!(/\0\z/, '')
@env["rack.request.form_vars"] = form_vars @env["rack.request.form_vars"] = form_vars
@env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars) @env["rack.request.form_hash"] = parse_query(form_vars)
@env["rack.input"].rewind @env["rack.input"].rewind
end end
@ -149,7 +162,7 @@ module Rack
# The union of GET and POST data. # The union of GET and POST data.
def params def params
self.put? ? self.GET : self.GET.update(self.POST) self.GET.update(self.POST)
rescue EOFError => e rescue EOFError => e
self.GET self.GET
end end
@ -175,6 +188,9 @@ module Rack
end end
alias referrer referer alias referrer referer
def user_agent
@env['HTTP_USER_AGENT']
end
def cookies def cookies
return {} unless @env["HTTP_COOKIE"] return {} unless @env["HTTP_COOKIE"]
@ -242,5 +258,14 @@ module Rack
@env['REMOTE_ADDR'] @env['REMOTE_ADDR']
end end
end end
protected
def parse_query(qs)
Utils.parse_nested_query(qs)
end
def parse_multipart(env)
Utils::Multipart.parse_multipart(env)
end
end end
end end

View file

@ -19,7 +19,7 @@ module Rack
attr_accessor :length attr_accessor :length
def initialize(body=[], status=200, header={}, &block) def initialize(body=[], status=200, header={}, &block)
@status = status @status = status.to_i
@header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
merge(header)) merge(header))
@ -54,45 +54,11 @@ module Rack
end end
def set_cookie(key, value) def set_cookie(key, value)
case value Utils.set_cookie_header!(header, key, value)
when Hash
domain = "; domain=" + value[:domain] if value[:domain]
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime.
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
end
value = [value] unless Array === value
cookie = Utils.escape(key) + "=" +
value.map { |v| Utils.escape v }.join("&") +
"#{domain}#{path}#{expires}#{secure}#{httponly}"
case self["Set-Cookie"]
when Array
self["Set-Cookie"] << cookie
when String
self["Set-Cookie"] = [self["Set-Cookie"], cookie]
when nil
self["Set-Cookie"] = cookie
end
end end
def delete_cookie(key, value={}) def delete_cookie(key, value={})
unless Array === self["Set-Cookie"] Utils.delete_cookie_header!(header, key, value)
self["Set-Cookie"] = [self["Set-Cookie"]].compact
end
self["Set-Cookie"].reject! { |cookie|
cookie =~ /\A#{Utils.escape(key)}=/
}
set_cookie(key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
end end
def redirect(target, status=302) def redirect(target, status=302)
@ -105,9 +71,9 @@ module Rack
if [204, 304].include?(status.to_i) if [204, 304].include?(status.to_i)
header.delete "Content-Type" header.delete "Content-Type"
[status.to_i, header.to_hash, []] [status.to_i, header, []]
else else
[status.to_i, header.to_hash, self] [status.to_i, header, self]
end end
end end
alias to_a finish # For *response alias to_a finish # For *response

View file

@ -72,6 +72,8 @@ module Rack
# access it because we have the file handle open. # access it because we have the file handle open.
@rewindable_io = Tempfile.new('RackRewindableInput') @rewindable_io = Tempfile.new('RackRewindableInput')
@rewindable_io.chmod(0000) @rewindable_io.chmod(0000)
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
@rewindable_io.binmode
if filesystem_has_posix_semantics? && !tempfile_unlink_contains_bug? if filesystem_has_posix_semantics? && !tempfile_unlink_contains_bug?
@rewindable_io.unlink @rewindable_io.unlink
@unlinked = true @unlinked = true

27
vendor/plugins/rack/lib/rack/runtime.rb vendored Normal file
View file

@ -0,0 +1,27 @@
module Rack
# Sets an "X-Runtime" response header, indicating the response
# time of the request, in seconds
#
# You can put it right before the application to see the processing
# time, or before all the other middlewares to include time for them,
# too.
class Runtime
def initialize(app, name = nil)
@app = app
@header_name = "X-Runtime"
@header_name << "-#{name}" if name
end
def call(env)
start_time = Time.now
status, headers, body = @app.call(env)
request_time = Time.now - start_time
if !headers.has_key?(@header_name)
headers[@header_name] = "%0.6f" % request_time
end
[status, headers, body]
end
end
end

142
vendor/plugins/rack/lib/rack/sendfile.rb vendored Normal file
View file

@ -0,0 +1,142 @@
require 'rack/file'
module Rack
class File #:nodoc:
alias :to_path :path
end
# = Sendfile
#
# The Sendfile middleware intercepts responses whose body is being
# served from a file and replaces it with a server specific X-Sendfile
# header. The web server is then responsible for writing the file contents
# to the client. This can dramatically reduce the amount of work required
# by the Ruby backend and takes advantage of the web servers optimized file
# delivery code.
#
# In order to take advantage of this middleware, the response body must
# respond to +to_path+ and the request must include an X-Sendfile-Type
# header. Rack::File and other components implement +to_path+ so there's
# rarely anything you need to do in your application. The X-Sendfile-Type
# header is typically set in your web servers configuration. The following
# sections attempt to document
#
# === Nginx
#
# Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
# but requires parts of the filesystem to be mapped into a private URL
# hierarachy.
#
# The following example shows the Nginx configuration required to create
# a private "/files/" area, enable X-Accel-Redirect, and pass the special
# X-Sendfile-Type and X-Accel-Mapping headers to the backend:
#
# location /files/ {
# internal;
# alias /var/www/;
# }
#
# location / {
# proxy_redirect false;
#
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#
# proxy_set_header X-Sendfile-Type X-Accel-Redirect
# proxy_set_header X-Accel-Mapping /files/=/var/www/;
#
# proxy_pass http://127.0.0.1:8080/;
# }
#
# Note that the X-Sendfile-Type header must be set exactly as shown above. The
# X-Accel-Mapping header should specify the name of the private URL pattern,
# followed by an equals sign (=), followed by the location on the file system
# that it maps to. The middleware performs a simple substitution on the
# resulting path.
#
# See Also: http://wiki.codemongers.com/NginxXSendfile
#
# === lighttpd
#
# Lighttpd has supported some variation of the X-Sendfile header for some
# time, although only recent version support X-Sendfile in a reverse proxy
# configuration.
#
# $HTTP["host"] == "example.com" {
# proxy-core.protocol = "http"
# proxy-core.balancer = "round-robin"
# proxy-core.backends = (
# "127.0.0.1:8000",
# "127.0.0.1:8001",
# ...
# )
#
# proxy-core.allow-x-sendfile = "enable"
# proxy-core.rewrite-request = (
# "X-Sendfile-Type" => (".*" => "X-Sendfile")
# )
# }
#
# See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
#
# === Apache
#
# X-Sendfile is supported under Apache 2.x using a separate module:
#
# http://tn123.ath.cx/mod_xsendfile/
#
# Once the module is compiled and installed, you can enable it using
# XSendFile config directive:
#
# RequestHeader Set X-Sendfile-Type X-Sendfile
# ProxyPassReverse / http://localhost:8001/
# XSendFile on
class Sendfile
F = ::File
def initialize(app, variation=nil)
@app = app
@variation = variation
end
def call(env)
status, headers, body = @app.call(env)
if body.respond_to?(:to_path)
case type = variation(env)
when 'X-Accel-Redirect'
path = F.expand_path(body.to_path)
if url = map_accel_path(env, path)
headers[type] = url
body = []
else
env['rack.errors'] << "X-Accel-Mapping header missing"
end
when 'X-Sendfile', 'X-Lighttpd-Send-File'
path = F.expand_path(body.to_path)
headers[type] = path
body = []
when '', nil
else
env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
end
end
[status, headers, body]
end
private
def variation(env)
@variation ||
env['sendfile.type'] ||
env['HTTP_X_SENDFILE_TYPE']
end
def map_accel_path(env, file)
if mapping = env['HTTP_X_ACCEL_MAPPING']
internal, external = mapping.split('=', 2).map{ |p| p.strip }
file.sub(/^#{internal}/i, external)
end
end
end
end

212
vendor/plugins/rack/lib/rack/server.rb vendored Normal file
View file

@ -0,0 +1,212 @@
require 'optparse'
module Rack
class Server
class Options
def parse!(args)
options = {}
opt_parser = OptionParser.new("", 24, ' ') do |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)") {
options[:debug] = true
}
opts.on("-w", "--warn", "turn warnings on for your script") {
options[:warn] = true
}
opts.on("-I", "--include PATH",
"specify $LOAD_PATH (may be used more than once)") { |path|
options[:include] = path.split(":")
}
opts.on("-r", "--require LIBRARY",
"require the library, before executing your script") { |library|
options[:require] = library
}
opts.separator ""
opts.separator "Rack options:"
opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
options[: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|
options[:environment] = e
}
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
options[:daemonize] = d ? true : false
}
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
options[: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
end
opt_parser.parse! args
options[:rack_file] = args.last if args.last
options
end
end
def self.start
new.start
end
attr_accessor :options
def initialize(options = nil)
@options = options
end
def options
@options ||= parse_options(ARGV)
end
def default_options
{
:environment => "development",
:pid => nil,
:Port => 9292,
:Host => "0.0.0.0",
:AccessLog => [],
:rack_file => ::File.expand_path("config.ru")
}
end
def app
@app ||= begin
if !::File.exist? options[:rack_file]
abort "configuration #{options[:rack_file]} not found"
end
app, options = Rack::Builder.parse_file(self.options[:rack_file], opt_parser)
self.options.merge! options
app
end
end
def self.middleware
@middleware ||= begin
m = Hash.new {|h,k| h[k] = []}
m["deployment"].concat [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
m
end
end
def middleware
self.class.middleware
end
def start
if options[:debug]
$DEBUG = true
require 'pp'
p options[:server]
pp wrapped_app
pp app
end
if options[:warn]
$-w = true
end
if includes = options[:include]
$LOAD_PATH.unshift *includes
end
if library = options[:require]
require library
end
daemonize_app if options[:daemonize]
write_pid if options[:pid]
server.run wrapped_app, options
end
def server
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
end
private
def parse_options(args)
options = default_options
# Don't evaluate CGI ISINDEX parameters.
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
args.clear if ENV.include?("REQUEST_METHOD")
options.merge! opt_parser.parse! args
options
end
def opt_parser
Options.new
end
def build_app(app)
middleware[options[:environment]].reverse_each do |middleware|
middleware = middleware.call(self) if middleware.respond_to?(:call)
next unless middleware
klass = middleware.shift
app = klass.new(app, *middleware)
end
app
end
def wrapped_app
@wrapped_app ||= build_app app
end
def daemonize_app
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
end
def write_pid
::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
end
end
end

View file

@ -107,18 +107,16 @@ module Rack
if not session_id = set_session(env, session_id, session, options) if not session_id = set_session(env, session_id, session, options)
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.") env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
[status, headers, body]
elsif options[:defer] and not options[:renew] elsif options[:defer] and not options[:renew]
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
[status, headers, body]
else else
cookie = Hash.new cookie = Hash.new
cookie[:value] = session_id cookie[:value] = session_id
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
response = Rack::Response.new(body, status, headers) Utils.set_cookie_header!(headers, @key, cookie.merge(options))
response.set_cookie(@key, cookie.merge(options))
response.to_a
end end
[status, headers, body]
end end
# All thread safety and session retrival proceedures should occur here. # All thread safety and session retrival proceedures should occur here.

View file

@ -70,16 +70,15 @@ module Rack
if session_data.size > (4096 - @key.size) if session_data.size > (4096 - @key.size)
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.") env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
[status, headers, body]
else else
options = env["rack.session.options"] options = env["rack.session.options"]
cookie = Hash.new cookie = Hash.new
cookie[:value] = session_data cookie[:value] = session_data
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
response = Rack::Response.new(body, status, headers) Utils.set_cookie_header!(headers, @key, cookie.merge(options))
response.set_cookie(@key, cookie.merge(options))
response.to_a
end end
[status, headers, body]
end end
def generate_hmac(data) def generate_hmac(data)

View file

@ -29,9 +29,13 @@ module Rack
super super
@mutex = Mutex.new @mutex = Mutex.new
@pool = MemCache. mserv = @default_options[:memcache_server]
new @default_options[:memcache_server], @default_options mopts = @default_options.
raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?} reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k }
@pool = MemCache.new mserv, mopts
unless @pool.active? and @pool.servers.any?{|c| c.alive? }
raise 'No memcache servers'
end
end end
def generate_sid def generate_sid
@ -41,24 +45,23 @@ module Rack
end end
end end
def get_session(env, sid) def get_session(env, session_id)
session = @pool.get(sid) if sid
@mutex.lock if env['rack.multithread'] @mutex.lock if env['rack.multithread']
unless sid and session unless session_id and session = @pool.get(session_id)
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil? session_id, session = generate_sid, {}
session = {} unless /^STORED/ =~ @pool.add(session_id, session)
sid = generate_sid raise "Session collision on '#{session_id.inspect}'"
ret = @pool.add sid, session
raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
end end
session.instance_variable_set('@old', {}.merge(session)) end
return [sid, session] session.instance_variable_set '@old', @pool.get(session_id, true)
rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted return [session_id, session]
warn "#{self} is unable to find server." rescue MemCache::MemCacheError, Errno::ECONNREFUSED
# MemCache server cannot be contacted
warn "#{self} is unable to find memcached server."
warn $!.inspect warn $!.inspect
return [ nil, {} ] return [ nil, {} ]
ensure ensure
@mutex.unlock if env['rack.multithread'] @mutex.unlock if @mutex.locked?
end end
def set_session(env, session_id, new_session, options) def set_session(env, session_id, new_session, options)
@ -66,43 +69,50 @@ module Rack
expiry = expiry.nil? ? 0 : expiry + 1 expiry = expiry.nil? ? 0 : expiry + 1
@mutex.lock if env['rack.multithread'] @mutex.lock if env['rack.multithread']
session = @pool.get(session_id) || {}
if options[:renew] or options[:drop] if options[:renew] or options[:drop]
@pool.delete session_id @pool.delete session_id
return false if options[:drop] return false if options[:drop]
session_id = generate_sid session_id = generate_sid
@pool.add session_id, 0 # so we don't worry about cache miss on #set @pool.add session_id, {} # so we don't worry about cache miss on #set
end end
old_session = new_session.instance_variable_get('@old') || {}
session = merge_sessions session_id, old_session, new_session, session session = @pool.get(session_id) || {}
old_session = new_session.instance_variable_get '@old'
old_session = old_session ? Marshal.load(old_session) : {}
unless Hash === old_session and Hash === new_session
env['rack.errors'].
puts 'Bad old_session or new_session sessions provided.'
else # merge sessions
# alterations are either update or delete, making as few changes as
# possible to prevent possible issues.
# removed keys
delete = old_session.keys - new_session.keys
if $VERBOSE and not delete.empty?
env['rack.errors'].
puts "//@#{session_id}: delete #{delete*','}"
end
delete.each{|k| session.delete k }
# added or altered keys
update = new_session.keys.
select{|k| new_session[k] != old_session[k] }
if $VERBOSE and not update.empty?
env['rack.errors'].puts "//@#{session_id}: update #{update*','}"
end
update.each{|k| session[k] = new_session[k] }
end
@pool.set session_id, session, expiry @pool.set session_id, session, expiry
return session_id return session_id
rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted rescue MemCache::MemCacheError, Errno::ECONNREFUSED
warn "#{self} is unable to find server." # MemCache server cannot be contacted
warn "#{self} is unable to find memcached server."
warn $!.inspect warn $!.inspect
return false return false
ensure ensure
@mutex.unlock if env['rack.multithread'] @mutex.unlock if @mutex.locked?
end
private
def merge_sessions sid, old, new, cur=nil
cur ||= {}
unless Hash === old and Hash === new
warn 'Bad old or new sessions provided.'
return cur
end
delete = old.keys - new.keys
warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
delete.each{|k| cur.delete k }
update = new.keys.select{|k| new[k] != old[k] }
warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
update.each{|k| cur[k] = new[k] }
cur
end end
end end
end end

View file

@ -13,7 +13,7 @@ module Rack
# In the context of a multithreaded environment, sessions being # In the context of a multithreaded environment, sessions being
# committed to the pool is done in a merging manner. # committed to the pool is done in a merging manner.
# #
# The :drop option is available in rack.session.options if you with to # The :drop option is available in rack.session.options if you wish to
# explicitly remove the session from the session cache. # explicitly remove the session from the session cache.
# #
# Example: # Example:

View file

@ -28,27 +28,28 @@ module Rack
raise ArgumentError, "paths need to start with /" raise ArgumentError, "paths need to start with /"
end end
location = location.chomp('/') location = location.chomp('/')
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
[host, location, app] [host, location, match, app]
}.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
end end
def call(env) def call(env)
path = env["PATH_INFO"].to_s.squeeze("/") path = env["PATH_INFO"].to_s
script_name = env['SCRIPT_NAME'] script_name = env['SCRIPT_NAME']
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
@mapping.each { |host, location, app| @mapping.each { |host, location, match, app|
next unless (hHost == host || sName == host \ next unless (hHost == host || sName == host \
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
next unless location == path[0, location.size] next unless path =~ match && rest = $1
next unless path[location.size] == nil || path[location.size] == ?/ next unless rest.empty? || rest[0] == ?/
return app.call( return app.call(
env.merge( env.merge(
'SCRIPT_NAME' => (script_name + location), 'SCRIPT_NAME' => (script_name + location),
'PATH_INFO' => path[location.size..-1])) 'PATH_INFO' => rest))
} }
[404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]] [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
end end
end end
end end

View file

@ -38,7 +38,9 @@ module Rack
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) } k, v = p.split('=', 2).map { |x| unescape(x) }
if v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
if cur = params[k] if cur = params[k]
if cur.class == Array if cur.class == Array
params[k] << v params[k] << v
@ -67,6 +69,9 @@ module Rack
module_function :parse_nested_query module_function :parse_nested_query
def normalize_params(params, name, v = nil) def normalize_params(params, name, v = nil)
if v and v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
name =~ %r(\A[\[\]]*([^\[\]]+)\]*) name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
k = $1 || '' k = $1 || ''
after = $' || '' after = $' || ''
@ -109,6 +114,25 @@ module Rack
end end
module_function :build_query 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. # Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string) def escape_html(string)
string.to_s.gsub("&", "&amp;"). string.to_s.gsub("&", "&amp;").
@ -149,6 +173,54 @@ module Rack
end end
module_function :select_best_encoding module_function :select_best_encoding
def set_cookie_header!(header, key, value)
case value
when Hash
domain = "; domain=" + value[:domain] if value[:domain]
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime.
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
end
value = [value] unless Array === value
cookie = escape(key) + "=" +
value.map { |v| escape v }.join("&") +
"#{domain}#{path}#{expires}#{secure}#{httponly}"
case header["Set-Cookie"]
when Array
header["Set-Cookie"] << cookie
when String
header["Set-Cookie"] = [header["Set-Cookie"], cookie]
when nil
header["Set-Cookie"] = cookie
end
nil
end
module_function :set_cookie_header!
def delete_cookie_header!(header, key, value = {})
unless Array === header["Set-Cookie"]
header["Set-Cookie"] = [header["Set-Cookie"]].compact
end
header["Set-Cookie"].reject! { |cookie|
cookie =~ /\A#{escape(key)}=/
}
set_cookie_header!(header, key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
nil
end
module_function :delete_cookie_header!
# Return the bytesize of String; uses String#length under Ruby 1.8 and # Return the bytesize of String; uses String#length under Ruby 1.8 and
# String#bytesize under 1.9. # String#bytesize under 1.9.
if ''.respond_to?(:bytesize) if ''.respond_to?(:bytesize)
@ -191,11 +263,22 @@ module Rack
# A case-insensitive Hash that preserves the original case of a # A case-insensitive Hash that preserves the original case of a
# header when set. # header when set.
class HeaderHash < Hash class HeaderHash < Hash
def self.new(hash={})
HeaderHash === hash ? hash : super(hash)
end
def initialize(hash={}) def initialize(hash={})
super()
@names = {} @names = {}
hash.each { |k, v| self[k] = v } hash.each { |k, v| self[k] = v }
end end
def each
super do |k, v|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
end
end
def to_hash def to_hash
inject({}) do |hash, (k,v)| inject({}) do |hash, (k,v)|
if v.respond_to? :to_ary if v.respond_to? :to_ary
@ -208,21 +291,24 @@ module Rack
end end
def [](k) def [](k)
super @names[k.downcase] super(@names[k] ||= @names[k.downcase])
end end
def []=(k, v) def []=(k, v)
delete k delete k
@names[k.downcase] = k @names[k] = @names[k.downcase] = k
super k, v super k, v
end end
def delete(k) def delete(k)
super @names.delete(k.downcase) canonical = k.downcase
result = super @names.delete(canonical)
@names.delete_if { |name,| name.downcase == canonical }
result
end end
def include?(k) def include?(k)
@names.has_key? k.downcase @names.include?(k) || @names.include?(k.downcase)
end end
alias_method :has_key?, :include? alias_method :has_key?, :include?
@ -238,13 +324,23 @@ module Rack
hash = dup hash = dup
hash.merge! other hash.merge! other
end end
def replace(other)
clear
other.each { |k, v| self[k] = v }
self
end
end end
# Every standard HTTP code mapped to the appropriate message. # Every standard HTTP code mapped to the appropriate message.
# Stolen from Mongrel. # Generated with:
# curl -s http://www.iana.org/assignments/http-status-codes | \
# ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
# puts " #{m[1]} => \x27#{m[2].strip}x27,"'
HTTP_STATUS_CODES = { HTTP_STATUS_CODES = {
100 => 'Continue', 100 => 'Continue',
101 => 'Switching Protocols', 101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK', 200 => 'OK',
201 => 'Created', 201 => 'Created',
202 => 'Accepted', 202 => 'Accepted',
@ -252,12 +348,15 @@ module Rack
204 => 'No Content', 204 => 'No Content',
205 => 'Reset Content', 205 => 'Reset Content',
206 => 'Partial Content', 206 => 'Partial Content',
207 => 'Multi-Status',
226 => 'IM Used',
300 => 'Multiple Choices', 300 => 'Multiple Choices',
301 => 'Moved Permanently', 301 => 'Moved Permanently',
302 => 'Found', 302 => 'Found',
303 => 'See Other', 303 => 'See Other',
304 => 'Not Modified', 304 => 'Not Modified',
305 => 'Use Proxy', 305 => 'Use Proxy',
306 => 'Reserved',
307 => 'Temporary Redirect', 307 => 'Temporary Redirect',
400 => 'Bad Request', 400 => 'Bad Request',
401 => 'Unauthorized', 401 => 'Unauthorized',
@ -273,27 +372,76 @@ module Rack
411 => 'Length Required', 411 => 'Length Required',
412 => 'Precondition Failed', 412 => 'Precondition Failed',
413 => 'Request Entity Too Large', 413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large', 414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type', 415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable', 416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed', 417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
426 => 'Upgrade Required',
500 => 'Internal Server Error', 500 => 'Internal Server Error',
501 => 'Not Implemented', 501 => 'Not Implemented',
502 => 'Bad Gateway', 502 => 'Bad Gateway',
503 => 'Service Unavailable', 503 => 'Service Unavailable',
504 => 'Gateway Timeout', 504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported' 505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
510 => 'Not Extended',
} }
# Responses with HTTP status codes that should not have an entity body # Responses with HTTP status codes that should not have an entity body
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304) STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)|
hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code
hash
}
def status_code(status)
if status.is_a?(Symbol)
SYMBOL_TO_STATUS_CODE[status] || 500
else
status.to_i
end
end
module_function :status_code
# A multipart form data parser, adapted from IOWA. # A multipart form data parser, adapted from IOWA.
# #
# Usually, Rack::Request#POST takes care of calling this. # Usually, Rack::Request#POST takes care of calling this.
module Multipart 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" EOL = "\r\n"
MULTIPART_BOUNDARY = "AaB03x"
def self.parse_multipart(env) def self.parse_multipart(env)
unless env['CONTENT_TYPE'] =~ unless env['CONTENT_TYPE'] =~
@ -388,7 +536,8 @@ module Rack
Utils.normalize_params(params, name, data) unless data.nil? Utils.normalize_params(params, name, data) unless data.nil?
break if buf.empty? || content_length == -1 # break if we're at the end of a buffer, but not if it is the end of a field
break if (buf.empty? && $1 != EOL) || content_length == -1
} }
input.rewind input.rewind
@ -396,6 +545,76 @@ module Rack
params params
end end
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 end
end end

38
vendor/plugins/rack/rack.gemspec vendored Normal file
View file

@ -0,0 +1,38 @@
Gem::Specification.new do |s|
s.name = "rack"
s.version = "1.1.0"
s.platform = Gem::Platform::RUBY
s.summary = "a modular Ruby webserver interface"
s.description = <<-EOF
Rack provides minimal, modular and adaptable interface for developing
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 = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] +
%w(COPYING KNOWN-ISSUES rack.gemspec RDOX README SPEC)
s.bindir = 'bin'
s.executables << 'rackup'
s.require_path = 'lib'
s.has_rdoc = true
s.extra_rdoc_files = ['README', 'SPEC', '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 'thin'
end

View file

@ -1,5 +1,4 @@
#!/usr/bin/env ../../bin/rackup #!/usr/bin/env ruby -I ../../lib ../../bin/rackup -E deployment -I ../../lib
#\ -E deployment -I ../../lib
# -*- ruby -*- # -*- ruby -*-
require '../testrequest' require '../testrequest'

View file

@ -0,0 +1,259 @@
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="bbbbbbbbbbbbbbb"
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="ccccccc"
ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="file.name"
INPUTMSG.gz
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="file.content_type"
application/octet-stream
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="file.path"
/var/tmp/uploads/4/0001728414
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="file.md5"
aa73198feb4b4c1c3186f5e7466cbbcc
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="file.size"
13212
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="size"
80892
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="mail_server_id"
<1111111111.22222222.3333333333333.JavaMail.app@ffff-aaaa.dddd>
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="addresses"
{"campsy_programmer@pinkedum.com":{"domain":"pinkedum.com","name":"Campsy Programmer","type":["env_sender"],"mailbox":"campsy_programmer"},"tex@rapidcity.com":{"domain":"rapidcity.com","name":"Big Tex","type":["env_recipients","to"],"mailbox":"tex"},"group-digests@linkedin.com":{"domain":"linkedin.com","name":"Group Members","type":["from"],"mailbox":"group-digests"}}
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="received_on"
2009-11-15T14:21:11Z
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="id"
dbfd9804d26d11deab24e3037639bf77
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon
Content-Disposition: form-data; name="ip_address"
127.0.0.1
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--

View file

@ -0,0 +1,814 @@
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="authenticity_token"
XCUgSyYsZ+iHQunq/yCSKFzjeVmsXV/WcphHQ0J+05I=
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[SESE]"
BooBar
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[BBBBBBBBB]"
18
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[CCCCCCCCCCCCCCCCCCC]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[STARTFOO]"
2009-11-04
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ENDFOO]"
2009-12-01
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[EEEEEEEEEE]"
10000
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[FFFFFFFFF]"
boskoizcool
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[YYYYYYYYYYYYYYY]"
5.00
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ZZZZZZZZZZZZZ]"
mille
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[XXXXXXXXXXXXXXXXXXXXX]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][9]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][10]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][11]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][12]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][13]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][14]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][15]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][16]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][17]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][18]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][19]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][20]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][21]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][22]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][23]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][0]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][1]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][2]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][3]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][4]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][5]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][6]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][7]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][8]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][ZEZE]"
PLAPLAPLAINCINCINC
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][123412341234e]"
SITE
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][12345678901]"
56
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_type]"
none
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][has_hashashas_has]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_freee]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_frefre]"
forever
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][self_block]"
0
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][COUCOUN]"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][REGREG]"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][c1c1]"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA_TARTARTAR_wizard_rule"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_rule]"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[selection_selection]"
R
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][selection_selection]"
1
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][ba_unit_id]"
1015
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][selection_selection]"
2
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][ba_unit_id]"
1017
------WebKitFormBoundaryWsY0GnpbI5U7ztzo
Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[tile_name]"
------WebKitFormBoundaryWsY0GnpbI5U7ztzo--

View file

@ -0,0 +1 @@
contents

View file

@ -0,0 +1 @@
log_output

View file

@ -0,0 +1,31 @@
require "#{File.dirname(__FILE__)}/../testrequest"
$stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w")
class EnvMiddleware
def initialize(app)
@app = app
end
def call(env)
# provides a way to test that lint is present
if env["PATH_INFO"] == "/broken_lint"
return [200, {}, ["Broken Lint"]]
# provides a way to kill the process without knowing the pid
elsif env["PATH_INFO"] == "/die"
exit!
end
env["test.$DEBUG"] = $DEBUG
env["test.$EVAL"] = BUKKIT if defined?(BUKKIT)
env["test.$VERBOSE"] = $VERBOSE
env["test.$LOAD_PATH"] = $LOAD_PATH
env["test.stderr"] = File.expand_path($stderr.path)
env["test.Ping"] = defined?(Ping)
env["test.pid"] = Process.pid
@app.call(env)
end
end
use EnvMiddleware
run TestRequest.new

View file

@ -28,15 +28,13 @@ context "Rack::Cascade" do
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
end end
specify "should fail if empty" do specify "should return 404 if empty" do
lambda { Rack::MockRequest.new(Rack::Cascade.new([])).get("/") }. Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
should.raise(ArgumentError)
end end
specify "should append new app" do specify "should append new app" do
cascade = Rack::Cascade.new([], [404, 403]) cascade = Rack::Cascade.new([], [404, 403])
lambda { Rack::MockRequest.new(cascade).get('/cgi/test') }. Rack::MockRequest.new(cascade).get('/').should.be.not_found
should.raise(ArgumentError)
cascade << app2 cascade << app2
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found

View file

@ -36,7 +36,7 @@ context "Rack::Handler::CGI" do
specify "should have rack headers" do specify "should have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,0] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be false response["rack.multithread"].should.be false
response["rack.multiprocess"].should.be true response["rack.multiprocess"].should.be true
response["rack.run_once"].should.be true response["rack.run_once"].should.be true
@ -47,7 +47,7 @@ context "Rack::Handler::CGI" do
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/" response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil response["PATH_INFO"].should.equal ""
response["QUERY_STRING"].should.equal "" response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal "" response["test.postdata"].should.equal ""

View file

@ -7,26 +7,55 @@ require 'rack/mock'
context "Rack::CommonLogger" do context "Rack::CommonLogger" do
app = lambda { |env| app = lambda { |env|
[200,
{"Content-Type" => "text/html", "Content-Length" => length.to_s},
[obj]]}
app_without_length = lambda { |env|
[200, [200,
{"Content-Type" => "text/html"}, {"Content-Type" => "text/html"},
["foo"]]} []]}
app_with_zero_length = lambda { |env|
[200,
{"Content-Type" => "text/html", "Content-Length" => "0"},
[]]}
specify "should log to rack.errors by default" do specify "should log to rack.errors by default" do
log = StringIO.new
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
res.errors.should.not.be.empty res.errors.should.not.be.empty
res.errors.should =~ /GET / res.errors.should =~ /"GET \/ " 200 #{length} /
res.errors.should =~ / 200 / # status
res.errors.should =~ / 3 / # length
end end
specify "should log to anything with <<" do specify "should log to anything with +write+" do
log = "" log = StringIO.new
res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
log.should =~ /GET / log.string.should =~ /"GET \/ " 200 #{length} /
log.should =~ / 200 / # status end
log.should =~ / 3 / # length
specify "should log - content length if header is missing" do
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")
res.errors.should.not.be.empty
res.errors.should =~ /"GET \/ " 200 - /
end
specify "should log - content length if header is zero" do
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/")
res.errors.should.not.be.empty
res.errors.should =~ /"GET \/ " 200 - /
end
def length
self.class.length
end
def self.length
123
end
def self.obj
"hello world"
end end
end end

View file

@ -0,0 +1,24 @@
require 'test/spec'
require 'rack/mock'
require 'rack/builder'
require 'rack/content_length'
require 'rack/config'
context "Rack::Config" do
specify "should accept a block that modifies the environment" do
app = Rack::Builder.new do
use Rack::Lint
use Rack::ContentLength
use Rack::Config do |env|
env['greeting'] = 'hello'
end
run lambda { |env|
[200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
}
end
response = Rack::MockRequest.new(app).get('/')
response.body.should.equal('hello')
end
end

View file

@ -6,7 +6,7 @@ require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::Directory" do context "Rack::Directory" do
DOCROOT = File.expand_path(File.dirname(__FILE__)) DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] } FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
app = Rack::Directory.new DOCROOT, FILE_CATCH app = Rack::Directory.new DOCROOT, FILE_CATCH

View file

@ -0,0 +1,17 @@
require 'test/spec'
require 'rack/mock'
require 'rack/etag'
context "Rack::ETag" do
specify "sets ETag if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
response = Rack::ETag.new(app).call({})
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
end
specify "does not change ETag if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
response = Rack::ETag.new(app).call({})
response[1]['ETag'].should.equal "\"abc\""
end
end

View file

@ -36,7 +36,7 @@ context "Rack::Handler::FastCGI" do
specify "should have rack headers" do specify "should have rack headers" do
GET("/test.fcgi") GET("/test.fcgi")
response["rack.version"].should.equal [1,0] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be false response["rack.multithread"].should.be false
response["rack.multiprocess"].should.be true response["rack.multiprocess"].should.be true
response["rack.run_once"].should.be false response["rack.run_once"].should.be false
@ -47,7 +47,7 @@ context "Rack::Handler::FastCGI" do
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test.fcgi" response["SCRIPT_NAME"].should.equal "/test.fcgi"
response["REQUEST_PATH"].should.equal "/" response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil response["PATH_INFO"].should.equal ""
response["QUERY_STRING"].should.equal "" response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal "" response["test.postdata"].should.equal ""

View file

@ -6,7 +6,7 @@ require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::File" do context "Rack::File" do
DOCROOT = File.expand_path(File.dirname(__FILE__)) DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
specify "serves files" do specify "serves files" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).

View file

@ -71,6 +71,11 @@ context "Rack::Lint" do
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
message.should.equal("session [] must respond to store and []=") message.should.equal("session [] must respond to store and []=")
lambda {
Rack::Lint.new(nil).call(env("rack.logger" => []))
}.should.raise(Rack::Lint::LintError).
message.should.equal("logger [] must respond to info")
lambda { lambda {
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?")) Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
@ -454,46 +459,48 @@ context "Rack::Lint" do
end end
specify "passes valid read calls" do specify "passes valid read calls" do
hello_str = "hello world"
hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read env["rack.input"].read
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read(0) env["rack.input"].read(0)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read(1) env["rack.input"].read(1)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil) env["rack.input"].read(nil)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read(nil, '') env["rack.input"].read(nil, '')
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].read(1, '') env["rack.input"].read(1, '')
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
}).call(env({"rack.input" => StringIO.new("hello world")})) }).call(env({"rack.input" => StringIO.new(hello_str)}))
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
end end
end end

View file

@ -0,0 +1,21 @@
require 'rack/logger'
require 'rack/lint'
require 'stringio'
context "Rack::Logger" do
specify "logs to rack.errors" do
app = lambda { |env|
log = env['rack.logger']
log.debug("Created logger")
log.info("Program started")
log.warn("Nothing to do!")
[200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
}
errors = StringIO.new
Rack::Logger.new(app).call({'rack.errors' => errors})
errors.string.should.match "INFO -- : Program started"
errors.string.should.match "WARN -- : Nothing to do"
end
end

View file

@ -93,6 +93,92 @@ context "Rack::MockRequest" do
env["rack.url_scheme"].should.equal "https" env["rack.url_scheme"].should.equal "https"
end end
specify "should set SSL port and HTTP flag on when using https" do
res = Rack::MockRequest.new(app).
get("https://example.org/foo")
res.should.be.kind_of Rack::MockResponse
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["SERVER_NAME"].should.equal "example.org"
env["SERVER_PORT"].should.equal "443"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/foo"
env["rack.url_scheme"].should.equal "https"
env["HTTPS"].should.equal "on"
end
specify "should prepend slash to uri path" do
res = Rack::MockRequest.new(app).
get("foo")
res.should.be.kind_of Rack::MockResponse
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 "/foo"
env["rack.url_scheme"].should.equal "http"
end
specify "should properly convert method name to an uppercase string" do
res = Rack::MockRequest.new(app).request(:get)
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
end
specify "should accept params and build query string for GET requests" do
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}})
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["QUERY_STRING"].should.match "baz=2"
env["QUERY_STRING"].should.match "foo[bar]=1"
env["PATH_INFO"].should.equal "/foo"
env["mock.postdata"].should.equal ""
end
specify "should accept raw input in params for GET requests" do
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["QUERY_STRING"].should.match "baz=2"
env["QUERY_STRING"].should.match "foo[bar]=1"
env["PATH_INFO"].should.equal "/foo"
env["mock.postdata"].should.equal ""
end
specify "should accept params and build url encoded params for POST requests" do
res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}})
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/foo"
env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded"
env["mock.postdata"].should.equal "foo[bar]=1"
end
specify "should accept raw input in params for POST requests" do
res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/foo"
env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded"
env["mock.postdata"].should.equal "foo[bar]=1"
end
specify "should accept params and build multipart encoded params for POST requests" do
files = Rack::Utils::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files })
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/foo"
env["CONTENT_TYPE"].should.equal "multipart/form-data; boundary=AaB03x"
env["mock.postdata"].length.should.equal 206
end
specify "should behave valid according to the Rack spec" do specify "should behave valid according to the Rack spec" do
lambda { lambda {
res = Rack::MockRequest.new(app). res = Rack::MockRequest.new(app).
@ -130,7 +216,7 @@ context "Rack::MockResponse" do
res.original_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_type.should.equal "text/yaml" res.content_type.should.equal "text/yaml"
res.content_length.should.be 401 # needs change often. res.content_length.should.be 414 # needs change often.
res.location.should.be.nil res.location.should.be.nil
end end

View file

@ -41,7 +41,7 @@ context "Rack::Handler::Mongrel" do
specify "should have rack headers" do specify "should have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,0] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be true response["rack.multithread"].should.be true
response["rack.multiprocess"].should.be false response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false response["rack.run_once"].should.be false
@ -52,7 +52,7 @@ context "Rack::Handler::Mongrel" do
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test" response["REQUEST_PATH"].should.equal "/test"
response["PATH_INFO"].should.be.nil response["PATH_INFO"].should.be.equal ""
response["QUERY_STRING"].should.equal "" response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal "" response["test.postdata"].should.equal ""

View file

@ -0,0 +1,13 @@
require 'rack/nulllogger'
require 'rack/lint'
require 'rack/mock'
context "Rack::NullLogger" do
specify "acks as a nop logger" do
app = lambda { |env|
env['rack.logger'].warn "b00m"
[200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
}
Rack::NullLogger.new(app).call({})
end
end

View file

@ -35,9 +35,18 @@ context "Rack::Request" do
req.host.should.equal "www2.example.org" req.host.should.equal "www2.example.org"
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org:9292") Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292")
req.host.should.equal "example.org" req.host.should.equal "example.org"
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292")
req.host.should.equal "example.org"
env = Rack::MockRequest.env_for("/", "SERVER_ADDR" => "192.168.1.1", "SERVER_PORT" => "9292")
env.delete("SERVER_NAME")
req = Rack::Request.new(env)
req.host.should.equal "192.168.1.1"
env = Rack::MockRequest.env_for("/") env = Rack::MockRequest.env_for("/")
env.delete("SERVER_NAME") env.delete("SERVER_NAME")
req = Rack::Request.new(env) req = Rack::Request.new(env)
@ -52,9 +61,16 @@ context "Rack::Request" do
req.params.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "can parse POST data" do specify "raises if rack.input is missing" do
req = Rack::Request.new({})
lambda { req.POST }.should.raise(RuntimeError)
end
specify "can parse POST data when method is POST and no Content-Type given" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla") Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST',
:input => "foo=bar&quux=bla")
req.content_type.should.be.nil req.content_type.should.be.nil
req.media_type.should.be.nil req.media_type.should.be.nil
req.query_string.should.equal "foo=quux" req.query_string.should.equal "foo=quux"
@ -63,7 +79,7 @@ context "Rack::Request" do
req.params.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "can parse POST data with explicit content type" do specify "can parse POST data with explicit content type regardless of method" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
@ -78,6 +94,7 @@ context "Rack::Request" do
specify "does not parse POST data when media type is not form-data" do specify "does not parse POST data when media type is not form-data" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST',
"CONTENT_TYPE" => 'text/plain;charset=utf-8', "CONTENT_TYPE" => 'text/plain;charset=utf-8',
:input => "foo=bar&quux=bla") :input => "foo=bar&quux=bla")
req.content_type.should.equal 'text/plain;charset=utf-8' req.content_type.should.equal 'text/plain;charset=utf-8'
@ -88,6 +105,16 @@ context "Rack::Request" do
req.body.read.should.equal "foo=bar&quux=bla" req.body.read.should.equal "foo=bar&quux=bla"
end end
specify "can parse POST data on PUT when media type is form-data" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'PUT',
"CONTENT_TYPE" => 'application/x-www-form-urlencoded',
:input => "foo=bar&quux=bla")
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.body.read.should.equal "foo=bar&quux=bla"
end
specify "rewinds input after parsing POST data" do specify "rewinds input after parsing POST data" do
input = StringIO.new("foo=bar&quux=bla") input = StringIO.new("foo=bar&quux=bla")
req = Rack::Request.new \ req = Rack::Request.new \
@ -100,7 +127,8 @@ context "Rack::Request" do
specify "cleans up Safari's ajax POST body" do specify "cleans up Safari's ajax POST body" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", :input => "foo=bar&quux=bla\0") Rack::MockRequest.env_for("/",
'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0")
req.POST.should.equal "foo" => "bar", "quux" => "bla" req.POST.should.equal "foo" => "bar", "quux" => "bla"
end end
@ -147,9 +175,21 @@ context "Rack::Request" do
req.referer.should.equal "/" req.referer.should.equal "/"
end end
specify "user agent should be extracted correct" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)")
req.user_agent.should.equal "Mozilla/4.0 (compatible)"
req = Rack::Request.new \
Rack::MockRequest.env_for("/")
req.user_agent.should.equal nil
end
specify "can cache, but invalidates the cache" do specify "can cache, but invalidates the cache" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla") Rack::MockRequest.env_for("/?foo=quux",
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
:input => "foo=bar&quux=bla")
req.GET.should.equal "foo" => "quux" req.GET.should.equal "foo" => "quux"
req.GET.should.equal "foo" => "quux" req.GET.should.equal "foo" => "quux"
req.env["QUERY_STRING"] = "bla=foo" req.env["QUERY_STRING"] = "bla=foo"
@ -424,6 +464,7 @@ Content-Transfer-Encoding: base64\r
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
--AaB03x--\r --AaB03x--\r
EOF EOF
input.force_encoding("ASCII-8BIT") if input.respond_to? :force_encoding
res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/", res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x", "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input) "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input)

View file

@ -118,6 +118,9 @@ context "Rack::Response" do
r = Rack::Response.new([], 500) r = Rack::Response.new([], 500)
r.status.should.equal 500 r.status.should.equal 500
r = Rack::Response.new([], "200 OK")
r.status.should.equal 200
end end
specify "has a constructor that can take a block" do specify "has a constructor that can take a block" do

View file

@ -0,0 +1,35 @@
require 'test/spec'
require 'rack/mock'
require 'rack/runtime'
context "Rack::Runtime" do
specify "sets X-Runtime is none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::Runtime.new(app).call({})
response[1]['X-Runtime'].should =~ /[\d\.]+/
end
specify "does not set the X-Runtime if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] }
response = Rack::Runtime.new(app).call({})
response[1]['X-Runtime'].should == "foobar"
end
specify "should allow a suffix to be set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::Runtime.new(app, "Test").call({})
response[1]['X-Runtime-Test'].should =~ /[\d\.]+/
end
specify "should allow multiple timers to be set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
runtime1 = Rack::Runtime.new(app, "App")
runtime2 = Rack::Runtime.new(runtime1, "All")
response = runtime2.call({})
response[1]['X-Runtime-App'].should =~ /[\d\.]+/
response[1]['X-Runtime-All'].should =~ /[\d\.]+/
Float(response[1]['X-Runtime-All']).should > Float(response[1]['X-Runtime-App'])
end
end

View file

@ -0,0 +1,86 @@
require 'test/spec'
require 'rack/mock'
require 'rack/sendfile'
context "Rack::File" do
specify "should respond to #to_path" do
Rack::File.new(Dir.pwd).should.respond_to :to_path
end
end
context "Rack::Sendfile" do
def sendfile_body
res = ['Hello World']
def res.to_path ; "/tmp/hello.txt" ; end
res
end
def simple_app(body=sendfile_body)
lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
end
def sendfile_app(body=sendfile_body)
Rack::Sendfile.new(simple_app(body))
end
setup do
@request = Rack::MockRequest.new(sendfile_app)
end
def request(headers={})
yield @request.get('/', headers)
end
specify "does nothing when no X-Sendfile-Type header present" do
request do |response|
response.should.be.ok
response.body.should.equal 'Hello World'
response.headers.should.not.include 'X-Sendfile'
end
end
specify "sets X-Sendfile response header and discards body" do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
response.should.be.ok
response.body.should.be.empty
response.headers['X-Sendfile'].should.equal '/tmp/hello.txt'
end
end
specify "sets X-Lighttpd-Send-File response header and discards body" do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
response.should.be.ok
response.body.should.be.empty
response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt'
end
end
specify "sets X-Accel-Redirect response header and discards body" do
headers = {
'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/'
}
request headers do |response|
response.should.be.ok
response.body.should.be.empty
response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt'
end
end
specify 'writes to rack.error when no X-Accel-Mapping is specified' do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
response.should.be.ok
response.body.should.equal 'Hello World'
response.headers.should.not.include 'X-Accel-Redirect'
response.errors.should.include 'X-Accel-Mapping'
end
end
specify 'does nothing when body does not respond to #to_path' do
@request = Rack::MockRequest.new(sendfile_app(['Not a file...']))
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
response.body.should.equal 'Not a file...'
response.headers.should.not.include 'X-Sendfile'
end
end
end

View file

@ -8,7 +8,7 @@ begin
context "Rack::Session::Memcache" do context "Rack::Session::Memcache" do
session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key] session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
session_match = /#{session_key}=[0-9a-fA-F]+;/ session_match = /#{session_key}=([0-9a-fA-F]+);/
incrementor = lambda do |env| incrementor = lambda do |env|
env["rack.session"]["counter"] ||= 0 env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1 env["rack.session"]["counter"] += 1
@ -27,14 +27,20 @@ begin
incrementor.call(env) incrementor.call(env)
end end
specify "MemCache can connect to existing server" do specify "faults on no connection" do
test_pool = MemCache.new :namespace => 'test:rack:session' if RUBY_VERSION < "1.9"
lambda do
Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver'
end.should.raise
else
lambda do
Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver'
end.should.raise ArgumentError
end
end end
specify "faults on no connection" do specify "connect to existing server" do
lambda do test_pool = MemCache.new incrementor, :namespace => 'test:rack:session'
Rack::Session::Memcache.new(incrementor, :memcache_server => '')
end.should.raise
end end
specify "creates a new cookie" do specify "creates a new cookie" do
@ -151,6 +157,31 @@ begin
res3.body.should.equal '{"counter"=>4}' res3.body.should.equal '{"counter"=>4}'
end end
specify "deep hashes are correctly updated" do
store = nil
hash_check = proc do |env|
session = env['rack.session']
unless session.include? 'test'
session.update :a => :b, :c => { :d => :e },
:f => { :g => { :h => :i} }, 'test' => true
else
session[:f][:g][:h] = :j
end
[200, {}, session.inspect]
end
pool = Rack::Session::Memcache.new(hash_check)
req = Rack::MockRequest.new(pool)
res0 = req.get("/")
session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
ses0 = pool.pool.get(session_id, true)
res1 = req.get("/", "HTTP_COOKIE" => cookie)
ses1 = pool.pool.get(session_id, true)
ses1.should.not.equal ses0
end
# anyone know how to do this better? # anyone know how to do this better?
specify "multithread: should cleanly merge sessions" do specify "multithread: should cleanly merge sessions" do
next unless $DEBUG next unless $DEBUG
@ -161,7 +192,7 @@ begin
res = req.get('/') res = req.get('/')
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
cookie = res["Set-Cookie"] cookie = res["Set-Cookie"]
sess_id = cookie[/#{pool.key}=([^,;]+)/,1] session_id = cookie[session_match, 1]
delta_incrementor = lambda do |env| delta_incrementor = lambda do |env|
# emulate disconjoinment of threading # emulate disconjoinment of threading
@ -178,12 +209,12 @@ begin
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
end end
end.reverse.map{|t| t.run.join.value } end.reverse.map{|t| t.run.join.value }
r.each do |res| r.each do |request|
res['Set-Cookie'].should.equal cookie request['Set-Cookie'].should.equal cookie
res.body.should.include '"counter"=>2' request.body.should.include '"counter"=>2'
end end
session = pool.pool.get(sess_id) session = pool.pool.get(session_id)
session.size.should.be tnum+1 # counter session.size.should.be tnum+1 # counter
session['counter'].should.be 2 # meeeh session['counter'].should.be 2 # meeeh
@ -202,12 +233,12 @@ begin
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
end end
end.reverse.map{|t| t.run.join.value } end.reverse.map{|t| t.run.join.value }
r.each do |res| r.each do |request|
res['Set-Cookie'].should.equal cookie request['Set-Cookie'].should.equal cookie
res.body.should.include '"counter"=>3' request.body.should.include '"counter"=>3'
end end
session = pool.pool.get(sess_id) session = pool.pool.get(session_id)
session.size.should.be tnum+1 session.size.should.be tnum+1
session['counter'].should.be 3 session['counter'].should.be 3
@ -224,17 +255,19 @@ begin
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
end end
end.reverse.map{|t| t.run.join.value } end.reverse.map{|t| t.run.join.value }
r.each do |res| r.each do |request|
res['Set-Cookie'].should.equal cookie request['Set-Cookie'].should.equal cookie
res.body.should.include '"foo"=>"bar"' request.body.should.include '"foo"=>"bar"'
end end
session = pool.pool.get(sess_id) session = pool.pool.get(session_id)
session.size.should.be r.size+1 session.size.should.be r.size+1
session['counter'].should.be.nil? session['counter'].should.be.nil?
session['foo'].should.equal 'bar' session['foo'].should.equal 'bar'
end end
end end
rescue RuntimeError
$stderr.puts "Skipping Rack::Session::Memcache tests. Start memcached and try again."
rescue LoadError rescue LoadError
$stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again." $stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again."
end end

View file

@ -44,6 +44,12 @@ context "Rack::URLMap" do
res["X-ScriptName"].should.equal "/foo/bar" res["X-ScriptName"].should.equal "/foo/bar"
res["X-PathInfo"].should.equal "/" res["X-PathInfo"].should.equal "/"
res = Rack::MockRequest.new(map).get("/foo///bar//quux")
res.status.should.equal 200
res.should.be.ok
res["X-ScriptName"].should.equal "/foo/bar"
res["X-PathInfo"].should.equal "//quux"
res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh") res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh")
res.should.be.ok res.should.be.ok
res["X-ScriptName"].should.equal "/bleh/foo" res["X-ScriptName"].should.equal "/bleh/foo"
@ -182,4 +188,28 @@ context "Rack::URLMap" do
res["X-PathInfo"].should.equal "/" res["X-PathInfo"].should.equal "/"
res["X-ScriptName"].should.equal "" res["X-ScriptName"].should.equal ""
end end
specify "should not squeeze slashes" do
map = Rack::URLMap.new("/" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "root",
"X-PathInfo" => env["PATH_INFO"],
"X-ScriptName" => env["SCRIPT_NAME"]
}, [""]]},
"/foo" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "foo",
"X-PathInfo" => env["PATH_INFO"],
"X-ScriptName" => env["SCRIPT_NAME"]
}, [""]]}
)
res = Rack::MockRequest.new(map).get("/http://example.org/bar")
res.should.be.ok
res["X-Position"].should.equal "root"
res["X-PathInfo"].should.equal "/http://example.org/bar"
res["X-ScriptName"].should.equal ""
end
end end

View file

@ -30,7 +30,10 @@ context "Rack::Utils" do
end end
specify "should parse query strings correctly" do specify "should parse query strings correctly" do
Rack::Utils.parse_query("foo=bar").should.equal "foo" => "bar" Rack::Utils.parse_query("foo=bar").
should.equal "foo" => "bar"
Rack::Utils.parse_query("foo=\"bar\"").
should.equal "foo" => "bar"
Rack::Utils.parse_query("foo=bar&foo=quux"). Rack::Utils.parse_query("foo=bar&foo=quux").
should.equal "foo" => ["bar", "quux"] should.equal "foo" => ["bar", "quux"]
Rack::Utils.parse_query("foo=1&bar=2"). Rack::Utils.parse_query("foo=1&bar=2").
@ -47,6 +50,8 @@ context "Rack::Utils" do
should.equal "foo" => "" should.equal "foo" => ""
Rack::Utils.parse_nested_query("foo=bar"). Rack::Utils.parse_nested_query("foo=bar").
should.equal "foo" => "bar" should.equal "foo" => "bar"
Rack::Utils.parse_nested_query("foo=\"bar\"").
should.equal "foo" => "bar"
Rack::Utils.parse_nested_query("foo=bar&foo=quux"). Rack::Utils.parse_nested_query("foo=bar&foo=quux").
should.equal "foo" => "quux" should.equal "foo" => "quux"
@ -126,6 +131,53 @@ context "Rack::Utils" do
should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F" should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
end end
specify "should build nested query strings correctly" do
Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
should.equal "foo=1&bar=2"
Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
Rack::Utils.build_nested_query("foo" => [nil]).
should.equal "foo[]"
Rack::Utils.build_nested_query("foo" => [""]).
should.equal "foo[]="
Rack::Utils.build_nested_query("foo" => ["bar"]).
should.equal "foo[]=bar"
# The ordering of the output query string is unpredictable with 1.8's
# unordered hash. Test that build_nested_query performs the inverse
# function of parse_nested_query.
[{"foo" => nil, "bar" => ""},
{"foo" => "bar", "baz" => ""},
{"foo" => ["1", "2"]},
{"foo" => "bar", "baz" => ["1", "2", "3"]},
{"foo" => ["bar"], "baz" => ["1", "2", "3"]},
{"foo" => ["1", "2"]},
{"foo" => "bar", "baz" => ["1", "2", "3"]},
{"x" => {"y" => {"z" => "1"}}},
{"x" => {"y" => {"z" => ["1"]}}},
{"x" => {"y" => {"z" => ["1", "2"]}}},
{"x" => {"y" => [{"z" => "1"}]}},
{"x" => {"y" => [{"z" => ["1"]}]}},
{"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
{"x" => {"y" => [{"v" => {"w" => "1"}}]}},
{"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
{"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
{"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
].each { |params|
qs = Rack::Utils.build_nested_query(params)
Rack::Utils.parse_nested_query(qs).should.equal params
}
lambda { Rack::Utils.build_nested_query("foo=bar") }.
should.raise(ArgumentError).
message.should.equal "value must be a Hash"
end
specify "should figure out which encodings are acceptable" do specify "should figure out which encodings are acceptable" do
helper = lambda do |a, b| helper = lambda do |a, b|
request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a)) request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
@ -152,6 +204,18 @@ context "Rack::Utils" do
specify "should return the bytesize of String" do specify "should return the bytesize of String" do
Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6 Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
end end
specify "should return status code for integer" do
Rack::Utils.status_code(200).should.equal 200
end
specify "should return status code for string" do
Rack::Utils.status_code("200").should.equal 200
end
specify "should return status code for symbol" do
Rack::Utils.status_code(:ok).should.equal 200
end
end end
context "Rack::Utils::HeaderHash" do context "Rack::Utils::HeaderHash" do
@ -191,6 +255,13 @@ context "Rack::Utils::HeaderHash" do
h.to_hash.should.equal({ "foo" => "bar\nbaz" }) h.to_hash.should.equal({ "foo" => "bar\nbaz" })
end end
specify "should replace hashes correctly" do
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
j = {"foo" => "bar"}
h.replace(j)
h["foo"].should.equal "bar"
end
specify "should be able to delete the given key case-sensitively" do specify "should be able to delete the given key case-sensitively" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("foo") h.delete("foo")
@ -214,6 +285,22 @@ context "Rack::Utils::HeaderHash" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("Hello").should.be.nil h.delete("Hello").should.be.nil
end end
specify "should avoid unnecessary object creation if possible" do
a = Rack::Utils::HeaderHash.new("foo" => "bar")
b = Rack::Utils::HeaderHash.new(a)
b.object_id.should.equal(a.object_id)
b.should.equal(a)
end
specify "should convert Array values to Strings when responding to #each" do
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
h.each do |k,v|
k.should.equal("foo")
v.should.equal("bar\nbaz")
end
end
end end
context "Rack::Utils::Context" do context "Rack::Utils::Context" do
@ -372,9 +459,83 @@ context "Rack::Utils::Multipart" do
input.read.length.should.equal 197 input.read.length.should.equal 197
end end
specify "builds multipart body" do
files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
options = {
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
"CONTENT_LENGTH" => data.length.to_s,
:input => StringIO.new(data)
}
env = Rack::MockRequest.env_for("/", options)
params = Rack::Utils::Multipart.parse_multipart(env)
params["submit-name"].should.equal "Larry"
params["files"][:filename].should.equal "file1.txt"
params["files"][:tempfile].read.should.equal "contents"
end
specify "builds nested multipart body" do
files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
options = {
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
"CONTENT_LENGTH" => data.length.to_s,
:input => StringIO.new(data)
}
env = Rack::MockRequest.env_for("/", options)
params = Rack::Utils::Multipart.parse_multipart(env)
params["people"][0]["submit-name"].should.equal "Larry"
params["people"][0]["files"][:filename].should.equal "file1.txt"
params["people"][0]["files"][:tempfile].read.should.equal "contents"
end
specify "can parse fields that end at the end of the buffer" do
input = File.read(multipart_file("bad_robots"))
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
"CONTENT_LENGTH" => input.size,
:input => input)
req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
req.POST['addresses'].should.not.equal nil
end
specify "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
options = {
"CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
"CONTENT_LENGTH" => data.length.to_s,
:input => StringIO.new(data)
}
env = Rack::MockRequest.env_for("/", options)
params = Rack::Utils::Multipart.parse_multipart(env)
params.should.not.equal nil
params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
end
specify "should return nil if no UploadedFiles were used" do
data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
data.should.equal nil
end
specify "should raise ArgumentError if params is not a Hash" do
lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }.
should.raise(ArgumentError).
message.should.equal "value must be a Hash"
end
private private
def multipart_fixture(name) def multipart_fixture(name)
file = File.join(File.dirname(__FILE__), "multipart", name.to_s) file = multipart_file(name)
data = File.open(file, 'rb') { |io| io.read } data = File.open(file, 'rb') { |io| io.read }
type = "multipart/form-data; boundary=AaB03x" type = "multipart/form-data; boundary=AaB03x"
@ -384,4 +545,8 @@ context "Rack::Utils::Multipart" do
"CONTENT_LENGTH" => length.to_s, "CONTENT_LENGTH" => length.to_s,
:input => StringIO.new(data) } :input => StringIO.new(data) }
end end
def multipart_file(name)
File.join(File.dirname(__FILE__), "multipart", name.to_s)
end
end end

View file

@ -39,7 +39,7 @@ context "Rack::Handler::WEBrick" do
specify "should have rack headers" do specify "should have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,0] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be true response["rack.multithread"].should.be true
response["rack.multiprocess"].should.be false response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false response["rack.run_once"].should.be false
@ -50,7 +50,7 @@ context "Rack::Handler::WEBrick" do
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/" response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil response["PATH_INFO"].should.be.equal ""
response["QUERY_STRING"].should.equal "" response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal "" response["test.postdata"].should.equal ""

154
vendor/plugins/rack/test/spec_rackup.rb vendored Normal file
View file

@ -0,0 +1,154 @@
require 'test/spec'
require 'testrequest'
require 'rack/server'
require 'open3'
begin
require "mongrel"
context "rackup" do
include TestRequest::Helpers
def run_rackup(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
flags = args.first
@host = options[:host] || "0.0.0.0"
@port = options[:port] || 9292
Dir.chdir("#{root}/test/rackup") do
@in, @rackup, @err = Open3.popen3("#{Gem.ruby} -S #{rackup} #{flags}")
end
return if options[:port] == false
# Wait until the server is available
begin
GET("/")
rescue
sleep 0.05
retry
end
end
def output
@rackup.read
end
after do
# This doesn't actually return a response, so we rescue
GET "/die" rescue nil
Dir["#{root}/**/*.pid"].each do |file|
File.delete(file)
end
File.delete("#{root}/log_output") if File.exist?("#{root}/log_output")
end
specify "rackup" do
run_rackup
response["PATH_INFO"].should.equal '/'
response["test.$DEBUG"].should.be false
response["test.$EVAL"].should.be nil
response["test.$VERBOSE"].should.be false
response["test.Ping"].should.be nil
response["SERVER_SOFTWARE"].should.not =~ /webrick/
end
specify "rackup --help" do
run_rackup "--help", :port => false
output.should.match /--port/
end
specify "rackup --port" do
run_rackup "--port 9000", :port => 9000
response["SERVER_PORT"].should.equal "9000"
end
specify "rackup --debug" do
run_rackup "--debug"
response["test.$DEBUG"].should.be true
end
specify "rackup --eval" do
run_rackup %{--eval "BUKKIT = 'BUKKIT'"}
response["test.$EVAL"].should.equal "BUKKIT"
end
specify "rackup --warn" do
run_rackup %{--warn}
response["test.$VERBOSE"].should.be true
end
specify "rackup --include" do
run_rackup %{--include /foo/bar}
response["test.$LOAD_PATH"].should.include "/foo/bar"
end
specify "rackup --require" do
run_rackup %{--require ping}
response["test.Ping"].should.equal "constant"
end
specify "rackup --server" do
run_rackup %{--server webrick}
response["SERVER_SOFTWARE"].should =~ /webrick/i
end
specify "rackup --host" do
run_rackup %{--host 127.0.0.1}, :host => "127.0.0.1"
response["REMOTE_ADDR"].should.equal "127.0.0.1"
end
specify "rackup --daemonize --pid" do
run_rackup %{--daemonize --pid testing.pid}
status.should.be 200
@rackup.should.be.eof?
Dir["#{root}/**/testing.pid"].should.not.be.empty?
end
specify "rackup --pid" do
run_rackup %{--pid testing.pid}
status.should.be 200
Dir["#{root}/**/testing.pid"].should.not.be.empty?
end
specify "rackup --version" do
run_rackup %{--version}, :port => false
output.should =~ /1.0/
end
specify "rackup --env development includes lint" do
run_rackup
GET("/broken_lint")
status.should.be 500
end
specify "rackup --env deployment does not include lint" do
run_rackup %{--env deployment}
GET("/broken_lint")
status.should.be 200
end
specify "rackup --env none does not include lint" do
run_rackup %{--env none}
GET("/broken_lint")
status.should.be 200
end
specify "rackup --env deployment does log" do
run_rackup %{--env deployment}
log = File.read(response["test.stderr"])
log.should.be.empty?
end
specify "rackup --env none does not log" do
run_rackup %{--env none}
GET("/")
log = File.read(response["test.stderr"])
log.should.be.empty?
end
end
rescue LoadError
$stderr.puts "Skipping rackup --server tests (mongrel is required). `gem install thin` and try again."
end

View file

@ -13,6 +13,17 @@ class TestRequest
module Helpers module Helpers
attr_reader :status, :response attr_reader :status, :response
ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
ENV["RUBYOPT"] = "-I#{ROOT}/lib -rubygems"
def root
ROOT
end
def rackup
"#{ROOT}/bin/rackup"
end
def GET(path, header={}) def GET(path, header={})
Net::HTTP.start(@host, @port) { |http| Net::HTTP.start(@host, @port) { |http|
user = header.delete(:user) user = header.delete(:user)
@ -22,7 +33,11 @@ class TestRequest
get.basic_auth user, passwd if user && passwd get.basic_auth user, passwd if user && passwd
http.request(get) { |response| http.request(get) { |response|
@status = response.code.to_i @status = response.code.to_i
begin
@response = YAML.load(response.body) @response = YAML.load(response.body)
rescue ArgumentError
@response = nil
end
} }
} }
end end

View file

@ -32,7 +32,7 @@ rescue LoadError
end end
begin begin
gem 'rack', '~> 1.1.0' gem 'rack', '~> 1.1.1'
require 'rack' require 'rack'
rescue Gem::LoadError rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/../../../plugins/rack/lib" $:.unshift "#{File.dirname(__FILE__)}/../../../plugins/rack/lib"