summaryrefslogtreecommitdiffstats
path: root/lib/zorglub/session.rb
blob: fc5fdbe4de7cf77687ebf9e89ff2a253ed06f821 (plain)
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
143
144
145
146
147
148
149
150
151
152
# -*- coding: UTF-8 -*-

require 'securerandom'

module Zorglub

    class Node

        @sessions = {}

        class << self
            attr_reader :sessions
        end

        def session
            @session ||= SessionHash.new @request, @response, Node.sessions, app.opt(:session_options)
        end
    end

    class SessionHash < Hash

        def initialize req, resp, sessions, options
            @request = req
            @response = resp
            @sessions = sessions
            @sid = nil
            @options = options
            super()
        end

        def [] key
            load_data!
            super key
        end

        def has_key? key
            load_data!
            super key
        end
        alias :key? :has_key?
        alias :include? :has_key?

        def []= key, value
            load_data!
            super key, value
        end

        def clear
            load_data!
            # @response.delete_cookie @options[:key]
            # @sessions.delete @sid
            # @sid = nil
            super
        end

        def to_hash
            load_data!
            h = {}.replace(self)
            h.delete_if { |k,v| v.nil? }
            h
        end

        def update hash
            load_data!
            super stringify_keys(hash)
        end

        def delete key
            load_data!
            super key
        end

        def inspect
            if loaded?
                super
            else
                "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
            end
        end

        def exists?
            ( loaded? ? @sessions.has_key?(@sid) : false )
        end

        def loaded?
            not @sid.nil?
        end

        def empty?
            load_data!
            super
        end

        private

        def load_data!
            return if loaded?
            if @options[:enabled]
                sid = @request.cookies[@options[:key]]
                if sid.nil?
                    sid = generate_sid!
                    @response.set_cookie @options[:key], sid
                end
                replace @sessions[sid] ||={}
                @sessions[sid] = self
                @sid = sid
            end
        end

        def stringify_keys other
            hash = {}
            other.each do |key, value|
                hash[key] = value
            end
            hash
        end

        def generate_sid!
            begin sid = sid_algorithm end while @sessions.has_key? sid
            sid
        end

        begin
            require 'securerandom'
            # Using SecureRandom, optional length.
            # SecureRandom is available since Ruby 1.8.7.
            # For Ruby versions earlier than that, you can require the uuidtools gem,
            # which has a drop-in replacement for SecureRandom.
            def sid_algorithm; SecureRandom.hex(@options[:sid_len]); end
        rescue LoadError
            require 'openssl'
            # Using OpenSSL::Random for generation, this is comparable in performance
            # with stdlib SecureRandom and also allows for optional length, it should
            # have the same behaviour as the SecureRandom::hex method of the
            # uuidtools gem.
            def sid_algorithm
                OpenSSL::Random.random_bytes(@options[:sid_len] / 2).unpack('H*')[0]
            end
        rescue LoadError
            # Digest::SHA2::hexdigest produces a string of length 64, although
            # collisions are not very likely, the entropy is still very low and
            # length is not optional.
            #
            # Replacing it with OS-provided random data would take a lot of code and
            # won't be as cross-platform as Ruby.
            def sid_algorithm
                entropy = [ srand, rand, Time.now.to_f, rand, $$, rand, object_id ]
                Digest::SHA2.hexdigest(entropy.join)
            end
        end
    end
end