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