1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/lib/action_dispatch/http/request.rb

399 lines
13 KiB
Ruby
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require 'stringio'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
require 'action_controller/metal/exceptions'
require 'rack/request'
require 'action_dispatch/http/cache'
require 'action_dispatch/http/mime_negotiation'
require 'action_dispatch/http/parameters'
require 'action_dispatch/http/filter_parameters'
require 'action_dispatch/http/upload'
require 'action_dispatch/http/url'
require 'active_support/core_ext/array/conversions'
module ActionDispatch
class Request
include Rack::Request::Helpers
include ActionDispatch::Http::Cache::Request
include ActionDispatch::Http::MimeNegotiation
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL
include Rack::Request::Env
autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils'
LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
ORIGINAL_SCRIPT_NAME
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
HTTP_X_FORWARDED_FOR HTTP_VERSION
HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
SERVER_ADDR
].freeze
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze
end # end
METHOD
end
def initialize(env)
super
@method = nil
@request_method = nil
@remote_ip = nil
@original_fullpath = nil
@fullpath = nil
@ip = nil
end
def check_path_parameters!
# If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
path_parameters.each do |key, value|
next unless value.respond_to?(:valid_encoding?)
unless value.valid_encoding?
raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}"
end
end
end
PASS_NOT_FOUND = Class.new { # :nodoc:
def self.action(_); self; end
def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end
}
def controller_class
check_path_parameters!
params = path_parameters
if params.key?(:controller)
controller_param = params[:controller].underscore
params[:action] ||= 'index'
const_name = "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
else
PASS_NOT_FOUND
end
end
def key?(key)
has_header? key
end
# List of HTTP request methods from the following RFCs:
# Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
# HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt)
# Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt)
# Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
# Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
# Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
# Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt)
# PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
RFC3648 = %w(ORDERPATCH)
RFC3744 = %w(ACL)
RFC5323 = %w(SEARCH)
RFC4791 = %w(MKCALENDAR)
RFC5789 = %w(PATCH)
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
HTTP_METHOD_LOOKUP = {}
# Populate the HTTP method lookup cache
HTTP_METHODS.each { |method|
HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
}
# Returns the HTTP \method that the application should see.
# In the case where the \method was overridden by a middleware
# (for instance, if a HEAD request was converted to a GET,
# or if a _method parameter was used to determine the \method
# the application should use), this \method returns the overridden
# value, not the original.
def request_method
@request_method ||= check_method(super)
end
def routes # :nodoc:
get_header("action_dispatch.routes".freeze)
end
def routes=(routes) # :nodoc:
set_header("action_dispatch.routes".freeze, routes)
end
def engine_script_name(_routes) # :nodoc:
get_header(_routes.env_key)
end
def engine_script_name=(name) # :nodoc:
set_header(routes.env_key, name.dup)
end
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = set_header("REQUEST_METHOD", request_method)
end
end
def controller_instance # :nodoc:
get_header('action_controller.instance'.freeze)
end
def controller_instance=(controller) # :nodoc:
set_header('action_controller.instance'.freeze, controller)
end
def http_auth_salt
get_header "action_dispatch.http_auth_salt"
end
def show_exceptions? # :nodoc:
# We're treating `nil` as "unset", and we want the default setting to be
# `true`. This logic should be extracted to `env_config` and calculated
# once.
!(get_header('action_dispatch.show_exceptions'.freeze) == false)
end
# Returns a symbol form of the #request_method
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
end
# Returns the original value of the environment's REQUEST_METHOD,
# even if it was overridden by middleware. See #request_method for
# more information.
def method
@method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD'))
end
# Returns a symbol form of the #method
def method_symbol
HTTP_METHOD_LOOKUP[method]
end
# Provides access to the request's HTTP headers, for example:
#
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= Http::Headers.new(self)
end
# Returns a +String+ with the last requested path including their params.
#
# # get '/foo'
# request.original_fullpath # => '/foo'
#
# # get '/foo?bar'
# request.original_fullpath # => '/foo?bar'
def original_fullpath
@original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
end
# Returns the +String+ full path including params of the last URL requested.
#
# # get "/articles"
# request.fullpath # => "/articles"
#
# # get "/articles?page=2"
# request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
# Returns the original request URL as a +String+.
#
# # get "/articles?page=2"
# request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
# The +String+ MIME type of the request.
#
# # get "/articles"
# request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type.to_s
end
# Returns the content length of the request as an integer.
def content_length
super.to_i
end
# Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
# (case-insensitive), which may need to be manually added depending on the
# choice of JavaScript libraries and frameworks.
def xml_http_request?
get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i
end
alias :xhr? :xml_http_request?
# Returns the IP address of client as a +String+.
def ip
@ip ||= super
end
# Returns the IP address of client as a +String+,
# usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
def remote_ip=(remote_ip)
set_header "action_dispatch.remote_ip".freeze, remote_ip
end
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
# Returns the unique request id, which is based on either the X-Request-Id header that can
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
# (which sets the action_dispatch.request_id environment variable).
#
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
# This relies on the rack variable set by the ActionDispatch::RequestId middleware.
def request_id
get_header ACTION_DISPATCH_REQUEST_ID
end
def request_id=(id) # :nodoc:
set_header ACTION_DISPATCH_REQUEST_ID, id
end
alias_method :uuid, :request_id
# Returns the lowercase name of the HTTP server software.
def server_software
(get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil
end
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
unless has_header? 'RAW_POST_DATA'
raw_post_body = body
set_header('RAW_POST_DATA', raw_post_body.read(content_length))
raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
get_header 'RAW_POST_DATA'
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
if raw_post = get_header('RAW_POST_DATA')
raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
body_stream
end
end
# Returns true if the request's content MIME type is
# +application/x-www-form-urlencoded+ or +multipart/form-data+.
def form_data?
FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
end
def body_stream #:nodoc:
get_header('rack.input')
end
# TODO This should be broken apart into AD::Request::Session and probably
# be included by the session middleware.
def reset_session
if session && session.respond_to?(:destroy)
session.destroy
else
self.session = {}
end
self.flash = nil
end
def session=(session) #:nodoc:
Session.set self, session
end
def session_options=(options)
Session::Options.set self, options
end
# Override Rack's GET method to support indifferent access
def GET
fetch_header("action_dispatch.request.query_parameters") do |k|
rack_query_params = super || {}
# Check for non UTF-8 parameter values, which would cause errors later
Request::Utils.check_param_encoding(rack_query_params)
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
end
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}", e)
end
alias :query_parameters :GET
# Override Rack's POST method to support indifferent access
def POST
fetch_header("action_dispatch.request.request_parameters") do
pr = parse_formatted_parameters(params_parsers) do |params|
super || {}
end
self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
rescue ParamsParser::ParseError # one of the parse strategies blew up
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}", e)
end
alias :request_parameters :POST
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
def authorization
get_header('HTTP_AUTHORIZATION') ||
get_header('X-HTTP_AUTHORIZATION') ||
get_header('X_HTTP_AUTHORIZATION') ||
get_header('REDIRECT_X_HTTP_AUTHORIZATION')
end
# True if the request came from localhost, 127.0.0.1, or ::1.
def local?
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
def request_parameters=(params)
raise if params.nil?
set_header("action_dispatch.request.request_parameters".freeze, params)
end
def logger
get_header("action_dispatch.logger".freeze)
end
def commit_flash
end
private
def check_method(name)
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
name
end
end
end