sessions.py (15093B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\sessions.py 2 import datetime 3 import os 4 import random 5 import time 6 import threading 7 import types 8 from warnings import warn 9 import cherrypy 10 from cherrypy._cpcompat import copyitems, pickle, random20 11 from cherrypy.lib import httputil 12 missing = object() 13 14 class Session(object): 15 _id = None 16 id_observers = None 17 18 def _get_id(self): 19 return self._id 20 21 def _set_id(self, value): 22 self._id = value 23 for o in self.id_observers: 24 o(value) 25 26 id = property(_get_id, _set_id, doc='The current session ID.') 27 timeout = 60 28 locked = False 29 loaded = False 30 clean_thread = None 31 clean_freq = 5 32 originalid = None 33 missing = False 34 regenerated = False 35 debug = False 36 37 def __init__(self, id = None, **kwargs): 38 self.id_observers = [] 39 self._data = {} 40 for k, v in kwargs.items(): 41 setattr(self, k, v) 42 43 self.originalid = id 44 self.missing = False 45 if id is None: 46 if self.debug: 47 cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS') 48 self._regenerate() 49 else: 50 self.id = id 51 if not self._exists(): 52 if self.debug: 53 cherrypy.log('Expired or malicious session %r; making a new one' % id, 'TOOLS.SESSIONS') 54 self.id = None 55 self.missing = True 56 self._regenerate() 57 58 def regenerate(self): 59 self.regenerated = True 60 self._regenerate() 61 62 def _regenerate(self): 63 if self.id is not None: 64 self.delete() 65 old_session_was_locked = self.locked 66 if old_session_was_locked: 67 self.release_lock() 68 self.id = None 69 while self.id is None: 70 self.id = self.generate_id() 71 if self._exists(): 72 self.id = None 73 74 if old_session_was_locked: 75 self.acquire_lock() 76 77 def clean_up(self): 78 pass 79 80 def generate_id(self): 81 return random20() 82 83 def save(self): 84 try: 85 if self.loaded: 86 t = datetime.timedelta(seconds=self.timeout * 60) 87 expiration_time = datetime.datetime.now() + t 88 if self.debug: 89 cherrypy.log('Saving with expiry %s' % expiration_time, 'TOOLS.SESSIONS') 90 self._save(expiration_time) 91 finally: 92 if self.locked: 93 self.release_lock() 94 95 def load(self): 96 data = self._load() 97 if data is None or data[1] < datetime.datetime.now(): 98 if self.debug: 99 cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS') 100 self._data = {} 101 else: 102 self._data = data[0] 103 self.loaded = True 104 cls = self.__class__ 105 if self.clean_freq and not cls.clean_thread: 106 t = cherrypy.process.plugins.Monitor(cherrypy.engine, self.clean_up, self.clean_freq * 60, name='Session cleanup') 107 t.subscribe() 108 cls.clean_thread = t 109 t.start() 110 111 def delete(self): 112 self._delete() 113 114 def __getitem__(self, key): 115 if not self.loaded: 116 self.load() 117 return self._data[key] 118 119 def __setitem__(self, key, value): 120 if not self.loaded: 121 self.load() 122 self._data[key] = value 123 124 def __delitem__(self, key): 125 if not self.loaded: 126 self.load() 127 del self._data[key] 128 129 def pop(self, key, default = missing): 130 if not self.loaded: 131 self.load() 132 if default is missing: 133 return self._data.pop(key) 134 else: 135 return self._data.pop(key, default) 136 137 def __contains__(self, key): 138 if not self.loaded: 139 self.load() 140 return key in self._data 141 142 def has_key(self, key): 143 if not self.loaded: 144 self.load() 145 return key in self._data 146 147 def get(self, key, default = None): 148 if not self.loaded: 149 self.load() 150 return self._data.get(key, default) 151 152 def update(self, d): 153 if not self.loaded: 154 self.load() 155 self._data.update(d) 156 157 def setdefault(self, key, default = None): 158 if not self.loaded: 159 self.load() 160 return self._data.setdefault(key, default) 161 162 def clear(self): 163 if not self.loaded: 164 self.load() 165 self._data.clear() 166 167 def keys(self): 168 if not self.loaded: 169 self.load() 170 return self._data.keys() 171 172 def items(self): 173 if not self.loaded: 174 self.load() 175 return self._data.items() 176 177 def values(self): 178 if not self.loaded: 179 self.load() 180 return self._data.values() 181 182 183 class RamSession(Session): 184 cache = {} 185 locks = {} 186 187 def clean_up(self): 188 now = datetime.datetime.now() 189 for id, (data, expiration_time) in copyitems(self.cache): 190 if expiration_time <= now: 191 try: 192 del self.cache[id] 193 except KeyError: 194 pass 195 196 try: 197 del self.locks[id] 198 except KeyError: 199 pass 200 201 def _exists(self): 202 return self.id in self.cache 203 204 def _load(self): 205 return self.cache.get(self.id) 206 207 def _save(self, expiration_time): 208 self.cache[self.id] = (self._data, expiration_time) 209 210 def _delete(self): 211 self.cache.pop(self.id, None) 212 213 def acquire_lock(self): 214 self.locked = True 215 self.locks.setdefault(self.id, threading.RLock()).acquire() 216 217 def release_lock(self): 218 self.locks[self.id].release() 219 self.locked = False 220 221 def __len__(self): 222 return len(self.cache) 223 224 225 class FileSession(Session): 226 SESSION_PREFIX = 'session-' 227 LOCK_SUFFIX = '.lock' 228 pickle_protocol = pickle.HIGHEST_PROTOCOL 229 230 def __init__(self, id = None, **kwargs): 231 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) 232 Session.__init__(self, id=id, **kwargs) 233 234 def setup(cls, **kwargs): 235 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) 236 for k, v in kwargs.items(): 237 setattr(cls, k, v) 238 239 lockfiles = [ fname for fname in os.listdir(cls.storage_path) if fname.startswith(cls.SESSION_PREFIX) and fname.endswith(cls.LOCK_SUFFIX) ] 240 if lockfiles: 241 plural = ('', 's')[len(lockfiles) > 1] 242 warn('%s session lockfile%s found at startup. If you are only running one process, then you may need to manually delete the lockfiles found at %r.' % (len(lockfiles), plural, cls.storage_path)) 243 244 setup = classmethod(setup) 245 246 def _get_file_path(self): 247 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id) 248 if not os.path.abspath(f).startswith(self.storage_path): 249 raise cherrypy.HTTPError(400, 'Invalid session id in cookie.') 250 return f 251 252 def _exists(self): 253 path = self._get_file_path() 254 return os.path.exists(path) 255 256 def _load(self, path = None): 257 if path is None: 258 path = self._get_file_path() 259 try: 260 f = open(path, 'rb') 261 try: 262 return pickle.load(f) 263 finally: 264 f.close() 265 266 except (IOError, EOFError): 267 return 268 269 def _save(self, expiration_time): 270 f = open(self._get_file_path(), 'wb') 271 try: 272 pickle.dump((self._data, expiration_time), f, self.pickle_protocol) 273 finally: 274 f.close() 275 276 def _delete(self): 277 try: 278 os.unlink(self._get_file_path()) 279 except OSError: 280 pass 281 282 def acquire_lock(self, path = None): 283 if path is None: 284 path = self._get_file_path() 285 path += self.LOCK_SUFFIX 286 while True: 287 try: 288 lockfd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL) 289 except OSError: 290 time.sleep(0.1) 291 else: 292 os.close(lockfd) 293 break 294 295 self.locked = True 296 297 def release_lock(self, path = None): 298 if path is None: 299 path = self._get_file_path() 300 os.unlink(path + self.LOCK_SUFFIX) 301 self.locked = False 302 303 def clean_up(self): 304 now = datetime.datetime.now() 305 for fname in os.listdir(self.storage_path): 306 if fname.startswith(self.SESSION_PREFIX) and not fname.endswith(self.LOCK_SUFFIX): 307 path = os.path.join(self.storage_path, fname) 308 self.acquire_lock(path) 309 try: 310 contents = self._load(path) 311 if contents is not None: 312 data, expiration_time = contents 313 if expiration_time < now: 314 os.unlink(path) 315 finally: 316 self.release_lock(path) 317 318 def __len__(self): 319 return len([ fname for fname in os.listdir(self.storage_path) if fname.startswith(self.SESSION_PREFIX) and not fname.endswith(self.LOCK_SUFFIX) ]) 320 321 322 class PostgresqlSession(Session): 323 pickle_protocol = pickle.HIGHEST_PROTOCOL 324 325 def __init__(self, id = None, **kwargs): 326 Session.__init__(self, id, **kwargs) 327 self.cursor = self.db.cursor() 328 329 def setup(cls, **kwargs): 330 for k, v in kwargs.items(): 331 setattr(cls, k, v) 332 333 self.db = self.get_db() 334 335 setup = classmethod(setup) 336 337 def __del__(self): 338 if self.cursor: 339 self.cursor.close() 340 self.db.commit() 341 342 def _exists(self): 343 self.cursor.execute('select data, expiration_time from session where id=%s', (self.id,)) 344 rows = self.cursor.fetchall() 345 return bool(rows) 346 347 def _load(self): 348 self.cursor.execute('select data, expiration_time from session where id=%s', (self.id,)) 349 rows = self.cursor.fetchall() 350 if not rows: 351 return None 352 pickled_data, expiration_time = rows[0] 353 data = pickle.loads(pickled_data) 354 return (data, expiration_time) 355 356 def _save(self, expiration_time): 357 pickled_data = pickle.dumps(self._data, self.pickle_protocol) 358 self.cursor.execute('update session set data = %s, expiration_time = %s where id = %s', (pickled_data, expiration_time, self.id)) 359 360 def _delete(self): 361 self.cursor.execute('delete from session where id=%s', (self.id,)) 362 363 def acquire_lock(self): 364 self.locked = True 365 self.cursor.execute('select id from session where id=%s for update', (self.id,)) 366 367 def release_lock(self): 368 self.cursor.close() 369 self.locked = False 370 371 def clean_up(self): 372 self.cursor.execute('delete from session where expiration_time < %s', (datetime.datetime.now(),)) 373 374 375 class MemcachedSession(Session): 376 mc_lock = threading.RLock() 377 locks = {} 378 servers = ['127.0.0.1:11211'] 379 380 def setup(cls, **kwargs): 381 for k, v in kwargs.items(): 382 setattr(cls, k, v) 383 384 import memcache 385 cls.cache = memcache.Client(cls.servers) 386 387 setup = classmethod(setup) 388 389 def _exists(self): 390 self.mc_lock.acquire() 391 try: 392 return bool(self.cache.get(self.id)) 393 finally: 394 self.mc_lock.release() 395 396 def _load(self): 397 self.mc_lock.acquire() 398 try: 399 return self.cache.get(self.id) 400 finally: 401 self.mc_lock.release() 402 403 def _save(self, expiration_time): 404 td = int(time.mktime(expiration_time.timetuple())) 405 self.mc_lock.acquire() 406 try: 407 if not self.cache.set(self.id, (self._data, expiration_time), td): 408 raise AssertionError('Session data for id %r not set.' % self.id) 409 finally: 410 self.mc_lock.release() 411 412 def _delete(self): 413 self.cache.delete(self.id) 414 415 def acquire_lock(self): 416 self.locked = True 417 self.locks.setdefault(self.id, threading.RLock()).acquire() 418 419 def release_lock(self): 420 self.locks[self.id].release() 421 self.locked = False 422 423 def __len__(self): 424 raise NotImplementedError 425 426 427 def save(): 428 if not hasattr(cherrypy.serving, 'session'): 429 return 430 request = cherrypy.serving.request 431 response = cherrypy.serving.response 432 if hasattr(request, '_sessionsaved'): 433 return 434 request._sessionsaved = True 435 if response.stream: 436 request.hooks.attach('on_end_request', cherrypy.session.save) 437 else: 438 if isinstance(response.body, types.GeneratorType): 439 response.collapse_body() 440 cherrypy.session.save() 441 442 443 save.failsafe = True 444 445 def close(): 446 sess = getattr(cherrypy.serving, 'session', None) 447 if getattr(sess, 'locked', False): 448 sess.release_lock() 449 450 451 close.failsafe = True 452 close.priority = 90 453 454 def init(storage_type = 'ram', path = None, path_header = None, name = 'session_id', timeout = 60, domain = None, secure = False, clean_freq = 5, persistent = True, debug = False, **kwargs): 455 request = cherrypy.serving.request 456 if hasattr(request, '_session_init_flag'): 457 return 458 request._session_init_flag = True 459 id = None 460 if name in request.cookie: 461 id = request.cookie[name].value 462 if debug: 463 cherrypy.log('ID obtained from request.cookie: %r' % id, 'TOOLS.SESSIONS') 464 storage_class = storage_type.title() + 'Session' 465 storage_class = globals()[storage_class] 466 if not hasattr(cherrypy, 'session'): 467 if hasattr(storage_class, 'setup'): 468 storage_class.setup(**kwargs) 469 kwargs['timeout'] = timeout 470 kwargs['clean_freq'] = clean_freq 471 cherrypy.serving.session = sess = storage_class(id, **kwargs) 472 sess.debug = debug 473 474 def update_cookie(id): 475 cherrypy.serving.response.cookie[name] = id 476 477 sess.id_observers.append(update_cookie) 478 if not hasattr(cherrypy, 'session'): 479 cherrypy.session = cherrypy._ThreadLocalProxy('session') 480 if persistent: 481 cookie_timeout = timeout 482 else: 483 cookie_timeout = None 484 set_response_cookie(path=path, path_header=path_header, name=name, timeout=cookie_timeout, domain=domain, secure=secure) 485 486 487 def set_response_cookie(path = None, path_header = None, name = 'session_id', timeout = 60, domain = None, secure = False): 488 cookie = cherrypy.serving.response.cookie 489 cookie[name] = cherrypy.serving.session.id 490 cookie[name]['path'] = path or cherrypy.serving.request.headers.get(path_header) or '/' 491 if timeout: 492 e = time.time() + timeout * 60 493 cookie[name]['expires'] = httputil.HTTPDate(e) 494 if domain is not None: 495 cookie[name]['domain'] = domain 496 if secure: 497 cookie[name]['secure'] = 1 498 499 500 def expire(): 501 name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id') 502 one_year = 31536000 503 e = time.time() - one_year 504 cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)