|
Package Dropbox ::
Package web2py ::
Package gluon ::
Module globals
|
|
1
2
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 storage import Storage, List
18 from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
19 from xmlrpc import handler
20 from contenttype import contenttype
21 from html import xmlescape, TABLE, TR, PRE, URL
22 from http import HTTP, redirect
23 from fileutils import up
24 from serializers import json, custom_json
25 import settings
26 from utils import web2py_uuid, secure_dumps, secure_loads
27 from settings import global_settings
28 import hashlib
29 import portalocker
30 import cPickle
31 import cStringIO
32 import datetime
33 import re
34 import Cookie
35 import os
36 import sys
37 import traceback
38 import threading
39
40 FMT = '%a, %d-%b-%Y %H:%M:%S PST'
41 PAST = 'Sat, 1-Jan-1971 00:00:00'
42 FUTURE = 'Tue, 1-Dec-2999 23:59:59'
43
44 try:
45 from gluon.contrib.minify import minify
46 have_minify = True
47 except ImportError:
48 have_minify = False
49
50 regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$')
51
52 __all__ = ['Request', 'Response', 'Session']
53
54 current = threading.local()
55
56 css_template = '<link href="%s" rel="stylesheet" type="text/css" />'
57 js_template = '<script src="%s" type="text/javascript"></script>'
58 coffee_template = '<script src="%s" type="text/coffee"></script>'
59 less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
60 css_inline = '<style type="text/css">\n%s\n</style>'
61 js_inline = '<script type="text/javascript">\n%s\n</script>'
62
63
65
66 """
67 defines the request object and the default values of its members
68
69 - env: environment variables, by gluon.main.wsgibase()
70 - cookies
71 - get_vars
72 - post_vars
73 - vars
74 - folder
75 - application
76 - function
77 - args
78 - extension
79 - now: datetime.datetime.today()
80 - restful()
81 """
82
84 Storage.__init__(self)
85 self.wsgi = Storage()
86 self.env = Storage()
87 self.cookies = Cookie.SimpleCookie()
88 self.get_vars = Storage()
89 self.post_vars = Storage()
90 self.vars = Storage()
91 self.folder = None
92 self.application = None
93 self.function = None
94 self.args = List()
95 self.extension = 'html'
96 self.now = datetime.datetime.now()
97 self.utcnow = datetime.datetime.utcnow()
98 self.is_restful = False
99 self.is_https = False
100 self.is_local = False
101 self.global_settings = settings.global_settings
102
104 self.uuid = '%s/%s.%s.%s' % (
105 self.application,
106 self.client.replace(':', '_'),
107 self.now.strftime('%Y-%m-%d.%H-%M-%S'),
108 web2py_uuid())
109 return self.uuid
110
123
138
139
140
142 def wrapper(action, self=self):
143 def f(_action=action, _self=self, *a, **b):
144 self.is_restful = True
145 method = _self.env.request_method
146 if len(_self.args) and '.' in _self.args[-1]:
147 _self.args[-
148 1], _self.extension = _self.args[-1].rsplit('.', 1)
149 current.response.headers['Content-Type'] = \
150 contenttype(_self.extension.lower())
151 if not method in ['GET', 'POST', 'DELETE', 'PUT']:
152 raise HTTP(400, "invalid method")
153 rest_action = _action().get(method, None)
154 if not rest_action:
155 raise HTTP(400, "method not supported")
156 try:
157 return rest_action(*_self.args, **_self.vars)
158 except TypeError, e:
159 exc_type, exc_value, exc_traceback = sys.exc_info()
160 if len(traceback.extract_tb(exc_traceback)) == 1:
161 raise HTTP(400, "invalid arguments")
162 else:
163 raise e
164 f.__doc__ = action.__doc__
165 f.__name__ = action.__name__
166 return f
167 return wrapper
168
169
171
172 """
173 defines the response object and the default values of its members
174 response.write( ) can be used to write in the output html
175 """
176
178 Storage.__init__(self)
179 self.status = 200
180 self.headers = dict()
181 self.headers['X-Powered-By'] = 'web2py'
182 self.body = cStringIO.StringIO()
183 self.session_id = None
184 self.cookies = Cookie.SimpleCookie()
185 self.postprocessing = []
186 self.flash = ''
187 self.meta = Storage()
188 self.menu = []
189 self.files = []
190 self.generic_patterns = []
191 self.delimiters = ('{{', '}}')
192 self._vars = None
193 self._caller = lambda f: f()
194 self._view_environment = None
195 self._custom_commit = None
196 self._custom_rollback = None
197
198 - def write(self, data, escape=True):
203
205 from compileapp import run_view_in
206 if len(a) > 2:
207 raise SyntaxError(
208 'Response.render can be called with two arguments, at most')
209 elif len(a) == 2:
210 (view, self._vars) = (a[0], a[1])
211 elif len(a) == 1 and isinstance(a[0], str):
212 (view, self._vars) = (a[0], {})
213 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read):
214 (view, self._vars) = (a[0], {})
215 elif len(a) == 1 and isinstance(a[0], dict):
216 (view, self._vars) = (None, a[0])
217 else:
218 (view, self._vars) = (None, {})
219 self._vars.update(b)
220 self._view_environment.update(self._vars)
221 if view:
222 import cStringIO
223 (obody, oview) = (self.body, self.view)
224 (self.body, self.view) = (cStringIO.StringIO(), view)
225 run_view_in(self._view_environment)
226 page = self.body.getvalue()
227 self.body.close()
228 (self.body, self.view) = (obody, oview)
229 else:
230 run_view_in(self._view_environment)
231 page = self.body.getvalue()
232 return page
233
239
241
242 """
243 Caching method for writing out files.
244 By default, caches in ram for 5 minutes. To change,
245 response.cache_includes = (cache_method, time_expire).
246 Example: (cache.disk, 60) # caches to disk for 1 minute.
247 """
248 from gluon import URL
249
250 files = []
251 has_js = has_css = False
252 for item in self.files:
253 if extensions and not item.split('.')[-1] in extensions:
254 continue
255 if item in files:
256 continue
257 if item.endswith('.js'):
258 has_js = True
259 if item.endswith('.css'):
260 has_css = True
261 files.append(item)
262
263 if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
264
265 key = hashlib.md5(repr(files)).hexdigest()
266
267 cache = self.cache_includes or (current.cache.ram, 60 * 5)
268
269 def call_minify(files=files):
270 return minify.minify(files,
271 URL('static', 'temp'),
272 current.request.folder,
273 self.optimize_css,
274 self.optimize_js)
275 if cache:
276 cache_model, time_expire = cache
277 files = cache_model('response.files.minified/' + key,
278 call_minify,
279 time_expire)
280 else:
281 files = call_minify()
282 s = ''
283 for item in files:
284 if isinstance(item, str):
285 f = item.lower().split('?')[0]
286 if self.static_version:
287 item = item.replace(
288 '/static/', '/static/_%s/' % self.static_version, 1)
289 if f.endswith('.css'):
290 s += css_template % item
291 elif f.endswith('.js'):
292 s += js_template % item
293 elif f.endswith('.coffee'):
294 s += coffee_template % item
295 elif f.endswith('.less'):
296 s += less_template % item
297 elif isinstance(item, (list, tuple)):
298 f = item[0]
299 if f == 'css:inline':
300 s += css_inline % item[1]
301 elif f == 'js:inline':
302 s += js_inline % item[1]
303 self.write(s, escape=False)
304
305 - def stream(
306 self,
307 stream,
308 chunk_size=DEFAULT_CHUNK_SIZE,
309 request=None,
310 attachment=False,
311 filename=None,
312 ):
313 """
314 if a controller function::
315
316 return response.stream(file, 100)
317
318 the file content will be streamed at 100 bytes at the time
319
320 Optional kwargs:
321 (for custom stream calls)
322 attachment=True # Send as attachment. Usually creates a
323 # pop-up download window on browsers
324 filename=None # The name for the attachment
325
326 Note: for using the stream name (filename) with attachments
327 the option must be explicitly set as function parameter(will
328 default to the last request argument otherwise)
329 """
330
331 headers = self.headers
332
333 keys = [item.lower() for item in headers]
334 if attachment:
335 if filename is None:
336 attname = ""
337 else:
338 attname = filename
339 headers["Content-Disposition"] = \
340 "attachment;filename=%s" % attname
341
342 if not request:
343 request = current.request
344 if isinstance(stream, (str, unicode)):
345 stream_file_or_304_or_206(stream,
346 chunk_size=chunk_size,
347 request=request,
348 headers=headers,
349 status=self.status)
350
351
352 if hasattr(stream, 'name'):
353 filename = stream.name
354
355 if filename and not 'content-type' in keys:
356 headers['Content-Type'] = contenttype(filename)
357 if filename and not 'content-length' in keys:
358 try:
359 headers['Content-Length'] = \
360 os.path.getsize(filename)
361 except OSError:
362 pass
363
364 env = request.env
365
366 if request.is_https and isinstance(env.http_user_agent, str) and \
367 not re.search(r'Opera', env.http_user_agent) and \
368 re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
369 headers['Pragma'] = 'cache'
370 headers['Cache-Control'] = 'private'
371
372 if request and env.web2py_use_wsgi_file_wrapper:
373 wrapped = env.wsgi_file_wrapper(stream, chunk_size)
374 else:
375 wrapped = streamer(stream, chunk_size=chunk_size)
376 return wrapped
377
379 """
380 example of usage in controller::
381
382 def download():
383 return response.download(request, db)
384
385 downloads from http://..../download/filename
386 """
387
388 if not request.args:
389 raise HTTP(404)
390 name = request.args[-1]
391 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
392 .match(name)
393 if not items:
394 raise HTTP(404)
395 (t, f) = (items.group('table'), items.group('field'))
396 try:
397 field = db[t][f]
398 except AttributeError:
399 raise HTTP(404)
400 try:
401 (filename, stream) = field.retrieve(name)
402 except IOError:
403 raise HTTP(404)
404 headers = self.headers
405 headers['Content-Type'] = contenttype(name)
406 if attachment:
407 headers['Content-Disposition'] = \
408 'attachment; filename="%s"' % filename.replace('"','\"')
409 return self.stream(stream, chunk_size=chunk_size, request=request)
410
411 - def json(self, data, default=None):
413
414 - def xmlrpc(self, request, methods):
415 """
416 assuming::
417
418 def add(a, b):
419 return a+b
420
421 if a controller function \"func\"::
422
423 return response.xmlrpc(request, [add])
424
425 the controller will be able to handle xmlrpc requests for
426 the add function. Example::
427
428 import xmlrpclib
429 connection = xmlrpclib.ServerProxy(
430 'http://hostname/app/contr/func')
431 print connection.add(3, 4)
432
433 """
434
435 return handler(request, self, methods)
436
478
479
481
482 """
483 defines the session object and the default values of its members (None)
484 """
485
486 - def connect(
487 self,
488 request=None,
489 response=None,
490 db=None,
491 tablename='web2py_session',
492 masterapp=None,
493 migrate=True,
494 separate=None,
495 check_client=False,
496 cookie_key=None,
497 cookie_expires=None,
498 compression_level=None
499 ):
500 """
501 separate can be separate=lambda(session_name): session_name[-2:]
502 and it is used to determine a session prefix.
503 separate can be True and it is set to session_name[-2:]
504 """
505 if request is None:
506 request = current.request
507 if response is None:
508 response = current.response
509 if separate is True:
510 separate = lambda session_name: session_name[-2:]
511 self._unlock(response)
512 if not masterapp:
513 masterapp = request.application
514 response.session_id_name = 'session_id_%s' % masterapp.lower()
515 response.session_data_name = 'session_data_%s' % masterapp.lower()
516 response.session_cookie_expires = cookie_expires
517
518
519 cookies = request.cookies
520
521
522 if response.session_id_name in cookies:
523 response.session_id = \
524 cookies[response.session_id_name].value
525 else:
526 response.session_id = None
527
528
529 if response.session_data_name in cookies:
530 session_cookie_data = cookies[response.session_data_name].value
531 else:
532 session_cookie_data = None
533
534
535 if cookie_key:
536 response.session_storage_type = 'cookie'
537 response.session_cookie_key = cookie_key
538 response.session_cookie_compression_level = compression_level
539 if session_cookie_data:
540 data = secure_loads(session_cookie_data, cookie_key,
541 compression_level=compression_level)
542 if data:
543 self.update(data)
544
545 elif not db:
546 response.session_storage_type = 'file'
547 if global_settings.db_sessions is True \
548 or masterapp in global_settings.db_sessions:
549 return
550 response.session_new = False
551 client = request.client and request.client.replace(':', '.')
552 if response.session_id:
553 if regex_session_id.match(response.session_id):
554 response.session_filename = \
555 os.path.join(up(request.folder), masterapp,
556 'sessions', response.session_id)
557 else:
558 response.session_id = None
559
560 if response.session_id and not session_cookie_data:
561
562 try:
563 response.session_file = \
564 open(response.session_filename, 'rb+')
565 try:
566 portalocker.lock(response.session_file,
567 portalocker.LOCK_EX)
568 response.session_locked = True
569 self.update(cPickle.load(response.session_file))
570 response.session_file.seek(0)
571 oc = response.session_filename.split('/')[-1]\
572 .split('-')[0]
573 if check_client and client != oc:
574 raise Exception("cookie attack")
575 except:
576 response.session_id = None
577 finally:
578 pass
579
580
581 except:
582 response.session_file = None
583 if not response.session_id:
584 uuid = web2py_uuid()
585 response.session_id = '%s-%s' % (client, uuid)
586 if separate:
587 prefix = separate(response.session_id)
588 response.session_id = '%s/%s' % \
589 (prefix, response.session_id)
590 response.session_filename = \
591 os.path.join(up(request.folder), masterapp,
592 'sessions', response.session_id)
593 response.session_new = True
594
595 else:
596 response.session_storage_type = 'db'
597 if global_settings.db_sessions is not True:
598 global_settings.db_sessions.add(masterapp)
599 if response.session_file:
600 self._close(response)
601 if settings.global_settings.web2py_runtime_gae:
602
603 request.tickets_db = db
604 if masterapp == request.application:
605 table_migrate = migrate
606 else:
607 table_migrate = False
608 tname = tablename + '_' + masterapp
609 table = db.get(tname, None)
610 Field = db.Field
611 if table is None:
612 db.define_table(
613 tname,
614 Field('locked', 'boolean', default=False),
615 Field('client_ip', length=64),
616 Field('created_datetime', 'datetime',
617 default=request.now),
618 Field('modified_datetime', 'datetime'),
619 Field('unique_key', length=64),
620 Field('session_data', 'blob'),
621 migrate=table_migrate,
622 )
623 table = db[tname]
624 try:
625
626
627 (record_id, unique_key) = response.session_id.split(':')
628 if record_id == '0':
629 raise Exception('record_id == 0')
630
631 if not session_cookie_data:
632 rows = db(table.id == record_id).select()
633
634 if len(rows) == 0 or rows[0].unique_key != unique_key:
635 raise Exception('No record')
636
637
638 session_data = cPickle.loads(rows[0].session_data)
639 self.update(session_data)
640 except Exception:
641 record_id = None
642 unique_key = web2py_uuid()
643 session_data = {}
644 response.session_id = '%s:%s' % (record_id, unique_key)
645 response.session_db_table = table
646 response.session_db_record_id = record_id
647 response.session_db_unique_key = unique_key
648 rcookies = response.cookies
649 rcookies[response.session_id_name] = response.session_id
650 rcookies[response.session_id_name]['path'] = '/'
651 if cookie_expires:
652 rcookies[response.session_id_name][
653 'expires'] = cookie_expires.strftime(FMT)
654
655
656 if session_cookie_data:
657 rcookies[response.session_data_name] = 'expired'
658 rcookies[response.session_data_name]['path'] = '/'
659 rcookies[response.session_data_name]['expires'] = PAST
660 if self.flash:
661 (response.flash, self.flash) = (self.flash, None)
662
664 previous_session_hash = self.pop('_session_hash', None)
665 Storage.clear(self)
666 if previous_session_hash:
667 self._session_hash = previous_session_hash
668
670 if self._start_timestamp:
671 return False
672 else:
673 self._start_timestamp = datetime.datetime.today()
674 return True
675
677 now = datetime.datetime.today()
678 if not self._last_timestamp or \
679 self._last_timestamp + datetime.timedelta(seconds=seconds) > now:
680 self._last_timestamp = now
681 return False
682 else:
683 return True
684
687
688 - def forget(self, response=None):
691
693 if response.session_storage_type != 'cookie':
694 return False
695 name = response.session_data_name
696 value = secure_dumps(dict(self), response.session_cookie_key, compression_level=response.session_cookie_compression_level)
697 expires = response.session_cookie_expires
698 rcookies = response.cookies
699 rcookies.pop(name, None)
700 rcookies[name] = value
701 rcookies[name]['path'] = '/'
702 if expires:
703 rcookies[name]['expires'] = expires.strftime(FMT)
704 return True
705
707 previous_session_hash = self.pop('_session_hash', None)
708 if not previous_session_hash and not \
709 any(value is not None for value in self.itervalues()):
710 return True
711 session_pickled = cPickle.dumps(dict(self))
712 session_hash = hashlib.md5(session_pickled).hexdigest()
713 if previous_session_hash == session_hash:
714 return True
715 else:
716 self._session_hash = session_hash
717 return False
718
720
721
722
723 if response.session_storage_type != 'db' or not response.session_id \
724 or self._forget or self._unchanged():
725 return False
726
727 table = response.session_db_table
728 record_id = response.session_db_record_id
729 unique_key = response.session_db_unique_key
730
731 dd = dict(locked=False,
732 client_ip=request.client.replace(':', '.'),
733 modified_datetime=request.now,
734 session_data=cPickle.dumps(dict(self)),
735 unique_key=unique_key)
736 if record_id:
737 table._db(table.id == record_id).update(**dd)
738 else:
739 record_id = table.insert(**dd)
740
741 cookies, session_id_name = response.cookies, response.session_id_name
742 cookies[session_id_name] = '%s:%s' % (record_id, unique_key)
743 cookies[session_id_name]['path'] = '/'
744 return True
745
750
772
780
789