DEFINITIONS
This source file includes following functions.
1 # Copyright (C) 2001 Yukihiro "Matz" Matsumoto
2 # Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
3 # Copyright (C) 2000 Information-technology Promotion Agency, Japan
4
5 require 'cgi'
6 require 'final'
7
8 class CGI
9 class Session
10
11 attr_reader :session_id
12
13 def Session::callback(dbman)
14 lambda{
15 dbman[0].close unless dbman.empty?
16 }
17 end
18
19 def Session::create_new_id
20 require 'digest/md5'
21 md5 = Digest::MD5::new
22 md5.update(String(Time::now))
23 md5.update(String(rand(0)))
24 md5.update(String($$))
25 md5.update('foobar')
26 md5.hexdigest[0,16]
27 end
28
29 def initialize(request, option={})
30 session_key = option['session_key'] || '_session_id'
31 id, = option['session_id']
32 unless id
33 if option['new_session']
34 id = Session::create_new_id
35 end
36 end
37 unless id
38 id, = request[session_key]
39 id = id.read if id.respond_to?(:read)
40 unless id
41 id, = request.cookies[session_key]
42 end
43 unless id
44 if option.key?('new_session') and not option['new_session']
45 raise ArgumentError, "session_key `%s' should be supplied"%session_key
46 end
47 id = Session::create_new_id
48 end
49 end
50 @session_id = id
51 dbman = option['database_manager'] || FileStore
52 @dbman = dbman::new(self, option)
53 request.instance_eval do
54 @output_hidden = {session_key => id}
55 @output_cookies = [
56 Cookie::new("name" => session_key,
57 "value" => id,
58 "expires" => option['session_expires'],
59 "domain" => option['session_domain'],
60 "secure" => option['session_secure'],
61 "path" => if option['session_path'] then
62 option['session_path']
63 elsif ENV["SCRIPT_NAME"] then
64 File::dirname(ENV["SCRIPT_NAME"])
65 else
66 ""
67 end)
68 ]
69 end
70 @dbprot = [@dbman]
71 ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
72 end
73
74 def [](key)
75 unless @data
76 @data = @dbman.restore
77 end
78 @data[key]
79 end
80
81 def []=(key, val)
82 unless @write_lock
83 @write_lock = true
84 end
85 unless @data
86 @data = @dbman.restore
87 end
88 @data[key] = val
89 end
90
91 def update
92 @dbman.update
93 end
94
95 def close
96 @dbman.close
97 @dbprot.clear
98 end
99
100 def delete
101 @dbman.delete
102 @dbprot.clear
103 end
104
105 class FileStore
106 def check_id(id)
107 /[^0-9a-zA-Z]/ =~ id.to_s ? false : true
108 end
109
110 def initialize(session, option={})
111 dir = option['tmpdir'] || ENV['TMP'] || '/tmp'
112 prefix = option['prefix'] || ''
113 id = session.session_id
114 unless check_id(id)
115 raise ArgumentError, "session_id `%s' is invalid" % id
116 end
117 path = dir+"/"+prefix+id
118 path.untaint
119 unless File::exist? path
120 @hash = {}
121 end
122 begin
123 @f = open(path, "r+")
124 rescue Errno::ENOENT
125 @f = open(path, "w+")
126 end
127 end
128
129 def restore
130 unless @hash
131 @hash = {}
132 @f.flock File::LOCK_EX
133 @f.rewind
134 for line in @f
135 line.chomp!
136 k, v = line.split('=',2)
137 @hash[CGI::unescape(k)] = CGI::unescape(v)
138 end
139 end
140 @hash
141 end
142
143 def update
144 return unless @hash
145 @f.rewind
146 for k,v in @hash
147 @f.printf "%s=%s\n", CGI::escape(k), CGI::escape(String(v))
148 end
149 @f.truncate @f.tell
150 end
151
152 def close
153 return if @f.closed?
154 update
155 @f.close
156 end
157
158 def delete
159 path = @f.path
160 @f.close
161 File::unlink path
162 end
163 end
164
165 class MemoryStore
166 GLOBAL_HASH_TABLE = {}
167
168 def initialize(session, option=nil)
169 @session_id = session.session_id
170 GLOBAL_HASH_TABLE[@session_id] ||= {}
171 end
172
173 def restore
174 GLOBAL_HASH_TABLE[@session_id]
175 end
176
177 def update
178 # don't need to update; hash is shared
179 end
180
181 def close
182 # don't need to close
183 end
184
185 def delete
186 GLOBAL_HASH_TABLE.delete(@session_id)
187 end
188 end
189 end
190 end