From 87957c6c76f4f1cc9310667f71a4c15a3f35eb23 Mon Sep 17 00:00:00 2001 From: Yehuda Katz + Carl Lerche Date: Sat, 21 Nov 2009 10:30:58 -0800 Subject: Test coverage for rackup Signed-off-by: Joshua Peek --- test/rackup/config.ru | 25 ++++++++++ test/spec_rackup.rb | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/testrequest.rb | 17 ++++++- 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 test/rackup/config.ru create mode 100644 test/spec_rackup.rb diff --git a/test/rackup/config.ru b/test/rackup/config.ru new file mode 100644 index 00000000..2490c6ed --- /dev/null +++ b/test/rackup/config.ru @@ -0,0 +1,25 @@ +require "#{File.dirname(__FILE__)}/../testrequest" + +$stderr = StringIO.new + +class EnvMiddleware + def initialize(app) + @app = app + end + + def call(env) + if env["PATH_INFO"] == "/broken_lint" + return [200, {}, ["Broken Lint"]] + end + + env["test.$DEBUG"] = $DEBUG + env["test.$EVAL"] = BUKKIT if defined?(BUKKIT) + env["test.$VERBOSE"] = $VERBOSE + env["test.$LOAD_PATH"] = $LOAD_PATH + env["test.Ping"] = defined?(Ping) + @app.call(env) + end +end + +use EnvMiddleware +run TestRequest.new diff --git a/test/spec_rackup.rb b/test/spec_rackup.rb new file mode 100644 index 00000000..99f48e3a --- /dev/null +++ b/test/spec_rackup.rb @@ -0,0 +1,126 @@ +require 'test/spec' +require 'testrequest' +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 + @rackup = IO.popen("#{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 + Process.kill(9, @rackup.pid) if @rackup + + Dir["#{root}/**/*.pid"].each do |file| + Process.kill(9, File.read(file).to_i) + File.delete(file) + end + 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 --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 --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" do + run_rackup %{--env deployment} + GET("/broken_lint") + status.should.be 200 + end +end +rescue LoadError + $stderr.puts "Skipping rackup --server tests (mongrel is required). `gem install thin` and try again." +end \ No newline at end of file diff --git a/test/testrequest.rb b/test/testrequest.rb index 7b7190cb..0da2b126 100644 --- a/test/testrequest.rb +++ b/test/testrequest.rb @@ -13,6 +13,17 @@ class TestRequest module Helpers 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={}) Net::HTTP.start(@host, @port) { |http| user = header.delete(:user) @@ -22,7 +33,11 @@ class TestRequest get.basic_auth user, passwd if user && passwd http.request(get) { |response| @status = response.code.to_i - @response = YAML.load(response.body) + begin + @response = YAML.load(response.body) + rescue ArgumentError + @response = nil + end } } end -- cgit v1.2.3-24-ge0c7 From 5db5d4e7432d026a7bddf344f97e8facd09128d8 Mon Sep 17 00:00:00 2001 From: Yehuda Katz + Carl Lerche Date: Sat, 21 Nov 2009 10:52:33 -0800 Subject: Refactor rackup into Rack::Server Signed-off-by: Joshua Peek --- bin/rackup | 178 +---------------------------------------------- lib/rack.rb | 1 + lib/rack/builder.rb | 15 ++++ lib/rack/handler.rb | 19 +++++ lib/rack/server.rb | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/rackup/config.ru | 10 ++- test/spec_rackup.rb | 38 ++++++++-- 7 files changed, 267 insertions(+), 183 deletions(-) create mode 100644 lib/rack/server.rb diff --git a/bin/rackup b/bin/rackup index 91abe17b..a6f98913 100755 --- a/bin/rackup +++ b/bin/rackup @@ -1,176 +1,2 @@ -#!/usr/bin/env ruby -# -*- ruby -*- - -require '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 - cfgfile.sub!(/^__END__\n.*/, '') - 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 +require "rack" +Rack::Server.start diff --git a/lib/rack.rb b/lib/rack.rb index 8d0815b6..703649cd 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -42,6 +42,7 @@ module Rack autoload :Mime, "rack/mime" autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" + autoload :Server, "rack/server" autoload :ShowExceptions, "rack/showexceptions" autoload :ShowStatus, "rack/showstatus" autoload :Static, "rack/static" diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb index 295235e5..f769b5fd 100644 --- a/lib/rack/builder.rb +++ b/lib/rack/builder.rb @@ -24,6 +24,21 @@ module Rack # You can use +map+ to construct a Rack::URLMap in a convenient way. class Builder + def self.parse_file(config, opts = nil) + if config =~ /\.ru$/ + cfgfile = ::File.read(config) + if cfgfile[/^#\\(.*)/] && opts + opts.parse! $1.split(/\s+/) + end + cfgfile.sub!(/^__END__\n.*/, '') + eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", + TOPLEVEL_BINDING, config + else + require config + Object.const_get(::File.basename(config, '.rb').capitalize) + end + end + def initialize(&block) @ins = [] instance_eval(&block) if block_given? diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb index 5624a1e7..3c09883e 100644 --- a/lib/rack/handler.rb +++ b/lib/rack/handler.rb @@ -22,6 +22,25 @@ module Rack 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, # then tries to require them but silences the LoadError if not found # diff --git a/lib/rack/server.rb b/lib/rack/server.rb new file mode 100644 index 00000000..ff041b3d --- /dev/null +++ b/lib/rack/server.rb @@ -0,0 +1,189 @@ +require 'optparse' + +module Rack + class Server + def self.start + new.start + end + + attr_accessor :options + + def initialize(options = nil) + @options = options + end + + def options + @options ||= begin + parse_options(ARGV) + end + end + + def default_options + { + :environment => "development", + :pid => nil, + :Port => 9292, + :Host => "0.0.0.0", + :AccessLog => [] + } + end + + def app + @app ||= begin + if !::File.exist? options[:rack_file] + abort "configuration #{options[:rack_file]} not found" + end + + Rack::Builder.parse_file(options[:rack_file], opt_parser) + 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 $DEBUG + require 'pp' + p options[:server] + pp wrapped_app + pp app + 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") + + opt_parser.parse! args + @options[:rack_file] = args.last || ::File.expand_path("config.ru") + @options + end + + def opt_parser + @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)") { + $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| + @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") { |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 + 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 diff --git a/test/rackup/config.ru b/test/rackup/config.ru index 2490c6ed..f1e2e1f3 100644 --- a/test/rackup/config.ru +++ b/test/rackup/config.ru @@ -1,22 +1,28 @@ require "#{File.dirname(__FILE__)}/../testrequest" -$stderr = StringIO.new +$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 diff --git a/test/spec_rackup.rb b/test/spec_rackup.rb index 99f48e3a..d9926fda 100644 --- a/test/spec_rackup.rb +++ b/test/spec_rackup.rb @@ -1,5 +1,6 @@ require 'test/spec' require 'testrequest' +require 'rack/server' require 'open3' begin @@ -15,7 +16,7 @@ context "rackup" do @port = options[:port] || 9292 Dir.chdir("#{root}/test/rackup") do - @rackup = IO.popen("#{Gem.ruby} -S #{rackup} #{flags}") + @in, @rackup, @err = Open3.popen3("#{Gem.ruby} -S #{rackup} #{flags}") end return if options[:port] == false @@ -34,12 +35,14 @@ context "rackup" do end after do - Process.kill(9, @rackup.pid) if @rackup + # This doesn't actually return a response, so we rescue + GET "/die" rescue nil Dir["#{root}/**/*.pid"].each do |file| - Process.kill(9, File.read(file).to_i) File.delete(file) end + + File.delete("#{root}/log_output") if File.exist?("#{root}/log_output") end specify "rackup" do @@ -97,13 +100,19 @@ context "rackup" do response["REMOTE_ADDR"].should.equal "127.0.0.1" end - specify "rackup --pid" do + 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/ @@ -115,11 +124,30 @@ context "rackup" do status.should.be 500 end - specify "rackup --env" do + 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." -- cgit v1.2.3-24-ge0c7 From 1a5d7356e3c256d28d9675bcfde39184bbd0c554 Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Sun, 22 Nov 2009 15:45:20 -0800 Subject: Initial removal of OpenID related files Removal of references in Rakefile and the core include --- Rakefile | 3 +- lib/rack.rb | 1 - lib/rack/auth/openid.rb | 487 ------------------------------------------ test/spec_rack_auth_openid.rb | 84 -------- 4 files changed, 1 insertion(+), 574 deletions(-) delete mode 100644 lib/rack/auth/openid.rb delete mode 100644 test/spec_rack_auth_openid.rb diff --git a/Rakefile b/Rakefile index c88f7aff..2da515ff 100644 --- a/Rakefile +++ b/Rakefile @@ -86,7 +86,7 @@ end desc "Run all the fast tests" task :test do - sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|Rack::Auth::OpenID)"'}" + sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache)"'}" end desc "Run all the tests" @@ -135,7 +135,6 @@ Also see http://rack.rubyforge.org. 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 diff --git a/lib/rack.rb b/lib/rack.rb index 703649cd..25438afe 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -59,7 +59,6 @@ module Rack autoload :Basic, "rack/auth/basic" autoload :AbstractRequest, "rack/auth/abstract/request" autoload :AbstractHandler, "rack/auth/abstract/handler" - autoload :OpenID, "rack/auth/openid" module Digest autoload :MD5, "rack/auth/digest/md5" autoload :Nonce, "rack/auth/digest/nonce" diff --git a/lib/rack/auth/openid.rb b/lib/rack/auth/openid.rb deleted file mode 100644 index 43cbe4f9..00000000 --- a/lib/rack/auth/openid.rb +++ /dev/null @@ -1,487 +0,0 @@ -# AUTHOR: Scytrin dai Kinthra ; blink#ruby-lang@irc.freenode.net - -gem 'ruby-openid', '~> 2' if defined? Gem -require 'rack/request' -require 'rack/utils' -require 'rack/auth/abstract/handler' - -require 'uri' -require 'openid' -require 'openid/extension' -require 'openid/store/memory' - -module Rack - class Request - def openid_request - @env['rack.auth.openid.request'] - end - - def openid_response - @env['rack.auth.openid.response'] - end - end - - module Auth - - # Rack::Auth::OpenID provides a simple method for setting up an OpenID - # Consumer. It requires the ruby-openid library from janrain to operate, - # as well as a rack method of session management. - # - # The ruby-openid home page is at http://openidenabled.com/ruby-openid/. - # - # The OpenID specifications can be found at - # http://openid.net/specs/openid-authentication-1_1.html - # and - # http://openid.net/specs/openid-authentication-2_0.html. Documentation - # for published OpenID extensions and related topics can be found at - # http://openid.net/developers/specs/. - # - # It is recommended to read through the OpenID spec, as well as - # ruby-openid's documentation, to understand what exactly goes on. However - # a setup as simple as the presented examples is enough to provide - # Consumer functionality. - # - # This library strongly intends to utilize the OpenID 2.0 features of the - # ruby-openid library, which provides OpenID 1.0 compatiblity. - # - # NOTE: Due to the amount of data that this library stores in the - # session, Rack::Session::Cookie may fault. - # - # == Examples - # - # simple_oid = OpenID.new('http://mysite.com/') - # - # return_oid = OpenID.new('http://mysite.com/', { - # :return_to => 'http://mysite.com/openid' - # }) - # - # complex_oid = OpenID.new('http://mysite.com/', - # :immediate => true, - # :extensions => { - # ::OpenID::SReg => [['email'],['nickname']] - # } - # ) - # - # = Advanced - # - # Most of the functionality of this library is encapsulated such that - # expansion and overriding functions isn't difficult nor tricky. - # Alternately, to avoid opening up singleton objects or subclassing, a - # wrapper rack middleware can be composed to act upon Auth::OpenID's - # responses. See #check and #finish for locations of pertinent data. - # - # == Responses - # - # To change the responses that Auth::OpenID returns, override the methods - # #redirect, #bad_request, #unauthorized, #access_denied, and - # #foreign_server_failure. - # - # Additionally #confirm_post_params is used when the URI would exceed - # length limits on a GET request when doing the initial verification - # request. - # - # == Processing - # - # To change methods of processing completed transactions, override the - # methods #success, #setup_needed, #cancel, and #failure. Please ensure - # the returned object is a rack compatible response. - # - # The first argument is an OpenID::Response, the second is a - # Rack::Request of the current request, the last is the hash used in - # ruby-openid handling, which can be found manually at - # env['rack.session'][:openid]. - # - # This is useful if you wanted to expand the processing done, such as - # setting up user accounts. - # - # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to - # def oid_app.success oid, request, session - # user = Models::User[oid.identity_url] - # user ||= Models::User.create_from_openid oid - # request['rack.session'][:user] = user.id - # redirect MyApp.site_home - # end - # - # site_map['/openid'] = oid_app - # map = Rack::URLMap.new site_map - # ... - - class OpenID - # Raised if an incompatible session is being used. - class NoSession < RuntimeError; end - # Raised if an extension not matching specifications is provided. - class BadExtension < RuntimeError; end - # Possible statuses returned from consumer responses. See definitions - # in the ruby-openid library. - ValidStatus = [ - ::OpenID::Consumer::SUCCESS, - ::OpenID::Consumer::FAILURE, - ::OpenID::Consumer::CANCEL, - ::OpenID::Consumer::SETUP_NEEDED - ] - - # The first argument is the realm, identifying the site they are trusting - # with their identity. This is required, also treated as the trust_root - # in OpenID 1.x exchanges. - # - # The lits of acceptable options include :return_to, :session_key, - # :openid_param, :store, :immediate, :extensions. - # - # :return_to defines the url to return to after the client - # authenticates with the openid service provider. This url should point - # to where Rack::Auth::OpenID is mounted. If unprovided, the url of - # the current request is used. - # - # :session_key defines the key to the session hash in the env. - # The default is 'rack.session'. - # - # :openid_param defines at what key in the request parameters to - # find the identifier to resolve. As per the 2.0 spec, the default is - # 'openid_identifier'. - # - # :store defined what OpenID Store to use for persistant - # information. By default a Store::Memory is used. - # - # :immediate as true will make initial requests to be of an - # immediate type. This is false by default. See OpenID specification - # documentation. - # - # :extensions should be a hash of openid extension - # implementations. The key should be the extension module, the value - # should be an array of arguments for extension::Request.new(). - # The hash is iterated over and passed to #add_extension for processing. - # Please see #add_extension for further documentation. - - def initialize(realm, options={}) - realm = URI(realm) - raise ArgumentError, "Invalid realm: #{realm}" \ - unless realm.absolute? \ - and realm.fragment.nil? \ - and realm.scheme =~ /^https?$/ \ - and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/ - realm.path = '/' if realm.path.empty? - @realm = realm.to_s - - if ruri = options[:return_to] - ruri = URI(ruri) - raise ArgumentError, "Invalid return_to: #{ruri}" \ - unless ruri.absolute? \ - and ruri.scheme =~ /^https?$/ \ - and ruri.fragment.nil? - raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \ - unless self.within_realm?(ruri) - @return_to = ruri.to_s - end - - @session_key = options[:session_key] || 'rack.session' - @openid_param = options[:openid_param] || 'openid_identifier' - @store = options[:store] || ::OpenID::Store::Memory.new - @immediate = !!options[:immediate] - - @extensions = {} - if extensions = options[:extensions] - extensions.each do |ext, args| - add_extension(ext, *args) - end - end - - # Undocumented, semi-experimental - @anonymous = !!options[:anonymous] - end - - attr_reader :realm, :return_to, :session_key, :openid_param, :store, - :immediate, :extensions - - # Sets up and uses session data at :openid within the session. - # Errors in this setup will raise a NoSession exception. - # - # If the parameter 'openid.mode' is set, which implies a followup from - # the openid server, processing is passed to #finish and the result is - # returned. However, if there is no appropriate openid information in the - # session, a 400 error is returned. - # - # If the parameter specified by options[:openid_param] is - # present, processing is passed to #check and the result is returned. - # - # If neither of these conditions are met, #bad_request is called. - - def call(env) - env['rack.auth.openid'] = self - env_session = env[@session_key] - unless env_session and env_session.is_a?(Hash) - raise NoSession, 'No compatible session.' - end - # let us work in our own namespace... - session = (env_session[:openid] ||= {}) - unless session and session.is_a?(Hash) - raise NoSession, 'Incompatible openid session.' - end - - request = Rack::Request.new(env) - consumer = ::OpenID::Consumer.new(session, @store) - - if mode = request.GET['openid.mode'] - finish(consumer, session, request) - elsif request.GET[@openid_param] - check(consumer, session, request) - else - bad_request - end - end - - # As the first part of OpenID consumer action, #check retrieves the data - # required for completion. - # - # If all parameters fit within the max length of a URI, a 303 redirect - # will be returned. Otherwise #confirm_post_params will be called. - # - # Any messages from OpenID's request are logged to env['rack.errors'] - # - # env['rack.auth.openid.request'] is the openid checkid request - # instance. - # - # session[:openid_param] is set to the openid identifier - # provided by the user. - # - # session[:return_to] is set to the return_to uri given to the - # identity provider. - - def check(consumer, session, req) - oid = consumer.begin(req.GET[@openid_param], @anonymous) - req.env['rack.auth.openid.request'] = oid - req.env['rack.errors'].puts(oid.message) - p oid if $DEBUG - - ## Extension support - extensions.each do |ext,args| - oid.add_extension(ext::Request.new(*args)) - end - - session[:openid_param] = req.GET[openid_param] - return_to_uri = return_to ? return_to : req.url - session[:return_to] = return_to_uri - immediate = session.key?(:setup_needed) ? false : immediate - - if oid.send_redirect?(realm, return_to_uri, immediate) - redirect(oid.redirect_url(realm, return_to_uri, immediate)) - else - confirm_post_params(oid, realm, return_to_uri, immediate) - end - rescue ::OpenID::DiscoveryFailure => e - # thrown from inside OpenID::Consumer#begin by yadis stuff - req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" ) - return foreign_server_failure - end - - # This is the final portion of authentication. - # If successful, a redirect to the realm is be returned. - # Data gathered from extensions are stored in session[:openid] with the - # extension's namespace uri as the key. - # - # Any messages from OpenID's response are logged to env['rack.errors'] - # - # env['rack.auth.openid.response'] will contain the openid - # response. - - def finish(consumer, session, req) - oid = consumer.complete(req.GET, req.url) - req.env['rack.auth.openid.response'] = oid - req.env['rack.errors'].puts(oid.message) - p oid if $DEBUG - - if ValidStatus.include?(oid.status) - __send__(oid.status, oid, req, session) - else - invalid_status(oid, req, session) - end - end - - # The first argument should be the main extension module. - # The extension module should contain the constants: - # * class Request, should have OpenID::Extension as an ancestor - # * class Response, should have OpenID::Extension as an ancestor - # * string NS_URI, which defining the namespace of the extension - # - # All trailing arguments will be passed to extension::Request.new in - # #check. - # The openid response will be passed to - # extension::Response#from_success_response, oid#get_extension_args will - # be called on the result to attain the gathered data. - # - # This method returns the key at which the response data will be found in - # the session, which is the namespace uri by default. - - def add_extension(ext, *args) - raise BadExtension unless valid_extension?(ext) - extensions[ext] = args - return ext::NS_URI - end - - # Checks the validitity, in the context of usage, of a submitted - # extension. - - def valid_extension?(ext) - if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) } - raise ArgumentError, 'Extension is missing constants.' - elsif not ext::Response.respond_to?(:from_success_response) - raise ArgumentError, 'Response is missing required method.' - end - return true - rescue - return false - end - - # Checks the provided uri to ensure it'd be considered within the realm. - # is currently not compatible with wildcard realms. - - def within_realm? uri - uri = URI.parse(uri.to_s) - realm = URI.parse(self.realm) - return false unless uri.absolute? - return false unless uri.path[0, realm.path.size] == realm.path - return false unless uri.host == realm.host or realm.host[/^\*\./] - # for wildcard support, is awkward with URI limitations - realm_match = Regexp.escape(realm.host). - sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$' - return false unless uri.host.match(realm_match) - return true - end - - alias_method :include?, :within_realm? - - protected - - # Returns an html form page for posting to an Identity Provider if the - # GET request would exceed the upper URI length limit. - - def confirm_post_params(oid, realm, return_to, immediate) - response = Rack::Response.new ''+ - 'Confirm...'+ - ''+oid.form_markup(realm, return_to, immediate)+''+ - '' - response.finish - end - - # Returns a 303 redirect with the destination of that provided by the - # argument. - - def redirect(uri) - [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0', - 'Location' => uri}, - [] ] - end - - # Returns an empty 400 response. - - def bad_request - [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'}, - [''] ] - end - - # Returns a basic unauthorized 401 response. - - def unauthorized - [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'}, - ['Unauthorized.'] ] - end - - # Returns a basic access denied 403 response. - - def access_denied - [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'}, - ['Access denied.'] ] - end - - # Returns a 503 response to be used if communication with the remote - # OpenID server fails. - - def foreign_server_failure - [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'}, - ['Foreign server failure.'] ] - end - - private - - # Called to complete processing on a successful transaction. - # Within the openid session, :openid_identity and :openid_identifier are - # set to the user friendly and the standard representation of the - # validated identity. All other data in the openid session is cleared. - - def success(oid, request, session) - session.clear - session[:openid_identity] = oid.display_identifier - session[:openid_identifier] = oid.identity_url - extensions.keys.each do |ext| - label = ext.name[/[^:]+$/].downcase - response = ext::Response.from_success_response(oid) - session[label] = response.data - end - redirect(realm) - end - - # Called if the Identity Provider indicates further setup by the user is - # required. - # The identifier is retrived from the openid session at :openid_param. - # And :setup_needed is set to true to prevent looping. - - def setup_needed(oid, request, session) - identifier = session[:openid_param] - session[:setup_needed] = true - redirect(req.script_name + '?' + openid_param + '=' + identifier) - end - - # Called if the user indicates they wish to cancel identification. - # Data within openid session is cleared. - - def cancel(oid, request, session) - session.clear - access_denied - end - - # Called if the Identity Provider indicates the user is unable to confirm - # their identity. Data within the openid session is left alone, in case - # of swarm auth attacks. - - def failure(oid, request, session) - unauthorized - end - - # To be called if there is no method for handling the OpenID response - # status. - - def invalid_status(oid, request, session) - msg = 'Invalid status returned by the OpenID authorization reponse.' - [ 500, - {'Content-Type'=>'text/plain','Content-Length'=>msg.length.to_s}, - [msg] ] - end - end - - # A class developed out of the request to use OpenID as an authentication - # middleware. The request will be sent to the OpenID instance unless the - # block evaluates to true. For example in rackup, you can use it as such: - # - # use Rack::Session::Pool - # use Rack::Auth::OpenIDAuth, realm, openid_options do |env| - # env['rack.session'][:authkey] == a_string - # end - # run RackApp - # - # Or simply: - # - # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth - - class OpenIDAuth < Rack::Auth::AbstractHandler - attr_reader :oid - def initialize(app, realm, options={}, &auth) - @oid = OpenID.new(realm, options) - super(app, &auth) - end - - def call(env) - to = @authenticator.call(env) ? @app : @oid - to.call(env) - end - end - end -end diff --git a/test/spec_rack_auth_openid.rb b/test/spec_rack_auth_openid.rb deleted file mode 100644 index ba248445..00000000 --- a/test/spec_rack_auth_openid.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'test/spec' - -begin -# requires the ruby-openid gem -require 'rack/auth/openid' - -context "Rack::Auth::OpenID" do - OID = Rack::Auth::OpenID - host = 'host' - subd = 'sub.host' - wild = '*.host' - path = 'path' - long = 'path/long' - scheme = 'http://' - realm = scheme+host+'/'+path - - specify 'realm uri should be valid' do - lambda{OID.new('/'+path)}.should.raise ArgumentError - lambda{OID.new('/'+long)}.should.raise ArgumentError - lambda{OID.new(scheme+host)}.should.not.raise - lambda{OID.new(scheme+host+'/')}.should.not.raise - lambda{OID.new(scheme+host+'/'+path)}.should.not.raise - lambda{OID.new(scheme+subd)}.should.not.raise - lambda{OID.new(scheme+subd+'/')}.should.not.raise - lambda{OID.new(scheme+subd+'/'+path)}.should.not.raise - end - - specify 'should be able to check if a uri is within the realm' do - end - - specify 'return_to should be valid' do - uri = '/'+path - lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError - uri = '/'+long - lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError - uri = scheme+host - lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError - uri = scheme+host+'/'+path - lambda{OID.new(realm, :return_to=>uri)}.should.not.raise - uri = scheme+subd+'/'+path - lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError - uri = scheme+host+'/'+long - lambda{OID.new(realm, :return_to=>uri)}.should.not.raise - uri = scheme+subd+'/'+long - lambda{OID.new(realm, :return_to=>uri)}.should.raise ArgumentError - end - - specify 'extensions should have required constants defined' do - badext = Rack::Auth::OpenID::BadExtension - ext = Object.new - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext = Module.new - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Request = nil - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Response = nil - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::NS_URI = nil - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - end - - specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do - $-w, w = nil, $-w # yuck - badext = Rack::Auth::OpenID::BadExtension - ext = Module.new - ext::Request = nil - ext::Response = nil - ext::NS_URI = nil - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Request = Class.new() - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Response = Class.new() - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Request = Class.new(::OpenID::Extension) - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - ext::Response = Class.new(::OpenID::Extension) - lambda{OID.new(realm).add_extension(ext)}.should.raise(badext) - $-w = w - end -end - -rescue LoadError - $stderr.puts "Skipping Rack::Auth::OpenID tests (ruby-openid 2 is required). `gem install ruby-openid` and try again." -end -- cgit v1.2.3-24-ge0c7 From adf996587aecdd604eff441b8b69e4c47a8c2617 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 23 Nov 2009 15:06:30 -0800 Subject: Extract the option parser Signed-off-by: Joshua Peek --- lib/rack/builder.rb | 10 +-- lib/rack/server.rb | 185 +++++++++++++++++++++++++++++----------------------- 2 files changed, 110 insertions(+), 85 deletions(-) diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb index f769b5fd..530f0aaf 100644 --- a/lib/rack/builder.rb +++ b/lib/rack/builder.rb @@ -24,19 +24,21 @@ module Rack # You can use +map+ to construct a Rack::URLMap in a convenient way. class Builder - def self.parse_file(config, opts = nil) + def self.parse_file(config, opts = Server::Options.new) + options = {} if config =~ /\.ru$/ cfgfile = ::File.read(config) if cfgfile[/^#\\(.*)/] && opts - opts.parse! $1.split(/\s+/) + options = opts.parse! $1.split(/\s+/) end cfgfile.sub!(/^__END__\n.*/, '') - eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", + app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", TOPLEVEL_BINDING, config else require config - Object.const_get(::File.basename(config, '.rb').capitalize) + app = Object.const_get(::File.basename(config, '.rb').capitalize) end + return app, options end def initialize(&block) diff --git a/lib/rack/server.rb b/lib/rack/server.rb index ff041b3d..7d0705ad 100644 --- a/lib/rack/server.rb +++ b/lib/rack/server.rb @@ -2,6 +2,83 @@ 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 @@ -13,18 +90,17 @@ module Rack end def options - @options ||= begin - parse_options(ARGV) - end + @options ||= parse_options(ARGV) end def default_options { :environment => "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [] + :pid => nil, + :Port => 9292, + :Host => "0.0.0.0", + :AccessLog => [], + :rack_file => ::File.expand_path("config.ru") } end @@ -34,7 +110,9 @@ module Rack abort "configuration #{options[:rack_file]} not found" end - Rack::Builder.parse_file(options[:rack_file], opt_parser) + app, options = Rack::Builder.parse_file(self.options[:rack_file], opt_parser) + self.options.merge! options + app end end @@ -52,13 +130,26 @@ module Rack end def start - if $DEBUG + 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 @@ -70,86 +161,18 @@ module Rack private def parse_options(args) - @options = default_options + options = default_options # Don't evaluate CGI ISINDEX parameters. # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html args.clear if ENV.include?("REQUEST_METHOD") - opt_parser.parse! args - @options[:rack_file] = args.last || ::File.expand_path("config.ru") - @options + options.merge! opt_parser.parse! args + options end def opt_parser - @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)") { - $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| - @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") { |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 + Options.new end def build_app(app) -- cgit v1.2.3-24-ge0c7 From 8b71767d1934bec6eb4afeb69bfad36414e4947b Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Tue, 17 Nov 2009 13:02:04 +0800 Subject: Response should call #to_i on the status, as per the spec. "The status, if parsed as integer (to_i), must be greater than or equal to 100." --- lib/rack/response.rb | 2 +- test/spec_rack_response.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rack/response.rb b/lib/rack/response.rb index d1f6a123..92c8c4a6 100644 --- a/lib/rack/response.rb +++ b/lib/rack/response.rb @@ -19,7 +19,7 @@ module Rack attr_accessor :length def initialize(body=[], status=200, header={}, &block) - @status = status + @status = status.to_i @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. merge(header)) diff --git a/test/spec_rack_response.rb b/test/spec_rack_response.rb index eb59b5c2..7989013d 100644 --- a/test/spec_rack_response.rb +++ b/test/spec_rack_response.rb @@ -118,6 +118,9 @@ context "Rack::Response" do r = Rack::Response.new([], 500) r.status.should.equal 500 + + r = Rack::Response.new([], "200 OK") + r.status.should.equal 200 end specify "has a constructor that can take a block" do -- cgit v1.2.3-24-ge0c7 From ac57058c067d343123e4f87932474114f250b89d Mon Sep 17 00:00:00 2001 From: Geoffrey Grosenbach Date: Wed, 2 Dec 2009 02:47:38 +0800 Subject: Added mime type for .manifest (HTML5 offline storage) --- lib/rack/mime.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb index 853f16bd..1414d19a 100644 --- a/lib/rack/mime.rb +++ b/lib/rack/mime.rb @@ -105,6 +105,7 @@ module Rack ".m3u" => "audio/x-mpegurl", ".m4v" => "video/mp4", ".man" => "text/troff", + ".manifest"=> "text/cache-manifest", ".mathml" => "application/mathml+xml", ".mbox" => "application/mbox", ".mdoc" => "text/troff", -- cgit v1.2.3-24-ge0c7 From 35b562e3df70de7f0b494fd1c7d5af653caa2abb Mon Sep 17 00:00:00 2001 From: mig-hub Date: Tue, 24 Nov 2009 20:35:04 +0800 Subject: Fix typo on lib/rack/session/pool.rb --- lib/rack/session/pool.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/session/pool.rb b/lib/rack/session/pool.rb index f6f87408..b3f8bd72 100644 --- a/lib/rack/session/pool.rb +++ b/lib/rack/session/pool.rb @@ -13,7 +13,7 @@ module Rack # In the context of a multithreaded environment, sessions being # 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. # # Example: -- cgit v1.2.3-24-ge0c7 From fb4f2b5fe26a0e3821ac0f6361a3885bd88b42ca Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Wed, 2 Dec 2009 19:32:56 -0800 Subject: Test added to check to ensure that quoted values are properly parsed Using a regular expression to identify quoted string values, could be optimized --- lib/rack/utils.rb | 7 ++++++- test/spec_rack_utils.rb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 884e6045..05c7734f 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -38,7 +38,9 @@ module Rack (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| k, v = p.split('=', 2).map { |x| unescape(x) } - + if v =~ /^("|')(.*)\1$/ + v = $2.gsub('\\'+$1, $1) + end if cur = params[k] if cur.class == Array params[k] << v @@ -67,6 +69,9 @@ module Rack module_function :parse_nested_query def normalize_params(params, name, v = nil) + if v and v =~ /^("|')(.*)\1$/ + v = $2.gsub('\\'+$1, $1) + end name =~ %r(\A[\[\]]*([^\[\]]+)\]*) k = $1 || '' after = $' || '' diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 52333773..dca4edca 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -30,7 +30,10 @@ context "Rack::Utils" do end 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"). should.equal "foo" => ["bar", "quux"] Rack::Utils.parse_query("foo=1&bar=2"). @@ -47,6 +50,8 @@ context "Rack::Utils" do should.equal "foo" => "" Rack::Utils.parse_nested_query("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"). should.equal "foo" => "quux" -- cgit v1.2.3-24-ge0c7 From 50fc8eb0f1c35b9d0b8b4b83b603ae15c2fe0c18 Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Sun, 22 Nov 2009 18:08:53 -0800 Subject: Inlining of #merge_sessions --- lib/rack/session/memcache.rb | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/lib/rack/session/memcache.rb b/lib/rack/session/memcache.rb index 4a65cbf3..7ae8707a 100644 --- a/lib/rack/session/memcache.rb +++ b/lib/rack/session/memcache.rb @@ -74,7 +74,22 @@ module Rack @pool.add session_id, 0 # so we don't worry about cache miss on #set end old_session = new_session.instance_variable_get('@old') || {} - session = merge_sessions session_id, old_session, new_session, session + + begin # merge sessions + unless Hash === old_session and Hash === new_session + warn 'Bad old_session or new_session sessions provided.' + return session + end + + delete = old_session.keys - new_session.keys + env['rack.errors'].puts("//@#{session_id}: delete #{delete*','}") if $VERBOSE and not delete.empty? + delete.each{|k| session.delete k } + + update = new_session.keys.select{|k| new_session[k] != old_session[k] } + env['rack.errors'].puts("//@#{session_id}: update #{update*','}") if $VERBOSE and not update.empty? + update.each{|k| session[k] = new_session[k] } + end + @pool.set session_id, session, expiry return session_id rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted @@ -84,26 +99,6 @@ module Rack ensure @mutex.unlock if env['rack.multithread'] 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 -- cgit v1.2.3-24-ge0c7 From 9cd37ca45503c1d1e6432e95d5444328ab4d6015 Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Sun, 22 Nov 2009 20:12:04 -0800 Subject: Updating Session::Memcache test Pointless instantiation removed Moved bad connection check above good connection check A blank string for the server specification uses defaults, fixed --- test/spec_rack_session_memcache.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/spec_rack_session_memcache.rb b/test/spec_rack_session_memcache.rb index f21ac3de..4b72a2f9 100644 --- a/test/spec_rack_session_memcache.rb +++ b/test/spec_rack_session_memcache.rb @@ -6,8 +6,6 @@ begin require 'rack/response' require 'thread' - pool = Rack::Session::Memcache.new(lambda {}) - context "Rack::Session::Memcache" do session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key] session_match = /#{session_key}=[0-9a-fA-F]+;/ @@ -29,22 +27,22 @@ begin incrementor.call(env) end - specify "MemCache can connect to existing server" do - test_pool = MemCache.new :namespace => 'test:rack:session' - end - specify "faults on no connection" do if RUBY_VERSION < "1.9" lambda do - Rack::Session::Memcache.new(incrementor, :memcache_server => '') + Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' end.should.raise else lambda do - Rack::Session::Memcache.new(incrementor, :memcache_server => '') + Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' end.should.raise ArgumentError end end + specify "connect to existing server" do + test_pool = MemCache.new incrementor, :namespace => 'test:rack:session' + end + specify "creates a new cookie" do pool = Rack::Session::Memcache.new(incrementor) res = Rack::MockRequest.new(pool).get("/") -- cgit v1.2.3-24-ge0c7 From 2a79d6ff0921ee4193fcd3739df604808c0adce1 Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Sun, 22 Nov 2009 20:15:28 -0800 Subject: Session::Memcache fixes Restructing logical branches to be less inlince Uniform naming of variables Fix of of inline session merging --- lib/rack/session/memcache.rb | 70 ++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/lib/rack/session/memcache.rb b/lib/rack/session/memcache.rb index 7ae8707a..7f48b360 100644 --- a/lib/rack/session/memcache.rb +++ b/lib/rack/session/memcache.rb @@ -29,9 +29,13 @@ module Rack super @mutex = Mutex.new - @pool = MemCache. - new @default_options[:memcache_server], @default_options - raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?} + mserv = @default_options[:memcache_server] + mopts = @default_options. + 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 def generate_sid @@ -41,24 +45,30 @@ module Rack end end - def get_session(env, sid) - session = @pool.get(sid) if sid + def get_session(env, session_id) + session = @pool.get(session_id) if session_id @mutex.lock if env['rack.multithread'] - unless sid and session - env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil? + unless session_id and session + if $VERBOSE and not session_id.nil? + env['rack.errors']. + puts "Session '#{session_id.inspect}' not found, initializing..." + end session = {} - sid = generate_sid - ret = @pool.add sid, session - raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret + session_id = generate_sid + ret = @pool.add session_id, session + unless /^STORED/ =~ ret + raise "Session collision on '#{session_id.inspect}'" + end end session.instance_variable_set('@old', {}.merge(session)) - return [sid, session] - rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted - warn "#{self} is unable to find server." + return [session_id, session] + rescue MemCache::MemCacheError, Errno::ECONNREFUSED + # MemCache server cannot be contacted + warn "#{self} is unable to find memcached server." warn $!.inspect return [ nil, {} ] ensure - @mutex.unlock if env['rack.multithread'] + @mutex.unlock if @mutex.locked? end def set_session(env, session_id, new_session, options) @@ -75,29 +85,39 @@ module Rack end old_session = new_session.instance_variable_get('@old') || {} - begin # merge sessions - unless Hash === old_session and Hash === new_session - warn 'Bad old_session or new_session sessions provided.' - return session - end + 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 - env['rack.errors'].puts("//@#{session_id}: delete #{delete*','}") if $VERBOSE and not delete.empty? + if $VERBOSE and not delete.empty? + env['rack.errors']. + puts "//@#{session_id}: delete #{delete*','}" + end delete.each{|k| session.delete k } - update = new_session.keys.select{|k| new_session[k] != old_session[k] } - env['rack.errors'].puts("//@#{session_id}: update #{update*','}") if $VERBOSE and not update.empty? + # 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 return session_id - rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted - 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 return false ensure - @mutex.unlock if env['rack.multithread'] + @mutex.unlock if @mutex.locked? end end end -- cgit v1.2.3-24-ge0c7 From ed3db52a936624b15c5b8d69aa7f3005dec48240 Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Thu, 3 Dec 2009 13:04:16 -0800 Subject: Added test for deep hash checks, prevent shallow copy check failure Rewording variables for clarity --- test/spec_rack_session_memcache.rb | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/test/spec_rack_session_memcache.rb b/test/spec_rack_session_memcache.rb index 4b72a2f9..faac796e 100644 --- a/test/spec_rack_session_memcache.rb +++ b/test/spec_rack_session_memcache.rb @@ -8,7 +8,7 @@ begin context "Rack::Session::Memcache" do 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| env["rack.session"]["counter"] ||= 0 env["rack.session"]["counter"] += 1 @@ -157,6 +157,31 @@ begin res3.body.should.equal '{"counter"=>4}' 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? specify "multithread: should cleanly merge sessions" do next unless $DEBUG @@ -167,7 +192,7 @@ begin res = req.get('/') res.body.should.equal '{"counter"=>1}' cookie = res["Set-Cookie"] - sess_id = cookie[/#{pool.key}=([^,;]+)/,1] + session_id = cookie[session_match, 1] delta_incrementor = lambda do |env| # emulate disconjoinment of threading @@ -189,7 +214,7 @@ begin request.body.should.include '"counter"=>2' end - session = pool.pool.get(sess_id) + session = pool.pool.get(session_id) session.size.should.be tnum+1 # counter session['counter'].should.be 2 # meeeh @@ -213,7 +238,7 @@ begin request.body.should.include '"counter"=>3' end - session = pool.pool.get(sess_id) + session = pool.pool.get(session_id) session.size.should.be tnum+1 session['counter'].should.be 3 @@ -235,7 +260,7 @@ begin request.body.should.include '"foo"=>"bar"' end - session = pool.pool.get(sess_id) + session = pool.pool.get(session_id) session.size.should.be r.size+1 session['counter'].should.be.nil? session['foo'].should.equal 'bar' -- cgit v1.2.3-24-ge0c7 From e89ef8f95389c4d95b1f02a7e64b29ae09b4763d Mon Sep 17 00:00:00 2001 From: Scytrin dai Kinthra Date: Thu, 3 Dec 2009 13:07:46 -0800 Subject: Test-fix for shallow copy change checks Simplification of new/missing session keys --- lib/rack/session/memcache.rb | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/rack/session/memcache.rb b/lib/rack/session/memcache.rb index 7f48b360..44629da3 100644 --- a/lib/rack/session/memcache.rb +++ b/lib/rack/session/memcache.rb @@ -46,21 +46,14 @@ module Rack end def get_session(env, session_id) - session = @pool.get(session_id) if session_id @mutex.lock if env['rack.multithread'] - unless session_id and session - if $VERBOSE and not session_id.nil? - env['rack.errors']. - puts "Session '#{session_id.inspect}' not found, initializing..." - end - session = {} - session_id = generate_sid - ret = @pool.add session_id, session - unless /^STORED/ =~ ret + unless session_id and session = @pool.get(session_id) + session_id, session = generate_sid, {} + unless /^STORED/ =~ @pool.add(session_id, session) raise "Session collision on '#{session_id.inspect}'" end end - session.instance_variable_set('@old', {}.merge(session)) + session.instance_variable_set '@old', @pool.get(session_id, true) return [session_id, session] rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted @@ -76,14 +69,16 @@ module Rack expiry = expiry.nil? ? 0 : expiry + 1 @mutex.lock if env['rack.multithread'] - session = @pool.get(session_id) || {} if options[:renew] or options[:drop] @pool.delete session_id return false if options[:drop] 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 - old_session = new_session.instance_variable_get('@old') || {} + + 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']. -- cgit v1.2.3-24-ge0c7 From ec8e0696bcd991d6bd5ea12799af6d3edee302ef Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 10 Dec 2009 21:03:32 -0600 Subject: Don't delete PATH_INFO from env in fastcgi, mongrel, and webrick handlers because PATH_INFO must not be nil according to SPEC [#75 state:resolved] --- lib/rack/handler/fastcgi.rb | 3 +-- lib/rack/handler/mongrel.rb | 3 +-- lib/rack/handler/webrick.rb | 4 +--- test/spec_rack_cgi.rb | 4 ++-- test/spec_rack_fastcgi.rb | 2 +- test/spec_rack_mongrel.rb | 6 +++--- test/spec_rack_webrick.rb | 6 +++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/rack/handler/fastcgi.rb b/lib/rack/handler/fastcgi.rb index 1739d659..d8301474 100644 --- a/lib/rack/handler/fastcgi.rb +++ b/lib/rack/handler/fastcgi.rb @@ -33,7 +33,7 @@ module Rack env.delete "HTTP_CONTENT_LENGTH" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" - + rack_input = RewindableInput.new(request.in) env.update({"rack.version" => [1,0], @@ -50,7 +50,6 @@ module Rack env["QUERY_STRING"] ||= "" env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["REQUEST_PATH"] ||= "/" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == "" env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == "" diff --git a/lib/rack/handler/mongrel.rb b/lib/rack/handler/mongrel.rb index cd1af2de..21f9f134 100644 --- a/lib/rack/handler/mongrel.rb +++ b/lib/rack/handler/mongrel.rb @@ -14,7 +14,7 @@ module Rack options[:throttle] || 0, options[:timeout] || 60) # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. - # Use is similar to #run, replacing the app argument with a hash of + # Use is similar to #run, replacing the app argument with a hash of # { path=>app, ... } or an instance of Rack::URLMap. if options[:map] if app.is_a? Hash @@ -63,7 +63,6 @@ module Rack "rack.url_scheme" => "http", }) env["QUERY_STRING"] ||= "" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" status, headers, body = @app.call(env) diff --git a/lib/rack/handler/webrick.rb b/lib/rack/handler/webrick.rb index 5b9ae740..43286113 100644 --- a/lib/rack/handler/webrick.rb +++ b/lib/rack/handler/webrick.rb @@ -40,9 +40,7 @@ module Rack env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["QUERY_STRING"] ||= "" env["REQUEST_PATH"] ||= "/" - if env["PATH_INFO"] == "" - env.delete "PATH_INFO" - else + unless env["PATH_INFO"] == "" path, n = req.request_uri.path, env["SCRIPT_NAME"].length env["PATH_INFO"] = path[n, path.length-n] end diff --git a/test/spec_rack_cgi.rb b/test/spec_rack_cgi.rb index 818fabdf..97456320 100644 --- a/test/spec_rack_cgi.rb +++ b/test/spec_rack_cgi.rb @@ -3,7 +3,7 @@ require 'testrequest' context "Rack::Handler::CGI" do include TestRequest::Helpers - + setup do @host = '0.0.0.0' @port = 9203 @@ -47,7 +47,7 @@ context "Rack::Handler::CGI" do response["REQUEST_METHOD"].should.equal "GET" response["SCRIPT_NAME"].should.equal "/test" response["REQUEST_PATH"].should.equal "/" - response["PATH_INFO"].should.be.nil + response["PATH_INFO"].should.equal "" response["QUERY_STRING"].should.equal "" response["test.postdata"].should.equal "" diff --git a/test/spec_rack_fastcgi.rb b/test/spec_rack_fastcgi.rb index 69478de5..98963c0e 100644 --- a/test/spec_rack_fastcgi.rb +++ b/test/spec_rack_fastcgi.rb @@ -47,7 +47,7 @@ context "Rack::Handler::FastCGI" do response["REQUEST_METHOD"].should.equal "GET" response["SCRIPT_NAME"].should.equal "/test.fcgi" response["REQUEST_PATH"].should.equal "/" - response["PATH_INFO"].should.be.nil + response["PATH_INFO"].should.equal "" response["QUERY_STRING"].should.equal "" response["test.postdata"].should.equal "" diff --git a/test/spec_rack_mongrel.rb b/test/spec_rack_mongrel.rb index d73e884c..6a69af75 100644 --- a/test/spec_rack_mongrel.rb +++ b/test/spec_rack_mongrel.rb @@ -6,14 +6,14 @@ require 'rack/urlmap' require 'rack/lint' require 'testrequest' require 'timeout' - + Thread.abort_on_exception = true $tcp_defer_accept_opts = nil $tcp_cork_opts = nil context "Rack::Handler::Mongrel" do include TestRequest::Helpers - + setup do server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201) server.register('/test', @@ -52,7 +52,7 @@ context "Rack::Handler::Mongrel" do response["REQUEST_METHOD"].should.equal "GET" response["SCRIPT_NAME"].should.equal "/test" response["REQUEST_PATH"].should.equal "/test" - response["PATH_INFO"].should.be.nil + response["PATH_INFO"].should.be.equal "" response["QUERY_STRING"].should.equal "" response["test.postdata"].should.equal "" diff --git a/test/spec_rack_webrick.rb b/test/spec_rack_webrick.rb index 3e63ea63..8951687a 100644 --- a/test/spec_rack_webrick.rb +++ b/test/spec_rack_webrick.rb @@ -9,7 +9,7 @@ Thread.abort_on_exception = true context "Rack::Handler::WEBrick" do include TestRequest::Helpers - + setup do @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0', :Port => @port=9202, @@ -50,7 +50,7 @@ context "Rack::Handler::WEBrick" do response["REQUEST_METHOD"].should.equal "GET" response["SCRIPT_NAME"].should.equal "/test" response["REQUEST_PATH"].should.equal "/" - response["PATH_INFO"].should.be.nil + response["PATH_INFO"].should.be.equal "" response["QUERY_STRING"].should.equal "" response["test.postdata"].should.equal "" @@ -60,7 +60,7 @@ context "Rack::Handler::WEBrick" do response["REQUEST_PATH"].should.equal "/" response["PATH_INFO"].should.equal "/foo" response["QUERY_STRING"].should.equal "quux=1" - + GET("/test/foo%25encoding?quux=1") response["REQUEST_METHOD"].should.equal "GET" response["SCRIPT_NAME"].should.equal "/test" -- cgit v1.2.3-24-ge0c7 From d4906ef283949d9fe656bd869f46e2c72b401bf8 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 10 Dec 2009 21:08:01 -0600 Subject: Tell people to report bugs to lighthouse --- README | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README b/README index c58009ff..230d98a9 100644 --- a/README +++ b/README @@ -130,7 +130,7 @@ Either with the embedded WEBrick starter: Or with rackup: - bin/rackup -Ilib example/lobster.ru + bin/rackup -Ilib example/lobster.ru By default, the lobster is found at http://localhost:9292. @@ -285,14 +285,12 @@ run on port 11211) and memcache-client installed. == Contact -Please mail bugs, suggestions and patches to -. +Please post bugs, suggestions and patches to +the bug tracker at . Mailing list archives are available at . -There is a bug tracker at . - Git repository (send Git patches to the mailing list): * http://github.com/rack/rack * http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git @@ -349,7 +347,7 @@ all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -358,6 +356,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Rack:: Rack's Rubyforge project:: Official Rack repositories:: +Rack Lighthouse Bug Tracking:: rack-devel mailing list:: Christian Neukirchen:: -- cgit v1.2.3-24-ge0c7 From 0a90548942409e6d8166a4fb636c30045273a541 Mon Sep 17 00:00:00 2001 From: Bosko Milekic Date: Thu, 5 Nov 2009 16:00:10 -0500 Subject: Introduce failing test case for multipart parser when it slices exactly on a boundary and patch multipart parser so it passes it - the failing test case comes with a sample payload specific to the fact that the default bufsize used by the multipart parser is exactly 16384. should this default be changed, the test will no longer apply. --- lib/rack/utils.rb | 3 + test/multipart/fail_16384_nofile | 814 +++++++++++++++++++++++++++++++++++++++ test/spec_rack_utils.rb | 19 + 3 files changed, 836 insertions(+) create mode 100644 test/multipart/fail_16384_nofile diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 05c7734f..606e0cd5 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -482,6 +482,9 @@ module Rack body << buf.slice!(0, i) buf.slice!(0, boundary_size+2) + # If sliced exactly at boundary, let's move: + next if buf.empty? && ($1 == EOL) + content_length = -1 if $1 == "--" end diff --git a/test/multipart/fail_16384_nofile b/test/multipart/fail_16384_nofile new file mode 100644 index 00000000..bdcd3320 --- /dev/null +++ b/test/multipart/fail_16384_nofile @@ -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-- + diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index dca4edca..96fb58a0 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -463,6 +463,25 @@ context "Rack::Utils::Multipart" do params["people"][0]["files"][:tempfile].read.should.equal "contents" 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 -- cgit v1.2.3-24-ge0c7 From a9440bc752be9b3093669614c6b56bf78d592958 Mon Sep 17 00:00:00 2001 From: Derek and Matt Date: Mon, 16 Nov 2009 13:50:27 -0500 Subject: Fixed multipart parameter parsing for when a field's body ends at the same time as a chunk (i.e. we've reached EOL and buffer is empty) --- lib/rack/utils.rb | 6 +- test/multipart/bad_robots | 259 ++++++++++++++++++++++++++++++++++++++++++++++ test/spec_rack_utils.rb | 12 +++ 3 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 test/multipart/bad_robots diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 606e0cd5..b5aa8a19 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -482,9 +482,6 @@ module Rack body << buf.slice!(0, i) buf.slice!(0, boundary_size+2) - # If sliced exactly at boundary, let's move: - next if buf.empty? && ($1 == EOL) - content_length = -1 if $1 == "--" end @@ -515,7 +512,8 @@ module Rack 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 diff --git a/test/multipart/bad_robots b/test/multipart/bad_robots new file mode 100644 index 00000000..7e5bd418 --- /dev/null +++ b/test/multipart/bad_robots @@ -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-- diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 96fb58a0..0a73d18c 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -462,6 +462,18 @@ context "Rack::Utils::Multipart" do 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") -- cgit v1.2.3-24-ge0c7 From cdf13618445fa7065c07d62b115907856d6d1095 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 10 Dec 2009 21:33:53 -0600 Subject: HeaderHash.new avoids unnecessary object creation Creating a new HeaderHash is an O(n) operation in addition to the cost of allocating a new object. When using multiple pieces of middleware, this can lead to unnecessary memory allocation and iteration overhead. We now explicitly define the HeaderHash.new class method to return its original argument if it is already a HeaderHash to avoid repeating work. Signed-off-by: Joshua Peek --- lib/rack/utils.rb | 4 ++++ test/spec_rack_utils.rb | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index b5aa8a19..4956177e 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -263,6 +263,10 @@ module Rack # A case-insensitive Hash that preserves the original case of a # header when set. class HeaderHash < Hash + def self.new(hash={}) + HeaderHash === hash ? hash : super(hash) + end + def initialize(hash={}) super() @names = {} diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 0a73d18c..755a1619 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -273,6 +273,14 @@ context "Rack::Utils::HeaderHash" do h = Rack::Utils::HeaderHash.new("foo" => "bar") h.delete("Hello").should.be.nil 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 + end context "Rack::Utils::Context" do -- cgit v1.2.3-24-ge0c7 From 8f836f406ca10274c6465e17c2b5646257a8412b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 10 Dec 2009 21:34:17 -0600 Subject: avoid HeaderHash#to_hash in middlewares Since HeaderHash objects are valid header responses, avoid converting the headers to Hash objects only to have it reconverted back to HeaderHash in the next middleware. Signed-off-by: Joshua Peek --- lib/rack/chunked.rb | 4 ++-- lib/rack/content_type.rb | 2 +- lib/rack/response.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb index 280d89dd..dddf9694 100644 --- a/lib/rack/chunked.rb +++ b/lib/rack/chunked.rb @@ -19,7 +19,7 @@ module Rack STATUS_WITH_NO_ENTITY_BODY.include?(status) || headers['Content-Length'] || headers['Transfer-Encoding'] - [status, headers.to_hash, body] + [status, headers, body] else dup.chunk(status, headers, body) end @@ -29,7 +29,7 @@ module Rack @body = body headers.delete('Content-Length') headers['Transfer-Encoding'] = 'chunked' - [status, headers.to_hash, self] + [status, headers, self] end def each diff --git a/lib/rack/content_type.rb b/lib/rack/content_type.rb index 0c1e1ca3..874c28cd 100644 --- a/lib/rack/content_type.rb +++ b/lib/rack/content_type.rb @@ -17,7 +17,7 @@ module Rack status, headers, body = @app.call(env) headers = Utils::HeaderHash.new(headers) headers['Content-Type'] ||= @content_type - [status, headers.to_hash, body] + [status, headers, body] end end end diff --git a/lib/rack/response.rb b/lib/rack/response.rb index 92c8c4a6..a7f9bf2b 100644 --- a/lib/rack/response.rb +++ b/lib/rack/response.rb @@ -71,9 +71,9 @@ module Rack if [204, 304].include?(status.to_i) header.delete "Content-Type" - [status.to_i, header.to_hash, []] + [status.to_i, header, []] else - [status.to_i, header.to_hash, self] + [status.to_i, header, self] end end alias to_a finish # For *response -- cgit v1.2.3-24-ge0c7 From 038ea40cbcc55f2cdb8fa31b19b4e224836534df Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 10 Dec 2009 21:34:50 -0600 Subject: CommonLogger uses HeaderHash to lookup Content-Length Since HeaderHash is cheaper to use now, encourage its usage instead of reinventing a way to lookup header values with an enforced O(n) overhead. Under best conditions, this can now be done in O(1) time if the rest of our middleware stack already uses (and passes) HeaderHash. This does make things slower if CommonLogger is the only middleware in the stack, however that's probably not too common. Signed-off-by: Joshua Peek --- lib/rack/commonlogger.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/rack/commonlogger.rb b/lib/rack/commonlogger.rb index 880f0fbf..1edc9b83 100644 --- a/lib/rack/commonlogger.rb +++ b/lib/rack/commonlogger.rb @@ -16,6 +16,7 @@ module Rack def 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 @@ -41,12 +42,8 @@ module Rack end def extract_content_length(headers) - headers.each do |key, value| - if key.downcase == 'content-length' - return value.to_s == '0' ? '-' : value - end - end - '-' + value = headers['Content-Length'] or return '-' + value.to_s == '0' ? '-' : value end end end -- cgit v1.2.3-24-ge0c7 From 0f1bd2526b3972e131dccad32c7f7ea96a675880 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 10 Dec 2009 21:35:12 -0600 Subject: HeaderHash#each yields Lint-OK multivalue headers Rack::Lint does not allow header values yielded by #each to be non-String objects, so we join them like we do in #to_hash. This finally allows HeaderHash to be passed in the Rack response as a header without needing #to_hash. Signed-off-by: Joshua Peek --- lib/rack/utils.rb | 6 ++++++ test/spec_rack_utils.rb | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 4956177e..46362bab 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -273,6 +273,12 @@ module Rack hash.each { |k, v| self[k] = v } 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 inject({}) do |hash, (k,v)| if v.respond_to? :to_ary diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 755a1619..c61ab363 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -281,6 +281,14 @@ context "Rack::Utils::HeaderHash" do 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 context "Rack::Utils::Context" do -- cgit v1.2.3-24-ge0c7 From 5c4bd17d791d97310667acf6b458456aa1f8af0d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 10 Dec 2009 21:52:38 -0600 Subject: Reverse hash for looking up status codes by symbol --- lib/rack/utils.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 46362bab..333d8bf5 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -27,7 +27,7 @@ module Rack module_function :unescape DEFAULT_SEP = /[&;] */n - + # Stolen from Mongrel, with some small modifications: # Parses a query string by breaking it up at the '&' # and ';' characters. You can also use this to parse @@ -394,6 +394,12 @@ module Rack # Responses with HTTP status codes that should not have an entity body STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304) + SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)| + hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code + hash + } + + # A multipart form data parser, adapted from IOWA. # # Usually, Rack::Request#POST takes care of calling this. -- cgit v1.2.3-24-ge0c7 From a1534a5d7b48812e1e67d7ff02ef53b70e3ea492 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 15:40:08 -0600 Subject: Import Config by jcrosby (Jon Crosby) into core --- lib/rack.rb | 1 + lib/rack/config.rb | 15 +++++++++++++++ test/spec_rack_config.rb | 24 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 lib/rack/config.rb create mode 100644 test/spec_rack_config.rb diff --git a/lib/rack.rb b/lib/rack.rb index 25438afe..b2aed6a5 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -28,6 +28,7 @@ module Rack autoload :Chunked, "rack/chunked" autoload :CommonLogger, "rack/commonlogger" autoload :ConditionalGet, "rack/conditionalget" + autoload :Config, "rack/config" autoload :ContentLength, "rack/content_length" autoload :ContentType, "rack/content_type" autoload :File, "rack/file" diff --git a/lib/rack/config.rb b/lib/rack/config.rb new file mode 100644 index 00000000..c6d446c0 --- /dev/null +++ b/lib/rack/config.rb @@ -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 diff --git a/test/spec_rack_config.rb b/test/spec_rack_config.rb new file mode 100644 index 00000000..a508ea4b --- /dev/null +++ b/test/spec_rack_config.rb @@ -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 -- cgit v1.2.3-24-ge0c7 From 7077d4b6d3eb25c856c83b7c8a22210216fde645 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 15:52:08 -0600 Subject: Import etag middleware from contrib into core --- lib/rack.rb | 1 + lib/rack/etag.rb | 23 +++++++++++++++++++++++ test/spec_rack_etag.rb | 17 +++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 lib/rack/etag.rb create mode 100644 test/spec_rack_etag.rb diff --git a/lib/rack.rb b/lib/rack.rb index b2aed6a5..20b3c379 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -31,6 +31,7 @@ module Rack autoload :Config, "rack/config" autoload :ContentLength, "rack/content_length" autoload :ContentType, "rack/content_type" + autoload :ETag, "rack/etag" autoload :File, "rack/file" autoload :Deflater, "rack/deflater" autoload :Directory, "rack/directory" diff --git a/lib/rack/etag.rb b/lib/rack/etag.rb new file mode 100644 index 00000000..06dbc6aa --- /dev/null +++ b/lib/rack/etag.rb @@ -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 diff --git a/test/spec_rack_etag.rb b/test/spec_rack_etag.rb new file mode 100644 index 00000000..73cd31ac --- /dev/null +++ b/test/spec_rack_etag.rb @@ -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 -- cgit v1.2.3-24-ge0c7 From 37195bedbc6d1f02a47fea5712ad792aad5c1d4b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:00:57 -0600 Subject: Import runtime middleware by paul (Paul Sadauskas) into core --- lib/rack.rb | 1 + lib/rack/runtime.rb | 27 +++++++++++++++++++++++++++ test/spec_rack_runtime.rb | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 lib/rack/runtime.rb create mode 100644 test/spec_rack_runtime.rb diff --git a/lib/rack.rb b/lib/rack.rb index 20b3c379..bb1e4ed1 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -44,6 +44,7 @@ module Rack autoload :Mime, "rack/mime" autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" + autoload :Runtime, "rack/runtime" autoload :Server, "rack/server" autoload :ShowExceptions, "rack/showexceptions" autoload :ShowStatus, "rack/showstatus" diff --git a/lib/rack/runtime.rb b/lib/rack/runtime.rb new file mode 100644 index 00000000..1bd411fd --- /dev/null +++ b/lib/rack/runtime.rb @@ -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 diff --git a/test/spec_rack_runtime.rb b/test/spec_rack_runtime.rb new file mode 100644 index 00000000..62d80956 --- /dev/null +++ b/test/spec_rack_runtime.rb @@ -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 -- cgit v1.2.3-24-ge0c7 From 981f182bcfa1b848aa9e66c72500d855f6ee77ff Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:03:39 -0600 Subject: Import Ryan's Sendfile from contrib into core --- lib/rack.rb | 1 + lib/rack/sendfile.rb | 142 +++++++++++++++++++++++++++++++++++++++++++++ test/spec_rack_sendfile.rb | 86 +++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 lib/rack/sendfile.rb create mode 100644 test/spec_rack_sendfile.rb diff --git a/lib/rack.rb b/lib/rack.rb index bb1e4ed1..5f0e797b 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -45,6 +45,7 @@ module Rack autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" autoload :Runtime, "rack/runtime" + autoload :Sendfile, "rack/sendfile" autoload :Server, "rack/server" autoload :ShowExceptions, "rack/showexceptions" autoload :ShowStatus, "rack/showstatus" diff --git a/lib/rack/sendfile.rb b/lib/rack/sendfile.rb new file mode 100644 index 00000000..4fa82946 --- /dev/null +++ b/lib/rack/sendfile.rb @@ -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 diff --git a/test/spec_rack_sendfile.rb b/test/spec_rack_sendfile.rb new file mode 100644 index 00000000..8cfe2017 --- /dev/null +++ b/test/spec_rack_sendfile.rb @@ -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 -- cgit v1.2.3-24-ge0c7 From df22480a10063d3735374a394154658e72780c35 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:18:41 -0600 Subject: rack.logger specification --- lib/rack/lint.rb | 39 ++++++++++++++++++++++++++++++++++----- lib/rack/request.rb | 7 ++++--- test/spec_rack_lint.rb | 31 ++++++++++++++++++------------- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb index a1fcc3c6..493982ac 100644 --- a/lib/rack/lint.rb +++ b/lib/rack/lint.rb @@ -148,6 +148,35 @@ module Rack } end + ## rack.logger:: 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 ## environment, too. The keys must contain at least one dot, ## and should be prefixed uniquely. The prefix rack. @@ -243,7 +272,7 @@ module Rack assert("rack.input #{input} is not opened in binary mode") { input.binmode? } if input.respond_to?(:binmode?) - + ## The input stream must respond to +gets+, +each+, +read+ and +rewind+. [:gets, :each, :read, :rewind].each { |method| assert("rack.input #{input} does not respond to ##{method}") { @@ -300,9 +329,9 @@ module Rack args[1].kind_of?(String) } end - + v = @input.read(*args) - + assert("rack.input#read didn't return nil or a String") { v.nil? or v.kind_of? String } @@ -311,7 +340,7 @@ module Rack !v.nil? } end - + v end @@ -325,7 +354,7 @@ module Rack yield line } end - + ## * +rewind+ must be called without arguments. It rewinds the input ## stream back to the beginning. It must not raise Errno::ESPIPE: ## that is, it may not be a pipe or a socket. Therefore, handler diff --git a/lib/rack/request.rb b/lib/rack/request.rb index 248ce18d..d5070257 100644 --- a/lib/rack/request.rb +++ b/lib/rack/request.rb @@ -32,6 +32,7 @@ module Rack def content_type; @env['CONTENT_TYPE'] end def session; @env['rack.session'] ||= {} 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 # without any media type parameters. e.g., when CONTENT_TYPE is @@ -92,7 +93,7 @@ module Rack PARSEABLE_DATA_MEDIA_TYPES = [ 'multipart/related', 'multipart/mixed' - ] + ] # Determine whether the request body contains form-data by checking # the request media_type against registered form-data media-types: @@ -216,11 +217,11 @@ module Rack url end - + def path script_name + path_info end - + def fullpath query_string.empty? ? path : "#{path}?#{query_string}" end diff --git a/test/spec_rack_lint.rb b/test/spec_rack_lint.rb index 9c5d5031..bbf75c17 100644 --- a/test/spec_rack_lint.rb +++ b/test/spec_rack_lint.rb @@ -71,6 +71,11 @@ context "Rack::Lint" do }.should.raise(Rack::Lint::LintError). 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 { Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?")) }.should.raise(Rack::Lint::LintError). @@ -110,7 +115,7 @@ context "Rack::Lint" do Rack::Lint.new(nil).call(env("rack.input" => "")) }.should.raise(Rack::Lint::LintError). message.should.match(/does not respond to #gets/) - + lambda { input = Object.new def input.binmode? @@ -119,7 +124,7 @@ context "Rack::Lint" do Rack::Lint.new(nil).call(env("rack.input" => input)) }.should.raise(Rack::Lint::LintError). message.should.match(/is not opened in binary mode/) - + lambda { input = Object.new def input.external_encoding @@ -347,25 +352,25 @@ context "Rack::Lint" do yield 23 yield 42 end - + def rewind raise Errno::ESPIPE, "Errno::ESPIPE" end end - + eof_weirdio = Object.new class << eof_weirdio def gets nil end - + def read(*args) nil end - + def each end - + def rewind end end @@ -452,7 +457,7 @@ context "Rack::Lint" do }.should.raise(Rack::Lint::LintError). message.should.match(/body was given for HEAD/) end - + specify "passes valid read calls" do hello_str = "hello world" hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding @@ -462,35 +467,35 @@ context "Rack::Lint" do [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] }).call(env({"rack.input" => StringIO.new(hello_str)})) }.should.not.raise(Rack::Lint::LintError) - + lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(0) [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] }).call(env({"rack.input" => StringIO.new(hello_str)})) }.should.not.raise(Rack::Lint::LintError) - + lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(1) [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] }).call(env({"rack.input" => StringIO.new(hello_str)})) }.should.not.raise(Rack::Lint::LintError) - + lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(nil) [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] }).call(env({"rack.input" => StringIO.new(hello_str)})) }.should.not.raise(Rack::Lint::LintError) - + lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(nil, '') [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] }).call(env({"rack.input" => StringIO.new(hello_str)})) }.should.not.raise(Rack::Lint::LintError) - + lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(1, '') -- cgit v1.2.3-24-ge0c7 From 45c3d819eaa9849265280858acf5fd4c461cf049 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:29:43 -0600 Subject: Nop NullLogger --- lib/rack.rb | 1 + lib/rack/nulllogger.rb | 9 +++++++++ test/spec_rack_nulllogger.rb | 13 +++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 lib/rack/nulllogger.rb create mode 100644 test/spec_rack_nulllogger.rb diff --git a/lib/rack.rb b/lib/rack.rb index 5f0e797b..3d8e8ed8 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -42,6 +42,7 @@ module Rack autoload :Lock, "rack/lock" autoload :MethodOverride, "rack/methodoverride" autoload :Mime, "rack/mime" + autoload :NullLogger, "rack/nulllogger" autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" autoload :Runtime, "rack/runtime" diff --git a/lib/rack/nulllogger.rb b/lib/rack/nulllogger.rb new file mode 100644 index 00000000..a5a2fac4 --- /dev/null +++ b/lib/rack/nulllogger.rb @@ -0,0 +1,9 @@ +module Rack + class NullLogger + 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 diff --git a/test/spec_rack_nulllogger.rb b/test/spec_rack_nulllogger.rb new file mode 100644 index 00000000..b14a200a --- /dev/null +++ b/test/spec_rack_nulllogger.rb @@ -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!"]] + } + app.call({'rack.logger' => Rack::NullLogger.new}) + end +end -- cgit v1.2.3-24-ge0c7 From 78ae5ef9c7e8c9410f18063bcb130e70666a0489 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:40:57 -0600 Subject: Simple rack logger middleware that wraps rack.errors --- lib/rack.rb | 1 + lib/rack/logger.rb | 20 ++++++++++++++++++++ test/spec_rack_logger.rb | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 lib/rack/logger.rb create mode 100644 test/spec_rack_logger.rb diff --git a/lib/rack.rb b/lib/rack.rb index 3d8e8ed8..6bdb878f 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -40,6 +40,7 @@ module Rack autoload :Head, "rack/head" autoload :Lint, "rack/lint" autoload :Lock, "rack/lock" + autoload :Logger, "rack/logger" autoload :MethodOverride, "rack/methodoverride" autoload :Mime, "rack/mime" autoload :NullLogger, "rack/nulllogger" diff --git a/lib/rack/logger.rb b/lib/rack/logger.rb new file mode 100644 index 00000000..d67d8ce2 --- /dev/null +++ b/lib/rack/logger.rb @@ -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 diff --git a/test/spec_rack_logger.rb b/test/spec_rack_logger.rb new file mode 100644 index 00000000..d55b9c77 --- /dev/null +++ b/test/spec_rack_logger.rb @@ -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 -- cgit v1.2.3-24-ge0c7 From ab6c66bc8a1ec928e4c3eb0f0b97323879db84dc Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:42:37 -0600 Subject: NullLogger should actually be a middleware --- lib/rack/nulllogger.rb | 9 +++++++++ test/spec_rack_nulllogger.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/rack/nulllogger.rb b/lib/rack/nulllogger.rb index a5a2fac4..77fb637d 100644 --- a/lib/rack/nulllogger.rb +++ b/lib/rack/nulllogger.rb @@ -1,5 +1,14 @@ 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 diff --git a/test/spec_rack_nulllogger.rb b/test/spec_rack_nulllogger.rb index b14a200a..b3c2bc9c 100644 --- a/test/spec_rack_nulllogger.rb +++ b/test/spec_rack_nulllogger.rb @@ -8,6 +8,6 @@ context "Rack::NullLogger" do env['rack.logger'].warn "b00m" [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } - app.call({'rack.logger' => Rack::NullLogger.new}) + Rack::NullLogger.new(app).call({}) end end -- cgit v1.2.3-24-ge0c7 From 4cc6af9b4f0b633b076f27d3a76bf86ebf9fe64e Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 11 Dec 2009 16:44:07 -0600 Subject: ignore rackup log output --- test/rackup/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/rackup/.gitignore diff --git a/test/rackup/.gitignore b/test/rackup/.gitignore new file mode 100644 index 00000000..2f9d279a --- /dev/null +++ b/test/rackup/.gitignore @@ -0,0 +1 @@ +log_output -- cgit v1.2.3-24-ge0c7