|
1 import errno |
|
2 import os |
|
3 import tempfile |
|
4 |
|
5 from django.conf import settings |
|
6 from django.contrib.sessions.backends.base import SessionBase, CreateError |
|
7 from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured |
|
8 |
|
9 |
|
10 class SessionStore(SessionBase): |
|
11 """ |
|
12 Implements a file based session store. |
|
13 """ |
|
14 def __init__(self, session_key=None): |
|
15 self.storage_path = getattr(settings, "SESSION_FILE_PATH", None) |
|
16 if not self.storage_path: |
|
17 self.storage_path = tempfile.gettempdir() |
|
18 |
|
19 # Make sure the storage path is valid. |
|
20 if not os.path.isdir(self.storage_path): |
|
21 raise ImproperlyConfigured( |
|
22 "The session storage path %r doesn't exist. Please set your" |
|
23 " SESSION_FILE_PATH setting to an existing directory in which" |
|
24 " Django can store session data." % self.storage_path) |
|
25 |
|
26 self.file_prefix = settings.SESSION_COOKIE_NAME |
|
27 super(SessionStore, self).__init__(session_key) |
|
28 |
|
29 def _key_to_file(self, session_key=None): |
|
30 """ |
|
31 Get the file associated with this session key. |
|
32 """ |
|
33 if session_key is None: |
|
34 session_key = self.session_key |
|
35 |
|
36 # Make sure we're not vulnerable to directory traversal. Session keys |
|
37 # should always be md5s, so they should never contain directory |
|
38 # components. |
|
39 if os.path.sep in session_key: |
|
40 raise SuspiciousOperation( |
|
41 "Invalid characters (directory components) in session key") |
|
42 |
|
43 return os.path.join(self.storage_path, self.file_prefix + session_key) |
|
44 |
|
45 def load(self): |
|
46 session_data = {} |
|
47 try: |
|
48 session_file = open(self._key_to_file(), "rb") |
|
49 try: |
|
50 file_data = session_file.read() |
|
51 # Don't fail if there is no data in the session file. |
|
52 # We may have opened the empty placeholder file. |
|
53 if file_data: |
|
54 try: |
|
55 session_data = self.decode(file_data) |
|
56 except (EOFError, SuspiciousOperation): |
|
57 self.create() |
|
58 finally: |
|
59 session_file.close() |
|
60 except IOError: |
|
61 pass |
|
62 return session_data |
|
63 |
|
64 def create(self): |
|
65 while True: |
|
66 self._session_key = self._get_new_session_key() |
|
67 try: |
|
68 self.save(must_create=True) |
|
69 except CreateError: |
|
70 continue |
|
71 self.modified = True |
|
72 self._session_cache = {} |
|
73 return |
|
74 |
|
75 def save(self, must_create=False): |
|
76 # Get the session data now, before we start messing |
|
77 # with the file it is stored within. |
|
78 session_data = self._get_session(no_load=must_create) |
|
79 |
|
80 session_file_name = self._key_to_file() |
|
81 |
|
82 try: |
|
83 # Make sure the file exists. If it does not already exist, an |
|
84 # empty placeholder file is created. |
|
85 flags = os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
|
86 if must_create: |
|
87 flags |= os.O_EXCL |
|
88 fd = os.open(session_file_name, flags) |
|
89 os.close(fd) |
|
90 |
|
91 except OSError, e: |
|
92 if must_create and e.errno == errno.EEXIST: |
|
93 raise CreateError |
|
94 raise |
|
95 |
|
96 # Write the session file without interfering with other threads |
|
97 # or processes. By writing to an atomically generated temporary |
|
98 # file and then using the atomic os.rename() to make the complete |
|
99 # file visible, we avoid having to lock the session file, while |
|
100 # still maintaining its integrity. |
|
101 # |
|
102 # Note: Locking the session file was explored, but rejected in part |
|
103 # because in order to be atomic and cross-platform, it required a |
|
104 # long-lived lock file for each session, doubling the number of |
|
105 # files in the session storage directory at any given time. This |
|
106 # rename solution is cleaner and avoids any additional overhead |
|
107 # when reading the session data, which is the more common case |
|
108 # unless SESSION_SAVE_EVERY_REQUEST = True. |
|
109 # |
|
110 # See ticket #8616. |
|
111 dir, prefix = os.path.split(session_file_name) |
|
112 |
|
113 try: |
|
114 output_file_fd, output_file_name = tempfile.mkstemp(dir=dir, |
|
115 prefix=prefix + '_out_') |
|
116 renamed = False |
|
117 try: |
|
118 try: |
|
119 os.write(output_file_fd, self.encode(session_data)) |
|
120 finally: |
|
121 os.close(output_file_fd) |
|
122 os.rename(output_file_name, session_file_name) |
|
123 renamed = True |
|
124 finally: |
|
125 if not renamed: |
|
126 os.unlink(output_file_name) |
|
127 |
|
128 except (OSError, IOError, EOFError): |
|
129 pass |
|
130 |
|
131 def exists(self, session_key): |
|
132 if os.path.exists(self._key_to_file(session_key)): |
|
133 return True |
|
134 return False |
|
135 |
|
136 def delete(self, session_key=None): |
|
137 if session_key is None: |
|
138 if self._session_key is None: |
|
139 return |
|
140 session_key = self._session_key |
|
141 try: |
|
142 os.unlink(self._key_to_file(session_key)) |
|
143 except OSError: |
|
144 pass |
|
145 |
|
146 def clean(self): |
|
147 pass |