5 require 'active_support/memoizable'
6 require 'action_controller/cgi_ext'
8 module ActionController
9 class Request
< Rack
::Request
11 %w
[ AUTH_TYPE GATEWAY_INTERFACE
12 PATH_TRANSLATED REMOTE_HOST
13 REMOTE_IDENT REMOTE_USER REMOTE_ADDR
14 SERVER_NAME SERVER_PROTOCOL
16 HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
17 HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
18 HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT
].each
do |env|
19 define_method(env.sub(/^HTTP_/n
, '').downcase
) do
28 HTTP_METHODS
= %w(get head put post delete options
)
29 HTTP_METHOD_LOOKUP
= HTTP_METHODS
.inject({}) { |h
, m
| h
[m
] = h
[m
.upcase
] = m
.to_sym
; h
}
31 # Returns the true HTTP request \method as a lowercase symbol, such as
32 # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
33 # constant above, an UnknownHttpMethod exception is raised.
35 @request_method ||= HTTP_METHOD_LOOKUP
[super] || raise(UnknownHttpMethod
, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
38 # Returns the HTTP request \method used for action processing as a
39 # lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
40 # method returns <tt>:get</tt> for a HEAD request because the two are
41 # functionally equivalent from the application's perspective.)
43 request_method
== :head ? :get : request_method
46 # Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
51 # Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
53 request_method
== :post
56 # Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
58 request_method
== :put
61 # Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
63 request_method
== :delete
66 # Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
67 # this \method checks the actual HTTP \method directly.
69 request_method
== :head
72 # Provides access to the request's HTTP headers, for example:
74 # request.headers["Content-Type"] # => "text/plain"
76 @headers ||= ActionController
::Http::Headers.new(@env)
79 # Returns the content length of the request as an integer.
84 # The MIME type of the HTTP request, such as Mime::XML.
86 # For backward compatibility, the post \format is extracted from the
87 # X-Post-Data-Format HTTP header if present.
89 @content_type ||= begin
90 if @env['CONTENT_TYPE'] =~
/^([^,\;]*)/
91 Mime
::Type.lookup($1.strip
.downcase
)
98 # Returns the accepted MIME type for the request.
101 header
= @env['HTTP_ACCEPT'].to_s
.strip
104 [content_type
, Mime
::ALL].compact
106 Mime
::Type.parse(header
)
111 def if_modified_since
112 if since
= env['HTTP_IF_MODIFIED_SINCE']
113 Time
.rfc2822(since
) rescue nil
118 env['HTTP_IF_NONE_MATCH']
121 def not_modified
?(modified_at
)
122 if_modified_since
&& modified_at
&& if_modified_since
>= modified_at
125 def etag_matches
?(etag
)
126 if_none_match
&& if_none_match
== etag
129 # Check response freshness (Last-Modified and ETag) against request
130 # If-Modified-Since and If-None-Match conditions. If both headers are
131 # supplied, both must match, or the request is not considered fresh.
134 when if_modified_since
&& if_none_match
135 not_modified
?(response
.last_modified
) && etag_matches
?(response
.etag
)
136 when if_modified_since
137 not_modified
?(response
.last_modified
)
139 etag_matches
?(response
.etag
)
145 # Returns the Mime type for the \format used in the request.
147 # GET /posts/5.xml | request.format => Mime::XML
148 # GET /posts/5.xhtml | request.format => Mime::HTML
149 # 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>
152 if parameters
[:format]
153 Mime
::Type.lookup_by_extension(parameters
[:format])
154 elsif ActionController
::Base.use_accept_header
157 Mime
::Type.lookup_by_extension("js")
159 Mime
::Type.lookup_by_extension("html")
164 # Sets the \format by string extension, which can be used to force custom formats
165 # that are not controlled by the extension.
167 # class ApplicationController < ActionController::Base
168 # before_filter :adjust_format_for_iphone
171 # def adjust_format_for_iphone
172 # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
175 def format
=(extension
)
176 parameters
[:format] = extension
.to_s
177 @format = Mime
::Type.lookup_by_extension(parameters
[:format])
180 # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
181 # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
184 parameter_format
= parameters
[:format]
199 # Returns true if the request's "X-Requested-With" header contains
200 # "XMLHttpRequest". (The Prototype Javascript library sends this header with
201 # every Ajax request.)
202 def xml_http_request
?
203 !(@env['HTTP_X_REQUESTED_WITH'] !~
/XMLHttpRequest/i
)
205 alias xhr
? :xml_http_request?
207 # Which IP addresses are "trusted proxies" that can be stripped from
208 # the right-hand-side of X-Forwarded-For
209 TRUSTED_PROXIES
= /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
211 # Determines originating IP address. REMOTE_ADDR is the standard
212 # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
213 # HTTP_X_FORWARDED_FOR are set by proxies so check for these if
214 # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
215 # delimited list in the case of multiple chained proxies; the last
216 # address which is not trusted is the originating IP.
218 remote_addr_list
= @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
220 unless remote_addr_list
.blank
?
221 not_trusted_addrs
= remote_addr_list
.reject
{|addr
| addr
=~ TRUSTED_PROXIES
}
222 return not_trusted_addrs
.first
unless not_trusted_addrs
.empty
?
224 remote_ips
= @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
226 if @env.include? 'HTTP_CLIENT_IP'
227 if ActionController
::Base.ip_spoofing_check
&& remote_ips
&& !remote_ips
.include?(@env['HTTP_CLIENT_IP'])
228 # We don't know which came from the proxy, and which from the user
229 raise ActionControllerError
.new(<<EOM)
231 HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
232 HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
236 return @env['HTTP_CLIENT_IP']
240 while remote_ips
.size
> 1 && TRUSTED_PROXIES
=~ remote_ips
.last
.strip
244 return remote_ips
.last
.strip
250 # Returns the lowercase name of the HTTP server software.
252 (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~
@env['SERVER_SOFTWARE']) ? $1.downcase
: nil
255 # Returns the complete URL used for this request.
257 protocol
+ host_with_port
+ request_uri
260 # Returns 'https://' if this is an SSL request and 'http://' otherwise.
262 ssl
? ? 'https://' : 'http://'
265 # Is this an SSL request?
267 @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
270 # Returns the \host for this request, such as "example.com".
271 def raw_host_with_port
272 if forwarded
= env["HTTP_X_FORWARDED_HOST"]
273 forwarded
.split(/,\s?/).last
275 env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
279 # Returns the host for this request, such as example.com.
281 raw_host_with_port
.sub(/:\d+$/, '')
284 # Returns a \host:\port string for this request, such as "example.com" or
285 # "example.com:8080".
287 "#{host}#{port_string}"
290 # Returns the port number of this request as an integer.
292 if raw_host_with_port
=~
/:(\d+)$/
299 # Returns the standard \port number for this request's protocol.
302 when 'https://' then 443
307 # Returns a \port suffix like ":8080" if the \port number of this request
308 # is not the default HTTP \port 80 or HTTPS \port 443.
310 port
== standard_port
? '' : ":#{port}"
313 # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
314 # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
315 def domain(tld_length
= 1)
316 return nil unless named_host
?(host
)
318 host
.split('.').last(1 + tld_length
).join('.')
321 # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
322 # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
323 # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
324 # in "www.rubyonrails.co.uk".
325 def subdomains(tld_length
= 1)
326 return [] unless named_host
?(host
)
327 parts
= host
.split('.')
328 parts
[0..-(tld_length
+2)]
331 # Returns the query string, accounting for server idiosyncrasies.
333 @env['QUERY_STRING'].present
? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
336 # Returns the request URI, accounting for server idiosyncrasies.
337 # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
339 if uri
= @env['REQUEST_URI']
340 # Remove domain, which webrick puts into the request_uri.
341 (%r
{^\w
+\
://[^
/]+(/.*|$
)$
} =~ uri
) ? $1 : uri
343 # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
344 uri
= @env['PATH_INFO'].to_s
346 if script_filename
= @env['SCRIPT_NAME'].to_s
.match(%r
{[^
/]+$
})
347 uri
= uri
.sub(/#{script_filename}\//, '')
350 env_qs
= @env['QUERY_STRING'].to_s
351 uri
+= "?#{env_qs}" unless env_qs
.empty
?
354 @env.delete('REQUEST_URI')
356 @env['REQUEST_URI'] = uri
361 # Returns the interpreted \path to requested resource after all the installation
362 # directory of this application was taken into account.
364 path
= request_uri
.to_s
[/\A[^\?]*/]
365 path
.sub
!(/\A#{ActionController::Base.relative_url_root}/, '')
369 # Read the request \body. This is useful for web services that need to
370 # work with raw requests directly.
372 unless @env.include? 'RAW_POST_DATA'
373 @env['RAW_POST_DATA'] = body
.read(@env['CONTENT_LENGTH'].to_i
)
374 body
.rewind
if body
.respond_to
?(:rewind)
376 @env['RAW_POST_DATA']
379 # Returns both GET and POST \parameters in a single hash.
381 @parameters ||= request_parameters
.merge(query_parameters
).update(path_parameters
).with_indifferent_access
383 alias_method
:params, :parameters
385 def path_parameters
=(parameters
) #:nodoc:
386 @env["rack.routing_args"] = parameters
387 @symbolized_path_parameters = @parameters = nil
390 # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
391 def symbolized_path_parameters
392 @symbolized_path_parameters ||= path_parameters
.symbolize_keys
395 # Returns a hash with the \parameters used to form the \path of the request.
396 # Returned hash keys are strings:
398 # {'action' => 'my_action', 'controller' => 'my_controller'}
400 # See <tt>symbolized_path_parameters</tt> for symbolized keys.
402 @env["rack.routing_args"] ||= {}
405 # The request body is an IO input stream. If the RAW_POST_DATA environment
406 # variable is already set, wrap it in a StringIO.
408 if raw_post
= @env['RAW_POST_DATA']
409 raw_post
.force_encoding(Encoding
::BINARY) if raw_post
.respond_to
?(:force_encoding)
410 StringIO
.new(raw_post
)
417 FORM_DATA_MEDIA_TYPES
.include?(content_type
.to_s
)
420 # Override Rack's GET method to support indifferent access
422 @env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
424 alias_method
:query_parameters, :GET
426 # Override Rack's POST method to support indifferent access
428 @env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
430 alias_method
:request_parameters, :POST
432 def body_stream
#:nodoc:
437 @env['rack.session'] ||= {}
440 def session
=(session
) #:nodoc:
441 @env['rack.session'] = session
445 @env['rack.session.options'].delete(:id)
446 @env['rack.session'] = {}
450 @env['rack.session.options'] ||= {}
453 def session_options
=(options
)
454 @env['rack.session.options'] = options
458 @env['SERVER_PORT'].to_i
462 def named_host
?(host
)
463 !(host
.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host
))
466 # Convert nested Hashs to HashWithIndifferentAccess and replace
467 # file upload hashs with UploadedFile objects
468 def normalize_parameters(value
)
471 if value
.has_key
?(:tempfile)
472 upload
= value
[:tempfile]
473 upload
.extend(UploadedFile
)
474 upload
.original_path
= value
[:filename]
475 upload
.content_type
= value
[:type]
479 value
.each
{ |k
, v
| h
[k
] = normalize_parameters(v
) }
480 h
.with_indifferent_access
483 value
.map
{ |e
| normalize_parameters(e
) }