pax_global_header00006660000000000000000000000064140013626710014512gustar00rootroot0000000000000052 comment=559489791faea9a4e93f3ace4e9842cfdf7b2ca3 ruby-rack-parser-0.7.0/000077500000000000000000000000001400136267100147075ustar00rootroot00000000000000ruby-rack-parser-0.7.0/.gitignore000066400000000000000000000000411400136267100166720ustar00rootroot00000000000000*.gem .bundle Gemfile.lock pkg/* ruby-rack-parser-0.7.0/Gemfile000066400000000000000000000001401400136267100161750ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem's dependencies in rack-parser.gemspec gemspec ruby-rack-parser-0.7.0/MIT-LICENSE000066400000000000000000000020401400136267100163370ustar00rootroot00000000000000Copyright (c) 2011 Arthur Chiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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. ruby-rack-parser-0.7.0/README.md000066400000000000000000000077061400136267100162000ustar00rootroot00000000000000# Rack::Parser # Rack::Parser is a rack middleware that allows your application to do decode/parse incoming post data into param hashes for your applications to use. You can provide a custom Parser for things like JSON, XML, MSGPACK using your library of choice. ## Installation ## install it via rubygems: ``` gem install rack-parser ``` or put it in your Gemfile: ```ruby # Gemfile gem 'rack-parser', :require => 'rack/parser' ``` ## Usage ## In a Sinatra or [Padrino](http://padrinorb.com) application, it would probably be something like: ```ruby # app.rb use Rack::Parser, :parsers => { 'application/json' => proc { |data| JSON.parse data }, 'application/xml' => proc { |data| XML.parse data }, %r{msgpack} => proc { |data| Msgpack.parse data } } ``` ### Content Type Parsing ### By default, Rack::Parser uses `JSON` decode/parse your JSON Data. This can be overwritten if you choose not to use them. You can do it like so: ```ruby use Rack::Parser, :parsers => { 'application/json' => proc { |body| MyCustomJsonEngine.do_it body }, 'application/xml' => proc { |body| MyCustomXmlEngine.decode body }, 'application/roll' => proc { |body| 'never gonna give you up' } } ``` ### Error Handling ### Rack::Parser comes with a default error handling response that is sent if an error is to occur. If a `logger` is present, it will try to `warn` with the content type and error message. You can additionally customize the error handling response as well to whatever it is you like: ```ruby use Rack::Parser, :handlers => { 'application/json' => proc { |e, type| [400, { 'Content-Type' => type }, ["broke"]] } } ``` The error handler expects to pass both the `error` and `content_type` so that you can use them within your responses. In addition, you can override the default response as well. If no content_type error handling response is present, it will return `400` Do note, the error handler rescues exceptions that are descents of `StandardError`. See http://www.mikeperham.com/2012/03/03/the-perils-of-rescue-exception/ ### Regex Matching ### With version `0.4.0`, you can specify regex matches for the content types that you want the `parsers` and `handlers` to match. NOTE: you need to explicitly pass a `Regexp` for it to regex match. ```ruby parser = proc { |data| JSON.parse data } handler = proc { |e, type| [400, {}, 'boop'] } use Rack::Parser, :parsers => { %r{json} => parser }, :handlers => { %r{heyyyy} => handler } ``` ## Inspirations ## This project came to being because of: * [Niko Dittmann's](https://www.github.com/niko) [rack-post-body-to-params](https://www.github.com/niko/rack-post-body-to-params) which some of its ideas are instilled in this middleware. * Rack::PostBodyContentTypeParser from rack-contrib which proved to be an inspiration for both libraries. ## External Sources/Documentations * [Sinatra recipes](https://github.com/sinatra/sinatra-recipes/blob/master/middleware/rack_parser.md) - mini tutorial on using rack-parser (thanks to [Eric Gjertsen](https://github.com/ericgj)) ## Contributors ## * [Stephen Becker IV](https://github.com/sbeckeriv) - For initial custom error response handling work. * [Tom May](https://github.com/tommay) - skip loading post body unless content type is set. * [Moonsik Kang](https://github.com/deepblue) - skip rack parser for content types that are not explicitly set. * [Guillermo Iguaran](https://github.com/guilleiguaran) - Updating `multi_xml` version dependency for XML/YAML exploit * [Doug Orleans](https://github.com/dougo) - Handle only post-body parsing errors and let upstream errors propogate downstream * [Akshay Moghe](https://github.com/amoghe) - Make default error handler rack compliant by responding to #each and use StandardError ## Copyright Copyright © 2011,2012,2013 Arthur Chiu. See [MIT-LICENSE](https://github.com/achiu/rack-parser/blob/master/MIT-LICENSE) for details. ruby-rack-parser-0.7.0/Rakefile000066400000000000000000000003151400136267100163530ustar00rootroot00000000000000require 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new(:spec) do |test| test.libs << 'lib' << 'spec' test.pattern = 'spec/**/*_spec.rb' test.warning = true test.verbose = true end ruby-rack-parser-0.7.0/lib/000077500000000000000000000000001400136267100154555ustar00rootroot00000000000000ruby-rack-parser-0.7.0/lib/rack/000077500000000000000000000000001400136267100163755ustar00rootroot00000000000000ruby-rack-parser-0.7.0/lib/rack/parser.rb000066400000000000000000000040201400136267100202120ustar00rootroot00000000000000module Rack class Parser POST_BODY = 'rack.input'.freeze FORM_INPUT = 'rack.request.form_input'.freeze FORM_HASH = 'rack.request.form_hash'.freeze PARSER_RESULT = 'rack.parser.result'.freeze JSON_PARSER = proc { |data| JSON.parse data } ERROR_HANDLER = proc { |err, type| [400, {}, ['']] } attr_reader :parsers, :handlers, :logger def initialize(app, options = {}) @app = app @parsers = options[:parsers] || { %r{json} => JSON_PARSER } @handlers = options[:handlers] || {} @logger = options[:logger] end def call(env) type = Rack::Request.new(env).media_type parser = match_content_types_for(parsers, type) if type return @app.call(env) unless parser body = env[POST_BODY].read ; env[POST_BODY].rewind return @app.call(env) unless body && !body.empty? begin parsed = parser.last.call body env[PARSER_RESULT] = parsed env.update FORM_HASH => parsed, FORM_INPUT => env[POST_BODY] if parsed.is_a?(Hash) rescue StandardError => e warn! e, type handler = match_content_types_for handlers, type handler ||= ['default', ERROR_HANDLER] return handler.last.call(e, type) end @app.call env end # Private: send a warning out to the logger # # error - Exception object # type - String of the Content-Type # def warn!(error, type) return unless logger message = "[Rack::Parser] Error on %s : %s" % [type, error.to_s] logger.warn message end # Private: matches content types for the given media type # # content_types - An array of the parsers or handlers options # type - The media type. gathered from the Rack::Request # # Returns The match from the parser/handler hash or nil def match_content_types_for(content_types, type) content_types.detect do |content_type, _| content_type.is_a?(Regexp) ? type.match(content_type) : type == content_type end end end end ruby-rack-parser-0.7.0/rack-parser.gemspec000066400000000000000000000015341400136267100204710ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) Gem::Specification.new do |s| s.name = "rack-parser" s.version = "0.7.0" s.authors = ["Arthur Chiu"] s.email = ["mr.arthur.chiu@gmail.com"] s.homepage = "https://www.github.com/achiu/rack-parser" s.summary = %q{Rack Middleware for parsing post body data} s.description = %q{Rack Middleware for parsing post body data for json, xml and various content types} s.rubyforge_project = "rack-parser" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_dependency 'rack' s.add_development_dependency 'minitest' s.add_development_dependency 'rack-test' end ruby-rack-parser-0.7.0/spec/000077500000000000000000000000001400136267100156415ustar00rootroot00000000000000ruby-rack-parser-0.7.0/spec/parser_spec.rb000066400000000000000000000100541400136267100204740ustar00rootroot00000000000000require File.expand_path('../spec_helper', __FILE__) describe Rack::Parser do it "allows you to setup parsers for content types" do middleware = Rack::Parser.new ParserApp, :parsers => { 'foo' => 'bar' } assert_equal 'bar', middleware.parsers['foo'] end it "should not remove fields from options in setup" do options = {:parsers => { 'foo' => 'bar' }} middleware = Rack::Parser.new ParserApp, options refute_nil options[:parsers] end it "allows you to setup error handlers" do stack = Rack::Parser.new ParserApp, :handlers => { 'foo' => 'bar' } assert_equal 'bar', stack.handlers['foo'] end it "parses a Content-Type" do payload = JSON.dump(:a => 1) parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { 'application/json' => parser } post '/post', payload, { 'CONTENT_TYPE' => 'application/json' } assert last_response.ok? assert_equal "{\"a\"=>1}", last_response.body end it "does nothing if unmatched Content-Type" do payload = JSON.dump(:a => 1) parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { 'application/json' => parser } post '/post', payload, { 'CONTENT_TYPE' => 'application/xml' } assert last_response.ok? assert_equal "{}", last_response.body # request.params won't pick up this content type end it "matches Content-Type by regex" do payload = JSON.dump(:a => 2) parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { %r{json} => parser } post '/post', payload, { 'CONTENT_TYPE' => 'application/vnd.foo+json' } assert last_response.ok? assert_equal "{\"a\"=>2}", last_response.body end it 'matches ambiguous string Content-Type and forces explicit regex' do payload = JSON.dump(:a => 2) parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { 'application/vnd.foo+json' => parser } post '/post', payload, { 'CONTENT_TYPE' => 'application/vnd.foo+json' } assert last_response.ok? assert_equal "{\"a\"=>2}", last_response.body end it "handles upstream errors" do assert_raises StandardError, 'error!' do parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { %r{json} => parser } post '/error', '{}', { 'CONTENT_TYPE' => 'application/json' } end end it "returns a default error" do parser = proc { |data| raise StandardError, 'wah wah' } stack Rack::Parser, :parsers => { %r{json} => parser } post '/post', '{}', { 'CONTENT_TYPE' => 'application/vnd.foo+json' } assert_equal 400, last_response.status end it "returns a custom error message" do parser = proc { |data| raise StandardError, "wah wah" } handler = proc { |err, type| [500, {}, "%s : %s" % [type, err]] } stack Rack::Parser, :parsers => { %r{json} => parser }, :handlers => { %r{json} => handler } post '/post', '{}', { 'CONTENT_TYPE' => 'application/vnd.foo+json' } assert_equal 500, last_response.status assert_equal 'application/vnd.foo+json : wah wah', last_response.body end it 'returns a custome error for ambiguous string Content-Type and forces explicit regex' do parser = proc { |data| raise StandardError, "wah wah" } handler = proc { |err, type| [500, {}, "%s : %s" % [type, err]] } stack Rack::Parser, :parsers => { %r{json} => parser }, :handlers => { 'application/vnd.foo+json' => handler } post '/post', '{}', { 'CONTENT_TYPE' => 'application/vnd.foo+json' } assert_equal 500, last_response.status assert_equal 'application/vnd.foo+json : wah wah', last_response.body end it "parses an array but do not set it to params" do payload = JSON.dump([1,2,3]) parser = proc { |data| JSON.parse data } stack Rack::Parser, :parsers => { 'application/json' => parser } post '/post', payload, { 'CONTENT_TYPE' => 'application/json' } assert last_response.ok? assert_equal last_request.env['rack.parser.result'], [1, 2, 3] assert_equal last_request.env['rack.request.form_hash'], nil end end ruby-rack-parser-0.7.0/spec/spec_helper.rb000066400000000000000000000014671400136267100204670ustar00rootroot00000000000000gem 'minitest' require 'minitest/autorun' require 'rack' require 'rack/test' require 'rack/builder' require 'json' require File.expand_path('../../lib/rack/parser', __FILE__) class ParserApp def call(env) request = Rack::Request.new(env) type = { 'Content-Type' => 'text/plain' } code, body = case request.path when '/' then [200, 'Hello World'] when '/post' then [200, request.params.inspect] when '/error' then raise(StandardError, 'error!') else [404, 'Nothing'] end [code, type, body] end end class Minitest::Spec include Rack::Test::Methods def app(*middleware) @builder = Rack::Builder.new @builder.use(*@stack) @builder.run ParserApp.new @builder.to_app end def stack(*middleware) @stack = middleware end end