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