module Rack::Request::Helpers
def GET
def GET rr_query_string = get_header(RACK_REQUEST_QUERY_STRING) query_string = self.query_string if rr_query_string == query_string get_header(RACK_REQUEST_QUERY_HASH) else if rr_query_string warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1 end 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 POST
This method support both application/x-www-form-urlencoded and
Returns the data received in the request body.
def POST if error = get_header(RACK_REQUEST_FORM_ERROR) raise error.class, error.message, cause: error.cause end begin rack_input = get_header(RACK_INPUT) # If the form hash was already memoized: if form_hash = get_header(RACK_REQUEST_FORM_HASH) form_input = get_header(RACK_REQUEST_FORM_INPUT) # And it was memoized from the same input: if form_input.equal?(rack_input) return form_hash elsif form_input warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1 end end # Otherwise, figure out how to parse the input: if rack_input.nil? set_header RACK_REQUEST_FORM_INPUT, nil set_header(RACK_REQUEST_FORM_HASH, {}) elsif form_data? || parseable_data? if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList) set_header RACK_REQUEST_FORM_PAIRS, pairs set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs) else form_vars = get_header(RACK_INPUT).read # Fix for Safari Ajax postings that always append \0 # form_vars.sub!(/\0\z/, '') # performance replacement: form_vars.slice!(-1) if form_vars.end_with?("\0") set_header RACK_REQUEST_FORM_VARS, form_vars set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&') end set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) get_header RACK_REQUEST_FORM_HASH else set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) set_header(RACK_REQUEST_FORM_HASH, {}) end rescue => error set_header(RACK_REQUEST_FORM_ERROR, error) raise end end
def accept_encoding
def accept_encoding parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING")) end
def accept_language
def accept_language parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE")) end
def allowed_scheme(header)
def allowed_scheme(header) header if ALLOWED_SCHEMES.include?(header) end
def authority
In HTTP/1, this is the `host` header.
https://tools.ietf.org/html/rfc3986#section-3.2
The authority of the incoming request as defined by RFC3976.
def authority forwarded_authority || host_authority || server_authority end
def base_url
def base_url "#{scheme}://#{host_with_port}" end
def body; get_header(RACK_INPUT) end
def body; get_header(RACK_INPUT) end
def content_charset
that, per RFC2616, text/* media types that specify no explicit
parameter was given, or nil if no "charset" was specified. Note
The character set of the request body if a "charset" media type
def content_charset media_type_params['charset'] end
def content_length; get_header('CONTENT_LENGTH') end
def content_length; get_header('CONTENT_LENGTH') end
def content_type
def content_type content_type = get_header('CONTENT_TYPE') content_type.nil? || content_type.empty? ? nil : content_type end
def cookies
def cookies hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key| set_header(key, {}) end string = get_header(HTTP_COOKIE) unless string == get_header(RACK_REQUEST_COOKIE_STRING) hash.replace Utils.parse_cookies_header(string) set_header(RACK_REQUEST_COOKIE_STRING, string) end hash end
def default_session; {}; end
def default_session; {}; end
def delete?; request_method == DELETE end
def delete?; request_method == DELETE end
def delete_param(k)
If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
def delete_param(k) post_value, get_value = self.POST.delete(k), self.GET.delete(k) post_value || get_value end
def expand_param_pairs(pairs, query_parser = query_parser())
def expand_param_pairs(pairs, query_parser = query_parser()) params = query_parser.make_params pairs.each do |k, v| query_parser.normalize_params(params, k, v) end params.to_params_hash end
def form_data?
A request body is also assumed to contain form-data when no
+FORM_DATA_MEDIA_TYPES+ array.
list of form-data media types can be modified through the
"application/x-www-form-urlencoded" or "multipart/form-data". The
the request content-type for one of the media-types:
Determine whether the request body contains form-data by checking
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 forwarded_authority
def forwarded_authority forwarded_priority.each do |type| case type when :forwarded if forwarded = get_http_forwarded(:host) return forwarded.last end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_HOST) return wrap_ipv6(split_header(value).last) end end end nil end
def forwarded_for
def forwarded_for forwarded_priority.each do |type| case type when :forwarded if forwarded_for = get_http_forwarded(:for) return(forwarded_for.map! do |authority| split_authority(authority)[1] end) end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_FOR) return(split_header(value).map do |authority| split_authority(wrap_ipv6(authority))[1] end) end end end nil end
def forwarded_port
def forwarded_port forwarded_priority.each do |type| case type when :forwarded if forwarded = get_http_forwarded(:for) return(forwarded.map do |authority| split_authority(authority)[2] end.compact) end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_PORT) return split_header(value).map(&:to_i) end end end nil end
def forwarded_priority
def forwarded_priority Request.forwarded_priority end
def forwarded_scheme
def forwarded_scheme forwarded_priority.each do |type| case type when :forwarded if (forwarded_proto = get_http_forwarded(:proto)) && (scheme = allowed_scheme(forwarded_proto.last)) return scheme end when :x_forwarded x_forwarded_proto_priority.each do |x_type| if header = FORWARDED_SCHEME_HEADERS[x_type] split_header(get_header(header)).reverse_each do |scheme| if allowed_scheme(scheme) return scheme end end end end end end nil end
def fullpath
def fullpath query_string.empty? ? path : "#{path}?#{query_string}" end
def get?; request_method == GET end
def get?; request_method == GET end
def get_http_forwarded(token)
def get_http_forwarded(token) Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token) end
def head?; request_method == HEAD end
def head?; request_method == HEAD end
def host
def host split_authority(self.authority)[0] end
def host_authority
def host_authority get_header(HTTP_HOST) end
def host_with_port(authority = self.authority)
def host_with_port(authority = self.authority) host, _, port = split_authority(authority) if port == DEFAULT_PORTS[self.scheme] host else authority end end
def hostname
as +host+. In the case of IPv6 or future address formats, the square
In the case of a domain name or IPv4 address, the result is the same
Returns an address suitable for being to resolve to an address.
def hostname split_authority(self.authority)[1] end
def ip
def ip remote_addresses = split_header(get_header('REMOTE_ADDR')) external_addresses = reject_trusted_ip_addresses(remote_addresses) unless external_addresses.empty? return external_addresses.last end if (forwarded_for = self.forwarded_for) && !forwarded_for.empty? # The forwarded for addresses are ordered: client, proxy1, proxy2. # So we reject all the trusted addresses (proxy*) and return the # last client. Or if we trust everyone, we just return the first # address. return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first end # If all the addresses are trusted, and we aren't forwarded, just return # the first remote address, which represents the source of the request. remote_addresses.first end
def link?; request_method == LINK end
def link?; request_method == LINK end
def logger; get_header(RACK_LOGGER) end
def logger; get_header(RACK_LOGGER) end
def media_type
For more information on the use of media types in HTTP, see:
"text/plain;charset=utf-8", the media-type is "text/plain".
without any media type parameters. e.g., when CONTENT_TYPE is
The media type (type/subtype) portion of the CONTENT_TYPE header
def media_type MediaType.type(content_type) end
def media_type_params
this method responds with the following Hash:
provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
an empty Hash if no CONTENT_TYPE or media-type parameters were
The media type parameters provided in CONTENT_TYPE as a Hash, or
def media_type_params MediaType.params(content_type) end
def options?; request_method == OPTIONS end
def options?; request_method == OPTIONS end
def params
The union of GET and POST data.
def params self.GET.merge(self.POST) end
def parse_http_accept_header(header)
def parse_http_accept_header(header) # It would be nice to use filter_map here, but it's Ruby 2.7+ parts = header.to_s.split(',') parts.map! do |part| part.strip! next if part.empty? attribute, parameters = part.split(';', 2) attribute.strip! parameters&.strip! quality = 1.0 if parameters and /\Aq=([\d.]+)/ =~ parameters quality = $1.to_f end [attribute, quality] end parts.compact! parts end
def parse_multipart
def parse_multipart Rack::Multipart.extract_multipart(self, query_parser) end
def parse_query(qs, d = '&')
def parse_query(qs, d = '&') query_parser.parse_nested_query(qs, d) end
def parseable_data?
Determine whether the request body contains data by checking
def parseable_data? PARSEABLE_DATA_MEDIA_TYPES.include?(media_type) end
def patch?; request_method == PATCH end
def patch?; request_method == PATCH end
def path
def path script_name + path_info end
def path_info; get_header(PATH_INFO).to_s end
def path_info; get_header(PATH_INFO).to_s end
def path_info=(s); set_header(PATH_INFO, s.to_s) end
def path_info=(s); set_header(PATH_INFO, s.to_s) end
def port
def port if authority = self.authority _, _, port = split_authority(authority) end port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port end
def post?; request_method == POST end
def post?; request_method == POST end
def put?; request_method == PUT end
def put?; request_method == PUT end
def query_parser
def query_parser Utils.default_query_parser end
def query_string; get_header(QUERY_STRING).to_s end
def query_string; get_header(QUERY_STRING).to_s end
def referer; get_header('HTTP_REFERER') end
def referer; get_header('HTTP_REFERER') end
def reject_trusted_ip_addresses(ip_addresses)
def reject_trusted_ip_addresses(ip_addresses) ip_addresses.reject { |ip| trusted_proxy?(ip) } end
def request_method; get_header(REQUEST_METHOD) end
def request_method; get_header(REQUEST_METHOD) end
def scheme
def scheme if get_header(HTTPS) == 'on' 'https' elsif get_header(HTTP_X_FORWARDED_SSL) == 'on' 'https' elsif forwarded_scheme forwarded_scheme else get_header(RACK_URL_SCHEME) end end
def script_name; get_header(SCRIPT_NAME).to_s end
def script_name; get_header(SCRIPT_NAME).to_s end
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
def server_authority
The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
def server_authority host = self.server_name port = self.server_port if host if port "#{host}:#{port}" else host end end end
def server_name
def server_name get_header(SERVER_NAME) end
def server_port
def server_port get_header(SERVER_PORT) end
def session
def session fetch_header(RACK_SESSION) do |k| set_header RACK_SESSION, default_session end end
def session_options
def session_options fetch_header(RACK_SESSION_OPTIONS) do |k| set_header RACK_SESSION_OPTIONS, {} end end
def split_authority(authority)
def split_authority(authority) return [] if authority.nil? return [] unless match = AUTHORITY.match(authority) return match[:host], match[:address], match[:port]&.to_i end
def split_header(value)
def split_header(value) value ? value.strip.split(/[,\s]+/) : [] end
def ssl?
def ssl? scheme == 'https' || scheme == 'wss' end
def trace?; request_method == TRACE end
def trace?; request_method == TRACE end
def trusted_proxy?(ip)
def trusted_proxy?(ip) Rack::Request.ip_filter.call(ip) end
def unlink?; request_method == UNLINK end
def unlink?; request_method == UNLINK end
def update_param(k, v)
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.
Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
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
def url
def url base_url + fullpath end
def user_agent; get_header('HTTP_USER_AGENT') end
def user_agent; get_header('HTTP_USER_AGENT') end
def values_at(*keys)
def values_at(*keys) warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1) keys.map { |key| params[key] } end
def wrap_ipv6(host)
def wrap_ipv6(host) # Even thought IPv6 addresses should be wrapped in square brackets, # sometimes this is not done in various legacy/underspecified headers. # So we try to fix this situation for compatibility reasons. # Try to detect IPv6 addresses which aren't escaped yet: if !host.start_with?('[') && host.count(':') > 1 "[#{host}]" else host end end
def x_forwarded_proto_priority
def x_forwarded_proto_priority Request.x_forwarded_proto_priority end
def xhr?
def xhr? get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" end