C0 code coverage information
Generated on Fri Jul 11 15:55:29 -0700 2008 with rcov 0.7.0
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 require 'fileutils'
2 require 'pathname'
3 require 'tempfile'
4
5 require 'openid/util'
6 require 'openid/store/interface'
7 require 'openid/association'
8
9 module OpenID
10 module Store
11 class Filesystem < Interface
12 @@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
13
14 # Create a Filesystem store instance, putting all data in +directory+.
15 def initialize(directory)
16 p_dir = Pathname.new(directory)
17 @nonce_dir = p_dir.join('nonces')
18 @association_dir = p_dir.join('associations')
19 @temp_dir = p_dir.join('temp')
20
21 self.ensure_dir(@nonce_dir)
22 self.ensure_dir(@association_dir)
23 self.ensure_dir(@temp_dir)
24 end
25
26 # Create a unique filename for a given server url and handle. The
27 # filename that is returned will contain the domain name from the
28 # server URL for ease of human inspection of the data dir.
29 def get_association_filename(server_url, handle)
30 unless server_url.index('://')
31 raise ArgumentError, "Bad server URL: #{server_url}"
32 end
33
34 proto, rest = server_url.split('://', 2)
35 domain = filename_escape(rest.split('/',2)[0])
36 url_hash = safe64(server_url)
37 if handle
38 handle_hash = safe64(handle)
39 else
40 handle_hash = ''
41 end
42 filename = [proto,domain,url_hash,handle_hash].join('-')
43 @association_dir.join(filename)
44 end
45
46 # Store an association in the assoc directory
47 def store_association(server_url, association)
48 assoc_s = association.serialize
49 filename = get_association_filename(server_url, association.handle)
50 f, tmp = mktemp
51
52 begin
53 begin
54 f.write(assoc_s)
55 f.fsync
56 ensure
57 f.close
58 end
59
60 begin
61 File.rename(tmp, filename)
62 rescue Errno::EEXIST
63
64 begin
65 File.unlink(filename)
66 rescue Errno::ENOENT
67 # do nothing
68 end
69
70 File.rename(tmp, filename)
71 end
72
73 rescue
74 self.remove_if_present(tmp)
75 raise
76 end
77 end
78
79 # Retrieve an association
80 def get_association(server_url, handle=nil)
81 # the filename with empty handle is the prefix for the associations
82 # for a given server url
83 filename = get_association_filename(server_url, handle)
84 if handle
85 return _get_association(filename)
86 end
87 assoc_filenames = Dir.glob(filename.to_s + '*')
88
89 assocs = assoc_filenames.collect do |f|
90 _get_association(f)
91 end
92
93 assocs = assocs.find_all { |a| not a.nil? }
94 assocs = assocs.sort_by { |a| a.issued }
95
96 return nil if assocs.empty?
97 return assocs[-1]
98 end
99
100 def _get_association(filename)
101 begin
102 assoc_file = File.open(filename, "r")
103 rescue Errno::ENOENT
104 return nil
105 else
106 begin
107 assoc_s = assoc_file.read
108 ensure
109 assoc_file.close
110 end
111
112 begin
113 association = Association.deserialize(assoc_s)
114 rescue
115 self.remove_if_present(filename)
116 return nil
117 end
118
119 # clean up expired associations
120 if association.expires_in == 0
121 self.remove_if_present(filename)
122 return nil
123 else
124 return association
125 end
126 end
127 end
128
129 # Remove an association if it exists, otherwise do nothing.
130 def remove_association(server_url, handle)
131 assoc = get_association(server_url, handle)
132
133 if assoc.nil?
134 return false
135 else
136 filename = get_association_filename(server_url, handle)
137 return self.remove_if_present(filename)
138 end
139 end
140
141 # Return whether the nonce is valid
142 def use_nonce(server_url, timestamp, salt)
143 return false if (timestamp - Time.now.to_i).abs > Nonce.skew
144
145 if server_url and !server_url.empty?
146 proto, rest = server_url.split('://',2)
147 else
148 proto, rest = '',''
149 end
150 raise "Bad server URL" unless proto && rest
151
152 domain = filename_escape(rest.split('/',2)[0])
153 url_hash = safe64(server_url)
154 salt_hash = safe64(salt)
155
156 nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]
157
158 filename = @nonce_dir.join(nonce_fn)
159
160 begin
161 fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)
162 fd.close
163 return true
164 rescue Errno::EEXIST
165 return false
166 end
167 end
168
169 # Remove expired entries from the database. This is potentially expensive,
170 # so only run when it is acceptable to take time.
171 def cleanup
172 cleanup_associations
173 cleanup_nonces
174 end
175
176 def cleanup_associations
177 association_filenames = Dir[@association_dir.join("*").to_s]
178 count = 0
179 association_filenames.each do |af|
180 begin
181 f = File.open(af, 'r')
182 rescue Errno::ENOENT
183 next
184 else
185 begin
186 assoc_s = f.read
187 ensure
188 f.close
189 end
190 begin
191 association = OpenID::Association.deserialize(assoc_s)
192 rescue StandardError
193 self.remove_if_present(af)
194 next
195 else
196 if association.expires_in == 0
197 self.remove_if_present(af)
198 count += 1
199 end
200 end
201 end
202 end
203 return count
204 end
205
206 def cleanup_nonces
207 nonces = Dir[@nonce_dir.join("*").to_s]
208 now = Time.now.to_i
209
210 count = 0
211 nonces.each do |filename|
212 nonce = filename.split('/')[-1]
213 timestamp = nonce.split('-', 2)[0].to_i(16)
214 nonce_age = (timestamp - now).abs
215 if nonce_age > Nonce.skew
216 self.remove_if_present(filename)
217 count += 1
218 end
219 end
220 return count
221 end
222
223 protected
224
225 # Create a temporary file and return the File object and filename.
226 def mktemp
227 f = Tempfile.new('tmp', @temp_dir)
228 [f, f.path]
229 end
230
231 # create a safe filename from a url
232 def filename_escape(s)
233 s = '' if s.nil?
234 filename_chunks = []
235 s.split('').each do |c|
236 if @@FILENAME_ALLOWED.index(c)
237 filename_chunks << c
238 else
239 filename_chunks << sprintf("_%02X", c[0])
240 end
241 end
242 filename_chunks.join("")
243 end
244
245 def safe64(s)
246 s = OpenID::CryptUtil.sha1(s)
247 s = OpenID::Util.to_base64(s)
248 s.gsub!('+', '_')
249 s.gsub!('/', '.')
250 s.gsub!('=', '')
251 return s
252 end
253
254 # remove file if present in filesystem
255 def remove_if_present(filename)
256 begin
257 File.unlink(filename)
258 rescue Errno::ENOENT
259 return false
260 end
261 return true
262 end
263
264 # ensure that a path exists
265 def ensure_dir(dir_name)
266 FileUtils::mkdir_p(dir_name)
267 end
268 end
269 end
270 end
271
Generated using the rcov code coverage analysis tool for Ruby version 0.7.0.