[Zope-Checkins] CVS: Zope/lib/python/DocumentTemplate - DT_String.py:1.49.16.1 DT_Util.py:1.85.16.1 DT_Var.py:1.49.16.1 cDocumentTemplate.c:1.42.16.1

Martijn Pieters mj@zope.com
Thu, 1 Aug 2002 12:01:28 -0400


Update of /cvs-repository/Zope/lib/python/DocumentTemplate
In directory cvs.zope.org:/tmp/cvs-serv9310/lib/python/DocumentTemplate

Modified Files:
      Tag: Zope-2_5-branch
	DT_String.py DT_Util.py DT_Var.py cDocumentTemplate.c 
Log Message:
Big change, merge from trunk.

- Make DTML automatically html quote data indirectly taken from REQUEST
  which contain a '<'. Make sure (almost) all string operation preserve the
  taint on this data.

- Fix exceptions that use REQUEST data; quote the data.

- Don't let form and cookie values mask the REQUEST computed values such as
  URL0 and BASE1.


=== Zope/lib/python/DocumentTemplate/DT_String.py 1.49 => 1.49.16.1 ===
         # print '============================================================'
 
         if mapping is None: mapping = {}
+        if hasattr(mapping, 'taintWrapper'): mapping = mapping.taintWrapper()
 
         if not hasattr(self,'_v_cooked'):
             try: changed=self.__changed__()


=== Zope/lib/python/DocumentTemplate/DT_Util.py 1.85 => 1.85.16.1 ===
         else:
             d[name] = f
 
+try:
+    # Wrap the string module so it can deal with TaintedString strings.
+    from ZPublisher.TaintedString import TaintedString
+    from types import FunctionType, BuiltinFunctionType, StringType
+    import string
+
+    class StringModuleWrapper:
+        def __getattr__(self, key):
+            attr = getattr(string, key)
+            if (isinstance(attr, FunctionType) or
+                isinstance(attr, BuiltinFunctionType)):
+                return StringFunctionWrapper(attr)
+            else:
+                return attr
+
+    class StringFunctionWrapper:
+        def __init__(self, method):
+            self._method = method
+
+        def __call__(self, *args, **kw):
+            tainted = 0
+            args = list(args)
+            for i in range(len(args)):
+                if isinstance(args[i], TaintedString):
+                    tainted = 1
+                    args[i] = str(args[i])
+            for k, v in kw.items():
+                if isinstance(v, TaintedString):
+                    tainted = 1
+                    kw[k] = str(v)
+            args = tuple(args)
+
+            retval = self._method(*args, **kw)
+            if tainted and isinstance(retval, StringType) and '<' in retval:
+                retval = TaintedString(retval)
+            return retval
+
+    d['string'] = StringModuleWrapper()
+
+except ImportError:
+    # Use the string module already defined in RestrictedPython.Utilities
+    pass
+
 # The functions below are meant to bind to the TemplateDict.
 
 _marker = []  # Create a new marker object.


=== Zope/lib/python/DocumentTemplate/DT_Var.py 1.49 => 1.49.16.1 ===
 from html_quote import html_quote # for import by other modules, dont remove!
 from types import StringType
 from Acquisition import aq_base
+from ZPublisher.TaintedString import TaintedString
 
 class Var: 
     name='var'
@@ -232,9 +233,19 @@
                     if hasattr(val, fmt):
                         val = _get(val, fmt)()
                     elif special_formats.has_key(fmt):
-                        val = special_formats[fmt](val, name, md)
+                        if fmt == 'html-quote' and \
+                           isinstance(val, TaintedString):
+                            # TaintedStrings will be quoted by default, don't
+                            # double quote.
+                            pass
+                        else:
+                            val = special_formats[fmt](val, name, md)
                     elif fmt=='': val=''
-                    else: val = fmt % val
+                    else: 
+                        if isinstance(val, TaintedString):
+                            val = TaintedString(fmt % val)
+                        else:
+                            val = fmt % val
                 except:
                     t, v= sys.exc_type, sys.exc_value
                     if hasattr(sys, 'exc_info'): t, v = sys.exc_info()[:2]
@@ -247,17 +258,40 @@
                 if hasattr(val, fmt):
                     val = _get(val, fmt)()
                 elif special_formats.has_key(fmt):
-                    val = special_formats[fmt](val, name, md)
+                    if fmt == 'html-quote' and \
+                        isinstance(val, TaintedString):
+                        # TaintedStrings will be quoted by default, don't
+                        # double quote.
+                        pass
+                    else:
+                        val = special_formats[fmt](val, name, md)
                 elif fmt=='': val=''
-                else: val = fmt % val
+                else:
+                    if isinstance(val, TaintedString):
+                        val = TaintedString(fmt % val)
+                    else:
+                        val = fmt % val
 
         # finally, pump it through the actual string format...
         fmt=self.fmt
