| Home | Trees | Indices | Help |
|
|---|
|
|
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Contains the classes for the global used variables:
10
11 - Request
12 - Response
13 - Session
14
15 """
16
17 from gluon.storage import Storage, List
18 from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
19 from gluon.xmlrpc import handler
20 from gluon.contenttype import contenttype
21 from gluon.html import xmlescape, TABLE, TR, PRE, URL
22 from gluon.http import HTTP, redirect
23 from gluon.fileutils import up
24 from gluon.serializers import json, custom_json
25 import gluon.settings as settings
26 from gluon.utils import web2py_uuid, secure_dumps, secure_loads
27 from gluon.settings import global_settings
28 import hashlib
29 import portalocker
30 import cPickle
31 from pickle import Pickler, MARK, DICT, EMPTY_DICT
32 from types import DictionaryType
33 import cStringIO
34 import datetime
35 import re
36 import Cookie
37 import os
38 import sys
39 import traceback
40 import threading
41 import cgi
42 import copy
43 import tempfile
44 from gluon.cache import CacheInRam
45 from gluon.fileutils import copystream
46
47 FMT = '%a, %d-%b-%Y %H:%M:%S PST'
48 PAST = 'Sat, 1-Jan-1971 00:00:00'
49 FUTURE = 'Tue, 1-Dec-2999 23:59:59'
50
51 try:
52 from gluon.contrib.minify import minify
53 have_minify = True
54 except ImportError:
55 have_minify = False
56
57 try:
58 import simplejson as sj #external installed library
59 except:
60 try:
61 import json as sj #standard installed library
62 except:
63 import gluon.contrib.simplejson as sj #pure python library
64
65 regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$')
66
67 __all__ = ['Request', 'Response', 'Session']
68
69 current = threading.local() # thread-local storage for request-scope globals
70
71 css_template = '<link href="%s" rel="stylesheet" type="text/css" />'
72 js_template = '<script src="%s" type="text/javascript"></script>'
73 coffee_template = '<script src="%s" type="text/coffee"></script>'
74 typescript_template = '<script src="%s" type="text/typescript"></script>'
75 less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
76 css_inline = '<style type="text/css">\n%s\n</style>'
77 js_inline = '<script type="text/javascript">\n%s\n</script>'
78
79 # IMPORTANT:
80 # this is required so that pickled dict(s) and class.__dict__
81 # are sorted and web2py can detect without ambiguity when a session changes
82 -class SortingPickler(Pickler):
84 self.write(EMPTY_DICT if self.bin else MARK+DICT)
85 self.memoize(obj)
86 self._batch_setitems([(key,obj[key]) for key in sorted(obj)])
87
88 SortingPickler.dispatch = copy.copy(Pickler.dispatch)
89 SortingPickler.dispatch[DictionaryType] = SortingPickler.save_dict
92 file = cStringIO.StringIO()
93 SortingPickler(file, protocol).dump(obj)
94 return file.getvalue()
95 # END #####################################################################
98 """
99 copies request.env.wsgi_input into request.body
100 and stores progress upload status in cache_ram
101 X-Progress-ID:length and X-Progress-ID:uploaded
102 """
103 env = request.env
104 if not env.get('CONTENT_LENGTH', None):
105 return cStringIO.StringIO()
106 source = env['wsgi.input']
107 try:
108 size = int(env['CONTENT_LENGTH'])
109 except ValueError:
110 raise HTTP(400, "Invalid Content-Length header")
111 try: # Android requires this
112 dest = tempfile.NamedTemporaryFile()
113 except NotImplementedError: # and GAE this
114 dest = tempfile.TemporaryFile()
115 if not 'X-Progress-ID' in request.get_vars:
116 copystream(source, dest, size, chunk_size)
117 return dest
118 cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
119 cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
120 cache_ram(cache_key + ':length', lambda: size, 0)
121 cache_ram(cache_key + ':uploaded', lambda: 0, 0)
122 while size > 0:
123 if size < chunk_size:
124 data = source.read(size)
125 cache_ram.increment(cache_key + ':uploaded', size)
126 else:
127 data = source.read(chunk_size)
128 cache_ram.increment(cache_key + ':uploaded', chunk_size)
129 length = len(data)
130 if length > size:
131 (data, length) = (data[:size], size)
132 size -= length
133 if length == 0:
134 break
135 dest.write(data)
136 if length < chunk_size:
137 break
138 dest.seek(0)
139 cache_ram(cache_key + ':length', None)
140 cache_ram(cache_key + ':uploaded', None)
141 return dest
142
144
145 """
146 defines the request object and the default values of its members
147
148 - env: environment variables, by gluon.main.wsgibase()
149 - cookies
150 - get_vars
151 - post_vars
152 - vars
153 - folder
154 - application
155 - function
156 - args
157 - extension
158 - now: datetime.datetime.today()
159 - restful()
160 """
161
163 Storage.__init__(self)
164 self.env = Storage(env)
165 self.env.web2py_path = global_settings.applications_parent
166 self.env.update(global_settings)
167 self.cookies = Cookie.SimpleCookie()
168 self._get_vars = None
169 self._post_vars = None
170 self._vars = None
171 self._body = None
172 self.folder = None
173 self.application = None
174 self.function = None
175 self.args = List()
176 self.extension = 'html'
177 self.now = datetime.datetime.now()
178 self.utcnow = datetime.datetime.utcnow()
179 self.is_restful = False
180 self.is_https = False
181 self.is_local = False
182 self.global_settings = settings.global_settings
183
184
186 query_string = self.env.get('QUERY_STRING','')
187 dget = cgi.parse_qs(query_string, keep_blank_values=1)
188 get_vars = self._get_vars = Storage(dget)
189 for (key, value) in get_vars.iteritems():
190 if isinstance(value,list) and len(value)==1:
191 get_vars[key] = value[0]
192
194 env = self.env
195 post_vars = self._post_vars = Storage()
196 body = self.body
197 #if content-type is application/json, we must read the body
198 is_json = env.get('content_type', '')[:16] == 'application/json'
199
200 if is_json:
201 try:
202 json_vars = sj.load(body)
203 except:
204 # incoherent request bodies can still be parsed "ad-hoc"
205 json_vars = {}
206 pass
207 # update vars and get_vars with what was posted as json
208 if isinstance(json_vars, dict):
209 post_vars.update(json_vars)
210
211 body.seek(0)
212
213 # parse POST variables on POST, PUT, BOTH only in post_vars
214 if (body and not is_json
215 and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
216 query_string = env.pop('QUERY_STRING',None)
217 dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
218 try:
219 post_vars.update(dpost)
220 except: pass
221 if query_string is not None:
222 env['QUERY_STRING'] = query_string
223 # The same detection used by FieldStorage to detect multipart POSTs
224 body.seek(0)
225
226 def listify(a):
227 return (not isinstance(a, list) and [a]) or a
228 try:
229 keys = sorted(dpost)
230 except TypeError:
231 keys = []
232 for key in keys:
233 if key is None:
234 continue # not sure why cgi.FieldStorage returns None key
235 dpk = dpost[key]
236 # if an element is not a file replace it with
237 # its value else leave it alone
238
239 pvalue = listify([(_dpk if _dpk.filename else _dpk.value)
240 for _dpk in dpk]
241 if isinstance(dpk, list) else
242 (dpk if dpk.filename else dpk.value))
243 if len(pvalue):
244 post_vars[key] = (len(pvalue) > 1 and pvalue) or pvalue[0]
245
246 @property
248 if self._body is None:
249 try:
250 self._body = copystream_progress(self)
251 except IOError:
252 raise HTTP(400, "Bad Request - HTTP body is incomplete")
253 return self._body
254
256 self._vars = copy.copy(self.get_vars)
257 for key,value in self.post_vars.iteritems():
258 if not key in self._vars:
259 self._vars[key] = value
260 else:
261 if not isinstance(self._vars[key],list):
262 self._vars[key] = [self._vars[key]]
263 self._vars[key] += value if isinstance(value,list) else [value]
264
265 @property
267 "lazily parse the query string into get_vars"
268 if self._get_vars is None:
269 self.parse_get_vars()
270 return self._get_vars
271
272 @property
274 "lazily parse the body into post_vars"
275 if self._post_vars is None:
276 self.parse_post_vars()
277 return self._post_vars
278
279 @property
281 "lazily parse all get_vars and post_vars to fill vars"
282 if self._vars is None:
283 self.parse_all_vars()
284 return self._vars
285
287 self.uuid = '%s/%s.%s.%s' % (
288 self.application,
289 self.client.replace(':', '_'),
290 self.now.strftime('%Y-%m-%d.%H-%M-%S'),
291 web2py_uuid())
292 return self.uuid
293
295 from gluon.contrib import user_agent_parser
296 session = current.session
297 user_agent = session._user_agent or \
298 user_agent_parser.detect(self.env.http_user_agent)
299 if session:
300 session._user_agent = user_agent
301 user_agent = Storage(user_agent)
302 for key, value in user_agent.items():
303 if isinstance(value, dict):
304 user_agent[key] = Storage(value)
305 return user_agent
306
308 """
309 If request comes in over HTTP, redirect it to HTTPS
310 and secure the session.
311 """
312 cmd_opts = global_settings.cmd_options
313 #checking if this is called within the scheduler or within the shell
314 #in addition to checking if it's not a cronjob
315 if ((cmd_opts and (cmd_opts.shell or cmd_opts.scheduler))
316 or global_settings.cronjob or self.is_https):
317 current.session.secure()
318 else:
319 current.session.forget()
320 redirect(URL(scheme='https', args=self.args, vars=self.vars))
321
323 def wrapper(action, self=self):
324 def f(_action=action, _self=self, *a, **b):
325 self.is_restful = True
326 method = _self.env.request_method
327 if len(_self.args) and '.' in _self.args[-1]:
328 _self.args[-1], _, self.extension = self.args[-1].rpartition('.')
329 current.response.headers['Content-Type'] = \
330 contenttype('.' + _self.extension.lower())
331 rest_action = _action().get(method, None)
332 if not (rest_action and method==method.upper()
333 and callable(rest_action)):
334 raise HTTP(400, "method not supported")
335 try:
336 return rest_action(*_self.args, **getattr(_self,'vars',{}))
337 except TypeError, e:
338 exc_type, exc_value, exc_traceback = sys.exc_info()
339 if len(traceback.extract_tb(exc_traceback)) == 1:
340 raise HTTP(400, "invalid arguments")
341 else:
342 raise e
343 f.__doc__ = action.__doc__
344 f.__name__ = action.__name__
345 return f
346 return wrapper
347
350
351 """
352 defines the response object and the default values of its members
353 response.write( ) can be used to write in the output html
354 """
355
357 Storage.__init__(self)
358 self.status = 200
359 self.headers = dict()
360 self.headers['X-Powered-By'] = 'web2py'
361 self.body = cStringIO.StringIO()
362 self.session_id = None
363 self.cookies = Cookie.SimpleCookie()
364 self.postprocessing = []
365 self.flash = '' # used by the default view layout
366 self.meta = Storage() # used by web2py_ajax.html
367 self.menu = [] # used by the default view layout
368 self.files = [] # used by web2py_ajax.html
369 self.generic_patterns = [] # patterns to allow generic views
370 self.delimiters = ('{{', '}}')
371 self._vars = None
372 self._caller = lambda f: f()
373 self._view_environment = None
374 self._custom_commit = None
375 self._custom_rollback = None
376
382
384 from compileapp import run_view_in
385 if len(a) > 2:
386 raise SyntaxError(
387 'Response.render can be called with two arguments, at most')
388 elif len(a) == 2:
389 (view, self._vars) = (a[0], a[1])
390 elif len(a) == 1 and isinstance(a[0], str):
391 (view, self._vars) = (a[0], {})
392 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read):
393 (view, self._vars) = (a[0], {})
394 elif len(a) == 1 and isinstance(a[0], dict):
395 (view, self._vars) = (None, a[0])
396 else:
397 (view, self._vars) = (None, {})
398 self._vars.update(b)
399 self._view_environment.update(self._vars)
400 if view:
401 import cStringIO
402 (obody, oview) = (self.body, self.view)
403 (self.body, self.view) = (cStringIO.StringIO(), view)
404 run_view_in(self._view_environment)
405 page = self.body.getvalue()
406 self.body.close()
407 (self.body, self.view) = (obody, oview)
408 else:
409 run_view_in(self._view_environment)
410 page = self.body.getvalue()
411 return page
412
414 s = '\n'.join(
415 '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
416 for k, v in (self.meta or {}).iteritems())
417 self.write(s, escape=False)
418
420
421 """
422 Caching method for writing out files.
423 By default, caches in ram for 5 minutes. To change,
424 response.cache_includes = (cache_method, time_expire).
425 Example: (cache.disk, 60) # caches to disk for 1 minute.
426 """
427 from gluon import URL
428
429 files = []
430 has_js = has_css = False
431 for item in self.files:
432 if extensions and not item.split('.')[-1] in extensions:
433 continue
434 if item in files:
435 continue
436 if item.endswith('.js'):
437 has_js = True
438 if item.endswith('.css'):
439 has_css = True
440 files.append(item)
441
442 if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
443 # cache for 5 minutes by default
444 key = hashlib.md5(repr(files)).hexdigest()
445
446 cache = self.cache_includes or (current.cache.ram, 60 * 5)
447
448 def call_minify(files=files):
449 return minify.minify(files,
450 URL('static', 'temp'),
451 current.request.folder,
452 self.optimize_css,
453 self.optimize_js)
454 if cache:
455 cache_model, time_expire = cache
456 files = cache_model('response.files.minified/' + key,
457 call_minify,
458 time_expire)
459 else:
460 files = call_minify()
461 s = ''
462 for item in files:
463 if isinstance(item, str):
464 f = item.lower().split('?')[0]
465 if self.static_version:
466 item = item.replace(
467 '/static/', '/static/_%s/' % self.static_version, 1)
468 if f.endswith('.css'):
469 s += css_template % item
470 elif f.endswith('.js'):
471 s += js_template % item
472 elif f.endswith('.coffee'):
473 s += coffee_template % item
474 elif f.endswith('.ts'):
475 # http://www.typescriptlang.org/
476 s += typescript_template % item
477 elif f.endswith('.less'):
478 s += less_template % item
479 elif isinstance(item, (list, tuple)):
480 f = item[0]
481 if f == 'css:inline':
482 s += css_inline % item[1]
483 elif f == 'js:inline':
484 s += js_inline % item[1]
485 self.write(s, escape=False)
486
487 - def stream(
488 self,
489 stream,
490 chunk_size=DEFAULT_CHUNK_SIZE,
491 request=None,
492 attachment=False,
493 filename=None
494 ):
495 """
496 if a controller function::
497
498 return response.stream(file, 100)
499
500 the file content will be streamed at 100 bytes at the time
501
502 Optional kwargs:
503 (for custom stream calls)
504 attachment=True # Send as attachment. Usually creates a
505 # pop-up download window on browsers
506 filename=None # The name for the attachment
507
508 Note: for using the stream name (filename) with attachments
509 the option must be explicitly set as function parameter(will
510 default to the last request argument otherwise)
511 """
512
513 headers = self.headers
514 # for attachment settings and backward compatibility
515 keys = [item.lower() for item in headers]
516 if attachment:
517 if filename is None:
518 attname = ""
519 else:
520 attname = filename
521 headers["Content-Disposition"] = \
522 "attachment;filename=%s" % attname
523
524 if not request:
525 request = current.request
526 if isinstance(stream, (str, unicode)):
527 stream_file_or_304_or_206(stream,
528 chunk_size=chunk_size,
529 request=request,
530 headers=headers,
531 status=self.status)
532
533 # ## the following is for backward compatibility
534 if hasattr(stream, 'name'):
535 filename = stream.name
536
537 if filename and not 'content-type' in keys:
538 headers['Content-Type'] = contenttype(filename)
539 if filename and not 'content-length' in keys:
540 try:
541 headers['Content-Length'] = \
542 os.path.getsize(filename)
543 except OSError:
544 pass
545
546 env = request.env
547 # Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled
548 if request.is_https and isinstance(env.http_user_agent, str) and \
549 not re.search(r'Opera', env.http_user_agent) and \
550 re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
551 headers['Pragma'] = 'cache'
552 headers['Cache-Control'] = 'private'
553
554 if request and env.web2py_use_wsgi_file_wrapper:
555 wrapped = env.wsgi_file_wrapper(stream, chunk_size)
556 else:
557 wrapped = streamer(stream, chunk_size=chunk_size)
558 return wrapped
559
560 - def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True, download_filename=None):
561 """
562 example of usage in controller::
563
564 def download():
565 return response.download(request, db)
566
567 downloads from http://..../download/filename
568 """
569
570 current.session.forget(current.response)
571
572 if not request.args:
573 raise HTTP(404)
574 name = request.args[-1]
575 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
576 .match(name)
577 if not items:
578 raise HTTP(404)
579 (t, f) = (items.group('table'), items.group('field'))
580 try:
581 field = db[t][f]
582 except AttributeError:
583 raise HTTP(404)
584 try:
585 (filename, stream) = field.retrieve(name,nameonly=True)
586 except IOError:
587 raise HTTP(404)
588 headers = self.headers
589 headers['Content-Type'] = contenttype(name)
590 if download_filename == None:
591 download_filename = filename
592 if attachment:
593 headers['Content-Disposition'] = \
594 'attachment; filename="%s"' % download_filename.replace('"','\"')
595 return self.stream(stream, chunk_size=chunk_size, request=request)
596
599
601 """
602 assuming::
603
604 def add(a, b):
605 return a+b
606
607 if a controller function \"func\"::
608
609 return response.xmlrpc(request, [add])
610
611 the controller will be able to handle xmlrpc requests for
612 the add function. Example::
613
614 import xmlrpclib
615 connection = xmlrpclib.ServerProxy(
616 'http://hostname/app/contr/func')
617 print connection.add(3, 4)
618
619 """
620
621 return handler(request, self, methods)
622
624 from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
625 BUTTON = TAG.button
626 admin = URL("admin", "default", "design", extension='html',
627 args=current.request.application)
628 from gluon.dal import DAL
629 dbstats = []
630 dbtables = {}
631 infos = DAL.get_instances()
632 for k,v in infos.iteritems():
633 dbstats.append(TABLE(*[TR(PRE(row[0]),'%.2fms' %
634 (row[1]*1000))
635 for row in v['dbstats']]))
636 dbtables[k] = dict(defined=v['dbtables']['defined'] or '[no defined tables]',
637 lazy=v['dbtables']['lazy'] or '[no lazy tables]')
638 u = web2py_uuid()
639 backtotop = A('Back to top', _href="#totop-%s" % u)
640 # Convert lazy request.vars from property to Storage so they
641 # will be displayed in the toolbar.
642 request = copy.copy(current.request)
643 request.update(vars=current.request.vars,
644 get_vars=current.request.get_vars,
645 post_vars=current.request.post_vars)
646 return DIV(
647 BUTTON('design', _onclick="document.location='%s'" % admin),
648 BUTTON('request',
649 _onclick="jQuery('#request-%s').slideToggle()" % u),
650 BUTTON('response',
651 _onclick="jQuery('#response-%s').slideToggle()" % u),
652 BUTTON('session',
653 _onclick="jQuery('#session-%s').slideToggle()" % u),
654 BUTTON('db tables',
655 _onclick="jQuery('#db-tables-%s').slideToggle()" % u),
656 BUTTON('db stats',
657 _onclick="jQuery('#db-stats-%s').slideToggle()" % u),
658 DIV(BEAUTIFY(request), backtotop,
659 _class="hidden", _id="request-%s" % u),
660 DIV(BEAUTIFY(current.session), backtotop,
661 _class="hidden", _id="session-%s" % u),
662 DIV(BEAUTIFY(current.response), backtotop,
663 _class="hidden", _id="response-%s" % u),
664 DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",
665 _id="db-tables-%s" % u),
666 DIV(BEAUTIFY(
667 dbstats), backtotop, _class="hidden", _id="db-stats-%s" % u),
668 SCRIPT("jQuery('.hidden').hide()"), _id="totop-%s" % u
669 )
670
673 """
674 defines the session object and the default values of its members (None)
675
676 response.session_storage_type : 'file', 'db', or 'cookie'
677 response.session_cookie_compression_level :
678 response.session_cookie_expires : cookie expiration
679 response.session_cookie_key : for encrypted sessions in cookies
680 response.session_id : a number or None if no session
681 response.session_id_name :
682 response.session_locked :
683 response.session_masterapp :
684 response.session_new : a new session obj is being created
685 response.session_hash : hash of the pickled loaded session
686 response.session_pickled : picked session
687
688 if session in cookie:
689
690 response.session_data_name : name of the cookie for session data
691
692 if session in db:
693
694 response.session_db_record_id :
695 response.session_db_table :
696 response.session_db_unique_key :
697
698 if session in file:
699
700 response.session_file :
701 response.session_filename :
702 """
703
704 - def connect(
705 self,
706 request=None,
707 response=None,
708 db=None,
709 tablename='web2py_session',
710 masterapp=None,
711 migrate=True,
712 separate=None,
713 check_client=False,
714 cookie_key=None,
715 cookie_expires=None,
716 compression_level=None
717 ):
718 """
719 separate can be separate=lambda(session_name): session_name[-2:]
720 and it is used to determine a session prefix.
721 separate can be True and it is set to session_name[-2:]
722 """
723 request = request or current.request
724 response = response or current.response
725 masterapp = masterapp or request.application
726 cookies = request.cookies
727
728 self._unlock(response)
729
730 response.session_masterapp = masterapp
731 response.session_id_name = 'session_id_%s' % masterapp.lower()
732 response.session_data_name = 'session_data_%s' % masterapp.lower()
733 response.session_cookie_expires = cookie_expires
734 response.session_client = str(request.client).replace(':', '.')
735 response.session_cookie_key = cookie_key
736 response.session_cookie_compression_level = compression_level
737
738 # check if there is a session_id in cookies
739 try:
740 old_session_id = cookies[response.session_id_name].value
741 except KeyError:
742 old_session_id = None
743 response.session_id = old_session_id
744
745 # if we are supposed to use cookie based session data
746 if cookie_key:
747 response.session_storage_type = 'cookie'
748 elif db:
749 response.session_storage_type = 'db'
750 else:
751 response.session_storage_type = 'file'
752 # why do we do this?
753 # because connect may be called twice, by web2py and in models.
754 # the first time there is no db yet so it should do nothing
755 if (global_settings.db_sessions is True or
756 masterapp in global_settings.db_sessions):
757 return
758
759 if response.session_storage_type == 'cookie':
760 # check if there is session data in cookies
761 if response.session_data_name in cookies:
762 session_cookie_data = cookies[response.session_data_name].value
763 else:
764 session_cookie_data = None
765 if session_cookie_data:
766 data = secure_loads(session_cookie_data, cookie_key,
767 compression_level=compression_level)
768 if data:
769 self.update(data)
770 response.session_id = True
771
772 # else if we are supposed to use file based sessions
773 elif response.session_storage_type == 'file':
774 response.session_new = False
775 response.session_file = None
776 # check if the session_id points to a valid sesion filename
777 if response.session_id:
778 if not regex_session_id.match(response.session_id):
779 response.session_id = None
780 else:
781 response.session_filename = \
782 os.path.join(up(request.folder), masterapp,
783 'sessions', response.session_id)
784 try:
785 response.session_file = \
786 open(response.session_filename, 'rb+')
787 portalocker.lock(response.session_file,
788 portalocker.LOCK_EX)
789 response.session_locked = True
790 self.update(cPickle.load(response.session_file))
791 response.session_file.seek(0)
792 oc = response.session_filename.split('/')[-1].split('-')[0]
793 if check_client and response.session_client != oc:
794 raise Exception("cookie attack")
795 except:
796 response.session_id = None
797 if not response.session_id:
798 uuid = web2py_uuid()
799 response.session_id = '%s-%s' % (response.session_client, uuid)
800 separate = separate and (lambda session_name: session_name[-2:])
801 if separate:
802 prefix = separate(response.session_id)
803 response.session_id = '%s/%s' % (prefix, response.session_id)
804 response.session_filename = \
805 os.path.join(up(request.folder), masterapp,
806 'sessions', response.session_id)
807 response.session_new = True
808
809 # else the session goes in db
810 elif response.session_storage_type == 'db':
811 if global_settings.db_sessions is not True:
812 global_settings.db_sessions.add(masterapp)
813 # if had a session on file alreday, close it (yes, can happen)
814 if response.session_file:
815 self._close(response)
816 # if on GAE tickets go also in DB
817 if settings.global_settings.web2py_runtime_gae:
818 request.tickets_db = db
819 table_migrate = (masterapp == request.application)
820 tname = tablename + '_' + masterapp
821 table = db.get(tname, None)
822 Field = db.Field
823 if table is None:
824 db.define_table(
825 tname,
826 Field('locked', 'boolean', default=False),
827 Field('client_ip', length=64),
828 Field('created_datetime', 'datetime',
829 default=request.now),
830 Field('modified_datetime', 'datetime'),
831 Field('unique_key', length=64),
832 Field('session_data', 'blob'),
833 migrate=table_migrate,
834 )
835 table = db[tname] # to allow for lazy table
836 response.session_db_table = table
837 if response.session_id:
838 # Get session data out of the database
839 try:
840 (record_id, unique_key) = response.session_id.split(':')
841 record_id = long(record_id)
842 except (TypeError,ValueError):
843 record_id = None
844
845 # Select from database
846 if record_id:
847 row = table(record_id) #,unique_key=unique_key)
848 # Make sure the session data exists in the database
849 if row:
850 # rows[0].update_record(locked=True)
851 # Unpickle the data
852 session_data = cPickle.loads(row.session_data)
853 self.update(session_data)
854 else:
855 record_id = None
856 if record_id:
857 response.session_id = '%s:%s' % (record_id, unique_key)
858 response.session_db_unique_key = unique_key
859 response.session_db_record_id = record_id
860 else:
861 response.session_id = None
862 response.session_new = True
863 # if there is no session id yet, we'll need to create a
864 # new session
865 else:
866 response.session_new = True
867
868 # set the cookie now if you know the session_id so user can set
869 # cookie attributes in controllers/models
870 # cookie will be reset later
871 # yet cookie may be reset later
872 # Removed comparison between old and new session ids - should send
873 # the cookie all the time
874 if isinstance(response.session_id,str):
875 response.cookies[response.session_id_name] = response.session_id
876 response.cookies[response.session_id_name]['path'] = '/'
877 if cookie_expires:
878 response.cookies[response.session_id_name]['expires'] = \
879 cookie_expires.strftime(FMT)
880
881 session_pickled = cPickle.dumps(self)
882 response.session_hash = hashlib.md5(session_pickled).hexdigest()
883
884 if self.flash:
885 (response.flash, self.flash) = (self.flash, None)
886
887
889
890 if clear_session:
891 self.clear()
892
893 request = current.request
894 response = current.response
895 session = response.session
896 masterapp = response.session_masterapp
897 cookies = request.cookies
898
899 if response.session_storage_type == 'cookie':
900 return
901
902 # if the session goes in file
903 if response.session_storage_type == 'file':
904 self._close(response)
905 uuid = web2py_uuid()
906 response.session_id = '%s-%s' % (response.session_client, uuid)
907 separate = (lambda s: s[-2:]) if session and response.session_id[2:3]=="/" else None
908 if separate:
909 prefix = separate(response.session_id)
910 response.session_id = '%s/%s' % \
911 (prefix, response.session_id)
912 response.session_filename = \
913 os.path.join(up(request.folder), masterapp,
914 'sessions', response.session_id)
915 response.session_new = True
916
917 # else the session goes in db
918 elif response.session_storage_type == 'db':
919 table = response.session_db_table
920
921 # verify that session_id exists
922 if response.session_file:
923 self._close(response)
924 if response.session_new:
925 return
926 # Get session data out of the database
927 if response.session_id is None:
928 return
929 (record_id, sep, unique_key) = response.session_id.partition(':')
930
931 if record_id.isdigit() and long(record_id)>0:
932 new_unique_key = web2py_uuid()
933 row = table(record_id)
934 if row and row.unique_key==unique_key:
935 table._db(table.id==record_id).update(unique_key=new_unique_key)
936 else:
937 record_id = None
938 if record_id:
939 response.session_id = '%s:%s' % (record_id, unique_key)
940 response.session_db_record_id = record_id
941 response.session_db_unique_key = new_unique_key
942 else:
943 response.session_new = True
944
946 response = current.response
947 rcookies = response.cookies
948 if self._forget and response.session_id_name in rcookies:
949 del rcookies[response.session_id_name]
950 elif self._secure and response.session_id_name in rcookies:
951 rcookies[response.session_id_name]['secure'] = True
952
968
991
994
996 if self._start_timestamp:
997 return False
998 else:
999 self._start_timestamp = datetime.datetime.today()
1000 return True
1001
1003 now = datetime.datetime.today()
1004 if not self._last_timestamp or \
1005 self._last_timestamp + datetime.timedelta(seconds=seconds) > now:
1006 self._last_timestamp = now
1007 return False
1008 else:
1009 return True
1010
1013
1017
1038
1040 session_pickled = cPickle.dumps(self)
1041 response.session_pickled = session_pickled
1042 session_hash = hashlib.md5(session_pickled).hexdigest()
1043 return response.session_hash == session_hash
1044
1046 # don't save if file-based sessions,
1047 # no session id, or session being forgotten
1048 # or no changes to session (Unless the session is new)
1049 if (not response.session_db_table or
1050 self._forget or
1051 (self._unchanged(response) and not response.session_new)):
1052 if (not response.session_db_table and
1053 global_settings.db_sessions is not True and
1054 response.session_masterapp in global_settings.db_sessions):
1055 global_settings.db_sessions.remove(response.session_masterapp)
1056 # self.clear_session_cookies()
1057 self.save_session_id_cookie()
1058 return False
1059
1060 table = response.session_db_table
1061 record_id = response.session_db_record_id
1062 if response.session_new:
1063 unique_key = web2py_uuid()
1064 else:
1065 unique_key = response.session_db_unique_key
1066
1067 session_pickled = response.session_pickled or cPickle.dumps(self)
1068
1069 dd = dict(locked=False,
1070 client_ip=response.session_client,
1071 modified_datetime=request.now,
1072 session_data=session_pickled,
1073 unique_key=unique_key)
1074 if record_id:
1075 if not table._db(table.id==record_id).update(**dd):
1076 record_id = None
1077 if not record_id:
1078 record_id = table.insert(**dd)
1079 response.session_id = '%s:%s' % (record_id, unique_key)
1080 response.session_db_unique_key = unique_key
1081 response.session_db_record_id = record_id
1082
1083 self.save_session_id_cookie()
1084 return True
1085
1091
1093 try:
1094 if (not response.session_id or self._forget
1095 or self._unchanged(response)):
1096 # self.clear_session_cookies()
1097 self.save_session_id_cookie()
1098 return False
1099 if response.session_new or not response.session_file:
1100 # Tests if the session sub-folder exists, if not, create it
1101 session_folder = os.path.dirname(response.session_filename)
1102 if not os.path.exists(session_folder): os.mkdir(session_folder)
1103 response.session_file = open(response.session_filename, 'wb')
1104 portalocker.lock(response.session_file, portalocker.LOCK_EX)
1105 response.session_locked = True
1106 if response.session_file:
1107 session_pickled = response.session_pickled or cPickle.dumps(self)
1108 response.session_file.write(session_pickled)
1109 response.session_file.truncate()
1110 finally:
1111 self._close(response)
1112
1113 self.save_session_id_cookie()
1114 return True
1115
1117 if response and response.session_file and response.session_locked:
1118 try:
1119 portalocker.unlock(response.session_file)
1120 response.session_locked = False
1121 except: # this should never happen but happens in Windows
1122 pass
1123
1132
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 14 15:17:18 2013 | http://epydoc.sourceforge.net |