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) super() @by = by @env = env @loaded = false end def id load! unless @loaded @id end def session_id ActiveSupport::Deprecation.warn( "ActionController::Session::AbstractStore::SessionHash#session_id" + "has been deprecated.Please use #id instead.", caller) 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 def data ActiveSupport::Deprecation.warn( "ActionController::Session::AbstractStore::SessionHash#data" + "has been deprecated.Please use #to_hash instead.", caller) to_hash end private def loaded? @loaded end def load! @id, session = @by.send(:load_session, @env) replace(session) @loaded = true end end DEFAULT_OPTIONS = { :key => '_session_id', :path => '/', :domain => nil, :expire_after => nil, :secure => false, :httponly => true, :cookie_only => true } def initialize(app, options = {}) # Process legacy CGI options options = options.symbolize_keys if options.has_key?(:session_path) options[:path] = options.delete(:session_path) end if options.has_key?(:session_key) options[:key] = options.delete(:session_key) end if options.has_key?(:session_http_only) options[:httponly] = options.delete(:session_http_only) end @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) env[ENV_SESSION_KEY] = session env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup response = @app.call(env) session_data = env[ENV_SESSION_KEY] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) options = env[ENV_SESSION_OPTIONS_KEY] if session_data.is_a?(AbstractStore::SessionHash) sid = session_data.id else sid = generate_sid end unless set_session(env, sid, session_data.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