aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb
blob: 218144c17f21b02d3ac61323c8c56f8e3d6039c1 (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
141
142
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
# bugrep: Andreas Zehnder

require 'time'
require 'rack/request'
require 'rack/response'

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 are
      # required to be overwritten.
      #
      # All parameters are optional.
      # * :key determines the name of the cookie, by default it is
      #   'rack.session'
      # * :path, :domain, :expire_after, :secure, and :httponly set the related
      #   cookie options as by Rack::Response#add_cookie
      # * :defer will not set a cookie in the response.
      # * :renew (implementation dependent) will prompt the generation of a new
      #   session id, and migration of data to be referenced at the new id. If
      #   :defer is set, it will be overridden and the cookie will be set.
      # * :sidbits sets the number of bits in length that a generated session
      #   id will be.
      #
      # These options can be set on a per request basis, at the location of
      # env['rack.session.options']. Additionally the id of the session can be
      # found within the options hash at the key :id. It is highly not
      # recommended to change its value.
      #
      # Is Rack::Utils::Context compatible.

      class ID
        DEFAULT_OPTIONS = {
          :path =>          '/',
          :domain =>        nil,
          :expire_after =>  nil,
          :secure =>        false,
          :httponly =>      true,
          :defer =>         false,
          :renew =>         false,
          :sidbits =>       128
        }

        attr_reader :key, :default_options
        def initialize(app, options={})
          @app = app
          @key = options[:key] || "rack.session"
          @default_options = self.class::DEFAULT_OPTIONS.merge(options)
        end

        def call(env)
          context(env)
        end

        def context(env, app=@app)
          load_session(env)
          status, headers, body = app.call(env)
          commit_session(env, status, headers, body)
        end

        private

        # Generate a new session id using Ruby #rand.  The size of the
        # session id is controlled by the :sidbits option.
        # Monkey patch this to use custom methods for session id generation.

        def generate_sid
          "%0#{@default_options[:sidbits] / 4}x" %
            rand(2**@default_options[:sidbits] - 1)
        end

        # 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)
          request = Rack::Request.new(env)
          session_id = request.cookies[@key]

          begin
            session_id, session = get_session(env, session_id)
            env['rack.session'] = session
          rescue
            env['rack.session'] = Hash.new
          end

          env['rack.session.options'] = @default_options.
            merge(:id => session_id)
        end

        # Acquires the session from the environment and the session id from
        # the session options and passes them to #set_session. If successful
        # and the :defer option is not true, a cookie will be added to the
        # response with the session's id.

        def commit_session(env, status, headers, body)
          session = env['rack.session']
          options = env['rack.session.options']
          session_id = options[:id]

          if not session_id = set_session(env, session_id, session, options)
            env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
            [status, headers, body]
          elsif options[:defer] and not options[:renew]
            env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
            [status, headers, body]
          else
            cookie = Hash.new
            cookie[:value] = session_id
            cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
            response = Rack::Response.new(body, status, headers)
            response.set_cookie(@key, cookie.merge(options))
            response.to_a
          end
        end

        # All thread safety and session retrival proceedures should occur here.
        # Should return [session_id, session].
        # 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 not 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, session, options)
          raise '#set_session not implemented.'
        end
      end
    end
  end
end