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
295
297
298 """
299 defines the session object and the default values of its members (None)
300 """
301
302 - def connect(
303 self,
304 request,
305 response,
306 db=None,
307 tablename='web2py_session',
308 masterapp=None,
309 migrate=True,
310 separate = None,
311 check_client=False,
312 ):
313 """
314 separate can be separate=lambda(session_name): session_name[-2:]
315 and it is used to determine a session prefix.
316 separate can be True and it is set to session_name[-2:]
317 """
318 if separate == True:
319 separate = lambda session_name: session_name[-2:]
320 self._unlock(response)
321 if not masterapp:
322 masterapp = request.application
323 response.session_id_name = 'session_id_%s' % masterapp.lower()
324
325 if not db:
326 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions:
327 return
328 response.session_new = False
329 client = request.client.replace(':', '.')
330 if response.session_id_name in request.cookies:
331 response.session_id = \
332 request.cookies[response.session_id_name].value
333 if regex_session_id.match(response.session_id):
334 response.session_filename = \
335 os.path.join(up(request.folder), masterapp,
336 'sessions', response.session_id)
337 else:
338 response.session_id = None
339 if response.session_id:
340 try:
341 response.session_file = \
342 open(response.session_filename, 'rb+')
343 try:
344 portalocker.lock(response.session_file,
345 portalocker.LOCK_EX)
346 response.session_locked = True
347 self.update(cPickle.load(response.session_file))
348 response.session_file.seek(0)
349 oc = response.session_filename.split('/')[-1].split('-')[0]
350 if check_client and client!=oc:
351 raise Exception, "cookie attack"
352 finally:
353 pass
354
355
356 except:
357 response.session_id = None
358 if not response.session_id:
359 uuid = web2py_uuid()
360 response.session_id = '%s-%s' % (client, uuid)
361 if separate:
362 prefix = separate(response.session_id)
363 response.session_id = '%s/%s' % (prefix,response.session_id)
364 response.session_filename = \
365 os.path.join(up(request.folder), masterapp,
366 'sessions', response.session_id)
367 response.session_new = True
368 else:
369 if global_settings.db_sessions is not True:
370 global_settings.db_sessions.add(masterapp)
371 response.session_db = True
372 if response.session_file:
373 self._close(response)
374 if settings.global_settings.web2py_runtime_gae:
375
376 request.tickets_db = db
377 if masterapp == request.application:
378 table_migrate = migrate
379 else:
380 table_migrate = False
381 tname = tablename + '_' + masterapp
382 table = db.get(tname, None)
383 if table is None:
384 table = db.define_table(
385 tname,
386 db.Field('locked', 'boolean', default=False),
387 db.Field('client_ip', length=64),
388 db.Field('created_datetime', 'datetime',
389 default=request.now),
390 db.Field('modified_datetime', 'datetime'),
391 db.Field('unique_key', length=64),
392 db.Field('session_data', 'blob'),
393 migrate=table_migrate,
394 )
395 try:
396 key = request.cookies[response.session_id_name].value
397 (record_id, unique_key) = key.split(':')
398 if record_id == '0':
399 raise Exception, 'record_id == 0'
400 rows = db(table.id == record_id).select()
401 if len(rows) == 0 or rows[0].unique_key != unique_key:
402 raise Exception, 'No record'
403
404
405
406 session_data = cPickle.loads(rows[0].session_data)
407 self.update(session_data)
408 except Exception:
409 record_id = None
410 unique_key = web2py_uuid()
411 session_data = {}
412 response._dbtable_and_field = \
413 (response.session_id_name, table, record_id, unique_key)
414 response.session_id = '%s:%s' % (record_id, unique_key)
415 response.cookies[response.session_id_name] = response.session_id
416 response.cookies[response.session_id_name]['path'] = '/'
417 self.__hash = hashlib.md5(str(self)).digest()
418 if self.flash:
419 (response.flash, self.flash) = (self.flash, None)
420
422 if self._start_timestamp:
423 return False
424 else:
425 self._start_timestamp = datetime.datetime.today()
426 return True
427
429 now = datetime.datetime.today()
430 if not self._last_timestamp or \
431 self._last_timestamp + datetime.timedelta(seconds = seconds) > now:
432 self._last_timestamp = now
433 return False
434 else:
435 return True
436
439
440 - def forget(self, response=None):
441 self._close(response)
442 self._forget = True
443
445
446
447 if not response.session_db or not response.session_id or self._forget:
448 return
449
450
451 __hash = self.__hash
452 if __hash is not None:
453 del self.__hash
454 if __hash == hashlib.md5(str(self)).digest():
455 return
456
457 (record_id_name, table, record_id, unique_key) = \
458 response._dbtable_and_field
459 dd = dict(locked=False, client_ip=request.env.remote_addr,
460 modified_datetime=request.now,
461 session_data=cPickle.dumps(dict(self)),
462 unique_key=unique_key)
463 if record_id:
464 table._db(table.id == record_id).update(**dd)
465 else:
466 record_id = table.insert(**dd)
467 response.cookies[response.session_id_name] = '%s:%s'\
468 % (record_id, unique_key)
469 response.cookies[response.session_id_name]['path'] = '/'
470
472
473
474 if response.session_db:
475 return
476
477
478 __hash = self.__hash
479 if __hash is not None:
480 del self.__hash
481 if __hash == hashlib.md5(str(self)).digest():
482 self._close(response)
483 return
484
485 if not response.session_id or self._forget:
486 self._close(response)
487 return
488
489 if response.session_new:
490
491 session_folder = os.path.dirname(response.session_filename)
492 if not os.path.exists(session_folder):
493 os.mkdir(session_folder)
494 response.session_file = open(response.session_filename, 'wb')
495 portalocker.lock(response.session_file, portalocker.LOCK_EX)
496 response.session_locked = True
497
498 if response.session_file:
499 cPickle.dump(dict(self), response.session_file)
500 response.session_file.truncate()
501 self._close(response)
502
504 if response and response.session_file and response.session_locked:
505 try:
506 portalocker.unlock(response.session_file)
507 response.session_locked = False
508 except:
509 pass
510
512 if response and response.session_file:
513 self._unlock(response)
514 try:
515 response.session_file.close()
516 del response.session_file
517 except:
518 pass
519