-        if fmt=='s': val=str(val)
-        else: val = ('%'+self.fmt) % (val,)
+        if fmt=='s':
+            # Keep tainted strings as tainted strings here.
+            if not isinstance(val, TaintedString):
+                val=str(val)
+        else:
+            # Keep tainted strings as tainted strings here.
+            wastainted = 0
+            if isinstance(val, TaintedString): wastainted = 1
+            val = ('%'+self.fmt) % (val,)
+            if wastainted and '<' in val:
+                val = TaintedString(val)
 
         # next, look for upper, lower, etc
-        for f in self.modifiers: val=f(val)
+        for f in self.modifiers:
+            if f.__name__ == 'html_quote' and isinstance(val, TaintedString):
+                # TaintedStrings will be quoted by default, don't double quote.
+                continue
+            val=f(val)
 
         if have_arg('size'):
             size=args['size']
@@ -274,6 +308,9 @@
                 else: l='...'
                 val=val+l
 
+        if isinstance(val, TaintedString):
+            val = val.quoted()
+        
         return val
 
     __call__=render
@@ -298,6 +335,9 @@
 
 
 def newline_to_br(v, name='(Unknown name)', md={}):
+    # Unsafe data is explicitly quoted here; we don't expect this to be HTML
+    # quoted later on anyway.
+    if isinstance(v, TaintedString): v = v.quoted()
     v=str(v)
     if v.find('\r') >= 0: v=''.join(v.split('\r'))
     if v.find('\n') >= 0: v='<br>\n'.join(v.split('\n'))
@@ -366,7 +406,7 @@
     This is needed to securely insert values into sql
     string literals in templates that generate sql.
     """
-    if v.find("'") >= 0: return "''".join(v.split("'"))
+    if v.find("'") >= 0: return v.replace("'", "''")
     return v
 
 special_formats={
@@ -387,7 +427,7 @@
     }
 
 def spacify(val):
-    if val.find('_') >= 0: val=" ".join(val.split('_'))
+    if val.find('_') >= 0: val=val.replace('_', ' ')
     return val
 
 modifiers=(html_quote, url_quote, url_quote_plus, newline_to_br,


=== Zope/lib/python/DocumentTemplate/cDocumentTemplate.c 1.42 => 1.42.16.1 ===
 static PyObject *py_hasRole, *py__proxy_roles, *py_Unauthorized;
 static PyObject *py_Unauthorized_fmt, *py_guarded_getattr;
 static PyObject *py__push, *py__pop, *py_aq_base, *py_renderNS;
-static PyObject *py___class__, *html_quote;
+static PyObject *py___class__, *html_quote, *untaint_name;
 
 /* ----------------------------------------------------- */
 
@@ -663,6 +663,7 @@
 {
   PyObject *block, *t;
   int l, i, k=0, append;
+  int skip_quote = 0;
 
   if ((l=PyList_Size(blocks)) < 0) return -1;
   for (i=0; i < l; i++)
@@ -685,13 +686,34 @@
 	      if (PyString_Check(t)) t=PyObject_GetItem(md, t);
 	      else t=PyObject_CallObject(t, mda);
 
-              if (t == NULL || (! PyString_Check(t)))
+              if (t == NULL) return -1;
+
+	      skip_quote = 0;
+
+	      if (!PyString_Check(t))
+	        {
+		  /* This might be a TaintedString object */
+		  PyObject *untaintmethod = NULL;
+
+		  untaintmethod = PyObject_GetAttr(t, untaint_name);
+		  if (untaintmethod) {
+		     /* Quote it */
+		     UNLESS_ASSIGN(t,
+		   	    PyObject_CallObject(untaintmethod, NULL)) return -1;
+		     skip_quote = 1;
+
+		  } else PyErr_Clear();
+
+		  Py_XDECREF(untaintmethod);
+	        }
+
+              if (!PyString_Check(t))
                 {
                   if (t) ASSIGN(t, PyObject_Str(t));
                   UNLESS(t) return -1;
                 }
 
-              if (PyString_Check(t) 
+              if (skip_quote == 0 && PyString_Check(t) 
                   && PyTuple_GET_SIZE(block) == 3) /* html_quote */
                 {
                   if (strchr(PyString_AS_STRING(t), '&')
@@ -878,6 +900,7 @@
   UNLESS(py_isDocTemp=PyString_FromString("isDocTemp")) return;
   UNLESS(py_renderNS=PyString_FromString("__render_with_namespace__")) return;
   UNLESS(py_blocks=PyString_FromString("blocks")) return;
+  UNLESS(untaint_name=PyString_FromString("__untaint__")) return;
   UNLESS(py_acquire=PyString_FromString("aq_acquire")) return;
   UNLESS(py___call__=PyString_FromString("__call__")) return;
   UNLESS(py___roles__=PyString_FromString("__roles__")) return;