cgi_process.rb 6.9 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
require 'action_controller/cgi_ext/cgi_ext'
2
require 'action_controller/cgi_ext/cookie_performance_fix'
3
require 'action_controller/cgi_ext/raw_post_data_fix'
D
Initial  
David Heinemeier Hansson 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

module ActionController #:nodoc:
  class Base
    # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
    # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
    #
    # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
    #   (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in 
    #   lib/action_controller/session.
    # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
    # * <tt>:session_id</tt> - the session id to use.  If not provided, then it is retrieved from the +session_key+ parameter
    #   of the request, or automatically generated for a new session.
    # * <tt>:new_session</tt> - if true, force creation of a new session.  If not set, a new session is only created if none currently
    #   exists.  If false, a new session is never created, and if none currently exists and the +session_id+ option is not set, 
    #   an ArgumentError is raised.
    # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object.  If not set, the session will continue
    #   indefinitely.
    # * <tt>:session_domain</tt> -  the hostname domain for which this session is valid. If not set, defaults to the hostname of the
    #   server.
    # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
    # * <tt>:session_path</tt> - the path for which this session applies.  Defaults to the directory of the CGI script.
    def self.process_cgi(cgi = CGI.new, session_options = {}) 
      new.process_cgi(cgi, session_options)
    end
  
    def process_cgi(cgi, session_options = {}) #:nodoc:
      process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
    end
  end

  class CgiRequest < AbstractRequest #:nodoc:
35
    attr_accessor :cgi, :session_options
D
Initial  
David Heinemeier Hansson 已提交
36

37 38
    DEFAULT_SESSION_OPTIONS = {
      :database_manager => CGI::Session::PStore,
39
      :prefix           => "ruby_sess.",
40
      :session_path     => "/"
41
    } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
D
Initial  
David Heinemeier Hansson 已提交
42 43 44 45

    def initialize(cgi, session_options = {})
      @cgi = cgi
      @session_options = session_options
46
      @env = @cgi.send(:env_table)
D
Initial  
David Heinemeier Hansson 已提交
47 48 49
      super()
    end

50
    def query_string
51 52
      if (qs = @cgi.query_string) && !qs.empty?
        qs
53
      elsif uri = @env['REQUEST_URI']
54 55 56
        parts = uri.split('?')  
        parts.shift
        parts.join('?')
57
      else
58
        @env['QUERY_STRING'] || ''
59
      end
60 61
    end

D
Initial  
David Heinemeier Hansson 已提交
62
    def query_parameters
63
      (qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
D
Initial  
David Heinemeier Hansson 已提交
64 65 66
    end

    def request_parameters
67 68 69 70 71 72
      @request_parameters ||=
        if ActionController::Base.param_parsers.has_key?(content_type)
          CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])
        else
          CGIMethods.parse_request_parameters(@cgi.params)
        end
D
Initial  
David Heinemeier Hansson 已提交
73
    end
74
   
D
Initial  
David Heinemeier Hansson 已提交
75 76 77 78
    def cookies
      @cgi.cookies.freeze
    end

79 80 81 82 83 84 85 86 87 88 89 90
    def host_with_port
      if forwarded = env["HTTP_X_FORWARDED_HOST"]
        forwarded.split(/,\s?/).last
      elsif http_host = env['HTTP_HOST']
        http_host
      elsif server_name = env['SERVER_NAME']
        server_name
      else
        "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
      end
    end

D
Initial  
David Heinemeier Hansson 已提交
91
    def host
92
      host_with_port[/^[^:]+/]
93
    end
94

95
    def port
96 97 98 99 100
      if host_with_port =~ /:(\d+)$/
        $1.to_i
      else
        standard_port
      end
D
Initial  
David Heinemeier Hansson 已提交
101
    end
102

D
Initial  
David Heinemeier Hansson 已提交
103
    def session
104 105
      unless @session
        if @session_options == false
106
          @session = Hash.new
107
        else
108 109 110 111 112 113 114
          stale_session_check! do
            if session_options_with_string_keys['new_session'] == true
              @session = new_session
            else
              @session = CGI::Session.new(@cgi, session_options_with_string_keys)
            end
            @session['__valid_session']
115
          end
116
        end
D
Initial  
David Heinemeier Hansson 已提交
117
      end
118
      @session
D
Initial  
David Heinemeier Hansson 已提交
119
    end
120

D
Initial  
David Heinemeier Hansson 已提交
121
    def reset_session
122
      @session.delete if CGI::Session === @session
123
      @session = new_session
D
Initial  
David Heinemeier Hansson 已提交
124 125 126 127 128 129 130
    end

    def method_missing(method_id, *arguments)
      @cgi.send(method_id, *arguments) rescue super
    end

    private
131
      # Delete an old session if it exists then create a new one.
D
Initial  
David Heinemeier Hansson 已提交
132
      def new_session
133
        if @session_options == false
134
          Hash.new
135 136 137 138
        else
          CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
          CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
        end
D
Initial  
David Heinemeier Hansson 已提交
139
      end
140 141

      def stale_session_check!
142
        yield
143 144 145 146 147 148
      rescue ArgumentError => argument_error
        if argument_error.message =~ %r{undefined class/module (\w+)}
          begin
            Module.const_missing($1)
          rescue LoadError, NameError => const_error
            raise ActionController::SessionRestoreError, <<end_msg
149
Session contains objects whose class definition isn\'t available.
150 151 152 153 154 155 156 157 158 159 160
Remember to require the classes for all objects kept in the session.
(Original exception: #{const_error.message} [#{const_error.class}])
end_msg
          end

          retry
        else
          raise
        end
      end

161
      def session_options_with_string_keys
162
        @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
163
      end
D
Initial  
David Heinemeier Hansson 已提交
164 165 166 167 168 169 170 171
  end

  class CgiResponse < AbstractResponse #:nodoc:
    def initialize(cgi)
      @cgi = cgi
      super()
    end

172
    def out(output = $stdout)
D
Initial  
David Heinemeier Hansson 已提交
173
      convert_content_type!(@headers)
174 175
      output.binmode      if output.respond_to?(:binmode)
      output.sync = false if output.respond_to?(:sync=)
176 177
      
      begin
178
        output.write(@cgi.header(@headers))
179

180 181 182
        if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
          return
        elsif @body.respond_to?(:call)
183 184 185
          # Flush the output now in case the @body Proc uses
          # #syswrite.
          output.flush if output.respond_to?(:flush)
186
          @body.call(self, output)
187
        else
188
          output.write(@body)
189
        end
190 191

        output.flush if output.respond_to?(:flush)
192 193
      rescue Errno::EPIPE => e
        # lost connection to the FCGI process -- ignore the output, then
D
Initial  
David Heinemeier Hansson 已提交
194 195 196 197 198
      end
    end

    private
      def convert_content_type!(headers)
199 200 201 202 203 204 205 206
        if header = headers.delete("Content-Type")
          headers["type"] = header
        end
        if header = headers.delete("Content-type")
          headers["type"] = header
        end
        if header = headers.delete("content-type")
          headers["type"] = header
D
Initial  
David Heinemeier Hansson 已提交
207 208 209 210
        end
      end
  end
end