[web] start a new message system based on id of message stored in session's data
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 12 Mar 2010 15:05:33 +0100
changeset 4897 e402e0b32075
parent 4896 45a1c3f0d0d9
child 4899 c666d265fb95
[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.
web/application.py
web/controller.py
web/request.py
--- 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"""