2007-12-21 08:48:59 +01:00
require 'tempfile'
require 'stringio'
require 'strscan'
2008-10-27 07:47:01 +01:00
require 'active_support/memoizable'
2009-02-04 21:26:08 +01:00
require 'action_controller/cgi_ext'
2007-12-21 08:48:59 +01:00
2008-10-27 07:47:01 +01:00
module ActionController
2009-02-04 21:26:08 +01:00
class Request < Rack :: Request
2008-10-27 07:47:01 +01:00
2009-02-04 21:26:08 +01:00
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ] . each do | env |
define_method ( env . sub ( / ^HTTP_ /n , '' ) . downcase ) do
@env [ env ]
end
end
def key? ( key )
@env . key? ( key )
2008-10-27 07:47:01 +01:00
end
HTTP_METHODS = %w( get head put post delete options )
HTTP_METHOD_LOOKUP = HTTP_METHODS . inject ( { } ) { | h , m | h [ m ] = h [ m . upcase ] = m . to_sym ; h }
2007-01-22 14:43:50 +01:00
2009-02-04 21:26:08 +01:00
# Returns the true HTTP request \method as a lowercase symbol, such as
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
# constant above, an UnknownHttpMethod exception is raised.
2007-12-21 08:48:59 +01:00
def request_method
2009-02-28 02:23:00 +01:00
@request_method || = HTTP_METHOD_LOOKUP [ super ] || raise ( UnknownHttpMethod , " #{ super } , accepted HTTP methods are #{ HTTP_METHODS . to_sentence ( :locale = > :en ) } " )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
# Returns the HTTP request \method used for action processing as a
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
# method returns <tt>:get</tt> for a HEAD request because the two are
# functionally equivalent from the application's perspective.)
2007-01-22 14:43:50 +01:00
def method
2007-12-21 08:48:59 +01:00
request_method == :head ? :get : request_method
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
# Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
2007-01-22 14:43:50 +01:00
def get?
method == :get
end
2008-05-18 06:22:34 +02:00
# Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
2007-01-22 14:43:50 +01:00
def post?
2007-12-21 08:48:59 +01:00
request_method == :post
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
# Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
2007-01-22 14:43:50 +01:00
def put?
2007-12-21 08:48:59 +01:00
request_method == :put
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
# Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
2007-01-22 14:43:50 +01:00
def delete?
2007-12-21 08:48:59 +01:00
request_method == :delete
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
# this \method checks the actual HTTP \method directly.
2007-01-22 14:43:50 +01:00
def head?
2007-12-21 08:48:59 +01:00
request_method == :head
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Provides access to the request's HTTP headers, for example:
#
# request.headers["Content-Type"] # => "text/plain"
2007-12-21 08:48:59 +01:00
def headers
2009-02-04 21:26:08 +01:00
@headers || = ActionController :: Http :: Headers . new ( @env )
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the content length of the request as an integer.
2007-12-21 08:48:59 +01:00
def content_length
2009-02-04 21:26:08 +01:00
super . to_i
2007-12-21 08:48:59 +01:00
end
# The MIME type of the HTTP request, such as Mime::XML.
2007-01-22 14:43:50 +01:00
#
2008-10-27 07:47:01 +01:00
# For backward compatibility, the post \format is extracted from the
2007-01-22 14:43:50 +01:00
# X-Post-Data-Format HTTP header if present.
def content_type
2009-02-04 21:26:08 +01:00
@content_type || = begin
if @env [ 'CONTENT_TYPE' ] =~ / ^([^, \ ;]*) /
Mime :: Type . lookup ( $1 . strip . downcase )
else
nil
end
end
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the accepted MIME type for the request.
2007-01-22 14:43:50 +01:00
def accepts
2009-02-04 21:26:08 +01:00
@accepts || = begin
header = @env [ 'HTTP_ACCEPT' ] . to_s . strip
2008-10-27 07:47:01 +01:00
2009-02-04 21:26:08 +01:00
if header . empty?
[ content_type , Mime :: ALL ] . compact
else
Mime :: Type . parse ( header )
end
2008-10-27 07:47:01 +01:00
end
end
def if_modified_since
if since = env [ 'HTTP_IF_MODIFIED_SINCE' ]
Time . rfc2822 ( since ) rescue nil
end
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
def if_none_match
env [ 'HTTP_IF_NONE_MATCH' ]
end
def not_modified? ( modified_at )
if_modified_since && modified_at && if_modified_since > = modified_at
end
def etag_matches? ( etag )
if_none_match && if_none_match == etag
end
# Check response freshness (Last-Modified and ETag) against request
# If-Modified-Since and If-None-Match conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh? ( response )
case
2009-02-04 21:26:08 +01:00
when if_modified_since && if_none_match
not_modified? ( response . last_modified ) && etag_matches? ( response . etag )
when if_modified_since
not_modified? ( response . last_modified )
when if_none_match
etag_matches? ( response . etag )
else
false
end
2008-10-27 07:47:01 +01:00
end
# Returns the Mime type for the \format used in the request.
2007-12-21 08:48:59 +01:00
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
2008-10-27 07:47:01 +01:00
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
2007-12-21 08:48:59 +01:00
def format
2008-10-27 07:47:01 +01:00
@format || =
if parameters [ :format ]
Mime :: Type . lookup_by_extension ( parameters [ :format ] )
elsif ActionController :: Base . use_accept_header
accepts . first
elsif xhr?
Mime :: Type . lookup_by_extension ( " js " )
else
Mime :: Type . lookup_by_extension ( " html " )
end
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
2007-12-21 08:48:59 +01:00
#
# class ApplicationController < ActionController::Base
# before_filter :adjust_format_for_iphone
2008-10-27 07:47:01 +01:00
#
2007-12-21 08:48:59 +01:00
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
# end
# end
def format = ( extension )
parameters [ :format ] = extension . to_s
2008-05-18 06:22:34 +02:00
@format = Mime :: Type . lookup_by_extension ( parameters [ :format ] )
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters [ :format ]
if parameter_format
parameter_format
elsif xhr?
:js
else
:html
end
end
def cache_format
parameters [ :format ]
end
2007-01-22 14:43:50 +01:00
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
def xml_http_request?
2007-12-21 08:48:59 +01:00
! ( @env [ 'HTTP_X_REQUESTED_WITH' ] !~ / XMLHttpRequest /i )
2007-01-22 14:43:50 +01:00
end
alias xhr? :xml_http_request?
2008-05-18 06:22:34 +02:00
# Which IP addresses are "trusted proxies" that can be stripped from
# the right-hand-side of X-Forwarded-For
TRUSTED_PROXIES = / ^127 \ .0 \ .0 \ .1$|^(10|172 \ .(1[6-9]|2[0-9]|30|31)|192 \ .168) \ . /i
2008-10-27 07:47:01 +01:00
# Determines originating IP address. REMOTE_ADDR is the standard
2007-01-22 14:43:50 +01:00
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
2008-05-18 06:22:34 +02:00
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
2007-01-22 14:43:50 +01:00
def remote_ip
2009-02-04 21:26:08 +01:00
remote_addr_list = @env [ 'REMOTE_ADDR' ] && @env [ 'REMOTE_ADDR' ] . scan ( / [^, \ s]+ / )
2008-09-07 07:54:05 +02:00
unless remote_addr_list . blank?
not_trusted_addrs = remote_addr_list . reject { | addr | addr =~ TRUSTED_PROXIES }
return not_trusted_addrs . first unless not_trusted_addrs . empty?
2008-05-18 06:22:34 +02:00
end
2008-09-07 07:54:05 +02:00
remote_ips = @env [ 'HTTP_X_FORWARDED_FOR' ] && @env [ 'HTTP_X_FORWARDED_FOR' ] . split ( ',' )
2008-05-18 06:22:34 +02:00
if @env . include? 'HTTP_CLIENT_IP'
2009-02-04 21:26:08 +01:00
if ActionController :: Base . ip_spoofing_check && remote_ips && ! remote_ips . include? ( @env [ 'HTTP_CLIENT_IP' ] )
2008-05-18 06:22:34 +02:00
# We don't know which came from the proxy, and which from the user
raise ActionControllerError . new ( <<EOM)
IP spoofing attack? !
HTTP_CLIENT_IP = #{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR = #{@env['HTTP_X_FORWARDED_FOR'].inspect}
EOM
end
2008-09-07 07:54:05 +02:00
2008-05-18 06:22:34 +02:00
return @env [ 'HTTP_CLIENT_IP' ]
end
2007-01-22 14:43:50 +01:00
2008-09-07 07:54:05 +02:00
if remote_ips
2008-05-18 06:22:34 +02:00
while remote_ips . size > 1 && TRUSTED_PROXIES =~ remote_ips . last . strip
remote_ips . pop
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
return remote_ips . last . strip
2007-01-22 14:43:50 +01:00
end
@env [ 'REMOTE_ADDR' ]
end
2007-12-21 08:48:59 +01:00
# Returns the lowercase name of the HTTP server software.
def server_software
( @env [ 'SERVER_SOFTWARE' ] && / ^([a-zA-Z]+) / =~ @env [ 'SERVER_SOFTWARE' ] ) ? $1 . downcase : nil
end
2008-10-27 07:47:01 +01:00
# Returns the complete URL used for this request.
2007-12-21 08:48:59 +01:00
def url
protocol + host_with_port + request_uri
end
2008-10-27 07:47:01 +01:00
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
2007-12-21 08:48:59 +01:00
def protocol
ssl? ? 'https://' : 'http://'
end
# Is this an SSL request?
def ssl?
@env [ 'HTTPS' ] == 'on' || @env [ 'HTTP_X_FORWARDED_PROTO' ] == 'https'
end
2008-10-27 07:47:01 +01:00
# Returns the \host for this request, such as "example.com".
def raw_host_with_port
if forwarded = env [ " HTTP_X_FORWARDED_HOST " ]
forwarded . split ( / , \ s? / ) . last
else
2009-02-04 21:26:08 +01:00
env [ 'HTTP_HOST' ] || " #{ env [ 'SERVER_NAME' ] || env [ 'SERVER_ADDR' ] } : #{ env [ 'SERVER_PORT' ] } "
2008-10-27 07:47:01 +01:00
end
end
2007-12-21 08:48:59 +01:00
# Returns the host for this request, such as example.com.
def host
2008-10-27 07:47:01 +01:00
raw_host_with_port . sub ( / : \ d+$ / , '' )
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
2007-12-21 08:48:59 +01:00
def host_with_port
2008-10-27 07:47:01 +01:00
" #{ host } #{ port_string } "
2007-12-21 08:48:59 +01:00
end
# Returns the port number of this request as an integer.
def port
2008-10-27 07:47:01 +01:00
if raw_host_with_port =~ / :( \ d+)$ /
$1 . to_i
else
standard_port
end
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the standard \port number for this request's protocol.
2007-12-21 08:48:59 +01:00
def standard_port
case protocol
when 'https://' then 443
else 80
end
end
2008-10-27 07:47:01 +01:00
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
2007-12-21 08:48:59 +01:00
def port_string
2008-10-27 07:47:01 +01:00
port == standard_port ? '' : " : #{ port } "
2007-12-21 08:48:59 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
2007-01-22 14:43:50 +01:00
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain ( tld_length = 1 )
2007-12-21 08:48:59 +01:00
return nil unless named_host? ( host )
2007-01-22 14:43:50 +01:00
host . split ( '.' ) . last ( 1 + tld_length ) . join ( '.' )
end
2008-10-27 07:47:01 +01:00
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
2007-01-22 14:43:50 +01:00
# in "www.rubyonrails.co.uk".
def subdomains ( tld_length = 1 )
2007-12-21 08:48:59 +01:00
return [ ] unless named_host? ( host )
2007-01-22 14:43:50 +01:00
parts = host . split ( '.' )
parts [ 0 .. - ( tld_length + 2 ) ]
end
2008-10-27 07:47:01 +01:00
# Returns the query string, accounting for server idiosyncrasies.
2007-12-21 08:48:59 +01:00
def query_string
2009-02-04 21:26:08 +01:00
@env [ 'QUERY_STRING' ] . present? ? @env [ 'QUERY_STRING' ] : ( @env [ 'REQUEST_URI' ] . split ( '?' , 2 ) [ 1 ] || '' )
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the request URI, accounting for server idiosyncrasies.
2007-02-09 09:04:31 +01:00
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
2007-01-22 14:43:50 +01:00
def request_uri
if uri = @env [ 'REQUEST_URI' ]
2007-02-09 09:04:31 +01:00
# Remove domain, which webrick puts into the request_uri.
( %r{ ^ \ w+ \ ://[^/]+(/.*|$)$ } =~ uri ) ? $1 : uri
else
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
2008-10-27 07:47:01 +01:00
uri = @env [ 'PATH_INFO' ] . to_s
if script_filename = @env [ 'SCRIPT_NAME' ] . to_s . match ( %r{ [^/]+$ } )
uri = uri . sub ( / #{ script_filename } \/ / , '' )
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
env_qs = @env [ 'QUERY_STRING' ] . to_s
uri += " ? #{ env_qs } " unless env_qs . empty?
if uri . blank?
2007-12-21 08:48:59 +01:00
@env . delete ( 'REQUEST_URI' )
else
@env [ 'REQUEST_URI' ] = uri
end
end
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
2007-01-22 14:43:50 +01:00
def path
2009-02-04 21:26:08 +01:00
path = request_uri . to_s [ / \ A[^ \ ?]* / ]
path . sub! ( / \ A #{ ActionController :: Base . relative_url_root } / , '' )
path
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Read the request \body. This is useful for web services that need to
2007-12-21 08:48:59 +01:00
# work with raw requests directly.
def raw_post
2009-02-04 21:26:08 +01:00
unless @env . include? 'RAW_POST_DATA'
@env [ 'RAW_POST_DATA' ] = body . read ( @env [ 'CONTENT_LENGTH' ] . to_i )
2007-12-21 08:48:59 +01:00
body . rewind if body . respond_to? ( :rewind )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
@env [ 'RAW_POST_DATA' ]
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# Returns both GET and POST \parameters in a single hash.
2007-12-21 08:48:59 +01:00
def parameters
@parameters || = request_parameters . merge ( query_parameters ) . update ( path_parameters ) . with_indifferent_access
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
alias_method :params , :parameters
2007-01-22 14:43:50 +01:00
def path_parameters = ( parameters ) #:nodoc:
2009-02-04 21:26:08 +01:00
@env [ " rack.routing_args " ] = parameters
2007-01-22 14:43:50 +01:00
@symbolized_path_parameters = @parameters = nil
end
2008-10-27 07:47:01 +01:00
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
2007-01-22 14:43:50 +01:00
@symbolized_path_parameters || = path_parameters . symbolize_keys
end
2008-10-27 07:47:01 +01:00
# Returns a hash with the \parameters used to form the \path of the request.
# Returned hash keys are strings:
2007-01-22 14:43:50 +01:00
#
2007-12-21 08:48:59 +01:00
# {'action' => 'my_action', 'controller' => 'my_controller'}
2008-10-27 07:47:01 +01:00
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
2007-01-22 14:43:50 +01:00
def path_parameters
2009-02-04 21:26:08 +01:00
@env [ " rack.routing_args " ] || = { }
2007-01-22 14:43:50 +01:00
end
2008-10-27 07:47:01 +01:00
# 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
2009-02-04 21:26:08 +01:00
if raw_post = @env [ 'RAW_POST_DATA' ]
2008-10-27 07:47:01 +01:00
raw_post . force_encoding ( Encoding :: BINARY ) if raw_post . respond_to? ( :force_encoding )
StringIO . new ( raw_post )
else
2009-02-04 21:26:08 +01:00
@env [ 'rack.input' ]
2008-10-27 07:47:01 +01:00
end
end
2009-02-04 21:26:08 +01:00
def form_data?
FORM_DATA_MEDIA_TYPES . include? ( content_type . to_s )
2008-10-27 07:47:01 +01:00
end
2009-02-28 02:23:00 +01:00
# Override Rack's GET method to support indifferent access
2009-02-04 21:26:08 +01:00
def GET
2009-02-28 02:23:00 +01:00
@env [ " action_controller.request.query_parameters " ] || = normalize_parameters ( super )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
alias_method :query_parameters , :GET
2007-01-22 14:43:50 +01:00
2009-02-28 02:23:00 +01:00
# Override Rack's POST method to support indifferent access
2009-02-04 21:26:08 +01:00
def POST
2009-02-28 02:23:00 +01:00
@env [ " action_controller.request.request_parameters " ] || = normalize_parameters ( super )
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
alias_method :request_parameters , :POST
2008-10-27 07:47:01 +01:00
def body_stream #:nodoc:
2009-02-04 21:26:08 +01:00
@env [ 'rack.input' ]
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
def session
@env [ 'rack.session' ] || = { }
2007-01-22 14:43:50 +01:00
end
def session = ( session ) #:nodoc:
2009-02-04 21:26:08 +01:00
@env [ 'rack.session' ] = session
2007-01-22 14:43:50 +01:00
end
2009-02-04 21:26:08 +01:00
def reset_session
2009-03-16 15:55:30 +01:00
@env [ 'rack.session.options' ] . delete ( :id )
2009-02-04 21:26:08 +01:00
@env [ 'rack.session' ] = { }
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2009-02-04 21:26:08 +01:00
def session_options
@env [ 'rack.session.options' ] || = { }
2007-12-21 08:48:59 +01:00
end
2009-02-04 21:26:08 +01:00
def session_options = ( options )
@env [ 'rack.session.options' ] = options
2007-12-21 08:48:59 +01:00
end
2009-02-04 21:26:08 +01:00
def server_port
@env [ 'SERVER_PORT' ] . to_i
2007-12-21 08:48:59 +01:00
end
private
2009-02-04 21:26:08 +01:00
def named_host? ( host )
! ( host . nil? || / \ d{1,3} \ . \ d{1,3} \ . \ d{1,3} \ . \ d{1,3}$ / . match ( host ) )
2007-12-21 08:48:59 +01:00
end
2009-02-28 02:23:00 +01:00
# Convert nested Hashs to HashWithIndifferentAccess and replace
# file upload hashs with UploadedFile objects
def normalize_parameters ( value )
case value
when Hash
if value . has_key? ( :tempfile )
upload = value [ :tempfile ]
upload . extend ( UploadedFile )
upload . original_path = value [ :filename ]
upload . content_type = value [ :type ]
upload
else
h = { }
value . each { | k , v | h [ k ] = normalize_parameters ( v ) }
h . with_indifferent_access
end
when Array
value . map { | e | normalize_parameters ( e ) }
else
value
end
end
2007-01-22 14:43:50 +01:00
end
end