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 try:
343 portalocker.lock(response.session_file,
344 portalocker.LOCK_EX)
345 response.session_locked = True
346 self.update(cPickle.load(response.session_file))
347 response.session_file.seek(0)
348 oc = response.session_filename.split('/')[-1].split('-')[0]
349 if check_client and client!=oc:
350 raise Exception, "cookie attack"
351 finally:
352 pass
353
354
355 except:
356 response.session_id = None
357 if not response.session_id:
358 uuid = web2py_uuid()
359 response.session_id = '%s-%s' % (client, uuid)
360 if separate:
361 prefix = separate(response.session_id)
362 response.session_id = '%s/%s' % (prefix,response.session_id)
363 response.session_filename = \
364 os.path.join(up(request.folder), masterapp,
365 'sessions', response.session_id)
366 response.session_new = True
367 else:
368 if global_settings.db_sessions is not True:
369 global_settings.db_sessions.add(masterapp)
370 response.session_db = True
371 if response.session_file:
372 self._close(response)
373 if settings.global_settings.web2py_runtime_gae:
374
375 request.tickets_db = db
376 if masterapp == request.application:
377 table_migrate = migrate
378 else:
379 table_migrate = False
380 tname = tablename + '_' + masterapp
381 table = db.get(tname, None)
382 if table is None:
383 table = db.define_table(
384 tname,
385 db.Field('locked', 'boolean', default=False),
386 db.Field('client_ip', length=64),
387 db.Field('created_datetime', 'datetime',
388 default=request.now),
389 db.Field('modified_datetime', 'datetime'),
390 db.Field('unique_key', length=64),
391 db.Field('session_data', 'blob'),
392 migrate=table_migrate,
393 )
394 try:
395 key = request.cookies[response.session_id_name].value
396 (record_id, unique_key) = key.split(':')
397 if record_id == '0':
398 raise Exception, 'record_id == 0'
399 rows = db(table.id == record_id).select()
400 if len(rows) == 0 or rows[0].unique_key != unique_key:
401 raise Exception, 'No record'
402
403
404
405 session_data = cPickle.loads(rows[0].session_data)
406 self.update(session_data)
407 except Exception:
408 record_id = None
409 unique_key = web2py_uuid()
410 session_data = {}
411 response._dbtable_and_field = \
412 (response.session_id_name, table, record_id, unique_key)
413 response.session_id = '%s:%s' % (record_id, unique_key)
414 response.cookies[response.session_id_name] = response.session_id
415 response.cookies[response.session_id_name]['path'] = '/'
416 self.__hash = hashlib.md5(str(self)).digest()
417 if self.flash:
418 (response.flash, self.flash) = (self.flash, None)
419
421 if self._start_timestamp:
422 return False
423 else:
424 self._start_timestamp = datetime.datetime.today()
425 return True
426
428 now = datetime.datetime.today()
429 if not self._last_timestamp or \
430 self._last_timestamp + datetime.timedelta(seconds = seconds) > now:
431 self._last_timestamp = now
432 return False
433 else:
434 return True
435
438
439 - def forget(self, response=None):
440 self._close(response)
441 self._forget = True
442
444
445
446 if not response.session_db or not response.session_id or self._forget:
447 return
448
449
450 __hash = self.__hash
451 if __hash is not None:
452 del self.__hash
453 if __hash == hashlib.md5(str(self)).digest():
454 return
455
456 (record_id_name, table, record_id, unique_key) = \
457 response._dbtable_and_field
458 dd = dict(locked=False, client_ip=request.env.remote_addr,
459 modified_datetime=request.now,
460 session_data=cPickle.dumps(dict(self)),
461 unique_key=unique_key)
462 if record_id:
463 table._db(table.id == record_id).update(**dd)
464 else:
465 record_id = table.insert(**dd)
466 response.cookies[response.session_id_name] = '%s:%s'\
467 % (record_id, unique_key)
468 response.cookies[response.session_id_name]['path'] = '/'
469
471
472
473 if response.session_db:
474 return
475
476
477 __hash = self.__hash
478 if __hash is not None:
479 del self.__hash
480 if __hash == hashlib.md5(str(self)).digest():
481 self._close(response)
482 return
483
484 if not response.session_id or self._forget:
485 self._close(response)
486 return
487
488 if response.session_new:
489
490 session_folder = os.path.dirname(response.session_filename)
491 if not os.path.exists(session_folder):
492 os.mkdir(session_folder)
493 response.session_file = open(response.session_filename, 'wb')
494 portalocker.lock(response.session_file, portalocker.LOCK_EX)
495 response.session_locked = True
496
497 if response.session_file:
498 cPickle.dump(dict(self), response.session_file)
499 response.session_file.truncate()
500 self._close(response)
501
503 if response and response.session_file and response.session_locked:
504 try:
505 portalocker.unlock(response.session_file)
506 response.session_locked = False
507 except:
508 pass
509
511 if response and response.session_file:
512 self._unlock(response)
513 try:
514 response.session_file.close()
515 del response.session_file
516 except:
517 pass
518