cgi_process.rb 7.1 KB
Newer Older
1
require 'action_controller/cgi_ext'
2
require 'action_controller/session/cookie_store'
D
Initial  
David Heinemeier Hansson 已提交
3 4 5 6 7 8 9

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
10
    #   (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
D
Initial  
David Heinemeier Hansson 已提交
11 12 13 14 15
    #   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
16
    #   exists.  If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
D
Initial  
David Heinemeier Hansson 已提交
17 18 19 20 21 22 23
    #   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.
24
    def self.process_cgi(cgi = CGI.new, session_options = {})
D
Initial  
David Heinemeier Hansson 已提交
25 26
      new.process_cgi(cgi, session_options)
    end
27

D
Initial  
David Heinemeier Hansson 已提交
28 29 30 31 32 33
    def process_cgi(cgi, session_options = {}) #:nodoc:
      process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
    end
  end

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

36
    DEFAULT_SESSION_OPTIONS = {
37 38 39
      :database_manager => CGI::Session::CookieStore, # store data in cookie
      :prefix           => "ruby_sess.",    # prefix session file names
      :session_path     => "/"              # available to all paths in app
40
    } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
D
Initial  
David Heinemeier Hansson 已提交
41 42 43 44

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

49
    def query_string
50
      qs = @cgi.query_string if @cgi.respond_to?(:query_string)
51
      if !qs.blank?
52
        qs
53
      elsif uri = @env['REQUEST_URI']
54
        uri.split('?', 2)[1] || ''
55
      else
56
        @env['QUERY_STRING'] || ''
57
      end
58 59
    end

60 61 62 63 64 65 66 67 68 69
    # 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']
        StringIO.new(raw_post)
      else
        @cgi.stdinput
      end
    end

D
Initial  
David Heinemeier Hansson 已提交
70
    def query_parameters
71
      @query_parameters ||= self.class.parse_query_parameters(query_string)
D
Initial  
David Heinemeier Hansson 已提交
72 73 74
    end

    def request_parameters
75
      @request_parameters ||= parse_formatted_request_parameters
D
Initial  
David Heinemeier Hansson 已提交
76
    end
77

D
Initial  
David Heinemeier Hansson 已提交
78 79 80 81
    def cookies
      @cgi.cookies.freeze
    end

82 83 84 85 86 87 88 89 90 91 92 93
    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 已提交
94
    def host
95
      host_with_port.sub(/:\d+$/, '')
96
    end
97

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

D
Initial  
David Heinemeier Hansson 已提交
106
    def session
107
      unless defined?(@session)
108
        if @session_options == false
109
          @session = Hash.new
110
        else
111
          stale_session_check! do
112 113 114 115 116 117 118 119 120 121 122 123
            case value = session_options_with_string_keys['new_session']
              when true
                @session = new_session
              when false
                begin
                  @session = CGI::Session.new(@cgi, session_options_with_string_keys)
                # CGI::Session raises ArgumentError if 'new_session' == false
                # and no session cookie or query param is present.
                rescue ArgumentError
                  @session = Hash.new
                end
              when nil
124
                @session = CGI::Session.new(@cgi, session_options_with_string_keys)
125 126
              else
                raise ArgumentError, "Invalid new_session option: #{value}"
127 128
            end
            @session['__valid_session']
129
          end
130
        end
D
Initial  
David Heinemeier Hansson 已提交
131
      end
132
      @session
D
Initial  
David Heinemeier Hansson 已提交
133
    end
134

D
Initial  
David Heinemeier Hansson 已提交
135
    def reset_session
136
      @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
137
      @session = new_session
D
Initial  
David Heinemeier Hansson 已提交
138 139 140 141 142 143 144
    end

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

    private
145
      # Delete an old session if it exists then create a new one.
D
Initial  
David Heinemeier Hansson 已提交
146
      def new_session
147
        if @session_options == false
148
          Hash.new
149 150 151 152
        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 已提交
153
      end
154 155

      def stale_session_check!
156
        yield
157
      rescue ArgumentError => argument_error
158
        if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
159
          begin
160 161
            # Note that the regexp does not allow $1 to end with a ':'
            $1.constantize
162
          rescue LoadError, NameError => const_error
163
            raise ActionController::SessionRestoreError, <<-end_msg
164
Session contains objects whose class definition isn\'t available.
165 166 167 168 169 170 171 172 173 174 175
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

176
      def session_options_with_string_keys
J
Jeremy Kemper 已提交
177
        @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
178
      end
D
Initial  
David Heinemeier Hansson 已提交
179 180 181 182 183 184 185 186
  end

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

187 188 189
    def out(output = $stdout)
      output.binmode      if output.respond_to?(:binmode)
      output.sync = false if output.respond_to?(:sync=)
190

191
      begin
192
        output.write(@cgi.header(@headers))
193

194 195 196
        if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
          return
        elsif @body.respond_to?(:call)
197 198 199
          # Flush the output now in case the @body Proc uses
          # #syswrite.
          output.flush if output.respond_to?(:flush)
200
          @body.call(self, output)
201
        else
202
          output.write(@body)
203
        end
204 205

        output.flush if output.respond_to?(:flush)
206 207
      rescue Errno::EPIPE, Errno::ECONNRESET
        # lost connection to parent process, ignore output
D
Initial  
David Heinemeier Hansson 已提交
208 209 210
      end
    end
  end
211
end