1 require 'action_controller/cgi_ext'
2 require 'action_controller/session/cookie_store'
4 module ActionController
#:nodoc:
5 class RackRequest
< AbstractRequest
#:nodoc:
6 attr_accessor
:session_options
9 class SessionFixationAttempt
< StandardError
#:nodoc:
12 DEFAULT_SESSION_OPTIONS
= {
13 :database_manager => CGI
::Session::CookieStore, # store data in cookie
14 :prefix => "ruby_sess.", # prefix session file names
15 :session_path => "/", # available to all paths in app
16 :session_key => "_session_id",
18 :session_http_only=> true
21 def initialize(env, session_options
= DEFAULT_SESSION_OPTIONS
)
22 @session_options = session_options
24 @cgi = CGIWrapper
.new(self)
28 %w
[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
29 PATH_TRANSLATED REMOTE_HOST
30 REMOTE_IDENT REMOTE_USER SCRIPT_NAME
31 SERVER_NAME SERVER_PROTOCOL
33 HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
34 HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
35 HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT
].each
do |env|
36 define_method(env.sub(/^HTTP_/n
, '').downcase
) do
50 def body_stream
#:nodoc:
59 return {} unless @env["HTTP_COOKIE"]
61 unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
62 @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
63 @env["rack.request.cookie_hash"] = CGI
::Cookie::parse(@env["rack.request.cookie_string"])
66 @env["rack.request.cookie_hash"]
70 @env['SERVER_PORT'].to_i
74 @env['SERVER_SOFTWARE'].split("/").first
78 unless defined?(@session)
79 if @session_options == false
82 stale_session_check
! do
83 if cookie_only
? && query_parameters
[session_options_with_string_keys
['session_key']]
84 raise SessionFixationAttempt
86 case value
= session_options_with_string_keys
['new_session']
88 @session = new_session
91 @session = CGI
::Session.new(@cgi, session_options_with_string_keys
)
92 # CGI::Session raises ArgumentError if 'new_session' == false
93 # and no session cookie or query param is present.
98 @session = CGI
::Session.new(@cgi, session_options_with_string_keys
)
100 raise ArgumentError
, "Invalid new_session option: #{value}"
102 @session['__valid_session']
110 @session.delete
if defined?(@session) && @session.is_a
?(CGI
::Session)
111 @session = new_session
115 # Delete an old session if it exists then create a new one.
117 if @session_options == false
120 CGI
::Session.new(@cgi, session_options_with_string_keys
.merge("new_session" => false)).delete
rescue nil
121 CGI
::Session.new(@cgi, session_options_with_string_keys
.merge("new_session" => true))
126 session_options_with_string_keys
['cookie_only']
129 def stale_session_check
!
131 rescue ArgumentError
=> argument_error
132 if argument_error
.message
=~
%r
{undefined
class/module ([\w
:]*\w
)}
134 # Note that the regexp does not allow $1 to end with a ':'
136 rescue LoadError
, NameError
=> const_error
137 raise ActionController
::SessionRestoreError, <<-end_msg
138 Session contains objects whose class definition isn\'t available.
139 Remember to require the classes for all objects kept in the session.
140 (Original exception: #{const_error.message} [#{const_error.class}])
150 def session_options_with_string_keys
151 @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS
.merge(@session_options).stringify_keys
155 class RackResponse
< AbstractResponse
#:nodoc:
156 def initialize(request
)
158 @writer = lambda
{ |x
| @body << x
}
163 # Retrieve status from instance variable if has already been delete
168 def out(output
= $stdout, &block
)
169 # Nasty hack because CGI sessions are closed after the normal
174 @status = headers
.delete("Status")
175 if [204, 304].include?(status
.to_i
)
176 headers
.delete("Content-Type")
177 [status
, headers
.to_hash
, []]
179 [status
, headers
.to_hash
, self]
185 if @body.respond_to
?(:call)
186 @writer = lambda
{ |x
| callback
.call(x
) }
187 @body.call(self, self)
188 elsif @body.is_a
?(String
)
189 @body.each_line(&callback
)
191 @body.each(&callback
)
195 @block.call(self) if @block
199 @writer.call str
.to_s
204 @body.close
if @body.respond_to
?(:close)
208 @block == nil && @body.empty
?
221 def convert_language
!
222 headers
["Content-Language"] = headers
.delete("language") if headers
["language"]
226 headers
["Expires"] = headers
.delete("") if headers
["expires"]
229 def convert_content_type
!
231 headers
['Content-Type'] = headers
.delete('type') || "text/html"
232 headers
['Content-Type'] += "; charset=" + headers
.delete('charset') if headers
['charset']
235 def set_content_length
!
237 headers
["Content-Length"] = headers
["Content-Length"].to_s
if headers
["Content-Length"]
241 self.status
||= "200 OK"
245 # Convert 'cookie' header to 'Set-Cookie' headers.
246 # Because Set-Cookie header can appear more the once in the response body,
247 # we store it in a line break separated string that will be translated to
248 # multiple Set-Cookie header by the handler.
249 if cookie
= headers
.delete('cookie')
253 when Array
then cookie
.each
{ |c
| cookies
<< c
.to_s
}
254 when Hash
then cookie
.each
{ |_
, c
| cookies
<< c
.to_s
}
255 else cookies
<< cookie
.to_s
258 @cgi.output_cookies
.each
{ |c
| cookies
<< c
.to_s
} if @cgi.output_cookies
260 headers
['Set-Cookie'] = [headers
['Set-Cookie'], cookies
].flatten
.compact
265 class CGIWrapper
< ::CGI
266 attr_reader
:output_cookies
268 def initialize(request
, *args
)
271 @input = request
.body
277 @params ||= @request.params
285 @request.query_string
288 # Used to wrap the normal args variable used inside CGI.
293 # Used to wrap the normal env_table variable used inside CGI.
298 # Used to wrap the normal stdinput variable used inside CGI.