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 compileapp import run_view_in
19 from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
20 from xmlrpc import handler
21 from contenttype import contenttype
22 from html import xmlescape
23 from http import HTTP
24 from fileutils import up
25 from serializers import json, custom_json
26 import settings
27 from utils import web2py_uuid
28 from settings import global_settings
29
30 import hashlib
31 import portalocker
32 import cPickle
33 import cStringIO
34 import datetime
35 import re
36 import Cookie
37 import os
38 import sys
39 import traceback
40
41 regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$')
42
43 __all__ = ['Request', 'Response', 'Session']
44
45
47
48 """
49 defines the request object and the default values of its members
50
51 - env: environment variables, by gluon.main.wsgibase()
52 - cookies
53 - get_vars
54 - post_vars
55 - vars
56 - folder
57 - application
58 - function
59 - args
60 - extension
61 - now: datetime.datetime.today()
62 - restful()
63 """
64
66 self.wsgi = Storage()
67 self.env = Storage()
68 self.cookies = Cookie.SimpleCookie()
69 self.get_vars = Storage()
70 self.post_vars = Storage()
71 self.vars = Storage()
72 self.folder = None
73 self.application = None
74 self.function = None
75 self.args = List()
76 self.extension = None
77 self.now = datetime.datetime.now()
78 self.is_restful = False
79
81 self.uuid = '%s/%s.%s.%s' % (
82 self.application,
83 self.client.replace(':', '_'),
84 self.now.strftime('%Y-%m-%d.%H-%M-%S'),
85 web2py_uuid())
86 return self.uuid
87
89 def wrapper(action,self=self):
90 def f(_action=action,_self=self,*a,**b):
91 self.is_restful = True
92 method = _self.env.request_method
93 if len(_self.args) and '.' in _self.args[-1]:
94 _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1)
95 if not method in ['GET','POST','DELETE','PUT']:
96 raise HTTP(400,"invalid method")
97 rest_action = _action().get(method,None)
98 if not rest_action:
99 raise HTTP(400,"method not supported")
100 try:
101 return rest_action(*_self.args,**_self.vars)
102 except TypeError, e:
103 exc_type, exc_value, exc_traceback = sys.exc_info()
104 if len(traceback.extract_tb(exc_traceback))==1:
105 raise HTTP(400,"invalid arguments")
106 else:
107 raise e
108 f.__doc__ = action.__doc__
109 f.__name__ = action.__name__
110 return f
111 return wrapper
112
113
115
116 """
117 defines the response object and the default values of its members
118 response.write( ) can be used to write in the output html
119 """
120
122 self.status = 200
123 self.headers = Storage()
124 self.body = cStringIO.StringIO()
125 self.session_id = None
126 self.cookies = Cookie.SimpleCookie()
127 self.postprocessing = []
128 self.flash = ''
129 self.meta = Storage()
130 self.menu = []
131 self.files = []
132 self._vars = None
133 self._caller = lambda f: f()
134 self._view_environment = None
135 self._custom_commit = None
136 self._custom_rollback = None
137
138 - def write(self, data, escape=True):
139 if not escape:
140 self.body.write(str(data))
141 else:
142 self.body.write(xmlescape(data))
143
145 if len(a) > 2:
146 raise SyntaxError, 'Response.render can be called with two arguments, at most'
147 elif len(a) == 2:
148 (view, self._vars) = (a[0], a[1])
149 elif len(a) == 1 and isinstance(a[0], str):
150 (view, self._vars) = (a[0], {})
151 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read):
152 (view, self._vars) = (a[0], {})
153 elif len(a) == 1 and isinstance(a[0], dict):
154 (view, self._vars) = (None, a[0])
155 else:
156 (view, self._vars) = (None, {})
157 self._vars.update(b)
158 self._view_environment.update(self._vars)
159 if view:
160 import cStringIO
161 (obody, oview) = (self.body, self.view)
162 (self.body, self.view) = (cStringIO.StringIO(), view)
163 run_view_in(self._view_environment)
164 page = self.body.getvalue()
165 self.body.close()
166 (self.body, self.view) = (obody, oview)
167 else:
168 run_view_in(self._view_environment)
169 page = self.body.getvalue()
170 return page
171
178 """
179 if a controller function::
180
181 return response.stream(file, 100)
182
183 the file content will be streamed at 100 bytes at the time
184 """
185
186 if isinstance(stream, (str, unicode)):
187 stream_file_or_304_or_206(stream,
188 chunk_size=chunk_size,
189 request=request,
190 headers=self.headers)
191
192
193
194 if hasattr(stream, 'name'):
195 filename = stream.name
196 else:
197 filename = None
198 keys = [item.lower() for item in self.headers]
199 if filename and not 'content-type' in keys:
200 self.headers['Content-Type'] = contenttype(filename)
201 if filename and not 'content-length' in keys:
202 try:
203 self.headers['Content-Length'] = \
204 os.path.getsize(filename)
205 except OSError:
206 pass
207 if request and request.env.web2py_use_wsgi_file_wrapper:
208 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
209 else:
210 wrapped = streamer(stream, chunk_size=chunk_size)
211 return wrapped
212
214 """
215 example of usage in controller::
216
217 def download():
218 return response.download(request, db)
219
220 downloads from http://..../download/filename
221 """
222
223 import contenttype as c
224 if not request.args:
225 raise HTTP(404)
226 name = request.args[-1]
227 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
228 .match(name)
229 if not items:
230 raise HTTP(404)
231 (t, f) = (items.group('table'), items.group('field'))
232 field = db[t][f]
233 try:
234 (filename, stream) = field.retrieve(name)
235 except IOError:
236 raise HTTP(404)
237 self.headers['Content-Type'] = c.contenttype(name)
238 if attachment:
239 self.headers['Content-Disposition'] = \
240 "attachment; filename=%s" % filename
241 return self.stream(stream, chunk_size = chunk_size, request=request)
242
243 - def json(self, data, default=None):
245
246 - def xmlrpc(self, request, methods):
247 """
248 assuming::
249
250 def add(a, b):
251 return a+b
252
253 if a controller function \"func\"::
254
255 return response.xmlrpc(request, [add])
256
257 the controller will be able to handle xmlrpc requests for
258 the add function. Example::
259
260 import xmlrpclib
261 connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func')
262 print connection.add(3, 4)
263
264 """
265
266 return handler(request, self, methods)
267
269
270 """
271 defines the session object and the default values of its members (None)
272 """
273
274 - def connect(
275 self,
276 request,
277 response,
278 db=None,
279 tablename='web2py_session',
280 masterapp=None,
281 migrate=True,
282 separate = None,
283 check_client=False,
284 ):
285 """
286 separate can be separate=lambda(session_name): session_name[-2:]
287 and it is used to determine a session prefix.
288 separate can be True and it is set to session_name[-2:]
289 """
290 if separate == True:
291 separate = lambda session_name: session_name[-2:]
292 self._unlock(response)
293 if not masterapp:
294 masterapp = request.application
295 response.session_id_name = 'session_id_%s' % masterapp.lower()
296
297 if not db:
298 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions:
299 return
300 response.session_new = False
301 client = request.client.replace(':', '.')
302 if response.session_id_name in request.cookies:
303 response.session_id = \
304 request.cookies[response.session_id_name].value
305 if regex_session_id.match(response.session_id):
306 response.session_filename = \
307 os.path.join(up(request.folder), masterapp,
308 'sessions', response.session_id)
309 else:
310 response.session_id = None
311 if response.session_id:
312 try:
313 response.session_file = \
314 open(response.session_filename, 'rb+')
315 portalocker.lock(response.session_file,
316 portalocker.LOCK_EX)
317 response.session_locked = True
318 self.update(cPickle.load(response.session_file))
319 response.session_file.seek(0)
320 oc = response.session_filename.split('/')[-1].split('-')[0]
321 if check_client and client!=oc:
322 raise Exception, "cookie attack"
323 except:
324 self._close(response)
325 response.session_id = None
326 if not response.session_id:
327 uuid = web2py_uuid()
328 response.session_id = '%s-%s' % (client, uuid)
329 if separate:
330 prefix = separate(response.session_id)
331 response.session_id = '%s/%s' % (prefix,response.session_id)
332 response.session_filename = \
333 os.path.join(up(request.folder), masterapp,
334 'sessions', response.session_id)
335 response.session_new = True
336 else:
337 if global_settings.db_sessions is not True:
338 global_settings.db_sessions.add(masterapp)
339 response.session_db = True
340 if response.session_file:
341 self._close(response)
342 if settings.global_settings.web2py_runtime_gae:
343
344 request.tickets_db = db
345 if masterapp == request.application:
346 table_migrate = migrate
347 else:
348 table_migrate = False
349 tname = tablename + '_' + masterapp
350 table = db.get(tname, None)
351 if table is None:
352 table = db.define_table(
353 tname,
354 db.Field('locked', 'boolean', default=False),
355 db.Field('client_ip', length=64),
356 db.Field('created_datetime', 'datetime',
357 default=request.now),
358 db.Field('modified_datetime', 'datetime'),
359 db.Field('unique_key', length=64),
360 db.Field('session_data', 'blob'),
361 migrate=table_migrate,
362 )
363 try:
364 key = request.cookies[response.session_id_name].value
365 (record_id, unique_key) = key.split(':')
366 if record_id == '0':
367 raise Exception, 'record_id == 0'
368 rows = db(table.id == record_id).select()
369 if len(rows) == 0 or rows[0].unique_key != unique_key:
370 raise Exception, 'No record'
371
372
373
374 session_data = cPickle.loads(rows[0].session_data)
375 self.update(session_data)
376 except Exception:
377 record_id = None
378 unique_key = web2py_uuid()
379 session_data = {}
380 response._dbtable_and_field = \
381 (response.session_id_name, table, record_id, unique_key)
382 response.session_id = '%s:%s' % (record_id, unique_key)
383 response.cookies[response.session_id_name] = response.session_id
384 response.cookies[response.session_id_name]['path'] = '/'
385 self.__hash = hashlib.md5(str(self)).digest()
386 if self.flash:
387 (response.flash, self.flash) = (self.flash, None)
388
390 if self._start_timestamp:
391 return False
392 else:
393 self._start_timestamp = datetime.datetime.today()
394 return True
395
397 now = datetime.datetime.today()
398 if not self._last_timestamp or \
399 self._last_timestamp + datetime.timedelta(seconds = seconds) > now:
400 self._last_timestamp = now
401 return False
402 else:
403 return True
404
407
408 - def forget(self, response=None):
409 self._close(response)
410 self._forget = True
411
413
414
415 if not response.session_db or not response.session_id or self._forget:
416 return
417
418
419 __hash = self.__hash
420 if __hash is not None:
421 del self.__hash
422 if __hash == hashlib.md5(str(self)).digest():
423 return
424
425 (record_id_name, table, record_id, unique_key) = \
426 response._dbtable_and_field
427 dd = dict(locked=False, client_ip=request.env.remote_addr,
428 modified_datetime=request.now,
429 session_data=cPickle.dumps(dict(self)),
430 unique_key=unique_key)
431 if record_id:
432 table._db(table.id == record_id).update(**dd)
433 else:
434 record_id = table.insert(**dd)
435 response.cookies[response.session_id_name] = '%s:%s'\
436 % (record_id, unique_key)
437 response.cookies[response.session_id_name]['path'] = '/'
438
440
441
442 if response.session_db:
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 self._close(response)
451 return
452
453 if not response.session_id or self._forget:
454 self._close(response)
455 return
456
457 if response.session_new:
458
459 session_folder = os.path.dirname(response.session_filename)
460 if not os.path.exists(session_folder):
461 os.mkdir(session_folder)
462 response.session_file = open(response.session_filename, 'wb')
463 portalocker.lock(response.session_file, portalocker.LOCK_EX)
464 response.session_locked = True
465
466 if response.session_file:
467 cPickle.dump(dict(self), response.session_file)
468 response.session_file.truncate()
469 self._close(response)
470
472 if response and response.session_file and response.session_locked:
473 try:
474 portalocker.unlock(response.session_file)
475 response.session_locked = False
476 except:
477 pass
478
480 if response and response.session_file:
481 self._unlock(response)
482 try:
483 response.session_file.close()
484 del response.session_file
485 except:
486 pass
487