[Zope3-checkins] SVN: Zope3/trunk/src/zope/publisher/ add MIME Content-Type helper functions;

Fred L. Drake, Jr. fdrake at gmail.com
Fri Jul 29 19:10:35 EDT 2005


Log message for revision 37582:
  add MIME Content-Type helper functions;
  these will be used where content-type needs to be understood
  

Changed:
  A   Zope3/trunk/src/zope/publisher/contenttype.py
  A   Zope3/trunk/src/zope/publisher/tests/test_contenttype.py

-=-
Added: Zope3/trunk/src/zope/publisher/contenttype.py
===================================================================
--- Zope3/trunk/src/zope/publisher/contenttype.py	2005-07-29 23:10:27 UTC (rev 37581)
+++ Zope3/trunk/src/zope/publisher/contenttype.py	2005-07-29 23:10:34 UTC (rev 37582)
@@ -0,0 +1,128 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""MIME Content-Type parsing helper functions.
+
+"""
+__docformat__ = "reStructuredText"
+
+import re
+
+
+def parse(string):
+    major, minor, params = parseOrdered(string)
+    d = {}
+    for (name, value) in params:
+        d[name] = value
+    return major, minor, d
+
+def parseOrdered(string):
+    if ";" in string:
+        type, params = string.split(";", 1)
+        params = _parse_params(params)
+    else:
+        type = string
+        params = []
+    if "/" not in type:
+        raise ValueError("content type missing major/minor parts")
+    type = type.strip()
+
+    major, minor = type.lower().split("/", 1)
+    return _check_token(major.strip()), _check_token(minor.strip()), params
+
+def _parse_params(string):
+    result = []
+    string = string.strip()
+    while string:
+        if not "=" in string:
+            raise ValueError("parameter values are not optional")
+        name, rest = string.split("=", 1)
+        name = _check_token(name.strip().lower())
+        rest = rest.strip()
+
+        # rest is: value *[";" parameter]
+
+        if rest[:1] == '"':
+            # quoted-string, defined in RFC 822.
+            m = _quoted_string_match(rest)
+            if m is None:
+                raise ValueError("invalid quoted-string in %r" % rest)
+            value = m.group()
+            rest = rest[m.end():].strip()
+            #import pdb; pdb.set_trace()
+            if rest[:1] not in ("", ";"):
+                raise ValueError(
+                    "invalid token following quoted-string: %r" % rest)
+            rest = rest[1:]
+            value = _unescape(value)
+
+        elif ";" in rest:
+            value, rest = rest.split(";")
+            value = _check_token(value.strip())
+
+        else:
+            value = _check_token(rest.strip())
+            rest = ""
+
+        result.append((name, value))
+        string = rest.strip()
+    return result
+
+
+def _quoted_string_match(string):
+    global _quoted_string_match
+    _quoted_string_match = re.compile(
+        '"(?:\\\\.|[^"\n\r\\\\])*"', re.DOTALL).match
+    return _quoted_string_match(string)
+
+def _check_token(string):
+    if _token_match(string) is None:
+        raise ValueError('"%s" is not a valid token' % string)
+    return string
+
+def _token_match(string):
+    global _token_match
+    _token_match = re.compile("[^][ \t\n\r()<>@,;:\"/?.=\\\\]+$").match
+    return _token_match(string)
+
+def _unescape(string):
+    assert string[0] == '"'
+    assert string[-1] == '"'
+    string = string[1:-1]
+    if "\\" in string:
+        string = re.sub(r"\\(.)", r"\1", string)
+    return string
+
+
+def join((major, minor, params)):
+    pstr = ""
+    try:
+        params.items
+    except AttributeError:
+        pass
+    else:
+        params = params.items()
+        # ensure a predictable order:
+        params.sort()
+    for name, value in params:
+        pstr += "; %s=%s" % (name, _escape(value))
+    return "%s/%s%s" % (major, minor, pstr)
+
+def _escape(string):
+    try:
+        return _check_token(string)
+    except ValueError:
+        # '\\' must be first
+        for c in '\\"\n\r':
+            string = string.replace(c, "\\" + c)
+        return '"%s"' % string


Property changes on: Zope3/trunk/src/zope/publisher/contenttype.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/publisher/tests/test_contenttype.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_contenttype.py	2005-07-29 23:10:27 UTC (rev 37581)
+++ Zope3/trunk/src/zope/publisher/tests/test_contenttype.py	2005-07-29 23:10:34 UTC (rev 37582)
@@ -0,0 +1,197 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests of the contenttype helpers.
+
+"""
+__docformat__ = "reStructuredText"
+
+import re
+import unittest
+
+from zope.publisher import contenttype
+
+
+class ParseOrderedTestCase(unittest.TestCase):
+
+    empty_params = []
+
+    def setUp(self):
+        self.parse = contenttype.parseOrdered
+
+    def oneParam(self, name, value):
+        return [(name, value)]
+
+    def test_without_params(self):
+        self.assertEqual(self.parse("text/plain"),
+                         ("text", "plain", self.empty_params))
+        self.assertEqual(self.parse("TEXT/PLAIN"),
+                         ("text", "plain", self.empty_params))
+        self.assertEqual(self.parse("TeXt / PlaIN"),
+                         ("text", "plain", self.empty_params))
+
+    def test_with_empty_params(self):
+        self.assertEqual(self.parse("text/plain ; "),
+                         ("text", "plain", self.empty_params))
+        self.assertEqual(self.parse("TEXT/PLAIN ;   "),
+                         ("text", "plain", self.empty_params))
+        self.assertEqual(self.parse("TeXt / PlaIN ; "),
+                         ("text", "plain", self.empty_params))
+
+    def test_bad_tokens(self):
+        self.assertRaises(ValueError,
+                          self.parse, "text stuff/plain")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain stuff")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain;some stuff=foo")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain;a=b;c d=e")
+
+    def test_missing_parts(self):
+        self.assertRaises(ValueError,
+                          self.parse, "text ; params")
+        self.assertRaises(ValueError,
+                          self.parse, "text/ ; params")
+        self.assertRaises(ValueError,
+                          self.parse, "/plain ; params")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; params")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; params=")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; =params")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; a=b; params")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; a=b; params=")
+        self.assertRaises(ValueError,
+                          self.parse, "text/plain ; a=b; =params")
+
+    def test_single_parameter(self):
+        self.assertEqual(self.parse("text/plain;charset=UTF-8"),
+                         ("text", "plain", self.oneParam("charset", "UTF-8")))
+        self.assertEqual(self.parse("text/plain ;\tcharset = UTF-8"),
+                         ("text", "plain", self.oneParam("charset", "UTF-8")))
+        # quoted-string parameter values
+        self.assertEqual(self.parse('text/plain;charset="UTF-8"'),
+                         ("text", "plain", self.oneParam("charset", "UTF-8")))
+        self.assertEqual(self.parse('text/plain ;\tcharset = "UTF-8"'),
+                         ("text", "plain", self.oneParam("charset", "UTF-8")))
+
+    def test_multiple_parameters(self):
+        self.assertEqual(
+            self.parse("text/plain;charset=utf-8;format=flowed"),
+            ("text", "plain", [("charset", "utf-8"), ("format", "flowed")]))
+        self.assertEqual(
+            self.parse('text/plain;charset=utf-8;format="flowed"'),
+            ("text", "plain", [("charset", "utf-8"), ("format", "flowed")]))
+
+    def test_quoted_strings(self):
+        p = self.oneParam("c", " This [] has <> ? other () chars\t")
+        self.assertEqual(
+            self.parse('a/b;c= " This [] has <> ? other () chars\t" '),
+            ("a", "b", p))
+        self.assertEqual(
+            self.parse('a/b;c=""'),
+            ("a", "b", self.oneParam("c", "")))
+        self.assertEqual(
+            self.parse(r'a/b;c="\\\""'),
+            ("a", "b", self.oneParam("c", r'\"')))
+
+class ParseTestCase(ParseOrderedTestCase):
+
+    empty_params = {}
+
+    def setUp(self):
+        self.parse = contenttype.parse
+
+    def oneParam(self, name, value):
+        return {name: value}
+
+    def test_multiple_parameters(self):
+        self.assertEqual(
+            self.parse("text/plain;charset=utf-8;format=flowed"),
+            ("text", "plain", {"charset": "utf-8", "format": "flowed"}))
+        self.assertEqual(
+            self.parse('text/plain;charset=utf-8;format="flowed"'),
+            ("text", "plain", {"charset": "utf-8", "format": "flowed"}))
+
+
+class JoinTestCase(unittest.TestCase):
+
+    def test_without_params(self):
+        self.assertEqual(contenttype.join(("text", "plain", [])),
+                         "text/plain")
+        self.assertEqual(contenttype.join(("text", "plain", {})),
+                         "text/plain")
+
+    def test_single_token_param(self):
+        self.assertEqual(
+            contenttype.join(("text", "plain", [("charset", "UTF-8")])),
+            "text/plain; charset=UTF-8")
+        self.assertEqual(
+            contenttype.join(("text", "plain", {"charset": "UTF-8"})),
+            "text/plain; charset=UTF-8")
+
+    def test_multi_params_list_maintains_order(self):
+        # multiple parameters given as a list maintain order:
+        self.assertEqual(
+            contenttype.join(("text", "plain",
+                              [("charset", "UTF-8"), ("format", "flowed")])),
+            "text/plain; charset=UTF-8; format=flowed")
+        self.assertEqual(
+            contenttype.join(("text", "plain",
+                              [("format", "flowed"), ("charset", "UTF-8")])),
+            "text/plain; format=flowed; charset=UTF-8")
+
+    def test_multi_params_dict_sorted_order(self):
+        # multiple parameters given as a dict are sorted by param name:
+        self.assertEqual(
+            contenttype.join(("text", "plain",
+                              {"charset": "UTF-8", "format": "flowed"})),
+            "text/plain; charset=UTF-8; format=flowed")
+
+    def test_params_list_quoted(self):
+        # parameter values are quoted automatically:
+        self.assertEqual(contenttype.join(("a", "b", [("c", "")])),
+                         'a/b; c=""')
+        self.assertEqual(contenttype.join(("a", "b", [("c", "ab cd")])),
+                         'a/b; c="ab cd"')
+        self.assertEqual(contenttype.join(("a", "b", [("c", " \t")])),
+                         'a/b; c=" \t"')
+        self.assertEqual(contenttype.join(("a", "b", [("c", '"')])),
+                         r'a/b; c="\""')
+        self.assertEqual(contenttype.join(("a", "b", [("c", "\n")])),
+                         'a/b; c="\\\n"')
+
+    def test_params_dict_quoted(self):
+        # parameter values are quoted automatically:
+        self.assertEqual(contenttype.join(("a", "b", {"c": ""})),
+                         'a/b; c=""')
+        self.assertEqual(contenttype.join(("a", "b", {"c": "ab cd"})),
+                         'a/b; c="ab cd"')
+        self.assertEqual(contenttype.join(("a", "b", {"c": " \t"})),
+                         'a/b; c=" \t"')
+        self.assertEqual(contenttype.join(("a", "b", {"c": '"'})),
+                         r'a/b; c="\""')
+        self.assertEqual(contenttype.join(("a", "b", {"c": "\n"})),
+                         'a/b; c="\\\n"')
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ParseOrderedTestCase))
+    suite.addTest(unittest.makeSuite(ParseTestCase))
+    suite.addTest(unittest.makeSuite(JoinTestCase))
+    return suite


Property changes on: Zope3/trunk/src/zope/publisher/tests/test_contenttype.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list