[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