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