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