lib/rack/request.rb in rack-1.6.13 vs lib/rack/request.rb in rack-2.0.0.alpha
- old
+ new
@@ -1,6 +1,7 @@
require 'rack/utils'
+require 'rack/media_type'
module Rack
# Rack::Request provides a convenient interface to a Rack
# environment. It is stateless, the environment +env+ passed to the
# constructor will be directly modified.
@@ -8,375 +9,438 @@
# req = Rack::Request.new(env)
# req.post?
# req.params["data"]
class Request
- # The environment of the request.
- attr_reader :env
-
- SCHEME_WHITELIST = %w(https http).freeze
-
def initialize(env)
- @env = env
+ @params = nil
+ super(env)
end
- def body; @env["rack.input"] end
- def script_name; @env[SCRIPT_NAME].to_s end
- def path_info; @env[PATH_INFO].to_s end
- def request_method; @env["REQUEST_METHOD"] end
- def query_string; @env[QUERY_STRING].to_s end
- def content_length; @env['CONTENT_LENGTH'] end
-
- def content_type
- content_type = @env['CONTENT_TYPE']
- content_type.nil? || content_type.empty? ? nil : content_type
+ # shortcut for <tt>request.params[key]</tt>
+ def [](key)
+ params[key.to_s]
end
- def session; @env['rack.session'] ||= {} end
- def session_options; @env['rack.session.options'] ||= {} end
- def logger; @env['rack.logger'] end
-
- # The media type (type/subtype) portion of the CONTENT_TYPE header
- # without any media type parameters. e.g., when CONTENT_TYPE is
- # "text/plain;charset=utf-8", the media-type is "text/plain".
+ # shortcut for <tt>request.params[key] = value</tt>
#
- # For more information on the use of media types in HTTP, see:
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
- def media_type
- content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
+ def []=(key, value)
+ params[key.to_s] = value
end
- # The media type parameters provided in CONTENT_TYPE as a Hash, or
- # an empty Hash if no CONTENT_TYPE or media-type parameters were
- # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
- # this method responds with the following Hash:
- # { 'charset' => 'utf-8' }
- def media_type_params
- return {} if content_type.nil?
- Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
- collect { |s| s.split('=', 2) }.
- map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
+ # like Hash#values_at
+ def values_at(*keys)
+ keys.map{|key| params[key] }
end
- # The character set of the request body if a "charset" media type
- # parameter was given, or nil if no "charset" was specified. Note
- # that, per RFC2616, text/* media types that specify no explicit
- # charset are to be considered ISO-8859-1.
- def content_charset
- media_type_params['charset']
+ def params
+ @params ||= super
end
- def scheme
- if @env['HTTPS'] == 'on'
- 'https'
- elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
- 'https'
- elsif forwarded_scheme
- forwarded_scheme
- else
- @env["rack.url_scheme"]
- end
+ def update_param(k, v)
+ super
+ @params = nil
end
- def ssl?
- scheme == 'https'
+ def delete_param(k)
+ v = super
+ @params = nil
+ v
end
- def host_with_port
- if forwarded = @env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- else
- @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
+ module Env
+ # The environment of the request.
+ attr_reader :env
+
+ def initialize(env)
+ @env = env
+ super()
end
- end
- def port
- if port = host_with_port.split(/:/)[1]
- port.to_i
- elsif port = @env['HTTP_X_FORWARDED_PORT']
- port.to_i
- elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
- DEFAULT_PORTS[scheme]
- elsif @env.has_key?("HTTP_X_FORWARDED_PROTO")
- DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]]
- else
- @env["SERVER_PORT"].to_i
+ # Predicate method to test to see if `name` has been set as request
+ # specific data
+ def has_header?(name)
+ @env.key? name
end
- end
- def host
- # Remove port number.
- host_with_port.to_s.sub(/:\d+\z/, '')
+ # Get a request specific value for `name`.
+ def get_header(name)
+ @env[name]
+ end
+
+ # If a block is given, it yields to the block if the value hasn't been set
+ # on the request.
+ def fetch_header(name, &block)
+ @env.fetch(name, &block)
+ end
+
+ # Loops through each key / value pair in the request specific data.
+ def each_header(&block)
+ @env.each(&block)
+ end
+
+ # Set a request specific value for `name` to `v`
+ def set_header(name, v)
+ @env[name] = v
+ end
+
+ # Add a header that may have multiple values.
+ #
+ # Example:
+ # request.add_header 'Accept', 'image/png'
+ # request.add_header 'Accept', '*/*'
+ #
+ # assert_equal 'image/png,*/*', request.get_header('Accept')
+ #
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+ def add_header key, v
+ if v.nil?
+ get_header key
+ elsif has_header? key
+ set_header key, "#{get_header key},#{v}"
+ else
+ set_header key, v
+ end
+ end
+
+ # Delete a request specific value for `name`.
+ def delete_header(name)
+ @env.delete name
+ end
+
+ def initialize_copy(other)
+ @env = other.env.dup
+ end
end
- def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
- def path_info=(s); @env["PATH_INFO"] = s.to_s end
+ module Helpers
+ # The set of form-data media-types. Requests that do not indicate
+ # one of the media types presents in this list will not be eligible
+ # for form-data / param parsing.
+ FORM_DATA_MEDIA_TYPES = [
+ 'application/x-www-form-urlencoded',
+ 'multipart/form-data'
+ ]
+ # The set of media-types. Requests that do not indicate
+ # one of the media types presents in this list will not be eligible
+ # for param parsing like soap attachments or generic multiparts
+ PARSEABLE_DATA_MEDIA_TYPES = [
+ 'multipart/related',
+ 'multipart/mixed'
+ ]
- # Checks the HTTP request method (or verb) to see if it was of type DELETE
- def delete?; request_method == "DELETE" end
+ # Default ports depending on scheme. Used to decide whether or not
+ # to include the port in a generated URI.
+ DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
- # Checks the HTTP request method (or verb) to see if it was of type GET
- def get?; request_method == GET end
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
- # Checks the HTTP request method (or verb) to see if it was of type HEAD
- def head?; request_method == HEAD end
+ def body; get_header(RACK_INPUT) end
+ def script_name; get_header(SCRIPT_NAME).to_s end
+ def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
- # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
- def options?; request_method == "OPTIONS" end
+ def path_info; get_header(PATH_INFO).to_s end
+ def path_info=(s); set_header(PATH_INFO, s.to_s) end
- # Checks the HTTP request method (or verb) to see if it was of type LINK
- def link?; request_method == "LINK" end
+ def request_method; get_header(REQUEST_METHOD) end
+ def query_string; get_header(QUERY_STRING).to_s end
+ def content_length; get_header('CONTENT_LENGTH') end
+ def logger; get_header(RACK_LOGGER) end
+ def user_agent; get_header('HTTP_USER_AGENT') end
+ def multithread?; get_header(RACK_MULTITHREAD) end
- # Checks the HTTP request method (or verb) to see if it was of type PATCH
- def patch?; request_method == "PATCH" end
+ # the referer of the client
+ def referer; get_header('HTTP_REFERER') end
+ alias referrer referer
- # Checks the HTTP request method (or verb) to see if it was of type POST
- def post?; request_method == "POST" end
+ def session
+ fetch_header(RACK_SESSION) do |k|
+ set_header RACK_SESSION, {}
+ end
+ end
- # Checks the HTTP request method (or verb) to see if it was of type PUT
- def put?; request_method == "PUT" end
+ def session_options
+ fetch_header(RACK_SESSION_OPTIONS) do |k|
+ set_header RACK_SESSION_OPTIONS, {}
+ end
+ end
- # Checks the HTTP request method (or verb) to see if it was of type TRACE
- def trace?; request_method == "TRACE" end
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
+ def delete?; request_method == DELETE end
- # Checks the HTTP request method (or verb) to see if it was of type UNLINK
- def unlink?; request_method == "UNLINK" end
+ # Checks the HTTP request method (or verb) to see if it was of type GET
+ def get?; request_method == GET end
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
+ def head?; request_method == HEAD end
- # The set of form-data media-types. Requests that do not indicate
- # one of the media types presents in this list will not be eligible
- # for form-data / param parsing.
- FORM_DATA_MEDIA_TYPES = [
- 'application/x-www-form-urlencoded',
- 'multipart/form-data'
- ]
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
+ def options?; request_method == OPTIONS end
- # The set of media-types. Requests that do not indicate
- # one of the media types presents in this list will not be eligible
- # for param parsing like soap attachments or generic multiparts
- PARSEABLE_DATA_MEDIA_TYPES = [
- 'multipart/related',
- 'multipart/mixed'
- ]
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
+ def link?; request_method == LINK end
- # Default ports depending on scheme. Used to decide whether or not
- # to include the port in a generated URI.
- DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
+ def patch?; request_method == PATCH end
- # Determine whether the request body contains form-data by checking
- # the request Content-Type for one of the media-types:
- # "application/x-www-form-urlencoded" or "multipart/form-data". The
- # list of form-data media types can be modified through the
- # +FORM_DATA_MEDIA_TYPES+ array.
- #
- # A request body is also assumed to contain form-data when no
- # Content-Type header is provided and the request_method is POST.
- def form_data?
- type = media_type
- meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
- (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
- end
+ # Checks the HTTP request method (or verb) to see if it was of type POST
+ def post?; request_method == POST end
- # Determine whether the request body contains data by checking
- # the request media_type against registered parse-data media-types
- def parseable_data?
- PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
- end
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
+ def put?; request_method == PUT end
- # Returns the data received in the query string.
- def GET
- if @env["rack.request.query_string"] == query_string
- @env["rack.request.query_hash"]
- else
- p = parse_query({ :query => query_string, :separator => '&;' })
- @env["rack.request.query_string"] = query_string
- @env["rack.request.query_hash"] = p
- end
- end
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
+ def trace?; request_method == TRACE end
- # Returns the data received in the request body.
- #
- # This method support both application/x-www-form-urlencoded and
- # multipart/form-data.
- def POST
- if @env["rack.input"].nil?
- raise "Missing rack.input"
- elsif @env["rack.request.form_input"].equal? @env["rack.input"]
- @env["rack.request.form_hash"]
- elsif form_data? || parseable_data?
- unless @env["rack.request.form_hash"] = parse_multipart(env)
- form_vars = @env["rack.input"].read
+ # Checks the HTTP request method (or verb) to see if it was of type UNLINK
+ def unlink?; request_method == UNLINK end
- # Fix for Safari Ajax postings that always append \0
- # form_vars.sub!(/\0\z/, '') # performance replacement:
- form_vars.slice!(-1) if form_vars[-1] == ?\0
+ def scheme
+ if get_header(HTTPS) == 'on'
+ 'https'
+ elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
+ 'https'
+ elsif get_header(HTTP_X_FORWARDED_SCHEME)
+ get_header(HTTP_X_FORWARDED_SCHEME)
+ elsif get_header(HTTP_X_FORWARDED_PROTO)
+ get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]
+ else
+ get_header(RACK_URL_SCHEME)
+ end
+ end
- @env["rack.request.form_vars"] = form_vars
- @env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' })
+ def authority
+ get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
+ end
- @env["rack.input"].rewind
+ def cookies
+ hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
+ set_header(k, {})
end
- @env["rack.request.form_input"] = @env["rack.input"]
- @env["rack.request.form_hash"]
- else
- {}
+ string = get_header HTTP_COOKIE
+
+ return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
+ hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
+ set_header(RACK_REQUEST_COOKIE_STRING, string)
+ hash
end
- end
- # The union of GET and POST data.
- #
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
- def params
- @params ||= self.GET.merge(self.POST)
- rescue EOFError
- self.GET.dup
- end
+ def content_type
+ content_type = get_header('CONTENT_TYPE')
+ content_type.nil? || content_type.empty? ? nil : content_type
+ end
- # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
- #
- # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
- #
- # env['rack.input'] is not touched.
- def update_param(k, v)
- found = false
- if self.GET.has_key?(k)
- found = true
- self.GET[k] = v
+ def xhr?
+ get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
end
- if self.POST.has_key?(k)
- found = true
- self.POST[k] = v
+
+ def host_with_port
+ if forwarded = get_header(HTTP_X_FORWARDED_HOST)
+ forwarded.split(/,\s?/).last
+ else
+ get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
+ end
end
- unless found
- self.GET[k] = v
+
+ def host
+ # Remove port number.
+ host_with_port.to_s.sub(/:\d+\z/, '')
end
- @params = nil
- nil
- end
- # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
- #
- # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
- #
- # env['rack.input'] is not touched.
- def delete_param(k)
- v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
- @params = nil
- v
- end
+ def port
+ if port = host_with_port.split(/:/)[1]
+ port.to_i
+ elsif port = get_header(HTTP_X_FORWARDED_PORT)
+ port.to_i
+ elsif has_header?(HTTP_X_FORWARDED_HOST)
+ DEFAULT_PORTS[scheme]
+ elsif has_header?(HTTP_X_FORWARDED_PROTO)
+ DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
+ else
+ get_header(SERVER_PORT).to_i
+ end
+ end
- # shortcut for request.params[key]
- def [](key)
- params[key.to_s]
- end
+ def ssl?
+ scheme == 'https'
+ end
- # shortcut for request.params[key] = value
- #
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
- def []=(key, value)
- params[key.to_s] = value
- end
+ def ip
+ remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
+ remote_addrs = reject_trusted_ip_addresses(remote_addrs)
- # like Hash#values_at
- def values_at(*keys)
- keys.map{|key| params[key] }
- end
+ return remote_addrs.first if remote_addrs.any?
- # the referer of the client
- def referer
- @env['HTTP_REFERER']
- end
- alias referrer referer
+ forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
- def user_agent
- @env['HTTP_USER_AGENT']
- end
+ return reject_trusted_ip_addresses(forwarded_ips).last || get_header("REMOTE_ADDR")
+ end
- def cookies
- hash = @env["rack.request.cookie_hash"] ||= {}
- string = @env["HTTP_COOKIE"]
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
+ # without any media type parameters. e.g., when CONTENT_TYPE is
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
+ #
+ # For more information on the use of media types in HTTP, see:
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
+ def media_type
+ MediaType.type(content_type)
+ end
- return hash if string == @env["rack.request.cookie_string"]
- hash.clear
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
+ # this method responds with the following Hash:
+ # { 'charset' => 'utf-8' }
+ def media_type_params
+ MediaType.params(content_type)
+ end
- # According to RFC 2109:
- # If multiple cookies satisfy the criteria above, they are ordered in
- # the Cookie header such that those with more specific Path attributes
- # precede those with less specific. Ordering with respect to other
- # attributes (e.g., Domain) is unspecified.
- cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
- cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
- @env["rack.request.cookie_string"] = string
- hash
- end
+ # The character set of the request body if a "charset" media type
+ # parameter was given, or nil if no "charset" was specified. Note
+ # that, per RFC2616, text/* media types that specify no explicit
+ # charset are to be considered ISO-8859-1.
+ def content_charset
+ media_type_params['charset']
+ end
- def xhr?
- @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
- end
+ # Determine whether the request body contains form-data by checking
+ # the request Content-Type for one of the media-types:
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
+ # list of form-data media types can be modified through the
+ # +FORM_DATA_MEDIA_TYPES+ array.
+ #
+ # A request body is also assumed to contain form-data when no
+ # Content-Type header is provided and the request_method is POST.
+ def form_data?
+ type = media_type
+ meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
+ (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
+ end
- def base_url
- url = "#{scheme}://#{host}"
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
- url
- end
+ # Determine whether the request body contains data by checking
+ # the request media_type against registered parse-data media-types
+ def parseable_data?
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
+ end
- # Tries to return a remake of the original request URL as a string.
- def url
- base_url + fullpath
- end
+ # Returns the data received in the query string.
+ def GET
+ if get_header(RACK_REQUEST_QUERY_STRING) == query_string
+ get_header(RACK_REQUEST_QUERY_HASH)
+ else
+ query_hash = parse_query(query_string, '&;')
+ set_header(RACK_REQUEST_QUERY_STRING, query_string)
+ set_header(RACK_REQUEST_QUERY_HASH, query_hash)
+ end
+ end
- def path
- script_name + path_info
- end
+ # Returns the data received in the request body.
+ #
+ # This method support both application/x-www-form-urlencoded and
+ # multipart/form-data.
+ def POST
+ if get_header(RACK_INPUT).nil?
+ raise "Missing rack.input"
+ elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
+ get_header(RACK_REQUEST_FORM_HASH)
+ elsif form_data? || parseable_data?
+ unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
+ form_vars = get_header(RACK_INPUT).read
- def fullpath
- query_string.empty? ? path : "#{path}?#{query_string}"
- end
+ # Fix for Safari Ajax postings that always append \0
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
+ form_vars.slice!(-1) if form_vars[-1] == ?\0
- def accept_encoding
- parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"])
- end
+ set_header RACK_REQUEST_FORM_VARS, form_vars
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
- def accept_language
- parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"])
- end
+ get_header(RACK_INPUT).rewind
+ end
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
+ get_header RACK_REQUEST_FORM_HASH
+ else
+ {}
+ end
+ end
- def trusted_proxy?(ip)
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
- end
+ # The union of GET and POST data.
+ #
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
+ def params
+ self.GET.merge(self.POST)
+ rescue EOFError
+ self.GET.dup
+ end
- def ip
- remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
- remote_addrs = reject_trusted_ip_addresses(remote_addrs)
+ # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
+ #
+ # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
+ #
+ # <tt>env['rack.input']</tt> is not touched.
+ def update_param(k, v)
+ found = false
+ if self.GET.has_key?(k)
+ found = true
+ self.GET[k] = v
+ end
+ if self.POST.has_key?(k)
+ found = true
+ self.POST[k] = v
+ end
+ unless found
+ self.GET[k] = v
+ end
+ end
- return remote_addrs.first if remote_addrs.any?
+ # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
+ #
+ # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
+ #
+ # <tt>env['rack.input']</tt> is not touched.
+ def delete_param(k)
+ [ self.POST.delete(k), self.GET.delete(k) ].compact.first
+ end
- forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
+ def base_url
+ url = "#{scheme}://#{host}"
+ url << ":#{port}" if port != DEFAULT_PORTS[scheme]
+ url
+ end
- return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
- end
+ # Tries to return a remake of the original request URL as a string.
+ def url
+ base_url + fullpath
+ end
- protected
- def split_ip_addresses(ip_addresses)
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
+ def path
+ script_name + path_info
end
- def reject_trusted_ip_addresses(ip_addresses)
- ip_addresses.reject { |ip| trusted_proxy?(ip) }
+ def fullpath
+ query_string.empty? ? path : "#{path}?#{query_string}"
end
- def parse_query(qs)
- d = '&'
- qs, d = qs[:query], qs[:separator] if Hash === qs
- Utils.parse_nested_query(qs, d)
+ def accept_encoding
+ parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
end
- def parse_multipart(env)
- Rack::Multipart.parse_multipart(env)
+ def accept_language
+ parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
end
+ def trusted_proxy?(ip)
+ ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
+ end
+
+ private
+
def parse_http_accept_header(header)
header.to_s.split(/\s*,\s*/).map do |part|
attribute, parameters = part.split(/\s*;\s*/, 2)
quality = 1.0
if parameters and /\Aq=([\d.]+)/ =~ parameters
@@ -384,28 +448,30 @@
end
[attribute, quality]
end
end
- private
- def strip_doublequotes(s)
- if s[0] == ?" && s[-1] == ?"
- s[1..-2]
- else
- s
+ def query_parser
+ Utils.default_query_parser
end
- end
- def forwarded_scheme
- scheme_headers = [
- @env['HTTP_X_FORWARDED_SCHEME'],
- @env['HTTP_X_FORWARDED_PROTO'].to_s.split(',')[0]
- ]
+ def parse_query(qs, d='&')
+ query_parser.parse_nested_query(qs, d)
+ end
- scheme_headers.each do |header|
- return header if SCHEME_WHITELIST.include?(header)
+ def parse_multipart
+ Rack::Multipart.extract_multipart(self, query_parser)
end
- nil
+ def split_ip_addresses(ip_addresses)
+ ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
+ end
+
+ def reject_trusted_ip_addresses(ip_addresses)
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
+ end
end
+
+ include Env
+ include Helpers
end
end