rack-contrib 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rack-contrib might be problematic. Click here for more details.
- data/COPYING +18 -0
 - data/README.rdoc +60 -0
 - data/Rakefile +92 -0
 - data/lib/rack/contrib/bounce_favicon.rb +16 -0
 - data/lib/rack/contrib/callbacks.rb +37 -0
 - data/lib/rack/contrib/etag.rb +20 -0
 - data/lib/rack/contrib/evil.rb +12 -0
 - data/lib/rack/contrib/garbagecollector.rb +14 -0
 - data/lib/rack/contrib/jsonp.rb +38 -0
 - data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
 - data/lib/rack/contrib/locale.rb +31 -0
 - data/lib/rack/contrib/mailexceptions.rb +120 -0
 - data/lib/rack/contrib/nested_params.rb +143 -0
 - data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
 - data/lib/rack/contrib/proctitle.rb +30 -0
 - data/lib/rack/contrib/profiler.rb +106 -0
 - data/lib/rack/contrib/route_exceptions.rb +48 -0
 - data/lib/rack/contrib/sendfile.rb +142 -0
 - data/lib/rack/contrib/time_zone.rb +25 -0
 - data/lib/rack/contrib.rb +25 -0
 - data/rack-contrib.gemspec +68 -0
 - data/test/mail_settings.rb +12 -0
 - data/test/spec_rack_callbacks.rb +65 -0
 - data/test/spec_rack_contrib.rb +8 -0
 - data/test/spec_rack_etag.rb +23 -0
 - data/test/spec_rack_evil.rb +19 -0
 - data/test/spec_rack_garbagecollector.rb +13 -0
 - data/test/spec_rack_jsonp.rb +21 -0
 - data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
 - data/test/spec_rack_mailexceptions.rb +97 -0
 - data/test/spec_rack_nested_params.rb +46 -0
 - data/test/spec_rack_post_body_content_type_parser.rb +32 -0
 - data/test/spec_rack_proctitle.rb +26 -0
 - data/test/spec_rack_profiler.rb +32 -0
 - data/test/spec_rack_sendfile.rb +86 -0
 - metadata +144 -0
 
    
        data/COPYING
    ADDED
    
    | 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2008 The Committers
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         
     | 
| 
      
 4 
     | 
    
         
            +
            of this software and associated documentation files (the "Software"), to
         
     | 
| 
      
 5 
     | 
    
         
            +
            deal in the Software without restriction, including without limitation the
         
     | 
| 
      
 6 
     | 
    
         
            +
            rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
         
     | 
| 
      
 7 
     | 
    
         
            +
            sell copies of the Software, and to permit persons to whom the Software is
         
     | 
| 
      
 8 
     | 
    
         
            +
            furnished to do so, subject to the following conditions:
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be included in
         
     | 
| 
      
 11 
     | 
    
         
            +
            all copies or substantial portions of the Software.
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         
     | 
| 
      
 14 
     | 
    
         
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         
     | 
| 
      
 15 
     | 
    
         
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
         
     | 
| 
      
 16 
     | 
    
         
            +
            THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
         
     | 
| 
      
 17 
     | 
    
         
            +
            IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
         
     | 
| 
      
 18 
     | 
    
         
            +
            CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.rdoc
    ADDED
    
    | 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            = Contributed Rack Middleware and Utilities
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            This package includes a variety of add-on components for Rack, a Ruby web server
         
     | 
| 
      
 4 
     | 
    
         
            +
            interface:
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            * Rack::ETag - Automatically sets the ETag header on all String bodies.
         
     | 
| 
      
 7 
     | 
    
         
            +
            * Rack::JSONP - Adds JSON-P support by stripping out the callback param
         
     | 
| 
      
 8 
     | 
    
         
            +
              and padding the response with the appropriate callback format.
         
     | 
| 
      
 9 
     | 
    
         
            +
            * Rack::LighttpdScriptNameFix - Fixes how lighttpd sets the SCRIPT_NAME
         
     | 
| 
      
 10 
     | 
    
         
            +
              and PATH_INFO variables in certain configurations.
         
     | 
| 
      
 11 
     | 
    
         
            +
            * Rack::Locale - Detects the client locale using the Accept-Language request
         
     | 
| 
      
 12 
     | 
    
         
            +
              header and sets a rack.locale variable in the environment.
         
     | 
