Skip to content

Commit

Permalink
Build query string and POST params parser on top of Rack::Request. Al…
Browse files Browse the repository at this point in the history
…so switch our multipart parser to use Racks. Moved XML, JSON, and YAML parsers into ActionController::ParamsParser middleware [#1661 state:resolved]
  • Loading branch information
josh committed Jan 18, 2009
1 parent aab760c commit ff0a267
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 333 deletions.
2 changes: 2 additions & 0 deletions actionpack/lib/action_controller.rb
Expand Up @@ -58,6 +58,7 @@ def self.load_all!
autoload :Layout, 'action_controller/layout'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :ParamsParser, 'action_controller/params_parser'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Request, 'action_controller/request'
Expand All @@ -74,6 +75,7 @@ def self.load_all!
autoload :TestCase, 'action_controller/test_case'
autoload :TestProcess, 'action_controller/test_process'
autoload :Translation, 'action_controller/translation'
autoload :UploadedFile, 'action_controller/uploaded_file'
autoload :UploadedStringIO, 'action_controller/uploaded_file'
autoload :UploadedTempfile, 'action_controller/uploaded_file'
autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser'
Expand Down
5 changes: 1 addition & 4 deletions actionpack/lib/action_controller/base.rb
Expand Up @@ -301,10 +301,7 @@ class Base
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
Mime::URL_ENCODED_FORM => :url_encoded_form,
Mime::XML => :xml_simple,
Mime::JSON => :json }
@@param_parsers = {}
cattr_accessor :param_parsers

# Controls the default charset for all renders.
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_controller/middlewares.rb
Expand Up @@ -19,4 +19,5 @@
end

use ActionController::RewindableInput
use ActionController::ParamsParser
use Rack::MethodOverride
71 changes: 71 additions & 0 deletions actionpack/lib/action_controller/params_parser.rb
@@ -0,0 +1,71 @@
module ActionController
class ParamsParser
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
ActionController::Base.param_parsers[Mime::JSON] = :json

def initialize(app)
@app = app
end

def call(env)
if params = parse_formatted_parameters(env)
env["action_controller.request.request_parameters"] = params
end

@app.call(env)
end

private
def parse_formatted_parameters(env)
request = Request.new(env)

return false if request.content_length.zero?

mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
strategy = ActionController::Base.param_parsers[mime_type]

return false unless strategy

case strategy
when Proc
strategy.call(request.raw_post)
when :xml_simple, :xml_node
body = request.raw_post
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
when :yaml
YAML.load(request.raw_post)
when :json
body = request.raw_post
if body.blank?
{}
else
data = ActiveSupport::JSON.decode(body)
data = {:_json => data} unless data.is_a?(Hash)
data.with_indifferent_access
end
else
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
raise
{ "body" => request.raw_post,
"content_type" => request.content_type,
"content_length" => request.content_length,
"exception" => "#{e.message} (#{e.class})",
"backtrace" => e.backtrace }
end

def content_type_from_legacy_post_data_format_header(env)
if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
case x_post_format.to_s.downcase
when 'yaml'
return Mime::YAML
when 'xml'
return Mime::XML
end
end

nil
end
end
end
1 change: 1 addition & 0 deletions actionpack/lib/action_controller/rack_ext.rb
@@ -1,2 +1,3 @@
require 'action_controller/rack_ext/lock'
require 'action_controller/rack_ext/multipart'
require 'action_controller/rack_ext/parse_query'
18 changes: 18 additions & 0 deletions actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -0,0 +1,18 @@
# Rack does not automatically cleanup Safari 2 AJAX POST body
# This has not yet been commited to Rack, please +1 this ticket:
# http://rack.lighthouseapp.com/projects/22435/tickets/19

module Rack
module Utils
alias_method :parse_query_without_ajax_body_cleanup, :parse_query
module_function :parse_query_without_ajax_body_cleanup

def parse_query(qs, d = '&;')
qs = qs.dup
qs.chop! if qs[-1] == 0
qs.gsub!(/&_=$/, '')
parse_query_without_ajax_body_cleanup(qs, d)
end
module_function :parse_query
end
end
34 changes: 24 additions & 10 deletions actionpack/lib/action_controller/request.rb
Expand Up @@ -9,11 +9,6 @@ module ActionController
class Request < Rack::Request
extend ActiveSupport::Memoizable

def initialize(env)
super
@parser = ActionController::RequestParser.new(env)
end

%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
Expand Down Expand Up @@ -92,7 +87,11 @@ def content_length
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
Mime::Type.lookup(@parser.content_type_without_parameters)
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
Mime::Type.lookup($1.strip.downcase)
else
nil
end
end
memoize :content_type

Expand Down Expand Up @@ -380,7 +379,11 @@ def path
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
@parser.raw_post
unless @env.include? 'RAW_POST_DATA'
@env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
body.rewind if body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
end

# Returns both GET and POST \parameters in a single hash.
Expand Down Expand Up @@ -409,19 +412,30 @@ def path_parameters
@env["rack.routing_args"] ||= {}
end

# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
@parser.body
if raw_post = @env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
@env['rack.input']
end
end

def form_data?
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end

# Override Rack's GET method to support nested query strings
def GET
@parser.query_parameters
@env["action_controller.request.query_parameters"] ||= UrlEncodedPairParser.parse_query_parameters(query_string)
end
alias_method :query_parameters, :GET

# Override Rack's POST method to support nested query strings
def POST
@parser.request_parameters
@env["action_controller.request.request_parameters"] ||= UrlEncodedPairParser.parse_hash_parameters(super)
end
alias_method :request_parameters, :POST

Expand Down

1 comment on commit ff0a267

@oggy
Copy link
Contributor

@oggy oggy commented on ff0a267 Jan 21, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Josh,

You forgot to remove the autoload line for ‘action_controller/request_parser’.

Please sign in to comment.