]> granicus.if.org Git - python/commitdiff
#10839: raise an error on add of duplicate unique headers in new email policies
authorR David Murray <rdmurray@bitdance.com>
Tue, 29 May 2012 13:14:44 +0000 (09:14 -0400)
committerR David Murray <rdmurray@bitdance.com>
Tue, 29 May 2012 13:14:44 +0000 (09:14 -0400)
This feature was supposed to be part of the initial email6 checkin, but it got
lost in my big refactoring.

In this patch I'm not providing an easy way to turn off the errors, but they
only happen when a header is added programmatically, and it is almost never
the right thing to do to allow the duplicate to be added.  An application that
needs to add duplicates of unique headers can create a policy subclass to
allow it.

Doc/library/email.policy.rst
Lib/email/_policybase.py
Lib/email/message.py
Lib/email/policy.py

index 9ab949eb7884e54969a8e552f5731aeec8d7c0a4..31b13c15f10ed29d916b63404e1974d6b086db31 100644 (file)
@@ -196,6 +196,25 @@ added matters.  To illustrate::
       custom ``Message`` objects) should also provide such an attribute,
       otherwise defects in parsed messages will raise unexpected errors.
 
+   .. method:: header_max_count(name)
+
+      Return the maximum allowed number of headers named *name*.
+
+      Called when a header is added to a :class:`~email.message.Message`
+      object.  If the returned value is not ``0`` or ``None``, and there are
+      already a number of headers with the name *name* equal to the value
+      returned, a :exc:`ValueError` is raised.
+
+      Because the default behavior of ``Message.__setitem__`` is to append the
+      value to the list of headers, it is easy to create duplicate headers
+      without realizing it.  This method allows certain headers to be limited
+      in the number of instances of that header that may be added to a
+      ``Message`` programmatically.  (The limit is not observed by the parser,
+      which will faithfully produce as many headers as exist in the message
+      being parsed.)
+
+      The default implementation returns ``None`` for all header names.
+
    .. method:: header_source_parse(sourcelines)
 
       The email package calls this method with a list of strings, each string
@@ -366,6 +385,12 @@ added matters.  To illustrate::
    The class provides the following concrete implementations of the abstract
    methods of :class:`Policy`:
 
+   .. method:: header_max_count(name)
+
+      Returns the value of the
+      :attr:`~email.headerregistry.BaseHeader.max_count` attribute of the
+      specialized class used to represent the header with the given name.
+
    .. method:: header_source_parse(sourcelines)
 
       The implementation of this method is the same as that for the
index 6bc298b945c850a218be884b773d58b71fe4fe8f..d5e8df99404c050f9d940da7b7b2ece2b3ec6776 100644 (file)
@@ -194,6 +194,25 @@ class Policy(_PolicyBase, metaclass=_DocstringExtenderMetaclass):
         """
         obj.defects.append(defect)
 
+    def header_max_count(self, name):
+        """Return the maximum allowed number of headers named 'name'.
+
+        Called when a header is added to a Message object.  If the returned
+        value is not 0 or None, and there are already a number of headers with
+        the name 'name' equal to the value returned, a ValueError is raised.
+
+        Because the default behavior of Message's __setitem__ is to append the
+        value to the list of headers, it is easy to create duplicate headers
+        without realizing it.  This method allows certain headers to be limited
+        in the number of instances of that header that may be added to a
+        Message programmatically.  (The limit is not observed by the parser,
+        which will faithfully produce as many headers as exist in the message
+        being parsed.)
+
+        The default implementation returns None for all header names.
+        """
+        return None
+
     @abc.abstractmethod
     def header_source_parse(self, sourcelines):
         """Given a list of linesep terminated strings constituting the lines of
index 62b82b79c100fa91100024ab34966d0087eaf5ad..9b06207d1487258853f5975b6eeba069a80629ae 100644 (file)
@@ -346,6 +346,16 @@ class Message:
         Note: this does not overwrite an existing header with the same field
         name.  Use __delitem__() first to delete any existing headers.
         """
+        max_count = self.policy.header_max_count(name)
+        if max_count:
+            lname = name.lower()
+            found = 0
+            for k, v in self._headers:
+                if k.lower() == lname:
+                    found += 1
+                    if found >= max_count:
+                        raise ValueError("There may be at most {} {} headers "
+                                         "in a message".format(max_count, name))
         self._headers.append(self.policy.header_store_parse(name, val))
 
     def __delitem__(self, name):
index 47ed66b642ad7ef4ade68f319624e7799afd1ff1..bfffb457dcd60145e25fc5469163fcf1d1cad772 100644 (file)
@@ -69,6 +69,14 @@ class EmailPolicy(Policy):
             object.__setattr__(self, 'header_factory', HeaderRegistry())
         super().__init__(**kw)
 
+    def header_max_count(self, name):
+        """+
+        The implementation for this class returns the max_count attribute from
+        the specialized header class that would be used to construct a header
+        of type 'name'.
+        """
+        return self.header_factory[name].max_count
+
     # The logic of the next three methods is chosen such that it is possible to
     # switch a Message object between a Compat32 policy and a policy derived
     # from this class and have the results stay consistent.  This allows a