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
22 from http import HTTP
23 from fileutils import up
24 from serializers import json, custom_json
25 import settings
26 from utils import web2py_uuid
27 from settings import global_settings
28
29 import hashlib
30 import portalocker
31 import cPickle
32 import cStringIO
33 import datetime
34 import re
35 import Cookie
36 import os
37 import sys
38 import traceback
39 import threading
40
41 regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$')
42
43 __all__ = ['Request', 'Response', 'Session']
44
45 current = threading.local()
46
48
49 """
50 defines the request object and the default values of its members
51
52 - env: environment variables, by gluon.main.wsgibase()
53 - cookies
54 - get_vars
55 - post_vars
56 - vars
57 - folder
58 - application
59 - function
60 - args
61 - extension
62 - now: datetime.datetime.today()
63 - restful()
64 """
65
84
86 self.uuid = '%s/%s.%s.%s' % (
87 self.application,
88 self.client.replace(':', '_'),
89 self.now.strftime('%Y-%m-%d.%H-%M-%S'),
90 web2py_uuid())
91 return self.uuid
92
94 from gluon.contrib import user_agent_parser
95 session = current.session
96 session._user_agent = session._user_agent or \
97 user_agent_parser.detect(self.env.http_user_agent)
98 return session._user_agent
99
101 def wrapper(action,self=self):
102 def f(_action=action,_self=self,*a,**b):
103 self.is_restful = True
104 method = _self.env.request_method
105 if len(_self.args) and '.' in _self.args[-1]:
106 _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1)
107 current.response.headers['Content-Type'] = \
108 contenttype(_self.extension.lower())
109 if not method in ['GET','POST','DELETE','PUT']:
110 raise HTTP(400,"invalid method")
111 rest_action = _action().get(method,None)
112 if not rest_action:
113 raise HTTP(400,"method not supported")
114 try:
115 return rest_action(*_self.args,**_self.vars)
116 except TypeError, e:
117 exc_type, exc_value, exc_traceback = sys.exc_info()
118 if len(traceback.extract_tb(exc_traceback))==1:
119 raise HTTP(400,"invalid arguments")
120 else:
121 raise e
122 f.__doc__ = action.__doc__
123 f.__name__ = action.__name__
124 return f
125 return wrapper
126
127
129
130 """
131 defines the response object and the default values of its members
132 response.write( ) can be used to write in the output html
133 """
134
136 self.status = 200
137 self.headers = Storage()
138 self.headers['X-Powered-By'] = 'web2py'
139 self.body = cStringIO.StringIO()
140 self.session_id = None
141 self.cookies = Cookie.SimpleCookie()
142 self.postprocessing = []
143 self.flash = ''
144 self.meta = Storage()
145 self.menu = []
146 self.files = []
147 self.generic_patterns = []
148 self._vars = None
149 self._caller = lambda f: f()
150 self._view_environment = None
151 self._custom_commit = None
152 self._custom_rollback = None
153
154 - def write(self, data, escape=True):
159
161 from compileapp import run_view_in
162 if len(a) > 2:
163 raise SyntaxError, 'Response.render can be called with two arguments, at most'
164 elif len(a) == 2:
165 (view, self._vars) = (a[0], a[1])
166 elif len(a) == 1 and isinstance(a[0], str):
167 (view, self._vars) = (a[0], {})
168 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read):
169 (view, self._vars) = (a[0], {})
170 elif len(a) == 1 and isinstance(a[0], dict):
171 (view, self._vars) = (None, a[0])
172 else:
173 (view, self._vars) = (None, {})
174 self._vars.update(b)
175 self._view_environment.update(self._vars)
176 if view:
177 import cStringIO
178 (obody, oview) = (self.body, self.view)
179 (self.body, self.view) = (cStringIO.StringIO(), view)
180 run_view_in(self._view_environment)
181 page = self.body.getvalue()
182 self.body.close()
183 (self.body, self.view) = (obody, oview)
184 else:
185 run_view_in(self._view_environment)
186 page = self.body.getvalue()
187 return page
188
194
196 s = ''
197 for k,f in enumerate(self.files or []):
198 if not f in self.files[:k]:
199 filename = f.lower().split('?')[0]
200 if filename.endswith('.css'):
201 s += '<link href="%s" rel="stylesheet" type="text/css" />' % f
202 elif filename.endswith('.js'):
203 s += '<script src="%s" type="text/javascript"></script>' % f
204 self.write(s,escape=False)
205
212 """
213 if a controller function::
214
215 return response.stream(file, 100)
216
217 the file content will be streamed at 100 bytes at the time
218 """
219
220 if isinstance(stream, (str, unicode)):
221 stream_file_or_304_or_206(stream,
222 chunk_size=chunk_size,
223 request=request,
224 headers=self.headers)
225
226
227
228 if hasattr(stream, 'name'):
229 filename = stream.name
230 else:
231 filename = None
232 keys = [item.lower() for item in self.headers]
233 if filename and not 'content-type' in keys:
234 self.headers['Content-Type'] = contenttype(filename)
235 if filename and not 'content-length' in keys:
236 try:
237 self.headers['Content-Length'] = \
238 os.path.getsize(filename)
239 except OSError:
240 pass
241 if request and request.env.web2py_use_wsgi_file_wrapper:
242 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
243 else:
244 wrapped = streamer(stream, chunk_size=chunk_size)
245 return wrapped
246
248 """
249 example of usage in controller::
250
251 def download():
252 return response.download(request, db)
253
254 downloads from http://..../download/filename
255 """
256
257 import contenttype as c
258 if not request.args:
259 raise HTTP(404)
260 name = request.args[-1]
261 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
262 .match(name)
263 if not items:
264 raise HTTP(404)
265 (t, f) = (items.group('table'), items.group('field'))
266 field = db[t][f]
267 try:
268 (filename, stream) = field.retrieve(name)
269 except IOError:
270 raise HTTP(404)
271 self.headers['Content-Type'] = c.contenttype(name)
272 if attachment:
273 self.headers['Content-Disposition'] = \
274 "attachment; filename=%s" % filename
275 return self.stream(stream, chunk_size = chunk_size, request=request)
276
277 - def json(self, data, default=None):
279
280 - def xmlrpc(self, request, methods):
281 """
282 assuming::
283
284 def add(a, b):
285 return a+b
286
287 if a controller function \"func\"::
288
289 return response.xmlrpc(request, [add])
290
291 the controller will be able to handle xmlrpc requests for
292 the add function. Example::
293
294 import xmlrpclib
295 connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func')
296 print connection.add(3, 4)
297
298 """
299
300 return handler(request, self, methods)
301
324
326
327 """
328 defines the session object and the default values of its members (None)
329 """
330
331 - def connect(
332 self,
333 request,
334 response,
335 db=None,
336 tablename='web2py_session',
337 masterapp=None,
338 migrate=True,
339 separate = None,
340 check_client=False,
341 ):
342 """
343 separate can be separate=lambda(session_name): session_name[-2:]
344 and it is used to determine a session prefix.
345 separate can be True and it is set to session_name[-2:]
346 """
347 if separate == True:
348 separate = lambda session_name: session_name[-2:]
349 self._unlock(response)
350 if not masterapp:
351 masterapp = request.application
352 response.session_id_name = 'session_id_%s' % masterapp.lower()
353
354 if not db:
355 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions:
356 return
357 response.session_new = False
358 client = request.client.replace(':', '.')
359 if response.session_id_name in request.cookies:
360 response.session_id = \
361 request.cookies[response.session_id_name].value
362 if regex_session_id.match(response.session_id):
363 response.session_filename = \
364 os.path.join(up(request.folder), masterapp,
365 'sessions', response.session_id)
366 else:
367 response.session_id = None
368 if response.session_id:
369 try:
370 response.session_file = \
371 open(response.session_filename, 'rb+')
372 try:
373 portalocker.lock(response.session_file,
374 portalocker.LOCK_EX)
375 response.session_locked = True
376 self.update(cPickle.load(response.session_file))
377 response.session_file.seek(0)
378 oc = response.session_filename.split('/')[-1].split('-')[0]
379 if check_client and client!=oc:
380 raise Exception, "cookie attack"
381 finally:
382 pass
383
384
385 except:
386 response.session_id = None
387 if not response.session_id:
388 uuid = web2py_uuid()
389 response.session_id = '%s-%s' % (client, uuid)
390 if separate:
391 prefix = separate(response.session_id)
392 response.session_id = '%s/%s' % (prefix,response.session_id)
393 response.session_filename = \
394 os.path.join(up(request.folder), masterapp,
395 'sessions', response.session_id)
396 response.session_new = True
397 else:
398 if global_settings.db_sessions is not True:
399 global_settings.db_sessions.add(masterapp)
400 response.session_db = True
401 if response.session_file:
402 self._close(response)
403 if settings.global_settings.web2py_runtime_gae:
404
405 request.tickets_db = db
406 if masterapp == request.application:
407 table_migrate = migrate
408 else:
409 table_migrate = False
410 tname = tablename + '_' + masterapp
411 table = db.get(tname, None)
412 if table is None:
413 table = db.define_table(
414 tname,
415 db.Field('locked', 'boolean', default=False),
416 db.Field('client_ip', length=64),
417 db.Field('created_datetime', 'datetime',
418 default=request.now),
419 db.Field('modified_datetime', 'datetime'),
420 db.Field('unique_key', length=64),
421 db.Field('session_data', 'blob'),
422 migrate=table_migrate,
423 )
424 try:
425 key = request.cookies[response.session_id_name].value
426 (record_id, unique_key) = key.split(':')
427 if record_id == '0':
428 raise Exception, 'record_id == 0'
429 rows = db(table.id == record_id).select()
430 if len(rows) == 0 or rows[0].unique_key != unique_key:
431 raise Exception, 'No record'
432
433
434
435 session_data = cPickle.loads(rows[0].session_data)
436 self.update(session_data)
437 except Exception:
438 record_id = None
439 unique_key = web2py_uuid()
440 session_data = {}
441 response._dbtable_and_field = \
442 (response.session_id_name, table, record_id, unique_key)
443 response.session_id = '%s:%s' % (record_id, unique_key)
444 response.cookies[response.session_id_name] = response.session_id
445 response.cookies[response.session_id_name]['path'] = '/'
446 self.__hash = hashlib.md5(str(self)).digest()
447 if self.flash:
448 (response.flash, self.flash) = (self.flash, None)
449
451 if self._start_timestamp:
452 return False
453 else:
454 self._start_timestamp = datetime.datetime.today()
455 return True
456
458 now = datetime.datetime.today()
459 if not self._last_timestamp or \
460 self._last_timestamp + datetime.timedelta(seconds = seconds) > now:
461 self._last_timestamp = now
462 return False
463 else:
464 return True
465
468
469 - def forget(self, response=None):
470 self._close(response)
471 self._forget = True
472
474
475
476 if not response.session_db or not response.session_id or self._forget:
477 return
478
479
480 __hash = self.__hash
481 if __hash is not None:
482 del self.__hash
483 if __hash == hashlib.md5(str(self)).digest():
484 return
485
486 (record_id_name, table, record_id, unique_key) = \
487 response._dbtable_and_field
488 dd = dict(locked=False, client_ip=request.env.remote_addr,
489 modified_datetime=request.now,
490 session_data=cPickle.dumps(dict(self)),
491 unique_key=unique_key)
492 if record_id:
493 table._db(table.id == record_id).update(**dd)
494 else:
495 record_id = table.insert(**dd)
496 response.cookies[response.session_id_name] = '%s:%s'\
497 % (record_id, unique_key)
498 response.cookies[response.session_id_name]['path'] = '/'
499
501
502
503 if response.session_db:
504 return
505
506
507 __hash = self.__hash
508 if __hash is not None:
509 del self.__hash
510 if __hash == hashlib.md5(str(self)).digest():
511 self._close(response)
512 return
513
514 if not response.session_id or self._forget:
515 self._close(response)
516 return
517
518 if response.session_new:
519
520 session_folder = os.path.dirname(response.session_filename)
521 if not os.path.exists(session_folder):
522 os.mkdir(session_folder)
523 response.session_file = open(response.session_filename, 'wb')
524 portalocker.lock(response.session_file, portalocker.LOCK_EX)
525 response.session_locked = True
526
527 if response.session_file:
528 cPickle.dump(dict(self), response.session_file)
529 response.session_file.truncate()
530 self._close(response)
531
533 if response and response.session_file and response.session_locked:
534 try:
535 portalocker.unlock(response.session_file)
536 response.session_locked = False
537 except:
538 pass
539
541 if response and response.session_file:
542 self._unlock(response)
543 try:
544 response.session_file.close()
545 del response.session_file
546 except:
547 pass
548