[web] start a new message system based on id of message stored in session's data
instead of using __message as today, which is problematic (allow message injection).
Also we can have html in messages. Removed the __createdpath hack used to escape those
limitation.
The old system should still work though (and will probably for a while, though
we should progressivly move to the new system where it's possible).
Cleanup request paramaters handling on the way.
--- a/web/application.py Fri Mar 12 15:01:52 2010 +0100
+++ b/web/application.py Fri Mar 12 15:05:33 2010 +0100
@@ -393,7 +393,7 @@
self.exception(repr(ex))
req.set_header('Cache-Control', 'no-cache')
req.remove_header('Etag')
- req.message = None
+ req.reset_message()
req.reset_headers()
if req.json_request:
raise RemoteCallFailed(unicode(ex))
--- a/web/controller.py Fri Mar 12 15:01:52 2010 +0100
+++ b/web/controller.py Fri Mar 12 15:05:33 2010 +0100
@@ -8,6 +8,8 @@
"""
__docformat__ = "restructuredtext en"
+from logilab.mtconverter import xml_escape
+
from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
from cubicweb.web import LOGGER, Redirect, RequestError
@@ -98,7 +100,7 @@
newparams = {}
# sets message if needed
if self._cw.message:
- newparams['__message'] = self._cw.message
+ newparams['_cwmsgid'] = self._cw.set_redirect_message(self._cw.message)
if self._cw.form.has_key('__action_apply'):
self._return_to_edition_view(newparams)
if self._cw.form.has_key('__action_cancel'):
@@ -120,10 +122,13 @@
elif '__redirectpath' in self._cw.form:
# if redirect path was explicitly specified in the form, use it
path = self._cw.form['__redirectpath']
- if self._edited_entity and path != self._edited_entity.rest_path():
- # XXX may be here on modification? if yes the message should be
- # modified where __createdpath is detected (cw.web.request)
- newparams['__createdpath'] = self._edited_entity.rest_path()
+ if (self._edited_entity and path != self._edited_entity.rest_path()
+ and '_cwmsgid' in newparams):
+ # XXX may be here on modification?
+ msg = u'(<a href="%s">%s</a>)' % (
+ xml_escape(self._edited_entity.absolute_url()),
+ self._cw._('click here to see created entity'))
+ self._cw.append_to_redirect_message(msg)
elif self._after_deletion_path:
# else it should have been set during form processing
path, params = self._after_deletion_path
@@ -149,7 +154,7 @@
path = 'view'
newparams['rql'] = form['rql']
else:
- self.warning("the edited data seems inconsistent")
+ self.warning('the edited data seems inconsistent')
path = 'view'
# pick up the correction edition view
if form.get('__form_id'):
--- a/web/request.py Fri Mar 12 15:01:52 2010 +0100
+++ b/web/request.py Fri Mar 12 15:05:33 2010 +0100
@@ -68,7 +68,6 @@
def __init__(self, vreg, https, form=None):
super(CubicWebRequestBase, self).__init__(vreg)
- self.message = None
self.authmode = vreg.config['auth-mode']
self.https = https
# raw html headers that can be added from any view
@@ -126,35 +125,24 @@
"""
super(CubicWebRequestBase, self).set_connection(cnx, user)
# set request language
- try:
- vreg = self.vreg
- if self.user:
- try:
- # 1. user specified language
- lang = vreg.typed_value('ui.language',
- self.user.properties['ui.language'])
+ vreg = self.vreg
+ if self.user:
+ try:
+ # 1. user specified language
+ lang = vreg.typed_value('ui.language',
+ self.user.properties['ui.language'])
+ self.set_language(lang)
+ return
+ except KeyError:
+ pass
+ if vreg.config['language-negociation']:
+ # 2. http negociated language
+ for lang in self.header_accept_language():
+ if lang in self.translations:
self.set_language(lang)
return
- except KeyError:
- pass
- if vreg.config['language-negociation']:
- # 2. http negociated language
- for lang in self.header_accept_language():
- if lang in self.translations:
- self.set_language(lang)
- return
- # 3. default language
- self.set_default_language(vreg)
- finally:
- # XXX code smell
- # have to be done here because language is not yet set in setup_params
- #
- # special key for created entity, added in controller's reset method
- # if no message set, we don't want this neither
- if '__createdpath' in self.form and self.message:
- self.message += ' (<a href="%s">%s</a>)' % (
- self.build_url(self.form.pop('__createdpath')),
- self._('click here to see created entity'))
+ # 3. default language
+ self.set_default_language(vreg)
def set_language(self, lang):
gettext, self.pgettext = self.translations[lang]
@@ -179,26 +167,27 @@
subclasses should overrides to
"""
+ self.form = {}
if params is None:
- params = {}
- self.form = params
+ return
encoding = self.encoding
- for k, v in params.items():
- if isinstance(v, (tuple, list)):
- v = [unicode(x, encoding) for x in v]
- if len(v) == 1:
- v = v[0]
- if k in self.no_script_form_params:
- v = self.no_script_form_param(k, value=v)
- if isinstance(v, str):
- v = unicode(v, encoding)
- if k == '__message':
- self.set_message(v)
- del self.form[k]
+ for param, val in params.iteritems():
+ if isinstance(val, (tuple, list)):
+ val = [unicode(x, encoding) for x in val]
+ if len(val) == 1:
+ val = val[0]
+ elif isinstance(val, str):
+ val = unicode(val, encoding)
+ if param in self.no_script_form_params and val:
+ val = self.no_script_form_param(param, val)
+ if param == '_cwmsgid':
+ self.set_message_id(val)
+ elif param == '__message':
+ self.set_message(val)
else:
- self.form[k] = v
+ self.form[param] = val
- def no_script_form_param(self, param, default=None, value=None):
+ def no_script_form_param(self, param, value):
"""ensure there is no script in a user form param
by default return a cleaned string instead of raising a security
@@ -208,16 +197,12 @@
that are at some point inserted in a generated html page to protect
against script kiddies
"""
- if value is None:
- value = self.form.get(param, default)
- if not value is default and value:
- # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
- if isinstance(value, (list, tuple)):
- self.error('no_script_form_param got a list (%s). Who generated the URL ?',
- repr(value))
- value = value[0]
- return remove_html_tags(value)
- return value
+ # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
+ if isinstance(value, (list, tuple)):
+ self.error('no_script_form_param got a list (%s). Who generated the URL ?',
+ repr(value))
+ value = value[0]
+ return remove_html_tags(value)
def list_form_param(self, param, form=None, pop=False):
"""get param from form parameters and return its value as a list,
@@ -245,9 +230,48 @@
# web state helpers #######################################################
+ @property
+ def message(self):
+ try:
+ return self.get_session_data(self._msgid, default=u'', pop=True)
+ except AttributeError:
+ try:
+ return self._msg
+ except AttributeError:
+ return None
+
def set_message(self, msg):
assert isinstance(msg, unicode)
- self.message = msg
+ self._msg = msg
+
+ def set_message_id(self, msgid):
+ self._msgid = msgid
+
+ @cached
+ def redirect_message_id(self):
+ return make_uid()
+
+ def set_redirect_message(self, msg):
+ assert isinstance(msg, unicode)
+ msgid = self.redirect_message_id()
+ self.set_session_data(msgid, msg)
+ return msgid
+
+ def append_to_redirect_message(self, msg):
+ msgid = self.redirect_message_id()
+ currentmsg = self.get_session_data(msgid)
+ if currentmsg is not None:
+ currentmsg = '%s %s' % (currentmsg, msg)
+ else:
+ currentmsg = msg
+ self.set_session_data(msgid, currentmsg)
+ return msgid
+
+ def reset_message(self):
+ if hasattr(self, '_msg'):
+ del self._msg
+ if hasattr(self, '_msgid'):
+ del self._msgid
def update_search_state(self):
"""update the current search state"""