Class: Rack::PostBodyToParams

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/post-body-to-params.rb

Overview

A Rack middleware for parsing POST/PUT body data when Content-Type is application/json or application/xml.

Uses ActiveSupport::JSON.decode for json and ActiveSupports enhanced Hash #from_xml for xml. Be shure to have ActiveSupport required beforehand.

Configure parsers for ActiveSupport (you should do this perhaps anyway):

ActiveSupport::JSON.backend = 'Yajl'
ActiveSupport::XmlMini.backend = 'Nokogiri'

concerning Yajl: rails.lighthouseapp.com/projects/8994/tickets/4897-yajl-backend-discovery-fails-in-activesupportjson

Note that all parsing errors will be rescued and returned back to the client.

Most parts blantly stolen from github.com/rack/rack-contrib.

Defined Under Namespace

Classes: RCETEST, YamlNotSafe

Constant Summary collapse

CONTENT_TYPE =

Constants

'CONTENT_TYPE'.freeze
POST_BODY =
'rack.input'.freeze
FORM_INPUT =
'rack.request.form_input'.freeze
FORM_HASH =
'rack.request.form_hash'.freeze
APPLICATION_JSON =

Supported Content-Types

'application/json'.freeze
APPLICATION_XML =
'application/xml'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, config = {}) ⇒ PostBodyToParams

Override the parsers and the error responses as needed:

use Rack::PostBodyContentTypeParser,
    :content_types => ['application/xml'],
    :parsers => {
      'application/xml' => Proc.new{|a| my_own_xml_parser a },
      'application/foo' => Proc.new{|a| my_foo_parser a }
    }


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/rack/post-body-to-params.rb', line 57

def initialize(app, config={})
  @content_types = config.delete(:content_types) || [APPLICATION_JSON, APPLICATION_XML]

  @parsers = {
    APPLICATION_JSON => Proc.new{ |post_body| parse_as_json post_body },
    APPLICATION_XML =>  Proc.new{ |post_body| parse_as_xml  post_body }
  }
  @parsers.update(config[:parsers]) if config[:parsers]

  @error_responses = {
    APPLICATION_JSON => Proc.new{ |error| json_error_response error },
    APPLICATION_XML =>  Proc.new{ |error| xml_error_response  error }
  }
  @error_responses.update(config[:error_responses]) if config[:error_responses]

  # Check wether we're vulnerable via YAML:
  begin
    parsers[APPLICATION_XML].call %Q{<?xml version="1.0" encoding="UTF-8"?><bang type="yaml">--- !ruby/hash:Rack::PostBodyToParams::RCETEST\n  foo: bar</bang>}
    # We shouldn't get here, the safe thing is to throw an exception (which ActiveSupport 3.1.x+ and safe_yaml )
    raise YamlNotSafe, 'Please educate about the ActiveSupport YAML remote code execution vulnerability and take measures. Either install and require safe_yaml or upgrade ActiveSupport'
  rescue YamlNotSafe => yns
    raise yns
  rescue Exception => e
    # Do nothing, we expect this to happen when we have safe parsing
  end

  @app = app
end

Instance Attribute Details

#error_responsesObject (readonly)

Returns the value of attribute error_responses.



46
47
48
# File 'lib/rack/post-body-to-params.rb', line 46

def error_responses
  @error_responses
end

#parsersObject (readonly)

Returns the value of attribute parsers.



46
47
48
# File 'lib/rack/post-body-to-params.rb', line 46

def parsers
  @parsers
end

Instance Method Details

#call(env) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rack/post-body-to-params.rb', line 100

def call(env)
  content_type = env[CONTENT_TYPE] && env[CONTENT_TYPE].split(';').first

  if content_type && @content_types.include?(content_type)
    post_body = env[POST_BODY].read

    unless post_body.blank?
      begin
        new_form_hash = parsers[content_type].call post_body
      rescue StandardError => error
        logger.warn "#{self.class} #{content_type} parsing error: #{error.to_s}" if respond_to? :logger
        return error_responses[content_type].call error
      end
      env.update(FORM_HASH => new_form_hash, FORM_INPUT => env[POST_BODY])
    end

  end

  @app.call(env)
end

#json_error_response(error) ⇒ Object



93
94
95
# File 'lib/rack/post-body-to-params.rb', line 93

def json_error_response(error)
  [ 400, {'Content-Type' => APPLICATION_JSON}, [ {"json-syntax-error" => error.to_s}.to_json ] ]
end

#parse_as_json(json_data) ⇒ Object



89
90
91
# File 'lib/rack/post-body-to-params.rb', line 89

def parse_as_json(json_data)
  ActiveSupport::JSON.decode json_data
end

#parse_as_xml(xml_data) ⇒ Object



86
87
88
# File 'lib/rack/post-body-to-params.rb', line 86

def parse_as_xml(xml_data)
  Hash.from_xml xml_data
end

#xml_error_response(error) ⇒ Object



96
97
98
# File 'lib/rack/post-body-to-params.rb', line 96

def xml_error_response(error)
  [ 400, {'Content-Type' => APPLICATION_XML}, [ {"xml-syntax-error" => error.to_s}.to_xml(:root => :errors) ] ]
end