aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb
blob: c220b2cb8d89e1495a614b6aacab92b9d0a60dca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
# bugrep: Andreas Zehnder

require 'rack/utils'
require 'time'

module Rack
  module Session
    module Abstract
      # ID sets up a basic framework for implementing an id based sessioning
      # service. Cookies sent to the client for maintaining sessions will only
      # contain an id reference. Only #get_session and #set_session should
      # need to be overwritten.
      #
      # All parameters are optional.
      # * :key determines the name of the cookie, by default it is
      #   'rack.session'
      # * :domain and :path set the related cookie values, by default
      #   domain is nil, and the path is '/'.
      # * :expire_after is the number of seconds in which the session
      #   cookie will expire. By default it is set not to provide any
      #   expiry time.
      class ID
        attr_reader :key
        DEFAULT_OPTIONS = {
          :key =>           'rack.session',
          :path =>          '/',
          :domain =>        nil,
          :expire_after =>  nil
        }

        def initialize(app, options={})
          @default_options = self.class::DEFAULT_OPTIONS.merge(options)
          @key = @default_options[:key]
          @default_context = context app
        end

        def call(env)
          @default_context.call(env)
        end

        def context(app)
          Rack::Utils::Context.new self, app do |env|
            load_session env
            response = app.call(env)
            commit_session env, response
            response
          end
        end

        private

        # Extracts the session id from provided cookies and passes it and the
        # environment to #get_session. It then sets the resulting session into
        # 'rack.session', and places options and session metadata into
        # 'rack.session.options'.
        def load_session(env)
          sid           = (env['HTTP_COOKIE']||'')[/#{@key}=([^,;]+)/,1]
          sid, session  = get_session(env, sid)
          unless session.is_a?(Hash)
            puts 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG
            raise TypeError, 'Session not a Hash'
          end

          options = @default_options.
            merge({ :id => sid, :by => self, :at => Time.now })

          env['rack.session'] = session
          env['rack.session.options'] = options

          return true
        end

        # Acquires the session from the environment and the session id from
        # the session options and passes them to #set_session. It then
        # proceeds to set a cookie up in the response with the session's id.
        def commit_session(env, response)
          unless response.is_a?(Array)
            puts 'Response: '+response.inspect if $DEBUG
            raise ArgumentError, 'Response is not an array.'
          end

          options = env['rack.session.options']
          unless options.is_a?(Hash)
            puts 'Options: '+options.inspect if $DEBUG
            raise TypeError, 'Options not a Hash'
          end

          sid, time, z = options.values_at(:id, :at, :by)
          unless self == z
            warn "#{self} not managing this session."
            return
          end

          unless env['rack.session'].is_a?(Hash)
            warn 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG
            raise TypeError, 'Session not a Hash'
          end

          unless set_session(env, sid)
            warn "Session not saved." if $DEBUG
            warn "#{env['rack.session'].inspect} has been lost."if $DEBUG
            return false
          end

          cookie = Utils.escape(@key)+'='+Utils.escape(sid)
          cookie<< "; domain=#{options[:domain]}" if options[:domain]
          cookie<< "; path=#{options[:path]}" if options[:path]
          if options[:expire_after]
            expiry = time + options[:expire_after]
            cookie<< "; expires=#{expiry.httpdate}"
          end

          case a = (h = response[1])['Set-Cookie']
          when Array then  a << cookie
          when String then h['Set-Cookie'] = [a, cookie]
          when nil then    h['Set-Cookie'] = cookie
          end

          return true
        end

        # Should return [session_id, session]. All thread safety and session
        # retrival proceedures should occur here.
        # If nil is provided as the session id, generation of a new valid id
        # should occur within.
        def get_session(env, sid)
          raise '#get_session needs to be implemented.'
        end

        # All thread safety and session storage proceedures should occur here.
        # Should return true or false dependant on whether or not the session
        # was saved or not.
        def set_session(env, sid)
          raise '#set_session needs to be implemented.'
        end
      end
    end
  end
end