Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / actionpack / lib / action_controller / request.rb
diff --git a/vendor/rails/actionpack/lib/action_controller/request.rb b/vendor/rails/actionpack/lib/action_controller/request.rb
new file mode 100755 (executable)
index 0000000..ef223f1
--- /dev/null
@@ -0,0 +1,489 @@
+require 'tempfile'
+require 'stringio'
+require 'strscan'
+
+require 'active_support/memoizable'
+require 'action_controller/cgi_ext'
+
+module ActionController
+  class Request < Rack::Request
+
+    %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)
+    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 }
+
+    # 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.
+    def request_method
+      @request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
+    end
+
+    # 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.)
+    def method
+      request_method == :head ? :get : request_method
+    end
+
+    # Is this a GET (or HEAD) request?  Equivalent to <tt>request.method == :get</tt>.
+    def get?
+      method == :get
+    end
+
+    # Is this a POST request?  Equivalent to <tt>request.method == :post</tt>.
+    def post?
+      request_method == :post
+    end
+
+    # Is this a PUT request?  Equivalent to <tt>request.method == :put</tt>.
+    def put?
+      request_method == :put
+    end
+
+    # Is this a DELETE request?  Equivalent to <tt>request.method == :delete</tt>.
+    def delete?
+      request_method == :delete
+    end
+
+    # Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
+    # this \method checks the actual HTTP \method directly.
+    def head?
+      request_method == :head
+    end
+
+    # Provides access to the request's HTTP headers, for example:
+    #
+    #   request.headers["Content-Type"] # => "text/plain"
+    def headers
+      @headers ||= ActionController::Http::Headers.new(@env)
+    end
+
+    # Returns the content length of the request as an integer.
+    def content_length
+      super.to_i
+    end
+
+    # The MIME type of the HTTP request, such as Mime::XML.
+    #
+    # For backward compatibility, the post \format is extracted from the
+    # X-Post-Data-Format HTTP header if present.
+    def content_type
+      @content_type ||= begin
+        if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+          Mime::Type.lookup($1.strip.downcase)
+        else
+          nil
+        end
+      end
+    end
+
+    # Returns the accepted MIME type for the request.
+    def accepts
+      @accepts ||= begin
+        header = @env['HTTP_ACCEPT'].to_s.strip
+
+        if header.empty?
+          [content_type, Mime::ALL].compact
+        else
+          Mime::Type.parse(header)
+        end
+      end
+    end
+
+    def if_modified_since
+      if since = env['HTTP_IF_MODIFIED_SINCE']
+        Time.rfc2822(since) rescue nil
+      end
+    end
+
+    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
+      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
+    end
+
+    # Returns the Mime type for the \format used in the request.
+    #
+    #   GET /posts/5.xml   | request.format => Mime::XML
+    #   GET /posts/5.xhtml | request.format => Mime::HTML
+    #   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>
+    def format
+      @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
+    end
+
+
+    # Sets the \format by string extension, which can be used to force custom formats
+    # that are not controlled by the extension.
+    #
+    #   class ApplicationController < ActionController::Base
+    #     before_filter :adjust_format_for_iphone
+    #
+    #     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
+      @format = Mime::Type.lookup_by_extension(parameters[:format])
+    end
+
+    # 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
+
+    # 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?
+      !(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
+    end
+    alias xhr? :xml_http_request?
+
+    # 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
+
+    # Determines originating IP address.  REMOTE_ADDR is the standard
+    # but will fail if the user is behind a proxy.  HTTP_CLIENT_IP and/or
+    # 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.
+    def remote_ip
+      remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
+
+      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?
+      end
+      remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
+
+      if @env.include? 'HTTP_CLIENT_IP'
+        if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
+          # 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
+
+        return @env['HTTP_CLIENT_IP']
+      end
+
+      if remote_ips
+        while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
+          remote_ips.pop
+        end
+
+        return remote_ips.last.strip
+      end
+
+      @env['REMOTE_ADDR']
+    end
+
+    # 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
+
+    # Returns the complete URL used for this request.
+    def url
+      protocol + host_with_port + request_uri
+    end
+
+    # Returns 'https://' if this is an SSL request and 'http://' otherwise.
+    def protocol
+      ssl? ? 'https://' : 'http://'
+    end
+
+    # Is this an SSL request?
+    def ssl?
+      @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
+    end
+
+    # 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
+        env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+      end
+    end
+
+    # Returns the host for this request, such as example.com.
+    def host
+      raw_host_with_port.sub(/:\d+$/, '')
+    end
+
+    # Returns a \host:\port string for this request, such as "example.com" or
+    # "example.com:8080".
+    def host_with_port
+      "#{host}#{port_string}"
+    end
+
+    # Returns the port number of this request as an integer.
+    def port
+      if raw_host_with_port =~ /:(\d+)$/
+        $1.to_i
+      else
+        standard_port
+      end
+    end
+
+    # Returns the standard \port number for this request's protocol.
+    def standard_port
+      case protocol
+        when 'https://' then 443
+        else 80
+      end
+    end
+
+    # Returns a \port suffix like ":8080" if the \port number of this request
+    # is not the default HTTP \port 80 or HTTPS \port 443.
+    def port_string
+      port == standard_port ? '' : ":#{port}"
+    end
+
+    # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
+    # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
+    def domain(tld_length = 1)
+      return nil unless named_host?(host)
+
+      host.split('.').last(1 + tld_length).join('.')
+    end
+
+    # 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>
+    # in "www.rubyonrails.co.uk".
+    def subdomains(tld_length = 1)
+      return [] unless named_host?(host)
+      parts = host.split('.')
+      parts[0..-(tld_length+2)]
+    end
+
+    # Returns the query string, accounting for server idiosyncrasies.
+    def query_string
+      @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
+    end
+
+    # Returns the request URI, accounting for server idiosyncrasies.
+    # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
+    def request_uri
+      if uri = @env['REQUEST_URI']
+        # 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.
+        uri = @env['PATH_INFO'].to_s
+
+        if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
+          uri = uri.sub(/#{script_filename}\//, '')
+        end
+
+        env_qs = @env['QUERY_STRING'].to_s
+        uri += "?#{env_qs}" unless env_qs.empty?
+
+        if uri.blank?
+          @env.delete('REQUEST_URI')
+        else
+          @env['REQUEST_URI'] = uri
+        end
+      end
+    end
+
+    # Returns the interpreted \path to requested resource after all the installation
+    # directory of this application was taken into account.
+    def path
+      path = request_uri.to_s[/\A[^\?]*/]
+      path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
+      path
+    end
+
+    # Read the request \body. This is useful for web services that need to
+    # work with raw requests directly.
+    def raw_post
+      unless @env.include? 'RAW_POST_DATA'
+        @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
+        body.rewind if body.respond_to?(:rewind)
+      end
+      @env['RAW_POST_DATA']
+    end
+
+    # Returns both GET and POST \parameters in a single hash.
+    def parameters
+      @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
+    end
+    alias_method :params, :parameters
+
+    def path_parameters=(parameters) #:nodoc:
+      @env["rack.routing_args"] = parameters
+      @symbolized_path_parameters = @parameters = nil
+    end
+
+    # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
+    def symbolized_path_parameters
+      @symbolized_path_parameters ||= path_parameters.symbolize_keys
+    end
+
+    # Returns a hash with the \parameters used to form the \path of the request.
+    # Returned hash keys are strings:
+    #
+    #   {'action' => 'my_action', 'controller' => 'my_controller'}
+    #
+    # See <tt>symbolized_path_parameters</tt> for symbolized keys.
+    def path_parameters
+      @env["rack.routing_args"] ||= {}
+    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 = @env['RAW_POST_DATA']
+        raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+        StringIO.new(raw_post)
+      else
+        @env['rack.input']
+      end
+    end
+
+    def form_data?
+      FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
+    end
+
+    # Override Rack's GET method to support indifferent access
+    def GET
+      @env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
+    end
+    alias_method :query_parameters, :GET
+
+    # Override Rack's POST method to support indifferent access
+    def POST
+      @env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
+    end
+    alias_method :request_parameters, :POST
+
+    def body_stream #:nodoc:
+      @env['rack.input']
+    end
+
+    def session
+      @env['rack.session'] ||= {}
+    end
+
+    def session=(session) #:nodoc:
+      @env['rack.session'] = session
+    end
+
+    def reset_session
+      @env['rack.session.options'].delete(:id)
+      @env['rack.session'] = {}
+    end
+
+    def session_options
+      @env['rack.session.options'] ||= {}
+    end
+
+    def session_options=(options)
+      @env['rack.session.options'] = options
+    end
+
+    def server_port
+      @env['SERVER_PORT'].to_i
+    end
+
+    private
+      def named_host?(host)
+        !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+      end
+
+      # 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
+  end
+end