aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller.rb15
-rw-r--r--actionpack/lib/action_controller/base.rb12
-rw-r--r--actionpack/lib/action_controller/cgi_ext.rb1
-rw-r--r--actionpack/lib/action_controller/cgi_ext/session.rb53
-rw-r--r--actionpack/lib/action_controller/cgi_process.rb2
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb8
-rw-r--r--actionpack/lib/action_controller/integration.rb4
-rw-r--r--actionpack/lib/action_controller/middleware_stack.rb25
-rw-r--r--actionpack/lib/action_controller/rack_process.rb139
-rw-r--r--actionpack/lib/action_controller/session/abstract_store.rb131
-rw-r--r--actionpack/lib/action_controller/session/active_record_store.rb350
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb337
-rwxr-xr-xactionpack/lib/action_controller/session/drb_server.rb32
-rw-r--r--actionpack/lib/action_controller/session/drb_store.rb35
-rw-r--r--actionpack/lib/action_controller/session/mem_cache_store.rb119
-rw-r--r--actionpack/lib/action_controller/session_management.rb174
-rw-r--r--actionpack/lib/action_view/template.rb10
-rw-r--r--actionpack/lib/action_view/template_handlers.rb25
18 files changed, 477 insertions, 995 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index abc404afe7..c170e4dd2a 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -89,18 +89,15 @@ module ActionController
autoload :Headers, 'action_controller/headers'
end
- # DEPRECATE: Remove CGI support
- autoload :CgiRequest, 'action_controller/cgi_process'
- autoload :CGIHandler, 'action_controller/cgi_process'
-end
-
-class CGI
- class Session
- autoload :ActiveRecordStore, 'action_controller/session/active_record_store'
+ module Session
+ autoload :AbstractStore, 'action_controller/session/abstract_store'
autoload :CookieStore, 'action_controller/session/cookie_store'
- autoload :DRbStore, 'action_controller/session/drb_store'
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
end
+
+ # DEPRECATE: Remove CGI support
+ autoload :CgiRequest, 'action_controller/cgi_process'
+ autoload :CGIHandler, 'action_controller/cgi_process'
end
autoload :Mime, 'action_controller/mime_type'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 13f2e9072e..0b32da55d5 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -164,8 +164,8 @@ module ActionController #:nodoc:
#
# Other options for session storage are:
#
- # * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
- # unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
+ # * ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
+ # unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
#
# config.action_controller.session_store = :active_record_store
#
@@ -1216,7 +1216,6 @@ module ActionController #:nodoc:
def log_processing
if logger && logger.info?
log_processing_for_request_id
- log_processing_for_session_id
log_processing_for_parameters
end
end
@@ -1229,13 +1228,6 @@ module ActionController #:nodoc:
logger.info(request_id)
end
- def log_processing_for_session_id
- if @_session && @_session.respond_to?(:session_id) && @_session.respond_to?(:dbman) &&
- !@_session.dbman.is_a?(CGI::Session::CookieStore)
- logger.info " Session ID: #{@_session.session_id}"
- end
- end
-
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
diff --git a/actionpack/lib/action_controller/cgi_ext.rb b/actionpack/lib/action_controller/cgi_ext.rb
index f3b8c08d8f..406b6f06d6 100644
--- a/actionpack/lib/action_controller/cgi_ext.rb
+++ b/actionpack/lib/action_controller/cgi_ext.rb
@@ -1,7 +1,6 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
-require 'action_controller/cgi_ext/session'
class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
diff --git a/actionpack/lib/action_controller/cgi_ext/session.rb b/actionpack/lib/action_controller/cgi_ext/session.rb
deleted file mode 100644
index d3f85e3705..0000000000
--- a/actionpack/lib/action_controller/cgi_ext/session.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'digest/md5'
-require 'cgi/session'
-require 'cgi/session/pstore'
-
-class CGI #:nodoc:
- # * Expose the CGI instance to session stores.
- # * Don't require 'digest/md5' whenever a new session id is generated.
- class Session #:nodoc:
- def self.generate_unique_id(constant = nil)
- ActiveSupport::SecureRandom.hex(16)
- end
-
- # Make the CGI instance available to session stores.
- attr_reader :cgi
- attr_reader :dbman
- alias_method :initialize_without_cgi_reader, :initialize
- def initialize(cgi, options = {})
- @cgi = cgi
- initialize_without_cgi_reader(cgi, options)
- end
-
- private
- # Create a new session id.
- def create_new_id
- @new_session = true
- self.class.generate_unique_id
- end
-
- # * Don't require 'digest/md5' whenever a new session is started.
- class PStore #:nodoc:
- def initialize(session, option={})
- dir = option['tmpdir'] || Dir::tmpdir
- prefix = option['prefix'] || ''
- id = session.session_id
- md5 = Digest::MD5.hexdigest(id)[0,16]
- path = dir+"/"+prefix+md5
- path.untaint
- if File::exist?(path)
- @hash = nil
- else
- unless session.new_session
- raise CGI::Session::NoSession, "uninitialized session"
- end
- @hash = {}
- end
- @p = ::PStore.new(path)
- @p.transaction do |p|
- File.chmod(0600, p.path)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb
index 5d6988e1b1..7e5e95e135 100644
--- a/actionpack/lib/action_controller/cgi_process.rb
+++ b/actionpack/lib/action_controller/cgi_process.rb
@@ -61,7 +61,7 @@ module ActionController #:nodoc:
class CgiRequest #:nodoc:
DEFAULT_SESSION_OPTIONS = {
- :database_manager => CGI::Session::CookieStore,
+ :database_manager => nil,
:prefix => "ruby_sess.",
:session_path => "/",
:session_key => "_session_id",
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 203f6b1683..c9a9264b6d 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -45,8 +45,10 @@ module ActionController
end
cattr_accessor :middleware
- self.middleware = MiddlewareStack.new
- self.middleware.use "ActionController::Failsafe"
+ self.middleware = MiddlewareStack.new do |middleware|
+ middleware.use "ActionController::Failsafe"
+ middleware.use "ActionController::SessionManagement::Middleware"
+ end
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
@@ -89,7 +91,7 @@ module ActionController
def _call(env)
@request = RackRequest.new(env)
- @response = RackResponse.new(@request)
+ @response = RackResponse.new
dispatch
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 212a293da0..1b0543033b 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -489,8 +489,8 @@ EOF
# By default, a single session is automatically created for you, but you
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
- def open_session
- application = ActionController::Dispatcher.new
+ def open_session(application = nil)
+ application ||= ActionController::Dispatcher.new
session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb
index 1864bed23a..a6597a6fec 100644
--- a/actionpack/lib/action_controller/middleware_stack.rb
+++ b/actionpack/lib/action_controller/middleware_stack.rb
@@ -4,7 +4,12 @@ module ActionController
attr_reader :klass, :args, :block
def initialize(klass, *args, &block)
- @klass = klass.is_a?(Class) ? klass : klass.to_s.constantize
+ if klass.is_a?(Class)
+ @klass = klass
+ else
+ @klass = klass.to_s.constantize
+ end
+
@args = args
@block = block
end
@@ -21,18 +26,28 @@ module ActionController
end
def inspect
- str = @klass.to_s
- @args.each { |arg| str += ", #{arg.inspect}" }
+ str = klass.to_s
+ args.each { |arg| str += ", #{arg.inspect}" }
str
end
def build(app)
- klass.new(app, *args, &block)
+ if block
+ klass.new(app, *args, &block)
+ else
+ klass.new(app, *args)
+ end
end
end
+ def initialize(*args, &block)
+ super(*args)
+ block.call(self) if block_given?
+ end
+
def use(*args, &block)
- push(Middleware.new(*args, &block))
+ middleware = Middleware.new(*args, &block)
+ push(middleware)
end
def build(app)
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 568f893c6c..e783839f34 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -3,24 +3,12 @@ require 'action_controller/cgi_ext'
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
attr_accessor :session_options
- attr_reader :cgi
class SessionFixationAttempt < StandardError #:nodoc:
end
- DEFAULT_SESSION_OPTIONS = {
- :database_manager => CGI::Session::CookieStore, # store data in cookie
- :prefix => "ruby_sess.", # prefix session file names
- :session_path => "/", # available to all paths in app
- :session_key => "_session_id",
- :cookie_only => true,
- :session_http_only=> true
- }
-
- def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
- @session_options = session_options
+ def initialize(env)
@env = env
- @cgi = CGIWrapper.new(self)
super()
end
@@ -66,87 +54,25 @@ module ActionController #:nodoc:
@env['SERVER_SOFTWARE'].split("/").first
end
- def session
- unless defined?(@session)
- if @session_options == false
- @session = Hash.new
- else
- stale_session_check! do
- if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
- raise SessionFixationAttempt
- end
- 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
- @session = CGI::Session.new(@cgi, session_options_with_string_keys)
- else
- raise ArgumentError, "Invalid new_session option: #{value}"
- end
- @session['__valid_session']
- end
- end
- end
- @session
+ def session_options
+ @env['rack.session.options'] ||= {}
end
- def reset_session
- @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
- @session = new_session
+ def session_options=(options)
+ @env['rack.session.options'] = options
end
- private
- # Delete an old session if it exists then create a new one.
- def new_session
- if @session_options == false
- Hash.new
- 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
- end
-
- def cookie_only?
- session_options_with_string_keys['cookie_only']
- end
-
- def stale_session_check!
- yield
- rescue ArgumentError => argument_error
- if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
- begin
- # Note that the regexp does not allow $1 to end with a ':'
- $1.constantize
- rescue LoadError, NameError => const_error
- raise ActionController::SessionRestoreError, <<-end_msg
-Session contains objects whose class definition isn\'t available.
-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
+ def session
+ @env['rack.session'] ||= {}
+ end
- def session_options_with_string_keys
- @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
- end
+ def reset_session
+ @env['rack.session'] = {}
+ end
end
class RackResponse < AbstractResponse #:nodoc:
- def initialize(request)
- @cgi = request.cgi
+ def initialize
@writer = lambda { |x| @body << x }
@block = nil
super()
@@ -247,49 +173,8 @@ end_msg
else cookies << cookie.to_s
end
- @cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
-
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
end
end
end
-
- class CGIWrapper < ::CGI
- attr_reader :output_cookies
-
- def initialize(request, *args)
- @request = request
- @args = *args
- @input = request.body
-
- super *args
- end
-
- def params
- @params ||= @request.params
- end
-
- def cookies
- @request.cookies
- end
-
- def query_string
- @request.query_string
- end
-
- # Used to wrap the normal args variable used inside CGI.
- def args
- @args
- end
-
- # Used to wrap the normal env_table variable used inside CGI.
- def env_table
- @request.env
- end
-
- # Used to wrap the normal stdinput variable used inside CGI.
- def stdinput
- @input
- end
- end
end
diff --git a/actionpack/lib/action_controller/session/abstract_store.rb b/actionpack/lib/action_controller/session/abstract_store.rb
new file mode 100644
index 0000000000..c6dd865fad
--- /dev/null
+++ b/actionpack/lib/action_controller/session/abstract_store.rb
@@ -0,0 +1,131 @@
+require 'rack/utils'
+
+module ActionController
+ module Session
+ class AbstractStore
+ ENV_SESSION_KEY = 'rack.session'.freeze
+ ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
+
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
+ SET_COOKIE = 'Set-Cookie'.freeze
+
+ class SessionHash < Hash
+ def initialize(by, env)
+ @by = by
+ @env = env
+ @loaded = false
+ end
+
+ def id
+ load! unless @loaded
+ @id
+ end
+
+ def [](key)
+ load! unless @loaded
+ super
+ end
+
+ def []=(key, value)
+ load! unless @loaded
+ super
+ end
+
+ def to_hash
+ h = {}.replace(self)
+ h.delete_if { |k,v| v.nil? }
+ h
+ end
+
+ private
+ def load!
+ @id, session = @by.send(:load_session, @env)
+ replace(session)
+ @loaded = true
+ end
+ end
+
+ DEFAULT_OPTIONS = {
+ :key => 'rack.session',
+ :path => '/',
+ :domain => nil,
+ :expire_after => nil,
+ :secure => false,
+ :httponly => true,
+ :cookie_only => true
+ }
+
+ def initialize(app, options = {})
+ @app = app
+ @default_options = DEFAULT_OPTIONS.merge(options)
+ @key = @default_options[:key]
+ @cookie_only = @default_options[:cookie_only]
+ end
+
+ def call(env)
+ session = SessionHash.new(self, env)
+ original_session = session.dup
+
+ env[ENV_SESSION_KEY] = session
+ env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
+
+ response = @app.call(env)
+
+ session = env[ENV_SESSION_KEY]
+ unless session == original_session
+ options = env[ENV_SESSION_OPTIONS_KEY]
+ sid = session.id
+
+ unless set_session(env, sid, session.to_hash)
+ return response
+ end
+
+ cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
+ cookie << "; domain=#{options[:domain]}" if options[:domain]
+ cookie << "; path=#{options[:path]}" if options[:path]
+ if options[:expire_after]
+ expiry = Time.now + options[:expire_after]
+ cookie << "; expires=#{expiry.httpdate}"
+ end
+ cookie << "; Secure" if options[:secure]
+ cookie << "; HttpOnly" if options[:httponly]
+
+ headers = response[1]
+ case a = headers[SET_COOKIE]
+ when Array
+ a << cookie
+ when String
+ headers[SET_COOKIE] = [a, cookie]
+ when nil
+ headers[SET_COOKIE] = cookie
+ end
+ end
+
+ response
+ end
+
+ private
+ def generate_sid
+ ActiveSupport::SecureRandom.hex(16)
+ end
+
+ def load_session(env)
+ request = Rack::Request.new(env)
+ sid = request.cookies[@key]
+ unless @cookie_only
+ sid ||= request.params[@key]
+ end
+ sid, session = get_session(env, sid)
+ [sid, session]
+ end
+
+ def get_session(env, sid)
+ raise '#get_session needs to be implemented.'
+ end
+
+ def set_session(env, sid, session_data)
+ raise '#set_session needs to be implemented.'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/session/active_record_store.rb b/actionpack/lib/action_controller/session/active_record_store.rb
deleted file mode 100644
index fadf2a6b32..0000000000
--- a/actionpack/lib/action_controller/session/active_record_store.rb
+++ /dev/null
@@ -1,350 +0,0 @@
-require 'cgi'
-require 'cgi/session'
-require 'digest/md5'
-
-class CGI
- class Session
- attr_reader :data
-
- # Return this session's underlying Session instance. Useful for the DB-backed session stores.
- def model
- @dbman.model if @dbman
- end
-
-
- # A session store backed by an Active Record class. A default class is
- # provided, but any object duck-typing to an Active Record Session class
- # with text +session_id+ and +data+ attributes is sufficient.
- #
- # The default assumes a +sessions+ tables with columns:
- # +id+ (numeric primary key),
- # +session_id+ (text, or longtext if your session data exceeds 65K), and
- # +data+ (text or longtext; careful if your session data exceeds 65KB).
- # The +session_id+ column should always be indexed for speedy lookups.
- # Session data is marshaled to the +data+ column in Base64 format.
- # If the data you write is larger than the column's size limit,
- # ActionController::SessionOverflowError will be raised.
- #
- # You may configure the table name, primary key, and data column.
- # For example, at the end of <tt>config/environment.rb</tt>:
- # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
- # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
- # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
- # Note that setting the primary key to the +session_id+ frees you from
- # having a separate +id+ column if you don't want it. However, you must
- # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
- # on ApplicationController is a good place.
- #
- # Since the default class is a simple Active Record, you get timestamps
- # for free if you add +created_at+ and +updated_at+ datetime columns to
- # the +sessions+ table, making periodic session expiration a snap.
- #
- # You may provide your own session class implementation, whether a
- # feature-packed Active Record or a bare-metal high-performance SQL
- # store, by setting
- # CGI::Session::ActiveRecordStore.session_class = MySessionClass
- # You must implement these methods:
- # self.find_by_session_id(session_id)
- # initialize(hash_of_session_id_and_data)
- # attr_reader :session_id
- # attr_accessor :data
- # save
- # destroy
- #
- # The example SqlBypass class is a generic SQL session store. You may
- # use it as a basis for high-performance database-specific stores.
- class ActiveRecordStore
- # The default Active Record class.
- class Session < ActiveRecord::Base
- ##
- # :singleton-method:
- # Customizable data column name. Defaults to 'data'.
- cattr_accessor :data_column_name
- self.data_column_name = 'data'
-
- before_save :marshal_data!
- before_save :raise_on_session_data_overflow!
-
- class << self
- # Don't try to reload ARStore::Session in dev mode.
- def reloadable? #:nodoc:
- false
- end
-
- def data_column_size_limit
- @data_column_size_limit ||= columns_hash[@@data_column_name].limit
- end
-
- # Hook to set up sessid compatibility.
- def find_by_session_id(session_id)
- setup_sessid_compatibility!
- find_by_session_id(session_id)
- end
-
- def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
- def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
-
- def create_table!
- connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{connection.quote_column_name('session_id')} TEXT UNIQUE,
- #{connection.quote_column_name(@@data_column_name)} TEXT(255)
- )
- end_sql
- end
-
- def drop_table!
- connection.execute "DROP TABLE #{table_name}"
- end
-
- private
- # Compatibility with tables using sessid instead of session_id.
- def setup_sessid_compatibility!
- # Reset column info since it may be stale.
- reset_column_information
- if columns_hash['sessid']
- def self.find_by_session_id(*args)
- find_by_sessid(*args)
- end
-
- define_method(:session_id) { sessid }
- define_method(:session_id=) { |session_id| self.sessid = session_id }
- else
- def self.find_by_session_id(session_id)
- find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
- end
- end
- end
- end
-
- # Lazy-unmarshal session state.
- def data
- @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
- end
-
- attr_writer :data
-
- # Has the session been loaded yet?
- def loaded?
- !! @data
- end
-
- private
-
- def marshal_data!
- return false if !loaded?
- write_attribute(@@data_column_name, self.class.marshal(self.data))
- end
-
- # Ensures that the data about to be stored in the database is not
- # larger than the data storage column. Raises
- # ActionController::SessionOverflowError.
- def raise_on_session_data_overflow!
- return false if !loaded?
- limit = self.class.data_column_size_limit
- if loaded? and limit and read_attribute(@@data_column_name).size > limit
- raise ActionController::SessionOverflowError
- end
- end
- end
-
- # A barebones session store which duck-types with the default session
- # store but bypasses Active Record and issues SQL directly. This is
- # an example session model class meant as a basis for your own classes.
- #
- # The database connection, table name, and session id and data columns
- # are configurable class attributes. Marshaling and unmarshaling
- # are implemented as class methods that you may override. By default,
- # marshaling data is
- #
- # ActiveSupport::Base64.encode64(Marshal.dump(data))
- #
- # and unmarshaling data is
- #
- # Marshal.load(ActiveSupport::Base64.decode64(data))
- #
- # This marshaling behavior is intended to store the widest range of
- # binary session data in a +text+ column. For higher performance,
- # store in a +blob+ column instead and forgo the Base64 encoding.
- class SqlBypass
- ##
- # :singleton-method:
- # Use the ActiveRecord::Base.connection by default.
- cattr_accessor :connection
-
- ##
- # :singleton-method:
- # The table name defaults to 'sessions'.
- cattr_accessor :table_name
- @@table_name = 'sessions'
-
- ##
- # :singleton-method:
- # The session id field defaults to 'session_id'.
- cattr_accessor :session_id_column
- @@session_id_column = 'session_id'
-
- ##
- # :singleton-method:
- # The data field defaults to 'data'.
- cattr_accessor :data_column
- @@data_column = 'data'
-
- class << self
-
- def connection
- @@connection ||= ActiveRecord::Base.connection
- end
-
- # Look up a session by id and unmarshal its data if found.
- def find_by_session_id(session_id)
- if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
- new(:session_id => session_id, :marshaled_data => record['data'])
- end
- end
-
- def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
- def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
-
- def create_table!
- @@connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
- #{@@connection.quote_column_name(data_column)} TEXT
- )
- end_sql
- end
-
- def drop_table!
- @@connection.execute "DROP TABLE #{table_name}"
- end
- end
-
- attr_reader :session_id
- attr_writer :data
-
- # Look for normal and marshaled data, self.find_by_session_id's way of
- # telling us to postpone unmarshaling until the data is requested.
- # We need to handle a normal data attribute in case of a new record.
- def initialize(attributes)
- @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
- @new_record = @marshaled_data.nil?
- end
-
- def new_record?
- @new_record
- end
-
- # Lazy-unmarshal session state.
- def data
- unless @data
- if @marshaled_data
- @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
- else
- @data = {}
- end
- end
- @data
- end
-
- def loaded?
- !! @data
- end
-
- def save
- return false if !loaded?
- marshaled_data = self.class.marshal(data)
-
- if @new_record
- @new_record = false
- @@connection.update <<-end_sql, 'Create session'
- INSERT INTO #{@@table_name} (
- #{@@connection.quote_column_name(@@session_id_column)},
- #{@@connection.quote_column_name(@@data_column)} )
- VALUES (
- #{@@connection.quote(session_id)},
- #{@@connection.quote(marshaled_data)} )
- end_sql
- else
- @@connection.update <<-end_sql, 'Update session'
- UPDATE #{@@table_name}
- SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
- end_sql
- end
- end
-
- def destroy
- unless @new_record
- @@connection.delete <<-end_sql, 'Destroy session'
- DELETE FROM #{@@table_name}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
- end_sql
- end
- end
- end
-
-
- # The class used for session storage. Defaults to
- # CGI::Session::ActiveRecordStore::Session.
- cattr_accessor :session_class
- self.session_class = Session
-
- # Find or instantiate a session given a CGI::Session.
- def initialize(session, option = nil)
- session_id = session.session_id
- unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
- unless session.new_session
- raise CGI::Session::NoSession, 'uninitialized session'
- end
- @session = @@session_class.new(:session_id => session_id, :data => {})
- # session saving can be lazy again, because of improved component implementation
- # therefore next line gets commented out:
- # @session.save
- end
- end
-
- # Access the underlying session model.
- def model
- @session
- end
-
- # Restore session state. The session model handles unmarshaling.
- def restore
- if @session
- @session.data
- end
- end
-
- # Save session store.
- def update
- if @session
- ActiveRecord::Base.silence { @session.save }
- end
- end
-
- # Save and close the session store.
- def close
- if @session
- update
- @session = nil
- end
- end
-
- # Delete and close the session store.
- def delete
- if @session
- ActiveRecord::Base.silence { @session.destroy }
- @session = nil
- end
- end
-
- protected
- def logger
- ActionController::Base.logger rescue nil
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index ea0ea4f841..f4089bfa8b 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -1,163 +1,200 @@
-require 'cgi'
-require 'cgi/session'
-
-# This cookie-based session store is the Rails default. Sessions typically
-# contain at most a user_id and flash message; both fit within the 4K cookie
-# size limit. Cookie-based sessions are dramatically faster than the
-# alternatives.
-#
-# If you have more than 4K of session data or don't want your data to be
-# visible to the user, pick another session store.
-#
-# CookieOverflow is raised if you attempt to store more than 4K of data.
-# TamperedWithCookie is raised if the data integrity check fails.
-#
-# A message digest is included with the cookie to ensure data integrity:
-# a user cannot alter his +user_id+ without knowing the secret key included in
-# the hash. New apps are generated with a pregenerated secret in
-# config/environment.rb. Set your own for old apps you're upgrading.
-#
-# Session options:
-#
-# * <tt>:secret</tt>: An application-wide key string or block returning a string
-# called per generated digest. The block is called with the CGI::Session
-# instance as an argument. It's important that the secret is not vulnerable to
-# a dictionary attack. Therefore, you should choose a secret consisting of
-# random numbers and letters and more than 30 characters. Examples:
-#
-# :secret => '449fe2e7daee471bffae2fd8dc02313d'
-# :secret => Proc.new { User.current_user.secret_key }
-#
-# * <tt>:digest</tt>: The message digest algorithm used to verify session
-# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
-# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
-#
-# To generate a secret key for an existing application, run
-# "rake secret" and set the key in config/environment.rb.
-#
-# Note that changing digest or secret invalidates all existing sessions!
-class CGI::Session::CookieStore
- # Cookies can typically store 4096 bytes.
- MAX = 4096
- SECRET_MIN_LENGTH = 30 # characters
-
- # Raised when storing more than 4K of session data.
- class CookieOverflow < StandardError; end
-
- # Raised when the cookie fails its integrity check.
- class TamperedWithCookie < StandardError; end
-
- # Called from CGI::Session only.
- def initialize(session, options = {})
- # The session_key option is required.
- if options['session_key'].blank?
- raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
- end
+module ActionController
+ module Session
+ # This cookie-based session store is the Rails default. Sessions typically
+ # contain at most a user_id and flash message; both fit within the 4K cookie
+ # size limit. Cookie-based sessions are dramatically faster than the
+ # alternatives.
+ #
+ # If you have more than 4K of session data or don't want your data to be
+ # visible to the user, pick another session store.
+ #
+ # CookieOverflow is raised if you attempt to store more than 4K of data.
+ #
+ # A message digest is included with the cookie to ensure data integrity:
+ # a user cannot alter his +user_id+ without knowing the secret key
+ # included in the hash. New apps are generated with a pregenerated secret
+ # in config/environment.rb. Set your own for old apps you're upgrading.
+ #
+ # Session options:
+ #
+ # * <tt>:secret</tt>: An application-wide key string or block returning a
+ # string called per generated digest. The block is called with the
+ # CGI::Session instance as an argument. It's important that the secret
+ # is not vulnerable to a dictionary attack. Therefore, you should choose
+ # a secret consisting of random numbers and letters and more than 30
+ # characters. Examples:
+ #
+ # :secret => '449fe2e7daee471bffae2fd8dc02313d'
+ # :secret => Proc.new { User.current_user.secret_key }
+ #
+ # * <tt>:digest</tt>: The message digest algorithm used to verify session
+ # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
+ # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+ #
+ # To generate a secret key for an existing application, run
+ # "rake secret" and set the key in config/environment.rb.
+ #
+ # Note that changing digest or secret invalidates all existing sessions!
+ class CookieStore
+ # Cookies can typically store 4096 bytes.
+ MAX = 4096
+ SECRET_MIN_LENGTH = 30 # characters
+
+ DEFAULT_OPTIONS = {
+ :domain => nil,
+ :path => "/",
+ :expire_after => nil
+ }.freeze
+
+ ENV_SESSION_KEY = "rack.session".freeze
+ ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
+ HTTP_SET_COOKIE = "Set-Cookie".freeze
+
+ # Raised when storing more than 4K of session data.
+ class CookieOverflow < StandardError; end
+
+ def initialize(app, options = {})
+ options = options.dup
+
+ @app = app
+
+ # The session_key option is required.
+ ensure_session_key(options[:key])
+ @key = options.delete(:key).freeze
+
+ # The secret option is required.
+ ensure_secret_secure(options[:secret])
+ @secret = options.delete(:secret).freeze
+
+ @digest = options.delete(:digest) || 'SHA1'
+ @verifier = verifier_for(@secret, @digest)
+
+ @default_options = DEFAULT_OPTIONS.merge(options).freeze
+
+ freeze
+ end
- # The secret option is required.
- ensure_secret_secure(options['secret'])
-
- # Keep the session and its secret on hand so we can read and write cookies.
- @session, @secret = session, options['secret']
-
- # Message digest defaults to SHA1.
- @digest = options['digest'] || 'SHA1'
-
- # Default cookie options derived from session settings.
- @cookie_options = {
- 'name' => options['session_key'],
- 'path' => options['session_path'],
- 'domain' => options['session_domain'],
- 'expires' => options['session_expires'],
- 'secure' => options['session_secure'],
- 'http_only' => options['session_http_only']
- }
-
- # Set no_hidden and no_cookies since the session id is unused and we
- # set our own data cookie.
- options['no_hidden'] = true
- options['no_cookies'] = true
- end
+ class SessionHash < AbstractStore::SessionHash
+ private
+ def load!
+ session = @by.send(:load_session, @env)
+ replace(session)
+ @loaded = true
+ end
+ end
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- # There's no way we can do this check if they've provided a proc for the
- # secret.
- return true if secret.is_a?(Proc)
+ def call(env)
+ session_data = SessionHash.new(self, env)
+ original_value = session_data.dup
- if secret.blank?
- raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
- end
+ env[ENV_SESSION_KEY] = session_data
+ env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
- end
- end
+ status, headers, body = @app.call(env)
- # Restore session data from the cookie.
- def restore
- @original = read_cookie
- @data = unmarshal(@original) || {}
- end
+ unless env[ENV_SESSION_KEY] == original_value
+ session_data = marshal(env[ENV_SESSION_KEY].to_hash)
- # Wait until close to write the session data cookie.
- def update; end
+ raise CookieOverflow if session_data.size > MAX
- # Write the session data cookie if it was loaded and has changed.
- def close
- if defined?(@data) && !@data.blank?
- updated = marshal(@data)
- raise CookieOverflow if updated.size > MAX
- write_cookie('value' => updated) unless updated == @original
- end
- end
+ options = env[ENV_SESSION_OPTIONS_KEY]
+ cookie = Hash.new
+ cookie[:value] = session_data
+ unless options[:expire_after].nil?
+ cookie[:expires] = Time.now + options[:expire_after]
+ end
- # Delete the session data by setting an expired cookie with no data.
- def delete
- @data = nil
- clear_old_cookie_value
- write_cookie('value' => nil, 'expires' => 1.year.ago)
- end
+ cookie = build_cookie(@key, cookie.merge(options))
+ case headers[HTTP_SET_COOKIE]
+ when Array
+ headers[HTTP_SET_COOKIE] << cookie
+ when String
+ headers[HTTP_SET_COOKIE] = [headers[HTTP_SET_COOKIE], cookie]
+ when nil
+ headers[HTTP_SET_COOKIE] = cookie
+ end
+ end
- private
- # Marshal a session hash into safe cookie data. Include an integrity hash.
- def marshal(session)
- verifier.generate(session)
- end
-
- # Unmarshal cookie data to a hash and verify its integrity.
- def unmarshal(cookie)
- if cookie
- verifier.verify(cookie)
+ [status, headers, body]
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- delete
- raise TamperedWithCookie
- end
-
- # Read the session data cookie.
- def read_cookie
- @session.cgi.cookies[@cookie_options['name']].first
- end
- # CGI likes to make you hack.
- def write_cookie(options)
- cookie = CGI::Cookie.new(@cookie_options.merge(options))
- @session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
- end
-
- # Clear cookie value so subsequent new_session doesn't reload old data.
- def clear_old_cookie_value
- @session.cgi.cookies[@cookie_options['name']].clear
- end
-
- def verifier
- if @secret.respond_to?(:call)
- key = @secret.call
- else
- key = @secret
- end
- ActiveSupport::MessageVerifier.new(key, @digest)
+ private
+ # Should be in Rack::Utils soon
+ def build_cookie(key, value)
+ case value
+ when Hash
+ domain = "; domain=" + value[:domain] if value[:domain]
+ path = "; path=" + value[:path] if value[:path]
+ # According to RFC 2109, we need dashes here.
+ # N.B.: cgi.rb uses spaces...
+ expires = "; expires=" + value[:expires].clone.gmtime.
+ strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
+ secure = "; secure" if value[:secure]
+ httponly = "; httponly" if value[:httponly]
+ value = value[:value]
+ end
+ value = [value] unless Array === value
+ cookie = Rack::Utils.escape(key) + "=" +
+ value.map { |v| Rack::Utils.escape(v) }.join("&") +
+ "#{domain}#{path}#{expires}#{secure}#{httponly}"
+ end
+
+ def load_session(env)
+ request = Rack::Request.new(env)
+ session_data = request.cookies[@key]
+ unmarshal(session_data) || {}
+ end
+
+ # Marshal a session hash into safe cookie data. Include an integrity hash.
+ def marshal(session)
+ @verifier.generate(session)
+ end
+
+ # Unmarshal cookie data to a hash and verify its integrity.
+ def unmarshal(cookie)
+ @verifier.verify(cookie) if cookie
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+
+ def ensure_session_key(key)
+ if key.blank?
+ raise ArgumentError, 'A session_key is required to write a ' +
+ 'cookie containing the session data. Use ' +
+ 'config.action_controller.session = { :session_key => ' +
+ '"_myapp_session", :secret => "some secret phrase" } in ' +
+ 'config/environment.rb'
+ end
+ end
+
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # secret they've provided is at least 30 characters in length.
+ def ensure_secret_secure(secret)
+ # There's no way we can do this check if they've provided a proc for the
+ # secret.
+ return true if secret.is_a?(Proc)
+
+ if secret.blank?
+ raise ArgumentError, "A secret is required to generate an " +
+ "integrity hash for cookie session data. Use " +
+ "config.action_controller.session = { :session_key => " +
+ "\"_myapp_session\", :secret => \"some secret phrase of at " +
+ "least #{SECRET_MIN_LENGTH} characters\" } " +
+ "in config/environment.rb"
+ end
+
+ if secret.length < SECRET_MIN_LENGTH
+ raise ArgumentError, "Secret should be something secure, " +
+ "like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
+ "provided, \"#{secret}\", is shorter than the minimum length " +
+ "of #{SECRET_MIN_LENGTH} characters"
+ end
+ end
+
+ def verifier_for(secret, digest)
+ key = secret.respond_to?(:call) ? secret.call : secret
+ ActiveSupport::MessageVerifier.new(key, digest)
+ end
end
+ end
end
diff --git a/actionpack/lib/action_controller/session/drb_server.rb b/actionpack/lib/action_controller/session/drb_server.rb
deleted file mode 100755
index 2caa27f62a..0000000000
--- a/actionpack/lib/action_controller/session/drb_server.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env ruby
-
-# This is a really simple session storage daemon, basically just a hash,
-# which is enabled for DRb access.
-
-require 'drb'
-
-session_hash = Hash.new
-session_hash.instance_eval { @mutex = Mutex.new }
-
-class <<session_hash
- def []=(key, value)
- @mutex.synchronize do
- super(key, value)
- end
- end
-
- def [](key)
- @mutex.synchronize do
- super(key)
- end
- end
-
- def delete(key)
- @mutex.synchronize do
- super(key)
- end
- end
-end
-
-DRb.start_service('druby://127.0.0.1:9192', session_hash)
-DRb.thread.join
diff --git a/actionpack/lib/action_controller/session/drb_store.rb b/actionpack/lib/action_controller/session/drb_store.rb
deleted file mode 100644
index 4feb2636e7..0000000000
--- a/actionpack/lib/action_controller/session/drb_store.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'cgi'
-require 'cgi/session'
-require 'drb'
-
-class CGI #:nodoc:all
- class Session
- class DRbStore
- @@session_data = DRbObject.new(nil, 'druby://localhost:9192')
-
- def initialize(session, option=nil)
- @session_id = session.session_id
- end
-
- def restore
- @h = @@session_data[@session_id] || {}
- end
-
- def update
- @@session_data[@session_id] = @h
- end
-
- def close
- update
- end
-
- def delete
- @@session_data.delete(@session_id)
- end
-
- def data
- @@session_data[@session_id]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/session/mem_cache_store.rb b/actionpack/lib/action_controller/session/mem_cache_store.rb
index 2f08af663d..f745715a97 100644
--- a/actionpack/lib/action_controller/session/mem_cache_store.rb
+++ b/actionpack/lib/action_controller/session/mem_cache_store.rb
@@ -1,95 +1,48 @@
-# cgi/session/memcached.rb - persistent storage of marshalled session data
-#
-# == Overview
-#
-# This file provides the CGI::Session::MemCache class, which builds
-# persistence of storage data on top of the MemCache library. See
-# cgi/session.rb for more details on session storage managers.
-#
-
begin
- require 'cgi/session'
require_library_or_gem 'memcache'
- class CGI
- class Session
- # MemCache-based session storage class.
- #
- # This builds upon the top-level MemCache class provided by the
- # library file memcache.rb. Session data is marshalled and stored
- # in a memcached cache.
- class MemCacheStore
- def check_id(id) #:nodoc:#
- /[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
- end
+ module ActionController
+ module Session
+ class MemCacheStore < AbstractStore
+ def initialize(app, options = {})
+ # Support old :expires option
+ options[:expire_after] ||= options[:expires]
- # Create a new CGI::Session::MemCache instance
- #
- # This constructor is used internally by CGI::Session. The
- # user does not generally need to call it directly.
- #
- # +session+ is the session for which this instance is being
- # created. The session id must only contain alphanumeric
- # characters; automatically generated session ids observe
- # this requirement.
- #
- # +options+ is a hash of options for the initializer. The
- # following options are recognized:
- #
- # cache:: an instance of a MemCache client to use as the
- # session cache.
- #
- # expires:: an expiry time value to use for session entries in
- # the session cache. +expires+ is interpreted in seconds
- # relative to the current time if it’s less than 60*60*24*30
- # (30 days), or as an absolute Unix time (e.g., Time#to_i) if
- # greater. If +expires+ is +0+, or not passed on +options+,
- # the entry will never expire.
- #
- # This session's memcache entry will be created if it does
- # not exist, or retrieved if it does.
- def initialize(session, options = {})
- id = session.session_id
- unless check_id(id)
- raise ArgumentError, "session_id '%s' is invalid" % id
- end
- @cache = options['cache'] || MemCache.new('localhost')
- @expires = options['expires'] || 0
- @session_key = "session:#{id}"
- @session_data = {}
- # Add this key to the store if haven't done so yet
- unless @cache.get(@session_key)
- @cache.add(@session_key, @session_data, @expires)
- end
- end
+ super
- # Restore session state from the session's memcache entry.
- #
- # Returns the session state as a hash.
- def restore
- @session_data = @cache[@session_key] || {}
- end
+ @default_options = {
+ :namespace => 'rack:session',
+ :memcache_server => 'localhost:11211'
+ }.merge(@default_options)
- # Save session state to the session's memcache entry.
- def update
- @cache.set(@session_key, @session_data, @expires)
- end
-
- # Update and close the session's memcache entry.
- def close
- update
- end
+ @pool = options[:cache] || MemCache.new(@default_options[:memcache_server], @default_options)
+ unless @pool.servers.any? { |s| s.alive? }
+ raise "#{self} unable to find server during initialization."
+ end
+ @mutex = Mutex.new
- # Delete the session's memcache entry.
- def delete
- @cache.delete(@session_key)
- @session_data = {}
- end
-
- def data
- @session_data
+ super
end
+ private
+ def get_session(env, sid)
+ sid ||= generate_sid
+ begin
+ session = @pool.get(sid) || {}
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED
+ session = {}
+ end
+ [sid, session]
+ end
+
+ def set_session(env, sid, session_data)
+ options = env['rack.session.options']
+ expiry = options[:expire_after] || 0
+ @pool.set(sid, session_data, expiry)
+ return true
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED
+ return false
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb
index 60a9aec39c..dd5001d328 100644
--- a/actionpack/lib/action_controller/session_management.rb
+++ b/actionpack/lib/action_controller/session_management.rb
@@ -3,8 +3,29 @@ module ActionController #:nodoc:
def self.included(base)
base.class_eval do
extend ClassMethods
- alias_method_chain :process, :session_management_support
- alias_method_chain :process_cleanup, :session_management_support
+ end
+ end
+
+ class Middleware
+ DEFAULT_OPTIONS = {
+ :path => "/",
+ :key => "_session_id",
+ :httponly => true,
+ }.freeze
+
+ def self.new(app)
+ cgi_options = ActionController::Base.session_options
+ options = cgi_options.symbolize_keys
+ options = DEFAULT_OPTIONS.merge(options)
+ options[:path] = options.delete(:session_path)
+ options[:key] = options.delete(:session_key)
+ options[:httponly] = options.delete(:session_http_only)
+
+ if store = ActionController::Base.session_store
+ store.new(app, options)
+ else # Sessions disabled
+ lambda { |env| app.call(env) }
+ end
end
end
@@ -12,144 +33,45 @@ module ActionController #:nodoc:
# Set the session store to be used for keeping the session data between requests.
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
- # <tt>:p_store</tt>, <tt>:drb_store</tt>, <tt>:mem_cache_store</tt>, or
- # <tt>:memory_store</tt>) or your own custom class.
+ # <tt>:mem_cache_store</tt>, or your own custom class.
def session_store=(store)
- ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
- store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
+ if store == :active_record_store
+ self.session_store = ActiveRecord::SessionStore
+ else
+ @@session_store = store.is_a?(Symbol) ?
+ Session.const_get(store.to_s.camelize) :
+ store
+ end
end
# Returns the session store class currently used.
def session_store
- ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
+ if defined? @@session_store
+ @@session_store
+ else
+ Session::CookieStore
+ end
+ end
+
+ def session=(options = {})
+ self.session_store = nil if options.delete(:disabled)
+ session_options.merge!(options)
end
# Returns the hash used to configure the session. Example use:
#
# ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
def session_options
- ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
- end
-
- # Specify how sessions ought to be managed for a subset of the actions on
- # the controller. Like filters, you can specify <tt>:only</tt> and
- # <tt>:except</tt> clauses to restrict the subset, otherwise options
- # apply to all actions on this controller.
- #
- # The session options are inheritable, as well, so if you specify them in
- # a parent controller, they apply to controllers that extend the parent.
- #
- # Usage:
- #
- # # turn off session management for all actions.
- # session :off
- #
- # # turn off session management for all actions _except_ foo and bar.
- # session :off, :except => %w(foo bar)
- #
- # # turn off session management for only the foo and bar actions.
- # session :off, :only => %w(foo bar)
- #
- # # the session will only work over HTTPS, but only for the foo action
- # session :only => :foo, :session_secure => true
- #
- # # the session by default uses HttpOnly sessions for security reasons.
- # # this can be switched off.
- # session :only => :foo, :session_http_only => false
- #
- # # the session will only be disabled for 'foo', and only if it is
- # # requested as a web service
- # session :off, :only => :foo,
- # :if => Proc.new { |req| req.parameters[:ws] }
- #
- # # the session will be disabled for non html/ajax requests
- # session :off,
- # :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
- #
- # # turn the session back on, useful when it was turned off in the
- # # application controller, and you need it on in another controller
- # session :on
- #
- # All session options described for ActionController::Base.process_cgi
- # are valid arguments.
- def session(*args)
- options = args.extract_options!
-
- options[:disabled] = false if args.delete(:on)
- options[:disabled] = true if !args.empty?
- options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
- options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
- if options[:only] && options[:except]
- raise ArgumentError, "only one of either :only or :except are allowed"
- end
-
- write_inheritable_array(:session_options, [options])
+ @session_options ||= {}
end
- # So we can declare session options in the Rails initializer.
- alias_method :session=, :session
-
- def cached_session_options #:nodoc:
- @session_options ||= read_inheritable_attribute(:session_options) || []
- end
-
- def session_options_for(request, action) #:nodoc:
- if (session_options = cached_session_options).empty?
- {}
- else
- options = {}
-
- action = action.to_s
- session_options.each do |opts|
- next if opts[:if] && !opts[:if].call(request)
- if opts[:only] && opts[:only].include?(action)
- options.merge!(opts)
- elsif opts[:except] && !opts[:except].include?(action)
- options.merge!(opts)
- elsif !opts[:only] && !opts[:except]
- options.merge!(opts)
- end
- end
-
- if options.empty? then options
- else
- options.delete :only
- options.delete :except
- options.delete :if
- options[:disabled] ? false : options
- end
- end
+ def session(*args)
+ ActiveSupport::Deprecation.warn(
+ "Disabling sessions for a single controller has been deprecated. " +
+ "Sessions are now lazy loaded. So if you don't access them, " +
+ "consider them off. You can still modify the session cookie " +
+ "options with request.session_options.", caller)
end
end
-
- def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
- set_session_options(request)
- process_without_session_management_support(request, response, method, *arguments)
- end
-
- private
- def set_session_options(request)
- request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
- end
-
- def process_cleanup_with_session_management_support
- clear_persistent_model_associations
- process_cleanup_without_session_management_support
- end
-
- # Clear cached associations in session data so they don't overflow
- # the database field. Only applies to ActiveRecordStore since there
- # is not a standard way to iterate over session data.
- def clear_persistent_model_associations #:doc:
- if defined?(@_session) && @_session.respond_to?(:data)
- session_data = @_session.data
-
- if session_data && session_data.respond_to?(:each_value)
- session_data.each_value do |obj|
- obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
- end
- end
- end
- end
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 93748638c3..8f4ca433c0 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -98,10 +98,6 @@ module ActionView #:nodoc:
end
private
- def valid_extension?(extension)
- Template.template_handler_extensions.include?(extension)
- end
-
def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
@@ -115,11 +111,11 @@ module ActionView #:nodoc:
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if valid_extension?(m[5]) # Multipart formats
+ if Template.valid_extension?(m[5]) # Multipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif valid_extension?(m[4]) # Single format
+ elsif Template.valid_extension?(m[4]) # Single format
[m[1], m[2], m[3], m[4]]
- elsif valid_extension?(m[3]) # No format
+ elsif Template.valid_extension?(m[3]) # No format
[m[1], m[2], nil, m[3]]
else # No extension
[m[1], m[2], m[3], nil]
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index d06ddd5fb5..c50a51b0d1 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -28,6 +28,10 @@ module ActionView #:nodoc:
@@template_handlers[extension.to_sym] = klass
end
+ def valid_extension?(extension)
+ template_handler_extensions.include?(extension) || init_path_for_extension(extension)
+ end
+
def template_handler_extensions
@@template_handlers.keys.map(&:to_s).sort
end
@@ -38,7 +42,26 @@ module ActionView #:nodoc:
end
def handler_class_for_extension(extension)
- (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
+ (extension && @@template_handlers[extension.to_sym] || autoload_handler_class(extension)) ||
+ @@default_template_handlers
end
+
+ private
+ def autoload_handler_class(extension)
+ return if Gem.loaded_specs[extension]
+ return unless init_path = init_path_for_extension(extension)
+ Gem.activate(extension)
+ load(init_path)
+ handler_class_for_extension(extension)
+ end
+
+ # Returns the path to the rails/init.rb file for the given extension,
+ # or nil if no gem provides it.
+ def init_path_for_extension(extension)
+ return unless spec = Gem.searcher.find(extension.to_s)
+ returning File.join(spec.full_gem_path, 'rails', 'init.rb') do |path|
+ return unless File.file?(path)
+ end
+ end
end
end