| 
      
 13 
     | 
    
         
            +
            * Rack::MailExceptions - Rescues exceptions raised from the app and
         
     | 
| 
      
 14 
     | 
    
         
            +
              sends a useful email with the exception, stacktrace, and contents of the
         
     | 
| 
      
 15 
     | 
    
         
            +
              environment.
         
     | 
| 
      
 16 
     | 
    
         
            +
            * Rack::NestedParams - parses form params with subscripts (e.g., * "post[title]=Hello")
         
     | 
| 
      
 17 
     | 
    
         
            +
              into a nested/recursive Hash structure (based on Rails' implementation).
         
     | 
| 
      
 18 
     | 
    
         
            +
            * Rack::PostBodyContentTypeParser - Adds support for JSON request bodies. The
         
     | 
| 
      
 19 
     | 
    
         
            +
              Rack parameter hash is populated by deserializing the JSON data provided in
         
     | 
| 
      
 20 
     | 
    
         
            +
              the request body when the Content-Type is application/json.
         
     | 
| 
      
 21 
     | 
    
         
            +
            * Rack::ProcTitle - Displays request information in process title ($0) for
         
     | 
| 
      
 22 
     | 
    
         
            +
              monitoring/inspection with ps(1).
         
     | 
| 
      
 23 
     | 
    
         
            +
            * Rack::Profiler - Uses ruby-prof to measure request time.
         
     | 
| 
      
 24 
     | 
    
         
            +
            * Rack::Sendfile - Enables X-Sendfile support for bodies that can be served
         
     | 
| 
      
 25 
     | 
    
         
            +
              from file.
         
     | 
| 
      
 26 
     | 
    
         
            +
            * Rack::TimeZone - Detects the clients timezone using JavaScript and sets
         
     | 
| 
      
 27 
     | 
    
         
            +
              a variable in Rack's environment with the offset from UTC.
         
     | 
| 
      
 28 
     | 
    
         
            +
            * Rack::Evil - Lets the rack application return a response to the client from any place.
         
     | 
| 
      
 29 
     | 
    
         
            +
            * Rack::Callbacks - Implements DLS for pure before/after filter like Middlewares.
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            === Use
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            Git is the quickest way to the rack-contrib sources:
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              git clone git://github.com/rack/rack-contrib.git
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            Gems are currently available from GitHub clones:
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              gem install rack-rack-contrib --source=http://gems.github.com/
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Requiring 'rack/contrib' will add autoloads to the Rack modules for all of the
         
     | 
| 
      
 42 
     | 
    
         
            +
            components included. The following example shows what a simple rackup
         
     | 
| 
      
 43 
     | 
    
         
            +
            (+config.ru+) file might look like:
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              require 'rack'
         
     | 
| 
      
 46 
     | 
    
         
            +
              require 'rack/contrib'
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              use Rack::Profiler if ENV['RACK_ENV'] == 'development'
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              use Rack::ETag
         
     | 
| 
      
 51 
     | 
    
         
            +
              use Rack::MailExceptions
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              run theapp
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            === Links
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            rack-contrib on GitHub:: <http://github.com/rack/rack-contrib>
         
     | 
| 
      
 58 
     | 
    
         
            +
            Rack:: <http://rack.rubyforge.org/>
         
     | 
| 
      
 59 
     | 
    
         
            +
            Rack On GitHub:: <http://github.org/rack/rack>
         
     | 
| 
      
 60 
     | 
    
         
            +
            rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,92 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Rakefile for Rack::Contrib.  -*-ruby-*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rake/rdoctask'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rake/testtask'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            desc "Run all the tests"
         
     | 
| 
      
 6 
     | 
    
         
            +
            task :default => [:test]
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            desc "Generate RDox"
         
     | 
| 
      
 9 
     | 
    
         
            +
            task "RDOX" do
         
     | 
| 
      
 10 
     | 
    
         
            +
              sh "specrb -Ilib:test -a --rdox >RDOX"
         
     | 
| 
      
 11 
     | 
    
         
            +
            end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            desc "Run all the fast tests"
         
     | 
| 
      
 14 
     | 
    
         
            +
            task :test do
         
     | 
| 
      
 15 
     | 
    
         
            +
              sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            desc "Run all the tests"
         
     | 
| 
      
 19 
     | 
    
         
            +
            task :fulltest do
         
     | 
| 
      
 20 
     | 
    
         
            +
              sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            desc "Generate RDoc documentation"
         
     | 
| 
      
 24 
     | 
    
         
            +
            Rake::RDocTask.new(:rdoc) do |rdoc|
         
     | 
| 
      
 25 
     | 
    
         
            +
              rdoc.options << '--line-numbers' << '--inline-source' <<
         
     | 
| 
      
 26 
     | 
    
         
            +
                '--main' << 'README' <<
         
     | 
| 
      
 27 
     | 
    
         
            +
                '--title' << 'Rack Contrib Documentation' <<
         
     | 
| 
      
 28 
     | 
    
         
            +
                '--charset' << 'utf-8'
         
     | 
| 
      
 29 
     | 
    
         
            +
              rdoc.rdoc_dir = "doc"
         
     | 
| 
      
 30 
     | 
    
         
            +
              rdoc.rdoc_files.include 'README.rdoc'
         
     | 
| 
      
 31 
     | 
    
         
            +
              rdoc.rdoc_files.include 'RDOX'
         
     | 
| 
      
 32 
     | 
    
         
            +
              rdoc.rdoc_files.include('lib/rack/*.rb')
         
     | 
| 
      
 33 
     | 
    
         
            +
              rdoc.rdoc_files.include('lib/rack/*/*.rb')
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
            task :rdoc => ["RDOX"]
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            # PACKAGING =================================================================
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            # load gemspec like github's gem builder to surface any SAFE issues.
         
     | 
| 
      
 41 
     | 
    
         
            +
            require 'rubygems/specification'
         
     | 
| 
      
 42 
     | 
    
         
            +
            $spec = eval(File.read('rack-contrib.gemspec'))
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            def package(ext='')
         
     | 
| 
      
 45 
     | 
    
         
            +
              "pkg/rack-contrib-#{$spec.version}" + ext
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            desc 'Build packages'
         
     | 
| 
      
 49 
     | 
    
         
            +
            task :package => %w[.gem .tar.gz].map {|e| package(e)}
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            desc 'Build and install as local gem'
         
     | 
| 
      
 52 
     | 
    
         
            +
            task :install => package('.gem') do
         
     | 
| 
      
 53 
     | 
    
         
            +
              sh "gem install #{package('.gem')}"
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            directory 'pkg/'
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            file package('.gem') => %w[pkg/ rack-contrib.gemspec] + $spec.files do |f|
         
     | 
| 
      
 59 
     | 
    
         
            +
              sh "gem build rack-contrib.gemspec"
         
     | 
| 
      
 60 
     | 
    
         
            +
              mv File.basename(f.name), f.name
         
     | 
| 
      
 61 
     | 
    
         
            +
            end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
         
     | 
| 
      
 64 
     | 
    
         
            +
              sh "git archive --format=tar HEAD | gzip > #{f.name}"
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            desc 'Publish gem and tarball to rubyforge'
         
     | 
| 
      
 68 
     | 
    
         
            +
            task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
         
     | 
| 
      
 69 
     | 
    
         
            +
              sh <<-end
         
     | 
| 
      
 70 
     | 
    
         
            +
                rubyforge add_release rack rack-contrib #{$spec.version} #{package('.gem')} &&
         
     | 
| 
      
 71 
     | 
    
         
            +
                rubyforge add_file    rack rack-contrib #{$spec.version} #{package('.tar.gz')}
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            # GEMSPEC ===================================================================
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            file 'rack-contrib.gemspec' => FileList['{lib,test}/**','Rakefile', 'README.rdoc'] do |f|
         
     | 
| 
      
 78 
     | 
    
         
            +
              # read spec file and split out manifest section
         
     | 
| 
      
 79 
     | 
    
         
            +
              spec = File.read(f.name)
         
     | 
| 
      
 80 
     | 
    
         
            +
              parts = spec.split("  # = MANIFEST =\n")
         
     | 
| 
      
 81 
     | 
    
         
            +
              fail 'bad spec' if parts.length != 3
         
     | 
| 
      
 82 
     | 
    
         
            +
              # determine file list from git ls-files
         
     | 
| 
      
 83 
     | 
    
         
            +
              files = `git ls-files`.
         
     | 
| 
      
 84 
     | 
    
         
            +
                split("\n").sort.reject{ |file| file =~ /^\./ }.
         
     | 
| 
      
 85 
     | 
    
         
            +
                map{ |file| "    #{file}" }.join("\n")
         
     | 
| 
      
 86 
     | 
    
         
            +
              # piece file back together and write...
         
     | 
| 
      
 87 
     | 
    
         
            +
              parts[1] = "  s.files = %w[\n#{files}\n  ]\n"
         
     | 
| 
      
 88 
     | 
    
         
            +
              spec = parts.join("  # = MANIFEST =\n")
         
     | 
| 
      
 89 
     | 
    
         
            +
              spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'")
         
     | 
| 
      
 90 
     | 
    
         
            +
              File.open(f.name, 'w') { |io| io.write(spec) }
         
     | 
| 
      
 91 
     | 
    
         
            +
              puts "updated #{f.name}"
         
     | 
| 
      
 92 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Bounce those annoying favicon.ico requests
         
     | 
| 
      
 3 
     | 
    
         
            +
              class BounceFavicon
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  if env["PATH_INFO"] == "/favicon.ico"
         
     | 
| 
      
 10 
     | 
    
         
            +
                    [404, {"Content-Type" => "text/html", "Content-Length" => "0"}, []]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  else
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Callbacks
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize(&block)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @before = []
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @after  = []
         
     | 
| 
      
 6 
     | 
    
         
            +
                  instance_eval(&block) if block_given?
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def before(middleware, *args, &block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @before << middleware.new(*args, &block)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  else
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @before << middleware.new(*args)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def after(middleware, *args, &block)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @after << middleware.new(*args, &block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  else
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @after << middleware.new(*args)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def run(app)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @before.each {|c| c.call(env) }
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  response = @app.call(env)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  @after.inject(response) {|r, c| c.call(r) }
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'digest/md5'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Automatically sets the ETag header on all String bodies
         
     | 
| 
      
 5 
     | 
    
         
            +
              class ETag
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  status, headers, body = @app.call(env)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  if !headers.has_key?('ETag') && body.is_a?(String)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    headers['ETag'] = %("#{Digest::MD5.hexdigest(body)}")
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  [status, headers, body]
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 2 
     | 
    
         
            +
              
         
     | 
| 
      
 3 
     | 
    
         
            +
              # A Rack middleware for providing JSON-P support.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # 
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
         
     | 
| 
      
 6 
     | 
    
         
            +
              # 
         
     | 
| 
      
 7 
     | 
    
         
            +
              class JSONP
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
                
         
     | 
| 
      
 13 
     | 
    
         
            +
                # Proxies the request to the application, stripping out the JSON-P callback
         
     | 
| 
      
 14 
     | 
    
         
            +
                # method and padding the response with the appropriate callback format.
         
     | 
| 
      
 15 
     | 
    
         
            +
                # 
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Changes nothing if no <tt>callback</tt> param is specified.
         
     | 
| 
      
 17 
     | 
    
         
            +
                # 
         
     | 
| 
      
 18 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  status, headers, response = @app.call(env)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  request = Rack::Request.new(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  response = pad(request.params.delete('callback'), response) if request.params.include?('callback')
         
     | 
| 
      
 22 
     | 
    
         
            +
                  [status, headers, response]
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Pads the response with the appropriate callback format according to the
         
     | 
| 
      
 26 
     | 
    
         
            +
                # JSON-P spec/requirements.
         
     | 
| 
      
 27 
     | 
    
         
            +
                # 
         
     | 
| 
      
 28 
     | 
    
         
            +
                # The Rack response spec indicates that it should be enumerable. The method
         
     | 
| 
      
 29 
     | 
    
         
            +
                # of combining all of the data into a sinle string makes sense since JSON
         
     | 
| 
      
 30 
     | 
    
         
            +
                # is returned as a full string.
         
     | 
| 
      
 31 
     | 
    
         
            +
                # 
         
     | 
| 
      
 32 
     | 
    
         
            +
                def pad(callback, response, body = "")
         
     | 
| 
      
 33 
     | 
    
         
            +
                  response.each{ |s| body << s }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  "#{callback}(#{body})"
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your
         
     | 
| 
      
 3 
     | 
    
         
            +
              # FastCGI app at "/". This middleware fixes this issue.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              class LighttpdScriptNameFix
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  env["PATH_INFO"] = env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s
         
     | 
| 
      
 12 
     | 
    
         
            +
                  env["SCRIPT_NAME"] = ""
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @app.call(env)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'i18n'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Locale
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  old_locale = I18n.locale
         
     | 
| 
      
 11 
     | 
    
         
            +
                  locale = nil
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
         
     | 
| 
      
 14 
     | 
    
         
            +
                  if lang = env["HTTP_ACCEPT_LANGUAGE"]
         
     | 
| 
      
 15 
     | 
    
         
            +
                    lang = lang.split(",").map { |l|
         
     | 
| 
      
 16 
     | 
    
         
            +
                      l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
         
     | 
| 
      
 17 
     | 
    
         
            +
                      l.split(';q=')
         
     | 
| 
      
 18 
     | 
    
         
            +
                    }.first
         
     | 
| 
      
 19 
     | 
    
         
            +
                    locale = lang.first.split("-").first
         
     | 
| 
      
 20 
     | 
    
         
            +
                  else
         
     | 
| 
      
 21 
     | 
    
         
            +
                    locale = I18n.default_locale
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  locale = env['rack.locale'] = I18n.locale = locale.to_s
         
     | 
| 
      
 25 
     | 
    
         
            +
                  status, headers, body = @app.call(env)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  headers['Content-Language'] = locale
         
     | 
| 
      
 27 
     | 
    
         
            +
                  I18n.locale = old_locale
         
     | 
| 
      
 28 
     | 
    
         
            +
                  [status, headers, body]
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,120 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'net/smtp'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'tmail'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'erb'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Catches all exceptions raised from the app it wraps and
         
     | 
| 
      
 7 
     | 
    
         
            +
              # sends a useful email with the exception, stacktrace, and
         
     | 
| 
      
 8 
     | 
    
         
            +
              # contents of the environment.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              class MailExceptions
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :config
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @config = {
         
     | 
| 
      
 16 
     | 
    
         
            +
                    :to      => nil,
         
     | 
| 
      
 17 
     | 
    
         
            +
                    :from    => ENV['USER'] || 'rack',
         
     | 
| 
      
 18 
     | 
    
         
            +
                    :subject => '[exception] %s',
         
     | 
| 
      
 19 
     | 
    
         
            +
                    :smtp    => {
         
     | 
| 
      
 20 
     | 
    
         
            +
                      :server         => 'localhost',
         
     | 
| 
      
 21 
     | 
    
         
            +
                      :domain         => 'localhost',
         
     | 
| 
      
 22 
     | 
    
         
            +
                      :port           => 25,
         
     | 
| 
      
 23 
     | 
    
         
            +
                      :authentication => :login,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      :user_name      => nil,
         
     | 
| 
      
 25 
     | 
    
         
            +
                      :password       => nil
         
     | 
| 
      
 26 
     | 
    
         
            +
                    }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  }
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @template = ERB.new(TEMPLATE)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  yield self if block_given?
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  status, headers, body =
         
     | 
| 
      
 34 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 35 
     | 
    
         
            +
                      @app.call(env)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    rescue => boom
         
     | 
| 
      
 37 
     | 
    
         
            +
                      # TODO don't allow exceptions from send_notification to
         
     | 
| 
      
 38 
     | 
    
         
            +
                      # propogate
         
     | 
| 
      
 39 
     | 
    
         
            +
                      send_notification boom, env
         
     | 
| 
      
 40 
     | 
    
         
            +
                      raise
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  send_notification env['mail.exception'], env if env['mail.exception']
         
     | 
| 
      
 43 
     | 
    
         
            +
                  [status, headers, body]
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                %w[to from subject].each do |meth|
         
     | 
| 
      
 47 
     | 
    
         
            +
                  define_method(meth) { |value| @config[meth.to_sym] = value }
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def smtp(settings={})
         
     | 
| 
      
 51 
     | 
    
         
            +
                  @config[:smtp].merge! settings
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              private
         
     | 
| 
      
 55 
     | 
    
         
            +
                def generate_mail(exception, env)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  mail = TMail::Mail.new
         
     | 
| 
      
 57 
     | 
    
         
            +
                  mail.to = Array(config[:to])
         
     | 
| 
      
 58 
     | 
    
         
            +
                  mail.from = config[:from]
         
     | 
| 
      
 59 
     | 
    
         
            +
                  mail.subject = config[:subject] % [exception.to_s]
         
     | 
| 
      
 60 
     | 
    
         
            +
                  mail.date = Time.now
         
     | 
| 
      
 61 
     | 
    
         
            +
                  mail.set_content_type 'text/plain'
         
     | 
| 
      
 62 
     | 
    
         
            +
                  mail.charset = 'UTF-8'
         
     | 
| 
      
 63 
     | 
    
         
            +
                  mail.body = @template.result(binding)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  mail
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def send_notification(exception, env)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  mail = generate_mail(exception, env)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  smtp = config[:smtp]
         
     | 
| 
      
 70 
     | 
    
         
            +
                  env['mail.sent'] = true
         
     | 
| 
      
 71 
     | 
    
         
            +
                  return if smtp[:server] == 'example.com'
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  Net::SMTP.start smtp[:address], smtp[:port], smtp[:domain], smtp[:user_name], smtp[:password], smtp[:authentication] do |server|
         
     | 
| 
      
 74 
     | 
    
         
            +
                    mail.to.each do |recipient|
         
     | 
| 
      
 75 
     | 
    
         
            +
                      server.send_message mail.to_s, mail.from, recipient
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def extract_body(env)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  if io = env['rack.input']
         
     | 
| 
      
 82 
     | 
    
         
            +
                    io.rewind if io.respond_to?(:rewind)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    io.read
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                TEMPLATE = (<<-'EMAIL').gsub(/^ {4}/, '')
         
     | 
| 
      
 88 
     | 
    
         
            +
                A <%= exception.class.to_s %> occured: <%= exception.to_s %>
         
     | 
| 
      
 89 
     | 
    
         
            +
                <% if body = extract_body(env) %>
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 92 
     | 
    
         
            +
                Request Body:
         
     | 
| 
      
 93 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                <%= body.gsub(/^/, '  ') %>
         
     | 
| 
      
 96 
     | 
    
         
            +
                <% end %>
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 99 
     | 
    
         
            +
                Rack Environment:
         
     | 
| 
      
 100 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  PID:                     <%= $$ %>
         
     | 
| 
      
 103 
     | 
    
         
            +
                  PWD:                     <%= Dir.getwd %>
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                  <%= env.to_a.
         
     | 
| 
      
 106 
     | 
    
         
            +
                    sort{|a,b| a.first <=> b.first}.
         
     | 
| 
      
 107 
     | 
    
         
            +
                    map{ |k,v| "%-25s%p" % [k+':', v] }.
         
     | 
| 
      
 108 
     | 
    
         
            +
                    join("\n  ") %>
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                <% if exception.respond_to?(:backtrace) %>
         
     | 
| 
      
 111 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 112 
     | 
    
         
            +
                Backtrace:
         
     | 
| 
      
 113 
     | 
    
         
            +
                ===================================================================
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  <%= exception.backtrace.join("\n  ") %>
         
     | 
| 
      
 116 
     | 
    
         
            +
                <% end %>
         
     | 
| 
      
 117 
     | 
    
         
            +
                EMAIL
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,143 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'cgi'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'strscan'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Rack middleware for parsing POST/PUT body data into nested parameters
         
     | 
| 
      
 6 
     | 
    
         
            +
              class NestedParams
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                CONTENT_TYPE = 'CONTENT_TYPE'.freeze
         
     | 
| 
      
 9 
     | 
    
         
            +
                POST_BODY = 'rack.input'.freeze
         
     | 
| 
      
 10 
     | 
    
         
            +
                FORM_INPUT = 'rack.request.form_input'.freeze
         
     | 
| 
      
 11 
     | 
    
         
            +
                FORM_HASH = 'rack.request.form_hash'.freeze
         
     | 
| 
      
 12 
     | 
    
         
            +
                FORM_VARS = 'rack.request.form_vars'.freeze
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # supported content type
         
     | 
| 
      
 15 
     | 
    
         
            +
                URL_ENCODED = 'application/x-www-form-urlencoded'.freeze
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  if form_vars = env[FORM_VARS]
         
     | 
| 
      
 23 
     | 
    
         
            +
                    env[FORM_HASH] = parse_query_parameters(form_vars)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  elsif env[CONTENT_TYPE] == URL_ENCODED
         
     | 
| 
      
 25 
     | 
    
         
            +
                    post_body = env[POST_BODY]
         
     | 
| 
      
 26 
     | 
    
         
            +
                    env[FORM_INPUT] = post_body
         
     | 
| 
      
 27 
     | 
    
         
            +
                    env[FORM_HASH] = parse_query_parameters(post_body.read)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    post_body.rewind if post_body.respond_to?(:rewind)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @app.call(env)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                ## the rest is nabbed from Rails ##
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def parse_query_parameters(query_string)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  return {} if query_string.nil? or query_string.empty?
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  pairs = query_string.split('&').collect do |chunk|
         
     | 
| 
      
 39 
     | 
    
         
            +
                    next if chunk.empty?
         
     | 
| 
      
 40 
     | 
    
         
            +
                    key, value = chunk.split('=', 2)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    next if key.empty?
         
     | 
| 
      
 42 
     | 
    
         
            +
                    value = value.nil? ? nil : CGI.unescape(value)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    [ CGI.unescape(key), value ]
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end.compact
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  UrlEncodedPairParser.new(pairs).result
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                class UrlEncodedPairParser < StringScanner
         
     | 
| 
      
 50 
     | 
    
         
            +
                  attr_reader :top, :parent, :result
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def initialize(pairs = [])
         
     | 
| 
      
 53 
     | 
    
         
            +
                    super('')
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @result = {}
         
     | 
| 
      
 55 
     | 
    
         
            +
                    pairs.each { |key, value| parse(key, value) }
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  KEY_REGEXP = %r{([^\[\]=&]+)}
         
     | 
| 
      
 59 
     | 
    
         
            +
                  BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  # Parse the query string
         
     | 
| 
      
 62 
     | 
    
         
            +
                  def parse(key, value)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    self.string = key
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @top, @parent = result, nil
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    # First scan the bare key
         
     | 
| 
      
 67 
     | 
    
         
            +
                    key = scan(KEY_REGEXP) or return
         
     | 
| 
      
 68 
     | 
    
         
            +
                    key = post_key_check(key)
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    # Then scan as many nestings as present
         
     | 
| 
      
 71 
     | 
    
         
            +
                    until eos?
         
     | 
| 
      
 72 
     | 
    
         
            +
                      r = scan(BRACKETED_KEY_REGEXP) or return
         
     | 
| 
      
 73 
     | 
    
         
            +
                      key = self[1]
         
     | 
| 
      
 74 
     | 
    
         
            +
                      key = post_key_check(key)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    bind(key, value)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  private
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # After we see a key, we must look ahead to determine our next action. Cases:
         
     | 
| 
      
 82 
     | 
    
         
            +
                    #
         
     | 
| 
      
 83 
     | 
    
         
            +
                    #   [] follows the key. Then the value must be an array.
         
     | 
| 
      
 84 
     | 
    
         
            +
                    #   = follows the key. (A value comes next)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    #   & or the end of string follows the key. Then the key is a flag.
         
     | 
| 
      
 86 
     | 
    
         
            +
                    #   otherwise, a hash follows the key.
         
     | 
| 
      
 87 
     | 
    
         
            +
                    def post_key_check(key)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      if scan(/\[\]/) # a[b][] indicates that b is an array
         
     | 
| 
      
 89 
     | 
    
         
            +
                        container(key, Array)
         
     | 
| 
      
 90 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 91 
     | 
    
         
            +
                      elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
         
     | 
| 
      
 92 
     | 
    
         
            +
                        container(key, Hash)
         
     | 
| 
      
 93 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 94 
     | 
    
         
            +
                      else # End of key? We do nothing.
         
     | 
| 
      
 95 
     | 
    
         
            +
                        key
         
     | 
| 
      
 96 
     | 
    
         
            +
                      end
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    # Add a container to the stack.
         
     | 
| 
      
 100 
     | 
    
         
            +
                    def container(key, klass)
         
     | 
| 
      
 101 
     | 
    
         
            +
                      type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
         
     | 
| 
      
 102 
     | 
    
         
            +
                      value = bind(key, klass.new)
         
     | 
| 
      
 103 
     | 
    
         
            +
                      type_conflict! klass, value unless value.is_a?(klass)
         
     | 
| 
      
 104 
     | 
    
         
            +
                      push(value)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                    # Push a value onto the 'stack', which is actually only the top 2 items.
         
     | 
| 
      
 108 
     | 
    
         
            +
                    def push(value)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      @parent, @top = @top, value
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    # Bind a key (which may be nil for items in an array) to the provided value.
         
     | 
| 
      
 113 
     | 
    
         
            +
                    def bind(key, value)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      if top.is_a? Array
         
     | 
| 
      
 115 
     | 
    
         
            +
                        if key
         
     | 
| 
      
 116 
     | 
    
         
            +
                          if top[-1].is_a?(Hash) && ! top[-1].key?(key)
         
     | 
| 
      
 117 
     | 
    
         
            +
                            top[-1][key] = value
         
     | 
| 
      
 118 
     | 
    
         
            +
                          else
         
     | 
| 
      
 119 
     | 
    
         
            +
                            top << {key => value}
         
     | 
| 
      
 120 
     | 
    
         
            +
                          end
         
     | 
| 
      
 121 
     | 
    
         
            +
                          push top.last
         
     | 
| 
      
 122 
     | 
    
         
            +
                          return top[key]
         
     | 
| 
      
 123 
     | 
    
         
            +
                        else
         
     | 
| 
      
 124 
     | 
    
         
            +
                          top << value
         
     | 
| 
      
 125 
     | 
    
         
            +
                          return value
         
     | 
| 
      
 126 
     | 
    
         
            +
                        end
         
     | 
| 
      
 127 
     | 
    
         
            +
                      elsif top.is_a? Hash
         
     | 
| 
      
 128 
     | 
    
         
            +
                        key = CGI.unescape(key)
         
     | 
| 
      
 129 
     | 
    
         
            +
                        parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
         
     | 
| 
      
 130 
     | 
    
         
            +
                        top[key] ||= value
         
     | 
| 
      
 131 
     | 
    
         
            +
                        return top[key]
         
     | 
| 
      
 132 
     | 
    
         
            +
                      else
         
     | 
| 
      
 133 
     | 
    
         
            +
                        raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
         
     | 
| 
      
 134 
     | 
    
         
            +
                      end
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    def type_conflict!(klass, value)
         
     | 
| 
      
 138 
     | 
    
         
            +
                      raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
         
     | 
| 
      
 139 
     | 
    
         
            +
                    end
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
      
 143 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,40 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            begin
         
     | 
| 
      
 2 
     | 
    
         
            +
              require 'json'
         
     | 
| 
      
 3 
     | 
    
         
            +
            rescue LoadError => e
         
     | 
| 
      
 4 
     | 
    
         
            +
              require 'json/pure'
         
     | 
| 
      
 5 
     | 
    
         
            +
            end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 8 
     | 
    
         
            +
              
         
     | 
| 
      
 9 
     | 
    
         
            +
              # A Rack middleware for parsing POST/PUT body data when Content-Type is
         
     | 
| 
      
 10 
     | 
    
         
            +
              # not one of the standard supported types, like <tt>application/json</tt>.
         
     | 
| 
      
 11 
     | 
    
         
            +
              # 
         
     | 
| 
      
 12 
     | 
    
         
            +
              # TODO: Find a better name.
         
     | 
| 
      
 13 
     | 
    
         
            +
              # 
         
     | 
| 
      
 14 
     | 
    
         
            +
              class PostBodyContentTypeParser
         
     | 
| 
      
 15 
     | 
    
         
            +
                
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Constants
         
     | 
| 
      
 17 
     | 
    
         
            +
                # 
         
     | 
| 
      
 18 
     | 
    
         
            +
                CONTENT_TYPE = 'CONTENT_TYPE'.freeze
         
     | 
| 
      
 19 
     | 
    
         
            +
                POST_BODY = 'rack.input'.freeze
         
     | 
| 
      
 20 
     | 
    
         
            +
                FORM_INPUT = 'rack.request.form_input'.freeze
         
     | 
| 
      
 21 
     | 
    
         
            +
                FORM_HASH = 'rack.request.form_hash'.freeze
         
     | 
| 
      
 22 
     | 
    
         
            +
                
         
     | 
| 
      
 23 
     | 
    
         
            +
                # Supported Content-Types
         
     | 
| 
      
 24 
     | 
    
         
            +
                # 
         
     | 
| 
      
 25 
     | 
    
         
            +
                APPLICATION_JSON = 'application/json'.freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
                
         
     | 
| 
      
 31 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  case env[CONTENT_TYPE]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  when APPLICATION_JSON
         
     | 
| 
      
 34 
     | 
    
         
            +
                    env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @app.call(env)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
                
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
            end
         
     |