Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/actionpack/lib/action_controller/cgi_process.rb

Revision 8578, 7.7 kB (checked in by bitsweat, 1 year ago)

The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. Closes #10549.

Line 
1 require 'action_controller/cgi_ext'
2 require 'action_controller/session/cookie_store'
3
4 module ActionController #:nodoc:
5   class Base
6     # Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
7     # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
8     #
9     # * <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
11     #   lib/action_controller/session.
12     # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
13     # * <tt>:session_id</tt> - the session id to use.  If not provided, then it is retrieved from the +session_key+ cookie, or
14     #   automatically generated for a new session.
15     # * <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,
17     #   an ArgumentError is raised.
18     # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object.  If not set, the session will continue
19     #   indefinitely.
20     # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
21     #   server.
22     # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
23     # * <tt>:session_path</tt> - the path for which this session applies.  Defaults to the directory of the CGI script.
24     # * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
25     #   the query string or POST parameters. This protects against session fixation attacks.
26     def self.process_cgi(cgi = CGI.new, session_options = {})
27       new.process_cgi(cgi, session_options)
28     end
29
30     def process_cgi(cgi, session_options = {}) #:nodoc:
31       process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
32     end
33   end
34
35   class CgiRequest < AbstractRequest #:nodoc:
36     attr_accessor :cgi, :session_options
37     class SessionFixationAttempt < StandardError #:nodoc:
38     end
39
40     DEFAULT_SESSION_OPTIONS = {
41       :database_manager => CGI::Session::CookieStore, # store data in cookie
42       :prefix           => "ruby_sess.",    # prefix session file names
43       :session_path     => "/",             # available to all paths in app
44       :session_key      => "_session_id",
45       :cookie_only      => true
46     } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
47
48     def initialize(cgi, session_options = {})
49       @cgi = cgi
50       @session_options = session_options
51       @env = @cgi.send!(:env_table)
52       super()
53     end
54
55     def query_string
56       qs = @cgi.query_string if @cgi.respond_to?(:query_string)
57       if !qs.blank?
58         qs
59       else
60         super
61       end
62     end
63
64     # The request body is an IO input stream. If the RAW_POST_DATA environment
65     # variable is already set, wrap it in a StringIO.
66     def body
67       if raw_post = env['RAW_POST_DATA']
68         StringIO.new(raw_post)
69       else
70         @cgi.stdinput
71       end
72     end
73
74     def query_parameters
75       @query_parameters ||= self.class.parse_query_parameters(query_string)
76     end
77
78     def request_parameters
79       @request_parameters ||= parse_formatted_request_parameters
80     end
81
82     def cookies
83       @cgi.cookies.freeze
84     end
85
86     def host_with_port_without_standard_port_handling
87       if forwarded = env["HTTP_X_FORWARDED_HOST"]
88         forwarded.split(/,\s?/).last
89       elsif http_host = env['HTTP_HOST']
90         http_host
91       elsif server_name = env['SERVER_NAME']
92         server_name
93       else
94         "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
95       end
96     end
97
98     def host
99       host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
100     end
101
102     def port
103       if host_with_port_without_standard_port_handling =~ /:(\d+)$/
104         $1.to_i
105       else
106         standard_port
107       end
108     end
109
110     def session
111       unless defined?(@session)
112         if @session_options == false
113           @session = Hash.new
114         else
115           stale_session_check! do
116             if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
117               raise SessionFixationAttempt
118             end
119             case value = session_options_with_string_keys['new_session']
120               when true
121                 @session = new_session
122               when false
123                 begin
124                   @session = CGI::Session.new(@cgi, session_options_with_string_keys)
125                 # CGI::Session raises ArgumentError if 'new_session' == false
126                 # and no session cookie or query param is present.
127                 rescue ArgumentError
128                   @session = Hash.new
129                 end
130               when nil
131                 @session = CGI::Session.new(@cgi, session_options_with_string_keys)
132               else
133                 raise ArgumentError, "Invalid new_session option: #{value}"
134             end
135             @session['__valid_session']
136           end
137         end
138       end
139       @session
140     end
141
142     def reset_session
143       @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
144       @session = new_session
145     end
146
147     def method_missing(method_id, *arguments)
148       @cgi.send!(method_id, *arguments) rescue super
149     end
150
151     private
152       # Delete an old session if it exists then create a new one.
153       def new_session
154         if @session_options == false
155           Hash.new
156         else
157           CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
158           CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
159         end
160       end
161
162       def cookie_only?
163         session_options_with_string_keys['cookie_only']
164       end
165
166       def stale_session_check!
167         yield
168       rescue ArgumentError => argument_error
169         if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
170           begin
171             # Note that the regexp does not allow $1 to end with a ':'
172             $1.constantize
173           rescue LoadError, NameError => const_error
174             raise ActionController::SessionRestoreError, <<-end_msg
175 Session contains objects whose class definition isn\'t available.
176 Remember to require the classes for all objects kept in the session.
177 (Original exception: #{const_error.message} [#{const_error.class}])
178 end_msg
179           end
180
181           retry
182         else
183           raise
184         end
185       end
186
187       def session_options_with_string_keys
188         @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
189       end
190   end
191
192   class CgiResponse < AbstractResponse #:nodoc:
193     def initialize(cgi)
194       @cgi = cgi
195       super()
196     end
197
198     def out(output = $stdout)
199       output.binmode      if output.respond_to?(:binmode)
200       output.sync = false if output.respond_to?(:sync=)
201
202       begin
203         output.write(@cgi.header(@headers))
204
205         if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
206           return
207         elsif @body.respond_to?(:call)
208           # Flush the output now in case the @body Proc uses
209           # #syswrite.
210           output.flush if output.respond_to?(:flush)
211           @body.call(self, output)
212         else
213           output.write(@body)
214         end
215
216         output.flush if output.respond_to?(:flush)
217       rescue Errno::EPIPE, Errno::ECONNRESET
218         # lost connection to parent process, ignore output
219       end
220     end
221   end
222 end
Note: See TracBrowser for help on using the browser.