1#
2# cgi/session/pstore.rb - persistent storage of marshalled session data
3#
4# Documentation: William Webber (william@williamwebber.com)
5#
6# == Overview
7#
8# This file provides the CGI::Session::PStore class, which builds
9# persistent of session data on top of the pstore library.  See
10# cgi/session.rb for more details on session storage managers.
11
12require 'cgi/session'
13require 'pstore'
14
15class CGI
16  class Session
17    # PStore-based session storage class.
18    #
19    # This builds upon the top-level PStore class provided by the
20    # library file pstore.rb.  Session data is marshalled and stored
21    # in a file.  File locking and transaction services are provided.
22    class PStore
23      # Create a new CGI::Session::PStore instance
24      #
25      # This constructor is used internally by CGI::Session.  The
26      # user does not generally need to call it directly.
27      #
28      # +session+ is the session for which this instance is being
29      # created.  The session id must only contain alphanumeric
30      # characters; automatically generated session ids observe
31      # this requirement.
32      #
33      # +option+ is a hash of options for the initializer.  The
34      # following options are recognised:
35      #
36      # tmpdir:: the directory to use for storing the PStore
37      #          file.  Defaults to Dir::tmpdir (generally "/tmp"
38      #          on Unix systems).
39      # prefix:: the prefix to add to the session id when generating
40      #          the filename for this session's PStore file.
41      #          Defaults to the empty string.
42      #
43      # This session's PStore file will be created if it does
44      # not exist, or opened if it does.
45      def initialize(session, option={})
46        dir = option['tmpdir'] || Dir::tmpdir
47        prefix = option['prefix'] || ''
48        id = session.session_id
49        require 'digest/md5'
50        md5 = Digest::MD5.hexdigest(id)[0,16]
51        path = dir+"/"+prefix+md5
52        path.untaint
53        if File::exist?(path)
54          @hash = nil
55        else
56          unless session.new_session
57            raise CGI::Session::NoSession, "uninitialized session"
58          end
59          @hash = {}
60        end
61        @p = ::PStore.new(path)
62        @p.transaction do |p|
63          File.chmod(0600, p.path)
64        end
65      end
66
67      # Restore session state from the session's PStore file.
68      #
69      # Returns the session state as a hash.
70      def restore
71        unless @hash
72          @p.transaction do
73            @hash = @p['hash'] || {}
74          end
75        end
76        @hash
77      end
78
79      # Save session state to the session's PStore file.
80      def update
81        @p.transaction do
82          @p['hash'] = @hash
83        end
84      end
85
86      # Update and close the session's PStore file.
87      def close
88        update
89      end
90
91      # Close and delete the session's PStore file.
92      def delete
93        path = @p.path
94        File::unlink path
95      end
96
97    end
98  end
99end
100
101if $0 == __FILE__
102  # :enddoc:
103  STDIN.reopen("/dev/null")
104  cgi = CGI.new
105  session = CGI::Session.new(cgi, 'database_manager' => CGI::Session::PStore)
106  session['key'] = {'k' => 'v'}
107  puts session['key'].class
108  fail unless Hash === session['key']
109  puts session['key'].inspect
110  fail unless session['key'].inspect == '{"k"=>"v"}'
111end
112