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
67 self.wsgi = Storage()
68 self.env = Storage()
69 self.cookies = Cookie.SimpleCookie()
70 self.get_vars = Storage()
71 self.post_vars = Storage()
72 self.vars = Storage()
73 self.folder = None
74 self.application = None
75 self.function = None
76 self.args = List()
77 self.extension = None
78 self.now = datetime.datetime.now()
79 self.is_restful = False
80 self.is_https = False
81 self.is_local = False
82
84 self.uuid = '%s/%s.%s.%s' % (
85 self.application,
86 self.client.replace(':', '_'),
87 self.now.strftime('%Y-%m-%d.%H-%M-%S'),
88 web2py_uuid())
89 return self.uuid
90
92 from gluon.contrib import user_agent_parser
93 session = current.session
94 session._user_agent = session._user_agent or \
95 user_agent_parser.detect(self.env.http_user_agent)
96 return session._user_agent
97
99 def wrapper(action,self=self):
100 def f(_action=action,_self=self,*a,**b):
101 self.is_restful = True
102 method = _self.env.request_method
103 if len(_self.args) and '.' in _self.args[-1]:
104 _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1)
105 if not method in ['GET','POST','DELETE','PUT']:
106 raise HTTP(400,"invalid method")
107 rest_action = _action().get(method,None)
108 if not rest_action:
109 raise HTTP(400,"method not supported")
110 try:
111 return rest_action(*_self.args,**_self.vars)
112 except TypeError, e:
113 exc_type, exc_value, exc_traceback = sys.exc_info()
114 if len(traceback.extract_tb(exc_traceback))==1:
115 raise HTTP(400,"invalid arguments")
116 else:
117 raise e
118 f.__doc__ = action.__doc__
119 f.__name__ = action.__name__
120 return f
121 return wrapper
122
123
125
126 """
127 defines the response object and the default values of its members
128 response.write( ) can be used to write in the output html
129 """
130
132 self.status = 200
133 self.headers = Storage()
134 self.headers['X-Powered-By'] = 'web2py'
135 self.body = cStringIO.StringIO()
136 self.session_id = None
137 self.cookies = Cookie.SimpleCookie()
138 self.postprocessing = []
139 self.flash = ''
140 self.meta = Storage()
141 self.menu = []
142 self.files = []
143 self.generic_patterns = []
144 self._vars = None
145 self._caller = lambda f: f()
146 self._view_environment = None
147 self._custom_commit = None
148 self._custom_rollback = None
149
150 - def write(self, data, escape=True):
151 if not escape:
152 self.body.write(str(data))
153 else:
154 self.body.write(xmlescape(data))
155
157 from compileapp import run_view_in
158 if len(a) > 2:
159 raise SyntaxError, 'Response.render can be called with two arguments, at most'
160 elif len(a) == 2:
161 (view, self._vars) = (a[0], a[1])
162 elif len(a) == 1 and isinstance(a[0], str):
163 (view, self._vars) = (a[0], {})
164 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read):
165 (view, self._vars) = (a[0], {})
166 elif len(a) == 1 and isinstance(a[0], dict):
167 (view, self._vars) = (None, a[0])
168 else:
169 (view, self._vars) = (None, {})
170 self._vars.update(b)
171 self._view_environment.update(self._vars)
172 if view:
173 import cStringIO
174 (obody, oview) = (self.body, self.view)
175 (self.body, self.view) = (cStringIO.StringIO(), view)
176 run_view_in(self._view_environment)
177 page = self.body.getvalue()
178 self.body.close()
179 (self.body, self.view) = (obody, oview)
180 else:
181 run_view_in(self._view_environment)
182 page = self.body.getvalue()
183 return page
184
191 """
192 if a controller function::
193
194 return response.stream(file, 100)
195
196 the file content will be streamed at 100 bytes at the time
197 """
198
199 if isinstance(stream, (str, unicode)):
200 stream_file_or_304_or_206(stream,
201 chunk_size=chunk_size,
202 request=request,
203 headers=self.headers)
204
205
206
207 if hasattr(stream, 'name'):
208 filename = stream.name
209 else:
210 filename = None
211 keys = [item.lower() for item in self.headers]
212 if filename and not 'content-type' in keys:
213 self.headers['Content-Type'] = contenttype(filename)
214 if filename and not 'content-length' in keys:
215 try:
216 self.headers['Content-Length'] = \
217 os.path.getsize(filename)
218 except OSError:
219 pass
220 if request and request.env.web2py_use_wsgi_file_wrapper:
221 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
222 else:
223 wrapped = streamer(stream, chunk_size=chunk_size)
224 return wrapped
225
227 """
228 example of usage in controller::
229
230 def download():
231 return response.download(request, db)
232
233 downloads from http://..../download/filename
234 """
235
236 import contenttype as c
237 if not request.args:
238 raise HTTP(404)
239 name = request.args[-1]
240 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
241 .match(name)
242 if not items:
243 raise HTTP(404)
244 (t, f) = (items.group('table'), items.group('field'))
245 field = db[t][f]
246 try:
247 (filename, stream) = field.retrieve(name)
248 except IOError:
249 raise HTTP(404)
250 self.headers['Content-Type'] = c.contenttype(name)
251 if attachment:
252 self.headers['Content-Disposition'] = \
253 "attachment; filename=%s" % filename
254 return self.stream(stream, chunk_size = chunk_size, request=request)
255
256 - def json(self, data, default=None):
258
259 - def xmlrpc(self, request, methods):
260 """
261 assuming::
262
263 def add(a, b):
264 return a+b
265
266 if a controller function \"func\"::
267
268 return response.xmlrpc(request, [add])
269
270 the controller will be able to handle xmlrpc requests for
271 the add function. Example::
272
273 import xmlrpclib
274 connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func')
275 print connection.add(3, 4)
276
277 """
278
279 return handler(request, self, methods)
280
303
305
306 """
307 defines the session object and the default values of its members (None)
308 """
309
310 - def connect(
311 self,
312 request,
313 response,
314 db=None,
315 tablename='web2py_session',
316 masterapp=None,
317 migrate=True,
318 separate = None,
319 check_client=False,
320 ):
321 """
322 separate can be separate=lambda(session_name): session_name[-2:]
323 and it is used to determine a session prefix.
324 separate can be True and it is set to session_name[-2:]
325 """
326 if separate == True:
327 separate = lambda session_name: session_name[-2:]
328 self._unlock(response)
329 if not masterapp:
330 masterapp = request.application
331 response.session_id_name = 'session_id_%s' % masterapp.lower()
332
333 if not db:
334 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions:
335 return
336 response.session_new = False
337 client = request.client.replace(':', '.')
338 if response.session_id_name in request.cookies:
339 response.session_id = \
340 request.cookies[response.session_id_name].value
341 if regex_session_id.match(response.session_id):
342 response.session_filename = \
343 os.path.join(up(request.folder), masterapp,
344 'sessions', response.session_id)
345 else:
346 response.session_id = None
347 if response.session_id:
348 try:
349 response.session_file = \
350 open(response.session_filename, 'rb+')
351 try:
352 portalocker.lock(response.session_file,
353 portalocker.LOCK_EX)
354 response.session_locked = True
355 self.update(cPickle.load(response.session_file))
356 response.session_file.seek(0)
357 oc = response.session_filename.split('/')[-1].split('-')[0]
358 if check_client and client!=oc:
359 raise Exception, "cookie attack"
360 finally:
361 pass
362
363
364 except:
365 response.session_id = None
366 if not response.session_id:
367 uuid = web2py_uuid()
368 response.session_id = '%s-%s' % (client, uuid)
369 if separate:
370 prefix = separate(response.session_id)
371 response.session_id = '%s/%s' % (prefix,response.session_id)
372 response.session_filename = \
373 os.path.join(up(request.folder), masterapp,
374 'sessions', response.session_id)
375 response.session_new = True
376 else:
377 if global_settings.db_sessions is not True:
378 global_settings.db_sessions.add(masterapp)
379 response.session_db = True
380 if response.session_file:
381 self._close(response)
382 if settings.global_settings.web2py_runtime_gae:
383
384 request.tickets_db = db
385 if masterapp == request.application:
386 table_migrate = migrate
387 else:
388 table_migrate = False
389 tname = tablename + '_' + masterapp
390 table = db.get(tname, None)
391 if table is None:
392 table = db.define_table(
393 tname,
394 db.Field('locked', 'boolean', default=False),
395 db.Field('client_ip', length=64),
396 db.Field('created_datetime', 'datetime',
397 default=request.now),
398 db.Field('modified_datetime', 'datetime'),
399 db.Field('unique_key', length=64),
400 db.Field('session_data', 'blob'),
401 migrate=table_migrate,
402 )
403 try:
404 key = request.cookies[response.session_id_name].value
405 (record_id, unique_key) = key.split(':')
406 if record_id == '0':
407 raise Exception, 'record_id == 0'
408 rows = db(table.id == record_id).select()
409 if len(rows) == 0 or rows[0].unique_key != unique_key:
410 raise Exception, 'No record'
411
412
413
414 session_data = cPickle.loads(rows[0].session_data)
415 self.update(session_data)
416 except Exception:
417 record_id = None
418 unique_key = web2py_uuid()
419 session_data = {}
420 response._dbtable_and_field = \
421 (response.session_id_name, table, record_id, unique_key)
422 response.session_id = '%s:%s' % (record_id, unique_key)
423 response.cookies[response.session_id_name] = response.session_id
424 response.cookies[response.session_id_name]['path'] = '/'
425 self.__hash = hashlib.md5(str(self)).digest()
426 if self.flash:
427 (response.flash, self.flash) = (self.flash, None)
428
430 if self._start_timestamp:
431 return False
432 else:
433 self._start_timestamp = datetime.datetime.today()
434 return True
435
437 now = datetime.datetime.today()
438 if not self._last_timestamp or \
439 self._last_timestamp + datetime.timedelta(seconds = seconds) > now:
440 self._last_timestamp = now
441 return False
442 else:
443 return True
444
447
448 - def forget(self, response=None):
449 self._close(response)
450 self._forget = True
451
453
454
455 if not response.session_db or not response.session_id or self._forget:
456 return
457
458
459 __hash = self.__hash
460 if __hash is not None:
461 del self.__hash
462 if __hash == hashlib.md5(str(self)).digest():
463 return
464
465 (record_id_name, table, record_id, unique_key) = \
466 response._dbtable_and_field
467 dd = dict(locked=False, client_ip=request.env.remote_addr,
468 modified_datetime=request.now,
469 session_data=cPickle.dumps(dict(self)),
470 unique_key=unique_key)
471 if record_id:
472 table._db(table.id == record_id).update(**dd)
473 else:
474 record_id = table.insert(**dd)
475 response.cookies[response.session_id_name] = '%s:%s'\
476 % (record_id, unique_key)
477 response.cookies[response.session_id_name]['path'] = '/'
478
480
481
482 if response.session_db:
483 return
484
485
486 __hash = self.__hash
487 if __hash is not None:
488 del self.__hash
489 if __hash == hashlib.md5(str(self)).digest():
490 self._close(response)
491 return
492
493 if not response.session_id or self._forget:
494 self._close(response)
495 return
496
497 if response.session_new:
498
499 session_folder = os.path.dirname(response.session_filename)
500 if not os.path.exists(session_folder):
501 os.mkdir(session_folder)
502 response.session_file = open(response.session_filename, 'wb')
503 portalocker.lock(response.session_file, portalocker.LOCK_EX)
504 response.session_locked = True
505
506 if response.session_file:
507 cPickle.dump(dict(self), response.session_file)
508 response.session_file.truncate()
509 self._close(response)
510
512 if response and response.session_file and response.session_locked:
513 try:
514 portalocker.unlock(response.session_file)
515 response.session_locked = False
516 except:
517 pass
518
520 if response and response.session_file:
521 self._unlock(response)
522 try:
523 response.session_file.close()
524 del response.session_file
525 except:
526 pass
527