From 40ef0067ad5af2458c54cc0316831ddb701ea3fd Mon Sep 17 00:00:00 2001
From: Barry Warsaw <barry@python.org>
Date: Sat, 18 Mar 2006 15:41:53 +0000
Subject: Merge email package 4.0 from the sandbox, including documentation,
 test cases, and NEWS updates.

---
 Doc/lib/email-dir.py                        |  131 +-
 Doc/lib/email-mime.py                       |    6 +-
 Doc/lib/email-simple.py                     |    2 +-
 Doc/lib/email-unpack.py                     |   65 +-
 Doc/lib/email.tex                           |   72 +-
 Doc/lib/emailcharsets.tex                   |    8 +-
 Doc/lib/emailencoders.tex                   |    4 +-
 Doc/lib/emailexc.tex                        |    6 +-
 Doc/lib/emailgenerator.tex                  |   25 +-
 Doc/lib/emailheaders.tex                    |   17 +-
 Doc/lib/emailiter.tex                       |    4 +-
 Doc/lib/emailmessage.tex                    |   73 +-
 Doc/lib/emailmimebase.tex                   |   65 +-
 Doc/lib/emailparser.tex                     |   26 +-
 Doc/lib/emailutil.tex                       |    6 +-
 Doc/lib/mimelib.tex                         |   17 +-
 Lib/email/Charset.py                        |  370 ----
 Lib/email/Encoders.py                       |   78 -
 Lib/email/Errors.py                         |   53 -
 Lib/email/FeedParser.py                     |  477 -----
 Lib/email/Generator.py                      |  352 ---
 Lib/email/Header.py                         |  495 -----
 Lib/email/Iterators.py                      |   67 -
 Lib/email/MIMEAudio.py                      |   72 -
 Lib/email/MIMEBase.py                       |   24 -
 Lib/email/MIMEImage.py                      |   45 -
 Lib/email/MIMEMessage.py                    |   32 -
 Lib/email/MIMEMultipart.py                  |   39 -
 Lib/email/MIMENonMultipart.py               |   24 -
 Lib/email/MIMEText.py                       |   28 -
 Lib/email/Message.py                        |  814 -------
 Lib/email/Parser.py                         |   88 -
 Lib/email/Utils.py                          |  291 ---
 Lib/email/__init__.py                       |   77 +-
 Lib/email/_parseaddr.py                     |    7 +
 Lib/email/base64MIME.py                     |  172 --
 Lib/email/base64mime.py                     |  184 ++
 Lib/email/charset.py                        |  388 ++++
 Lib/email/encoders.py                       |   88 +
 Lib/email/errors.py                         |   57 +
 Lib/email/feedparser.py                     |  480 +++++
 Lib/email/generator.py                      |  348 +++
 Lib/email/header.py                         |  502 +++++
 Lib/email/iterators.py                      |   73 +
 Lib/email/message.py                        |  773 +++++++
 Lib/email/mime/__init__.py                  |    0
 Lib/email/mime/application.py               |   36 +
 Lib/email/mime/audio.py                     |   73 +
 Lib/email/mime/base.py                      |   26 +
 Lib/email/mime/image.py                     |   46 +
 Lib/email/mime/message.py                   |   34 +
 Lib/email/mime/multipart.py                 |   41 +
 Lib/email/mime/nonmultipart.py              |   26 +
 Lib/email/mime/text.py                      |   30 +
 Lib/email/parser.py                         |   91 +
 Lib/email/quopriMIME.py                     |  318 ---
 Lib/email/quoprimime.py                     |  336 +++
 Lib/email/test/test_email.py                |  125 +-
 Lib/email/test/test_email_codecs.py         |    7 +
 Lib/email/test/test_email_codecs_renamed.py |   77 +
 Lib/email/test/test_email_renamed.py        | 3078 +++++++++++++++++++++++++++
 Lib/email/utils.py                          |  306 +++
 Lib/test/test_pyclbr.py                     |    2 +-
 63 files changed, 7515 insertions(+), 4162 deletions(-)
 delete mode 100644 Lib/email/Charset.py
 delete mode 100644 Lib/email/Encoders.py
 delete mode 100644 Lib/email/Errors.py
 delete mode 100644 Lib/email/FeedParser.py
 delete mode 100644 Lib/email/Generator.py
 delete mode 100644 Lib/email/Header.py
 delete mode 100644 Lib/email/Iterators.py
 delete mode 100644 Lib/email/MIMEAudio.py
 delete mode 100644 Lib/email/MIMEBase.py
 delete mode 100644 Lib/email/MIMEImage.py
 delete mode 100644 Lib/email/MIMEMessage.py
 delete mode 100644 Lib/email/MIMEMultipart.py
 delete mode 100644 Lib/email/MIMENonMultipart.py
 delete mode 100644 Lib/email/MIMEText.py
 delete mode 100644 Lib/email/Message.py
 delete mode 100644 Lib/email/Parser.py
 delete mode 100644 Lib/email/Utils.py
 delete mode 100644 Lib/email/base64MIME.py
 create mode 100644 Lib/email/base64mime.py
 create mode 100644 Lib/email/charset.py
 create mode 100644 Lib/email/encoders.py
 create mode 100644 Lib/email/errors.py
 create mode 100644 Lib/email/feedparser.py
 create mode 100644 Lib/email/generator.py
 create mode 100644 Lib/email/header.py
 create mode 100644 Lib/email/iterators.py
 create mode 100644 Lib/email/message.py
 create mode 100644 Lib/email/mime/__init__.py
 create mode 100644 Lib/email/mime/application.py
 create mode 100644 Lib/email/mime/audio.py
 create mode 100644 Lib/email/mime/base.py
 create mode 100644 Lib/email/mime/image.py
 create mode 100644 Lib/email/mime/message.py
 create mode 100644 Lib/email/mime/multipart.py
 create mode 100644 Lib/email/mime/nonmultipart.py
 create mode 100644 Lib/email/mime/text.py
 create mode 100644 Lib/email/parser.py
 delete mode 100644 Lib/email/quopriMIME.py
 create mode 100644 Lib/email/quoprimime.py
 create mode 100644 Lib/email/test/test_email_codecs_renamed.py
 create mode 100644 Lib/email/test/test_email_renamed.py
 create mode 100644 Lib/email/utils.py

diff --git a/Doc/lib/email-dir.py b/Doc/lib/email-dir.py
index 2d89a2f..c04f57d 100644
--- a/Doc/lib/email-dir.py
+++ b/Doc/lib/email-dir.py
@@ -1,83 +1,69 @@
 #!/usr/bin/env python
 
-"""Send the contents of a directory as a MIME message.
+"""Send the contents of a directory as a MIME message."""
 
-Usage: dirmail [options] from to [to ...]*
-
-Options:
-    -h / --help
-        Print this message and exit.
-
-    -d directory
-    --directory=directory
-        Mail the contents of the specified directory, otherwise use the
-        current directory.  Only the regular files in the directory are sent,
-        and we don't recurse to subdirectories.
-
-`from' is the email address of the sender of the message.
-
-`to' is the email address of the recipient of the message, and multiple
-recipients may be given.
-
-The email is sent by forwarding to your local SMTP server, which then does the
-normal delivery process.  Your local machine must be running an SMTP server.
-"""
-
-import sys
 import os
-import getopt
+import sys
 import smtplib
 # For guessing MIME type based on file name extension
 import mimetypes
 
-from email import Encoders
-from email.Message import Message
-from email.MIMEAudio import MIMEAudio
-from email.MIMEBase import MIMEBase
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEImage import MIMEImage
-from email.MIMEText import MIMEText
-
-COMMASPACE = ', '
+from optparse import OptionParser
 
+from email import encoders
+from email.message import Message
+from email.mime.audio import MIMEAudio
+from email.mime.base import MIMEBase
+from email.mime.image import MIMEImage
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
 
-def usage(code, msg=''):
-    print >> sys.stderr, __doc__
-    if msg:
-        print >> sys.stderr, msg
-    sys.exit(code)
+COMMASPACE = ', '
 
 
 def main():
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], 'hd:', ['help', 'directory='])
-    except getopt.error, msg:
-        usage(1, msg)
-
-    dir = os.curdir
-    for opt, arg in opts:
-        if opt in ('-h', '--help'):
-            usage(0)
-        elif opt in ('-d', '--directory'):
-            dir = arg
-
-    if len(args) < 2:
-        usage(1)
-
-    sender = args[0]
-    recips = args[1:]
-
+    parser = OptionParser(usage="""\
+Send the contents of a directory as a MIME message.
+
+Usage: %prog [options]
+
+Unless the -o option is given, the email is sent by forwarding to your local
+SMTP server, which then does the normal delivery process.  Your local machine
+must be running an SMTP server.
+""")
+    parser.add_option('-d', '--directory',
+                      type='string', action='store',
+                      help="""Mail the contents of the specified directory,
+                      otherwise use the current directory.  Only the regular
+                      files in the directory are sent, and we don't recurse to
+                      subdirectories.""")
+    parser.add_option('-o', '--output',
+                      type='string', action='store', metavar='FILE',
+                      help="""Print the composed message to FILE instead of
+                      sending the message to the SMTP server.""")
+    parser.add_option('-s', '--sender',
+                      type='string', action='store', metavar='SENDER',
+                      help='The value of the From: header (required)')
+    parser.add_option('-r', '--recipient',
+                      type='string', action='append', metavar='RECIPIENT',
+                      default=[], dest='recipients',
+                      help='A To: header value (at least one required)')
+    opts, args = parser.parse_args()
+    if not opts.sender or not opts.recipients:
+        parser.print_help()
+        sys.exit(1)
+    directory = opts.directory
+    if not directory:
+        directory = '.'
     # Create the enclosing (outer) message
     outer = MIMEMultipart()
-    outer['Subject'] = 'Contents of directory %s' % os.path.abspath(dir)
-    outer['To'] = COMMASPACE.join(recips)
-    outer['From'] = sender
+    outer['Subject'] = 'Contents of directory %s' % os.path.abspath(directory)
+    outer['To'] = COMMASPACE.join(opts.recipients)
+    outer['From'] = opts.sender
     outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'
-    # To guarantee the message ends with a newline
-    outer.epilogue = ''
 
-    for filename in os.listdir(dir):
-        path = os.path.join(dir, filename)
+    for filename in os.listdir(directory):
+        path = os.path.join(directory, filename)
         if not os.path.isfile(path):
             continue
         # Guess the content type based on the file's extension.  Encoding
@@ -108,16 +94,21 @@ def main():
             msg.set_payload(fp.read())
             fp.close()
             # Encode the payload using Base64
-            Encoders.encode_base64(msg)
+            encoders.encode_base64(msg)
         # Set the filename parameter
         msg.add_header('Content-Disposition', 'attachment', filename=filename)
         outer.attach(msg)
-
-    # Now send the message
-    s = smtplib.SMTP()
-    s.connect()
-    s.sendmail(sender, recips, outer.as_string())
-    s.close()
+    # Now send or store the message
+    composed = outer.as_string()
+    if opts.output:
+        fp = open(opts.output, 'w')
+        fp.write(composed)
+        fp.close()
+    else:
+        s = smtplib.SMTP()
+        s.connect()
+        s.sendmail(opts.sender, opts.recipients, composed)
+        s.close()
 
 
 if __name__ == '__main__':
diff --git a/Doc/lib/email-mime.py b/Doc/lib/email-mime.py
index 048a59f..5097253 100644
--- a/Doc/lib/email-mime.py
+++ b/Doc/lib/email-mime.py
@@ -2,8 +2,8 @@
 import smtplib
 
 # Here are the email package modules we'll need
-from email.MIMEImage import MIMEImage
-from email.MIMEMultipart import MIMEMultipart
+from email.mime.image import MIMEImage
+from email.mime.multipart import MIMEMultipart
 
 COMMASPACE = ', '
 
@@ -15,8 +15,6 @@ msg['Subject'] = 'Our family reunion'
 msg['From'] = me
 msg['To'] = COMMASPACE.join(family)
 msg.preamble = 'Our family reunion'
-# Guarantees the message ends in a newline
-msg.epilogue = ''
 
 # Assume we know that the image files are all in PNG format
 for file in pngfiles:
diff --git a/Doc/lib/email-simple.py b/Doc/lib/email-simple.py
index a445f1b..44152a4 100644
--- a/Doc/lib/email-simple.py
+++ b/Doc/lib/email-simple.py
@@ -2,7 +2,7 @@
 import smtplib
 
 # Import the email modules we'll need
-from email.MIMEText import MIMEText
+from email.mime.text import MIMEText
 
 # Open a plain text file for reading.  For this example, assume that
 # the text file contains only ASCII characters.
diff --git a/Doc/lib/email-unpack.py b/Doc/lib/email-unpack.py
index b166fdb..fc05d99 100644
--- a/Doc/lib/email-unpack.py
+++ b/Doc/lib/email-unpack.py
@@ -1,59 +1,44 @@
 #!/usr/bin/env python
 
-"""Unpack a MIME message into a directory of files.
+"""Unpack a MIME message into a directory of files."""
 
-Usage: unpackmail [options] msgfile
-
-Options:
-    -h / --help
-        Print this message and exit.
-
-    -d directory
-    --directory=directory
-        Unpack the MIME message into the named directory, which will be
-        created if it doesn't already exist.
-
-msgfile is the path to the file containing the MIME message.
-"""
-
-import sys
 import os
-import getopt
+import sys
+import email
 import errno
 import mimetypes
-import email
-
 
-def usage(code, msg=''):
-    print >> sys.stderr, __doc__
-    if msg:
-        print >> sys.stderr, msg
-    sys.exit(code)
+from optparse import OptionParser
 
 
 def main():
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], 'hd:', ['help', 'directory='])
-    except getopt.error, msg:
-        usage(1, msg)
-
-    dir = os.curdir
-    for opt, arg in opts:
-        if opt in ('-h', '--help'):
-            usage(0)
-        elif opt in ('-d', '--directory'):
-            dir = arg
+    parser = OptionParser(usage="""\
+Unpack a MIME message into a directory of files.
+
+Usage: %prog [options] msgfile
+""")
+    parser.add_option('-d', '--directory',
+                      type='string', action='store',
+                      help="""Unpack the MIME message into the named
+                      directory, which will be created if it doesn't already
+                      exist.""")
+    opts, args = parser.parse_args()
+    if not opts.directory:
+        parser.print_help()
+        sys.exit(1)
 
     try:
         msgfile = args[0]
     except IndexError:
-        usage(1)
+        parser.print_help()
+        sys.exit(1)
 
     try:
-        os.mkdir(dir)
+        os.mkdir(opts.directory)
     except OSError, e:
         # Ignore directory exists error
-        if e.errno <> errno.EEXIST: raise
+        if e.errno <> errno.EEXIST:
+            raise
 
     fp = open(msgfile)
     msg = email.message_from_file(fp)
@@ -74,8 +59,8 @@ def main():
                 ext = '.bin'
             filename = 'part-%03d%s' % (counter, ext)
         counter += 1
-        fp = open(os.path.join(dir, filename), 'wb')
-        fp.write(part.get_payload(decode=1))
+        fp = open(os.path.join(opts.directory, filename), 'wb')
+        fp.write(part.get_payload(decode=True))
         fp.close()
 
 
diff --git a/Doc/lib/email.tex b/Doc/lib/email.tex
index 3a90e22..6853325 100644
--- a/Doc/lib/email.tex
+++ b/Doc/lib/email.tex
@@ -1,4 +1,4 @@
-% Copyright (C) 2001-2004 Python Software Foundation
+% Copyright (C) 2001-2006 Python Software Foundation
 % Author: barry@python.org (Barry Warsaw)
 
 \section{\module{email} ---
@@ -18,10 +18,10 @@ subsumes most of the functionality in several older standard modules
 such as \refmodule{rfc822}, \refmodule{mimetools},
 \refmodule{multifile}, and other non-standard packages such as
 \module{mimecntl}.  It is specifically \emph{not} designed to do any
-sending of email messages to SMTP (\rfc{2821}) servers; that is the
-function of the \refmodule{smtplib} module.  The \module{email}
-package attempts to be as RFC-compliant as possible, supporting in
-addition to \rfc{2822}, such MIME-related RFCs as
+sending of email messages to SMTP (\rfc{2821}), NNTP, or other servers; those
+are functions of modules such as \refmodule{smtplib} and \refmodule{nntplib}.
+The \module{email} package attempts to be as RFC-compliant as possible,
+supporting in addition to \rfc{2822}, such MIME-related RFCs as
 \rfc{2045}, \rfc{2046}, \rfc{2047}, and \rfc{2231}.
 
 The primary distinguishing feature of the \module{email} package is
@@ -41,7 +41,7 @@ The following sections describe the functionality of the
 should be common in applications: an email message is read as flat
 text from a file or other source, the text is parsed to produce the
 object structure of the email message, this structure is manipulated,
-and finally rendered back into flat text.
+and finally, the object tree is rendered back into flat text.
 
 It is perfectly feasible to create the object structure out of whole
 cloth --- i.e. completely from scratch.  From there, a similar
@@ -56,6 +56,7 @@ package, a section on differences and porting is provided.
 
 \begin{seealso}
     \seemodule{smtplib}{SMTP protocol client}
+    \seemodule{nntplib}{NNTP protocol client}
 \end{seealso}
 
 \subsection{Representing an email message}
@@ -88,22 +89,51 @@ package, a section on differences and porting is provided.
 \subsection{Iterators}
 \input{emailiter}
 
-\subsection{Package History}
+\subsection{Package History\label{email-pkg-history}}
 
-Version 1 of the \module{email} package was bundled with Python
-releases up to Python 2.2.1.  Version 2 was developed for the Python
-2.3 release, and backported to Python 2.2.2.  It was also available as
-a separate distutils-based package, and is compatible back to Python 2.1.
+This table describes the release history of the email package, corresponding
+to the version of Python that the package was released with.  For purposes of
+this document, when you see a note about change or added versions, these refer
+to the Python version the change was made it, \emph{not} the email package
+version.  This table also describes the Python compatibility of each version
+of the package.
 
-\module{email} version 3.0 was released with Python 2.4 and as a separate
-distutils-based package.  It is compatible back to Python 2.3.
+\begin{tableiii}{l|l|l}{constant}{email version}{distributed with}{compatible with}
+\lineiii{1.x}{Python 2.2.0 to Python 2.2.1}{\emph{no longer supported}}
+\lineiii{2.5}{Python 2.2.2+ and Python 2.3}{Python 2.1 to 2.5}
+\lineiii{3.0}{Python 2.4}{Python 2.3 to 2.5}
+\lineiii{4.0}{Python 2.5}{Python 2.3 to 2.5}
+\end{tableiii}
 
-Here are the differences between \module{email} version 3 and version 2:
+Here are the major differences between \module{email} verson 4 and version 3:
+
+\begin{itemize}
+\item All modules have been renamed according to \pep{8} standards.  For
+      example, the version 3 module \module{email.Message} was renamed to
+      \module{email.message} in version 4.
+
+\item A new subpackage \module{email.mime} was added and all the version 3
+      \module{email.MIME*} modules were renamed and situated into the
+      \module{email.mime} subpackage.  For example, the version 3 module
+      \module{email.MIMEText} was renamed to \module{email.mime.text}.
+
+      \emph{Note that the version 3 names will continue to work until Python
+      2.6}.
+
+\item The \module{email.mime.application} module was added, which contains the
+      \class{MIMEApplication} class.
+
+\item Methods that were deprecated in version 3 have been removed.  These
+      include \method{Generator.__call__()}, \method{Message.get_type()},
+      \method{Message.get_main_type()}, \method{Message.get_subtype()}.
+\end{itemize}
+
+Here are the major differences between \module{email} version 3 and version 2:
 
 \begin{itemize}
 \item The \class{FeedParser} class was introduced, and the \class{Parser}
       class was implemented in terms of the \class{FeedParser}.  All parsing
-      there for is non-strict, and parsing will make a best effort never to
+      therefore is non-strict, and parsing will make a best effort never to
       raise an exception.  Problems found while parsing messages are stored in
       the message's \var{defect} attribute.
 
@@ -117,7 +147,7 @@ Here are the differences between \module{email} version 3 and version 2:
       \method{Generator.__call__()}, \method{Message.get_type()},
       \method{Message.get_main_type()}, \method{Message.get_subtype()}, and
       the \var{strict} argument to the \class{Parser} class.  These are
-      expected to be removed in email 3.1.
+      expected to be removed in future versions.
 
 \item Support for Pythons earlier than 2.3 has been removed.
 \end{itemize}
@@ -278,12 +308,12 @@ The \class{Message} class has the following differences:
 \item The method \method{getpayloadastext()} was removed.  Similar
       functionality
       is supported by the \class{DecodedGenerator} class in the
-      \refmodule{email.Generator} module.
+      \refmodule{email.generator} module.
 
 \item The method \method{getbodyastext()} was removed.  You can get
       similar functionality by creating an iterator with
       \function{typed_subpart_iterator()} in the
-      \refmodule{email.Iterators} module.
+      \refmodule{email.iterators} module.
 \end{itemize}
 
 The \class{Parser} class has no differences in its public interface.
@@ -295,7 +325,7 @@ notification\footnote{Delivery Status Notifications (DSN) are defined
 in \rfc{1894}.}.
 
 The \class{Generator} class has no differences in its public
-interface.  There is a new class in the \refmodule{email.Generator}
+interface.  There is a new class in the \refmodule{email.generator}
 module though, called \class{DecodedGenerator} which provides most of
 the functionality previously available in the
 \method{Message.getpayloadastext()} method.
@@ -329,11 +359,11 @@ The following modules and classes have been changed:
 
 \module{mimelib} provided some utility functions in its
 \module{address} and \module{date} modules.  All of these functions
-have been moved to the \refmodule{email.Utils} module.
+have been moved to the \refmodule{email.utils} module.
 
 The \code{MsgReader} class/module has been removed.  Its functionality
 is most closely supported in the \function{body_line_iterator()}
-function in the \refmodule{email.Iterators} module.
+function in the \refmodule{email.iterators} module.
 
 \subsection{Examples}
 
diff --git a/Doc/lib/emailcharsets.tex b/Doc/lib/emailcharsets.tex
index 18f2a01..e0be68a 100644
--- a/Doc/lib/emailcharsets.tex
+++ b/Doc/lib/emailcharsets.tex
@@ -1,4 +1,4 @@
-\declaremodule{standard}{email.Charset}
+\declaremodule{standard}{email.charset}
 \modulesynopsis{Character Sets}
 
 This module provides a class \class{Charset} for representing
@@ -7,6 +7,8 @@ well as a character set registry and several convenience methods for
 manipulating this registry.  Instances of \class{Charset} are used in
 several other modules within the \module{email} package.
 
+Import this class from the \module{email.charset} module.
+
 \versionadded{2.2.2}
 
 \begin{classdesc}{Charset}{\optional{input_charset}}
@@ -153,7 +155,7 @@ input charset to the output charset automatically.  This is not useful
 for multibyte character sets, which have line length issues (multibyte
 characters must be split on a character, not a byte boundary); use the
 higher-level \class{Header} class to deal with these issues (see
-\refmodule{email.Header}).  \var{convert} defaults to \code{False}.
+\refmodule{email.header}).  \var{convert} defaults to \code{False}.
 
 The type of encoding (base64 or quoted-printable) will be based on
 the \var{header_encoding} attribute.
@@ -188,7 +190,7 @@ This method allows you to compare two \class{Charset} instances for equality.
 This method allows you to compare two \class{Charset} instances for inequality.
 \end{methoddesc}
 
-The \module{email.Charset} module also provides the following
+The \module{email.charset} module also provides the following
 functions for adding new entries to the global character set, alias,
 and codec registries:
 
diff --git a/Doc/lib/emailencoders.tex b/Doc/lib/emailencoders.tex
index a49e04d..3d05c2a 100644
--- a/Doc/lib/emailencoders.tex
+++ b/Doc/lib/emailencoders.tex
@@ -1,4 +1,4 @@
-\declaremodule{standard}{email.Encoders}
+\declaremodule{standard}{email.encoders}
 \modulesynopsis{Encoders for email message payloads.}
 
 When creating \class{Message} objects from scratch, you often need to
@@ -7,7 +7,7 @@ This is especially true for \mimetype{image/*} and \mimetype{text/*}
 type messages containing binary data.
 
 The \module{email} package provides some convenient encodings in its
-\module{Encoders} module.  These encoders are actually used by the
+\module{encoders} module.  These encoders are actually used by the
 \class{MIMEAudio} and \class{MIMEImage} class constructors to provide default
 encodings.  All encoder functions take exactly one argument, the message
 object to encode.  They usually extract the payload, encode it, and reset the
diff --git a/Doc/lib/emailexc.tex b/Doc/lib/emailexc.tex
index 6ac0889..3cef1d5 100644
--- a/Doc/lib/emailexc.tex
+++ b/Doc/lib/emailexc.tex
@@ -1,8 +1,8 @@
-\declaremodule{standard}{email.Errors}
+\declaremodule{standard}{email.errors}
 \modulesynopsis{The exception classes used by the email package.}
 
 The following exception classes are defined in the
-\module{email.Errors} module:
+\module{email.errors} module:
 
 \begin{excclassdesc}{MessageError}{}
 This is the base class for all exceptions that the \module{email}
@@ -59,7 +59,7 @@ problem was found, so for example, if a message nested inside a
 \mimetype{multipart/alternative} had a malformed header, that nested message
 object would have a defect, but the containing messages would not.
 
-All defect classes are subclassed from \class{email.Errors.MessageDefect}, but
+All defect classes are subclassed from \class{email.errors.MessageDefect}, but
 this class is \emph{not} an exception!
 
 \versionadded[All the defect classes were added]{2.4}
diff --git a/Doc/lib/emailgenerator.tex b/Doc/lib/emailgenerator.tex
index 330abc0..3415442 100644
--- a/Doc/lib/emailgenerator.tex
+++ b/Doc/lib/emailgenerator.tex
@@ -1,4 +1,4 @@
-\declaremodule{standard}{email.Generator}
+\declaremodule{standard}{email.generator}
 \modulesynopsis{Generate flat text email messages from a message structure.}
 
 One of the most common tasks is to generate the flat text of the email
@@ -8,7 +8,7 @@ module or the \refmodule{nntplib} module, or print the message on the
 console.  Taking a message object structure and producing a flat text
 document is the job of the \class{Generator} class.
 
-Again, as with the \refmodule{email.Parser} module, you aren't limited
+Again, as with the \refmodule{email.parser} module, you aren't limited
 to the functionality of the bundled generator; you could write one
 from scratch yourself.  However the bundled generator knows how to
 generate most email in a standards-compliant way, should handle MIME
@@ -17,7 +17,8 @@ transformation from flat text, to a message structure via the
 \class{Parser} class, and back to flat text, is idempotent (the input
 is identical to the output).
 
-Here are the public methods of the \class{Generator} class:
+Here are the public methods of the \class{Generator} class, imported from the
+\module{email.generator} module:
 
 \begin{classdesc}{Generator}{outfp\optional{, mangle_from_\optional{,
     maxheaderlen}}}
@@ -40,7 +41,7 @@ mailbox format files.
 Optional \var{maxheaderlen} specifies the longest length for a
 non-continued header.  When a header line is longer than
 \var{maxheaderlen} (in characters, with tabs expanded to 8 spaces),
-the header will be split as defined in the \module{email.Header}
+the header will be split as defined in the \module{email.header.Header}
 class.  Set to zero to disable header wrapping.  The default is 78, as
 recommended (but not required) by \rfc{2822}.
 \end{classdesc}
@@ -81,9 +82,9 @@ be used in extended print statements.
 As a convenience, see the methods \method{Message.as_string()} and
 \code{str(aMessage)}, a.k.a. \method{Message.__str__()}, which
 simplify the generation of a formatted string representation of a
-message object.  For more detail, see \refmodule{email.Message}.
+message object.  For more detail, see \refmodule{email.message}.
 
-The \module{email.Generator} module also provides a derived class,
+The \module{email.generator} module also provides a derived class,
 called \class{DecodedGenerator} which is like the \class{Generator}
 base class, except that non-\mimetype{text} parts are substituted with
 a format string representing the part.
@@ -128,13 +129,5 @@ The default value for \var{fmt} is \code{None}, meaning
 \versionadded{2.2.2}
 \end{classdesc}
 
-\subsubsection{Deprecated methods}
-
-The following methods are deprecated in \module{email} version 2.
-They are documented here for completeness.
-
-\begin{methoddesc}[Generator]{__call__}{msg\optional{, unixfrom}}
-This method is identical to the \method{flatten()} method.
-
-\deprecated{2.2.2}{Use the \method{flatten()} method instead.}
-\end{methoddesc}
+\versionchanged[The previously deprecated method \method{__call__()} was
+removed]{2.5}
diff --git a/Doc/lib/emailheaders.tex b/Doc/lib/emailheaders.tex
index 2795644b..524d08c 100644
--- a/Doc/lib/emailheaders.tex
+++ b/Doc/lib/emailheaders.tex
@@ -1,4 +1,4 @@
-\declaremodule{standard}{email.Header}
+\declaremodule{standard}{email.header}
 \modulesynopsis{Representing non-ASCII headers}
 
 \rfc{2822} is the base standard that describes the format of email
@@ -15,17 +15,18 @@ slew of RFCs have been written describing how to encode email
 containing non-\ASCII{} characters into \rfc{2822}-compliant format.
 These RFCs include \rfc{2045}, \rfc{2046}, \rfc{2047}, and \rfc{2231}.
 The \module{email} package supports these standards in its
-\module{email.Header} and \module{email.Charset} modules.
+\module{email.header} and \module{email.charset} modules.
 
 If you want to include non-\ASCII{} characters in your email headers,
 say in the \mailheader{Subject} or \mailheader{To} fields, you should
 use the \class{Header} class and assign the field in the
 \class{Message} object to an instance of \class{Header} instead of
-using a string for the header value.  For example:
+using a string for the header value.  Import the \class{Header} class from the
+\module{email.header} module.  For example:
 
 \begin{verbatim}
->>> from email.Message import Message
->>> from email.Header import Header
+>>> from email.message import Message
+>>> from email.header import Header
 >>> msg = Message()
 >>> h = Header('p\xf6stal', 'iso-8859-1')
 >>> msg['Subject'] = h
@@ -87,7 +88,7 @@ Optional \var{errors} is passed straight through to the
 Append the string \var{s} to the MIME header.
 
 Optional \var{charset}, if given, should be a \class{Charset} instance
-(see \refmodule{email.Charset}) or the name of a character set, which
+(see \refmodule{email.charset}) or the name of a character set, which
 will be converted to a \class{Charset} instance.  A value of
 \code{None} (the default) means that the \var{charset} given in the
 constructor is used.
@@ -139,7 +140,7 @@ This method allows you to compare two \class{Header} instances for equality.
 This method allows you to compare two \class{Header} instances for inequality.
 \end{methoddesc}
 
-The \module{email.Header} module also provides the following
+The \module{email.header} module also provides the following
 convenient functions.
 
 \begin{funcdesc}{decode_header}{header}
@@ -155,7 +156,7 @@ encoded string.
 Here's an example:
 
 \begin{verbatim}
->>> from email.Header import decode_header
+>>> from email.header import decode_header
 >>> decode_header('=?iso-8859-1?q?p=F6stal?=')
 [('p\xf6stal', 'iso-8859-1')]
 \end{verbatim}
diff --git a/Doc/lib/emailiter.tex b/Doc/lib/emailiter.tex
index d1a8f98..ef8ef6f 100644
--- a/Doc/lib/emailiter.tex
+++ b/Doc/lib/emailiter.tex
@@ -1,8 +1,8 @@
-\declaremodule{standard}{email.Iterators}
+\declaremodule{standard}{email.iterators}
 \modulesynopsis{Iterate over a  message object tree.}
 
 Iterating over a message object tree is fairly easy with the
-\method{Message.walk()} method.  The \module{email.Iterators} module
+\method{Message.walk()} method.  The \module{email.iterators} module
 provides some useful higher level iterations over message object
 trees.
 
diff --git a/Doc/lib/emailmessage.tex b/Doc/lib/emailmessage.tex
index 9b41852..7bd7dd8 100644
--- a/Doc/lib/emailmessage.tex
+++ b/Doc/lib/emailmessage.tex
@@ -1,10 +1,11 @@
-\declaremodule{standard}{email.Message}
+\declaremodule{standard}{email.message}
 \modulesynopsis{The base class representing email messages.}
 
 The central class in the \module{email} package is the
-\class{Message} class; it is the base class for the \module{email}
-object model.  \class{Message} provides the core functionality for
-setting and querying header fields, and for accessing message bodies.
+\class{Message} class, imported from the \module{email.message} module.  It is
+the base class for the \module{email} object model.  \class{Message} provides
+the core functionality for setting and querying header fields, and for
+accessing message bodies.
 
 Conceptually, a \class{Message} object consists of \emph{headers} and
 \emph{payloads}.  Headers are \rfc{2822} style field names and
@@ -45,7 +46,7 @@ begin with \code{From }.  For more flexibility, instantiate a
 
 \begin{verbatim}
 from cStringIO import StringIO
-from email.Generator import Generator
+from email.generator import Generator
 fp = StringIO()
 g = Generator(fp, mangle_from_=False, maxheaderlen=60)
 g.flatten(msg)
@@ -119,7 +120,7 @@ client's responsibility to ensure the payload invariants.  Optional
 
 \begin{methoddesc}[Message]{set_charset}{charset}
 Set the character set of the payload to \var{charset}, which can
-either be a \class{Charset} instance (see \refmodule{email.Charset}), a
+either be a \class{Charset} instance (see \refmodule{email.charset}), a
 string naming a character set,
 or \code{None}.  If it is a string, it will be converted to a
 \class{Charset} instance.  If \var{charset} is \code{None}, the
@@ -128,8 +129,8 @@ or \code{None}.  If it is a string, it will be converted to a
 \exception{TypeError}.
 
 The message will be assumed to be of type \mimetype{text/*} encoded with
-\code{charset.input_charset}.  It will be converted to
-\code{charset.output_charset}
+\var{charset.input_charset}.  It will be converted to
+\var{charset.output_charset}
 and encoded properly, if needed, when generating the plain text
 representation of the message.  MIME headers
 (\mailheader{MIME-Version}, \mailheader{Content-Type},
@@ -513,6 +514,9 @@ message/rfc822
 \end{verbatim}
 \end{methoddesc}
 
+\versionchanged[The previously deprecated methods \method{get_type()},
+\method{get_main_type()}, and \method{get_subtype()} were removed]{2.5}
+
 \class{Message} objects can also optionally contain two instance
 attributes, which can be used when generating the plain text of a MIME
 message.
@@ -532,7 +536,7 @@ to the message's \var{preamble} attribute.  When the \class{Generator}
 is writing out the plain text representation of a MIME message, and it
 finds the message has a \var{preamble} attribute, it will write this
 text in the area between the headers and the first boundary.  See
-\refmodule{email.Parser} and \refmodule{email.Generator} for details.
+\refmodule{email.parser} and \refmodule{email.generator} for details.
 
 Note that if the message object has no preamble, the
 \var{preamble} attribute will be \code{None}.
@@ -543,58 +547,15 @@ The \var{epilogue} attribute acts the same way as the \var{preamble}
 attribute, except that it contains text that appears between the last
 boundary and the end of the message.
 
-One note: when generating the flat text for a \mimetype{multipart}
-message that has no \var{epilogue} (using the standard
-\class{Generator} class), no newline is added after the closing
-boundary line.  If the message object has an \var{epilogue} and its
-value does not start with a newline, a newline is printed after the
-closing boundary.  This seems a little clumsy, but it makes the most
-practical sense.  The upshot is that if you want to ensure that a
-newline get printed after your closing \mimetype{multipart} boundary,
-set the \var{epilogue} to the empty string.
+\versionchanged[You do not need to set the epilogue to the empty string in
+order for the \class{Generator} to print a newline at the end of the
+file]{2.5}
 \end{datadesc}
 
 \begin{datadesc}{defects}
 The \var{defects} attribute contains a list of all the problems found when
-parsing this message.  See \refmodule{email.Errors} for a detailed description
+parsing this message.  See \refmodule{email.errors} for a detailed description
 of the possible parsing defects.
 
 \versionadded{2.4}
 \end{datadesc}
-
-\subsubsection{Deprecated methods}
-
-\versionchanged[The \method{add_payload()} method was removed; use the
-\method{attach()} method instead]{2.4}
-
-The following methods are deprecated.  They are documented here for
-completeness.
-
-\begin{methoddesc}[Message]{get_type}{\optional{failobj}}
-Return the message's content type, as a string of the form
-\mimetype{maintype/subtype} as taken from the
-\mailheader{Content-Type} header.
-The returned string is coerced to lowercase.
-
-If there is no \mailheader{Content-Type} header in the message,
-\var{failobj} is returned (defaults to \code{None}).
-
-\deprecated{2.2.2}{Use the \method{get_content_type()} method instead.}
-\end{methoddesc}
-
-\begin{methoddesc}[Message]{get_main_type}{\optional{failobj}}
-Return the message's \emph{main} content type.  This essentially returns the
-\var{maintype} part of the string returned by \method{get_type()}, with the
-same semantics for \var{failobj}.
-
-\deprecated{2.2.2}{Use the \method{get_content_maintype()} method instead.}
-\end{methoddesc}
-
-\begin{methoddesc}[Message]{get_subtype}{\optional{failobj}}
-Return the message's sub-content type.  This essentially returns the
-\var{subtype} part of the string returned by \method{get_type()}, with the
-same semantics for \var{failobj}.
-
-\deprecated{2.2.2}{Use the \method{get_content_subtype()} method instead.}
-\end{methoddesc}
-
diff --git a/Doc/lib/emailmimebase.tex b/Doc/lib/emailmimebase.tex
index 070c9a2..4735be3 100644
--- a/Doc/lib/emailmimebase.tex
+++ b/Doc/lib/emailmimebase.tex
@@ -1,3 +1,11 @@
+\declaremodule{standard}{email.mime}
+\declaremodule{standard}{email.mime.base}
+\declaremodule{standard}{email.mime.nonmultipart}
+\declaremodule{standard}{email.mime.multipart}
+\declaremodule{standard}{email.mime.audio}
+\declaremodule{standard}{email.mime.image}
+\declaremodule{standard}{email.mime.message}
+\declaremodule{standard}{email.mime.text}
 Ordinarily, you get a message object structure by passing a file or
 some text to a parser, which parses the text and returns the root
 message object.  However you can also build a complete message
@@ -6,26 +14,16 @@ hand.  In fact, you can also take an existing structure and add new
 \class{Message} objects, move them around, etc.  This makes a very
 convenient interface for slicing-and-dicing MIME messages.
 
-You can create a new object structure by creating \class{Message}
-instances, adding attachments and all the appropriate headers manually.
-For MIME messages though, the \module{email} package provides some
-convenient subclasses to make things easier.  Each of these classes
-should be imported from a module with the same name as the class, from
-within the \module{email} package.  E.g.:
-
-\begin{verbatim}
-import email.MIMEImage.MIMEImage
-\end{verbatim}
-
-or
-
-\begin{verbatim}
-from email.MIMEText import MIMEText
-\end{verbatim}
+You can create a new object structure by creating \class{Message} instances,
+adding attachments and all the appropriate headers manually.  For MIME
+messages though, the \module{email} package provides some convenient
+subclasses to make things easier.
 
 Here are the classes:
 
 \begin{classdesc}{MIMEBase}{_maintype, _subtype, **_params}
+Module: \module{email.mime.base}
+
 This is the base class for all the MIME-specific subclasses of
 \class{Message}.  Ordinarily you won't create instances specifically
 of \class{MIMEBase}, although you could.  \class{MIMEBase} is provided
@@ -45,6 +43,8 @@ The \class{MIMEBase} class always adds a \mailheader{Content-Type} header
 \end{classdesc}
 
 \begin{classdesc}{MIMENonMultipart}{}
+Module: \module{email.mime.nonmultipart}
+
 A subclass of \class{MIMEBase}, this is an intermediate base class for
 MIME messages that are not \mimetype{multipart}.  The primary purpose
 of this class is to prevent the use of the \method{attach()} method,
@@ -57,6 +57,7 @@ exception is raised.
 
 \begin{classdesc}{MIMEMultipart}{\optional{subtype\optional{,
     boundary\optional{, _subparts\optional{, _params}}}}}
+Module: \module{email.mime.multipart}
 
 A subclass of \class{MIMEBase}, this is an intermediate base class for
 MIME messages that are \mimetype{multipart}.  Optional \var{_subtype}
@@ -80,8 +81,31 @@ argument, which is a keyword dictionary.
 \versionadded{2.2.2}
 \end{classdesc}
 
+\begin{classdesc}{MIMEApplication}{_data\optional{, _subtype\optional{,
+    _encoder\optional{, **_params}}}}
+Module: \module{email.mime.application}
+
+A subclass of \class{MIMENonMultipart}, the \class{MIMEApplication} class is
+used to represent MIME message objects of major type \mimetype{application}.
+\var{_data} is a string containing the raw byte data.  Optional \var{_subtype}
+specifies the MIME subtype and defaults to \mimetype{octet-stream}.  
+
+Optional \var{_encoder} is a callable (i.e. function) which will
+perform the actual encoding of the data for transport.  This
+callable takes one argument, which is the \class{MIMEApplication} instance.
+It should use \method{get_payload()} and \method{set_payload()} to
+change the payload to encoded form.  It should also add any
+\mailheader{Content-Transfer-Encoding} or other headers to the message
+object as necessary.  The default encoding is base64.  See the
+\refmodule{email.encoders} module for a list of the built-in encoders.
+
+\var{_params} are passed straight through to the base class constructor.
+\versionadded{2.5}
+\end{classdesc}
+
 \begin{classdesc}{MIMEAudio}{_audiodata\optional{, _subtype\optional{,
     _encoder\optional{, **_params}}}}
+Module: \module{email.mime.audio}
 
 A subclass of \class{MIMENonMultipart}, the \class{MIMEAudio} class
 is used to create MIME message objects of major type \mimetype{audio}.
@@ -100,13 +124,14 @@ It should use \method{get_payload()} and \method{set_payload()} to
 change the payload to encoded form.  It should also add any
 \mailheader{Content-Transfer-Encoding} or other headers to the message
 object as necessary.  The default encoding is base64.  See the
-\refmodule{email.Encoders} module for a list of the built-in encoders.
+\refmodule{email.encoders} module for a list of the built-in encoders.
 
 \var{_params} are passed straight through to the base class constructor.
 \end{classdesc}
 
 \begin{classdesc}{MIMEImage}{_imagedata\optional{, _subtype\optional{,
     _encoder\optional{, **_params}}}}
+Module: \module{email.mime.image}
 
 A subclass of \class{MIMENonMultipart}, the \class{MIMEImage} class is
 used to create MIME message objects of major type \mimetype{image}.
@@ -125,13 +150,15 @@ It should use \method{get_payload()} and \method{set_payload()} to
 change the payload to encoded form.  It should also add any
 \mailheader{Content-Transfer-Encoding} or other headers to the message
 object as necessary.  The default encoding is base64.  See the
-\refmodule{email.Encoders} module for a list of the built-in encoders.
+\refmodule{email.encoders} module for a list of the built-in encoders.
 
 \var{_params} are passed straight through to the \class{MIMEBase}
 constructor.
 \end{classdesc}
 
 \begin{classdesc}{MIMEMessage}{_msg\optional{, _subtype}}
+Module: \module{email.mime.message}
+
 A subclass of \class{MIMENonMultipart}, the \class{MIMEMessage} class
 is used to create MIME objects of main type \mimetype{message}.
 \var{_msg} is used as the payload, and must be an instance of class
@@ -143,6 +170,8 @@ to \mimetype{rfc822}.
 \end{classdesc}
 
 \begin{classdesc}{MIMEText}{_text\optional{, _subtype\optional{, _charset}}}
+Module: \module{email.mime.text}
+
 A subclass of \class{MIMENonMultipart}, the \class{MIMEText} class is
 used to create MIME objects of major type \mimetype{text}.
 \var{_text} is the string for the payload.  \var{_subtype} is the
diff --git a/Doc/lib/emailparser.tex b/Doc/lib/emailparser.tex
index 5fac92f..609fa40 100644
--- a/Doc/lib/emailparser.tex
+++ b/Doc/lib/emailparser.tex
@@ -1,4 +1,4 @@
-\declaremodule{standard}{email.Parser}
+\declaremodule{standard}{email.parser}
 \modulesynopsis{Parse flat text email messages to produce a message
 	        object structure.}
 
@@ -41,9 +41,10 @@ message object trees any way it finds necessary.
 
 \versionadded{2.4}
 
-The \class{FeedParser} provides an API that is conducive to incremental
-parsing of email messages, such as would be necessary when reading the text of
-an email message from a source that can block (e.g. a socket).  The
+The \class{FeedParser}, imported from the \module{email.feedparser} module,
+provides an API that is conducive to incremental parsing of email messages,
+such as would be necessary when reading the text of an email message from a
+source that can block (e.g. a socket).  The
 \class{FeedParser} can of course be used to parse an email message fully
 contained in a string or a file, but the classic \class{Parser} API may be
 more convenient for such use cases.  The semantics and results of the two
@@ -56,14 +57,14 @@ accurate when parsing standards-compliant messages, and it does a very good
 job of parsing non-compliant messages, providing information about how a
 message was deemed broken.  It will populate a message object's \var{defects}
 attribute with a list of any problems it found in a message.  See the
-\refmodule{email.Errors} module for the list of defects that it can find.
+\refmodule{email.errors} module for the list of defects that it can find.
 
 Here is the API for the \class{FeedParser}:
 
 \begin{classdesc}{FeedParser}{\optional{_factory}}
 Create a \class{FeedParser} instance.  Optional \var{_factory} is a
 no-argument callable that will be called whenever a new message object is
-needed.  It defaults to the \class{email.Message.Message} class.
+needed.  It defaults to the \class{email.message.Message} class.
 \end{classdesc}
 
 \begin{methoddesc}[FeedParser]{feed}{data}
@@ -82,21 +83,22 @@ more data to a closed \class{FeedParser}.
 
 \subsubsection{Parser class API}
 
-The \class{Parser} provides an API that can be used to parse a message when
-the complete contents of the message are available in a string or file.  The
-\module{email.Parser} module also provides a second class, called
+The \class{Parser} class, imported from the \module{email.parser} module,
+provides an API that can be used to parse a message when the complete contents
+of the message are available in a string or file.  The
+\module{email.parser} module also provides a second class, called
 \class{HeaderParser} which can be used if you're only interested in
 the headers of the message. \class{HeaderParser} can be much faster in
 these situations, since it does not attempt to parse the message body,
 instead setting the payload to the raw body as a string.
 \class{HeaderParser} has the same API as the \class{Parser} class.
 
-\begin{classdesc}{Parser}{\optional{_class\optional{, strict}}}
+\begin{classdesc}{Parser}{\optional{_class}}
 The constructor for the \class{Parser} class takes an optional
 argument \var{_class}.  This must be a callable factory (such as a
 function or a class), and it is used whenever a sub-message object
 needs to be created.  It defaults to \class{Message} (see
-\refmodule{email.Message}).  The factory will be called without
+\refmodule{email.message}).  The factory will be called without
 arguments.
 
 The optional \var{strict} flag is ignored.  \deprecated{2.4}{Because the
@@ -201,6 +203,6 @@ Here are some notes on the parsing semantics:
       \method{is_multipart()} method may return \code{False}.  If such
       messages were parsed with the \class{FeedParser}, they will have an
       instance of the \class{MultipartInvariantViolationDefect} class in their
-      \var{defects} attribute list.  See \refmodule{email.Errors} for
+      \var{defects} attribute list.  See \refmodule{email.errors} for
       details.
 \end{itemize}
diff --git a/Doc/lib/emailutil.tex b/Doc/lib/emailutil.tex
index 491a2b9..fe96473 100644
--- a/Doc/lib/emailutil.tex
+++ b/Doc/lib/emailutil.tex
@@ -1,7 +1,7 @@
-\declaremodule{standard}{email.Utils}
+\declaremodule{standard}{email.utils}
 \modulesynopsis{Miscellaneous email package utilities.}
 
-There are several useful utilities provided in the \module{email.Utils}
+There are several useful utilities provided in the \module{email.utils}
 module:
 
 \begin{funcdesc}{quote}{str}
@@ -38,7 +38,7 @@ values as might be returned by \method{Message.get_all()}.  Here's a
 simple example that gets all the recipients of a message:
 
 \begin{verbatim}
-from email.Utils import getaddresses
+from email.utils import getaddresses
 
 tos = msg.get_all('to', [])
 ccs = msg.get_all('cc', [])
diff --git a/Doc/lib/mimelib.tex b/Doc/lib/mimelib.tex
index 67de597..491d844 100644
--- a/Doc/lib/mimelib.tex
+++ b/Doc/lib/mimelib.tex
@@ -12,9 +12,9 @@
 \authoraddress{\email{barry@python.org}}
 
 \date{\today}
-\release{3.0}			% software release, not documentation
+\release{4.0}			% software release, not documentation
 \setreleaseinfo{}		% empty for final release
-\setshortversion{3.0}		% major.minor only for software
+\setshortversion{4.0}		% major.minor only for software
 
 \begin{document}
 
@@ -38,11 +38,11 @@ The \module{email} package provides classes and utilities to create,
 parse, generate, and modify email messages, conforming to all the
 relevant email and MIME related RFCs.
 
-This document describes version 3.0 of the \module{email} package, which is
-distributed with Python 2.4 and is available as a standalone distutils-based
-package for use with Python 2.3.  \module{email} 3.0 is not compatible with
-Python versions earlier than 2.3.  For more information about the
-\module{email} package, including download links and mailing lists, see
+This document describes version 4.0 of the \module{email} package, which is
+distributed with Python 2.5 and is available as a standalone distutils-based
+package for use with earlier Python versions.  \module{email} 4.0 is not
+compatible with Python versions earlier than 2.3.  For more information about
+the \module{email} package, including download links and mailing lists, see
 \ulink{Python's email SIG}{http://www.python.org/sigs/email-sig}.
 
 The documentation that follows was written for the Python project, so
@@ -51,7 +51,8 @@ package documentation, there are a few notes to be aware of:
 
 \begin{itemize}
 \item Deprecation and ``version added'' notes are relative to the
-      Python version a feature was added or deprecated.
+      Python version a feature was added or deprecated.  See
+      the package history in section \ref{email-pkg-history} for details.
 
 \item If you're reading this documentation as part of the
       standalone \module{email} package, some of the internal links to
diff --git a/Lib/email/Charset.py b/Lib/email/Charset.py
deleted file mode 100644
index fd4043b..0000000
--- a/Lib/email/Charset.py
+++ /dev/null
@@ -1,370 +0,0 @@
-# Copyright (C) 2001-2006 Python Software Foundation
-# Author: Ben Gertzfield, Barry Warsaw
-# Contact: email-sig@python.org
-
-import email.base64MIME
-import email.quopriMIME
-from email.Encoders import encode_7or8bit
-
-
-
-# Flags for types of header encodings
-QP          = 1 # Quoted-Printable
-BASE64      = 2 # Base64
-SHORTEST    = 3 # the shorter of QP and base64, but only for headers
-
-# In "=?charset?q?hello_world?=", the =?, ?q?, and ?= add up to 7
-MISC_LEN = 7
-
-DEFAULT_CHARSET = 'us-ascii'
-
-
-
-# Defaults
-CHARSETS = {
-    # input        header enc  body enc output conv
-    'iso-8859-1':  (QP,        QP,      None),
-    'iso-8859-2':  (QP,        QP,      None),
-    'iso-8859-3':  (QP,        QP,      None),
-    'iso-8859-4':  (QP,        QP,      None),
-    # iso-8859-5 is Cyrillic, and not especially used
-    # iso-8859-6 is Arabic, also not particularly used
-    # iso-8859-7 is Greek, QP will not make it readable
-    # iso-8859-8 is Hebrew, QP will not make it readable
-    'iso-8859-9':  (QP,        QP,      None),
-    'iso-8859-10': (QP,        QP,      None),
-    # iso-8859-11 is Thai, QP will not make it readable
-    'iso-8859-13': (QP,        QP,      None),
-    'iso-8859-14': (QP,        QP,      None),
-    'iso-8859-15': (QP,        QP,      None),
-    'windows-1252':(QP,        QP,      None),
-    'viscii':      (QP,        QP,      None),
-    'us-ascii':    (None,      None,    None),
-    'big5':        (BASE64,    BASE64,  None),
-    'gb2312':      (BASE64,    BASE64,  None),
-    'euc-jp':      (BASE64,    None,    'iso-2022-jp'),
-    'shift_jis':   (BASE64,    None,    'iso-2022-jp'),
-    'iso-2022-jp': (BASE64,    None,    None),
-    'koi8-r':      (BASE64,    BASE64,  None),
-    'utf-8':       (SHORTEST,  BASE64, 'utf-8'),
-    # We're making this one up to represent raw unencoded 8-bit
-    '8bit':        (None,      BASE64, 'utf-8'),
-    }
-
-# Aliases for other commonly-used names for character sets.  Map
-# them to the real ones used in email.
-ALIASES = {
-    'latin_1': 'iso-8859-1',
-    'latin-1': 'iso-8859-1',
-    'latin_2': 'iso-8859-2',
-    'latin-2': 'iso-8859-2',
-    'latin_3': 'iso-8859-3',
-    'latin-3': 'iso-8859-3',
-    'latin_4': 'iso-8859-4',
-    'latin-4': 'iso-8859-4',
-    'latin_5': 'iso-8859-9',
-    'latin-5': 'iso-8859-9',
-    'latin_6': 'iso-8859-10',
-    'latin-6': 'iso-8859-10',
-    'latin_7': 'iso-8859-13',
-    'latin-7': 'iso-8859-13',
-    'latin_8': 'iso-8859-14',
-    'latin-8': 'iso-8859-14',
-    'latin_9': 'iso-8859-15',
-    'latin-9': 'iso-8859-15',
-    'cp949':   'ks_c_5601-1987',
-    'euc_jp':  'euc-jp',
-    'euc_kr':  'euc-kr',
-    'ascii':   'us-ascii',
-    }
-
-
-# Map charsets to their Unicode codec strings.
-CODEC_MAP = {
-    'gb2312':      'eucgb2312_cn',
-    'big5':        'big5_tw',
-    # Hack: We don't want *any* conversion for stuff marked us-ascii, as all
-    # sorts of garbage might be sent to us in the guise of 7-bit us-ascii.
-    # Let that stuff pass through without conversion to/from Unicode.
-    'us-ascii':    None,
-    }
-
-
-
-# Convenience functions for extending the above mappings
-def add_charset(charset, header_enc=None, body_enc=None, output_charset=None):
-    """Add character set properties to the global registry.
-
-    charset is the input character set, and must be the canonical name of a
-    character set.
-
-    Optional header_enc and body_enc is either Charset.QP for
-    quoted-printable, Charset.BASE64 for base64 encoding, Charset.SHORTEST for
-    the shortest of qp or base64 encoding, or None for no encoding.  SHORTEST
-    is only valid for header_enc.  It describes how message headers and
-    message bodies in the input charset are to be encoded.  Default is no
-    encoding.
-
-    Optional output_charset is the character set that the output should be
-    in.  Conversions will proceed from input charset, to Unicode, to the
-    output charset when the method Charset.convert() is called.  The default
-    is to output in the same character set as the input.
-
-    Both input_charset and output_charset must have Unicode codec entries in
-    the module's charset-to-codec mapping; use add_codec(charset, codecname)
-    to add codecs the module does not know about.  See the codecs module's
-    documentation for more information.
-    """
-    if body_enc == SHORTEST:
-        raise ValueError('SHORTEST not allowed for body_enc')
-    CHARSETS[charset] = (header_enc, body_enc, output_charset)
-
-
-def add_alias(alias, canonical):
-    """Add a character set alias.
-
-    alias is the alias name, e.g. latin-1
-    canonical is the character set's canonical name, e.g. iso-8859-1
-    """
-    ALIASES[alias] = canonical
-
-
-def add_codec(charset, codecname):
-    """Add a codec that map characters in the given charset to/from Unicode.
-
-    charset is the canonical name of a character set.  codecname is the name
-    of a Python codec, as appropriate for the second argument to the unicode()
-    built-in, or to the encode() method of a Unicode string.
-    """
-    CODEC_MAP[charset] = codecname
-
-
-
-class Charset:
-    """Map character sets to their email properties.
-
-    This class provides information about the requirements imposed on email
-    for a specific character set.  It also provides convenience routines for
-    converting between character sets, given the availability of the
-    applicable codecs.  Given a character set, it will do its best to provide
-    information on how to use that character set in an email in an
-    RFC-compliant way.
-
-    Certain character sets must be encoded with quoted-printable or base64
-    when used in email headers or bodies.  Certain character sets must be
-    converted outright, and are not allowed in email.  Instances of this
-    module expose the following information about a character set:
-
-    input_charset: The initial character set specified.  Common aliases
-                   are converted to their `official' email names (e.g. latin_1
-                   is converted to iso-8859-1).  Defaults to 7-bit us-ascii.
-
-    header_encoding: If the character set must be encoded before it can be
-                     used in an email header, this attribute will be set to
-                     Charset.QP (for quoted-printable), Charset.BASE64 (for
-                     base64 encoding), or Charset.SHORTEST for the shortest of
-                     QP or BASE64 encoding.  Otherwise, it will be None.
-
-    body_encoding: Same as header_encoding, but describes the encoding for the
-                   mail message's body, which indeed may be different than the
-                   header encoding.  Charset.SHORTEST is not allowed for
-                   body_encoding.
-
-    output_charset: Some character sets must be converted before the can be
-                    used in email headers or bodies.  If the input_charset is
-                    one of them, this attribute will contain the name of the
-                    charset output will be converted to.  Otherwise, it will
-                    be None.
-
-    input_codec: The name of the Python codec used to convert the
-                 input_charset to Unicode.  If no conversion codec is
-                 necessary, this attribute will be None.
-
-    output_codec: The name of the Python codec used to convert Unicode
-                  to the output_charset.  If no conversion codec is necessary,
-                  this attribute will have the same value as the input_codec.
-    """
-    def __init__(self, input_charset=DEFAULT_CHARSET):
-        # RFC 2046, $4.1.2 says charsets are not case sensitive.  We coerce to
-        # unicode because its .lower() is locale insensitive.
-        input_charset = unicode(input_charset, 'ascii').lower()
-        # Set the input charset after filtering through the aliases
-        self.input_charset = ALIASES.get(input_charset, input_charset)
-        # We can try to guess which encoding and conversion to use by the
-        # charset_map dictionary.  Try that first, but let the user override
-        # it.
-        henc, benc, conv = CHARSETS.get(self.input_charset,
-                                        (SHORTEST, BASE64, None))
-        if not conv:
-            conv = self.input_charset
-        # Set the attributes, allowing the arguments to override the default.
-        self.header_encoding = henc
-        self.body_encoding = benc
-        self.output_charset = ALIASES.get(conv, conv)
-        # Now set the codecs.  If one isn't defined for input_charset,
-        # guess and try a Unicode codec with the same name as input_codec.
-        self.input_codec = CODEC_MAP.get(self.input_charset,
-                                         self.input_charset)
-        self.output_codec = CODEC_MAP.get(self.output_charset,
-                                          self.output_charset)
-
-    def __str__(self):
-        return self.input_charset.lower()
-
-    __repr__ = __str__
-
-    def __eq__(self, other):
-        return str(self) == str(other).lower()
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def get_body_encoding(self):
-        """Return the content-transfer-encoding used for body encoding.
-
-        This is either the string `quoted-printable' or `base64' depending on
-        the encoding used, or it is a function in which case you should call
-        the function with a single argument, the Message object being
-        encoded.  The function should then set the Content-Transfer-Encoding
-        header itself to whatever is appropriate.
-
-        Returns "quoted-printable" if self.body_encoding is QP.
-        Returns "base64" if self.body_encoding is BASE64.
-        Returns "7bit" otherwise.
-        """
-        assert self.body_encoding <> SHORTEST
-        if self.body_encoding == QP:
-            return 'quoted-printable'
-        elif self.body_encoding == BASE64:
-            return 'base64'
-        else:
-            return encode_7or8bit
-
-    def convert(self, s):
-        """Convert a string from the input_codec to the output_codec."""
-        if self.input_codec <> self.output_codec:
-            return unicode(s, self.input_codec).encode(self.output_codec)
-        else:
-            return s
-
-    def to_splittable(self, s):
-        """Convert a possibly multibyte string to a safely splittable format.
-
-        Uses the input_codec to try and convert the string to Unicode, so it
-        can be safely split on character boundaries (even for multibyte
-        characters).
-
-        Returns the string as-is if it isn't known how to convert it to
-        Unicode with the input_charset.
-
-        Characters that could not be converted to Unicode will be replaced
-        with the Unicode replacement character U+FFFD.
-        """
-        if isinstance(s, unicode) or self.input_codec is None:
-            return s
-        try:
-            return unicode(s, self.input_codec, 'replace')
-        except LookupError:
-            # Input codec not installed on system, so return the original
-            # string unchanged.
-            return s
-
-    def from_splittable(self, ustr, to_output=True):
-        """Convert a splittable string back into an encoded string.
-
-        Uses the proper codec to try and convert the string from Unicode back
-        into an encoded format.  Return the string as-is if it is not Unicode,
-        or if it could not be converted from Unicode.
-
-        Characters that could not be converted from Unicode will be replaced
-        with an appropriate character (usually '?').
-
-        If to_output is True (the default), uses output_codec to convert to an
-        encoded format.  If to_output is False, uses input_codec.
-        """
-        if to_output:
-            codec = self.output_codec
-        else:
-            codec = self.input_codec
-        if not isinstance(ustr, unicode) or codec is None:
-            return ustr
-        try:
-            return ustr.encode(codec, 'replace')
-        except LookupError:
-            # Output codec not installed
-            return ustr
-
-    def get_output_charset(self):
-        """Return the output character set.
-
-        This is self.output_charset if that is not None, otherwise it is
-        self.input_charset.
-        """
-        return self.output_charset or self.input_charset
-
-    def encoded_header_len(self, s):
-        """Return the length of the encoded header string."""
-        cset = self.get_output_charset()
-        # The len(s) of a 7bit encoding is len(s)
-        if self.header_encoding == BASE64:
-            return email.base64MIME.base64_len(s) + len(cset) + MISC_LEN
-        elif self.header_encoding == QP:
-            return email.quopriMIME.header_quopri_len(s) + len(cset) + MISC_LEN
-        elif self.header_encoding == SHORTEST:
-            lenb64 = email.base64MIME.base64_len(s)
-            lenqp = email.quopriMIME.header_quopri_len(s)
-            return min(lenb64, lenqp) + len(cset) + MISC_LEN
-        else:
-            return len(s)
-
-    def header_encode(self, s, convert=False):
-        """Header-encode a string, optionally converting it to output_charset.
-
-        If convert is True, the string will be converted from the input
-        charset to the output charset automatically.  This is not useful for
-        multibyte character sets, which have line length issues (multibyte
-        characters must be split on a character, not a byte boundary); use the
-        high-level Header class to deal with these issues.  convert defaults
-        to False.
-
-        The type of encoding (base64 or quoted-printable) will be based on
-        self.header_encoding.
-        """
-        cset = self.get_output_charset()
-        if convert:
-            s = self.convert(s)
-        # 7bit/8bit encodings return the string unchanged (modulo conversions)
-        if self.header_encoding == BASE64:
-            return email.base64MIME.header_encode(s, cset)
-        elif self.header_encoding == QP:
-            return email.quopriMIME.header_encode(s, cset, maxlinelen=None)
-        elif self.header_encoding == SHORTEST:
-            lenb64 = email.base64MIME.base64_len(s)
-            lenqp = email.quopriMIME.header_quopri_len(s)
-            if lenb64 < lenqp:
-                return email.base64MIME.header_encode(s, cset)
-            else:
-                return email.quopriMIME.header_encode(s, cset, maxlinelen=None)
-        else:
-            return s
-
-    def body_encode(self, s, convert=True):
-        """Body-encode a string and convert it to output_charset.
-
-        If convert is True (the default), the string will be converted from
-        the input charset to output charset automatically.  Unlike
-        header_encode(), there are no issues with byte boundaries and
-        multibyte charsets in email bodies, so this is usually pretty safe.
-
-        The type of encoding (base64 or quoted-printable) will be based on
-        self.body_encoding.
-        """
-        if convert:
-            s = self.convert(s)
-        # 7bit/8bit encodings return the string unchanged (module conversions)
-        if self.body_encoding is BASE64:
-            return email.base64MIME.body_encode(s)
-        elif self.body_encoding is QP:
-            return email.quopriMIME.body_encode(s)
-        else:
-            return s
diff --git a/Lib/email/Encoders.py b/Lib/email/Encoders.py
deleted file mode 100644
index baac2a3..0000000
--- a/Lib/email/Encoders.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Encodings and related functions."""
-
-import base64
-from quopri import encodestring as _encodestring
-
-def _qencode(s):
-    enc = _encodestring(s, quotetabs=True)
-    # Must encode spaces, which quopri.encodestring() doesn't do
-    return enc.replace(' ', '=20')
-
-
-def _bencode(s):
-    # We can't quite use base64.encodestring() since it tacks on a "courtesy
-    # newline".  Blech!
-    if not s:
-        return s
-    hasnewline = (s[-1] == '\n')
-    value = base64.encodestring(s)
-    if not hasnewline and value[-1] == '\n':
-        return value[:-1]
-    return value
-
-
-
-def encode_base64(msg):
-    """Encode the message's payload in Base64.
-
-    Also, add an appropriate Content-Transfer-Encoding header.
-    """
-    orig = msg.get_payload()
-    encdata = _bencode(orig)
-    msg.set_payload(encdata)
-    msg['Content-Transfer-Encoding'] = 'base64'
-
-
-
-def encode_quopri(msg):
-    """Encode the message's payload in quoted-printable.
-
-    Also, add an appropriate Content-Transfer-Encoding header.
-    """
-    orig = msg.get_payload()
-    encdata = _qencode(orig)
-    msg.set_payload(encdata)
-    msg['Content-Transfer-Encoding'] = 'quoted-printable'
-
-
-
-def encode_7or8bit(msg):
-    """Set the Content-Transfer-Encoding header to 7bit or 8bit."""
-    orig = msg.get_payload()
-    if orig is None:
-        # There's no payload.  For backwards compatibility we use 7bit
-        msg['Content-Transfer-Encoding'] = '7bit'
-        return
-    # We play a trick to make this go fast.  If encoding to ASCII succeeds, we
-    # know the data must be 7bit, otherwise treat it as 8bit.
-    try:
-        orig.encode('ascii')
-    except UnicodeError:
-        # iso-2022-* is non-ASCII but still 7-bit
-        charset = msg.get_charset()
-        output_cset = charset and charset.output_charset
-        if output_cset and output_cset.lower().startswith('iso-2202-'):
-            msg['Content-Transfer-Encoding'] = '7bit'
-        else:
-            msg['Content-Transfer-Encoding'] = '8bit'
-    else:
-        msg['Content-Transfer-Encoding'] = '7bit'
-
-
-
-def encode_noop(msg):
-    """Do nothing."""
diff --git a/Lib/email/Errors.py b/Lib/email/Errors.py
deleted file mode 100644
index e13a2c7..0000000
--- a/Lib/email/Errors.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""email package exception classes."""
-
-
-
-class MessageError(Exception):
-    """Base class for errors in the email package."""
-
-
-class MessageParseError(MessageError):
-    """Base class for message parsing errors."""
-
-
-class HeaderParseError(MessageParseError):
-    """Error while parsing headers."""
-
-
-class BoundaryError(MessageParseError):
-    """Couldn't find terminating boundary."""
-
-
-class MultipartConversionError(MessageError, TypeError):
-    """Conversion to a multipart is prohibited."""
-
-
-
-# These are parsing defects which the parser was able to work around.
-class MessageDefect:
-    """Base class for a message defect."""
-
-    def __init__(self, line=None):
-        self.line = line
-
-class NoBoundaryInMultipartDefect(MessageDefect):
-    """A message claimed to be a multipart but had no boundary parameter."""
-
-class StartBoundaryNotFoundDefect(MessageDefect):
-    """The claimed start boundary was never found."""
-
-class FirstHeaderLineIsContinuationDefect(MessageDefect):
-    """A message had a continuation line as its first header line."""
-
-class MisplacedEnvelopeHeaderDefect(MessageDefect):
-    """A 'Unix-from' header was found in the middle of a header block."""
-
-class MalformedHeaderDefect(MessageDefect):
-    """Found a header that was missing a colon, or was otherwise malformed."""
-
-class MultipartInvariantViolationDefect(MessageDefect):
-    """A message claimed to be a multipart but no subparts were found."""
diff --git a/Lib/email/FeedParser.py b/Lib/email/FeedParser.py
deleted file mode 100644
index a2130e2..0000000
--- a/Lib/email/FeedParser.py
+++ /dev/null
@@ -1,477 +0,0 @@
-# Copyright (C) 2004-2006 Python Software Foundation
-# Authors: Baxter, Wouters and Warsaw
-# Contact: email-sig@python.org
-
-"""FeedParser - An email feed parser.
-
-The feed parser implements an interface for incrementally parsing an email
-message, line by line.  This has advantages for certain applications, such as
-those reading email messages off a socket.
-
-FeedParser.feed() is the primary interface for pushing new data into the
-parser.  It returns when there's nothing more it can do with the available
-data.  When you have no more data to push into the parser, call .close().
-This completes the parsing and returns the root message object.
-
-The other advantage of this parser is that it will never throw a parsing
-exception.  Instead, when it finds something unexpected, it adds a 'defect' to
-the current message.  Defects are just instances that live on the message
-object's .defects attribute.
-"""
-
-import re
-from email import Errors
-from email import Message
-
-NLCRE = re.compile('\r\n|\r|\n')
-NLCRE_bol = re.compile('(\r\n|\r|\n)')
-NLCRE_eol = re.compile('(\r\n|\r|\n)$')
-NLCRE_crack = re.compile('(\r\n|\r|\n)')
-# RFC 2822 $3.6.8 Optional fields.  ftext is %d33-57 / %d59-126, Any character
-# except controls, SP, and ":".
-headerRE = re.compile(r'^(From |[\041-\071\073-\176]{1,}:|[\t ])')
-EMPTYSTRING = ''
-NL = '\n'
-
-NeedMoreData = object()
-
-
-
-class BufferedSubFile(object):
-    """A file-ish object that can have new data loaded into it.
-
-    You can also push and pop line-matching predicates onto a stack.  When the
-    current predicate matches the current line, a false EOF response
-    (i.e. empty string) is returned instead.  This lets the parser adhere to a
-    simple abstraction -- it parses until EOF closes the current message.
-    """
-    def __init__(self):
-        # The last partial line pushed into this object.
-        self._partial = ''
-        # The list of full, pushed lines, in reverse order
-        self._lines = []
-        # The stack of false-EOF checking predicates.
-        self._eofstack = []
-        # A flag indicating whether the file has been closed or not.
-        self._closed = False
-
-    def push_eof_matcher(self, pred):
-        self._eofstack.append(pred)
-
-    def pop_eof_matcher(self):
-        return self._eofstack.pop()
-
-    def close(self):
-        # Don't forget any trailing partial line.
-        self._lines.append(self._partial)
-        self._partial = ''
-        self._closed = True
-
-    def readline(self):
-        if not self._lines:
-            if self._closed:
-                return ''
-            return NeedMoreData
-        # Pop the line off the stack and see if it matches the current
-        # false-EOF predicate.
-        line = self._lines.pop()
-        # RFC 2046, section 5.1.2 requires us to recognize outer level
-        # boundaries at any level of inner nesting.  Do this, but be sure it's
-        # in the order of most to least nested.
-        for ateof in self._eofstack[::-1]:
-            if ateof(line):
-                # We're at the false EOF.  But push the last line back first.
-                self._lines.append(line)
-                return ''
-        return line
-
-    def unreadline(self, line):
-        # Let the consumer push a line back into the buffer.
-        assert line is not NeedMoreData
-        self._lines.append(line)
-
-    def push(self, data):
-        """Push some new data into this object."""
-        # Handle any previous leftovers
-        data, self._partial = self._partial + data, ''
-        # Crack into lines, but preserve the newlines on the end of each
-        parts = NLCRE_crack.split(data)
-        # The *ahem* interesting behaviour of re.split when supplied grouping
-        # parentheses is that the last element of the resulting list is the
-        # data after the final RE.  In the case of a NL/CR terminated string,
-        # this is the empty string.
-        self._partial = parts.pop()
-        # parts is a list of strings, alternating between the line contents
-        # and the eol character(s).  Gather up a list of lines after
-        # re-attaching the newlines.
-        lines = []
-        for i in range(len(parts) // 2):
-            lines.append(parts[i*2] + parts[i*2+1])
-        self.pushlines(lines)
-
-    def pushlines(self, lines):
-        # Reverse and insert at the front of the lines.
-        self._lines[:0] = lines[::-1]
-
-    def is_closed(self):
-        return self._closed
-
-    def __iter__(self):
-        return self
-
-    def next(self):
-        line = self.readline()
-        if line == '':
-            raise StopIteration
-        return line
-
-
-
-class FeedParser:
-    """A feed-style parser of email."""
-
-    def __init__(self, _factory=Message.Message):
-        """_factory is called with no arguments to create a new message obj"""
-        self._factory = _factory
-        self._input = BufferedSubFile()
-        self._msgstack = []
-        self._parse = self._parsegen().next
-        self._cur = None
-        self._last = None
-        self._headersonly = False
-
-    # Non-public interface for supporting Parser's headersonly flag
-    def _set_headersonly(self):
-        self._headersonly = True
-
-    def feed(self, data):
-        """Push more data into the parser."""
-        self._input.push(data)
-        self._call_parse()
-
-    def _call_parse(self):
-        try:
-            self._parse()
-        except StopIteration:
-            pass
-
-    def close(self):
-        """Parse all remaining data and return the root message object."""
-        self._input.close()
-        self._call_parse()
-        root = self._pop_message()
-        assert not self._msgstack
-        # Look for final set of defects
-        if root.get_content_maintype() == 'multipart' \
-               and not root.is_multipart():
-            root.defects.append(Errors.MultipartInvariantViolationDefect())
-        return root
-
-    def _new_message(self):
-        msg = self._factory()
-        if self._cur and self._cur.get_content_type() == 'multipart/digest':
-            msg.set_default_type('message/rfc822')
-        if self._msgstack:
-            self._msgstack[-1].attach(msg)
-        self._msgstack.append(msg)
-        self._cur = msg
-        self._last = msg
-
-    def _pop_message(self):
-        retval = self._msgstack.pop()
-        if self._msgstack:
-            self._cur = self._msgstack[-1]
-        else:
-            self._cur = None
-        return retval
-
-    def _parsegen(self):
-        # Create a new message and start by parsing headers.
-        self._new_message()
-        headers = []
-        # Collect the headers, searching for a line that doesn't match the RFC
-        # 2822 header or continuation pattern (including an empty line).
-        for line in self._input:
-            if line is NeedMoreData:
-                yield NeedMoreData
-                continue
-            if not headerRE.match(line):
-                # If we saw the RFC defined header/body separator
-                # (i.e. newline), just throw it away. Otherwise the line is
-                # part of the body so push it back.
-                if not NLCRE.match(line):
-                    self._input.unreadline(line)
-                break
-            headers.append(line)
-        # Done with the headers, so parse them and figure out what we're
-        # supposed to see in the body of the message.
-        self._parse_headers(headers)
-        # Headers-only parsing is a backwards compatibility hack, which was
-        # necessary in the older parser, which could throw errors.  All
-        # remaining lines in the input are thrown into the message body.
-        if self._headersonly:
-            lines = []
-            while True:
-                line = self._input.readline()
-                if line is NeedMoreData:
-                    yield NeedMoreData
-                    continue
-                if line == '':
-                    break
-                lines.append(line)
-            self._cur.set_payload(EMPTYSTRING.join(lines))
-            return
-        if self._cur.get_content_type() == 'message/delivery-status':
-            # message/delivery-status contains blocks of headers separated by
-            # a blank line.  We'll represent each header block as a separate
-            # nested message object, but the processing is a bit different
-            # than standard message/* types because there is no body for the
-            # nested messages.  A blank line separates the subparts.
-            while True:
-                self._input.push_eof_matcher(NLCRE.match)
-                for retval in self._parsegen():
-                    if retval is NeedMoreData:
-                        yield NeedMoreData
-                        continue
-                    break
-                msg = self._pop_message()
-                # We need to pop the EOF matcher in order to tell if we're at
-                # the end of the current file, not the end of the last block
-                # of message headers.
-                self._input.pop_eof_matcher()
-                # The input stream must be sitting at the newline or at the
-                # EOF.  We want to see if we're at the end of this subpart, so
-                # first consume the blank line, then test the next line to see
-                # if we're at this subpart's EOF.
-                while True:
-                    line = self._input.readline()
-                    if line is NeedMoreData:
-                        yield NeedMoreData
-                        continue
-                    break
-                while True:
-                    line = self._input.readline()
-                    if line is NeedMoreData:
-                        yield NeedMoreData
-                        continue
-                    break
-                if line == '':
-                    break
-                # Not at EOF so this is a line we're going to need.
-                self._input.unreadline(line)
-            return
-        if self._cur.get_content_maintype() == 'message':
-            # The message claims to be a message/* type, then what follows is
-            # another RFC 2822 message.
-            for retval in self._parsegen():
-                if retval is NeedMoreData:
-                    yield NeedMoreData
-                    continue
-                break
-            self._pop_message()
-            return
-        if self._cur.get_content_maintype() == 'multipart':
-            boundary = self._cur.get_boundary()
-            if boundary is None:
-                # The message /claims/ to be a multipart but it has not
-                # defined a boundary.  That's a problem which we'll handle by
-                # reading everything until the EOF and marking the message as
-                # defective.
-                self._cur.defects.append(Errors.NoBoundaryInMultipartDefect())
-                lines = []
-                for line in self._input:
-                    if line is NeedMoreData:
-                        yield NeedMoreData
-                        continue
-                    lines.append(line)
-                self._cur.set_payload(EMPTYSTRING.join(lines))
-                return
-            # Create a line match predicate which matches the inter-part
-            # boundary as well as the end-of-multipart boundary.  Don't push
-            # this onto the input stream until we've scanned past the
-            # preamble.
-            separator = '--' + boundary
-            boundaryre = re.compile(
-                '(?P<sep>' + re.escape(separator) +
-                r')(?P<end>--)?(?P<ws>[ \t]*)(?P<linesep>\r\n|\r|\n)?$')
-            capturing_preamble = True
-            preamble = []
-            linesep = False
-            while True:
-                line = self._input.readline()
-                if line is NeedMoreData:
-                    yield NeedMoreData
-                    continue
-                if line == '':
-                    break
-                mo = boundaryre.match(line)
-                if mo:
-                    # If we're looking at the end boundary, we're done with
-                    # this multipart.  If there was a newline at the end of
-                    # the closing boundary, then we need to initialize the
-                    # epilogue with the empty string (see below).
-                    if mo.group('end'):
-                        linesep = mo.group('linesep')
-                        break
-                    # We saw an inter-part boundary.  Were we in the preamble?
-                    if capturing_preamble:
-                        if preamble:
-                            # According to RFC 2046, the last newline belongs
-                            # to the boundary.
-                            lastline = preamble[-1]
-                            eolmo = NLCRE_eol.search(lastline)
-                            if eolmo:
-                                preamble[-1] = lastline[:-len(eolmo.group(0))]
-                            self._cur.preamble = EMPTYSTRING.join(preamble)
-                        capturing_preamble = False
-                        self._input.unreadline(line)
-                        continue
-                    # We saw a boundary separating two parts.  Consume any
-                    # multiple boundary lines that may be following.  Our
-                    # interpretation of RFC 2046 BNF grammar does not produce
-                    # body parts within such double boundaries.
-                    while True:
-                        line = self._input.readline()
-                        if line is NeedMoreData:
-                            yield NeedMoreData
-                            continue
-                        mo = boundaryre.match(line)
-                        if not mo:
-                            self._input.unreadline(line)
-                            break
-                    # Recurse to parse this subpart; the input stream points
-                    # at the subpart's first line.
-                    self._input.push_eof_matcher(boundaryre.match)
-                    for retval in self._parsegen():
-                        if retval is NeedMoreData:
-                            yield NeedMoreData
-                            continue
-                        break
-                    # Because of RFC 2046, the newline preceding the boundary
-                    # separator actually belongs to the boundary, not the
-                    # previous subpart's payload (or epilogue if the previous
-                    # part is a multipart).
-                    if self._last.get_content_maintype() == 'multipart':
-                        epilogue = self._last.epilogue
-                        if epilogue == '':
-                            self._last.epilogue = None
-                        elif epilogue is not None:
-                            mo = NLCRE_eol.search(epilogue)
-                            if mo:
-                                end = len(mo.group(0))
-                                self._last.epilogue = epilogue[:-end]
-                    else:
-                        payload = self._last.get_payload()
-                        if isinstance(payload, basestring):
-                            mo = NLCRE_eol.search(payload)
-                            if mo:
-                                payload = payload[:-len(mo.group(0))]
-                                self._last.set_payload(payload)
-                    self._input.pop_eof_matcher()
-                    self._pop_message()
-                    # Set the multipart up for newline cleansing, which will
-                    # happen if we're in a nested multipart.
-                    self._last = self._cur
-                else:
-                    # I think we must be in the preamble
-                    assert capturing_preamble
-                    preamble.append(line)
-            # We've seen either the EOF or the end boundary.  If we're still
-            # capturing the preamble, we never saw the start boundary.  Note
-            # that as a defect and store the captured text as the payload.
-            # Everything from here to the EOF is epilogue.
-            if capturing_preamble:
-                self._cur.defects.append(Errors.StartBoundaryNotFoundDefect())
-                self._cur.set_payload(EMPTYSTRING.join(preamble))
-                epilogue = []
-                for line in self._input:
-                    if line is NeedMoreData:
-                        yield NeedMoreData
-                        continue
-                self._cur.epilogue = EMPTYSTRING.join(epilogue)
-                return
-            # If the end boundary ended in a newline, we'll need to make sure
-            # the epilogue isn't None
-            if linesep:
-                epilogue = ['']
-            else:
-                epilogue = []
-            for line in self._input:
-                if line is NeedMoreData:
-                    yield NeedMoreData
-                    continue
-                epilogue.append(line)
-            # Any CRLF at the front of the epilogue is not technically part of
-            # the epilogue.  Also, watch out for an empty string epilogue,
-            # which means a single newline.
-            if epilogue:
-                firstline = epilogue[0]
-                bolmo = NLCRE_bol.match(firstline)
-                if bolmo:
-                    epilogue[0] = firstline[len(bolmo.group(0)):]
-            self._cur.epilogue = EMPTYSTRING.join(epilogue)
-            return
-        # Otherwise, it's some non-multipart type, so the entire rest of the
-        # file contents becomes the payload.
-        lines = []
-        for line in self._input:
-            if line is NeedMoreData:
-                yield NeedMoreData
-                continue
-            lines.append(line)
-        self._cur.set_payload(EMPTYSTRING.join(lines))
-
-    def _parse_headers(self, lines):
-        # Passed a list of lines that make up the headers for the current msg
-        lastheader = ''
-        lastvalue = []
-        for lineno, line in enumerate(lines):
-            # Check for continuation
-            if line[0] in ' \t':
-                if not lastheader:
-                    # The first line of the headers was a continuation.  This
-                    # is illegal, so let's note the defect, store the illegal
-                    # line, and ignore it for purposes of headers.
-                    defect = Errors.FirstHeaderLineIsContinuationDefect(line)
-                    self._cur.defects.append(defect)
-                    continue
-                lastvalue.append(line)
-                continue
-            if lastheader:
-                # XXX reconsider the joining of folded lines
-                lhdr = EMPTYSTRING.join(lastvalue)[:-1].rstrip('\r\n')
-                self._cur[lastheader] = lhdr
-                lastheader, lastvalue = '', []
-            # Check for envelope header, i.e. unix-from
-            if line.startswith('From '):
-                if lineno == 0:
-                    # Strip off the trailing newline
-                    mo = NLCRE_eol.search(line)
-                    if mo:
-                        line = line[:-len(mo.group(0))]
-                    self._cur.set_unixfrom(line)
-                    continue
-                elif lineno == len(lines) - 1:
-                    # Something looking like a unix-from at the end - it's
-                    # probably the first line of the body, so push back the
-                    # line and stop.
-                    self._input.unreadline(line)
-                    return
-                else:
-                    # Weirdly placed unix-from line.  Note this as a defect
-                    # and ignore it.
-                    defect = Errors.MisplacedEnvelopeHeaderDefect(line)
-                    self._cur.defects.append(defect)
-                    continue
-            # Split the line on the colon separating field name from value.
-            i = line.find(':')
-            if i < 0:
-                defect = Errors.MalformedHeaderDefect(line)
-                self._cur.defects.append(defect)
-                continue
-            lastheader = line[:i]
-            lastvalue = [line[i+1:].lstrip()]
-        # Done with all the lines, so handle the last header.
-        if lastheader:
-            # XXX reconsider the joining of folded lines
-            self._cur[lastheader] = EMPTYSTRING.join(lastvalue).rstrip('\r\n')
diff --git a/Lib/email/Generator.py b/Lib/email/Generator.py
deleted file mode 100644
index 7969916..0000000
--- a/Lib/email/Generator.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# Copyright (C) 2001-2006 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Classes to generate plain text from a message object tree."""
-
-import re
-import sys
-import time
-import random
-import warnings
-from cStringIO import StringIO
-
-from email.Header import Header
-
-UNDERSCORE = '_'
-NL = '\n'
-
-fcre = re.compile(r'^From ', re.MULTILINE)
-
-def _is8bitstring(s):
-    if isinstance(s, str):
-        try:
-            unicode(s, 'us-ascii')
-        except UnicodeError:
-            return True
-    return False
-
-
-
-class Generator:
-    """Generates output from a Message object tree.
-
-    This basic generator writes the message to the given file object as plain
-    text.
-    """
-    #
-    # Public interface
-    #
-
-    def __init__(self, outfp, mangle_from_=True, maxheaderlen=78):
-        """Create the generator for message flattening.
-
-        outfp is the output file-like object for writing the message to.  It
-        must have a write() method.
-
-        Optional mangle_from_ is a flag that, when True (the default), escapes
-        From_ lines in the body of the message by putting a `>' in front of
-        them.
-
-        Optional maxheaderlen specifies the longest length for a non-continued
-        header.  When a header line is longer (in characters, with tabs
-        expanded to 8 spaces) than maxheaderlen, the header will split as
-        defined in the Header class.  Set maxheaderlen to zero to disable
-        header wrapping.  The default is 78, as recommended (but not required)
-        by RFC 2822.
-        """
-        self._fp = outfp
-        self._mangle_from_ = mangle_from_
-        self._maxheaderlen = maxheaderlen
-
-    def write(self, s):
-        # Just delegate to the file object
-        self._fp.write(s)
-
-    def flatten(self, msg, unixfrom=False):
-        """Print the message object tree rooted at msg to the output file
-        specified when the Generator instance was created.
-
-        unixfrom is a flag that forces the printing of a Unix From_ delimiter
-        before the first object in the message tree.  If the original message
-        has no From_ delimiter, a `standard' one is crafted.  By default, this
-        is False to inhibit the printing of any From_ delimiter.
-
-        Note that for subobjects, no From_ line is printed.
-        """
-        if unixfrom:
-            ufrom = msg.get_unixfrom()
-            if not ufrom:
-                ufrom = 'From nobody ' + time.ctime(time.time())
-            print >> self._fp, ufrom
-        self._write(msg)
-
-    # For backwards compatibility, but this is slower
-    def __call__(self, msg, unixfrom=False):
-        warnings.warn('__call__() deprecated; use flatten()',
-                      DeprecationWarning, 2)
-        self.flatten(msg, unixfrom)
-
-    def clone(self, fp):
-        """Clone this generator with the exact same options."""
-        return self.__class__(fp, self._mangle_from_, self._maxheaderlen)
-
-    #
-    # Protected interface - undocumented ;/
-    #
-
-    def _write(self, msg):
-        # We can't write the headers yet because of the following scenario:
-        # say a multipart message includes the boundary string somewhere in
-        # its body.  We'd have to calculate the new boundary /before/ we write
-        # the headers so that we can write the correct Content-Type:
-        # parameter.
-        #
-        # The way we do this, so as to make the _handle_*() methods simpler,
-        # is to cache any subpart writes into a StringIO.  The we write the
-        # headers and the StringIO contents.  That way, subpart handlers can
-        # Do The Right Thing, and can still modify the Content-Type: header if
-        # necessary.
-        oldfp = self._fp
-        try:
-            self._fp = sfp = StringIO()
-            self._dispatch(msg)
-        finally:
-            self._fp = oldfp
-        # Write the headers.  First we see if the message object wants to
-        # handle that itself.  If not, we'll do it generically.
-        meth = getattr(msg, '_write_headers', None)
-        if meth is None:
-            self._write_headers(msg)
-        else:
-            meth(self)
-        self._fp.write(sfp.getvalue())
-
-    def _dispatch(self, msg):
-        # Get the Content-Type: for the message, then try to dispatch to
-        # self._handle_<maintype>_<subtype>().  If there's no handler for the
-        # full MIME type, then dispatch to self._handle_<maintype>().  If
-        # that's missing too, then dispatch to self._writeBody().
-        main = msg.get_content_maintype()
-        sub = msg.get_content_subtype()
-        specific = UNDERSCORE.join((main, sub)).replace('-', '_')
-        meth = getattr(self, '_handle_' + specific, None)
-        if meth is None:
-            generic = main.replace('-', '_')
-            meth = getattr(self, '_handle_' + generic, None)
-            if meth is None:
-                meth = self._writeBody
-        meth(msg)
-
-    #
-    # Default handlers
-    #
-
-    def _write_headers(self, msg):
-        for h, v in msg.items():
-            print >> self._fp, '%s:' % h,
-            if self._maxheaderlen == 0:
-                # Explicit no-wrapping
-                print >> self._fp, v
-            elif isinstance(v, Header):
-                # Header instances know what to do
-                print >> self._fp, v.encode()
-            elif _is8bitstring(v):
-                # If we have raw 8bit data in a byte string, we have no idea
-                # what the encoding is.  There is no safe way to split this
-                # string.  If it's ascii-subset, then we could do a normal
-                # ascii split, but if it's multibyte then we could break the
-                # string.  There's no way to know so the least harm seems to
-                # be to not split the string and risk it being too long.
-                print >> self._fp, v
-            else:
-                # Header's got lots of smarts, so use it.
-                print >> self._fp, Header(
-                    v, maxlinelen=self._maxheaderlen,
-                    header_name=h, continuation_ws='\t').encode()
-        # A blank line always separates headers from body
-        print >> self._fp
-
-    #
-    # Handlers for writing types and subtypes
-    #
-
-    def _handle_text(self, msg):
-        payload = msg.get_payload()
-        if payload is None:
-            return
-        if not isinstance(payload, basestring):
-            raise TypeError('string payload expected: %s' % type(payload))
-        if self._mangle_from_:
-            payload = fcre.sub('>From ', payload)
-        self._fp.write(payload)
-
-    # Default body handler
-    _writeBody = _handle_text
-
-    def _handle_multipart(self, msg):
-        # The trick here is to write out each part separately, merge them all
-        # together, and then make sure that the boundary we've chosen isn't
-        # present in the payload.
-        msgtexts = []
-        subparts = msg.get_payload()
-        if subparts is None:
-            subparts = []
-        elif isinstance(subparts, basestring):
-            # e.g. a non-strict parse of a message with no starting boundary.
-            self._fp.write(subparts)
-            return
-        elif not isinstance(subparts, list):
-            # Scalar payload
-            subparts = [subparts]
-        for part in subparts:
-            s = StringIO()
-            g = self.clone(s)
-            g.flatten(part, unixfrom=False)
-            msgtexts.append(s.getvalue())
-        # Now make sure the boundary we've selected doesn't appear in any of
-        # the message texts.
-        alltext = NL.join(msgtexts)
-        # BAW: What about boundaries that are wrapped in double-quotes?
-        boundary = msg.get_boundary(failobj=_make_boundary(alltext))
-        # If we had to calculate a new boundary because the body text
-        # contained that string, set the new boundary.  We don't do it
-        # unconditionally because, while set_boundary() preserves order, it
-        # doesn't preserve newlines/continuations in headers.  This is no big
-        # deal in practice, but turns out to be inconvenient for the unittest
-        # suite.
-        if msg.get_boundary() <> boundary:
-            msg.set_boundary(boundary)
-        # If there's a preamble, write it out, with a trailing CRLF
-        if msg.preamble is not None:
-            print >> self._fp, msg.preamble
-        # dash-boundary transport-padding CRLF
-        print >> self._fp, '--' + boundary
-        # body-part
-        if msgtexts:
-            self._fp.write(msgtexts.pop(0))
-        # *encapsulation
-        # --> delimiter transport-padding
-        # --> CRLF body-part
-        for body_part in msgtexts:
-            # delimiter transport-padding CRLF
-            print >> self._fp, '\n--' + boundary
-            # body-part
-            self._fp.write(body_part)
-        # close-delimiter transport-padding
-        self._fp.write('\n--' + boundary + '--')
-        if msg.epilogue is not None:
-            print >> self._fp
-            self._fp.write(msg.epilogue)
-
-    def _handle_message_delivery_status(self, msg):
-        # We can't just write the headers directly to self's file object
-        # because this will leave an extra newline between the last header
-        # block and the boundary.  Sigh.
-        blocks = []
-        for part in msg.get_payload():
-            s = StringIO()
-            g = self.clone(s)
-            g.flatten(part, unixfrom=False)
-            text = s.getvalue()
-            lines = text.split('\n')
-            # Strip off the unnecessary trailing empty line
-            if lines and lines[-1] == '':
-                blocks.append(NL.join(lines[:-1]))
-            else:
-                blocks.append(text)
-        # Now join all the blocks with an empty line.  This has the lovely
-        # effect of separating each block with an empty line, but not adding
-        # an extra one after the last one.
-        self._fp.write(NL.join(blocks))
-
-    def _handle_message(self, msg):
-        s = StringIO()
-        g = self.clone(s)
-        # The payload of a message/rfc822 part should be a multipart sequence
-        # of length 1.  The zeroth element of the list should be the Message
-        # object for the subpart.  Extract that object, stringify it, and
-        # write it out.
-        g.flatten(msg.get_payload(0), unixfrom=False)
-        self._fp.write(s.getvalue())
-
-
-
-_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
-
-class DecodedGenerator(Generator):
-    """Generator a text representation of a message.
-
-    Like the Generator base class, except that non-text parts are substituted
-    with a format string representing the part.
-    """
-    def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None):
-        """Like Generator.__init__() except that an additional optional
-        argument is allowed.
-
-        Walks through all subparts of a message.  If the subpart is of main
-        type `text', then it prints the decoded payload of the subpart.
-
-        Otherwise, fmt is a format string that is used instead of the message
-        payload.  fmt is expanded with the following keywords (in
-        %(keyword)s format):
-
-        type       : Full MIME type of the non-text part
-        maintype   : Main MIME type of the non-text part
-        subtype    : Sub-MIME type of the non-text part
-        filename   : Filename of the non-text part
-        description: Description associated with the non-text part
-        encoding   : Content transfer encoding of the non-text part
-
-        The default value for fmt is None, meaning
-
-        [Non-text (%(type)s) part of message omitted, filename %(filename)s]
-        """
-        Generator.__init__(self, outfp, mangle_from_, maxheaderlen)
-        if fmt is None:
-            self._fmt = _FMT
-        else:
-            self._fmt = fmt
-
-    def _dispatch(self, msg):
-        for part in msg.walk():
-            maintype = part.get_content_maintype()
-            if maintype == 'text':
-                print >> self, part.get_payload(decode=True)
-            elif maintype == 'multipart':
-                # Just skip this
-                pass
-            else:
-                print >> self, self._fmt % {
-                    'type'       : part.get_content_type(),
-                    'maintype'   : part.get_content_maintype(),
-                    'subtype'    : part.get_content_subtype(),
-                    'filename'   : part.get_filename('[no filename]'),
-                    'description': part.get('Content-Description',
-                                            '[no description]'),
-                    'encoding'   : part.get('Content-Transfer-Encoding',
-                                            '[no encoding]'),
-                    }
-
-
-
-# Helper
-_width = len(repr(sys.maxint-1))
-_fmt = '%%0%dd' % _width
-
-def _make_boundary(text=None):
-    # Craft a random boundary.  If text is given, ensure that the chosen
-    # boundary doesn't appear in the text.
-    token = random.randrange(sys.maxint)
-    boundary = ('=' * 15) + (_fmt % token) + '=='
-    if text is None:
-        return boundary
-    b = boundary
-    counter = 0
-    while True:
-        cre = re.compile('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
-        if not cre.search(text):
-            break
-        b = boundary + '.' + str(counter)
-        counter += 1
-    return b
diff --git a/Lib/email/Header.py b/Lib/email/Header.py
deleted file mode 100644
index 5e24afe..0000000
--- a/Lib/email/Header.py
+++ /dev/null
@@ -1,495 +0,0 @@
-# Copyright (C) 2002-2004 Python Software Foundation
-# Author: Ben Gertzfield, Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Header encoding and decoding functionality."""
-
-import re
-import binascii
-
-import email.quopriMIME
-import email.base64MIME
-from email.Errors import HeaderParseError
-from email.Charset import Charset
-
-NL = '\n'
-SPACE = ' '
-USPACE = u' '
-SPACE8 = ' ' * 8
-UEMPTYSTRING = u''
-
-MAXLINELEN = 76
-
-USASCII = Charset('us-ascii')
-UTF8 = Charset('utf-8')
-
-# Match encoded-word strings in the form =?charset?q?Hello_World?=
-ecre = re.compile(r'''
-  =\?                   # literal =?
-  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
-  \?                    # literal ?
-  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
-  \?                    # literal ?
-  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string
-  \?=                   # literal ?=
-  ''', re.VERBOSE | re.IGNORECASE)
-
-# Field name regexp, including trailing colon, but not separating whitespace,
-# according to RFC 2822.  Character range is from tilde to exclamation mark.
-# For use with .match()
-fcre = re.compile(r'[\041-\176]+:$')
-
-
-
-# Helpers
-_max_append = email.quopriMIME._max_append
-
-
-
-def decode_header(header):
-    """Decode a message header value without converting charset.
-
-    Returns a list of (decoded_string, charset) pairs containing each of the
-    decoded parts of the header.  Charset is None for non-encoded parts of the
-    header, otherwise a lower-case string containing the name of the character
-    set specified in the encoded string.
-
-    An email.Errors.HeaderParseError may be raised when certain decoding error
-    occurs (e.g. a base64 decoding exception).
-    """
-    # If no encoding, just return the header
-    header = str(header)
-    if not ecre.search(header):
-        return [(header, None)]
-    decoded = []
-    dec = ''
-    for line in header.splitlines():
-        # This line might not have an encoding in it
-        if not ecre.search(line):
-            decoded.append((line, None))
-            continue
-        parts = ecre.split(line)
-        while parts:
-            unenc = parts.pop(0).strip()
-            if unenc:
-                # Should we continue a long line?
-                if decoded and decoded[-1][1] is None:
-                    decoded[-1] = (decoded[-1][0] + SPACE + unenc, None)
-                else:
-                    decoded.append((unenc, None))
-            if parts:
-                charset, encoding = [s.lower() for s in parts[0:2]]
-                encoded = parts[2]
-                dec = None
-                if encoding == 'q':
-                    dec = email.quopriMIME.header_decode(encoded)
-                elif encoding == 'b':
-                    try:
-                        dec = email.base64MIME.decode(encoded)
-                    except binascii.Error:
-                        # Turn this into a higher level exception.  BAW: Right
-                        # now we throw the lower level exception away but
-                        # when/if we get exception chaining, we'll preserve it.
-                        raise HeaderParseError
-                if dec is None:
-                    dec = encoded
-
-                if decoded and decoded[-1][1] == charset:
-                    decoded[-1] = (decoded[-1][0] + dec, decoded[-1][1])
-                else:
-                    decoded.append((dec, charset))
-            del parts[0:3]
-    return decoded
-
-
-
-def make_header(decoded_seq, maxlinelen=None, header_name=None,
-                continuation_ws=' '):
-    """Create a Header from a sequence of pairs as returned by decode_header()
-
-    decode_header() takes a header value string and returns a sequence of
-    pairs of the format (decoded_string, charset) where charset is the string
-    name of the character set.
-
-    This function takes one of those sequence of pairs and returns a Header
-    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
-    the Header constructor.
-    """
-    h = Header(maxlinelen=maxlinelen, header_name=header_name,
-               continuation_ws=continuation_ws)
-    for s, charset in decoded_seq:
-        # None means us-ascii but we can simply pass it on to h.append()
-        if charset is not None and not isinstance(charset, Charset):
-            charset = Charset(charset)
-        h.append(s, charset)
-    return h
-
-
-
-class Header:
-    def __init__(self, s=None, charset=None,
-                 maxlinelen=None, header_name=None,
-                 continuation_ws=' ', errors='strict'):
-        """Create a MIME-compliant header that can contain many character sets.
-
-        Optional s is the initial header value.  If None, the initial header
-        value is not set.  You can later append to the header with .append()
-        method calls.  s may be a byte string or a Unicode string, but see the
-        .append() documentation for semantics.
-
-        Optional charset serves two purposes: it has the same meaning as the
-        charset argument to the .append() method.  It also sets the default
-        character set for all subsequent .append() calls that omit the charset
-        argument.  If charset is not provided in the constructor, the us-ascii
-        charset is used both as s's initial charset and as the default for
-        subsequent .append() calls.
-
-        The maximum line length can be specified explicit via maxlinelen.  For
-        splitting the first line to a shorter value (to account for the field
-        header which isn't included in s, e.g. `Subject') pass in the name of
-        the field in header_name.  The default maxlinelen is 76.
-
-        continuation_ws must be RFC 2822 compliant folding whitespace (usually
-        either a space or a hard tab) which will be prepended to continuation
-        lines.
-
-        errors is passed through to the .append() call.
-        """
-        if charset is None:
-            charset = USASCII
-        if not isinstance(charset, Charset):
-            charset = Charset(charset)
-        self._charset = charset
-        self._continuation_ws = continuation_ws
-        cws_expanded_len = len(continuation_ws.replace('\t', SPACE8))
-        # BAW: I believe `chunks' and `maxlinelen' should be non-public.
-        self._chunks = []
-        if s is not None:
-            self.append(s, charset, errors)
-        if maxlinelen is None:
-            maxlinelen = MAXLINELEN
-        if header_name is None:
-            # We don't know anything about the field header so the first line
-            # is the same length as subsequent lines.
-            self._firstlinelen = maxlinelen
-        else:
-            # The first line should be shorter to take into account the field
-            # header.  Also subtract off 2 extra for the colon and space.
-            self._firstlinelen = maxlinelen - len(header_name) - 2
-        # Second and subsequent lines should subtract off the length in
-        # columns of the continuation whitespace prefix.
-        self._maxlinelen = maxlinelen - cws_expanded_len
-
-    def __str__(self):
-        """A synonym for self.encode()."""
-        return self.encode()
-
-    def __unicode__(self):
-        """Helper for the built-in unicode function."""
-        uchunks = []
-        lastcs = None
-        for s, charset in self._chunks:
-            # We must preserve spaces between encoded and non-encoded word
-            # boundaries, which means for us we need to add a space when we go
-            # from a charset to None/us-ascii, or from None/us-ascii to a
-            # charset.  Only do this for the second and subsequent chunks.
-            nextcs = charset
-            if uchunks:
-                if lastcs not in (None, 'us-ascii'):
-                    if nextcs in (None, 'us-ascii'):
-                        uchunks.append(USPACE)
-                        nextcs = None
-                elif nextcs not in (None, 'us-ascii'):
-                    uchunks.append(USPACE)
-            lastcs = nextcs
-            uchunks.append(unicode(s, str(charset)))
-        return UEMPTYSTRING.join(uchunks)
-
-    # Rich comparison operators for equality only.  BAW: does it make sense to
-    # have or explicitly disable <, <=, >, >= operators?
-    def __eq__(self, other):
-        # other may be a Header or a string.  Both are fine so coerce
-        # ourselves to a string, swap the args and do another comparison.
-        return other == self.encode()
-
-    def __ne__(self, other):
-        return not self == other
-
-    def append(self, s, charset=None, errors='strict'):
-        """Append a string to the MIME header.
-
-        Optional charset, if given, should be a Charset instance or the name
-        of a character set (which will be converted to a Charset instance).  A
-        value of None (the default) means that the charset given in the
-        constructor is used.
-
-        s may be a byte string or a Unicode string.  If it is a byte string
-        (i.e. isinstance(s, str) is true), then charset is the encoding of
-        that byte string, and a UnicodeError will be raised if the string
-        cannot be decoded with that charset.  If s is a Unicode string, then
-        charset is a hint specifying the character set of the characters in
-        the string.  In this case, when producing an RFC 2822 compliant header
-        using RFC 2047 rules, the Unicode string will be encoded using the
-        following charsets in order: us-ascii, the charset hint, utf-8.  The
-        first character set not to provoke a UnicodeError is used.
-
-        Optional `errors' is passed as the third argument to any unicode() or
-        ustr.encode() call.
-        """
-        if charset is None:
-            charset = self._charset
-        elif not isinstance(charset, Charset):
-            charset = Charset(charset)
-        # If the charset is our faux 8bit charset, leave the string unchanged
-        if charset <> '8bit':
-            # We need to test that the string can be converted to unicode and
-            # back to a byte string, given the input and output codecs of the
-            # charset.
-            if isinstance(s, str):
-                # Possibly raise UnicodeError if the byte string can't be
-                # converted to a unicode with the input codec of the charset.
-                incodec = charset.input_codec or 'us-ascii'
-                ustr = unicode(s, incodec, errors)
-                # Now make sure that the unicode could be converted back to a
-                # byte string with the output codec, which may be different
-                # than the iput coded.  Still, use the original byte string.
-                outcodec = charset.output_codec or 'us-ascii'
-                ustr.encode(outcodec, errors)
-            elif isinstance(s, unicode):
-                # Now we have to be sure the unicode string can be converted
-                # to a byte string with a reasonable output codec.  We want to
-                # use the byte string in the chunk.
-                for charset in USASCII, charset, UTF8:
-                    try:
-                        outcodec = charset.output_codec or 'us-ascii'
-                        s = s.encode(outcodec, errors)
-                        break
-                    except UnicodeError:
-                        pass
-                else:
-                    assert False, 'utf-8 conversion failed'
-        self._chunks.append((s, charset))
-
-    def _split(self, s, charset, maxlinelen, splitchars):
-        # Split up a header safely for use with encode_chunks.
-        splittable = charset.to_splittable(s)
-        encoded = charset.from_splittable(splittable, True)
-        elen = charset.encoded_header_len(encoded)
-        # If the line's encoded length first, just return it
-        if elen <= maxlinelen:
-            return [(encoded, charset)]
-        # If we have undetermined raw 8bit characters sitting in a byte
-        # string, we really don't know what the right thing to do is.  We
-        # can't really split it because it might be multibyte data which we
-        # could break if we split it between pairs.  The least harm seems to
-        # be to not split the header at all, but that means they could go out
-        # longer than maxlinelen.
-        if charset == '8bit':
-            return [(s, charset)]
-        # BAW: I'm not sure what the right test here is.  What we're trying to
-        # do is be faithful to RFC 2822's recommendation that ($2.2.3):
-        #
-        # "Note: Though structured field bodies are defined in such a way that
-        #  folding can take place between many of the lexical tokens (and even
-        #  within some of the lexical tokens), folding SHOULD be limited to
-        #  placing the CRLF at higher-level syntactic breaks."
-        #
-        # For now, I can only imagine doing this when the charset is us-ascii,
-        # although it's possible that other charsets may also benefit from the
-        # higher-level syntactic breaks.
-        elif charset == 'us-ascii':
-            return self._split_ascii(s, charset, maxlinelen, splitchars)
-        # BAW: should we use encoded?
-        elif elen == len(s):
-            # We can split on _maxlinelen boundaries because we know that the
-            # encoding won't change the size of the string
-            splitpnt = maxlinelen
-            first = charset.from_splittable(splittable[:splitpnt], False)
-            last = charset.from_splittable(splittable[splitpnt:], False)
-        else:
-            # Binary search for split point
-            first, last = _binsplit(splittable, charset, maxlinelen)
-        # first is of the proper length so just wrap it in the appropriate
-        # chrome.  last must be recursively split.
-        fsplittable = charset.to_splittable(first)
-        fencoded = charset.from_splittable(fsplittable, True)
-        chunk = [(fencoded, charset)]
-        return chunk + self._split(last, charset, self._maxlinelen, splitchars)
-
-    def _split_ascii(self, s, charset, firstlen, splitchars):
-        chunks = _split_ascii(s, firstlen, self._maxlinelen,
-                              self._continuation_ws, splitchars)
-        return zip(chunks, [charset]*len(chunks))
-
-    def _encode_chunks(self, newchunks, maxlinelen):
-        # MIME-encode a header with many different charsets and/or encodings.
-        #
-        # Given a list of pairs (string, charset), return a MIME-encoded
-        # string suitable for use in a header field.  Each pair may have
-        # different charsets and/or encodings, and the resulting header will
-        # accurately reflect each setting.
-        #
-        # Each encoding can be email.Utils.QP (quoted-printable, for
-        # ASCII-like character sets like iso-8859-1), email.Utils.BASE64
-        # (Base64, for non-ASCII like character sets like KOI8-R and
-        # iso-2022-jp), or None (no encoding).
-        #
-        # Each pair will be represented on a separate line; the resulting
-        # string will be in the format:
-        #
-        # =?charset1?q?Mar=EDa_Gonz=E1lez_Alonso?=\n
-        #  =?charset2?b?SvxyZ2VuIEL2aW5n?="
-        chunks = []
-        for header, charset in newchunks:
-            if not header:
-                continue
-            if charset is None or charset.header_encoding is None:
-                s = header
-            else:
-                s = charset.header_encode(header)
-            # Don't add more folding whitespace than necessary
-            if chunks and chunks[-1].endswith(' '):
-                extra = ''
-            else:
-                extra = ' '
-            _max_append(chunks, s, maxlinelen, extra)
-        joiner = NL + self._continuation_ws
-        return joiner.join(chunks)
-
-    def encode(self, splitchars=';, '):
-        """Encode a message header into an RFC-compliant format.
-
-        There are many issues involved in converting a given string for use in
-        an email header.  Only certain character sets are readable in most
-        email clients, and as header strings can only contain a subset of
-        7-bit ASCII, care must be taken to properly convert and encode (with
-        Base64 or quoted-printable) header strings.  In addition, there is a
-        75-character length limit on any given encoded header field, so
-        line-wrapping must be performed, even with double-byte character sets.
-
-        This method will do its best to convert the string to the correct
-        character set used in email, and encode and line wrap it safely with
-        the appropriate scheme for that character set.
-
-        If the given charset is not known or an error occurs during
-        conversion, this function will return the header untouched.
-
-        Optional splitchars is a string containing characters to split long
-        ASCII lines on, in rough support of RFC 2822's `highest level
-        syntactic breaks'.  This doesn't affect RFC 2047 encoded lines.
-        """
-        newchunks = []
-        maxlinelen = self._firstlinelen
-        lastlen = 0
-        for s, charset in self._chunks:
-            # The first bit of the next chunk should be just long enough to
-            # fill the next line.  Don't forget the space separating the
-            # encoded words.
-            targetlen = maxlinelen - lastlen - 1
-            if targetlen < charset.encoded_header_len(''):
-                # Stick it on the next line
-                targetlen = maxlinelen
-            newchunks += self._split(s, charset, targetlen, splitchars)
-            lastchunk, lastcharset = newchunks[-1]
-            lastlen = lastcharset.encoded_header_len(lastchunk)
-        return self._encode_chunks(newchunks, maxlinelen)
-
-
-
-def _split_ascii(s, firstlen, restlen, continuation_ws, splitchars):
-    lines = []
-    maxlen = firstlen
-    for line in s.splitlines():
-        # Ignore any leading whitespace (i.e. continuation whitespace) already
-        # on the line, since we'll be adding our own.
-        line = line.lstrip()
-        if len(line) < maxlen:
-            lines.append(line)
-            maxlen = restlen
-            continue
-        # Attempt to split the line at the highest-level syntactic break
-        # possible.  Note that we don't have a lot of smarts about field
-        # syntax; we just try to break on semi-colons, then commas, then
-        # whitespace.
-        for ch in splitchars:
-            if ch in line:
-                break
-        else:
-            # There's nothing useful to split the line on, not even spaces, so
-            # just append this line unchanged
-            lines.append(line)
-            maxlen = restlen
-            continue
-        # Now split the line on the character plus trailing whitespace
-        cre = re.compile(r'%s\s*' % ch)
-        if ch in ';,':
-            eol = ch
-        else:
-            eol = ''
-        joiner = eol + ' '
-        joinlen = len(joiner)
-        wslen = len(continuation_ws.replace('\t', SPACE8))
-        this = []
-        linelen = 0
-        for part in cre.split(line):
-            curlen = linelen + max(0, len(this)-1) * joinlen
-            partlen = len(part)
-            onfirstline = not lines
-            # We don't want to split after the field name, if we're on the
-            # first line and the field name is present in the header string.
-            if ch == ' ' and onfirstline and \
-                   len(this) == 1 and fcre.match(this[0]):
-                this.append(part)
-                linelen += partlen
-            elif curlen + partlen > maxlen:
-                if this:
-                    lines.append(joiner.join(this) + eol)
-                # If this part is longer than maxlen and we aren't already
-                # splitting on whitespace, try to recursively split this line
-                # on whitespace.
-                if partlen > maxlen and ch <> ' ':
-                    subl = _split_ascii(part, maxlen, restlen,
-                                        continuation_ws, ' ')
-                    lines.extend(subl[:-1])
-                    this = [subl[-1]]
-                else:
-                    this = [part]
-                linelen = wslen + len(this[-1])
-                maxlen = restlen
-            else:
-                this.append(part)
-                linelen += partlen
-        # Put any left over parts on a line by themselves
-        if this:
-            lines.append(joiner.join(this))
-    return lines
-
-
-
-def _binsplit(splittable, charset, maxlinelen):
-    i = 0
-    j = len(splittable)
-    while i < j:
-        # Invariants:
-        # 1. splittable[:k] fits for all k <= i (note that we *assume*,
-        #    at the start, that splittable[:0] fits).
-        # 2. splittable[:k] does not fit for any k > j (at the start,
-        #    this means we shouldn't look at any k > len(splittable)).
-        # 3. We don't know about splittable[:k] for k in i+1..j.
-        # 4. We want to set i to the largest k that fits, with i <= k <= j.
-        #
-        m = (i+j+1) >> 1  # ceiling((i+j)/2); i < m <= j
-        chunk = charset.from_splittable(splittable[:m], True)
-        chunklen = charset.encoded_header_len(chunk)
-        if chunklen <= maxlinelen:
-            # m is acceptable, so is a new lower bound.
-            i = m
-        else:
-            # m is not acceptable, so final i must be < m.
-            j = m - 1
-    # i == j.  Invariant #1 implies that splittable[:i] fits, and
-    # invariant #2 implies that splittable[:i+1] does not fit, so i
-    # is what we're looking for.
-    first = charset.from_splittable(splittable[:i], False)
-    last  = charset.from_splittable(splittable[i:], False)
-    return first, last
diff --git a/Lib/email/Iterators.py b/Lib/email/Iterators.py
deleted file mode 100644
index 74a93c7..0000000
--- a/Lib/email/Iterators.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Various types of useful iterators and generators."""
-
-import sys
-from cStringIO import StringIO
-
-
-
-# This function will become a method of the Message class
-def walk(self):
-    """Walk over the message tree, yielding each subpart.
-
-    The walk is performed in depth-first order.  This method is a
-    generator.
-    """
-    yield self
-    if self.is_multipart():
-        for subpart in self.get_payload():
-            for subsubpart in subpart.walk():
-                yield subsubpart
-
-
-
-# These two functions are imported into the Iterators.py interface module.
-# The Python 2.2 version uses generators for efficiency.
-def body_line_iterator(msg, decode=False):
-    """Iterate over the parts, returning string payloads line-by-line.
-
-    Optional decode (default False) is passed through to .get_payload().
-    """
-    for subpart in msg.walk():
-        payload = subpart.get_payload(decode=decode)
-        if isinstance(payload, basestring):
-            for line in StringIO(payload):
-                yield line
-
-
-def typed_subpart_iterator(msg, maintype='text', subtype=None):
-    """Iterate over the subparts with a given MIME type.
-
-    Use `maintype' as the main MIME type to match against; this defaults to
-    "text".  Optional `subtype' is the MIME subtype to match against; if
-    omitted, only the main type is matched.
-    """
-    for subpart in msg.walk():
-        if subpart.get_content_maintype() == maintype:
-            if subtype is None or subpart.get_content_subtype() == subtype:
-                yield subpart
-
-
-
-def _structure(msg, fp=None, level=0, include_default=False):
-    """A handy debugging aid"""
-    if fp is None:
-        fp = sys.stdout
-    tab = ' ' * (level * 4)
-    print >> fp, tab + msg.get_content_type(),
-    if include_default:
-        print >> fp, '[%s]' % msg.get_default_type()
-    else:
-        print >> fp
-    if msg.is_multipart():
-        for subpart in msg.get_payload():
-            _structure(subpart, fp, level+1, include_default)
diff --git a/Lib/email/MIMEAudio.py b/Lib/email/MIMEAudio.py
deleted file mode 100644
index 266ec4c..0000000
--- a/Lib/email/MIMEAudio.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Anthony Baxter
-# Contact: email-sig@python.org
-
-"""Class representing audio/* type MIME documents."""
-
-import sndhdr
-from cStringIO import StringIO
-
-from email import Errors
-from email import Encoders
-from email.MIMENonMultipart import MIMENonMultipart
-
-
-
-_sndhdr_MIMEmap = {'au'  : 'basic',
-                   'wav' :'x-wav',
-                   'aiff':'x-aiff',
-                   'aifc':'x-aiff',
-                   }
-
-# There are others in sndhdr that don't have MIME types. :(
-# Additional ones to be added to sndhdr? midi, mp3, realaudio, wma??
-def _whatsnd(data):
-    """Try to identify a sound file type.
-
-    sndhdr.what() has a pretty cruddy interface, unfortunately.  This is why
-    we re-do it here.  It would be easier to reverse engineer the Unix 'file'
-    command and use the standard 'magic' file, as shipped with a modern Unix.
-    """
-    hdr = data[:512]
-    fakefile = StringIO(hdr)
-    for testfn in sndhdr.tests:
-        res = testfn(hdr, fakefile)
-        if res is not None:
-            return _sndhdr_MIMEmap.get(res[0])
-    return None
-
-
-
-class MIMEAudio(MIMENonMultipart):
-    """Class for generating audio/* MIME documents."""
-
-    def __init__(self, _audiodata, _subtype=None,
-                 _encoder=Encoders.encode_base64, **_params):
-        """Create an audio/* type MIME document.
-
-        _audiodata is a string containing the raw audio data.  If this data
-        can be decoded by the standard Python `sndhdr' module, then the
-        subtype will be automatically included in the Content-Type header.
-        Otherwise, you can specify  the specific audio subtype via the
-        _subtype parameter.  If _subtype is not given, and no subtype can be
-        guessed, a TypeError is raised.
-
-        _encoder is a function which will perform the actual encoding for
-        transport of the image data.  It takes one argument, which is this
-        Image instance.  It should use get_payload() and set_payload() to
-        change the payload to the encoded form.  It should also add any
-        Content-Transfer-Encoding or other headers to the message as
-        necessary.  The default encoding is Base64.
-
-        Any additional keyword arguments are passed to the base class
-        constructor, which turns them into parameters on the Content-Type
-        header.
-        """
-        if _subtype is None:
-            _subtype = _whatsnd(_audiodata)
-        if _subtype is None:
-            raise TypeError('Could not find audio MIME subtype')
-        MIMENonMultipart.__init__(self, 'audio', _subtype, **_params)
-        self.set_payload(_audiodata)
-        _encoder(self)
diff --git a/Lib/email/MIMEBase.py b/Lib/email/MIMEBase.py
deleted file mode 100644
index 88691f8..0000000
--- a/Lib/email/MIMEBase.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Base class for MIME specializations."""
-
-from email import Message
-
-
-
-class MIMEBase(Message.Message):
-    """Base class for MIME specializations."""
-
-    def __init__(self, _maintype, _subtype, **_params):
-        """This constructor adds a Content-Type: and a MIME-Version: header.
-
-        The Content-Type: header is taken from the _maintype and _subtype
-        arguments.  Additional parameters for this header are taken from the
-        keyword arguments.
-        """
-        Message.Message.__init__(self)
-        ctype = '%s/%s' % (_maintype, _subtype)
-        self.add_header('Content-Type', ctype, **_params)
-        self['MIME-Version'] = '1.0'
diff --git a/Lib/email/MIMEImage.py b/Lib/email/MIMEImage.py
deleted file mode 100644
index a658067..0000000
--- a/Lib/email/MIMEImage.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Class representing image/* type MIME documents."""
-
-import imghdr
-
-from email import Errors
-from email import Encoders
-from email.MIMENonMultipart import MIMENonMultipart
-
-
-
-class MIMEImage(MIMENonMultipart):
-    """Class for generating image/* type MIME documents."""
-
-    def __init__(self, _imagedata, _subtype=None,
-                 _encoder=Encoders.encode_base64, **_params):
-        """Create an image/* type MIME document.
-
-        _imagedata is a string containing the raw image data.  If this data
-        can be decoded by the standard Python `imghdr' module, then the
-        subtype will be automatically included in the Content-Type header.
-        Otherwise, you can specify the specific image subtype via the _subtype
-        parameter.
-
-        _encoder is a function which will perform the actual encoding for
-        transport of the image data.  It takes one argument, which is this
-        Image instance.  It should use get_payload() and set_payload() to
-        change the payload to the encoded form.  It should also add any
-        Content-Transfer-Encoding or other headers to the message as
-        necessary.  The default encoding is Base64.
-
-        Any additional keyword arguments are passed to the base class
-        constructor, which turns them into parameters on the Content-Type
-        header.
-        """
-        if _subtype is None:
-            _subtype = imghdr.what(None, _imagedata)
-        if _subtype is None:
-            raise TypeError('Could not guess image MIME subtype')
-        MIMENonMultipart.__init__(self, 'image', _subtype, **_params)
-        self.set_payload(_imagedata)
-        _encoder(self)
diff --git a/Lib/email/MIMEMessage.py b/Lib/email/MIMEMessage.py
deleted file mode 100644
index 3021934..0000000
--- a/Lib/email/MIMEMessage.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Class representing message/* MIME documents."""
-
-from email import Message
-from email.MIMENonMultipart import MIMENonMultipart
-
-
-
-class MIMEMessage(MIMENonMultipart):
-    """Class representing message/* MIME documents."""
-
-    def __init__(self, _msg, _subtype='rfc822'):
-        """Create a message/* type MIME document.
-
-        _msg is a message object and must be an instance of Message, or a
-        derived class of Message, otherwise a TypeError is raised.
-
-        Optional _subtype defines the subtype of the contained message.  The
-        default is "rfc822" (this is defined by the MIME standard, even though
-        the term "rfc822" is technically outdated by RFC 2822).
-        """
-        MIMENonMultipart.__init__(self, 'message', _subtype)
-        if not isinstance(_msg, Message.Message):
-            raise TypeError('Argument is not an instance of Message')
-        # It's convenient to use this base class method.  We need to do it
-        # this way or we'll get an exception
-        Message.Message.attach(self, _msg)
-        # And be sure our default type is set correctly
-        self.set_default_type('message/rfc822')
diff --git a/Lib/email/MIMEMultipart.py b/Lib/email/MIMEMultipart.py
deleted file mode 100644
index 9072a64..0000000
--- a/Lib/email/MIMEMultipart.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2002-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Base class for MIME multipart/* type messages."""
-
-from email import MIMEBase
-
-
-
-class MIMEMultipart(MIMEBase.MIMEBase):
-    """Base class for MIME multipart/* type messages."""
-
-    def __init__(self, _subtype='mixed', boundary=None, _subparts=None,
-                 **_params):
-        """Creates a multipart/* type message.
-
-        By default, creates a multipart/mixed message, with proper
-        Content-Type and MIME-Version headers.
-
-        _subtype is the subtype of the multipart content type, defaulting to
-        `mixed'.
-
-        boundary is the multipart boundary string.  By default it is
-        calculated as needed.
-
-        _subparts is a sequence of initial subparts for the payload.  It
-        must be an iterable object, such as a list.  You can always
-        attach new subparts to the message by using the attach() method.
-
-        Additional parameters for the Content-Type header are taken from the
-        keyword arguments (or passed into the _params argument).
-        """
-        MIMEBase.MIMEBase.__init__(self, 'multipart', _subtype, **_params)
-        if _subparts:
-            for p in _subparts:
-                self.attach(p)
-        if boundary:
-            self.set_boundary(boundary)
diff --git a/Lib/email/MIMENonMultipart.py b/Lib/email/MIMENonMultipart.py
deleted file mode 100644
index 4195d2a..0000000
--- a/Lib/email/MIMENonMultipart.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2002-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Base class for MIME type messages that are not multipart."""
-
-from email import Errors
-from email import MIMEBase
-
-
-
-class MIMENonMultipart(MIMEBase.MIMEBase):
-    """Base class for MIME multipart/* type messages."""
-
-    __pychecker__ = 'unusednames=payload'
-
-    def attach(self, payload):
-        # The public API prohibits attaching multiple subparts to MIMEBase
-        # derived subtypes since none of them are, by definition, of content
-        # type multipart/*
-        raise Errors.MultipartConversionError(
-            'Cannot attach additional subparts to non-multipart/*')
-
-    del __pychecker__
diff --git a/Lib/email/MIMEText.py b/Lib/email/MIMEText.py
deleted file mode 100644
index 5ef1876..0000000
--- a/Lib/email/MIMEText.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Class representing text/* type MIME documents."""
-
-from email.MIMENonMultipart import MIMENonMultipart
-from email.Encoders import encode_7or8bit
-
-
-
-class MIMEText(MIMENonMultipart):
-    """Class for generating text/* type MIME documents."""
-
-    def __init__(self, _text, _subtype='plain', _charset='us-ascii'):
-        """Create a text/* type MIME document.
-
-        _text is the string for this message object.
-
-        _subtype is the MIME sub content type, defaulting to "plain".
-
-        _charset is the character set parameter added to the Content-Type
-        header.  This defaults to "us-ascii".  Note that as a side-effect, the
-        Content-Transfer-Encoding header will also be set.
-        """
-        MIMENonMultipart.__init__(self, 'text', _subtype,
-                                  **{'charset': _charset})
-        self.set_payload(_text, _charset)
diff --git a/Lib/email/Message.py b/Lib/email/Message.py
deleted file mode 100644
index bc76416..0000000
--- a/Lib/email/Message.py
+++ /dev/null
@@ -1,814 +0,0 @@
-# Copyright (C) 2001-2006 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Basic message object for the email package object model."""
-
-import re
-import uu
-import binascii
-import warnings
-from cStringIO import StringIO
-
-# Intrapackage imports
-from email import Utils
-from email import Errors
-from email import Charset
-
-SEMISPACE = '; '
-
-# Regular expression used to split header parameters.  BAW: this may be too
-# simple.  It isn't strictly RFC 2045 (section 5.1) compliant, but it catches
-# most headers found in the wild.  We may eventually need a full fledged
-# parser eventually.
-paramre = re.compile(r'\s*;\s*')
-# Regular expression that matches `special' characters in parameters, the
-# existance of which force quoting of the parameter value.
-tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
-
-
-
-# Helper functions
-def _formatparam(param, value=None, quote=True):
-    """Convenience function to format and return a key=value pair.
-
-    This will quote the value if needed or if quote is true.
-    """
-    if value is not None and len(value) > 0:
-        # A tuple is used for RFC 2231 encoded parameter values where items
-        # are (charset, language, value).  charset is a string, not a Charset
-        # instance.
-        if isinstance(value, tuple):
-            # Encode as per RFC 2231
-            param += '*'
-            value = Utils.encode_rfc2231(value[2], value[0], value[1])
-        # BAW: Please check this.  I think that if quote is set it should
-        # force quoting even if not necessary.
-        if quote or tspecials.search(value):
-            return '%s="%s"' % (param, Utils.quote(value))
-        else:
-            return '%s=%s' % (param, value)
-    else:
-        return param
-
-def _parseparam(s):
-    plist = []
-    while s[:1] == ';':
-        s = s[1:]
-        end = s.find(';')
-        while end > 0 and s.count('"', 0, end) % 2:
-            end = s.find(';', end + 1)
-        if end < 0:
-            end = len(s)
-        f = s[:end]
-        if '=' in f:
-            i = f.index('=')
-            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
-        plist.append(f.strip())
-        s = s[end:]
-    return plist
-
-
-def _unquotevalue(value):
-    # This is different than Utils.collapse_rfc2231_value() because it doesn't
-    # try to convert the value to a unicode.  Message.get_param() and
-    # Message.get_params() are both currently defined to return the tuple in
-    # the face of RFC 2231 parameters.
-    if isinstance(value, tuple):
-        return value[0], value[1], Utils.unquote(value[2])
-    else:
-        return Utils.unquote(value)
-
-
-
-class Message:
-    """Basic message object.
-
-    A message object is defined as something that has a bunch of RFC 2822
-    headers and a payload.  It may optionally have an envelope header
-    (a.k.a. Unix-From or From_ header).  If the message is a container (i.e. a
-    multipart or a message/rfc822), then the payload is a list of Message
-    objects, otherwise it is a string.
-
-    Message objects implement part of the `mapping' interface, which assumes
-    there is exactly one occurrance of the header per message.  Some headers
-    do in fact appear multiple times (e.g. Received) and for those headers,
-    you must use the explicit API to set or get all the headers.  Not all of
-    the mapping methods are implemented.
-    """
-    def __init__(self):
-        self._headers = []
-        self._unixfrom = None
-        self._payload = None
-        self._charset = None
-        # Defaults for multipart messages
-        self.preamble = self.epilogue = None
-        self.defects = []
-        # Default content type
-        self._default_type = 'text/plain'
-
-    def __str__(self):
-        """Return the entire formatted message as a string.
-        This includes the headers, body, and envelope header.
-        """
-        return self.as_string(unixfrom=True)
-
-    def as_string(self, unixfrom=False):
-        """Return the entire formatted message as a string.
-        Optional `unixfrom' when True, means include the Unix From_ envelope
-        header.
-
-        This is a convenience method and may not generate the message exactly
-        as you intend because by default it mangles lines that begin with
-        "From ".  For more flexibility, use the flatten() method of a
-        Generator instance.
-        """
-        from email.Generator import Generator
-        fp = StringIO()
-        g = Generator(fp)
-        g.flatten(self, unixfrom=unixfrom)
-        return fp.getvalue()
-
-    def is_multipart(self):
-        """Return True if the message consists of multiple parts."""
-        return isinstance(self._payload, list)
-
-    #
-    # Unix From_ line
-    #
-    def set_unixfrom(self, unixfrom):
-        self._unixfrom = unixfrom
-
-    def get_unixfrom(self):
-        return self._unixfrom
-
-    #
-    # Payload manipulation.
-    #
-    def attach(self, payload):
-        """Add the given payload to the current payload.
-
-        The current payload will always be a list of objects after this method
-        is called.  If you want to set the payload to a scalar object, use
-        set_payload() instead.
-        """
-        if self._payload is None:
-            self._payload = [payload]
-        else:
-            self._payload.append(payload)
-
-    def get_payload(self, i=None, decode=False):
-        """Return a reference to the payload.
-
-        The payload will either be a list object or a string.  If you mutate
-        the list object, you modify the message's payload in place.  Optional
-        i returns that index into the payload.
-
-        Optional decode is a flag indicating whether the payload should be
-        decoded or not, according to the Content-Transfer-Encoding header
-        (default is False).
-
-        When True and the message is not a multipart, the payload will be
-        decoded if this header's value is `quoted-printable' or `base64'.  If
-        some other encoding is used, or the header is missing, or if the
-        payload has bogus data (i.e. bogus base64 or uuencoded data), the
-        payload is returned as-is.
-
-        If the message is a multipart and the decode flag is True, then None
-        is returned.
-        """
-        if i is None:
-            payload = self._payload
-        elif not isinstance(self._payload, list):
-            raise TypeError('Expected list, got %s' % type(self._payload))
-        else:
-            payload = self._payload[i]
-        if decode:
-            if self.is_multipart():
-                return None
-            cte = self.get('content-transfer-encoding', '').lower()
-            if cte == 'quoted-printable':
-                return Utils._qdecode(payload)
-            elif cte == 'base64':
-                try:
-                    return Utils._bdecode(payload)
-                except binascii.Error:
-                    # Incorrect padding
-                    return payload
-            elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
-                sfp = StringIO()
-                try:
-                    uu.decode(StringIO(payload+'\n'), sfp)
-                    payload = sfp.getvalue()
-                except uu.Error:
-                    # Some decoding problem
-                    return payload
-        # Everything else, including encodings with 8bit or 7bit are returned
-        # unchanged.
-        return payload
-
-    def set_payload(self, payload, charset=None):
-        """Set the payload to the given value.
-
-        Optional charset sets the message's default character set.  See
-        set_charset() for details.
-        """
-        self._payload = payload
-        if charset is not None:
-            self.set_charset(charset)
-
-    def set_charset(self, charset):
-        """Set the charset of the payload to a given character set.
-
-        charset can be a Charset instance, a string naming a character set, or
-        None.  If it is a string it will be converted to a Charset instance.
-        If charset is None, the charset parameter will be removed from the
-        Content-Type field.  Anything else will generate a TypeError.
-
-        The message will be assumed to be of type text/* encoded with
-        charset.input_charset.  It will be converted to charset.output_charset
-        and encoded properly, if needed, when generating the plain text
-        representation of the message.  MIME headers (MIME-Version,
-        Content-Type, Content-Transfer-Encoding) will be added as needed.
-
-        """
-        if charset is None:
-            self.del_param('charset')
-            self._charset = None
-            return
-        if isinstance(charset, str):
-            charset = Charset.Charset(charset)
-        if not isinstance(charset, Charset.Charset):
-            raise TypeError(charset)
-        # BAW: should we accept strings that can serve as arguments to the
-        # Charset constructor?
-        self._charset = charset
-        if not self.has_key('MIME-Version'):
-            self.add_header('MIME-Version', '1.0')
-        if not self.has_key('Content-Type'):
-            self.add_header('Content-Type', 'text/plain',
-                            charset=charset.get_output_charset())
-        else:
-            self.set_param('charset', charset.get_output_charset())
-        if str(charset) <> charset.get_output_charset():
-            self._payload = charset.body_encode(self._payload)
-        if not self.has_key('Content-Transfer-Encoding'):
-            cte = charset.get_body_encoding()
-            try:
-                cte(self)
-            except TypeError:
-                self._payload = charset.body_encode(self._payload)
-                self.add_header('Content-Transfer-Encoding', cte)
-
-    def get_charset(self):
-        """Return the Charset instance associated with the message's payload.
-        """
-        return self._charset
-
-    #
-    # MAPPING INTERFACE (partial)
-    #
-    def __len__(self):
-        """Return the total number of headers, including duplicates."""
-        return len(self._headers)
-
-    def __getitem__(self, name):
-        """Get a header value.
-
-        Return None if the header is missing instead of raising an exception.
-
-        Note that if the header appeared multiple times, exactly which
-        occurrance gets returned is undefined.  Use get_all() to get all
-        the values matching a header field name.
-        """
-        return self.get(name)
-
-    def __setitem__(self, name, val):
-        """Set the value of a header.
-
-        Note: this does not overwrite an existing header with the same field
-        name.  Use __delitem__() first to delete any existing headers.
-        """
-        self._headers.append((name, val))
-
-    def __delitem__(self, name):
-        """Delete all occurrences of a header, if present.
-
-        Does not raise an exception if the header is missing.
-        """
-        name = name.lower()
-        newheaders = []
-        for k, v in self._headers:
-            if k.lower() <> name:
-                newheaders.append((k, v))
-        self._headers = newheaders
-
-    def __contains__(self, name):
-        return name.lower() in [k.lower() for k, v in self._headers]
-
-    def has_key(self, name):
-        """Return true if the message contains the header."""
-        missing = object()
-        return self.get(name, missing) is not missing
-
-    def keys(self):
-        """Return a list of all the message's header field names.
-
-        These will be sorted in the order they appeared in the original
-        message, or were added to the message, and may contain duplicates.
-        Any fields deleted and re-inserted are always appended to the header
-        list.
-        """
-        return [k for k, v in self._headers]
-
-    def values(self):
-        """Return a list of all the message's header values.
-
-        These will be sorted in the order they appeared in the original
-        message, or were added to the message, and may contain duplicates.
-        Any fields deleted and re-inserted are always appended to the header
-        list.
-        """
-        return [v for k, v in self._headers]
-
-    def items(self):
-        """Get all the message's header fields and values.
-
-        These will be sorted in the order they appeared in the original
-        message, or were added to the message, and may contain duplicates.
-        Any fields deleted and re-inserted are always appended to the header
-        list.
-        """
-        return self._headers[:]
-
-    def get(self, name, failobj=None):
-        """Get a header value.
-
-        Like __getitem__() but return failobj instead of None when the field
-        is missing.
-        """
-        name = name.lower()
-        for k, v in self._headers:
-            if k.lower() == name:
-                return v
-        return failobj
-
-    #
-    # Additional useful stuff
-    #
-
-    def get_all(self, name, failobj=None):
-        """Return a list of all the values for the named field.
-
-        These will be sorted in the order they appeared in the original
-        message, and may contain duplicates.  Any fields deleted and
-        re-inserted are always appended to the header list.
-
-        If no such fields exist, failobj is returned (defaults to None).
-        """
-        values = []
-        name = name.lower()
-        for k, v in self._headers:
-            if k.lower() == name:
-                values.append(v)
-        if not values:
-            return failobj
-        return values
-
-    def add_header(self, _name, _value, **_params):
-        """Extended header setting.
-
-        name is the header field to add.  keyword arguments can be used to set
-        additional parameters for the header field, with underscores converted
-        to dashes.  Normally the parameter will be added as key="value" unless
-        value is None, in which case only the key will be added.
-
-        Example:
-
-        msg.add_header('content-disposition', 'attachment', filename='bud.gif')
-        """
-        parts = []
-        for k, v in _params.items():
-            if v is None:
-                parts.append(k.replace('_', '-'))
-            else:
-                parts.append(_formatparam(k.replace('_', '-'), v))
-        if _value is not None:
-            parts.insert(0, _value)
-        self._headers.append((_name, SEMISPACE.join(parts)))
-
-    def replace_header(self, _name, _value):
-        """Replace a header.
-
-        Replace the first matching header found in the message, retaining
-        header order and case.  If no matching header was found, a KeyError is
-        raised.
-        """
-        _name = _name.lower()
-        for i, (k, v) in zip(range(len(self._headers)), self._headers):
-            if k.lower() == _name:
-                self._headers[i] = (k, _value)
-                break
-        else:
-            raise KeyError(_name)
-
-    #
-    # Deprecated methods.  These will be removed in email 3.1.
-    #
-
-    def get_type(self, failobj=None):
-        """Returns the message's content type.
-
-        The returned string is coerced to lowercase and returned as a single
-        string of the form `maintype/subtype'.  If there was no Content-Type
-        header in the message, failobj is returned (defaults to None).
-        """
-        warnings.warn('get_type() deprecated; use get_content_type()',
-                      DeprecationWarning, 2)
-        missing = object()
-        value = self.get('content-type', missing)
-        if value is missing:
-            return failobj
-        return paramre.split(value)[0].lower().strip()
-
-    def get_main_type(self, failobj=None):
-        """Return the message's main content type if present."""
-        warnings.warn('get_main_type() deprecated; use get_content_maintype()',
-                      DeprecationWarning, 2)
-        missing = object()
-        ctype = self.get_type(missing)
-        if ctype is missing:
-            return failobj
-        if ctype.count('/') <> 1:
-            return failobj
-        return ctype.split('/')[0]
-
-    def get_subtype(self, failobj=None):
-        """Return the message's content subtype if present."""
-        warnings.warn('get_subtype() deprecated; use get_content_subtype()',
-                      DeprecationWarning, 2)
-        missing = object()
-        ctype = self.get_type(missing)
-        if ctype is missing:
-            return failobj
-        if ctype.count('/') <> 1:
-            return failobj
-        return ctype.split('/')[1]
-
-    #
-    # Use these three methods instead of the three above.
-    #
-
-    def get_content_type(self):
-        """Return the message's content type.
-
-        The returned string is coerced to lower case of the form
-        `maintype/subtype'.  If there was no Content-Type header in the
-        message, the default type as given by get_default_type() will be
-        returned.  Since according to RFC 2045, messages always have a default
-        type this will always return a value.
-
-        RFC 2045 defines a message's default type to be text/plain unless it
-        appears inside a multipart/digest container, in which case it would be
-        message/rfc822.
-        """
-        missing = object()
-        value = self.get('content-type', missing)
-        if value is missing:
-            # This should have no parameters
-            return self.get_default_type()
-        ctype = paramre.split(value)[0].lower().strip()
-        # RFC 2045, section 5.2 says if its invalid, use text/plain
-        if ctype.count('/') <> 1:
-            return 'text/plain'
-        return ctype
-
-    def get_content_maintype(self):
-        """Return the message's main content type.
-
-        This is the `maintype' part of the string returned by
-        get_content_type().
-        """
-        ctype = self.get_content_type()
-        return ctype.split('/')[0]
-
-    def get_content_subtype(self):
-        """Returns the message's sub-content type.
-
-        This is the `subtype' part of the string returned by
-        get_content_type().
-        """
-        ctype = self.get_content_type()
-        return ctype.split('/')[1]
-
-    def get_default_type(self):
-        """Return the `default' content type.
-
-        Most messages have a default content type of text/plain, except for
-        messages that are subparts of multipart/digest containers.  Such
-        subparts have a default content type of message/rfc822.
-        """
-        return self._default_type
-
-    def set_default_type(self, ctype):
-        """Set the `default' content type.
-
-        ctype should be either "text/plain" or "message/rfc822", although this
-        is not enforced.  The default content type is not stored in the
-        Content-Type header.
-        """
-        self._default_type = ctype
-
-    def _get_params_preserve(self, failobj, header):
-        # Like get_params() but preserves the quoting of values.  BAW:
-        # should this be part of the public interface?
-        missing = object()
-        value = self.get(header, missing)
-        if value is missing:
-            return failobj
-        params = []
-        for p in _parseparam(';' + value):
-            try:
-                name, val = p.split('=', 1)
-                name = name.strip()
-                val = val.strip()
-            except ValueError:
-                # Must have been a bare attribute
-                name = p.strip()
-                val = ''
-            params.append((name, val))
-        params = Utils.decode_params(params)
-        return params
-
-    def get_params(self, failobj=None, header='content-type', unquote=True):
-        """Return the message's Content-Type parameters, as a list.
-
-        The elements of the returned list are 2-tuples of key/value pairs, as
-        split on the `=' sign.  The left hand side of the `=' is the key,
-        while the right hand side is the value.  If there is no `=' sign in
-        the parameter the value is the empty string.  The value is as
-        described in the get_param() method.
-
-        Optional failobj is the object to return if there is no Content-Type
-        header.  Optional header is the header to search instead of
-        Content-Type.  If unquote is True, the value is unquoted.
-        """
-        missing = object()
-        params = self._get_params_preserve(missing, header)
-        if params is missing:
-            return failobj
-        if unquote:
-            return [(k, _unquotevalue(v)) for k, v in params]
-        else:
-            return params
-
-    def get_param(self, param, failobj=None, header='content-type',
-                  unquote=True):
-        """Return the parameter value if found in the Content-Type header.
-
-        Optional failobj is the object to return if there is no Content-Type
-        header, or the Content-Type header has no such parameter.  Optional
-        header is the header to search instead of Content-Type.
-
-        Parameter keys are always compared case insensitively.  The return
-        value can either be a string, or a 3-tuple if the parameter was RFC
-        2231 encoded.  When it's a 3-tuple, the elements of the value are of
-        the form (CHARSET, LANGUAGE, VALUE).  Note that both CHARSET and
-        LANGUAGE can be None, in which case you should consider VALUE to be
-        encoded in the us-ascii charset.  You can usually ignore LANGUAGE.
-
-        Your application should be prepared to deal with 3-tuple return
-        values, and can convert the parameter to a Unicode string like so:
-
-            param = msg.get_param('foo')
-            if isinstance(param, tuple):
-                param = unicode(param[2], param[0] or 'us-ascii')
-
-        In any case, the parameter value (either the returned string, or the
-        VALUE item in the 3-tuple) is always unquoted, unless unquote is set
-        to False.
-        """
-        if not self.has_key(header):
-            return failobj
-        for k, v in self._get_params_preserve(failobj, header):
-            if k.lower() == param.lower():
-                if unquote:
-                    return _unquotevalue(v)
-                else:
-                    return v
-        return failobj
-
-    def set_param(self, param, value, header='Content-Type', requote=True,
-                  charset=None, language=''):
-        """Set a parameter in the Content-Type header.
-
-        If the parameter already exists in the header, its value will be
-        replaced with the new value.
-
-        If header is Content-Type and has not yet been defined for this
-        message, it will be set to "text/plain" and the new parameter and
-        value will be appended as per RFC 2045.
-
-        An alternate header can specified in the header argument, and all
-        parameters will be quoted as necessary unless requote is False.
-
-        If charset is specified, the parameter will be encoded according to RFC
-        2231.  Optional language specifies the RFC 2231 language, defaulting
-        to the empty string.  Both charset and language should be strings.
-        """
-        if not isinstance(value, tuple) and charset:
-            value = (charset, language, value)
-
-        if not self.has_key(header) and header.lower() == 'content-type':
-            ctype = 'text/plain'
-        else:
-            ctype = self.get(header)
-        if not self.get_param(param, header=header):
-            if not ctype:
-                ctype = _formatparam(param, value, requote)
-            else:
-                ctype = SEMISPACE.join(
-                    [ctype, _formatparam(param, value, requote)])
-        else:
-            ctype = ''
-            for old_param, old_value in self.get_params(header=header,
-                                                        unquote=requote):
-                append_param = ''
-                if old_param.lower() == param.lower():
-                    append_param = _formatparam(param, value, requote)
-                else:
-                    append_param = _formatparam(old_param, old_value, requote)
-                if not ctype:
-                    ctype = append_param
-                else:
-                    ctype = SEMISPACE.join([ctype, append_param])
-        if ctype <> self.get(header):
-            del self[header]
-            self[header] = ctype
-
-    def del_param(self, param, header='content-type', requote=True):
-        """Remove the given parameter completely from the Content-Type header.
-
-        The header will be re-written in place without the parameter or its
-        value. All values will be quoted as necessary unless requote is
-        False.  Optional header specifies an alternative to the Content-Type
-        header.
-        """
-        if not self.has_key(header):
-            return
-        new_ctype = ''
-        for p, v in self.get_params(header=header, unquote=requote):
-            if p.lower() <> param.lower():
-                if not new_ctype:
-                    new_ctype = _formatparam(p, v, requote)
-                else:
-                    new_ctype = SEMISPACE.join([new_ctype,
-                                                _formatparam(p, v, requote)])
-        if new_ctype <> self.get(header):
-            del self[header]
-            self[header] = new_ctype
-
-    def set_type(self, type, header='Content-Type', requote=True):
-        """Set the main type and subtype for the Content-Type header.
-
-        type must be a string in the form "maintype/subtype", otherwise a
-        ValueError is raised.
-
-        This method replaces the Content-Type header, keeping all the
-        parameters in place.  If requote is False, this leaves the existing
-        header's quoting as is.  Otherwise, the parameters will be quoted (the
-        default).
-
-        An alternative header can be specified in the header argument.  When
-        the Content-Type header is set, we'll always also add a MIME-Version
-        header.
-        """
-        # BAW: should we be strict?
-        if not type.count('/') == 1:
-            raise ValueError
-        # Set the Content-Type, you get a MIME-Version
-        if header.lower() == 'content-type':
-            del self['mime-version']
-            self['MIME-Version'] = '1.0'
-        if not self.has_key(header):
-            self[header] = type
-            return
-        params = self.get_params(header=header, unquote=requote)
-        del self[header]
-        self[header] = type
-        # Skip the first param; it's the old type.
-        for p, v in params[1:]:
-            self.set_param(p, v, header, requote)
-
-    def get_filename(self, failobj=None):
-        """Return the filename associated with the payload if present.
-
-        The filename is extracted from the Content-Disposition header's
-        `filename' parameter, and it is unquoted.  If that header is missing
-        the `filename' parameter, this method falls back to looking for the
-        `name' parameter.
-        """
-        missing = object()
-        filename = self.get_param('filename', missing, 'content-disposition')
-        if filename is missing:
-            filename = self.get_param('name', missing, 'content-disposition')
-        if filename is missing:
-            return failobj
-        return Utils.collapse_rfc2231_value(filename).strip()
-
-    def get_boundary(self, failobj=None):
-        """Return the boundary associated with the payload if present.
-
-        The boundary is extracted from the Content-Type header's `boundary'
-        parameter, and it is unquoted.
-        """
-        missing = object()
-        boundary = self.get_param('boundary', missing)
-        if boundary is missing:
-            return failobj
-        # RFC 2046 says that boundaries may begin but not end in w/s
-        return Utils.collapse_rfc2231_value(boundary).rstrip()
-
-    def set_boundary(self, boundary):
-        """Set the boundary parameter in Content-Type to 'boundary'.
-
-        This is subtly different than deleting the Content-Type header and
-        adding a new one with a new boundary parameter via add_header().  The
-        main difference is that using the set_boundary() method preserves the
-        order of the Content-Type header in the original message.
-
-        HeaderParseError is raised if the message has no Content-Type header.
-        """
-        missing = object()
-        params = self._get_params_preserve(missing, 'content-type')
-        if params is missing:
-            # There was no Content-Type header, and we don't know what type
-            # to set it to, so raise an exception.
-            raise Errors.HeaderParseError, 'No Content-Type header found'
-        newparams = []
-        foundp = False
-        for pk, pv in params:
-            if pk.lower() == 'boundary':
-                newparams.append(('boundary', '"%s"' % boundary))
-                foundp = True
-            else:
-                newparams.append((pk, pv))
-        if not foundp:
-            # The original Content-Type header had no boundary attribute.
-            # Tack one on the end.  BAW: should we raise an exception
-            # instead???
-            newparams.append(('boundary', '"%s"' % boundary))
-        # Replace the existing Content-Type header with the new value
-        newheaders = []
-        for h, v in self._headers:
-            if h.lower() == 'content-type':
-                parts = []
-                for k, v in newparams:
-                    if v == '':
-                        parts.append(k)
-                    else:
-                        parts.append('%s=%s' % (k, v))
-                newheaders.append((h, SEMISPACE.join(parts)))
-
-            else:
-                newheaders.append((h, v))
-        self._headers = newheaders
-
-    def get_content_charset(self, failobj=None):
-        """Return the charset parameter of the Content-Type header.
-
-        The returned string is always coerced to lower case.  If there is no
-        Content-Type header, or if that header has no charset parameter,
-        failobj is returned.
-        """
-        missing = object()
-        charset = self.get_param('charset', missing)
-        if charset is missing:
-            return failobj
-        if isinstance(charset, tuple):
-            # RFC 2231 encoded, so decode it, and it better end up as ascii.
-            pcharset = charset[0] or 'us-ascii'
-            charset = unicode(charset[2], pcharset).encode('us-ascii')
-        # RFC 2046, $4.1.2 says charsets are not case sensitive
-        return charset.lower()
-
-    def get_charsets(self, failobj=None):
-        """Return a list containing the charset(s) used in this message.
-
-        The returned list of items describes the Content-Type headers'
-        charset parameter for this message and all the subparts in its
-        payload.
-
-        Each item will either be a string (the value of the charset parameter
-        in the Content-Type header of that part) or the value of the
-        'failobj' parameter (defaults to None), if the part does not have a
-        main MIME type of "text", or the charset is not defined.
-
-        The list will contain one string for each part of the message, plus
-        one for the container message (i.e. self), so that a non-multipart
-        message will still return a list of length 1.
-        """
-        return [part.get_content_charset(failobj) for part in self.walk()]
-
-    # I.e. def walk(self): ...
-    from email.Iterators import walk
diff --git a/Lib/email/Parser.py b/Lib/email/Parser.py
deleted file mode 100644
index 0c05224..0000000
--- a/Lib/email/Parser.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
-# Contact: email-sig@python.org
-
-"""A parser of RFC 2822 and MIME email messages."""
-
-import warnings
-from cStringIO import StringIO
-from email.FeedParser import FeedParser
-from email.Message import Message
-
-
-
-class Parser:
-    def __init__(self, *args, **kws):
-        """Parser of RFC 2822 and MIME email messages.
-
-        Creates an in-memory object tree representing the email message, which
-        can then be manipulated and turned over to a Generator to return the
-        textual representation of the message.
-
-        The string must be formatted as a block of RFC 2822 headers and header
-        continuation lines, optionally preceeded by a `Unix-from' header.  The
-        header block is terminated either by the end of the string or by a
-        blank line.
-
-        _class is the class to instantiate for new message objects when they
-        must be created.  This class must have a constructor that can take
-        zero arguments.  Default is Message.Message.
-        """
-        if len(args) >= 1:
-            if '_class' in kws:
-                raise TypeError("Multiple values for keyword arg '_class'")
-            kws['_class'] = args[0]
-        if len(args) == 2:
-            if 'strict' in kws:
-                raise TypeError("Multiple values for keyword arg 'strict'")
-            kws['strict'] = args[1]
-        if len(args) > 2:
-            raise TypeError('Too many arguments')
-        if '_class' in kws:
-            self._class = kws['_class']
-            del kws['_class']
-        else:
-            self._class = Message
-        if 'strict' in kws:
-            warnings.warn("'strict' argument is deprecated (and ignored)",
-                          DeprecationWarning, 2)
-            del kws['strict']
-        if kws:
-            raise TypeError('Unexpected keyword arguments')
-
-    def parse(self, fp, headersonly=False):
-        """Create a message structure from the data in a file.
-
-        Reads all the data from the file and returns the root of the message
-        structure.  Optional headersonly is a flag specifying whether to stop
-        parsing after reading the headers or not.  The default is False,
-        meaning it parses the entire contents of the file.
-        """
-        feedparser = FeedParser(self._class)
-        if headersonly:
-            feedparser._set_headersonly()
-        while True:
-            data = fp.read(8192)
-            if not data:
-                break
-            feedparser.feed(data)
-        return feedparser.close()
-
-    def parsestr(self, text, headersonly=False):
-        """Create a message structure from a string.
-
-        Returns the root of the message structure.  Optional headersonly is a
-        flag specifying whether to stop parsing after reading the headers or
-        not.  The default is False, meaning it parses the entire contents of
-        the file.
-        """
-        return self.parse(StringIO(text), headersonly=headersonly)
-
-
-
-class HeaderParser(Parser):
-    def parse(self, fp, headersonly=True):
-        return Parser.parse(self, fp, True)
-
-    def parsestr(self, text, headersonly=True):
-        return Parser.parsestr(self, text, True)
diff --git a/Lib/email/Utils.py b/Lib/email/Utils.py
deleted file mode 100644
index 9ba7601..0000000
--- a/Lib/email/Utils.py
+++ /dev/null
@@ -1,291 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Barry Warsaw
-# Contact: email-sig@python.org
-
-"""Miscellaneous utilities."""
-
-import os
-import re
-import time
-import base64
-import random
-import socket
-import warnings
-from cStringIO import StringIO
-
-from email._parseaddr import quote
-from email._parseaddr import AddressList as _AddressList
-from email._parseaddr import mktime_tz
-
-# We need wormarounds for bugs in these methods in older Pythons (see below)
-from email._parseaddr import parsedate as _parsedate
-from email._parseaddr import parsedate_tz as _parsedate_tz
-
-from quopri import decodestring as _qdecode
-
-# Intrapackage imports
-from email.Encoders import _bencode, _qencode
-
-COMMASPACE = ', '
-EMPTYSTRING = ''
-UEMPTYSTRING = u''
-CRLF = '\r\n'
-
-specialsre = re.compile(r'[][\\()<>@,:;".]')
-escapesre = re.compile(r'[][\\()"]')
-
-
-
-# Helpers
-
-def _identity(s):
-    return s
-
-
-def _bdecode(s):
-    # We can't quite use base64.encodestring() since it tacks on a "courtesy
-    # newline".  Blech!
-    if not s:
-        return s
-    value = base64.decodestring(s)
-    if not s.endswith('\n') and value.endswith('\n'):
-        return value[:-1]
-    return value
-
-
-
-def fix_eols(s):
-    """Replace all line-ending characters with \r\n."""
-    # Fix newlines with no preceding carriage return
-    s = re.sub(r'(?<!\r)\n', CRLF, s)
-    # Fix carriage returns with no following newline
-    s = re.sub(r'\r(?!\n)', CRLF, s)
-    return s
-
-
-
-def formataddr(pair):
-    """The inverse of parseaddr(), this takes a 2-tuple of the form
-    (realname, email_address) and returns the string value suitable
-    for an RFC 2822 From, To or Cc header.
-
-    If the first element of pair is false, then the second element is
-    returned unmodified.
-    """
-    name, address = pair
-    if name:
-        quotes = ''
-        if specialsre.search(name):
-            quotes = '"'
-        name = escapesre.sub(r'\\\g<0>', name)
-        return '%s%s%s <%s>' % (quotes, name, quotes, address)
-    return address
-
-
-
-def getaddresses(fieldvalues):
-    """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
-    all = COMMASPACE.join(fieldvalues)
-    a = _AddressList(all)
-    return a.addresslist
-
-
-
-ecre = re.compile(r'''
-  =\?                   # literal =?
-  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
-  \?                    # literal ?
-  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
-  \?                    # literal ?
-  (?P<atom>.*?)         # non-greedy up to the next ?= is the atom
-  \?=                   # literal ?=
-  ''', re.VERBOSE | re.IGNORECASE)
-
-
-
-def formatdate(timeval=None, localtime=False, usegmt=False):
-    """Returns a date string as specified by RFC 2822, e.g.:
-
-    Fri, 09 Nov 2001 01:08:47 -0000
-
-    Optional timeval if given is a floating point time value as accepted by
-    gmtime() and localtime(), otherwise the current time is used.
-
-    Optional localtime is a flag that when True, interprets timeval, and
-    returns a date relative to the local timezone instead of UTC, properly
-    taking daylight savings time into account.
-
-    Optional argument usegmt means that the timezone is written out as
-    an ascii string, not numeric one (so "GMT" instead of "+0000"). This
-    is needed for HTTP, and is only used when localtime==False.
-    """
-    # Note: we cannot use strftime() because that honors the locale and RFC
-    # 2822 requires that day and month names be the English abbreviations.
-    if timeval is None:
-        timeval = time.time()
-    if localtime:
-        now = time.localtime(timeval)
-        # Calculate timezone offset, based on whether the local zone has
-        # daylight savings time, and whether DST is in effect.
-        if time.daylight and now[-1]:
-            offset = time.altzone
-        else:
-            offset = time.timezone
-        hours, minutes = divmod(abs(offset), 3600)
-        # Remember offset is in seconds west of UTC, but the timezone is in
-        # minutes east of UTC, so the signs differ.
-        if offset > 0:
-            sign = '-'
-        else:
-            sign = '+'
-        zone = '%s%02d%02d' % (sign, hours, minutes // 60)
-    else:
-        now = time.gmtime(timeval)
-        # Timezone offset is always -0000
-        if usegmt:
-            zone = 'GMT'
-        else:
-            zone = '-0000'
-    return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
-        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],
-        now[2],
-        ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
-         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],
-        now[0], now[3], now[4], now[5],
-        zone)
-
-
-
-def make_msgid(idstring=None):
-    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
-
-    <20020201195627.33539.96671@nightshade.la.mastaler.com>
-
-    Optional idstring if given is a string used to strengthen the
-    uniqueness of the message id.
-    """
-    timeval = time.time()
-    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
-    pid = os.getpid()
-    randint = random.randrange(100000)
-    if idstring is None:
-        idstring = ''
-    else:
-        idstring = '.' + idstring
-    idhost = socket.getfqdn()
-    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
-    return msgid
-
-
-
-# These functions are in the standalone mimelib version only because they've
-# subsequently been fixed in the latest Python versions.  We use this to worm
-# around broken older Pythons.
-def parsedate(data):
-    if not data:
-        return None
-    return _parsedate(data)
-
-
-def parsedate_tz(data):
-    if not data:
-        return None
-    return _parsedate_tz(data)
-
-
-def parseaddr(addr):
-    addrs = _AddressList(addr).addresslist
-    if not addrs:
-        return '', ''
-    return addrs[0]
-
-
-# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.
-def unquote(str):
-    """Remove quotes from a string."""
-    if len(str) > 1:
-        if str.startswith('"') and str.endswith('"'):
-            return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
-        if str.startswith('<') and str.endswith('>'):
-            return str[1:-1]
-    return str
-
-
-
-# RFC2231-related functions - parameter encoding and decoding
-def decode_rfc2231(s):
-    """Decode string according to RFC 2231"""
-    import urllib
-    parts = s.split("'", 2)
-    if len(parts) == 1:
-        return None, None, urllib.unquote(s)
-    charset, language, s = parts
-    return charset, language, urllib.unquote(s)
-
-
-def encode_rfc2231(s, charset=None, language=None):
-    """Encode string according to RFC 2231.
-
-    If neither charset nor language is given, then s is returned as-is.  If
-    charset is given but not language, the string is encoded using the empty
-    string for language.
-    """
-    import urllib
-    s = urllib.quote(s, safe='')
-    if charset is None and language is None:
-        return s
-    if language is None:
-        language = ''
-    return "%s'%s'%s" % (charset, language, s)
-
-
-rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$')
-
-def decode_params(params):
-    """Decode parameters list according to RFC 2231.
-
-    params is a sequence of 2-tuples containing (content type, string value).
-    """
-    new_params = []
-    # maps parameter's name to a list of continuations
-    rfc2231_params = {}
-    # params is a sequence of 2-tuples containing (content_type, string value)
-    name, value = params[0]
-    new_params.append((name, value))
-    # Cycle through each of the rest of the parameters.
-    for name, value in params[1:]:
-        value = unquote(value)
-        mo = rfc2231_continuation.match(name)
-        if mo:
-            name, num = mo.group('name', 'num')
-            if num is not None:
-                num = int(num)
-            rfc2231_param1 = rfc2231_params.setdefault(name, [])
-            rfc2231_param1.append((num, value))
-        else:
-            new_params.append((name, '"%s"' % quote(value)))
-    if rfc2231_params:
-        for name, continuations in rfc2231_params.items():
-            value = []
-            # Sort by number
-            continuations.sort()
-            # And now append all values in num order
-            for num, continuation in continuations:
-                value.append(continuation)
-            charset, language, value = decode_rfc2231(EMPTYSTRING.join(value))
-            new_params.append(
-                (name, (charset, language, '"%s"' % quote(value))))
-    return new_params
-
-def collapse_rfc2231_value(value, errors='replace',
-                           fallback_charset='us-ascii'):
-    if isinstance(value, tuple):
-        rawval = unquote(value[2])
-        charset = value[0] or 'us-ascii'
-        try:
-            return unicode(rawval, charset, errors)
-        except LookupError:
-            # XXX charset is unknown to Python.
-            return unicode(rawval, fallback_charset, errors)
-    else:
-        return unquote(value)
diff --git a/Lib/email/__init__.py b/Lib/email/__init__.py
index e5c0e2e..0a6a72d 100644
--- a/Lib/email/__init__.py
+++ b/Lib/email/__init__.py
@@ -4,9 +4,10 @@
 
 """A package for parsing, handling, and generating email messages."""
 
-__version__ = '3.0.1'
+__version__ = '4.0a2'
 
 __all__ = [
+    # Old names
     'base64MIME',
     'Charset',
     'Encoders',
@@ -27,6 +28,19 @@ __all__ = [
     'Utils',
     'message_from_string',
     'message_from_file',
+    # new names
+    'base64mime',
+    'charset',
+    'encoders',
+    'errors',
+    'generator',
+    'header',
+    'iterators',
+    'message',
+    'mime',
+    'parser',
+    'quoprimime',
+    'utils',
     ]
 
 
@@ -39,7 +53,7 @@ def message_from_string(s, *args, **kws):
 
     Optional _class and strict are passed to the Parser constructor.
     """
-    from email.Parser import Parser
+    from email.parser import Parser
     return Parser(*args, **kws).parsestr(s)
 
 
@@ -48,5 +62,62 @@ def message_from_file(fp, *args, **kws):
 
     Optional _class and strict are passed to the Parser constructor.
     """
-    from email.Parser import Parser
+    from email.parser import Parser
     return Parser(*args, **kws).parse(fp)
+
+
+
+# Lazy loading to provide name mapping from new-style names (PEP 8 compatible
+# email 4.0 module names), to old-style names (email 3.0 module names).
+import sys
+
+class LazyImporter(object):
+  def __init__(self, module_name):
+      self.__name__ = 'email.' + module_name
+
+  def __getattr__(self, name):
+      __import__(self.__name__)
+      mod = sys.modules[self.__name__]
+      self.__dict__.update(mod.__dict__)
+      return getattr(mod, name)
+
+
+_LOWERNAMES = [
+    # email.<old name> -> email.<new name is lowercased old name>
+    'Charset',
+    'Encoders',
+    'Errors',
+    'FeedParser',
+    'Generator',
+    'Header',
+    'Iterators',
+    'Message',
+    'Parser',
+    'Utils',
+    'base64MIME',
+    'quopriMIME',
+    ]
+
+_MIMENAMES = [
+    # email.MIME<old name> -> email.mime.<new name is lowercased old name>
+    'Audio',
+    'Base',
+    'Image',
+    'Message',
+    'Multipart',
+    'NonMultipart',
+    'Text',
+    ]
+
+for _name in _LOWERNAMES:
+    importer = LazyImporter(_name.lower())
+    sys.modules['email.' + _name] = importer
+    setattr(sys.modules['email'], _name, importer)
+
+
+import email.mime
+for _name in _MIMENAMES:
+    importer = LazyImporter('mime.' + _name.lower())
+    sys.modules['email.MIME' + _name] = importer
+    setattr(sys.modules['email'], 'MIME' + _name, importer)
+    setattr(sys.modules['email.mime'], _name, importer)
diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py
index 7d759ef..109ff5f 100644
--- a/Lib/email/_parseaddr.py
+++ b/Lib/email/_parseaddr.py
@@ -6,6 +6,13 @@
 Lifted directly from rfc822.py.  This should eventually be rewritten.
 """
 
+__all__ = [
+    'mktime_tz',
+    'parsedate',
+    'parsedate_tz',
+    'quote',
+    ]
+
 import time
 
 SPACE = ' '
diff --git a/Lib/email/base64MIME.py b/Lib/email/base64MIME.py
deleted file mode 100644
index 6ed1d53..0000000
--- a/Lib/email/base64MIME.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# Copyright (C) 2002-2004 Python Software Foundation
-# Author: Ben Gertzfield
-# Contact: email-sig@python.org
-
-"""Base64 content transfer encoding per RFCs 2045-2047.
-
-This module handles the content transfer encoding method defined in RFC 2045
-to encode arbitrary 8-bit data using the three 8-bit bytes in four 7-bit
-characters encoding known as Base64.
-
-It is used in the MIME standards for email to attach images, audio, and text
-using some 8-bit character sets to messages.
-
-This module provides an interface to encode and decode both headers and bodies
-with Base64 encoding.
-
-RFC 2045 defines a method for including character set information in an
-`encoded-word' in a header.  This method is commonly used for 8-bit real names
-in To:, From:, Cc:, etc. fields, as well as Subject: lines.
-
-This module does not do the line wrapping or end-of-line character conversion
-necessary for proper internationalized headers; it only does dumb encoding and
-decoding.  To deal with the various line wrapping issues, use the email.Header
-module.
-"""
-
-import re
-from binascii import b2a_base64, a2b_base64
-from email.Utils import fix_eols
-
-CRLF = '\r\n'
-NL = '\n'
-EMPTYSTRING = ''
-
-# See also Charset.py
-MISC_LEN = 7
-
-
-
-# Helpers
-def base64_len(s):
-    """Return the length of s when it is encoded with base64."""
-    groups_of_3, leftover = divmod(len(s), 3)
-    # 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
-    # Thanks, Tim!
-    n = groups_of_3 * 4
-    if leftover:
-        n += 4
-    return n
-
-
-
-def header_encode(header, charset='iso-8859-1', keep_eols=False,
-                  maxlinelen=76, eol=NL):
-    """Encode a single header line with Base64 encoding in a given charset.
-
-    Defined in RFC 2045, this Base64 encoding is identical to normal Base64
-    encoding, except that each line must be intelligently wrapped (respecting
-    the Base64 encoding), and subsequent lines must start with a space.
-
-    charset names the character set to use to encode the header.  It defaults
-    to iso-8859-1.
-
-    End-of-line characters (\\r, \\n, \\r\\n) will be automatically converted
-    to the canonical email line separator \\r\\n unless the keep_eols
-    parameter is True (the default is False).
-
-    Each line of the header will be terminated in the value of eol, which
-    defaults to "\\n".  Set this to "\\r\\n" if you are using the result of
-    this function directly in email.
-
-    The resulting string will be in the form:
-
-    "=?charset?b?WW/5ciBtYXp66XLrIHf8eiBhIGhhbXBzdGHuciBBIFlv+XIgbWF6euly?=\\n
-      =?charset?b?6yB3/HogYSBoYW1wc3Rh7nIgQkMgWW/5ciBtYXp66XLrIHf8eiBhIGhh?="
-
-    with each line wrapped at, at most, maxlinelen characters (defaults to 76
-    characters).
-    """
-    # Return empty headers unchanged
-    if not header:
-        return header
-
-    if not keep_eols:
-        header = fix_eols(header)
-
-    # Base64 encode each line, in encoded chunks no greater than maxlinelen in
-    # length, after the RFC chrome is added in.
-    base64ed = []
-    max_encoded = maxlinelen - len(charset) - MISC_LEN
-    max_unencoded = max_encoded * 3 // 4
-
-    for i in range(0, len(header), max_unencoded):
-        base64ed.append(b2a_base64(header[i:i+max_unencoded]))
-
-    # Now add the RFC chrome to each encoded chunk
-    lines = []
-    for line in base64ed:
-        # Ignore the last character of each line if it is a newline
-        if line.endswith(NL):
-            line = line[:-1]
-        # Add the chrome
-        lines.append('=?%s?b?%s?=' % (charset, line))
-    # Glue the lines together and return it.  BAW: should we be able to
-    # specify the leading whitespace in the joiner?
-    joiner = eol + ' '
-    return joiner.join(lines)
-
-
-
-def encode(s, binary=True, maxlinelen=76, eol=NL):
-    """Encode a string with base64.
-
-    Each line will be wrapped at, at most, maxlinelen characters (defaults to
-    76 characters).
-
-    If binary is False, end-of-line characters will be converted to the
-    canonical email end-of-line sequence \\r\\n.  Otherwise they will be left
-    verbatim (this is the default).
-
-    Each line of encoded text will end with eol, which defaults to "\\n".  Set
-    this to "\r\n" if you will be using the result of this function directly
-    in an email.
-    """
-    if not s:
-        return s
-
-    if not binary:
-        s = fix_eols(s)
-
-    encvec = []
-    max_unencoded = maxlinelen * 3 // 4
-    for i in range(0, len(s), max_unencoded):
-        # BAW: should encode() inherit b2a_base64()'s dubious behavior in
-        # adding a newline to the encoded string?
-        enc = b2a_base64(s[i:i + max_unencoded])
-        if enc.endswith(NL) and eol <> NL:
-            enc = enc[:-1] + eol
-        encvec.append(enc)
-    return EMPTYSTRING.join(encvec)
-
-
-# For convenience and backwards compatibility w/ standard base64 module
-body_encode = encode
-encodestring = encode
-
-
-
-def decode(s, convert_eols=None):
-    """Decode a raw base64 string.
-
-    If convert_eols is set to a string value, all canonical email linefeeds,
-    e.g. "\\r\\n", in the decoded text will be converted to the value of
-    convert_eols.  os.linesep is a good choice for convert_eols if you are
-    decoding a text attachment.
-
-    This function does not parse a full MIME header value encoded with
-    base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
-    level email.Header class for that functionality.
-    """
-    if not s:
-        return s
-
-    dec = a2b_base64(s)
-    if convert_eols:
-        return dec.replace(CRLF, convert_eols)
-    return dec
-
-
-# For convenience and backwards compatibility w/ standard base64 module
-body_decode = decode
-decodestring = decode
diff --git a/Lib/email/base64mime.py b/Lib/email/base64mime.py
new file mode 100644
index 0000000..0129d9d
--- /dev/null
+++ b/Lib/email/base64mime.py
@@ -0,0 +1,184 @@
+# Copyright (C) 2002-2006 Python Software Foundation
+# Author: Ben Gertzfield
+# Contact: email-sig@python.org
+
+"""Base64 content transfer encoding per RFCs 2045-2047.
+
+This module handles the content transfer encoding method defined in RFC 2045
+to encode arbitrary 8-bit data using the three 8-bit bytes in four 7-bit
+characters encoding known as Base64.
+
+It is used in the MIME standards for email to attach images, audio, and text
+using some 8-bit character sets to messages.
+
+This module provides an interface to encode and decode both headers and bodies
+with Base64 encoding.
+
+RFC 2045 defines a method for including character set information in an
+`encoded-word' in a header.  This method is commonly used for 8-bit real names
+in To:, From:, Cc:, etc. fields, as well as Subject: lines.
+
+This module does not do the line wrapping or end-of-line character conversion
+necessary for proper internationalized headers; it only does dumb encoding and
+decoding.  To deal with the various line wrapping issues, use the email.Header
+module.
+"""
+
+__all__ = [
+    'base64_len',
+    'body_decode',
+    'body_encode',
+    'decode',
+    'decodestring',
+    'encode',
+    'encodestring',
+    'header_encode',
+    ]
+
+import re
+
+from binascii import b2a_base64, a2b_base64
+from email.utils import fix_eols
+
+CRLF = '\r\n'
+NL = '\n'
+EMPTYSTRING = ''
+
+# See also Charset.py
+MISC_LEN = 7
+
+
+
+# Helpers
+def base64_len(s):
+    """Return the length of s when it is encoded with base64."""
+    groups_of_3, leftover = divmod(len(s), 3)
+    # 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
+    # Thanks, Tim!
+    n = groups_of_3 * 4
+    if leftover:
+        n += 4
+    return n
+
+
+
+def header_encode(header, charset='iso-8859-1', keep_eols=False,
+                  maxlinelen=76, eol=NL):
+    """Encode a single header line with Base64 encoding in a given charset.
+
+    Defined in RFC 2045, this Base64 encoding is identical to normal Base64
+    encoding, except that each line must be intelligently wrapped (respecting
+    the Base64 encoding), and subsequent lines must start with a space.
+
+    charset names the character set to use to encode the header.  It defaults
+    to iso-8859-1.
+
+    End-of-line characters (\\r, \\n, \\r\\n) will be automatically converted
+    to the canonical email line separator \\r\\n unless the keep_eols
+    parameter is True (the default is False).
+
+    Each line of the header will be terminated in the value of eol, which
+    defaults to "\\n".  Set this to "\\r\\n" if you are using the result of
+    this function directly in email.
+
+    The resulting string will be in the form:
+
+    "=?charset?b?WW/5ciBtYXp66XLrIHf8eiBhIGhhbXBzdGHuciBBIFlv+XIgbWF6euly?=\\n
+      =?charset?b?6yB3/HogYSBoYW1wc3Rh7nIgQkMgWW/5ciBtYXp66XLrIHf8eiBhIGhh?="
+
+    with each line wrapped at, at most, maxlinelen characters (defaults to 76
+    characters).
+    """
+    # Return empty headers unchanged
+    if not header:
+        return header
+
+    if not keep_eols:
+        header = fix_eols(header)
+
+    # Base64 encode each line, in encoded chunks no greater than maxlinelen in
+    # length, after the RFC chrome is added in.
+    base64ed = []
+    max_encoded = maxlinelen - len(charset) - MISC_LEN
+    max_unencoded = max_encoded * 3 // 4
+
+    for i in range(0, len(header), max_unencoded):
+        base64ed.append(b2a_base64(header[i:i+max_unencoded]))
+
+    # Now add the RFC chrome to each encoded chunk
+    lines = []
+    for line in base64ed:
+        # Ignore the last character of each line if it is a newline
+        if line.endswith(NL):
+            line = line[:-1]
+        # Add the chrome
+        lines.append('=?%s?b?%s?=' % (charset, line))
+    # Glue the lines together and return it.  BAW: should we be able to
+    # specify the leading whitespace in the joiner?
+    joiner = eol + ' '
+    return joiner.join(lines)
+
+
+
+def encode(s, binary=True, maxlinelen=76, eol=NL):
+    """Encode a string with base64.
+
+    Each line will be wrapped at, at most, maxlinelen characters (defaults to
+    76 characters).
+
+    If binary is False, end-of-line characters will be converted to the
+    canonical email end-of-line sequence \\r\\n.  Otherwise they will be left
+    verbatim (this is the default).
+
+    Each line of encoded text will end with eol, which defaults to "\\n".  Set
+    this to "\r\n" if you will be using the result of this function directly
+    in an email.
+    """
+    if not s:
+        return s
+
+    if not binary:
+        s = fix_eols(s)
+
+    encvec = []
+    max_unencoded = maxlinelen * 3 // 4
+    for i in range(0, len(s), max_unencoded):
+        # BAW: should encode() inherit b2a_base64()'s dubious behavior in
+        # adding a newline to the encoded string?
+        enc = b2a_base64(s[i:i + max_unencoded])
+        if enc.endswith(NL) and eol <> NL:
+            enc = enc[:-1] + eol
+        encvec.append(enc)
+    return EMPTYSTRING.join(encvec)
+
+
+# For convenience and backwards compatibility w/ standard base64 module
+body_encode = encode
+encodestring = encode
+
+
+
+def decode(s, convert_eols=None):
+    """Decode a raw base64 string.
+
+    If convert_eols is set to a string value, all canonical email linefeeds,
+    e.g. "\\r\\n", in the decoded text will be converted to the value of
+    convert_eols.  os.linesep is a good choice for convert_eols if you are
+    decoding a text attachment.
+
+    This function does not parse a full MIME header value encoded with
+    base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
+    level email.Header class for that functionality.
+    """
+    if not s:
+        return s
+
+    dec = a2b_base64(s)
+    if convert_eols:
+        return dec.replace(CRLF, convert_eols)
+    return dec
+
+
+# For convenience and backwards compatibility w/ standard base64 module
+body_decode = decode
+decodestring = decode
diff --git a/Lib/email/charset.py b/Lib/email/charset.py
new file mode 100644
index 0000000..8f218b2
--- /dev/null
+++ b/Lib/email/charset.py
@@ -0,0 +1,388 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Ben Gertzfield, Barry Warsaw
+# Contact: email-sig@python.org
+
+__all__ = [
+    'Charset',
+    'add_alias',
+    'add_charset',
+    'add_codec',
+    ]
+
+import email.base64mime
+import email.quoprimime
+
+from email import errors
+from email.encoders import encode_7or8bit
+
+
+
+# Flags for types of header encodings
+QP          = 1 # Quoted-Printable
+BASE64      = 2 # Base64
+SHORTEST    = 3 # the shorter of QP and base64, but only for headers
+
+# In "=?charset?q?hello_world?=", the =?, ?q?, and ?= add up to 7
+MISC_LEN = 7
+
+DEFAULT_CHARSET = 'us-ascii'
+
+
+
+# Defaults
+CHARSETS = {
+    # input        header enc  body enc output conv
+    'iso-8859-1':  (QP,        QP,      None),
+    'iso-8859-2':  (QP,        QP,      None),
+    'iso-8859-3':  (QP,        QP,      None),
+    'iso-8859-4':  (QP,        QP,      None),
+    # iso-8859-5 is Cyrillic, and not especially used
+    # iso-8859-6 is Arabic, also not particularly used
+    # iso-8859-7 is Greek, QP will not make it readable
+    # iso-8859-8 is Hebrew, QP will not make it readable
+    'iso-8859-9':  (QP,        QP,      None),
+    'iso-8859-10': (QP,        QP,      None),
+    # iso-8859-11 is Thai, QP will not make it readable
+    'iso-8859-13': (QP,        QP,      None),
+    'iso-8859-14': (QP,        QP,      None),
+    'iso-8859-15': (QP,        QP,      None),
+    'windows-1252':(QP,        QP,      None),
+    'viscii':      (QP,        QP,      None),
+    'us-ascii':    (None,      None,    None),
+    'big5':        (BASE64,    BASE64,  None),
+    'gb2312':      (BASE64,    BASE64,  None),
+    'euc-jp':      (BASE64,    None,    'iso-2022-jp'),
+    'shift_jis':   (BASE64,    None,    'iso-2022-jp'),
+    'iso-2022-jp': (BASE64,    None,    None),
+    'koi8-r':      (BASE64,    BASE64,  None),
+    'utf-8':       (SHORTEST,  BASE64, 'utf-8'),
+    # We're making this one up to represent raw unencoded 8-bit
+    '8bit':        (None,      BASE64, 'utf-8'),
+    }
+
+# Aliases for other commonly-used names for character sets.  Map
+# them to the real ones used in email.
+ALIASES = {
+    'latin_1': 'iso-8859-1',
+    'latin-1': 'iso-8859-1',
+    'latin_2': 'iso-8859-2',
+    'latin-2': 'iso-8859-2',
+    'latin_3': 'iso-8859-3',
+    'latin-3': 'iso-8859-3',
+    'latin_4': 'iso-8859-4',
+    'latin-4': 'iso-8859-4',
+    'latin_5': 'iso-8859-9',
+    'latin-5': 'iso-8859-9',
+    'latin_6': 'iso-8859-10',
+    'latin-6': 'iso-8859-10',
+    'latin_7': 'iso-8859-13',
+    'latin-7': 'iso-8859-13',
+    'latin_8': 'iso-8859-14',
+    'latin-8': 'iso-8859-14',
+    'latin_9': 'iso-8859-15',
+    'latin-9': 'iso-8859-15',
+    'cp949':   'ks_c_5601-1987',
+    'euc_jp':  'euc-jp',
+    'euc_kr':  'euc-kr',
+    'ascii':   'us-ascii',
+    }
+
+
+# Map charsets to their Unicode codec strings.
+CODEC_MAP = {
+    'gb2312':      'eucgb2312_cn',
+    'big5':        'big5_tw',
+    # Hack: We don't want *any* conversion for stuff marked us-ascii, as all
+    # sorts of garbage might be sent to us in the guise of 7-bit us-ascii.
+    # Let that stuff pass through without conversion to/from Unicode.
+    'us-ascii':    None,
+    }
+
+
+
+# Convenience functions for extending the above mappings
+def add_charset(charset, header_enc=None, body_enc=None, output_charset=None):
+    """Add character set properties to the global registry.
+
+    charset is the input character set, and must be the canonical name of a
+    character set.
+
+    Optional header_enc and body_enc is either Charset.QP for
+    quoted-printable, Charset.BASE64 for base64 encoding, Charset.SHORTEST for
+    the shortest of qp or base64 encoding, or None for no encoding.  SHORTEST
+    is only valid for header_enc.  It describes how message headers and
+    message bodies in the input charset are to be encoded.  Default is no
+    encoding.
+
+    Optional output_charset is the character set that the output should be
+    in.  Conversions will proceed from input charset, to Unicode, to the
+    output charset when the method Charset.convert() is called.  The default
+    is to output in the same character set as the input.
+
+    Both input_charset and output_charset must have Unicode codec entries in
+    the module's charset-to-codec mapping; use add_codec(charset, codecname)
+    to add codecs the module does not know about.  See the codecs module's
+    documentation for more information.
+    """
+    if body_enc == SHORTEST:
+        raise ValueError('SHORTEST not allowed for body_enc')
+    CHARSETS[charset] = (header_enc, body_enc, output_charset)
+
+
+def add_alias(alias, canonical):
+    """Add a character set alias.
+
+    alias is the alias name, e.g. latin-1
+    canonical is the character set's canonical name, e.g. iso-8859-1
+    """
+    ALIASES[alias] = canonical
+
+
+def add_codec(charset, codecname):
+    """Add a codec that map characters in the given charset to/from Unicode.
+
+    charset is the canonical name of a character set.  codecname is the name
+    of a Python codec, as appropriate for the second argument to the unicode()
+    built-in, or to the encode() method of a Unicode string.
+    """
+    CODEC_MAP[charset] = codecname
+
+
+
+class Charset:
+    """Map character sets to their email properties.
+
+    This class provides information about the requirements imposed on email
+    for a specific character set.  It also provides convenience routines for
+    converting between character sets, given the availability of the
+    applicable codecs.  Given a character set, it will do its best to provide
+    information on how to use that character set in an email in an
+    RFC-compliant way.
+
+    Certain character sets must be encoded with quoted-printable or base64
+    when used in email headers or bodies.  Certain character sets must be
+    converted outright, and are not allowed in email.  Instances of this
+    module expose the following information about a character set:
+
+    input_charset: The initial character set specified.  Common aliases
+                   are converted to their `official' email names (e.g. latin_1
+                   is converted to iso-8859-1).  Defaults to 7-bit us-ascii.
+
+    header_encoding: If the character set must be encoded before it can be
+                     used in an email header, this attribute will be set to
+                     Charset.QP (for quoted-printable), Charset.BASE64 (for
+                     base64 encoding), or Charset.SHORTEST for the shortest of
+                     QP or BASE64 encoding.  Otherwise, it will be None.
+
+    body_encoding: Same as header_encoding, but describes the encoding for the
+                   mail message's body, which indeed may be different than the
+                   header encoding.  Charset.SHORTEST is not allowed for
+                   body_encoding.
+
+    output_charset: Some character sets must be converted before the can be
+                    used in email headers or bodies.  If the input_charset is
+                    one of them, this attribute will contain the name of the
+                    charset output will be converted to.  Otherwise, it will
+                    be None.
+
+    input_codec: The name of the Python codec used to convert the
+                 input_charset to Unicode.  If no conversion codec is
+                 necessary, this attribute will be None.
+
+    output_codec: The name of the Python codec used to convert Unicode
+                  to the output_charset.  If no conversion codec is necessary,
+                  this attribute will have the same value as the input_codec.
+    """
+    def __init__(self, input_charset=DEFAULT_CHARSET):
+        # RFC 2046, $4.1.2 says charsets are not case sensitive.  We coerce to
+        # unicode because its .lower() is locale insensitive.  If the argument
+        # is already a unicode, we leave it at that, but ensure that the
+        # charset is ASCII, as the standard (RFC XXX) requires.
+        try:
+            if isinstance(input_charset, unicode):
+                input_charset.encode('ascii')
+            else:
+                input_charset = unicode(input_charset, 'ascii')
+        except UnicodeError:
+            raise errors.CharsetError(input_charset)
+        input_charset = input_charset.lower()
+        # Set the input charset after filtering through the aliases
+        self.input_charset = ALIASES.get(input_charset, input_charset)
+        # We can try to guess which encoding and conversion to use by the
+        # charset_map dictionary.  Try that first, but let the user override
+        # it.
+        henc, benc, conv = CHARSETS.get(self.input_charset,
+                                        (SHORTEST, BASE64, None))
+        if not conv:
+            conv = self.input_charset
+        # Set the attributes, allowing the arguments to override the default.
+        self.header_encoding = henc
+        self.body_encoding = benc
+        self.output_charset = ALIASES.get(conv, conv)
+        # Now set the codecs.  If one isn't defined for input_charset,
+        # guess and try a Unicode codec with the same name as input_codec.
+        self.input_codec = CODEC_MAP.get(self.input_charset,
+                                         self.input_charset)
+        self.output_codec = CODEC_MAP.get(self.output_charset,
+                                          self.output_charset)
+
+    def __str__(self):
+        return self.input_charset.lower()
+
+    __repr__ = __str__
+
+    def __eq__(self, other):
+        return str(self) == str(other).lower()
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def get_body_encoding(self):
+        """Return the content-transfer-encoding used for body encoding.
+
+        This is either the string `quoted-printable' or `base64' depending on
+        the encoding used, or it is a function in which case you should call
+        the function with a single argument, the Message object being
+        encoded.  The function should then set the Content-Transfer-Encoding
+        header itself to whatever is appropriate.
+
+        Returns "quoted-printable" if self.body_encoding is QP.
+        Returns "base64" if self.body_encoding is BASE64.
+        Returns "7bit" otherwise.
+        """
+        assert self.body_encoding <> SHORTEST
+        if self.body_encoding == QP:
+            return 'quoted-printable'
+        elif self.body_encoding == BASE64:
+            return 'base64'
+        else:
+            return encode_7or8bit
+
+    def convert(self, s):
+        """Convert a string from the input_codec to the output_codec."""
+        if self.input_codec <> self.output_codec:
+            return unicode(s, self.input_codec).encode(self.output_codec)
+        else:
+            return s
+
+    def to_splittable(self, s):
+        """Convert a possibly multibyte string to a safely splittable format.
+
+        Uses the input_codec to try and convert the string to Unicode, so it
+        can be safely split on character boundaries (even for multibyte
+        characters).
+
+        Returns the string as-is if it isn't known how to convert it to
+        Unicode with the input_charset.
+
+        Characters that could not be converted to Unicode will be replaced
+        with the Unicode replacement character U+FFFD.
+        """
+        if isinstance(s, unicode) or self.input_codec is None:
+            return s
+        try:
+            return unicode(s, self.input_codec, 'replace')
+        except LookupError:
+            # Input codec not installed on system, so return the original
+            # string unchanged.
+            return s
+
+    def from_splittable(self, ustr, to_output=True):
+        """Convert a splittable string back into an encoded string.
+
+        Uses the proper codec to try and convert the string from Unicode back
+        into an encoded format.  Return the string as-is if it is not Unicode,
+        or if it could not be converted from Unicode.
+
+        Characters that could not be converted from Unicode will be replaced
+        with an appropriate character (usually '?').
+
+        If to_output is True (the default), uses output_codec to convert to an
+        encoded format.  If to_output is False, uses input_codec.
+        """
+        if to_output:
+            codec = self.output_codec
+        else:
+            codec = self.input_codec
+        if not isinstance(ustr, unicode) or codec is None:
+            return ustr
+        try:
+            return ustr.encode(codec, 'replace')
+        except LookupError:
+            # Output codec not installed
+            return ustr
+
+    def get_output_charset(self):
+        """Return the output character set.
+
+        This is self.output_charset if that is not None, otherwise it is
+        self.input_charset.
+        """
+        return self.output_charset or self.input_charset
+
+    def encoded_header_len(self, s):
+        """Return the length of the encoded header string."""
+        cset = self.get_output_charset()
+        # The len(s) of a 7bit encoding is len(s)
+        if self.header_encoding == BASE64:
+            return email.base64mime.base64_len(s) + len(cset) + MISC_LEN
+        elif self.header_encoding == QP:
+            return email.quoprimime.header_quopri_len(s) + len(cset) + MISC_LEN
+        elif self.header_encoding == SHORTEST:
+            lenb64 = email.base64mime.base64_len(s)
+            lenqp = email.quoprimime.header_quopri_len(s)
+            return min(lenb64, lenqp) + len(cset) + MISC_LEN
+        else:
+            return len(s)
+
+    def header_encode(self, s, convert=False):
+        """Header-encode a string, optionally converting it to output_charset.
+
+        If convert is True, the string will be converted from the input
+        charset to the output charset automatically.  This is not useful for
+        multibyte character sets, which have line length issues (multibyte
+        characters must be split on a character, not a byte boundary); use the
+        high-level Header class to deal with these issues.  convert defaults
+        to False.
+
+        The type of encoding (base64 or quoted-printable) will be based on
+        self.header_encoding.
+        """
+        cset = self.get_output_charset()
+        if convert:
+            s = self.convert(s)
+        # 7bit/8bit encodings return the string unchanged (modulo conversions)
+        if self.header_encoding == BASE64:
+            return email.base64mime.header_encode(s, cset)
+        elif self.header_encoding == QP:
+            return email.quoprimime.header_encode(s, cset, maxlinelen=None)
+        elif self.header_encoding == SHORTEST:
+            lenb64 = email.base64mime.base64_len(s)
+            lenqp = email.quoprimime.header_quopri_len(s)
+            if lenb64 < lenqp:
+                return email.base64mime.header_encode(s, cset)
+            else:
+                return email.quoprimime.header_encode(s, cset, maxlinelen=None)
+        else:
+            return s
+
+    def body_encode(self, s, convert=True):
+        """Body-encode a string and convert it to output_charset.
+
+        If convert is True (the default), the string will be converted from
+        the input charset to output charset automatically.  Unlike
+        header_encode(), there are no issues with byte boundaries and
+        multibyte charsets in email bodies, so this is usually pretty safe.
+
+        The type of encoding (base64 or quoted-printable) will be based on
+        self.body_encoding.
+        """
+        if convert:
+            s = self.convert(s)
+        # 7bit/8bit encodings return the string unchanged (module conversions)
+        if self.body_encoding is BASE64:
+            return email.base64mime.body_encode(s)
+        elif self.body_encoding is QP:
+            return email.quoprimime.body_encode(s)
+        else:
+            return s
diff --git a/Lib/email/encoders.py b/Lib/email/encoders.py
new file mode 100644
index 0000000..06016cd
--- /dev/null
+++ b/Lib/email/encoders.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Encodings and related functions."""
+
+__all__ = [
+    'encode_7or8bit',
+    'encode_base64',
+    'encode_noop',
+    'encode_quopri',
+    ]
+
+import base64
+
+from quopri import encodestring as _encodestring
+
+
+
+def _qencode(s):
+    enc = _encodestring(s, quotetabs=True)
+    # Must encode spaces, which quopri.encodestring() doesn't do
+    return enc.replace(' ', '=20')
+
+
+def _bencode(s):
+    # We can't quite use base64.encodestring() since it tacks on a "courtesy
+    # newline".  Blech!
+    if not s:
+        return s
+    hasnewline = (s[-1] == '\n')
+    value = base64.encodestring(s)
+    if not hasnewline and value[-1] == '\n':
+        return value[:-1]
+    return value
+
+
+
+def encode_base64(msg):
+    """Encode the message's payload in Base64.
+
+    Also, add an appropriate Content-Transfer-Encoding header.
+    """
+    orig = msg.get_payload()
+    encdata = _bencode(orig)
+    msg.set_payload(encdata)
+    msg['Content-Transfer-Encoding'] = 'base64'
+
+
+
+def encode_quopri(msg):
+    """Encode the message's payload in quoted-printable.
+
+    Also, add an appropriate Content-Transfer-Encoding header.
+    """
+    orig = msg.get_payload()
+    encdata = _qencode(orig)
+    msg.set_payload(encdata)
+    msg['Content-Transfer-Encoding'] = 'quoted-printable'
+
+
+
+def encode_7or8bit(msg):
+    """Set the Content-Transfer-Encoding header to 7bit or 8bit."""
+    orig = msg.get_payload()
+    if orig is None:
+        # There's no payload.  For backwards compatibility we use 7bit
+        msg['Content-Transfer-Encoding'] = '7bit'
+        return
+    # We play a trick to make this go fast.  If encoding to ASCII succeeds, we
+    # know the data must be 7bit, otherwise treat it as 8bit.
+    try:
+        orig.encode('ascii')
+    except UnicodeError:
+        # iso-2022-* is non-ASCII but still 7-bit
+        charset = msg.get_charset()
+        output_cset = charset and charset.output_charset
+        if output_cset and output_cset.lower().startswith('iso-2202-'):
+            msg['Content-Transfer-Encoding'] = '7bit'
+        else:
+            msg['Content-Transfer-Encoding'] = '8bit'
+    else:
+        msg['Content-Transfer-Encoding'] = '7bit'
+
+
+
+def encode_noop(msg):
+    """Do nothing."""
diff --git a/Lib/email/errors.py b/Lib/email/errors.py
new file mode 100644
index 0000000..d52a624
--- /dev/null
+++ b/Lib/email/errors.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""email package exception classes."""
+
+
+
+class MessageError(Exception):
+    """Base class for errors in the email package."""
+
+
+class MessageParseError(MessageError):
+    """Base class for message parsing errors."""
+
+
+class HeaderParseError(MessageParseError):
+    """Error while parsing headers."""
+
+
+class BoundaryError(MessageParseError):
+    """Couldn't find terminating boundary."""
+
+
+class MultipartConversionError(MessageError, TypeError):
+    """Conversion to a multipart is prohibited."""
+
+
+class CharsetError(MessageError):
+    """An illegal charset was given."""
+
+
+
+# These are parsing defects which the parser was able to work around.
+class MessageDefect:
+    """Base class for a message defect."""
+
+    def __init__(self, line=None):
+        self.line = line
+
+class NoBoundaryInMultipartDefect(MessageDefect):
+    """A message claimed to be a multipart but had no boundary parameter."""
+
+class StartBoundaryNotFoundDefect(MessageDefect):
+    """The claimed start boundary was never found."""
+
+class FirstHeaderLineIsContinuationDefect(MessageDefect):
+    """A message had a continuation line as its first header line."""
+
+class MisplacedEnvelopeHeaderDefect(MessageDefect):
+    """A 'Unix-from' header was found in the middle of a header block."""
+
+class MalformedHeaderDefect(MessageDefect):
+    """Found a header that was missing a colon, or was otherwise malformed."""
+
+class MultipartInvariantViolationDefect(MessageDefect):
+    """A message claimed to be a multipart but no subparts were found."""
diff --git a/Lib/email/feedparser.py b/Lib/email/feedparser.py
new file mode 100644
index 0000000..afb02b3
--- /dev/null
+++ b/Lib/email/feedparser.py
@@ -0,0 +1,480 @@
+# Copyright (C) 2004-2006 Python Software Foundation
+# Authors: Baxter, Wouters and Warsaw
+# Contact: email-sig@python.org
+
+"""FeedParser - An email feed parser.
+
+The feed parser implements an interface for incrementally parsing an email
+message, line by line.  This has advantages for certain applications, such as
+those reading email messages off a socket.
+
+FeedParser.feed() is the primary interface for pushing new data into the
+parser.  It returns when there's nothing more it can do with the available
+data.  When you have no more data to push into the parser, call .close().
+This completes the parsing and returns the root message object.
+
+The other advantage of this parser is that it will never throw a parsing
+exception.  Instead, when it finds something unexpected, it adds a 'defect' to
+the current message.  Defects are just instances that live on the message
+object's .defects attribute.
+"""
+
+__all__ = ['FeedParser']
+
+import re
+
+from email import errors
+from email import message
+
+NLCRE = re.compile('\r\n|\r|\n')
+NLCRE_bol = re.compile('(\r\n|\r|\n)')
+NLCRE_eol = re.compile('(\r\n|\r|\n)$')
+NLCRE_crack = re.compile('(\r\n|\r|\n)')
+# RFC 2822 $3.6.8 Optional fields.  ftext is %d33-57 / %d59-126, Any character
+# except controls, SP, and ":".
+headerRE = re.compile(r'^(From |[\041-\071\073-\176]{1,}:|[\t ])')
+EMPTYSTRING = ''
+NL = '\n'
+
+NeedMoreData = object()
+
+
+
+class BufferedSubFile(object):
+    """A file-ish object that can have new data loaded into it.
+
+    You can also push and pop line-matching predicates onto a stack.  When the
+    current predicate matches the current line, a false EOF response
+    (i.e. empty string) is returned instead.  This lets the parser adhere to a
+    simple abstraction -- it parses until EOF closes the current message.
+    """
+    def __init__(self):
+        # The last partial line pushed into this object.
+        self._partial = ''
+        # The list of full, pushed lines, in reverse order
+        self._lines = []
+        # The stack of false-EOF checking predicates.
+        self._eofstack = []
+        # A flag indicating whether the file has been closed or not.
+        self._closed = False
+
+    def push_eof_matcher(self, pred):
+        self._eofstack.append(pred)
+
+    def pop_eof_matcher(self):
+        return self._eofstack.pop()
+
+    def close(self):
+        # Don't forget any trailing partial line.
+        self._lines.append(self._partial)
+        self._partial = ''
+        self._closed = True
+
+    def readline(self):
+        if not self._lines:
+            if self._closed:
+                return ''
+            return NeedMoreData
+        # Pop the line off the stack and see if it matches the current
+        # false-EOF predicate.
+        line = self._lines.pop()
+        # RFC 2046, section 5.1.2 requires us to recognize outer level
+        # boundaries at any level of inner nesting.  Do this, but be sure it's
+        # in the order of most to least nested.
+        for ateof in self._eofstack[::-1]:
+            if ateof(line):
+                # We're at the false EOF.  But push the last line back first.
+                self._lines.append(line)
+                return ''
+        return line
+
+    def unreadline(self, line):
+        # Let the consumer push a line back into the buffer.
+        assert line is not NeedMoreData
+        self._lines.append(line)
+
+    def push(self, data):
+        """Push some new data into this object."""
+        # Handle any previous leftovers
+        data, self._partial = self._partial + data, ''
+        # Crack into lines, but preserve the newlines on the end of each
+        parts = NLCRE_crack.split(data)
+        # The *ahem* interesting behaviour of re.split when supplied grouping
+        # parentheses is that the last element of the resulting list is the
+        # data after the final RE.  In the case of a NL/CR terminated string,
+        # this is the empty string.
+        self._partial = parts.pop()
+        # parts is a list of strings, alternating between the line contents
+        # and the eol character(s).  Gather up a list of lines after
+        # re-attaching the newlines.
+        lines = []
+        for i in range(len(parts) // 2):
+            lines.append(parts[i*2] + parts[i*2+1])
+        self.pushlines(lines)
+
+    def pushlines(self, lines):
+        # Reverse and insert at the front of the lines.
+        self._lines[:0] = lines[::-1]
+
+    def is_closed(self):
+        return self._closed
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        line = self.readline()
+        if line == '':
+            raise StopIteration
+        return line
+
+
+
+class FeedParser:
+    """A feed-style parser of email."""
+
+    def __init__(self, _factory=message.Message):
+        """_factory is called with no arguments to create a new message obj"""
+        self._factory = _factory
+        self._input = BufferedSubFile()
+        self._msgstack = []
+        self._parse = self._parsegen().next
+        self._cur = None
+        self._last = None
+        self._headersonly = False
+
+    # Non-public interface for supporting Parser's headersonly flag
+    def _set_headersonly(self):
+        self._headersonly = True
+
+    def feed(self, data):
+        """Push more data into the parser."""
+        self._input.push(data)
+        self._call_parse()
+
+    def _call_parse(self):
+        try:
+            self._parse()
+        except StopIteration:
+            pass
+
+    def close(self):
+        """Parse all remaining data and return the root message object."""
+        self._input.close()
+        self._call_parse()
+        root = self._pop_message()
+        assert not self._msgstack
+        # Look for final set of defects
+        if root.get_content_maintype() == 'multipart' \
+               and not root.is_multipart():
+            root.defects.append(errors.MultipartInvariantViolationDefect())
+        return root
+
+    def _new_message(self):
+        msg = self._factory()
+        if self._cur and self._cur.get_content_type() == 'multipart/digest':
+            msg.set_default_type('message/rfc822')
+        if self._msgstack:
+            self._msgstack[-1].attach(msg)
+        self._msgstack.append(msg)
+        self._cur = msg
+        self._last = msg
+
+    def _pop_message(self):
+        retval = self._msgstack.pop()
+        if self._msgstack:
+            self._cur = self._msgstack[-1]
+        else:
+            self._cur = None
+        return retval
+
+    def _parsegen(self):
+        # Create a new message and start by parsing headers.
+        self._new_message()
+        headers = []
+        # Collect the headers, searching for a line that doesn't match the RFC
+        # 2822 header or continuation pattern (including an empty line).
+        for line in self._input:
+            if line is NeedMoreData:
+                yield NeedMoreData
+                continue
+            if not headerRE.match(line):
+                # If we saw the RFC defined header/body separator
+                # (i.e. newline), just throw it away. Otherwise the line is
+                # part of the body so push it back.
+                if not NLCRE.match(line):
+                    self._input.unreadline(line)
+                break
+            headers.append(line)
+        # Done with the headers, so parse them and figure out what we're
+        # supposed to see in the body of the message.
+        self._parse_headers(headers)
+        # Headers-only parsing is a backwards compatibility hack, which was
+        # necessary in the older parser, which could throw errors.  All
+        # remaining lines in the input are thrown into the message body.
+        if self._headersonly:
+            lines = []
+            while True:
+                line = self._input.readline()
+                if line is NeedMoreData:
+                    yield NeedMoreData
+                    continue
+                if line == '':
+                    break
+                lines.append(line)
+            self._cur.set_payload(EMPTYSTRING.join(lines))
+            return
+        if self._cur.get_content_type() == 'message/delivery-status':
+            # message/delivery-status contains blocks of headers separated by
+            # a blank line.  We'll represent each header block as a separate
+            # nested message object, but the processing is a bit different
+            # than standard message/* types because there is no body for the
+            # nested messages.  A blank line separates the subparts.
+            while True:
+                self._input.push_eof_matcher(NLCRE.match)
+                for retval in self._parsegen():
+                    if retval is NeedMoreData:
+                        yield NeedMoreData
+                        continue
+                    break
+                msg = self._pop_message()
+                # We need to pop the EOF matcher in order to tell if we're at
+                # the end of the current file, not the end of the last block
+                # of message headers.
+                self._input.pop_eof_matcher()
+                # The input stream must be sitting at the newline or at the
+                # EOF.  We want to see if we're at the end of this subpart, so
+                # first consume the blank line, then test the next line to see
+                # if we're at this subpart's EOF.
+                while True:
+                    line = self._input.readline()
+                    if line is NeedMoreData:
+                        yield NeedMoreData
+                        continue
+                    break
+                while True:
+                    line = self._input.readline()
+                    if line is NeedMoreData:
+                        yield NeedMoreData
+                        continue
+                    break
+                if line == '':
+                    break
+                # Not at EOF so this is a line we're going to need.
+                self._input.unreadline(line)
+            return
+        if self._cur.get_content_maintype() == 'message':
+            # The message claims to be a message/* type, then what follows is
+            # another RFC 2822 message.
+            for retval in self._parsegen():
+                if retval is NeedMoreData:
+                    yield NeedMoreData
+                    continue
+                break
+            self._pop_message()
+            return
+        if self._cur.get_content_maintype() == 'multipart':
+            boundary = self._cur.get_boundary()
+            if boundary is None:
+                # The message /claims/ to be a multipart but it has not
+                # defined a boundary.  That's a problem which we'll handle by
+                # reading everything until the EOF and marking the message as
+                # defective.
+                self._cur.defects.append(errors.NoBoundaryInMultipartDefect())
+                lines = []
+                for line in self._input:
+                    if line is NeedMoreData:
+                        yield NeedMoreData
+                        continue
+                    lines.append(line)
+                self._cur.set_payload(EMPTYSTRING.join(lines))
+                return
+            # Create a line match predicate which matches the inter-part
+            # boundary as well as the end-of-multipart boundary.  Don't push
+            # this onto the input stream until we've scanned past the
+            # preamble.
+            separator = '--' + boundary
+            boundaryre = re.compile(
+                '(?P<sep>' + re.escape(separator) +
+                r')(?P<end>--)?(?P<ws>[ \t]*)(?P<linesep>\r\n|\r|\n)?$')
+            capturing_preamble = True
+            preamble = []
+            linesep = False
+            while True:
+                line = self._input.readline()
+                if line is NeedMoreData:
+                    yield NeedMoreData
+                    continue
+                if line == '':
+                    break
+                mo = boundaryre.match(line)
+                if mo:
+                    # If we're looking at the end boundary, we're done with
+                    # this multipart.  If there was a newline at the end of
+                    # the closing boundary, then we need to initialize the
+                    # epilogue with the empty string (see below).
+                    if mo.group('end'):
+                        linesep = mo.group('linesep')
+                        break
+                    # We saw an inter-part boundary.  Were we in the preamble?
+                    if capturing_preamble:
+                        if preamble:
+                            # According to RFC 2046, the last newline belongs
+                            # to the boundary.
+                            lastline = preamble[-1]
+                            eolmo = NLCRE_eol.search(lastline)
+                            if eolmo:
+                                preamble[-1] = lastline[:-len(eolmo.group(0))]
+                            self._cur.preamble = EMPTYSTRING.join(preamble)
+                        capturing_preamble = False
+                        self._input.unreadline(line)
+                        continue
+                    # We saw a boundary separating two parts.  Consume any
+                    # multiple boundary lines that may be following.  Our
+                    # interpretation of RFC 2046 BNF grammar does not produce
+                    # body parts within such double boundaries.
+                    while True:
+                        line = self._input.readline()
+                        if line is NeedMoreData:
+                            yield NeedMoreData
+                            continue
+                        mo = boundaryre.match(line)
+                        if not mo:
+                            self._input.unreadline(line)
+                            break
+                    # Recurse to parse this subpart; the input stream points
+                    # at the subpart's first line.
+                    self._input.push_eof_matcher(boundaryre.match)
+                    for retval in self._parsegen():
+                        if retval is NeedMoreData:
+                            yield NeedMoreData
+                            continue
+                        break
+                    # Because of RFC 2046, the newline preceding the boundary
+                    # separator actually belongs to the boundary, not the
+                    # previous subpart's payload (or epilogue if the previous
+                    # part is a multipart).
+                    if self._last.get_content_maintype() == 'multipart':
+                        epilogue = self._last.epilogue
+                        if epilogue == '':
+                            self._last.epilogue = None
+                        elif epilogue is not None:
+                            mo = NLCRE_eol.search(epilogue)
+                            if mo:
+                                end = len(mo.group(0))
+                                self._last.epilogue = epilogue[:-end]
+                    else:
+                        payload = self._last.get_payload()
+                        if isinstance(payload, basestring):
+                            mo = NLCRE_eol.search(payload)
+                            if mo:
+                                payload = payload[:-len(mo.group(0))]
+                                self._last.set_payload(payload)
+                    self._input.pop_eof_matcher()
+                    self._pop_message()
+                    # Set the multipart up for newline cleansing, which will
+                    # happen if we're in a nested multipart.
+                    self._last = self._cur
+                else:
+                    # I think we must be in the preamble
+                    assert capturing_preamble
+                    preamble.append(line)
+            # We've seen either the EOF or the end boundary.  If we're still
+            # capturing the preamble, we never saw the start boundary.  Note
+            # that as a defect and store the captured text as the payload.
+            # Everything from here to the EOF is epilogue.
+            if capturing_preamble:
+                self._cur.defects.append(errors.StartBoundaryNotFoundDefect())
+                self._cur.set_payload(EMPTYSTRING.join(preamble))
+                epilogue = []
+                for line in self._input:
+                    if line is NeedMoreData:
+                        yield NeedMoreData
+                        continue
+                self._cur.epilogue = EMPTYSTRING.join(epilogue)
+                return
+            # If the end boundary ended in a newline, we'll need to make sure
+            # the epilogue isn't None
+            if linesep:
+                epilogue = ['']
+            else:
+                epilogue = []
+            for line in self._input:
+                if line is NeedMoreData:
+                    yield NeedMoreData
+                    continue
+                epilogue.append(line)
+            # Any CRLF at the front of the epilogue is not technically part of
+            # the epilogue.  Also, watch out for an empty string epilogue,
+            # which means a single newline.
+            if epilogue:
+                firstline = epilogue[0]
+                bolmo = NLCRE_bol.match(firstline)
+                if bolmo:
+                    epilogue[0] = firstline[len(bolmo.group(0)):]
+            self._cur.epilogue = EMPTYSTRING.join(epilogue)
+            return
+        # Otherwise, it's some non-multipart type, so the entire rest of the
+        # file contents becomes the payload.
+        lines = []
+        for line in self._input:
+            if line is NeedMoreData:
+                yield NeedMoreData
+                continue
+            lines.append(line)
+        self._cur.set_payload(EMPTYSTRING.join(lines))
+
+    def _parse_headers(self, lines):
+        # Passed a list of lines that make up the headers for the current msg
+        lastheader = ''
+        lastvalue = []
+        for lineno, line in enumerate(lines):
+            # Check for continuation
+            if line[0] in ' \t':
+                if not lastheader:
+                    # The first line of the headers was a continuation.  This
+                    # is illegal, so let's note the defect, store the illegal
+                    # line, and ignore it for purposes of headers.
+                    defect = errors.FirstHeaderLineIsContinuationDefect(line)
+                    self._cur.defects.append(defect)
+                    continue
+                lastvalue.append(line)
+                continue
+            if lastheader:
+                # XXX reconsider the joining of folded lines
+                lhdr = EMPTYSTRING.join(lastvalue)[:-1].rstrip('\r\n')
+                self._cur[lastheader] = lhdr
+                lastheader, lastvalue = '', []
+            # Check for envelope header, i.e. unix-from
+            if line.startswith('From '):
+                if lineno == 0:
+                    # Strip off the trailing newline
+                    mo = NLCRE_eol.search(line)
+                    if mo:
+                        line = line[:-len(mo.group(0))]
+                    self._cur.set_unixfrom(line)
+                    continue
+                elif lineno == len(lines) - 1:
+                    # Something looking like a unix-from at the end - it's
+                    # probably the first line of the body, so push back the
+                    # line and stop.
+                    self._input.unreadline(line)
+                    return
+                else:
+                    # Weirdly placed unix-from line.  Note this as a defect
+                    # and ignore it.
+                    defect = errors.MisplacedEnvelopeHeaderDefect(line)
+                    self._cur.defects.append(defect)
+                    continue
+            # Split the line on the colon separating field name from value.
+            i = line.find(':')
+            if i < 0:
+                defect = errors.MalformedHeaderDefect(line)
+                self._cur.defects.append(defect)
+                continue
+            lastheader = line[:i]
+            lastvalue = [line[i+1:].lstrip()]
+        # Done with all the lines, so handle the last header.
+        if lastheader:
+            # XXX reconsider the joining of folded lines
+            self._cur[lastheader] = EMPTYSTRING.join(lastvalue).rstrip('\r\n')
diff --git a/Lib/email/generator.py b/Lib/email/generator.py
new file mode 100644
index 0000000..6e7a515
--- /dev/null
+++ b/Lib/email/generator.py
@@ -0,0 +1,348 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Classes to generate plain text from a message object tree."""
+
+__all__ = ['Generator', 'DecodedGenerator']
+
+import re
+import sys
+import time
+import random
+import warnings
+
+from cStringIO import StringIO
+from email.header import Header
+
+UNDERSCORE = '_'
+NL = '\n'
+
+fcre = re.compile(r'^From ', re.MULTILINE)
+
+def _is8bitstring(s):
+    if isinstance(s, str):
+        try:
+            unicode(s, 'us-ascii')
+        except UnicodeError:
+            return True
+    return False
+
+
+
+class Generator:
+    """Generates output from a Message object tree.
+
+    This basic generator writes the message to the given file object as plain
+    text.
+    """
+    #
+    # Public interface
+    #
+
+    def __init__(self, outfp, mangle_from_=True, maxheaderlen=78):
+        """Create the generator for message flattening.
+
+        outfp is the output file-like object for writing the message to.  It
+        must have a write() method.
+
+        Optional mangle_from_ is a flag that, when True (the default), escapes
+        From_ lines in the body of the message by putting a `>' in front of
+        them.
+
+        Optional maxheaderlen specifies the longest length for a non-continued
+        header.  When a header line is longer (in characters, with tabs
+        expanded to 8 spaces) than maxheaderlen, the header will split as
+        defined in the Header class.  Set maxheaderlen to zero to disable
+        header wrapping.  The default is 78, as recommended (but not required)
+        by RFC 2822.
+        """
+        self._fp = outfp
+        self._mangle_from_ = mangle_from_
+        self._maxheaderlen = maxheaderlen
+
+    def write(self, s):
+        # Just delegate to the file object
+        self._fp.write(s)
+
+    def flatten(self, msg, unixfrom=False):
+        """Print the message object tree rooted at msg to the output file
+        specified when the Generator instance was created.
+
+        unixfrom is a flag that forces the printing of a Unix From_ delimiter
+        before the first object in the message tree.  If the original message
+        has no From_ delimiter, a `standard' one is crafted.  By default, this
+        is False to inhibit the printing of any From_ delimiter.
+
+        Note that for subobjects, no From_ line is printed.
+        """
+        if unixfrom:
+            ufrom = msg.get_unixfrom()
+            if not ufrom:
+                ufrom = 'From nobody ' + time.ctime(time.time())
+            print >> self._fp, ufrom
+        self._write(msg)
+
+    def clone(self, fp):
+        """Clone this generator with the exact same options."""
+        return self.__class__(fp, self._mangle_from_, self._maxheaderlen)
+
+    #
+    # Protected interface - undocumented ;/
+    #
+
+    def _write(self, msg):
+        # We can't write the headers yet because of the following scenario:
+        # say a multipart message includes the boundary string somewhere in
+        # its body.  We'd have to calculate the new boundary /before/ we write
+        # the headers so that we can write the correct Content-Type:
+        # parameter.
+        #
+        # The way we do this, so as to make the _handle_*() methods simpler,
+        # is to cache any subpart writes into a StringIO.  The we write the
+        # headers and the StringIO contents.  That way, subpart handlers can
+        # Do The Right Thing, and can still modify the Content-Type: header if
+        # necessary.
+        oldfp = self._fp
+        try:
+            self._fp = sfp = StringIO()
+            self._dispatch(msg)
+        finally:
+            self._fp = oldfp
+        # Write the headers.  First we see if the message object wants to
+        # handle that itself.  If not, we'll do it generically.
+        meth = getattr(msg, '_write_headers', None)
+        if meth is None:
+            self._write_headers(msg)
+        else:
+            meth(self)
+        self._fp.write(sfp.getvalue())
+
+    def _dispatch(self, msg):
+        # Get the Content-Type: for the message, then try to dispatch to
+        # self._handle_<maintype>_<subtype>().  If there's no handler for the
+        # full MIME type, then dispatch to self._handle_<maintype>().  If
+        # that's missing too, then dispatch to self._writeBody().
+        main = msg.get_content_maintype()
+        sub = msg.get_content_subtype()
+        specific = UNDERSCORE.join((main, sub)).replace('-', '_')
+        meth = getattr(self, '_handle_' + specific, None)
+        if meth is None:
+            generic = main.replace('-', '_')
+            meth = getattr(self, '_handle_' + generic, None)
+            if meth is None:
+                meth = self._writeBody
+        meth(msg)
+
+    #
+    # Default handlers
+    #
+
+    def _write_headers(self, msg):
+        for h, v in msg.items():
+            print >> self._fp, '%s:' % h,
+            if self._maxheaderlen == 0:
+                # Explicit no-wrapping
+                print >> self._fp, v
+            elif isinstance(v, Header):
+                # Header instances know what to do
+                print >> self._fp, v.encode()
+            elif _is8bitstring(v):
+                # If we have raw 8bit data in a byte string, we have no idea
+                # what the encoding is.  There is no safe way to split this
+                # string.  If it's ascii-subset, then we could do a normal
+                # ascii split, but if it's multibyte then we could break the
+                # string.  There's no way to know so the least harm seems to
+                # be to not split the string and risk it being too long.
+                print >> self._fp, v
+            else:
+                # Header's got lots of smarts, so use it.
+                print >> self._fp, Header(
+                    v, maxlinelen=self._maxheaderlen,
+                    header_name=h, continuation_ws='\t').encode()
+        # A blank line always separates headers from body
+        print >> self._fp
+
+    #
+    # Handlers for writing types and subtypes
+    #
+
+    def _handle_text(self, msg):
+        payload = msg.get_payload()
+        if payload is None:
+            return
+        if not isinstance(payload, basestring):
+            raise TypeError('string payload expected: %s' % type(payload))
+        if self._mangle_from_:
+            payload = fcre.sub('>From ', payload)
+        self._fp.write(payload)
+
+    # Default body handler
+    _writeBody = _handle_text
+
+    def _handle_multipart(self, msg):
+        # The trick here is to write out each part separately, merge them all
+        # together, and then make sure that the boundary we've chosen isn't
+        # present in the payload.
+        msgtexts = []
+        subparts = msg.get_payload()
+        if subparts is None:
+            subparts = []
+        elif isinstance(subparts, basestring):
+            # e.g. a non-strict parse of a message with no starting boundary.
+            self._fp.write(subparts)
+            return
+        elif not isinstance(subparts, list):
+            # Scalar payload
+            subparts = [subparts]
+        for part in subparts:
+            s = StringIO()
+            g = self.clone(s)
+            g.flatten(part, unixfrom=False)
+            msgtexts.append(s.getvalue())
+        # Now make sure the boundary we've selected doesn't appear in any of
+        # the message texts.
+        alltext = NL.join(msgtexts)
+        # BAW: What about boundaries that are wrapped in double-quotes?
+        boundary = msg.get_boundary(failobj=_make_boundary(alltext))
+        # If we had to calculate a new boundary because the body text
+        # contained that string, set the new boundary.  We don't do it
+        # unconditionally because, while set_boundary() preserves order, it
+        # doesn't preserve newlines/continuations in headers.  This is no big
+        # deal in practice, but turns out to be inconvenient for the unittest
+        # suite.
+        if msg.get_boundary() <> boundary:
+            msg.set_boundary(boundary)
+        # If there's a preamble, write it out, with a trailing CRLF
+        if msg.preamble is not None:
+            print >> self._fp, msg.preamble
+        # dash-boundary transport-padding CRLF
+        print >> self._fp, '--' + boundary
+        # body-part
+        if msgtexts:
+            self._fp.write(msgtexts.pop(0))
+        # *encapsulation
+        # --> delimiter transport-padding
+        # --> CRLF body-part
+        for body_part in msgtexts:
+            # delimiter transport-padding CRLF
+            print >> self._fp, '\n--' + boundary
+            # body-part
+            self._fp.write(body_part)
+        # close-delimiter transport-padding
+        self._fp.write('\n--' + boundary + '--')
+        if msg.epilogue is not None:
+            print >> self._fp
+            self._fp.write(msg.epilogue)
+
+    def _handle_message_delivery_status(self, msg):
+        # We can't just write the headers directly to self's file object
+        # because this will leave an extra newline between the last header
+        # block and the boundary.  Sigh.
+        blocks = []
+        for part in msg.get_payload():
+            s = StringIO()
+            g = self.clone(s)
+            g.flatten(part, unixfrom=False)
+            text = s.getvalue()
+            lines = text.split('\n')
+            # Strip off the unnecessary trailing empty line
+            if lines and lines[-1] == '':
+                blocks.append(NL.join(lines[:-1]))
+            else:
+                blocks.append(text)
+        # Now join all the blocks with an empty line.  This has the lovely
+        # effect of separating each block with an empty line, but not adding
+        # an extra one after the last one.
+        self._fp.write(NL.join(blocks))
+
+    def _handle_message(self, msg):
+        s = StringIO()
+        g = self.clone(s)
+        # The payload of a message/rfc822 part should be a multipart sequence
+        # of length 1.  The zeroth element of the list should be the Message
+        # object for the subpart.  Extract that object, stringify it, and
+        # write it out.
+        g.flatten(msg.get_payload(0), unixfrom=False)
+        self._fp.write(s.getvalue())
+
+
+
+_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
+
+class DecodedGenerator(Generator):
+    """Generator a text representation of a message.
+
+    Like the Generator base class, except that non-text parts are substituted
+    with a format string representing the part.
+    """
+    def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None):
+        """Like Generator.__init__() except that an additional optional
+        argument is allowed.
+
+        Walks through all subparts of a message.  If the subpart is of main
+        type `text', then it prints the decoded payload of the subpart.
+
+        Otherwise, fmt is a format string that is used instead of the message
+        payload.  fmt is expanded with the following keywords (in
+        %(keyword)s format):
+
+        type       : Full MIME type of the non-text part
+        maintype   : Main MIME type of the non-text part
+        subtype    : Sub-MIME type of the non-text part
+        filename   : Filename of the non-text part
+        description: Description associated with the non-text part
+        encoding   : Content transfer encoding of the non-text part
+
+        The default value for fmt is None, meaning
+
+        [Non-text (%(type)s) part of message omitted, filename %(filename)s]
+        """
+        Generator.__init__(self, outfp, mangle_from_, maxheaderlen)
+        if fmt is None:
+            self._fmt = _FMT
+        else:
+            self._fmt = fmt
+
+    def _dispatch(self, msg):
+        for part in msg.walk():
+            maintype = part.get_content_maintype()
+            if maintype == 'text':
+                print >> self, part.get_payload(decode=True)
+            elif maintype == 'multipart':
+                # Just skip this
+                pass
+            else:
+                print >> self, self._fmt % {
+                    'type'       : part.get_content_type(),
+                    'maintype'   : part.get_content_maintype(),
+                    'subtype'    : part.get_content_subtype(),
+                    'filename'   : part.get_filename('[no filename]'),
+                    'description': part.get('Content-Description',
+                                            '[no description]'),
+                    'encoding'   : part.get('Content-Transfer-Encoding',
+                                            '[no encoding]'),
+                    }
+
+
+
+# Helper
+_width = len(repr(sys.maxint-1))
+_fmt = '%%0%dd' % _width
+
+def _make_boundary(text=None):
+    # Craft a random boundary.  If text is given, ensure that the chosen
+    # boundary doesn't appear in the text.
+    token = random.randrange(sys.maxint)
+    boundary = ('=' * 15) + (_fmt % token) + '=='
+    if text is None:
+        return boundary
+    b = boundary
+    counter = 0
+    while True:
+        cre = re.compile('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
+        if not cre.search(text):
+            break
+        b = boundary + '.' + str(counter)
+        counter += 1
+    return b
diff --git a/Lib/email/header.py b/Lib/email/header.py
new file mode 100644
index 0000000..183c337
--- /dev/null
+++ b/Lib/email/header.py
@@ -0,0 +1,502 @@
+# Copyright (C) 2002-2006 Python Software Foundation
+# Author: Ben Gertzfield, Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Header encoding and decoding functionality."""
+
+__all__ = [
+    'Header',
+    'decode_header',
+    'make_header',
+    ]
+
+import re
+import binascii
+
+import email.quoprimime
+import email.base64mime
+
+from email.errors import HeaderParseError
+from email.charset import Charset
+
+NL = '\n'
+SPACE = ' '
+USPACE = u' '
+SPACE8 = ' ' * 8
+UEMPTYSTRING = u''
+
+MAXLINELEN = 76
+
+USASCII = Charset('us-ascii')
+UTF8 = Charset('utf-8')
+
+# Match encoded-word strings in the form =?charset?q?Hello_World?=
+ecre = re.compile(r'''
+  =\?                   # literal =?
+  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
+  \?                    # literal ?
+  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
+  \?                    # literal ?
+  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string
+  \?=                   # literal ?=
+  ''', re.VERBOSE | re.IGNORECASE)
+
+# Field name regexp, including trailing colon, but not separating whitespace,
+# according to RFC 2822.  Character range is from tilde to exclamation mark.
+# For use with .match()
+fcre = re.compile(r'[\041-\176]+:$')
+
+
+
+# Helpers
+_max_append = email.quoprimime._max_append
+
+
+
+def decode_header(header):
+    """Decode a message header value without converting charset.
+
+    Returns a list of (decoded_string, charset) pairs containing each of the
+    decoded parts of the header.  Charset is None for non-encoded parts of the
+    header, otherwise a lower-case string containing the name of the character
+    set specified in the encoded string.
+
+    An email.Errors.HeaderParseError may be raised when certain decoding error
+    occurs (e.g. a base64 decoding exception).
+    """
+    # If no encoding, just return the header
+    header = str(header)
+    if not ecre.search(header):
+        return [(header, None)]
+    decoded = []
+    dec = ''
+    for line in header.splitlines():
+        # This line might not have an encoding in it
+        if not ecre.search(line):
+            decoded.append((line, None))
+            continue
+        parts = ecre.split(line)
+        while parts:
+            unenc = parts.pop(0).strip()
+            if unenc:
+                # Should we continue a long line?
+                if decoded and decoded[-1][1] is None:
+                    decoded[-1] = (decoded[-1][0] + SPACE + unenc, None)
+                else:
+                    decoded.append((unenc, None))
+            if parts:
+                charset, encoding = [s.lower() for s in parts[0:2]]
+                encoded = parts[2]
+                dec = None
+                if encoding == 'q':
+                    dec = email.quoprimime.header_decode(encoded)
+                elif encoding == 'b':
+                    try:
+                        dec = email.base64mime.decode(encoded)
+                    except binascii.Error:
+                        # Turn this into a higher level exception.  BAW: Right
+                        # now we throw the lower level exception away but
+                        # when/if we get exception chaining, we'll preserve it.
+                        raise HeaderParseError
+                if dec is None:
+                    dec = encoded
+
+                if decoded and decoded[-1][1] == charset:
+                    decoded[-1] = (decoded[-1][0] + dec, decoded[-1][1])
+                else:
+                    decoded.append((dec, charset))
+            del parts[0:3]
+    return decoded
+
+
+
+def make_header(decoded_seq, maxlinelen=None, header_name=None,
+                continuation_ws=' '):
+    """Create a Header from a sequence of pairs as returned by decode_header()
+
+    decode_header() takes a header value string and returns a sequence of
+    pairs of the format (decoded_string, charset) where charset is the string
+    name of the character set.
+
+    This function takes one of those sequence of pairs and returns a Header
+    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
+    the Header constructor.
+    """
+    h = Header(maxlinelen=maxlinelen, header_name=header_name,
+               continuation_ws=continuation_ws)
+    for s, charset in decoded_seq:
+        # None means us-ascii but we can simply pass it on to h.append()
+        if charset is not None and not isinstance(charset, Charset):
+            charset = Charset(charset)
+        h.append(s, charset)
+    return h
+
+
+
+class Header:
+    def __init__(self, s=None, charset=None,
+                 maxlinelen=None, header_name=None,
+                 continuation_ws=' ', errors='strict'):
+        """Create a MIME-compliant header that can contain many character sets.
+
+        Optional s is the initial header value.  If None, the initial header
+        value is not set.  You can later append to the header with .append()
+        method calls.  s may be a byte string or a Unicode string, but see the
+        .append() documentation for semantics.
+
+        Optional charset serves two purposes: it has the same meaning as the
+        charset argument to the .append() method.  It also sets the default
+        character set for all subsequent .append() calls that omit the charset
+        argument.  If charset is not provided in the constructor, the us-ascii
+        charset is used both as s's initial charset and as the default for
+        subsequent .append() calls.
+
+        The maximum line length can be specified explicit via maxlinelen.  For
+        splitting the first line to a shorter value (to account for the field
+        header which isn't included in s, e.g. `Subject') pass in the name of
+        the field in header_name.  The default maxlinelen is 76.
+
+        continuation_ws must be RFC 2822 compliant folding whitespace (usually
+        either a space or a hard tab) which will be prepended to continuation
+        lines.
+
+        errors is passed through to the .append() call.
+        """
+        if charset is None:
+            charset = USASCII
+        if not isinstance(charset, Charset):
+            charset = Charset(charset)
+        self._charset = charset
+        self._continuation_ws = continuation_ws
+        cws_expanded_len = len(continuation_ws.replace('\t', SPACE8))
+        # BAW: I believe `chunks' and `maxlinelen' should be non-public.
+        self._chunks = []
+        if s is not None:
+            self.append(s, charset, errors)
+        if maxlinelen is None:
+            maxlinelen = MAXLINELEN
+        if header_name is None:
+            # We don't know anything about the field header so the first line
+            # is the same length as subsequent lines.
+            self._firstlinelen = maxlinelen
+        else:
+            # The first line should be shorter to take into account the field
+            # header.  Also subtract off 2 extra for the colon and space.
+            self._firstlinelen = maxlinelen - len(header_name) - 2
+        # Second and subsequent lines should subtract off the length in
+        # columns of the continuation whitespace prefix.
+        self._maxlinelen = maxlinelen - cws_expanded_len
+
+    def __str__(self):
+        """A synonym for self.encode()."""
+        return self.encode()
+
+    def __unicode__(self):
+        """Helper for the built-in unicode function."""
+        uchunks = []
+        lastcs = None
+        for s, charset in self._chunks:
+            # We must preserve spaces between encoded and non-encoded word
+            # boundaries, which means for us we need to add a space when we go
+            # from a charset to None/us-ascii, or from None/us-ascii to a
+            # charset.  Only do this for the second and subsequent chunks.
+            nextcs = charset
+            if uchunks:
+                if lastcs not in (None, 'us-ascii'):
+                    if nextcs in (None, 'us-ascii'):
+                        uchunks.append(USPACE)
+                        nextcs = None
+                elif nextcs not in (None, 'us-ascii'):
+                    uchunks.append(USPACE)
+            lastcs = nextcs
+            uchunks.append(unicode(s, str(charset)))
+        return UEMPTYSTRING.join(uchunks)
+
+    # Rich comparison operators for equality only.  BAW: does it make sense to
+    # have or explicitly disable <, <=, >, >= operators?
+    def __eq__(self, other):
+        # other may be a Header or a string.  Both are fine so coerce
+        # ourselves to a string, swap the args and do another comparison.
+        return other == self.encode()
+
+    def __ne__(self, other):
+        return not self == other
+
+    def append(self, s, charset=None, errors='strict'):
+        """Append a string to the MIME header.
+
+        Optional charset, if given, should be a Charset instance or the name
+        of a character set (which will be converted to a Charset instance).  A
+        value of None (the default) means that the charset given in the
+        constructor is used.
+
+        s may be a byte string or a Unicode string.  If it is a byte string
+        (i.e. isinstance(s, str) is true), then charset is the encoding of
+        that byte string, and a UnicodeError will be raised if the string
+        cannot be decoded with that charset.  If s is a Unicode string, then
+        charset is a hint specifying the character set of the characters in
+        the string.  In this case, when producing an RFC 2822 compliant header
+        using RFC 2047 rules, the Unicode string will be encoded using the
+        following charsets in order: us-ascii, the charset hint, utf-8.  The
+        first character set not to provoke a UnicodeError is used.
+
+        Optional `errors' is passed as the third argument to any unicode() or
+        ustr.encode() call.
+        """
+        if charset is None:
+            charset = self._charset
+        elif not isinstance(charset, Charset):
+            charset = Charset(charset)
+        # If the charset is our faux 8bit charset, leave the string unchanged
+        if charset <> '8bit':
+            # We need to test that the string can be converted to unicode and
+            # back to a byte string, given the input and output codecs of the
+            # charset.
+            if isinstance(s, str):
+                # Possibly raise UnicodeError if the byte string can't be
+                # converted to a unicode with the input codec of the charset.
+                incodec = charset.input_codec or 'us-ascii'
+                ustr = unicode(s, incodec, errors)
+                # Now make sure that the unicode could be converted back to a
+                # byte string with the output codec, which may be different
+                # than the iput coded.  Still, use the original byte string.
+                outcodec = charset.output_codec or 'us-ascii'
+                ustr.encode(outcodec, errors)
+            elif isinstance(s, unicode):
+                # Now we have to be sure the unicode string can be converted
+                # to a byte string with a reasonable output codec.  We want to
+                # use the byte string in the chunk.
+                for charset in USASCII, charset, UTF8:
+                    try:
+                        outcodec = charset.output_codec or 'us-ascii'
+                        s = s.encode(outcodec, errors)
+                        break
+                    except UnicodeError:
+                        pass
+                else:
+                    assert False, 'utf-8 conversion failed'
+        self._chunks.append((s, charset))
+
+    def _split(self, s, charset, maxlinelen, splitchars):
+        # Split up a header safely for use with encode_chunks.
+        splittable = charset.to_splittable(s)
+        encoded = charset.from_splittable(splittable, True)
+        elen = charset.encoded_header_len(encoded)
+        # If the line's encoded length first, just return it
+        if elen <= maxlinelen:
+            return [(encoded, charset)]
+        # If we have undetermined raw 8bit characters sitting in a byte
+        # string, we really don't know what the right thing to do is.  We
+        # can't really split it because it might be multibyte data which we
+        # could break if we split it between pairs.  The least harm seems to
+        # be to not split the header at all, but that means they could go out
+        # longer than maxlinelen.
+        if charset == '8bit':
+            return [(s, charset)]
+        # BAW: I'm not sure what the right test here is.  What we're trying to
+        # do is be faithful to RFC 2822's recommendation that ($2.2.3):
+        #
+        # "Note: Though structured field bodies are defined in such a way that
+        #  folding can take place between many of the lexical tokens (and even
+        #  within some of the lexical tokens), folding SHOULD be limited to
+        #  placing the CRLF at higher-level syntactic breaks."
+        #
+        # For now, I can only imagine doing this when the charset is us-ascii,
+        # although it's possible that other charsets may also benefit from the
+        # higher-level syntactic breaks.
+        elif charset == 'us-ascii':
+            return self._split_ascii(s, charset, maxlinelen, splitchars)
+        # BAW: should we use encoded?
+        elif elen == len(s):
+            # We can split on _maxlinelen boundaries because we know that the
+            # encoding won't change the size of the string
+            splitpnt = maxlinelen
+            first = charset.from_splittable(splittable[:splitpnt], False)
+            last = charset.from_splittable(splittable[splitpnt:], False)
+        else:
+            # Binary search for split point
+            first, last = _binsplit(splittable, charset, maxlinelen)
+        # first is of the proper length so just wrap it in the appropriate
+        # chrome.  last must be recursively split.
+        fsplittable = charset.to_splittable(first)
+        fencoded = charset.from_splittable(fsplittable, True)
+        chunk = [(fencoded, charset)]
+        return chunk + self._split(last, charset, self._maxlinelen, splitchars)
+
+    def _split_ascii(self, s, charset, firstlen, splitchars):
+        chunks = _split_ascii(s, firstlen, self._maxlinelen,
+                              self._continuation_ws, splitchars)
+        return zip(chunks, [charset]*len(chunks))
+
+    def _encode_chunks(self, newchunks, maxlinelen):
+        # MIME-encode a header with many different charsets and/or encodings.
+        #
+        # Given a list of pairs (string, charset), return a MIME-encoded
+        # string suitable for use in a header field.  Each pair may have
+        # different charsets and/or encodings, and the resulting header will
+        # accurately reflect each setting.
+        #
+        # Each encoding can be email.Utils.QP (quoted-printable, for
+        # ASCII-like character sets like iso-8859-1), email.Utils.BASE64
+        # (Base64, for non-ASCII like character sets like KOI8-R and
+        # iso-2022-jp), or None (no encoding).
+        #
+        # Each pair will be represented on a separate line; the resulting
+        # string will be in the format:
+        #
+        # =?charset1?q?Mar=EDa_Gonz=E1lez_Alonso?=\n
+        #  =?charset2?b?SvxyZ2VuIEL2aW5n?="
+        chunks = []
+        for header, charset in newchunks:
+            if not header:
+                continue
+            if charset is None or charset.header_encoding is None:
+                s = header
+            else:
+                s = charset.header_encode(header)
+            # Don't add more folding whitespace than necessary
+            if chunks and chunks[-1].endswith(' '):
+                extra = ''
+            else:
+                extra = ' '
+            _max_append(chunks, s, maxlinelen, extra)
+        joiner = NL + self._continuation_ws
+        return joiner.join(chunks)
+
+    def encode(self, splitchars=';, '):
+        """Encode a message header into an RFC-compliant format.
+
+        There are many issues involved in converting a given string for use in
+        an email header.  Only certain character sets are readable in most
+        email clients, and as header strings can only contain a subset of
+        7-bit ASCII, care must be taken to properly convert and encode (with
+        Base64 or quoted-printable) header strings.  In addition, there is a
+        75-character length limit on any given encoded header field, so
+        line-wrapping must be performed, even with double-byte character sets.
+
+        This method will do its best to convert the string to the correct
+        character set used in email, and encode and line wrap it safely with
+        the appropriate scheme for that character set.
+
+        If the given charset is not known or an error occurs during
+        conversion, this function will return the header untouched.
+
+        Optional splitchars is a string containing characters to split long
+        ASCII lines on, in rough support of RFC 2822's `highest level
+        syntactic breaks'.  This doesn't affect RFC 2047 encoded lines.
+        """
+        newchunks = []
+        maxlinelen = self._firstlinelen
+        lastlen = 0
+        for s, charset in self._chunks:
+            # The first bit of the next chunk should be just long enough to
+            # fill the next line.  Don't forget the space separating the
+            # encoded words.
+            targetlen = maxlinelen - lastlen - 1
+            if targetlen < charset.encoded_header_len(''):
+                # Stick it on the next line
+                targetlen = maxlinelen
+            newchunks += self._split(s, charset, targetlen, splitchars)
+            lastchunk, lastcharset = newchunks[-1]
+            lastlen = lastcharset.encoded_header_len(lastchunk)
+        return self._encode_chunks(newchunks, maxlinelen)
+
+
+
+def _split_ascii(s, firstlen, restlen, continuation_ws, splitchars):
+    lines = []
+    maxlen = firstlen
+    for line in s.splitlines():
+        # Ignore any leading whitespace (i.e. continuation whitespace) already
+        # on the line, since we'll be adding our own.
+        line = line.lstrip()
+        if len(line) < maxlen:
+            lines.append(line)
+            maxlen = restlen
+            continue
+        # Attempt to split the line at the highest-level syntactic break
+        # possible.  Note that we don't have a lot of smarts about field
+        # syntax; we just try to break on semi-colons, then commas, then
+        # whitespace.
+        for ch in splitchars:
+            if ch in line:
+                break
+        else:
+            # There's nothing useful to split the line on, not even spaces, so
+            # just append this line unchanged
+            lines.append(line)
+            maxlen = restlen
+            continue
+        # Now split the line on the character plus trailing whitespace
+        cre = re.compile(r'%s\s*' % ch)
+        if ch in ';,':
+            eol = ch
+        else:
+            eol = ''
+        joiner = eol + ' '
+        joinlen = len(joiner)
+        wslen = len(continuation_ws.replace('\t', SPACE8))
+        this = []
+        linelen = 0
+        for part in cre.split(line):
+            curlen = linelen + max(0, len(this)-1) * joinlen
+            partlen = len(part)
+            onfirstline = not lines
+            # We don't want to split after the field name, if we're on the
+            # first line and the field name is present in the header string.
+            if ch == ' ' and onfirstline and \
+                   len(this) == 1 and fcre.match(this[0]):
+                this.append(part)
+                linelen += partlen
+            elif curlen + partlen > maxlen:
+                if this:
+                    lines.append(joiner.join(this) + eol)
+                # If this part is longer than maxlen and we aren't already
+                # splitting on whitespace, try to recursively split this line
+                # on whitespace.
+                if partlen > maxlen and ch <> ' ':
+                    subl = _split_ascii(part, maxlen, restlen,
+                                        continuation_ws, ' ')
+                    lines.extend(subl[:-1])
+                    this = [subl[-1]]
+                else:
+                    this = [part]
+                linelen = wslen + len(this[-1])
+                maxlen = restlen
+            else:
+                this.append(part)
+                linelen += partlen
+        # Put any left over parts on a line by themselves
+        if this:
+            lines.append(joiner.join(this))
+    return lines
+
+
+
+def _binsplit(splittable, charset, maxlinelen):
+    i = 0
+    j = len(splittable)
+    while i < j:
+        # Invariants:
+        # 1. splittable[:k] fits for all k <= i (note that we *assume*,
+        #    at the start, that splittable[:0] fits).
+        # 2. splittable[:k] does not fit for any k > j (at the start,
+        #    this means we shouldn't look at any k > len(splittable)).
+        # 3. We don't know about splittable[:k] for k in i+1..j.
+        # 4. We want to set i to the largest k that fits, with i <= k <= j.
+        #
+        m = (i+j+1) >> 1  # ceiling((i+j)/2); i < m <= j
+        chunk = charset.from_splittable(splittable[:m], True)
+        chunklen = charset.encoded_header_len(chunk)
+        if chunklen <= maxlinelen:
+            # m is acceptable, so is a new lower bound.
+            i = m
+        else:
+            # m is not acceptable, so final i must be < m.
+            j = m - 1
+    # i == j.  Invariant #1 implies that splittable[:i] fits, and
+    # invariant #2 implies that splittable[:i+1] does not fit, so i
+    # is what we're looking for.
+    first = charset.from_splittable(splittable[:i], False)
+    last  = charset.from_splittable(splittable[i:], False)
+    return first, last
diff --git a/Lib/email/iterators.py b/Lib/email/iterators.py
new file mode 100644
index 0000000..e99f228
--- /dev/null
+++ b/Lib/email/iterators.py
@@ -0,0 +1,73 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Various types of useful iterators and generators."""
+
+__all__ = [
+    'body_line_iterator',
+    'typed_subpart_iterator',
+    'walk',
+    # Do not include _structure() since it's part of the debugging API.
+    ]
+
+import sys
+from cStringIO import StringIO
+
+
+
+# This function will become a method of the Message class
+def walk(self):
+    """Walk over the message tree, yielding each subpart.
+
+    The walk is performed in depth-first order.  This method is a
+    generator.
+    """
+    yield self
+    if self.is_multipart():
+        for subpart in self.get_payload():
+            for subsubpart in subpart.walk():
+                yield subsubpart
+
+
+
+# These two functions are imported into the Iterators.py interface module.
+def body_line_iterator(msg, decode=False):
+    """Iterate over the parts, returning string payloads line-by-line.
+
+    Optional decode (default False) is passed through to .get_payload().
+    """
+    for subpart in msg.walk():
+        payload = subpart.get_payload(decode=decode)
+        if isinstance(payload, basestring):
+            for line in StringIO(payload):
+                yield line
+
+
+def typed_subpart_iterator(msg, maintype='text', subtype=None):
+    """Iterate over the subparts with a given MIME type.
+
+    Use `maintype' as the main MIME type to match against; this defaults to
+    "text".  Optional `subtype' is the MIME subtype to match against; if
+    omitted, only the main type is matched.
+    """
+    for subpart in msg.walk():
+        if subpart.get_content_maintype() == maintype:
+            if subtype is None or subpart.get_content_subtype() == subtype:
+                yield subpart
+
+
+
+def _structure(msg, fp=None, level=0, include_default=False):
+    """A handy debugging aid"""
+    if fp is None:
+        fp = sys.stdout
+    tab = ' ' * (level * 4)
+    print >> fp, tab + msg.get_content_type(),
+    if include_default:
+        print >> fp, '[%s]' % msg.get_default_type()
+    else:
+        print >> fp
+    if msg.is_multipart():
+        for subpart in msg.get_payload():
+            _structure(subpart, fp, level+1, include_default)
diff --git a/Lib/email/message.py b/Lib/email/message.py
new file mode 100644
index 0000000..50d90b4
--- /dev/null
+++ b/Lib/email/message.py
@@ -0,0 +1,773 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Basic message object for the email package object model."""
+
+__all__ = ['Message']
+
+import re
+import uu
+import binascii
+import warnings
+from cStringIO import StringIO
+
+# Intrapackage imports
+import email.charset
+from email import utils
+from email import errors
+
+SEMISPACE = '; '
+
+# Regular expression used to split header parameters.  BAW: this may be too
+# simple.  It isn't strictly RFC 2045 (section 5.1) compliant, but it catches
+# most headers found in the wild.  We may eventually need a full fledged
+# parser eventually.
+paramre = re.compile(r'\s*;\s*')
+# Regular expression that matches `special' characters in parameters, the
+# existance of which force quoting of the parameter value.
+tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
+
+
+
+# Helper functions
+def _formatparam(param, value=None, quote=True):
+    """Convenience function to format and return a key=value pair.
+
+    This will quote the value if needed or if quote is true.
+    """
+    if value is not None and len(value) > 0:
+        # A tuple is used for RFC 2231 encoded parameter values where items
+        # are (charset, language, value).  charset is a string, not a Charset
+        # instance.
+        if isinstance(value, tuple):
+            # Encode as per RFC 2231
+            param += '*'
+            value = utils.encode_rfc2231(value[2], value[0], value[1])
+        # BAW: Please check this.  I think that if quote is set it should
+        # force quoting even if not necessary.
+        if quote or tspecials.search(value):
+            return '%s="%s"' % (param, utils.quote(value))
+        else:
+            return '%s=%s' % (param, value)
+    else:
+        return param
+
+def _parseparam(s):
+    plist = []
+    while s[:1] == ';':
+        s = s[1:]
+        end = s.find(';')
+        while end > 0 and s.count('"', 0, end) % 2:
+            end = s.find(';', end + 1)
+        if end < 0:
+            end = len(s)
+        f = s[:end]
+        if '=' in f:
+            i = f.index('=')
+            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
+        plist.append(f.strip())
+        s = s[end:]
+    return plist
+
+
+def _unquotevalue(value):
+    # This is different than utils.collapse_rfc2231_value() because it doesn't
+    # try to convert the value to a unicode.  Message.get_param() and
+    # Message.get_params() are both currently defined to return the tuple in
+    # the face of RFC 2231 parameters.
+    if isinstance(value, tuple):
+        return value[0], value[1], utils.unquote(value[2])
+    else:
+        return utils.unquote(value)
+
+
+
+class Message:
+    """Basic message object.
+
+    A message object is defined as something that has a bunch of RFC 2822
+    headers and a payload.  It may optionally have an envelope header
+    (a.k.a. Unix-From or From_ header).  If the message is a container (i.e. a
+    multipart or a message/rfc822), then the payload is a list of Message
+    objects, otherwise it is a string.
+
+    Message objects implement part of the `mapping' interface, which assumes
+    there is exactly one occurrance of the header per message.  Some headers
+    do in fact appear multiple times (e.g. Received) and for those headers,
+    you must use the explicit API to set or get all the headers.  Not all of
+    the mapping methods are implemented.
+    """
+    def __init__(self):
+        self._headers = []
+        self._unixfrom = None
+        self._payload = None
+        self._charset = None
+        # Defaults for multipart messages
+        self.preamble = self.epilogue = None
+        self.defects = []
+        # Default content type
+        self._default_type = 'text/plain'
+
+    def __str__(self):
+        """Return the entire formatted message as a string.
+        This includes the headers, body, and envelope header.
+        """
+        return self.as_string(unixfrom=True)
+
+    def as_string(self, unixfrom=False):
+        """Return the entire formatted message as a string.
+        Optional `unixfrom' when True, means include the Unix From_ envelope
+        header.
+
+        This is a convenience method and may not generate the message exactly
+        as you intend because by default it mangles lines that begin with
+        "From ".  For more flexibility, use the flatten() method of a
+        Generator instance.
+        """
+        from email.Generator import Generator
+        fp = StringIO()
+        g = Generator(fp)
+        g.flatten(self, unixfrom=unixfrom)
+        return fp.getvalue()
+
+    def is_multipart(self):
+        """Return True if the message consists of multiple parts."""
+        return isinstance(self._payload, list)
+
+    #
+    # Unix From_ line
+    #
+    def set_unixfrom(self, unixfrom):
+        self._unixfrom = unixfrom
+
+    def get_unixfrom(self):
+        return self._unixfrom
+
+    #
+    # Payload manipulation.
+    #
+    def attach(self, payload):
+        """Add the given payload to the current payload.
+
+        The current payload will always be a list of objects after this method
+        is called.  If you want to set the payload to a scalar object, use
+        set_payload() instead.
+        """
+        if self._payload is None:
+            self._payload = [payload]
+        else:
+            self._payload.append(payload)
+
+    def get_payload(self, i=None, decode=False):
+        """Return a reference to the payload.
+
+        The payload will either be a list object or a string.  If you mutate
+        the list object, you modify the message's payload in place.  Optional
+        i returns that index into the payload.
+
+        Optional decode is a flag indicating whether the payload should be
+        decoded or not, according to the Content-Transfer-Encoding header
+        (default is False).
+
+        When True and the message is not a multipart, the payload will be
+        decoded if this header's value is `quoted-printable' or `base64'.  If
+        some other encoding is used, or the header is missing, or if the
+        payload has bogus data (i.e. bogus base64 or uuencoded data), the
+        payload is returned as-is.
+
+        If the message is a multipart and the decode flag is True, then None
+        is returned.
+        """
+        if i is None:
+            payload = self._payload
+        elif not isinstance(self._payload, list):
+            raise TypeError('Expected list, got %s' % type(self._payload))
+        else:
+            payload = self._payload[i]
+        if decode:
+            if self.is_multipart():
+                return None
+            cte = self.get('content-transfer-encoding', '').lower()
+            if cte == 'quoted-printable':
+                return utils._qdecode(payload)
+            elif cte == 'base64':
+                try:
+                    return utils._bdecode(payload)
+                except binascii.Error:
+                    # Incorrect padding
+                    return payload
+            elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
+                sfp = StringIO()
+                try:
+                    uu.decode(StringIO(payload+'\n'), sfp, quiet=True)
+                    payload = sfp.getvalue()
+                except uu.Error:
+                    # Some decoding problem
+                    return payload
+        # Everything else, including encodings with 8bit or 7bit are returned
+        # unchanged.
+        return payload
+
+    def set_payload(self, payload, charset=None):
+        """Set the payload to the given value.
+
+        Optional charset sets the message's default character set.  See
+        set_charset() for details.
+        """
+        self._payload = payload
+        if charset is not None:
+            self.set_charset(charset)
+
+    def set_charset(self, charset):
+        """Set the charset of the payload to a given character set.
+
+        charset can be a Charset instance, a string naming a character set, or
+        None.  If it is a string it will be converted to a Charset instance.
+        If charset is None, the charset parameter will be removed from the
+        Content-Type field.  Anything else will generate a TypeError.
+
+        The message will be assumed to be of type text/* encoded with
+        charset.input_charset.  It will be converted to charset.output_charset
+        and encoded properly, if needed, when generating the plain text
+        representation of the message.  MIME headers (MIME-Version,
+        Content-Type, Content-Transfer-Encoding) will be added as needed.
+
+        """
+        if charset is None:
+            self.del_param('charset')
+            self._charset = None
+            return
+        if isinstance(charset, str):
+            charset = email.charset.Charset(charset)
+        if not isinstance(charset, email.charset.Charset):
+            raise TypeError(charset)
+        # BAW: should we accept strings that can serve as arguments to the
+        # Charset constructor?
+        self._charset = charset
+        if not self.has_key('MIME-Version'):
+            self.add_header('MIME-Version', '1.0')
+        if not self.has_key('Content-Type'):
+            self.add_header('Content-Type', 'text/plain',
+                            charset=charset.get_output_charset())
+        else:
+            self.set_param('charset', charset.get_output_charset())
+        if str(charset) <> charset.get_output_charset():
+            self._payload = charset.body_encode(self._payload)
+        if not self.has_key('Content-Transfer-Encoding'):
+            cte = charset.get_body_encoding()
+            try:
+                cte(self)
+            except TypeError:
+                self._payload = charset.body_encode(self._payload)
+                self.add_header('Content-Transfer-Encoding', cte)
+
+    def get_charset(self):
+        """Return the Charset instance associated with the message's payload.
+        """
+        return self._charset
+
+    #
+    # MAPPING INTERFACE (partial)
+    #
+    def __len__(self):
+        """Return the total number of headers, including duplicates."""
+        return len(self._headers)
+
+    def __getitem__(self, name):
+        """Get a header value.
+
+        Return None if the header is missing instead of raising an exception.
+
+        Note that if the header appeared multiple times, exactly which
+        occurrance gets returned is undefined.  Use get_all() to get all
+        the values matching a header field name.
+        """
+        return self.get(name)
+
+    def __setitem__(self, name, val):
+        """Set the value of a header.
+
+        Note: this does not overwrite an existing header with the same field
+        name.  Use __delitem__() first to delete any existing headers.
+        """
+        self._headers.append((name, val))
+
+    def __delitem__(self, name):
+        """Delete all occurrences of a header, if present.
+
+        Does not raise an exception if the header is missing.
+        """
+        name = name.lower()
+        newheaders = []
+        for k, v in self._headers:
+            if k.lower() <> name:
+                newheaders.append((k, v))
+        self._headers = newheaders
+
+    def __contains__(self, name):
+        return name.lower() in [k.lower() for k, v in self._headers]
+
+    def has_key(self, name):
+        """Return true if the message contains the header."""
+        missing = object()
+        return self.get(name, missing) is not missing
+
+    def keys(self):
+        """Return a list of all the message's header field names.
+
+        These will be sorted in the order they appeared in the original
+        message, or were added to the message, and may contain duplicates.
+        Any fields deleted and re-inserted are always appended to the header
+        list.
+        """
+        return [k for k, v in self._headers]
+
+    def values(self):
+        """Return a list of all the message's header values.
+
+        These will be sorted in the order they appeared in the original
+        message, or were added to the message, and may contain duplicates.
+        Any fields deleted and re-inserted are always appended to the header
+        list.
+        """
+        return [v for k, v in self._headers]
+
+    def items(self):
+        """Get all the message's header fields and values.
+
+        These will be sorted in the order they appeared in the original
+        message, or were added to the message, and may contain duplicates.
+        Any fields deleted and re-inserted are always appended to the header
+        list.
+        """
+        return self._headers[:]
+
+    def get(self, name, failobj=None):
+        """Get a header value.
+
+        Like __getitem__() but return failobj instead of None when the field
+        is missing.
+        """
+        name = name.lower()
+        for k, v in self._headers:
+            if k.lower() == name:
+                return v
+        return failobj
+
+    #
+    # Additional useful stuff
+    #
+
+    def get_all(self, name, failobj=None):
+        """Return a list of all the values for the named field.
+
+        These will be sorted in the order they appeared in the original
+        message, and may contain duplicates.  Any fields deleted and
+        re-inserted are always appended to the header list.
+
+        If no such fields exist, failobj is returned (defaults to None).
+        """
+        values = []
+        name = name.lower()
+        for k, v in self._headers:
+            if k.lower() == name:
+                values.append(v)
+        if not values:
+            return failobj
+        return values
+
+    def add_header(self, _name, _value, **_params):
+        """Extended header setting.
+
+        name is the header field to add.  keyword arguments can be used to set
+        additional parameters for the header field, with underscores converted
+        to dashes.  Normally the parameter will be added as key="value" unless
+        value is None, in which case only the key will be added.
+
+        Example:
+
+        msg.add_header('content-disposition', 'attachment', filename='bud.gif')
+        """
+        parts = []
+        for k, v in _params.items():
+            if v is None:
+                parts.append(k.replace('_', '-'))
+            else:
+                parts.append(_formatparam(k.replace('_', '-'), v))
+        if _value is not None:
+            parts.insert(0, _value)
+        self._headers.append((_name, SEMISPACE.join(parts)))
+
+    def replace_header(self, _name, _value):
+        """Replace a header.
+
+        Replace the first matching header found in the message, retaining
+        header order and case.  If no matching header was found, a KeyError is
+        raised.
+        """
+        _name = _name.lower()
+        for i, (k, v) in zip(range(len(self._headers)), self._headers):
+            if k.lower() == _name:
+                self._headers[i] = (k, _value)
+                break
+        else:
+            raise KeyError(_name)
+
+    #
+    # Use these three methods instead of the three above.
+    #
+
+    def get_content_type(self):
+        """Return the message's content type.
+
+        The returned string is coerced to lower case of the form
+        `maintype/subtype'.  If there was no Content-Type header in the
+        message, the default type as given by get_default_type() will be
+        returned.  Since according to RFC 2045, messages always have a default
+        type this will always return a value.
+
+        RFC 2045 defines a message's default type to be text/plain unless it
+        appears inside a multipart/digest container, in which case it would be
+        message/rfc822.
+        """
+        missing = object()
+        value = self.get('content-type', missing)
+        if value is missing:
+            # This should have no parameters
+            return self.get_default_type()
+        ctype = paramre.split(value)[0].lower().strip()
+        # RFC 2045, section 5.2 says if its invalid, use text/plain
+        if ctype.count('/') <> 1:
+            return 'text/plain'
+        return ctype
+
+    def get_content_maintype(self):
+        """Return the message's main content type.
+
+        This is the `maintype' part of the string returned by
+        get_content_type().
+        """
+        ctype = self.get_content_type()
+        return ctype.split('/')[0]
+
+    def get_content_subtype(self):
+        """Returns the message's sub-content type.
+
+        This is the `subtype' part of the string returned by
+        get_content_type().
+        """
+        ctype = self.get_content_type()
+        return ctype.split('/')[1]
+
+    def get_default_type(self):
+        """Return the `default' content type.
+
+        Most messages have a default content type of text/plain, except for
+        messages that are subparts of multipart/digest containers.  Such
+        subparts have a default content type of message/rfc822.
+        """
+        return self._default_type
+
+    def set_default_type(self, ctype):
+        """Set the `default' content type.
+
+        ctype should be either "text/plain" or "message/rfc822", although this
+        is not enforced.  The default content type is not stored in the
+        Content-Type header.
+        """
+        self._default_type = ctype
+
+    def _get_params_preserve(self, failobj, header):
+        # Like get_params() but preserves the quoting of values.  BAW:
+        # should this be part of the public interface?
+        missing = object()
+        value = self.get(header, missing)
+        if value is missing:
+            return failobj
+        params = []
+        for p in _parseparam(';' + value):
+            try:
+                name, val = p.split('=', 1)
+                name = name.strip()
+                val = val.strip()
+            except ValueError:
+                # Must have been a bare attribute
+                name = p.strip()
+                val = ''
+            params.append((name, val))
+        params = utils.decode_params(params)
+        return params
+
+    def get_params(self, failobj=None, header='content-type', unquote=True):
+        """Return the message's Content-Type parameters, as a list.
+
+        The elements of the returned list are 2-tuples of key/value pairs, as
+        split on the `=' sign.  The left hand side of the `=' is the key,
+        while the right hand side is the value.  If there is no `=' sign in
+        the parameter the value is the empty string.  The value is as
+        described in the get_param() method.
+
+        Optional failobj is the object to return if there is no Content-Type
+        header.  Optional header is the header to search instead of
+        Content-Type.  If unquote is True, the value is unquoted.
+        """
+        missing = object()
+        params = self._get_params_preserve(missing, header)
+        if params is missing:
+            return failobj
+        if unquote:
+            return [(k, _unquotevalue(v)) for k, v in params]
+        else:
+            return params
+
+    def get_param(self, param, failobj=None, header='content-type',
+                  unquote=True):
+        """Return the parameter value if found in the Content-Type header.
+
+        Optional failobj is the object to return if there is no Content-Type
+        header, or the Content-Type header has no such parameter.  Optional
+        header is the header to search instead of Content-Type.
+
+        Parameter keys are always compared case insensitively.  The return
+        value can either be a string, or a 3-tuple if the parameter was RFC
+        2231 encoded.  When it's a 3-tuple, the elements of the value are of
+        the form (CHARSET, LANGUAGE, VALUE).  Note that both CHARSET and
+        LANGUAGE can be None, in which case you should consider VALUE to be
+        encoded in the us-ascii charset.  You can usually ignore LANGUAGE.
+
+        Your application should be prepared to deal with 3-tuple return
+        values, and can convert the parameter to a Unicode string like so:
+
+            param = msg.get_param('foo')
+            if isinstance(param, tuple):
+                param = unicode(param[2], param[0] or 'us-ascii')
+
+        In any case, the parameter value (either the returned string, or the
+        VALUE item in the 3-tuple) is always unquoted, unless unquote is set
+        to False.
+        """
+        if not self.has_key(header):
+            return failobj
+        for k, v in self._get_params_preserve(failobj, header):
+            if k.lower() == param.lower():
+                if unquote:
+                    return _unquotevalue(v)
+                else:
+                    return v
+        return failobj
+
+    def set_param(self, param, value, header='Content-Type', requote=True,
+                  charset=None, language=''):
+        """Set a parameter in the Content-Type header.
+
+        If the parameter already exists in the header, its value will be
+        replaced with the new value.
+
+        If header is Content-Type and has not yet been defined for this
+        message, it will be set to "text/plain" and the new parameter and
+        value will be appended as per RFC 2045.
+
+        An alternate header can specified in the header argument, and all
+        parameters will be quoted as necessary unless requote is False.
+
+        If charset is specified, the parameter will be encoded according to RFC
+        2231.  Optional language specifies the RFC 2231 language, defaulting
+        to the empty string.  Both charset and language should be strings.
+        """
+        if not isinstance(value, tuple) and charset:
+            value = (charset, language, value)
+
+        if not self.has_key(header) and header.lower() == 'content-type':
+            ctype = 'text/plain'
+        else:
+            ctype = self.get(header)
+        if not self.get_param(param, header=header):
+            if not ctype:
+                ctype = _formatparam(param, value, requote)
+            else:
+                ctype = SEMISPACE.join(
+                    [ctype, _formatparam(param, value, requote)])
+        else:
+            ctype = ''
+            for old_param, old_value in self.get_params(header=header,
+                                                        unquote=requote):
+                append_param = ''
+                if old_param.lower() == param.lower():
+                    append_param = _formatparam(param, value, requote)
+                else:
+                    append_param = _formatparam(old_param, old_value, requote)
+                if not ctype:
+                    ctype = append_param
+                else:
+                    ctype = SEMISPACE.join([ctype, append_param])
+        if ctype <> self.get(header):
+            del self[header]
+            self[header] = ctype
+
+    def del_param(self, param, header='content-type', requote=True):
+        """Remove the given parameter completely from the Content-Type header.
+
+        The header will be re-written in place without the parameter or its
+        value. All values will be quoted as necessary unless requote is
+        False.  Optional header specifies an alternative to the Content-Type
+        header.
+        """
+        if not self.has_key(header):
+            return
+        new_ctype = ''
+        for p, v in self.get_params(header=header, unquote=requote):
+            if p.lower() <> param.lower():
+                if not new_ctype:
+                    new_ctype = _formatparam(p, v, requote)
+                else:
+                    new_ctype = SEMISPACE.join([new_ctype,
+                                                _formatparam(p, v, requote)])
+        if new_ctype <> self.get(header):
+            del self[header]
+            self[header] = new_ctype
+
+    def set_type(self, type, header='Content-Type', requote=True):
+        """Set the main type and subtype for the Content-Type header.
+
+        type must be a string in the form "maintype/subtype", otherwise a
+        ValueError is raised.
+
+        This method replaces the Content-Type header, keeping all the
+        parameters in place.  If requote is False, this leaves the existing
+        header's quoting as is.  Otherwise, the parameters will be quoted (the
+        default).
+
+        An alternative header can be specified in the header argument.  When
+        the Content-Type header is set, we'll always also add a MIME-Version
+        header.
+        """
+        # BAW: should we be strict?
+        if not type.count('/') == 1:
+            raise ValueError
+        # Set the Content-Type, you get a MIME-Version
+        if header.lower() == 'content-type':
+            del self['mime-version']
+            self['MIME-Version'] = '1.0'
+        if not self.has_key(header):
+            self[header] = type
+            return
+        params = self.get_params(header=header, unquote=requote)
+        del self[header]
+        self[header] = type
+        # Skip the first param; it's the old type.
+        for p, v in params[1:]:
+            self.set_param(p, v, header, requote)
+
+    def get_filename(self, failobj=None):
+        """Return the filename associated with the payload if present.
+
+        The filename is extracted from the Content-Disposition header's
+        `filename' parameter, and it is unquoted.  If that header is missing
+        the `filename' parameter, this method falls back to looking for the
+        `name' parameter.
+        """
+        missing = object()
+        filename = self.get_param('filename', missing, 'content-disposition')
+        if filename is missing:
+            filename = self.get_param('name', missing, 'content-disposition')
+        if filename is missing:
+            return failobj
+        return utils.collapse_rfc2231_value(filename).strip()
+
+    def get_boundary(self, failobj=None):
+        """Return the boundary associated with the payload if present.
+
+        The boundary is extracted from the Content-Type header's `boundary'
+        parameter, and it is unquoted.
+        """
+        missing = object()
+        boundary = self.get_param('boundary', missing)
+        if boundary is missing:
+            return failobj
+        # RFC 2046 says that boundaries may begin but not end in w/s
+        return utils.collapse_rfc2231_value(boundary).rstrip()
+
+    def set_boundary(self, boundary):
+        """Set the boundary parameter in Content-Type to 'boundary'.
+
+        This is subtly different than deleting the Content-Type header and
+        adding a new one with a new boundary parameter via add_header().  The
+        main difference is that using the set_boundary() method preserves the
+        order of the Content-Type header in the original message.
+
+        HeaderParseError is raised if the message has no Content-Type header.
+        """
+        missing = object()
+        params = self._get_params_preserve(missing, 'content-type')
+        if params is missing:
+            # There was no Content-Type header, and we don't know what type
+            # to set it to, so raise an exception.
+            raise errors.HeaderParseError('No Content-Type header found')
+        newparams = []
+        foundp = False
+        for pk, pv in params:
+            if pk.lower() == 'boundary':
+                newparams.append(('boundary', '"%s"' % boundary))
+                foundp = True
+            else:
+                newparams.append((pk, pv))
+        if not foundp:
+            # The original Content-Type header had no boundary attribute.
+            # Tack one on the end.  BAW: should we raise an exception
+            # instead???
+            newparams.append(('boundary', '"%s"' % boundary))
+        # Replace the existing Content-Type header with the new value
+        newheaders = []
+        for h, v in self._headers:
+            if h.lower() == 'content-type':
+                parts = []
+                for k, v in newparams:
+                    if v == '':
+                        parts.append(k)
+                    else:
+                        parts.append('%s=%s' % (k, v))
+                newheaders.append((h, SEMISPACE.join(parts)))
+
+            else:
+                newheaders.append((h, v))
+        self._headers = newheaders
+
+    def get_content_charset(self, failobj=None):
+        """Return the charset parameter of the Content-Type header.
+
+        The returned string is always coerced to lower case.  If there is no
+        Content-Type header, or if that header has no charset parameter,
+        failobj is returned.
+        """
+        missing = object()
+        charset = self.get_param('charset', missing)
+        if charset is missing:
+            return failobj
+        if isinstance(charset, tuple):
+            # RFC 2231 encoded, so decode it, and it better end up as ascii.
+            pcharset = charset[0] or 'us-ascii'
+            charset = unicode(charset[2], pcharset).encode('us-ascii')
+        # RFC 2046, $4.1.2 says charsets are not case sensitive
+        return charset.lower()
+
+    def get_charsets(self, failobj=None):
+        """Return a list containing the charset(s) used in this message.
+
+        The returned list of items describes the Content-Type headers'
+        charset parameter for this message and all the subparts in its
+        payload.
+
+        Each item will either be a string (the value of the charset parameter
+        in the Content-Type header of that part) or the value of the
+        'failobj' parameter (defaults to None), if the part does not have a
+        main MIME type of "text", or the charset is not defined.
+
+        The list will contain one string for each part of the message, plus
+        one for the container message (i.e. self), so that a non-multipart
+        message will still return a list of length 1.
+        """
+        return [part.get_content_charset(failobj) for part in self.walk()]
+
+    # I.e. def walk(self): ...
+    from email.Iterators import walk
diff --git a/Lib/email/mime/__init__.py b/Lib/email/mime/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Lib/email/mime/application.py b/Lib/email/mime/application.py
new file mode 100644
index 0000000..6f8bb8a
--- /dev/null
+++ b/Lib/email/mime/application.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Keith Dart
+# Contact: email-sig@python.org
+
+"""Class representing application/* type MIME documents."""
+
+__all__ = ["MIMEApplication"]
+
+from email import encoders
+from email.mime.nonmultipart import MIMENonMultipart
+
+
+class MIMEApplication(MIMENonMultipart):
+    """Class for generating application/* MIME documents."""
+
+    def __init__(self, _data, _subtype='octet-stream',
+                 _encoder=encoders.encode_base64, **_params):
+        """Create an application/* type MIME document.
+
+        _data is a string containing the raw applicatoin data.
+
+        _subtype is the MIME content type subtype, defaulting to
+        'octet-stream'.
+
+        _encoder is a function which will perform the actual encoding for
+        transport of the application data, defaulting to base64 encoding.
+
+        Any additional keyword arguments are passed to the base class
+        constructor, which turns them into parameters on the Content-Type
+        header.
+        """
+        if _subtype is None:
+            raise TypeError('Invalid application MIME subtype')
+        MIMENonMultipart.__init__(self, 'application', _subtype, **_params)
+        self.set_payload(_data)
+        _encoder(self)
diff --git a/Lib/email/mime/audio.py b/Lib/email/mime/audio.py
new file mode 100644
index 0000000..c7290c4
--- /dev/null
+++ b/Lib/email/mime/audio.py
@@ -0,0 +1,73 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Anthony Baxter
+# Contact: email-sig@python.org
+
+"""Class representing audio/* type MIME documents."""
+
+__all__ = ['MIMEAudio']
+
+import sndhdr
+
+from cStringIO import StringIO
+from email import encoders
+from email.mime.nonmultipart import MIMENonMultipart
+
+
+
+_sndhdr_MIMEmap = {'au'  : 'basic',
+                   'wav' :'x-wav',
+                   'aiff':'x-aiff',
+                   'aifc':'x-aiff',
+                   }
+
+# There are others in sndhdr that don't have MIME types. :(
+# Additional ones to be added to sndhdr? midi, mp3, realaudio, wma??
+def _whatsnd(data):
+    """Try to identify a sound file type.
+
+    sndhdr.what() has a pretty cruddy interface, unfortunately.  This is why
+    we re-do it here.  It would be easier to reverse engineer the Unix 'file'
+    command and use the standard 'magic' file, as shipped with a modern Unix.
+    """
+    hdr = data[:512]
+    fakefile = StringIO(hdr)
+    for testfn in sndhdr.tests:
+        res = testfn(hdr, fakefile)
+        if res is not None:
+            return _sndhdr_MIMEmap.get(res[0])
+    return None
+
+
+
+class MIMEAudio(MIMENonMultipart):
+    """Class for generating audio/* MIME documents."""
+
+    def __init__(self, _audiodata, _subtype=None,
+                 _encoder=encoders.encode_base64, **_params):
+        """Create an audio/* type MIME document.
+
+        _audiodata is a string containing the raw audio data.  If this data
+        can be decoded by the standard Python `sndhdr' module, then the
+        subtype will be automatically included in the Content-Type header.
+        Otherwise, you can specify  the specific audio subtype via the
+        _subtype parameter.  If _subtype is not given, and no subtype can be
+        guessed, a TypeError is raised.
+
+        _encoder is a function which will perform the actual encoding for
+        transport of the image data.  It takes one argument, which is this
+        Image instance.  It should use get_payload() and set_payload() to
+        change the payload to the encoded form.  It should also add any
+        Content-Transfer-Encoding or other headers to the message as
+        necessary.  The default encoding is Base64.
+
+        Any additional keyword arguments are passed to the base class
+        constructor, which turns them into parameters on the Content-Type
+        header.
+        """
+        if _subtype is None:
+            _subtype = _whatsnd(_audiodata)
+        if _subtype is None:
+            raise TypeError('Could not find audio MIME subtype')
+        MIMENonMultipart.__init__(self, 'audio', _subtype, **_params)
+        self.set_payload(_audiodata)
+        _encoder(self)
diff --git a/Lib/email/mime/base.py b/Lib/email/mime/base.py
new file mode 100644
index 0000000..ac91925
--- /dev/null
+++ b/Lib/email/mime/base.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Base class for MIME specializations."""
+
+__all__ = ['MIMEBase']
+
+from email import message
+
+
+
+class MIMEBase(message.Message):
+    """Base class for MIME specializations."""
+
+    def __init__(self, _maintype, _subtype, **_params):
+        """This constructor adds a Content-Type: and a MIME-Version: header.
+
+        The Content-Type: header is taken from the _maintype and _subtype
+        arguments.  Additional parameters for this header are taken from the
+        keyword arguments.
+        """
+        message.Message.__init__(self)
+        ctype = '%s/%s' % (_maintype, _subtype)
+        self.add_header('Content-Type', ctype, **_params)
+        self['MIME-Version'] = '1.0'
diff --git a/Lib/email/mime/image.py b/Lib/email/mime/image.py
new file mode 100644
index 0000000..5563823
--- /dev/null
+++ b/Lib/email/mime/image.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Class representing image/* type MIME documents."""
+
+__all__ = ['MIMEImage']
+
+import imghdr
+
+from email import encoders
+from email.mime.nonmultipart import MIMENonMultipart
+
+
+
+class MIMEImage(MIMENonMultipart):
+    """Class for generating image/* type MIME documents."""
+
+    def __init__(self, _imagedata, _subtype=None,
+                 _encoder=encoders.encode_base64, **_params):
+        """Create an image/* type MIME document.
+
+        _imagedata is a string containing the raw image data.  If this data
+        can be decoded by the standard Python `imghdr' module, then the
+        subtype will be automatically included in the Content-Type header.
+        Otherwise, you can specify the specific image subtype via the _subtype
+        parameter.
+
+        _encoder is a function which will perform the actual encoding for
+        transport of the image data.  It takes one argument, which is this
+        Image instance.  It should use get_payload() and set_payload() to
+        change the payload to the encoded form.  It should also add any
+        Content-Transfer-Encoding or other headers to the message as
+        necessary.  The default encoding is Base64.
+
+        Any additional keyword arguments are passed to the base class
+        constructor, which turns them into parameters on the Content-Type
+        header.
+        """
+        if _subtype is None:
+            _subtype = imghdr.what(None, _imagedata)
+        if _subtype is None:
+            raise TypeError('Could not guess image MIME subtype')
+        MIMENonMultipart.__init__(self, 'image', _subtype, **_params)
+        self.set_payload(_imagedata)
+        _encoder(self)
diff --git a/Lib/email/mime/message.py b/Lib/email/mime/message.py
new file mode 100644
index 0000000..275dbfd
--- /dev/null
+++ b/Lib/email/mime/message.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Class representing message/* MIME documents."""
+
+__all__ = ['MIMEMessage']
+
+from email import message
+from email.mime.nonmultipart import MIMENonMultipart
+
+
+
+class MIMEMessage(MIMENonMultipart):
+    """Class representing message/* MIME documents."""
+
+    def __init__(self, _msg, _subtype='rfc822'):
+        """Create a message/* type MIME document.
+
+        _msg is a message object and must be an instance of Message, or a
+        derived class of Message, otherwise a TypeError is raised.
+
+        Optional _subtype defines the subtype of the contained message.  The
+        default is "rfc822" (this is defined by the MIME standard, even though
+        the term "rfc822" is technically outdated by RFC 2822).
+        """
+        MIMENonMultipart.__init__(self, 'message', _subtype)
+        if not isinstance(_msg, message.Message):
+            raise TypeError('Argument is not an instance of Message')
+        # It's convenient to use this base class method.  We need to do it
+        # this way or we'll get an exception
+        message.Message.attach(self, _msg)
+        # And be sure our default type is set correctly
+        self.set_default_type('message/rfc822')
diff --git a/Lib/email/mime/multipart.py b/Lib/email/mime/multipart.py
new file mode 100644
index 0000000..5c8c9db
--- /dev/null
+++ b/Lib/email/mime/multipart.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2002-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Base class for MIME multipart/* type messages."""
+
+__all__ = ['MIMEMultipart']
+
+from email.mime.base import MIMEBase
+
+
+
+class MIMEMultipart(MIMEBase):
+    """Base class for MIME multipart/* type messages."""
+
+    def __init__(self, _subtype='mixed', boundary=None, _subparts=None,
+                 **_params):
+        """Creates a multipart/* type message.
+
+        By default, creates a multipart/mixed message, with proper
+        Content-Type and MIME-Version headers.
+
+        _subtype is the subtype of the multipart content type, defaulting to
+        `mixed'.
+
+        boundary is the multipart boundary string.  By default it is
+        calculated as needed.
+
+        _subparts is a sequence of initial subparts for the payload.  It
+        must be an iterable object, such as a list.  You can always
+        attach new subparts to the message by using the attach() method.
+
+        Additional parameters for the Content-Type header are taken from the
+        keyword arguments (or passed into the _params argument).
+        """
+        MIMEBase.__init__(self, 'multipart', _subtype, **_params)
+        if _subparts:
+            for p in _subparts:
+                self.attach(p)
+        if boundary:
+            self.set_boundary(boundary)
diff --git a/Lib/email/mime/nonmultipart.py b/Lib/email/mime/nonmultipart.py
new file mode 100644
index 0000000..dd280b5
--- /dev/null
+++ b/Lib/email/mime/nonmultipart.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2002-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Base class for MIME type messages that are not multipart."""
+
+__all__ = ['MIMENonMultipart']
+
+from email import errors
+from email.mime.base import MIMEBase
+
+
+
+class MIMENonMultipart(MIMEBase):
+    """Base class for MIME multipart/* type messages."""
+
+    __pychecker__ = 'unusednames=payload'
+
+    def attach(self, payload):
+        # The public API prohibits attaching multiple subparts to MIMEBase
+        # derived subtypes since none of them are, by definition, of content
+        # type multipart/*
+        raise errors.MultipartConversionError(
+            'Cannot attach additional subparts to non-multipart/*')
+
+    del __pychecker__
diff --git a/Lib/email/mime/text.py b/Lib/email/mime/text.py
new file mode 100644
index 0000000..5747db5
--- /dev/null
+++ b/Lib/email/mime/text.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Class representing text/* type MIME documents."""
+
+__all__ = ['MIMEText']
+
+from email.encoders import encode_7or8bit
+from email.mime.nonmultipart import MIMENonMultipart
+
+
+
+class MIMEText(MIMENonMultipart):
+    """Class for generating text/* type MIME documents."""
+
+    def __init__(self, _text, _subtype='plain', _charset='us-ascii'):
+        """Create a text/* type MIME document.
+
+        _text is the string for this message object.
+
+        _subtype is the MIME sub content type, defaulting to "plain".
+
+        _charset is the character set parameter added to the Content-Type
+        header.  This defaults to "us-ascii".  Note that as a side-effect, the
+        Content-Transfer-Encoding header will also be set.
+        """
+        MIMENonMultipart.__init__(self, 'text', _subtype,
+                                  **{'charset': _charset})
+        self.set_payload(_text, _charset)
diff --git a/Lib/email/parser.py b/Lib/email/parser.py
new file mode 100644
index 0000000..2fcaf25
--- /dev/null
+++ b/Lib/email/parser.py
@@ -0,0 +1,91 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
+# Contact: email-sig@python.org
+
+"""A parser of RFC 2822 and MIME email messages."""
+
+__all__ = ['Parser', 'HeaderParser']
+
+import warnings
+from cStringIO import StringIO
+
+from email.feedparser import FeedParser
+from email.message import Message
+
+
+
+class Parser:
+    def __init__(self, *args, **kws):
+        """Parser of RFC 2822 and MIME email messages.
+
+        Creates an in-memory object tree representing the email message, which
+        can then be manipulated and turned over to a Generator to return the
+        textual representation of the message.
+
+        The string must be formatted as a block of RFC 2822 headers and header
+        continuation lines, optionally preceeded by a `Unix-from' header.  The
+        header block is terminated either by the end of the string or by a
+        blank line.
+
+        _class is the class to instantiate for new message objects when they
+        must be created.  This class must have a constructor that can take
+        zero arguments.  Default is Message.Message.
+        """
+        if len(args) >= 1:
+            if '_class' in kws:
+                raise TypeError("Multiple values for keyword arg '_class'")
+            kws['_class'] = args[0]
+        if len(args) == 2:
+            if 'strict' in kws:
+                raise TypeError("Multiple values for keyword arg 'strict'")
+            kws['strict'] = args[1]
+        if len(args) > 2:
+            raise TypeError('Too many arguments')
+        if '_class' in kws:
+            self._class = kws['_class']
+            del kws['_class']
+        else:
+            self._class = Message
+        if 'strict' in kws:
+            warnings.warn("'strict' argument is deprecated (and ignored)",
+                          DeprecationWarning, 2)
+            del kws['strict']
+        if kws:
+            raise TypeError('Unexpected keyword arguments')
+
+    def parse(self, fp, headersonly=False):
+        """Create a message structure from the data in a file.
+
+        Reads all the data from the file and returns the root of the message
+        structure.  Optional headersonly is a flag specifying whether to stop
+        parsing after reading the headers or not.  The default is False,
+        meaning it parses the entire contents of the file.
+        """
+        feedparser = FeedParser(self._class)
+        if headersonly:
+            feedparser._set_headersonly()
+        while True:
+            data = fp.read(8192)
+            if not data:
+                break
+            feedparser.feed(data)
+        return feedparser.close()
+
+    def parsestr(self, text, headersonly=False):
+        """Create a message structure from a string.
+
+        Returns the root of the message structure.  Optional headersonly is a
+        flag specifying whether to stop parsing after reading the headers or
+        not.  The default is False, meaning it parses the entire contents of
+        the file.
+        """
+        return self.parse(StringIO(text), headersonly=headersonly)
+
+
+
+class HeaderParser(Parser):
+    def parse(self, fp, headersonly=True):
+        return Parser.parse(self, fp, True)
+
+    def parsestr(self, text, headersonly=True):
+        return Parser.parsestr(self, text, True)
diff --git a/Lib/email/quopriMIME.py b/Lib/email/quopriMIME.py
deleted file mode 100644
index a9b5d49..0000000
--- a/Lib/email/quopriMIME.py
+++ /dev/null
@@ -1,318 +0,0 @@
-# Copyright (C) 2001-2004 Python Software Foundation
-# Author: Ben Gertzfield
-# Contact: email-sig@python.org
-
-"""Quoted-printable content transfer encoding per RFCs 2045-2047.
-
-This module handles the content transfer encoding method defined in RFC 2045
-to encode US ASCII-like 8-bit data called `quoted-printable'.  It is used to
-safely encode text that is in a character set similar to the 7-bit US ASCII
-character set, but that includes some 8-bit characters that are normally not
-allowed in email bodies or headers.
-
-Quoted-printable is very space-inefficient for encoding binary files; use the
-email.base64MIME module for that instead.
-
-This module provides an interface to encode and decode both headers and bodies
-with quoted-printable encoding.
-
-RFC 2045 defines a method for including character set information in an
-`encoded-word' in a header.  This method is commonly used for 8-bit real names
-in To:/From:/Cc: etc. fields, as well as Subject: lines.
-
-This module does not do the line wrapping or end-of-line character
-conversion necessary for proper internationalized headers; it only
-does dumb encoding and decoding.  To deal with the various line
-wrapping issues, use the email.Header module.
-"""
-
-import re
-from string import hexdigits
-from email.Utils import fix_eols
-
-CRLF = '\r\n'
-NL = '\n'
-
-# See also Charset.py
-MISC_LEN = 7
-
-hqre = re.compile(r'[^-a-zA-Z0-9!*+/ ]')
-bqre = re.compile(r'[^ !-<>-~\t]')
-
-
-
-# Helpers
-def header_quopri_check(c):
-    """Return True if the character should be escaped with header quopri."""
-    return bool(hqre.match(c))
-
-
-def body_quopri_check(c):
-    """Return True if the character should be escaped with body quopri."""
-    return bool(bqre.match(c))
-
-
-def header_quopri_len(s):
-    """Return the length of str when it is encoded with header quopri."""
-    count = 0
-    for c in s:
-        if hqre.match(c):
-            count += 3
-        else:
-            count += 1
-    return count
-
-
-def body_quopri_len(str):
-    """Return the length of str when it is encoded with body quopri."""
-    count = 0
-    for c in str:
-        if bqre.match(c):
-            count += 3
-        else:
-            count += 1
-    return count
-
-
-def _max_append(L, s, maxlen, extra=''):
-    if not L:
-        L.append(s.lstrip())
-    elif len(L[-1]) + len(s) <= maxlen:
-        L[-1] += extra + s
-    else:
-        L.append(s.lstrip())
-
-
-def unquote(s):
-    """Turn a string in the form =AB to the ASCII character with value 0xab"""
-    return chr(int(s[1:3], 16))
-
-
-def quote(c):
-    return "=%02X" % ord(c)
-
-
-
-def header_encode(header, charset="iso-8859-1", keep_eols=False,
-                  maxlinelen=76, eol=NL):
-    """Encode a single header line with quoted-printable (like) encoding.
-
-    Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
-    used specifically for email header fields to allow charsets with mostly 7
-    bit characters (and some 8 bit) to remain more or less readable in non-RFC
-    2045 aware mail clients.
-
-    charset names the character set to use to encode the header.  It defaults
-    to iso-8859-1.
-
-    The resulting string will be in the form:
-
-    "=?charset?q?I_f=E2rt_in_your_g=E8n=E8ral_dire=E7tion?\\n
-      =?charset?q?Silly_=C8nglish_Kn=EEghts?="
-
-    with each line wrapped safely at, at most, maxlinelen characters (defaults
-    to 76 characters).  If maxlinelen is None, the entire string is encoded in
-    one chunk with no splitting.
-
-    End-of-line characters (\\r, \\n, \\r\\n) will be automatically converted
-    to the canonical email line separator \\r\\n unless the keep_eols
-    parameter is True (the default is False).
-
-    Each line of the header will be terminated in the value of eol, which
-    defaults to "\\n".  Set this to "\\r\\n" if you are using the result of
-    this function directly in email.
-    """
-    # Return empty headers unchanged
-    if not header:
-        return header
-
-    if not keep_eols:
-        header = fix_eols(header)
-
-    # Quopri encode each line, in encoded chunks no greater than maxlinelen in
-    # length, after the RFC chrome is added in.
-    quoted = []
-    if maxlinelen is None:
-        # An obnoxiously large number that's good enough
-        max_encoded = 100000
-    else:
-        max_encoded = maxlinelen - len(charset) - MISC_LEN - 1
-
-    for c in header:
-        # Space may be represented as _ instead of =20 for readability
-        if c == ' ':
-            _max_append(quoted, '_', max_encoded)
-        # These characters can be included verbatim
-        elif not hqre.match(c):
-            _max_append(quoted, c, max_encoded)
-        # Otherwise, replace with hex value like =E2
-        else:
-            _max_append(quoted, "=%02X" % ord(c), max_encoded)
-
-    # Now add the RFC chrome to each encoded chunk and glue the chunks
-    # together.  BAW: should we be able to specify the leading whitespace in
-    # the joiner?
-    joiner = eol + ' '
-    return joiner.join(['=?%s?q?%s?=' % (charset, line) for line in quoted])
-
-
-
-def encode(body, binary=False, maxlinelen=76, eol=NL):
-    """Encode with quoted-printable, wrapping at maxlinelen characters.
-
-    If binary is False (the default), end-of-line characters will be converted
-    to the canonical email end-of-line sequence \\r\\n.  Otherwise they will
-    be left verbatim.
-
-    Each line of encoded text will end with eol, which defaults to "\\n".  Set
-    this to "\\r\\n" if you will be using the result of this function directly
-    in an email.
-
-    Each line will be wrapped at, at most, maxlinelen characters (defaults to
-    76 characters).  Long lines will have the `soft linefeed' quoted-printable
-    character "=" appended to them, so the decoded text will be identical to
-    the original text.
-    """
-    if not body:
-        return body
-
-    if not binary:
-        body = fix_eols(body)
-
-    # BAW: We're accumulating the body text by string concatenation.  That
-    # can't be very efficient, but I don't have time now to rewrite it.  It
-    # just feels like this algorithm could be more efficient.
-    encoded_body = ''
-    lineno = -1
-    # Preserve line endings here so we can check later to see an eol needs to
-    # be added to the output later.
-    lines = body.splitlines(1)
-    for line in lines:
-        # But strip off line-endings for processing this line.
-        if line.endswith(CRLF):
-            line = line[:-2]
-        elif line[-1] in CRLF:
-            line = line[:-1]
-
-        lineno += 1
-        encoded_line = ''
-        prev = None
-        linelen = len(line)
-        # Now we need to examine every character to see if it needs to be
-        # quopri encoded.  BAW: again, string concatenation is inefficient.
-        for j in range(linelen):
-            c = line[j]
-            prev = c
-            if bqre.match(c):
-                c = quote(c)
-            elif j+1 == linelen:
-                # Check for whitespace at end of line; special case
-                if c not in ' \t':
-                    encoded_line += c
-                prev = c
-                continue
-            # Check to see to see if the line has reached its maximum length
-            if len(encoded_line) + len(c) >= maxlinelen:
-                encoded_body += encoded_line + '=' + eol
-                encoded_line = ''
-            encoded_line += c
-        # Now at end of line..
-        if prev and prev in ' \t':
-            # Special case for whitespace at end of file
-            if lineno + 1 == len(lines):
-                prev = quote(prev)
-                if len(encoded_line) + len(prev) > maxlinelen:
-                    encoded_body += encoded_line + '=' + eol + prev
-                else:
-                    encoded_body += encoded_line + prev
-            # Just normal whitespace at end of line
-            else:
-                encoded_body += encoded_line + prev + '=' + eol
-            encoded_line = ''
-        # Now look at the line we just finished and it has a line ending, we
-        # need to add eol to the end of the line.
-        if lines[lineno].endswith(CRLF) or lines[lineno][-1] in CRLF:
-            encoded_body += encoded_line + eol
-        else:
-            encoded_body += encoded_line
-        encoded_line = ''
-    return encoded_body
-
-
-# For convenience and backwards compatibility w/ standard base64 module
-body_encode = encode
-encodestring = encode
-
-
-
-# BAW: I'm not sure if the intent was for the signature of this function to be
-# the same as base64MIME.decode() or not...
-def decode(encoded, eol=NL):
-    """Decode a quoted-printable string.
-
-    Lines are separated with eol, which defaults to \\n.
-    """
-    if not encoded:
-        return encoded
-    # BAW: see comment in encode() above.  Again, we're building up the
-    # decoded string with string concatenation, which could be done much more
-    # efficiently.
-    decoded = ''
-
-    for line in encoded.splitlines():
-        line = line.rstrip()
-        if not line:
-            decoded += eol
-            continue
-
-        i = 0
-        n = len(line)
-        while i < n:
-            c = line[i]
-            if c <> '=':
-                decoded += c
-                i += 1
-            # Otherwise, c == "=".  Are we at the end of the line?  If so, add
-            # a soft line break.
-            elif i+1 == n:
-                i += 1
-                continue
-            # Decode if in form =AB
-            elif i+2 < n and line[i+1] in hexdigits and line[i+2] in hexdigits:
-                decoded += unquote(line[i:i+3])
-                i += 3
-            # Otherwise, not in form =AB, pass literally
-            else:
-                decoded += c
-                i += 1
-
-            if i == n:
-                decoded += eol
-    # Special case if original string did not end with eol
-    if not encoded.endswith(eol) and decoded.endswith(eol):
-        decoded = decoded[:-1]
-    return decoded
-
-
-# For convenience and backwards compatibility w/ standard base64 module
-body_decode = decode
-decodestring = decode
-
-
-
-def _unquote_match(match):
-    """Turn a match in the form =AB to the ASCII character with value 0xab"""
-    s = match.group(0)
-    return unquote(s)
-
-
-# Header decoding is done a bit differently
-def header_decode(s):
-    """Decode a string encoded with RFC 2045 MIME header `Q' encoding.
-
-    This function does not parse a full MIME header value encoded with
-    quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
-    the high level email.Header class for that functionality.
-    """
-    s = s.replace('_', ' ')
-    return re.sub(r'=\w{2}', _unquote_match, s)
diff --git a/Lib/email/quoprimime.py b/Lib/email/quoprimime.py
new file mode 100644
index 0000000..a5658dd
--- /dev/null
+++ b/Lib/email/quoprimime.py
@@ -0,0 +1,336 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Ben Gertzfield
+# Contact: email-sig@python.org
+
+"""Quoted-printable content transfer encoding per RFCs 2045-2047.
+
+This module handles the content transfer encoding method defined in RFC 2045
+to encode US ASCII-like 8-bit data called `quoted-printable'.  It is used to
+safely encode text that is in a character set similar to the 7-bit US ASCII
+character set, but that includes some 8-bit characters that are normally not
+allowed in email bodies or headers.
+
+Quoted-printable is very space-inefficient for encoding binary files; use the
+email.base64MIME module for that instead.
+
+This module provides an interface to encode and decode both headers and bodies
+with quoted-printable encoding.
+
+RFC 2045 defines a method for including character set information in an
+`encoded-word' in a header.  This method is commonly used for 8-bit real names
+in To:/From:/Cc: etc. fields, as well as Subject: lines.
+
+This module does not do the line wrapping or end-of-line character
+conversion necessary for proper internationalized headers; it only
+does dumb encoding and decoding.  To deal with the various line
+wrapping issues, use the email.Header module.
+"""
+
+__all__ = [
+    'body_decode',
+    'body_encode',
+    'body_quopri_check',
+    'body_quopri_len',
+    'decode',
+    'decodestring',
+    'encode',
+    'encodestring',
+    'header_decode',
+    'header_encode',
+    'header_quopri_check',
+    'header_quopri_len',
+    'quote',
+    'unquote',
+    ]
+
+import re
+
+from string import hexdigits
+from email.utils import fix_eols
+
+CRLF = '\r\n'
+NL = '\n'
+
+# See also Charset.py
+MISC_LEN = 7
+
+hqre = re.compile(r'[^-a-zA-Z0-9!*+/ ]')
+bqre = re.compile(r'[^ !-<>-~\t]')
+
+
+
+# Helpers
+def header_quopri_check(c):
+    """Return True if the character should be escaped with header quopri."""
+    return bool(hqre.match(c))
+
+
+def body_quopri_check(c):
+    """Return True if the character should be escaped with body quopri."""
+    return bool(bqre.match(c))
+
+
+def header_quopri_len(s):
+    """Return the length of str when it is encoded with header quopri."""
+    count = 0
+    for c in s:
+        if hqre.match(c):
+            count += 3
+        else:
+            count += 1
+    return count
+
+
+def body_quopri_len(str):
+    """Return the length of str when it is encoded with body quopri."""
+    count = 0
+    for c in str:
+        if bqre.match(c):
+            count += 3
+        else:
+            count += 1
+    return count
+
+
+def _max_append(L, s, maxlen, extra=''):
+    if not L:
+        L.append(s.lstrip())
+    elif len(L[-1]) + len(s) <= maxlen:
+        L[-1] += extra + s
+    else:
+        L.append(s.lstrip())
+
+
+def unquote(s):
+    """Turn a string in the form =AB to the ASCII character with value 0xab"""
+    return chr(int(s[1:3], 16))
+
+
+def quote(c):
+    return "=%02X" % ord(c)
+
+
+
+def header_encode(header, charset="iso-8859-1", keep_eols=False,
+                  maxlinelen=76, eol=NL):
+    """Encode a single header line with quoted-printable (like) encoding.
+
+    Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
+    used specifically for email header fields to allow charsets with mostly 7
+    bit characters (and some 8 bit) to remain more or less readable in non-RFC
+    2045 aware mail clients.
+
+    charset names the character set to use to encode the header.  It defaults
+    to iso-8859-1.
+
+    The resulting string will be in the form:
+
+    "=?charset?q?I_f=E2rt_in_your_g=E8n=E8ral_dire=E7tion?\\n
+      =?charset?q?Silly_=C8nglish_Kn=EEghts?="
+
+    with each line wrapped safely at, at most, maxlinelen characters (defaults
+    to 76 characters).  If maxlinelen is None, the entire string is encoded in
+    one chunk with no splitting.
+
+    End-of-line characters (\\r, \\n, \\r\\n) will be automatically converted
+    to the canonical email line separator \\r\\n unless the keep_eols
+    parameter is True (the default is False).
+
+    Each line of the header will be terminated in the value of eol, which
+    defaults to "\\n".  Set this to "\\r\\n" if you are using the result of
+    this function directly in email.
+    """
+    # Return empty headers unchanged
+    if not header:
+        return header
+
+    if not keep_eols:
+        header = fix_eols(header)
+
+    # Quopri encode each line, in encoded chunks no greater than maxlinelen in
+    # length, after the RFC chrome is added in.
+    quoted = []
+    if maxlinelen is None:
+        # An obnoxiously large number that's good enough
+        max_encoded = 100000
+    else:
+        max_encoded = maxlinelen - len(charset) - MISC_LEN - 1
+
+    for c in header:
+        # Space may be represented as _ instead of =20 for readability
+        if c == ' ':
+            _max_append(quoted, '_', max_encoded)
+        # These characters can be included verbatim
+        elif not hqre.match(c):
+            _max_append(quoted, c, max_encoded)
+        # Otherwise, replace with hex value like =E2
+        else:
+            _max_append(quoted, "=%02X" % ord(c), max_encoded)
+
+    # Now add the RFC chrome to each encoded chunk and glue the chunks
+    # together.  BAW: should we be able to specify the leading whitespace in
+    # the joiner?
+    joiner = eol + ' '
+    return joiner.join(['=?%s?q?%s?=' % (charset, line) for line in quoted])
+
+
+
+def encode(body, binary=False, maxlinelen=76, eol=NL):
+    """Encode with quoted-printable, wrapping at maxlinelen characters.
+
+    If binary is False (the default), end-of-line characters will be converted
+    to the canonical email end-of-line sequence \\r\\n.  Otherwise they will
+    be left verbatim.
+
+    Each line of encoded text will end with eol, which defaults to "\\n".  Set
+    this to "\\r\\n" if you will be using the result of this function directly
+    in an email.
+
+    Each line will be wrapped at, at most, maxlinelen characters (defaults to
+    76 characters).  Long lines will have the `soft linefeed' quoted-printable
+    character "=" appended to them, so the decoded text will be identical to
+    the original text.
+    """
+    if not body:
+        return body
+
+    if not binary:
+        body = fix_eols(body)
+
+    # BAW: We're accumulating the body text by string concatenation.  That
+    # can't be very efficient, but I don't have time now to rewrite it.  It
+    # just feels like this algorithm could be more efficient.
+    encoded_body = ''
+    lineno = -1
+    # Preserve line endings here so we can check later to see an eol needs to
+    # be added to the output later.
+    lines = body.splitlines(1)
+    for line in lines:
+        # But strip off line-endings for processing this line.
+        if line.endswith(CRLF):
+            line = line[:-2]
+        elif line[-1] in CRLF:
+            line = line[:-1]
+
+        lineno += 1
+        encoded_line = ''
+        prev = None
+        linelen = len(line)
+        # Now we need to examine every character to see if it needs to be
+        # quopri encoded.  BAW: again, string concatenation is inefficient.
+        for j in range(linelen):
+            c = line[j]
+            prev = c
+            if bqre.match(c):
+                c = quote(c)
+            elif j+1 == linelen:
+                # Check for whitespace at end of line; special case
+                if c not in ' \t':
+                    encoded_line += c
+                prev = c
+                continue
+            # Check to see to see if the line has reached its maximum length
+            if len(encoded_line) + len(c) >= maxlinelen:
+                encoded_body += encoded_line + '=' + eol
+                encoded_line = ''
+            encoded_line += c
+        # Now at end of line..
+        if prev and prev in ' \t':
+            # Special case for whitespace at end of file
+            if lineno + 1 == len(lines):
+                prev = quote(prev)
+                if len(encoded_line) + len(prev) > maxlinelen:
+                    encoded_body += encoded_line + '=' + eol + prev
+                else:
+                    encoded_body += encoded_line + prev
+            # Just normal whitespace at end of line
+            else:
+                encoded_body += encoded_line + prev + '=' + eol
+            encoded_line = ''
+        # Now look at the line we just finished and it has a line ending, we
+        # need to add eol to the end of the line.
+        if lines[lineno].endswith(CRLF) or lines[lineno][-1] in CRLF:
+            encoded_body += encoded_line + eol
+        else:
+            encoded_body += encoded_line
+        encoded_line = ''
+    return encoded_body
+
+
+# For convenience and backwards compatibility w/ standard base64 module
+body_encode = encode
+encodestring = encode
+
+
+
+# BAW: I'm not sure if the intent was for the signature of this function to be
+# the same as base64MIME.decode() or not...
+def decode(encoded, eol=NL):
+    """Decode a quoted-printable string.
+
+    Lines are separated with eol, which defaults to \\n.
+    """
+    if not encoded:
+        return encoded
+    # BAW: see comment in encode() above.  Again, we're building up the
+    # decoded string with string concatenation, which could be done much more
+    # efficiently.
+    decoded = ''
+
+    for line in encoded.splitlines():
+        line = line.rstrip()
+        if not line:
+            decoded += eol
+            continue
+
+        i = 0
+        n = len(line)
+        while i < n:
+            c = line[i]
+            if c <> '=':
+                decoded += c
+                i += 1
+            # Otherwise, c == "=".  Are we at the end of the line?  If so, add
+            # a soft line break.
+            elif i+1 == n:
+                i += 1
+                continue
+            # Decode if in form =AB
+            elif i+2 < n and line[i+1] in hexdigits and line[i+2] in hexdigits:
+                decoded += unquote(line[i:i+3])
+                i += 3
+            # Otherwise, not in form =AB, pass literally
+            else:
+                decoded += c
+                i += 1
+
+            if i == n:
+                decoded += eol
+    # Special case if original string did not end with eol
+    if not encoded.endswith(eol) and decoded.endswith(eol):
+        decoded = decoded[:-1]
+    return decoded
+
+
+# For convenience and backwards compatibility w/ standard base64 module
+body_decode = decode
+decodestring = decode
+
+
+
+def _unquote_match(match):
+    """Turn a match in the form =AB to the ASCII character with value 0xab"""
+    s = match.group(0)
+    return unquote(s)
+
+
+# Header decoding is done a bit differently
+def header_decode(s):
+    """Decode a string encoded with RFC 2045 MIME header `Q' encoding.
+
+    This function does not parse a full MIME header value encoded with
+    quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
+    the high level email.Header class for that functionality.
+    """
+    s = s.replace('_', ' ')
+    return re.sub(r'=\w{2}', _unquote_match, s)
diff --git a/Lib/email/test/test_email.py b/Lib/email/test/test_email.py
index 5a42c227..d35e770 100644
--- a/Lib/email/test/test_email.py
+++ b/Lib/email/test/test_email.py
@@ -39,9 +39,6 @@ NL = '\n'
 EMPTYSTRING = ''
 SPACE = ' '
 
-# We don't care about DeprecationWarnings
-warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
-
 
 
 def openfile(filename, mode='r'):
@@ -87,7 +84,7 @@ class TestMessageAPI(TestEmailBase):
         charset = Charset('iso-8859-1')
         msg.set_charset(charset)
         eq(msg['mime-version'], '1.0')
-        eq(msg.get_type(), 'text/plain')
+        eq(msg.get_content_type(), 'text/plain')
         eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
         eq(msg.get_param('charset'), 'iso-8859-1')
         eq(msg['content-transfer-encoding'], 'quoted-printable')
@@ -211,6 +208,19 @@ class TestMessageAPI(TestEmailBase):
         msg.set_payload('foo')
         eq(msg.get_payload(decode=True), 'foo')
 
+    def test_decode_bogus_uu_payload_quietly(self):
+        msg = Message()
+        msg.set_payload('begin 664 foo.txt\n%<W1F=0000H \n \nend\n')
+        msg['Content-Transfer-Encoding'] = 'x-uuencode'
+        old_stderr = sys.stderr
+        try:
+            sys.stderr = sfp = StringIO()
+            # We don't care about the payload
+            msg.get_payload(decode=True)
+        finally:
+            sys.stderr = old_stderr
+        self.assertEqual(sfp.getvalue(), '')
+
     def test_decoded_generator(self):
         eq = self.assertEqual
         msg = self._msgobj('msg_07.txt')
@@ -893,7 +903,7 @@ class TestMIMEAudio(unittest.TestCase):
         self._au = MIMEAudio(self._audiodata)
 
     def test_guess_minor_type(self):
-        self.assertEqual(self._au.get_type(), 'audio/basic')
+        self.assertEqual(self._au.get_content_type(), 'audio/basic')
 
     def test_encoding(self):
         payload = self._au.get_payload()
@@ -901,7 +911,7 @@ class TestMIMEAudio(unittest.TestCase):
 
     def test_checkSetMinor(self):
         au = MIMEAudio(self._audiodata, 'fish')
-        self.assertEqual(au.get_type(), 'audio/fish')
+        self.assertEqual(au.get_content_type(), 'audio/fish')
 
     def test_add_header(self):
         eq = self.assertEqual
@@ -936,7 +946,7 @@ class TestMIMEImage(unittest.TestCase):
         self._im = MIMEImage(self._imgdata)
 
     def test_guess_minor_type(self):
-        self.assertEqual(self._im.get_type(), 'image/gif')
+        self.assertEqual(self._im.get_content_type(), 'image/gif')
 
     def test_encoding(self):
         payload = self._im.get_payload()
@@ -944,7 +954,7 @@ class TestMIMEImage(unittest.TestCase):
 
     def test_checkSetMinor(self):
         im = MIMEImage(self._imgdata, 'fish')
-        self.assertEqual(im.get_type(), 'image/fish')
+        self.assertEqual(im.get_content_type(), 'image/fish')
 
     def test_add_header(self):
         eq = self.assertEqual
@@ -976,7 +986,7 @@ class TestMIMEText(unittest.TestCase):
     def test_types(self):
         eq = self.assertEqual
         unless = self.failUnless
-        eq(self._msg.get_type(), 'text/plain')
+        eq(self._msg.get_content_type(), 'text/plain')
         eq(self._msg.get_param('charset'), 'us-ascii')
         missing = []
         unless(self._msg.get_param('foobar', missing) is missing)
@@ -1045,7 +1055,7 @@ This is the dingus fish.
         # tests
         m = self._msg
         unless(m.is_multipart())
-        eq(m.get_type(), 'multipart/mixed')
+        eq(m.get_content_type(), 'multipart/mixed')
         eq(len(m.get_payload()), 2)
         raises(IndexError, m.get_payload, 2)
         m0 = m.get_payload(0)
@@ -1379,7 +1389,7 @@ class TestNonConformant(TestEmailBase):
     def test_parse_missing_minor_type(self):
         eq = self.assertEqual
         msg = self._msgobj('msg_14.txt')
-        eq(msg.get_type(), 'text')
+        eq(msg.get_content_type(), 'text/plain')
         eq(msg.get_content_maintype(), 'text')
         eq(msg.get_content_subtype(), 'plain')
 
@@ -1531,7 +1541,7 @@ class TestMIMEMessage(TestEmailBase):
         m = Message()
         m['Subject'] = subject
         r = MIMEMessage(m)
-        eq(r.get_type(), 'message/rfc822')
+        eq(r.get_content_type(), 'message/rfc822')
         payload = r.get_payload()
         unless(isinstance(payload, list))
         eq(len(payload), 1)
@@ -1572,7 +1582,7 @@ Here is the body of the message.
         eq = self.assertEqual
         unless = self.failUnless
         msg = self._msgobj('msg_11.txt')
-        eq(msg.get_type(), 'message/rfc822')
+        eq(msg.get_content_type(), 'message/rfc822')
         payload = msg.get_payload()
         unless(isinstance(payload, list))
         eq(len(payload), 1)
@@ -1586,12 +1596,12 @@ Here is the body of the message.
         unless = self.failUnless
         # msg 16 is a Delivery Status Notification, see RFC 1894
         msg = self._msgobj('msg_16.txt')
-        eq(msg.get_type(), 'multipart/report')
+        eq(msg.get_content_type(), 'multipart/report')
         unless(msg.is_multipart())
         eq(len(msg.get_payload()), 3)
         # Subpart 1 is a text/plain, human readable section
         subpart = msg.get_payload(0)
-        eq(subpart.get_type(), 'text/plain')
+        eq(subpart.get_content_type(), 'text/plain')
         eq(subpart.get_payload(), """\
 This report relates to a message you sent with the following header fields:
 
@@ -1611,7 +1621,7 @@ Your message cannot be delivered to the following recipients:
         # consists of two blocks of headers, represented by two nested Message
         # objects.
         subpart = msg.get_payload(1)
-        eq(subpart.get_type(), 'message/delivery-status')
+        eq(subpart.get_content_type(), 'message/delivery-status')
         eq(len(subpart.get_payload()), 2)
         # message/delivery-status should treat each block as a bunch of
         # headers, i.e. a bunch of Message objects.
@@ -1629,13 +1639,13 @@ Your message cannot be delivered to the following recipients:
         eq(dsn2.get_param('rfc822', header='final-recipient'), '')
         # Subpart 3 is the original message
         subpart = msg.get_payload(2)
-        eq(subpart.get_type(), 'message/rfc822')
+        eq(subpart.get_content_type(), 'message/rfc822')
         payload = subpart.get_payload()
         unless(isinstance(payload, list))
         eq(len(payload), 1)
         subsubpart = payload[0]
         unless(isinstance(subsubpart, Message))
-        eq(subsubpart.get_type(), 'text/plain')
+        eq(subsubpart.get_content_type(), 'text/plain')
         eq(subsubpart['message-id'],
            '<002001c144a6$8752e060$56104586@oxy.edu>')
 
@@ -1706,16 +1716,16 @@ Two
             fp.close()
         container1 = msg.get_payload(0)
         eq(container1.get_default_type(), 'message/rfc822')
-        eq(container1.get_type(), None)
+        eq(container1.get_content_type(), 'message/rfc822')
         container2 = msg.get_payload(1)
         eq(container2.get_default_type(), 'message/rfc822')
-        eq(container2.get_type(), None)
+        eq(container2.get_content_type(), 'message/rfc822')
         container1a = container1.get_payload(0)
         eq(container1a.get_default_type(), 'text/plain')
-        eq(container1a.get_type(), 'text/plain')
+        eq(container1a.get_content_type(), 'text/plain')
         container2a = container2.get_payload(0)
         eq(container2a.get_default_type(), 'text/plain')
-        eq(container2a.get_type(), 'text/plain')
+        eq(container2a.get_content_type(), 'text/plain')
 
     def test_default_type_with_explicit_container_type(self):
         eq = self.assertEqual
@@ -1726,16 +1736,16 @@ Two
             fp.close()
         container1 = msg.get_payload(0)
         eq(container1.get_default_type(), 'message/rfc822')
-        eq(container1.get_type(), 'message/rfc822')
+        eq(container1.get_content_type(), 'message/rfc822')
         container2 = msg.get_payload(1)
         eq(container2.get_default_type(), 'message/rfc822')
-        eq(container2.get_type(), 'message/rfc822')
+        eq(container2.get_content_type(), 'message/rfc822')
         container1a = container1.get_payload(0)
         eq(container1a.get_default_type(), 'text/plain')
-        eq(container1a.get_type(), 'text/plain')
+        eq(container1a.get_content_type(), 'text/plain')
         container2a = container2.get_payload(0)
         eq(container2a.get_default_type(), 'text/plain')
-        eq(container2a.get_type(), 'text/plain')
+        eq(container2a.get_content_type(), 'text/plain')
 
     def test_default_type_non_parsed(self):
         eq = self.assertEqual
@@ -1750,9 +1760,9 @@ Two
         subpart2 = MIMEMessage(subpart2a)
         container.attach(subpart1)
         container.attach(subpart2)
-        eq(subpart1.get_type(), 'message/rfc822')
+        eq(subpart1.get_content_type(), 'message/rfc822')
         eq(subpart1.get_default_type(), 'message/rfc822')
-        eq(subpart2.get_type(), 'message/rfc822')
+        eq(subpart2.get_content_type(), 'message/rfc822')
         eq(subpart2.get_default_type(), 'message/rfc822')
         neq(container.as_string(0), '''\
 Content-Type: multipart/digest; boundary="BOUNDARY"
@@ -1784,9 +1794,9 @@ message 2
         del subpart1['mime-version']
         del subpart2['content-type']
         del subpart2['mime-version']
-        eq(subpart1.get_type(), None)
+        eq(subpart1.get_content_type(), 'message/rfc822')
         eq(subpart1.get_default_type(), 'message/rfc822')
-        eq(subpart2.get_type(), None)
+        eq(subpart2.get_content_type(), 'message/rfc822')
         eq(subpart2.get_default_type(), 'message/rfc822')
         neq(container.as_string(0), '''\
 Content-Type: multipart/digest; boundary="BOUNDARY"
@@ -1847,7 +1857,7 @@ class TestIdempotent(TestEmailBase):
     def test_parse_text_message(self):
         eq = self.assertEquals
         msg, text = self._msgobj('msg_01.txt')
-        eq(msg.get_type(), 'text/plain')
+        eq(msg.get_content_type(), 'text/plain')
         eq(msg.get_content_maintype(), 'text')
         eq(msg.get_content_subtype(), 'plain')
         eq(msg.get_params()[1], ('charset', 'us-ascii'))
@@ -1859,7 +1869,7 @@ class TestIdempotent(TestEmailBase):
     def test_parse_untyped_message(self):
         eq = self.assertEquals
         msg, text = self._msgobj('msg_03.txt')
-        eq(msg.get_type(), None)
+        eq(msg.get_content_type(), 'text/plain')
         eq(msg.get_params(), None)
         eq(msg.get_param('charset'), None)
         self._idempotent(msg, text)
@@ -1933,7 +1943,7 @@ class TestIdempotent(TestEmailBase):
         unless = self.failUnless
         # Get a message object and reset the seek pointer for other tests
         msg, text = self._msgobj('msg_05.txt')
-        eq(msg.get_type(), 'multipart/report')
+        eq(msg.get_content_type(), 'multipart/report')
         # Test the Content-Type: parameters
         params = {}
         for pk, pv in msg.get_params():
@@ -1945,13 +1955,13 @@ class TestIdempotent(TestEmailBase):
         eq(len(msg.get_payload()), 3)
         # Make sure the subparts are what we expect
         msg1 = msg.get_payload(0)
-        eq(msg1.get_type(), 'text/plain')
+        eq(msg1.get_content_type(), 'text/plain')
         eq(msg1.get_payload(), 'Yadda yadda yadda\n')
         msg2 = msg.get_payload(1)
-        eq(msg2.get_type(), None)
+        eq(msg2.get_content_type(), 'text/plain')
         eq(msg2.get_payload(), 'Yadda yadda yadda\n')
         msg3 = msg.get_payload(2)
-        eq(msg3.get_type(), 'message/rfc822')
+        eq(msg3.get_content_type(), 'message/rfc822')
         self.failUnless(isinstance(msg3, Message))
         payload = msg3.get_payload()
         unless(isinstance(payload, list))
@@ -1965,7 +1975,7 @@ class TestIdempotent(TestEmailBase):
         unless = self.failUnless
         msg, text = self._msgobj('msg_06.txt')
         # Check some of the outer headers
-        eq(msg.get_type(), 'message/rfc822')
+        eq(msg.get_content_type(), 'message/rfc822')
         # Make sure the payload is a list of exactly one sub-Message, and that
         # that submessage has a type of text/plain
         payload = msg.get_payload()
@@ -1973,7 +1983,7 @@ class TestIdempotent(TestEmailBase):
         eq(len(payload), 1)
         msg1 = payload[0]
         self.failUnless(isinstance(msg1, Message))
-        eq(msg1.get_type(), 'text/plain')
+        eq(msg1.get_content_type(), 'text/plain')
         self.failUnless(isinstance(msg1.get_payload(), str))
         eq(msg1.get_payload(), '\n')
 
@@ -2058,13 +2068,19 @@ class TestMiscellaneous(TestEmailBase):
         module = __import__('email')
         all = module.__all__
         all.sort()
-        self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
-                               'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
-                               'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
-                               'MIMENonMultipart', 'MIMEText', 'Message',
-                               'Parser', 'Utils', 'base64MIME',
-                               'message_from_file', 'message_from_string',
-                               'quopriMIME'])
+        self.assertEqual(all, [
+            # Old names
+            'Charset', 'Encoders', 'Errors', 'Generator',
+            'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
+            'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
+            'MIMENonMultipart', 'MIMEText', 'Message',
+            'Parser', 'Utils', 'base64MIME',
+            # new names
+            'base64mime', 'charset', 'encoders', 'errors', 'generator',
+            'header', 'iterators', 'message', 'message_from_file',
+            'message_from_string', 'mime', 'parser',
+            'quopriMIME', 'quoprimime', 'utils',
+            ])
 
     def test_formatdate(self):
         now = time.time()
@@ -2356,7 +2372,7 @@ class TestParsers(TestEmailBase):
             fp.close()
         eq(msg['from'], 'ppp-request@zzz.org')
         eq(msg['to'], 'ppp@zzz.org')
-        eq(msg.get_type(), 'multipart/mixed')
+        eq(msg.get_content_type(), 'multipart/mixed')
         self.failIf(msg.is_multipart())
         self.failUnless(isinstance(msg.get_payload(), str))
 
@@ -2405,10 +2421,10 @@ Here's the message body
             fp.close()
         eq(len(msg.get_payload()), 2)
         part1 = msg.get_payload(0)
-        eq(part1.get_type(), 'text/plain')
+        eq(part1.get_content_type(), 'text/plain')
         eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
         part2 = msg.get_payload(1)
-        eq(part2.get_type(), 'application/riscos')
+        eq(part2.get_content_type(), 'application/riscos')
 
     def test_multipart_digest_with_extra_mime_headers(self):
         eq = self.assertEqual
@@ -2427,21 +2443,21 @@ Here's the message body
         eq(msg.is_multipart(), 1)
         eq(len(msg.get_payload()), 2)
         part1 = msg.get_payload(0)
-        eq(part1.get_type(), 'message/rfc822')
+        eq(part1.get_content_type(), 'message/rfc822')
         eq(part1.is_multipart(), 1)
         eq(len(part1.get_payload()), 1)
         part1a = part1.get_payload(0)
         eq(part1a.is_multipart(), 0)
-        eq(part1a.get_type(), 'text/plain')
+        eq(part1a.get_content_type(), 'text/plain')
         neq(part1a.get_payload(), 'message 1\n')
         # next message/rfc822
         part2 = msg.get_payload(1)
-        eq(part2.get_type(), 'message/rfc822')
+        eq(part2.get_content_type(), 'message/rfc822')
         eq(part2.is_multipart(), 1)
         eq(len(part2.get_payload()), 1)
         part2a = part2.get_payload(0)
         eq(part2a.is_multipart(), 0)
-        eq(part2a.get_type(), 'text/plain')
+        eq(part2a.get_content_type(), 'text/plain')
         neq(part2a.get_payload(), 'message 2\n')
 
     def test_three_lines(self):
@@ -2723,6 +2739,11 @@ class TestCharset(unittest.TestCase):
         c = Charset('fake')
         eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
 
+    def test_unicode_charset_name(self):
+        charset = Charset(u'us-ascii')
+        self.assertEqual(str(charset), 'us-ascii')
+        self.assertRaises(Errors.CharsetError, Charset, 'asc\xffii')
+
 
 
 # Test multilingual MIME headers.
diff --git a/Lib/email/test/test_email_codecs.py b/Lib/email/test/test_email_codecs.py
index 159989c..38b7d95 100644
--- a/Lib/email/test/test_email_codecs.py
+++ b/Lib/email/test/test_email_codecs.py
@@ -10,6 +10,13 @@ from email.Charset import Charset
 from email.Header import Header, decode_header
 from email.Message import Message
 
+# We're compatible with Python 2.3, but it doesn't have the built-in Asian
+# codecs, so we have to skip all these tests.
+try:
+    unicode('foo', 'euc-jp')
+except LookupError:
+    raise TestSkipped
+
 
 
 class TestEmailAsianCodecs(TestEmailBase):
diff --git a/Lib/email/test/test_email_codecs_renamed.py b/Lib/email/test/test_email_codecs_renamed.py
new file mode 100644
index 0000000..56baccd
--- /dev/null
+++ b/Lib/email/test/test_email_codecs_renamed.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2002-2006 Python Software Foundation
+# Contact: email-sig@python.org
+# email package unit tests for (optional) Asian codecs
+
+import unittest
+from test.test_support import TestSkipped, run_unittest
+
+from email.test.test_email import TestEmailBase
+from email.charset import Charset
+from email.header import Header, decode_header
+from email.message import Message
+
+# We're compatible with Python 2.3, but it doesn't have the built-in Asian
+# codecs, so we have to skip all these tests.
+try:
+    unicode('foo', 'euc-jp')
+except LookupError:
+    raise TestSkipped
+
+
+
+class TestEmailAsianCodecs(TestEmailBase):
+    def test_japanese_codecs(self):
+        eq = self.ndiffAssertEqual
+        j = Charset("euc-jp")
+        g = Charset("iso-8859-1")
+        h = Header("Hello World!")
+        jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
+        ghello = 'Gr\xfc\xdf Gott!'
+        h.append(jhello, j)
+        h.append(ghello, g)
+        # BAW: This used to -- and maybe should -- fold the two iso-8859-1
+        # chunks into a single encoded word.  However it doesn't violate the
+        # standard to have them as two encoded chunks and maybe it's
+        # reasonable <wink> for each .append() call to result in a separate
+        # encoded word.
+        eq(h.encode(), """\
+Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=
+ =?iso-8859-1?q?Gr=FC=DF?= =?iso-8859-1?q?_Gott!?=""")
+        eq(decode_header(h.encode()),
+           [('Hello World!', None),
+            ('\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
+            ('Gr\xfc\xdf Gott!', 'iso-8859-1')])
+        long = 'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9'
+        h = Header(long, j, header_name="Subject")
+        # test a very long header
+        enc = h.encode()
+        # TK: splitting point may differ by codec design and/or Header encoding
+        eq(enc , """\
+=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?=
+ =?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?=""")
+        # TK: full decode comparison
+        eq(h.__unicode__().encode('euc-jp'), long)
+
+    def test_payload_encoding(self):
+        jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
+        jcode  = 'euc-jp'
+        msg = Message()
+        msg.set_payload(jhello, jcode)
+        ustr = unicode(msg.get_payload(), msg.get_content_charset())
+        self.assertEqual(jhello, ustr.encode(jcode))
+
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestEmailAsianCodecs))
+    return suite
+
+
+def test_main():
+    run_unittest(TestEmailAsianCodecs)
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/Lib/email/test/test_email_renamed.py b/Lib/email/test/test_email_renamed.py
new file mode 100644
index 0000000..ed186a0
--- /dev/null
+++ b/Lib/email/test/test_email_renamed.py
@@ -0,0 +1,3078 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Contact: email-sig@python.org
+# email package unit tests
+
+import os
+import sys
+import time
+import base64
+import difflib
+import unittest
+import warnings
+from cStringIO import StringIO
+
+import email
+
+from email.charset import Charset
+from email.header import Header, decode_header, make_header
+from email.parser import Parser, HeaderParser
+from email.generator import Generator, DecodedGenerator
+from email.message import Message
+from email.mime.application import MIMEApplication
+from email.mime.audio import MIMEAudio
+from email.mime.text import MIMEText
+from email.mime.image import MIMEImage
+from email.mime.base import MIMEBase
+from email.mime.message import MIMEMessage
+from email.mime.multipart import MIMEMultipart
+from email import utils
+from email import errors
+from email import encoders
+from email import iterators
+from email import base64mime
+from email import quoprimime
+
+from test.test_support import findfile, run_unittest
+from email.test import __file__ as landmark
+
+
+NL = '\n'
+EMPTYSTRING = ''
+SPACE = ' '
+
+
+
+def openfile(filename, mode='r'):
+    path = os.path.join(os.path.dirname(landmark), 'data', filename)
+    return open(path, mode)
+
+
+
+# Base test class
+class TestEmailBase(unittest.TestCase):
+    def ndiffAssertEqual(self, first, second):
+        """Like failUnlessEqual except use ndiff for readable output."""
+        if first <> second:
+            sfirst = str(first)
+            ssecond = str(second)
+            diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
+            fp = StringIO()
+            print >> fp, NL, NL.join(diff)
+            raise self.failureException, fp.getvalue()
+
+    def _msgobj(self, filename):
+        fp = openfile(findfile(filename))
+        try:
+            msg = email.message_from_file(fp)
+        finally:
+            fp.close()
+        return msg
+
+
+
+# Test various aspects of the Message class's API
+class TestMessageAPI(TestEmailBase):
+    def test_get_all(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_20.txt')
+        eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
+        eq(msg.get_all('xx', 'n/a'), 'n/a')
+
+    def test_getset_charset(self):
+        eq = self.assertEqual
+        msg = Message()
+        eq(msg.get_charset(), None)
+        charset = Charset('iso-8859-1')
+        msg.set_charset(charset)
+        eq(msg['mime-version'], '1.0')
+        eq(msg.get_content_type(), 'text/plain')
+        eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
+        eq(msg.get_param('charset'), 'iso-8859-1')
+        eq(msg['content-transfer-encoding'], 'quoted-printable')
+        eq(msg.get_charset().input_charset, 'iso-8859-1')
+        # Remove the charset
+        msg.set_charset(None)
+        eq(msg.get_charset(), None)
+        eq(msg['content-type'], 'text/plain')
+        # Try adding a charset when there's already MIME headers present
+        msg = Message()
+        msg['MIME-Version'] = '2.0'
+        msg['Content-Type'] = 'text/x-weird'
+        msg['Content-Transfer-Encoding'] = 'quinted-puntable'
+        msg.set_charset(charset)
+        eq(msg['mime-version'], '2.0')
+        eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
+        eq(msg['content-transfer-encoding'], 'quinted-puntable')
+
+    def test_set_charset_from_string(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.set_charset('us-ascii')
+        eq(msg.get_charset().input_charset, 'us-ascii')
+        eq(msg['content-type'], 'text/plain; charset="us-ascii"')
+
+    def test_set_payload_with_charset(self):
+        msg = Message()
+        charset = Charset('iso-8859-1')
+        msg.set_payload('This is a string payload', charset)
+        self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
+
+    def test_get_charsets(self):
+        eq = self.assertEqual
+
+        msg = self._msgobj('msg_08.txt')
+        charsets = msg.get_charsets()
+        eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
+
+        msg = self._msgobj('msg_09.txt')
+        charsets = msg.get_charsets('dingbat')
+        eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
+                      'koi8-r'])
+
+        msg = self._msgobj('msg_12.txt')
+        charsets = msg.get_charsets()
+        eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
+                      'iso-8859-3', 'us-ascii', 'koi8-r'])
+
+    def test_get_filename(self):
+        eq = self.assertEqual
+
+        msg = self._msgobj('msg_04.txt')
+        filenames = [p.get_filename() for p in msg.get_payload()]
+        eq(filenames, ['msg.txt', 'msg.txt'])
+
+        msg = self._msgobj('msg_07.txt')
+        subpart = msg.get_payload(1)
+        eq(subpart.get_filename(), 'dingusfish.gif')
+
+    def test_get_filename_with_name_parameter(self):
+        eq = self.assertEqual
+
+        msg = self._msgobj('msg_44.txt')
+        filenames = [p.get_filename() for p in msg.get_payload()]
+        eq(filenames, ['msg.txt', 'msg.txt'])
+
+    def test_get_boundary(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_07.txt')
+        # No quotes!
+        eq(msg.get_boundary(), 'BOUNDARY')
+
+    def test_set_boundary(self):
+        eq = self.assertEqual
+        # This one has no existing boundary parameter, but the Content-Type:
+        # header appears fifth.
+        msg = self._msgobj('msg_01.txt')
+        msg.set_boundary('BOUNDARY')
+        header, value = msg.items()[4]
+        eq(header.lower(), 'content-type')
+        eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
+        # This one has a Content-Type: header, with a boundary, stuck in the
+        # middle of its headers.  Make sure the order is preserved; it should
+        # be fifth.
+        msg = self._msgobj('msg_04.txt')
+        msg.set_boundary('BOUNDARY')
+        header, value = msg.items()[4]
+        eq(header.lower(), 'content-type')
+        eq(value, 'multipart/mixed; boundary="BOUNDARY"')
+        # And this one has no Content-Type: header at all.
+        msg = self._msgobj('msg_03.txt')
+        self.assertRaises(errors.HeaderParseError,
+                          msg.set_boundary, 'BOUNDARY')
+
+    def test_get_decoded_payload(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_10.txt')
+        # The outer message is a multipart
+        eq(msg.get_payload(decode=True), None)
+        # Subpart 1 is 7bit encoded
+        eq(msg.get_payload(0).get_payload(decode=True),
+           'This is a 7bit encoded message.\n')
+        # Subpart 2 is quopri
+        eq(msg.get_payload(1).get_payload(decode=True),
+           '\xa1This is a Quoted Printable encoded message!\n')
+        # Subpart 3 is base64
+        eq(msg.get_payload(2).get_payload(decode=True),
+           'This is a Base64 encoded message.')
+        # Subpart 4 has no Content-Transfer-Encoding: header.
+        eq(msg.get_payload(3).get_payload(decode=True),
+           'This has no Content-Transfer-Encoding: header.\n')
+
+    def test_get_decoded_uu_payload(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
+        for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
+            msg['content-transfer-encoding'] = cte
+            eq(msg.get_payload(decode=True), 'hello world')
+        # Now try some bogus data
+        msg.set_payload('foo')
+        eq(msg.get_payload(decode=True), 'foo')
+
+    def test_decoded_generator(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_07.txt')
+        fp = openfile('msg_17.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        s = StringIO()
+        g = DecodedGenerator(s)
+        g.flatten(msg)
+        eq(s.getvalue(), text)
+
+    def test__contains__(self):
+        msg = Message()
+        msg['From'] = 'Me'
+        msg['to'] = 'You'
+        # Check for case insensitivity
+        self.failUnless('from' in msg)
+        self.failUnless('From' in msg)
+        self.failUnless('FROM' in msg)
+        self.failUnless('to' in msg)
+        self.failUnless('To' in msg)
+        self.failUnless('TO' in msg)
+
+    def test_as_string(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_01.txt')
+        fp = openfile('msg_01.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        eq(text, msg.as_string())
+        fullrepr = str(msg)
+        lines = fullrepr.split('\n')
+        self.failUnless(lines[0].startswith('From '))
+        eq(text, NL.join(lines[1:]))
+
+    def test_bad_param(self):
+        msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
+        self.assertEqual(msg.get_param('baz'), '')
+
+    def test_missing_filename(self):
+        msg = email.message_from_string("From: foo\n")
+        self.assertEqual(msg.get_filename(), None)
+
+    def test_bogus_filename(self):
+        msg = email.message_from_string(
+        "Content-Disposition: blarg; filename\n")
+        self.assertEqual(msg.get_filename(), '')
+
+    def test_missing_boundary(self):
+        msg = email.message_from_string("From: foo\n")
+        self.assertEqual(msg.get_boundary(), None)
+
+    def test_get_params(self):
+        eq = self.assertEqual
+        msg = email.message_from_string(
+            'X-Header: foo=one; bar=two; baz=three\n')
+        eq(msg.get_params(header='x-header'),
+           [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
+        msg = email.message_from_string(
+            'X-Header: foo; bar=one; baz=two\n')
+        eq(msg.get_params(header='x-header'),
+           [('foo', ''), ('bar', 'one'), ('baz', 'two')])
+        eq(msg.get_params(), None)
+        msg = email.message_from_string(
+            'X-Header: foo; bar="one"; baz=two\n')
+        eq(msg.get_params(header='x-header'),
+           [('foo', ''), ('bar', 'one'), ('baz', 'two')])
+
+    def test_get_param_liberal(self):
+        msg = Message()
+        msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
+        self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
+
+    def test_get_param(self):
+        eq = self.assertEqual
+        msg = email.message_from_string(
+            "X-Header: foo=one; bar=two; baz=three\n")
+        eq(msg.get_param('bar', header='x-header'), 'two')
+        eq(msg.get_param('quuz', header='x-header'), None)
+        eq(msg.get_param('quuz'), None)
+        msg = email.message_from_string(
+            'X-Header: foo; bar="one"; baz=two\n')
+        eq(msg.get_param('foo', header='x-header'), '')
+        eq(msg.get_param('bar', header='x-header'), 'one')
+        eq(msg.get_param('baz', header='x-header'), 'two')
+        # XXX: We are not RFC-2045 compliant!  We cannot parse:
+        # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
+        # msg.get_param("weird")
+        # yet.
+
+    def test_get_param_funky_continuation_lines(self):
+        msg = self._msgobj('msg_22.txt')
+        self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
+
+    def test_get_param_with_semis_in_quotes(self):
+        msg = email.message_from_string(
+            'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
+        self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
+        self.assertEqual(msg.get_param('name', unquote=False),
+                         '"Jim&amp;&amp;Jill"')
+
+    def test_has_key(self):
+        msg = email.message_from_string('Header: exists')
+        self.failUnless(msg.has_key('header'))
+        self.failUnless(msg.has_key('Header'))
+        self.failUnless(msg.has_key('HEADER'))
+        self.failIf(msg.has_key('headeri'))
+
+    def test_set_param(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.set_param('charset', 'iso-2022-jp')
+        eq(msg.get_param('charset'), 'iso-2022-jp')
+        msg.set_param('importance', 'high value')
+        eq(msg.get_param('importance'), 'high value')
+        eq(msg.get_param('importance', unquote=False), '"high value"')
+        eq(msg.get_params(), [('text/plain', ''),
+                              ('charset', 'iso-2022-jp'),
+                              ('importance', 'high value')])
+        eq(msg.get_params(unquote=False), [('text/plain', ''),
+                                       ('charset', '"iso-2022-jp"'),
+                                       ('importance', '"high value"')])
+        msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
+        eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
+
+    def test_del_param(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_05.txt')
+        eq(msg.get_params(),
+           [('multipart/report', ''), ('report-type', 'delivery-status'),
+            ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
+        old_val = msg.get_param("report-type")
+        msg.del_param("report-type")
+        eq(msg.get_params(),
+           [('multipart/report', ''),
+            ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
+        msg.set_param("report-type", old_val)
+        eq(msg.get_params(),
+           [('multipart/report', ''),
+            ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
+            ('report-type', old_val)])
+
+    def test_del_param_on_other_header(self):
+        msg = Message()
+        msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
+        msg.del_param('filename', 'content-disposition')
+        self.assertEqual(msg['content-disposition'], 'attachment')
+
+    def test_set_type(self):
+        eq = self.assertEqual
+        msg = Message()
+        self.assertRaises(ValueError, msg.set_type, 'text')
+        msg.set_type('text/plain')
+        eq(msg['content-type'], 'text/plain')
+        msg.set_param('charset', 'us-ascii')
+        eq(msg['content-type'], 'text/plain; charset="us-ascii"')
+        msg.set_type('text/html')
+        eq(msg['content-type'], 'text/html; charset="us-ascii"')
+
+    def test_set_type_on_other_header(self):
+        msg = Message()
+        msg['X-Content-Type'] = 'text/plain'
+        msg.set_type('application/octet-stream', 'X-Content-Type')
+        self.assertEqual(msg['x-content-type'], 'application/octet-stream')
+
+    def test_get_content_type_missing(self):
+        msg = Message()
+        self.assertEqual(msg.get_content_type(), 'text/plain')
+
+    def test_get_content_type_missing_with_default_type(self):
+        msg = Message()
+        msg.set_default_type('message/rfc822')
+        self.assertEqual(msg.get_content_type(), 'message/rfc822')
+
+    def test_get_content_type_from_message_implicit(self):
+        msg = self._msgobj('msg_30.txt')
+        self.assertEqual(msg.get_payload(0).get_content_type(),
+                         'message/rfc822')
+
+    def test_get_content_type_from_message_explicit(self):
+        msg = self._msgobj('msg_28.txt')
+        self.assertEqual(msg.get_payload(0).get_content_type(),
+                         'message/rfc822')
+
+    def test_get_content_type_from_message_text_plain_implicit(self):
+        msg = self._msgobj('msg_03.txt')
+        self.assertEqual(msg.get_content_type(), 'text/plain')
+
+    def test_get_content_type_from_message_text_plain_explicit(self):
+        msg = self._msgobj('msg_01.txt')
+        self.assertEqual(msg.get_content_type(), 'text/plain')
+
+    def test_get_content_maintype_missing(self):
+        msg = Message()
+        self.assertEqual(msg.get_content_maintype(), 'text')
+
+    def test_get_content_maintype_missing_with_default_type(self):
+        msg = Message()
+        msg.set_default_type('message/rfc822')
+        self.assertEqual(msg.get_content_maintype(), 'message')
+
+    def test_get_content_maintype_from_message_implicit(self):
+        msg = self._msgobj('msg_30.txt')
+        self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
+
+    def test_get_content_maintype_from_message_explicit(self):
+        msg = self._msgobj('msg_28.txt')
+        self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
+
+    def test_get_content_maintype_from_message_text_plain_implicit(self):
+        msg = self._msgobj('msg_03.txt')
+        self.assertEqual(msg.get_content_maintype(), 'text')
+
+    def test_get_content_maintype_from_message_text_plain_explicit(self):
+        msg = self._msgobj('msg_01.txt')
+        self.assertEqual(msg.get_content_maintype(), 'text')
+
+    def test_get_content_subtype_missing(self):
+        msg = Message()
+        self.assertEqual(msg.get_content_subtype(), 'plain')
+
+    def test_get_content_subtype_missing_with_default_type(self):
+        msg = Message()
+        msg.set_default_type('message/rfc822')
+        self.assertEqual(msg.get_content_subtype(), 'rfc822')
+
+    def test_get_content_subtype_from_message_implicit(self):
+        msg = self._msgobj('msg_30.txt')
+        self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
+
+    def test_get_content_subtype_from_message_explicit(self):
+        msg = self._msgobj('msg_28.txt')
+        self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
+
+    def test_get_content_subtype_from_message_text_plain_implicit(self):
+        msg = self._msgobj('msg_03.txt')
+        self.assertEqual(msg.get_content_subtype(), 'plain')
+
+    def test_get_content_subtype_from_message_text_plain_explicit(self):
+        msg = self._msgobj('msg_01.txt')
+        self.assertEqual(msg.get_content_subtype(), 'plain')
+
+    def test_get_content_maintype_error(self):
+        msg = Message()
+        msg['Content-Type'] = 'no-slash-in-this-string'
+        self.assertEqual(msg.get_content_maintype(), 'text')
+
+    def test_get_content_subtype_error(self):
+        msg = Message()
+        msg['Content-Type'] = 'no-slash-in-this-string'
+        self.assertEqual(msg.get_content_subtype(), 'plain')
+
+    def test_replace_header(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.add_header('First', 'One')
+        msg.add_header('Second', 'Two')
+        msg.add_header('Third', 'Three')
+        eq(msg.keys(), ['First', 'Second', 'Third'])
+        eq(msg.values(), ['One', 'Two', 'Three'])
+        msg.replace_header('Second', 'Twenty')
+        eq(msg.keys(), ['First', 'Second', 'Third'])
+        eq(msg.values(), ['One', 'Twenty', 'Three'])
+        msg.add_header('First', 'Eleven')
+        msg.replace_header('First', 'One Hundred')
+        eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
+        eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
+        self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
+
+    def test_broken_base64_payload(self):
+        x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
+        msg = Message()
+        msg['content-type'] = 'audio/x-midi'
+        msg['content-transfer-encoding'] = 'base64'
+        msg.set_payload(x)
+        self.assertEqual(msg.get_payload(decode=True), x)
+
+
+
+# Test the email.encoders module
+class TestEncoders(unittest.TestCase):
+    def test_encode_empty_payload(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.set_charset('us-ascii')
+        eq(msg['content-transfer-encoding'], '7bit')
+
+    def test_default_cte(self):
+        eq = self.assertEqual
+        msg = MIMEText('hello world')
+        eq(msg['content-transfer-encoding'], '7bit')
+
+    def test_default_cte(self):
+        eq = self.assertEqual
+        # With no explicit _charset its us-ascii, and all are 7-bit
+        msg = MIMEText('hello world')
+        eq(msg['content-transfer-encoding'], '7bit')
+        # Similar, but with 8-bit data
+        msg = MIMEText('hello \xf8 world')
+        eq(msg['content-transfer-encoding'], '8bit')
+        # And now with a different charset
+        msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
+        eq(msg['content-transfer-encoding'], 'quoted-printable')
+
+
+
+# Test long header wrapping
+class TestLongHeaders(TestEmailBase):
+    def test_split_long_continuation(self):
+        eq = self.ndiffAssertEqual
+        msg = email.message_from_string("""\
+Subject: bug demonstration
+\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
+\tmore text
+
+test
+""")
+        sfp = StringIO()
+        g = Generator(sfp)
+        g.flatten(msg)
+        eq(sfp.getvalue(), """\
+Subject: bug demonstration
+\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
+\tmore text
+
+test
+""")
+
+    def test_another_long_almost_unsplittable_header(self):
+        eq = self.ndiffAssertEqual
+        hstr = """\
+bug demonstration
+\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
+\tmore text"""
+        h = Header(hstr, continuation_ws='\t')
+        eq(h.encode(), """\
+bug demonstration
+\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
+\tmore text""")
+        h = Header(hstr)
+        eq(h.encode(), """\
+bug demonstration
+ 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
+ more text""")
+
+    def test_long_nonstring(self):
+        eq = self.ndiffAssertEqual
+        g = Charset("iso-8859-1")
+        cz = Charset("iso-8859-2")
+        utf8 = Charset("utf-8")
+        g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
+        cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
+        utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
+        h = Header(g_head, g, header_name='Subject')
+        h.append(cz_head, cz)
+        h.append(utf8_head, utf8)
+        msg = Message()
+        msg['Subject'] = h
+        sfp = StringIO()
+        g = Generator(sfp)
+        g.flatten(msg)
+        eq(sfp.getvalue(), """\
+Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
+ =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
+ =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
+ =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
+ =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
+ =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
+ =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
+ =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
+ =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
+ =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
+ =?utf-8?b?44Gm44GE44G+44GZ44CC?=
+
+""")
+        eq(h.encode(), """\
+=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
+ =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
+ =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
+ =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
+ =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
+ =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
+ =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
+ =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
+ =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
+ =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
+ =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
+
+    def test_long_header_encode(self):
+        eq = self.ndiffAssertEqual
+        h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
+                   'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
+                   header_name='X-Foobar-Spoink-Defrobnit')
+        eq(h.encode(), '''\
+wasnipoop; giraffes="very-long-necked-animals";
+ spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
+
+    def test_long_header_encode_with_tab_continuation(self):
+        eq = self.ndiffAssertEqual
+        h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
+                   'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
+                   header_name='X-Foobar-Spoink-Defrobnit',
+                   continuation_ws='\t')
+        eq(h.encode(), '''\
+wasnipoop; giraffes="very-long-necked-animals";
+\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
+
+    def test_header_splitter(self):
+        eq = self.ndiffAssertEqual
+        msg = MIMEText('')
+        # It'd be great if we could use add_header() here, but that doesn't
+        # guarantee an order of the parameters.
+        msg['X-Foobar-Spoink-Defrobnit'] = (
+            'wasnipoop; giraffes="very-long-necked-animals"; '
+            'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
+        sfp = StringIO()
+        g = Generator(sfp)
+        g.flatten(msg)
+        eq(sfp.getvalue(), '''\
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
+\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
+
+''')
+
+    def test_no_semis_header_splitter(self):
+        eq = self.ndiffAssertEqual
+        msg = Message()
+        msg['From'] = 'test@dom.ain'
+        msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
+        msg.set_payload('Test')
+        sfp = StringIO()
+        g = Generator(sfp)
+        g.flatten(msg)
+        eq(sfp.getvalue(), """\
+From: test@dom.ain
+References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
+\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
+
+Test""")
+
+    def test_no_split_long_header(self):
+        eq = self.ndiffAssertEqual
+        hstr = 'References: ' + 'x' * 80
+        h = Header(hstr, continuation_ws='\t')
+        eq(h.encode(), """\
+References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
+
+    def test_splitting_multiple_long_lines(self):
+        eq = self.ndiffAssertEqual
+        hstr = """\
+from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
+\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
+\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
+"""
+        h = Header(hstr, continuation_ws='\t')
+        eq(h.encode(), """\
+from babylon.socal-raves.org (localhost [127.0.0.1]);
+\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
+\tfor <mailman-admin@babylon.socal-raves.org>;
+\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
+\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
+\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
+\tfor <mailman-admin@babylon.socal-raves.org>;
+\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
+\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
+\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
+\tfor <mailman-admin@babylon.socal-raves.org>;
+\tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
+
+    def test_splitting_first_line_only_is_long(self):
+        eq = self.ndiffAssertEqual
+        hstr = """\
+from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
+\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
+\tid 17k4h5-00034i-00
+\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
+        h = Header(hstr, maxlinelen=78, header_name='Received',
+                   continuation_ws='\t')
+        eq(h.encode(), """\
+from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
+\thelo=cthulhu.gerg.ca)
+\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
+\tid 17k4h5-00034i-00
+\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
+
+    def test_long_8bit_header(self):
+        eq = self.ndiffAssertEqual
+        msg = Message()
+        h = Header('Britische Regierung gibt', 'iso-8859-1',
+                    header_name='Subject')
+        h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
+        msg['Subject'] = h
+        eq(msg.as_string(), """\
+Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
+ =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
+
+""")
+
+    def test_long_8bit_header_no_charset(self):
+        eq = self.ndiffAssertEqual
+        msg = Message()
+        msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
+        eq(msg.as_string(), """\
+Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
+
+""")
+
+    def test_long_to_header(self):
+        eq = self.ndiffAssertEqual
+        to = '"Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,"Someone Test #B" <someone@umich.edu>, "Someone Test #C" <someone@eecs.umich.edu>, "Someone Test #D" <someone@eecs.umich.edu>'
+        msg = Message()
+        msg['To'] = to
+        eq(msg.as_string(0), '''\
+To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
+\t"Someone Test #B" <someone@umich.edu>,
+\t"Someone Test #C" <someone@eecs.umich.edu>,
+\t"Someone Test #D" <someone@eecs.umich.edu>
+
+''')
+
+    def test_long_line_after_append(self):
+        eq = self.ndiffAssertEqual
+        s = 'This is an example of string which has almost the limit of header length.'
+        h = Header(s)
+        h.append('Add another line.')
+        eq(h.encode(), """\
+This is an example of string which has almost the limit of header length.
+ Add another line.""")
+
+    def test_shorter_line_with_append(self):
+        eq = self.ndiffAssertEqual
+        s = 'This is a shorter line.'
+        h = Header(s)
+        h.append('Add another sentence. (Surprise?)')
+        eq(h.encode(),
+           'This is a shorter line. Add another sentence. (Surprise?)')
+
+    def test_long_field_name(self):
+        eq = self.ndiffAssertEqual
+        fn = 'X-Very-Very-Very-Long-Header-Name'
+        gs = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
+        h = Header(gs, 'iso-8859-1', header_name=fn)
+        # BAW: this seems broken because the first line is too long
+        eq(h.encode(), """\
+=?iso-8859-1?q?Die_Mieter_treten_hier_?=
+ =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
+ =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
+ =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
+
+    def test_long_received_header(self):
+        h = 'from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700'
+        msg = Message()
+        msg['Received-1'] = Header(h, continuation_ws='\t')
+        msg['Received-2'] = h
+        self.assertEqual(msg.as_string(), """\
+Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
+\throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
+\tWed, 05 Mar 2003 18:10:18 -0700
+Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
+\throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
+\tWed, 05 Mar 2003 18:10:18 -0700
+
+""")
+
+    def test_string_headerinst_eq(self):
+        h = '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
+        msg = Message()
+        msg['Received-1'] = Header(h, header_name='Received-1',
+                                   continuation_ws='\t')
+        msg['Received-2'] = h
+        self.assertEqual(msg.as_string(), """\
+Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
+\t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
+Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
+\t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
+
+""")
+
+    def test_long_unbreakable_lines_with_continuation(self):
+        eq = self.ndiffAssertEqual
+        msg = Message()
+        t = """\
+ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
+ locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
+        msg['Face-1'] = t
+        msg['Face-2'] = Header(t, header_name='Face-2')
+        eq(msg.as_string(), """\
+Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
+\tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
+Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
+ locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
+
+""")
+
+    def test_another_long_multiline_header(self):
+        eq = self.ndiffAssertEqual
+        m = '''\
+Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
+\tWed, 16 Oct 2002 07:41:11 -0700'''
+        msg = email.message_from_string(m)
+        eq(msg.as_string(), '''\
+Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
+\tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
+
+''')
+
+    def test_long_lines_with_different_header(self):
+        eq = self.ndiffAssertEqual
+        h = """\
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
+        <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
+        msg = Message()
+        msg['List'] = h
+        msg['List'] = Header(h, header_name='List')
+        eq(msg.as_string(), """\
+List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
+\t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
+List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
+ <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
+
+""")
+
+
+
+# Test mangling of "From " lines in the body of a message
+class TestFromMangling(unittest.TestCase):
+    def setUp(self):
+        self.msg = Message()
+        self.msg['From'] = 'aaa@bbb.org'
+        self.msg.set_payload("""\
+From the desk of A.A.A.:
+Blah blah blah
+""")
+
+    def test_mangled_from(self):
+        s = StringIO()
+        g = Generator(s, mangle_from_=True)
+        g.flatten(self.msg)
+        self.assertEqual(s.getvalue(), """\
+From: aaa@bbb.org
+
+>From the desk of A.A.A.:
+Blah blah blah
+""")
+
+    def test_dont_mangle_from(self):
+        s = StringIO()
+        g = Generator(s, mangle_from_=False)
+        g.flatten(self.msg)
+        self.assertEqual(s.getvalue(), """\
+From: aaa@bbb.org
+
+From the desk of A.A.A.:
+Blah blah blah
+""")
+
+
+
+# Test the basic MIMEAudio class
+class TestMIMEAudio(unittest.TestCase):
+    def setUp(self):
+        # Make sure we pick up the audiotest.au that lives in email/test/data.
+        # In Python, there's an audiotest.au living in Lib/test but that isn't
+        # included in some binary distros that don't include the test
+        # package.  The trailing empty string on the .join() is significant
+        # since findfile() will do a dirname().
+        datadir = os.path.join(os.path.dirname(landmark), 'data', '')
+        fp = open(findfile('audiotest.au', datadir), 'rb')
+        try:
+            self._audiodata = fp.read()
+        finally:
+            fp.close()
+        self._au = MIMEAudio(self._audiodata)
+
+    def test_guess_minor_type(self):
+        self.assertEqual(self._au.get_content_type(), 'audio/basic')
+
+    def test_encoding(self):
+        payload = self._au.get_payload()
+        self.assertEqual(base64.decodestring(payload), self._audiodata)
+
+    def test_checkSetMinor(self):
+        au = MIMEAudio(self._audiodata, 'fish')
+        self.assertEqual(au.get_content_type(), 'audio/fish')
+
+    def test_add_header(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        self._au.add_header('Content-Disposition', 'attachment',
+                            filename='audiotest.au')
+        eq(self._au['content-disposition'],
+           'attachment; filename="audiotest.au"')
+        eq(self._au.get_params(header='content-disposition'),
+           [('attachment', ''), ('filename', 'audiotest.au')])
+        eq(self._au.get_param('filename', header='content-disposition'),
+           'audiotest.au')
+        missing = []
+        eq(self._au.get_param('attachment', header='content-disposition'), '')
+        unless(self._au.get_param('foo', failobj=missing,
+                                  header='content-disposition') is missing)
+        # Try some missing stuff
+        unless(self._au.get_param('foobar', missing) is missing)
+        unless(self._au.get_param('attachment', missing,
+                                  header='foobar') is missing)
+
+
+
+# Test the basic MIMEImage class
+class TestMIMEImage(unittest.TestCase):
+    def setUp(self):
+        fp = openfile('PyBanner048.gif')
+        try:
+            self._imgdata = fp.read()
+        finally:
+            fp.close()
+        self._im = MIMEImage(self._imgdata)
+
+    def test_guess_minor_type(self):
+        self.assertEqual(self._im.get_content_type(), 'image/gif')
+
+    def test_encoding(self):
+        payload = self._im.get_payload()
+        self.assertEqual(base64.decodestring(payload), self._imgdata)
+
+    def test_checkSetMinor(self):
+        im = MIMEImage(self._imgdata, 'fish')
+        self.assertEqual(im.get_content_type(), 'image/fish')
+
+    def test_add_header(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        self._im.add_header('Content-Disposition', 'attachment',
+                            filename='dingusfish.gif')
+        eq(self._im['content-disposition'],
+           'attachment; filename="dingusfish.gif"')
+        eq(self._im.get_params(header='content-disposition'),
+           [('attachment', ''), ('filename', 'dingusfish.gif')])
+        eq(self._im.get_param('filename', header='content-disposition'),
+           'dingusfish.gif')
+        missing = []
+        eq(self._im.get_param('attachment', header='content-disposition'), '')
+        unless(self._im.get_param('foo', failobj=missing,
+                                  header='content-disposition') is missing)
+        # Try some missing stuff
+        unless(self._im.get_param('foobar', missing) is missing)
+        unless(self._im.get_param('attachment', missing,
+                                  header='foobar') is missing)
+
+
+
+# Test the basic MIMEApplication class
+class TestMIMEApplication(unittest.TestCase):
+    def test_headers(self):
+        eq = self.assertEqual
+        msg = MIMEApplication('\xfa\xfb\xfc\xfd\xfe\xff')
+        eq(msg.get_content_type(), 'application/octet-stream')
+        eq(msg['content-transfer-encoding'], 'base64')
+
+    def test_body(self):
+        eq = self.assertEqual
+        bytes = '\xfa\xfb\xfc\xfd\xfe\xff'
+        msg = MIMEApplication(bytes)
+        eq(msg.get_payload(), '+vv8/f7/')
+        eq(msg.get_payload(decode=True), bytes)
+
+
+
+# Test the basic MIMEText class
+class TestMIMEText(unittest.TestCase):
+    def setUp(self):
+        self._msg = MIMEText('hello there')
+
+    def test_types(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        eq(self._msg.get_content_type(), 'text/plain')
+        eq(self._msg.get_param('charset'), 'us-ascii')
+        missing = []
+        unless(self._msg.get_param('foobar', missing) is missing)
+        unless(self._msg.get_param('charset', missing, header='foobar')
+               is missing)
+
+    def test_payload(self):
+        self.assertEqual(self._msg.get_payload(), 'hello there')
+        self.failUnless(not self._msg.is_multipart())
+
+    def test_charset(self):
+        eq = self.assertEqual
+        msg = MIMEText('hello there', _charset='us-ascii')
+        eq(msg.get_charset().input_charset, 'us-ascii')
+        eq(msg['content-type'], 'text/plain; charset="us-ascii"')
+
+
+
+# Test complicated multipart/* messages
+class TestMultipart(TestEmailBase):
+    def setUp(self):
+        fp = openfile('PyBanner048.gif')
+        try:
+            data = fp.read()
+        finally:
+            fp.close()
+
+        container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
+        image = MIMEImage(data, name='dingusfish.gif')
+        image.add_header('content-disposition', 'attachment',
+                         filename='dingusfish.gif')
+        intro = MIMEText('''\
+Hi there,
+
+This is the dingus fish.
+''')
+        container.attach(intro)
+        container.attach(image)
+        container['From'] = 'Barry <barry@digicool.com>'
+        container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
+        container['Subject'] = 'Here is your dingus fish'
+
+        now = 987809702.54848599
+        timetuple = time.localtime(now)
+        if timetuple[-1] == 0:
+            tzsecs = time.timezone
+        else:
+            tzsecs = time.altzone
+        if tzsecs > 0:
+            sign = '-'
+        else:
+            sign = '+'
+        tzoffset = ' %s%04d' % (sign, tzsecs / 36)
+        container['Date'] = time.strftime(
+            '%a, %d %b %Y %H:%M:%S',
+            time.localtime(now)) + tzoffset
+        self._msg = container
+        self._im = image
+        self._txt = intro
+
+    def test_hierarchy(self):
+        # convenience
+        eq = self.assertEqual
+        unless = self.failUnless
+        raises = self.assertRaises
+        # tests
+        m = self._msg
+        unless(m.is_multipart())
+        eq(m.get_content_type(), 'multipart/mixed')
+        eq(len(m.get_payload()), 2)
+        raises(IndexError, m.get_payload, 2)
+        m0 = m.get_payload(0)
+        m1 = m.get_payload(1)
+        unless(m0 is self._txt)
+        unless(m1 is self._im)
+        eq(m.get_payload(), [m0, m1])
+        unless(not m0.is_multipart())
+        unless(not m1.is_multipart())
+
+    def test_empty_multipart_idempotent(self):
+        text = """\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+
+--BOUNDARY
+
+
+--BOUNDARY--
+"""
+        msg = Parser().parsestr(text)
+        self.ndiffAssertEqual(text, msg.as_string())
+
+    def test_no_parts_in_a_multipart_with_none_epilogue(self):
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.set_boundary('BOUNDARY')
+        self.ndiffAssertEqual(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+
+--BOUNDARY--''')
+
+    def test_no_parts_in_a_multipart_with_empty_epilogue(self):
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.preamble = ''
+        outer.epilogue = ''
+        outer.set_boundary('BOUNDARY')
+        self.ndiffAssertEqual(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+
+--BOUNDARY
+
+--BOUNDARY--
+''')
+
+    def test_one_part_in_a_multipart(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.set_boundary('BOUNDARY')
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--''')
+
+    def test_seq_parts_in_a_multipart_with_empty_preamble(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.preamble = ''
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        outer.set_boundary('BOUNDARY')
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--''')
+
+
+    def test_seq_parts_in_a_multipart_with_none_preamble(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.preamble = None
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        outer.set_boundary('BOUNDARY')
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--''')
+
+
+    def test_seq_parts_in_a_multipart_with_none_epilogue(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.epilogue = None
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        outer.set_boundary('BOUNDARY')
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--''')
+
+
+    def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.epilogue = ''
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        outer.set_boundary('BOUNDARY')
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--
+''')
+
+
+    def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
+        eq = self.ndiffAssertEqual
+        outer = MIMEBase('multipart', 'mixed')
+        outer['Subject'] = 'A subject'
+        outer['To'] = 'aperson@dom.ain'
+        outer['From'] = 'bperson@dom.ain'
+        outer.epilogue = '\n'
+        msg = MIMEText('hello world')
+        outer.attach(msg)
+        outer.set_boundary('BOUNDARY')
+        eq(outer.as_string(), '''\
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+MIME-Version: 1.0
+Subject: A subject
+To: aperson@dom.ain
+From: bperson@dom.ain
+
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+hello world
+--BOUNDARY--
+
+''')
+
+    def test_message_external_body(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_36.txt')
+        eq(len(msg.get_payload()), 2)
+        msg1 = msg.get_payload(1)
+        eq(msg1.get_content_type(), 'multipart/alternative')
+        eq(len(msg1.get_payload()), 2)
+        for subpart in msg1.get_payload():
+            eq(subpart.get_content_type(), 'message/external-body')
+            eq(len(subpart.get_payload()), 1)
+            subsubpart = subpart.get_payload(0)
+            eq(subsubpart.get_content_type(), 'text/plain')
+
+    def test_double_boundary(self):
+        # msg_37.txt is a multipart that contains two dash-boundary's in a
+        # row.  Our interpretation of RFC 2046 calls for ignoring the second
+        # and subsequent boundaries.
+        msg = self._msgobj('msg_37.txt')
+        self.assertEqual(len(msg.get_payload()), 3)
+
+    def test_nested_inner_contains_outer_boundary(self):
+        eq = self.ndiffAssertEqual
+        # msg_38.txt has an inner part that contains outer boundaries.  My
+        # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
+        # these are illegal and should be interpreted as unterminated inner
+        # parts.
+        msg = self._msgobj('msg_38.txt')
+        sfp = StringIO()
+        iterators._structure(msg, sfp)
+        eq(sfp.getvalue(), """\
+multipart/mixed
+    multipart/mixed
+        multipart/alternative
+            text/plain
+        text/plain
+    text/plain
+    text/plain
+""")
+
+    def test_nested_with_same_boundary(self):
+        eq = self.ndiffAssertEqual
+        # msg 39.txt is similarly evil in that it's got inner parts that use
+        # the same boundary as outer parts.  Again, I believe the way this is
+        # parsed is closest to the spirit of RFC 2046
+        msg = self._msgobj('msg_39.txt')
+        sfp = StringIO()
+        iterators._structure(msg, sfp)
+        eq(sfp.getvalue(), """\
+multipart/mixed
+    multipart/mixed
+        multipart/alternative
+        application/octet-stream
+        application/octet-stream
+    text/plain
+""")
+
+    def test_boundary_in_non_multipart(self):
+        msg = self._msgobj('msg_40.txt')
+        self.assertEqual(msg.as_string(), '''\
+MIME-Version: 1.0
+Content-Type: text/html; boundary="--961284236552522269"
+
+----961284236552522269
+Content-Type: text/html;
+Content-Transfer-Encoding: 7Bit
+
+<html></html>
+
+----961284236552522269--
+''')
+
+    def test_boundary_with_leading_space(self):
+        eq = self.assertEqual
+        msg = email.message_from_string('''\
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="    XXXX"
+
+--    XXXX
+Content-Type: text/plain
+
+
+--    XXXX
+Content-Type: text/plain
+
+--    XXXX--
+''')
+        self.failUnless(msg.is_multipart())
+        eq(msg.get_boundary(), '    XXXX')
+        eq(len(msg.get_payload()), 2)
+
+    def test_boundary_without_trailing_newline(self):
+        m = Parser().parsestr("""\
+Content-Type: multipart/mixed; boundary="===============0012394164=="
+MIME-Version: 1.0
+
+--===============0012394164==
+Content-Type: image/file1.jpg
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+
+YXNkZg==
+--===============0012394164==--""")
+        self.assertEquals(m.get_payload(0).get_payload(), 'YXNkZg==')
+
+
+
+# Test some badly formatted messages
+class TestNonConformant(TestEmailBase):
+    def test_parse_missing_minor_type(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_14.txt')
+        eq(msg.get_content_type(), 'text/plain')
+        eq(msg.get_content_maintype(), 'text')
+        eq(msg.get_content_subtype(), 'plain')
+
+    def test_same_boundary_inner_outer(self):
+        unless = self.failUnless
+        msg = self._msgobj('msg_15.txt')
+        # XXX We can probably eventually do better
+        inner = msg.get_payload(0)
+        unless(hasattr(inner, 'defects'))
+        self.assertEqual(len(inner.defects), 1)
+        unless(isinstance(inner.defects[0],
+                          errors.StartBoundaryNotFoundDefect))
+
+    def test_multipart_no_boundary(self):
+        unless = self.failUnless
+        msg = self._msgobj('msg_25.txt')
+        unless(isinstance(msg.get_payload(), str))
+        self.assertEqual(len(msg.defects), 2)
+        unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
+        unless(isinstance(msg.defects[1],
+                          errors.MultipartInvariantViolationDefect))
+
+    def test_invalid_content_type(self):
+        eq = self.assertEqual
+        neq = self.ndiffAssertEqual
+        msg = Message()
+        # RFC 2045, $5.2 says invalid yields text/plain
+        msg['Content-Type'] = 'text'
+        eq(msg.get_content_maintype(), 'text')
+        eq(msg.get_content_subtype(), 'plain')
+        eq(msg.get_content_type(), 'text/plain')
+        # Clear the old value and try something /really/ invalid
+        del msg['content-type']
+        msg['Content-Type'] = 'foo'
+        eq(msg.get_content_maintype(), 'text')
+        eq(msg.get_content_subtype(), 'plain')
+        eq(msg.get_content_type(), 'text/plain')
+        # Still, make sure that the message is idempotently generated
+        s = StringIO()
+        g = Generator(s)
+        g.flatten(msg)
+        neq(s.getvalue(), 'Content-Type: foo\n\n')
+
+    def test_no_start_boundary(self):
+        eq = self.ndiffAssertEqual
+        msg = self._msgobj('msg_31.txt')
+        eq(msg.get_payload(), """\
+--BOUNDARY
+Content-Type: text/plain
+
+message 1
+
+--BOUNDARY
+Content-Type: text/plain
+
+message 2
+
+--BOUNDARY--
+""")
+
+    def test_no_separating_blank_line(self):
+        eq = self.ndiffAssertEqual
+        msg = self._msgobj('msg_35.txt')
+        eq(msg.as_string(), """\
+From: aperson@dom.ain
+To: bperson@dom.ain
+Subject: here's something interesting
+
+counter to RFC 2822, there's no separating newline here
+""")
+
+    def test_lying_multipart(self):
+        unless = self.failUnless
+        msg = self._msgobj('msg_41.txt')
+        unless(hasattr(msg, 'defects'))
+        self.assertEqual(len(msg.defects), 2)
+        unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
+        unless(isinstance(msg.defects[1],
+                          errors.MultipartInvariantViolationDefect))
+
+    def test_missing_start_boundary(self):
+        outer = self._msgobj('msg_42.txt')
+        # The message structure is:
+        #
+        # multipart/mixed
+        #    text/plain
+        #    message/rfc822
+        #        multipart/mixed [*]
+        #
+        # [*] This message is missing its start boundary
+        bad = outer.get_payload(1).get_payload(0)
+        self.assertEqual(len(bad.defects), 1)
+        self.failUnless(isinstance(bad.defects[0],
+                                   errors.StartBoundaryNotFoundDefect))
+
+
+
+# Test RFC 2047 header encoding and decoding
+class TestRFC2047(unittest.TestCase):
+    def test_rfc2047_multiline(self):
+        eq = self.assertEqual
+        s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
+ foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
+        dh = decode_header(s)
+        eq(dh, [
+            ('Re:', None),
+            ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
+            ('baz foo bar', None),
+            ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
+        eq(str(make_header(dh)),
+           """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
+ =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
+
+    def test_whitespace_eater_unicode(self):
+        eq = self.assertEqual
+        s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
+        dh = decode_header(s)
+        eq(dh, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
+        hu = unicode(make_header(dh)).encode('latin-1')
+        eq(hu, 'Andr\xe9 Pirard <pirard@dom.ain>')
+
+    def test_whitespace_eater_unicode_2(self):
+        eq = self.assertEqual
+        s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
+        dh = decode_header(s)
+        eq(dh, [('The', None), ('quick brown fox', 'iso-8859-1'),
+                ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
+        hu = make_header(dh).__unicode__()
+        eq(hu, u'The quick brown fox jumped over the lazy dog')
+
+
+
+# Test the MIMEMessage class
+class TestMIMEMessage(TestEmailBase):
+    def setUp(self):
+        fp = openfile('msg_11.txt')
+        try:
+            self._text = fp.read()
+        finally:
+            fp.close()
+
+    def test_type_error(self):
+        self.assertRaises(TypeError, MIMEMessage, 'a plain string')
+
+    def test_valid_argument(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        subject = 'A sub-message'
+        m = Message()
+        m['Subject'] = subject
+        r = MIMEMessage(m)
+        eq(r.get_content_type(), 'message/rfc822')
+        payload = r.get_payload()
+        unless(isinstance(payload, list))
+        eq(len(payload), 1)
+        subpart = payload[0]
+        unless(subpart is m)
+        eq(subpart['subject'], subject)
+
+    def test_bad_multipart(self):
+        eq = self.assertEqual
+        msg1 = Message()
+        msg1['Subject'] = 'subpart 1'
+        msg2 = Message()
+        msg2['Subject'] = 'subpart 2'
+        r = MIMEMessage(msg1)
+        self.assertRaises(errors.MultipartConversionError, r.attach, msg2)
+
+    def test_generate(self):
+        # First craft the message to be encapsulated
+        m = Message()
+        m['Subject'] = 'An enclosed message'
+        m.set_payload('Here is the body of the message.\n')
+        r = MIMEMessage(m)
+        r['Subject'] = 'The enclosing message'
+        s = StringIO()
+        g = Generator(s)
+        g.flatten(r)
+        self.assertEqual(s.getvalue(), """\
+Content-Type: message/rfc822
+MIME-Version: 1.0
+Subject: The enclosing message
+
+Subject: An enclosed message
+
+Here is the body of the message.
+""")
+
+    def test_parse_message_rfc822(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        msg = self._msgobj('msg_11.txt')
+        eq(msg.get_content_type(), 'message/rfc822')
+        payload = msg.get_payload()
+        unless(isinstance(payload, list))
+        eq(len(payload), 1)
+        submsg = payload[0]
+        self.failUnless(isinstance(submsg, Message))
+        eq(submsg['subject'], 'An enclosed message')
+        eq(submsg.get_payload(), 'Here is the body of the message.\n')
+
+    def test_dsn(self):
+        eq = self.assertEqual
+        unless = self.failUnless
+        # msg 16 is a Delivery Status Notification, see RFC 1894
+        msg = self._msgobj('msg_16.txt')
+        eq(msg.get_content_type(), 'multipart/report')
+        unless(msg.is_multipart())
+        eq(len(msg.get_payload()), 3)
+        # Subpart 1 is a text/plain, human readable section
+        subpart = msg.get_payload(0)
+        eq(subpart.get_content_type(), 'text/plain')
+        eq(subpart.get_payload(), """\
+This report relates to a message you sent with the following header fields:
+
+  Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
+  Date: Sun, 23 Sep 2001 20:10:55 -0700
+  From: "Ian T. Henry" <henryi@oxy.edu>
+  To: SoCal Raves <scr@socal-raves.org>
+  Subject: [scr] yeah for Ians!!
+
+Your message cannot be delivered to the following recipients:
+
+  Recipient address: jangel1@cougar.noc.ucla.edu
+  Reason: recipient reached disk quota
+
+""")
+        # Subpart 2 contains the machine parsable DSN information.  It
+        # consists of two blocks of headers, represented by two nested Message
+        # objects.
+        subpart = msg.get_payload(1)
+        eq(subpart.get_content_type(), 'message/delivery-status')
+        eq(len(subpart.get_payload()), 2)
+        # message/delivery-status should treat each block as a bunch of
+        # headers, i.e. a bunch of Message objects.
+        dsn1 = subpart.get_payload(0)
+        unless(isinstance(dsn1, Message))
+        eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
+        eq(dsn1.get_param('dns', header='reporting-mta'), '')
+        # Try a missing one <wink>
+        eq(dsn1.get_param('nsd', header='reporting-mta'), None)
+        dsn2 = subpart.get_payload(1)
+        unless(isinstance(dsn2, Message))
+        eq(dsn2['action'], 'failed')
+        eq(dsn2.get_params(header='original-recipient'),
+           [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
+        eq(dsn2.get_param('rfc822', header='final-recipient'), '')
+        # Subpart 3 is the original message
+        subpart = msg.get_payload(2)
+        eq(subpart.get_content_type(), 'message/rfc822')
+        payload = subpart.get_payload()
+        unless(isinstance(payload, list))
+        eq(len(payload), 1)
+        subsubpart = payload[0]
+        unless(isinstance(subsubpart, Message))
+        eq(subsubpart.get_content_type(), 'text/plain')
+        eq(subsubpart['message-id'],
+           '<002001c144a6$8752e060$56104586@oxy.edu>')
+
+    def test_epilogue(self):
+        eq = self.ndiffAssertEqual
+        fp = openfile('msg_21.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        msg = Message()
+        msg['From'] = 'aperson@dom.ain'
+        msg['To'] = 'bperson@dom.ain'
+        msg['Subject'] = 'Test'
+        msg.preamble = 'MIME message'
+        msg.epilogue = 'End of MIME message\n'
+        msg1 = MIMEText('One')
+        msg2 = MIMEText('Two')
+        msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
+        msg.attach(msg1)
+        msg.attach(msg2)
+        sfp = StringIO()
+        g = Generator(sfp)
+        g.flatten(msg)
+        eq(sfp.getvalue(), text)
+
+    def test_no_nl_preamble(self):
+        eq = self.ndiffAssertEqual
+        msg = Message()
+        msg['From'] = 'aperson@dom.ain'
+        msg['To'] = 'bperson@dom.ain'
+        msg['Subject'] = 'Test'
+        msg.preamble = 'MIME message'
+        msg.epilogue = ''
+        msg1 = MIMEText('One')
+        msg2 = MIMEText('Two')
+        msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
+        msg.attach(msg1)
+        msg.attach(msg2)
+        eq(msg.as_string(), """\
+From: aperson@dom.ain
+To: bperson@dom.ain
+Subject: Test
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+MIME message
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+One
+--BOUNDARY
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+Two
+--BOUNDARY--
+""")
+
+    def test_default_type(self):
+        eq = self.assertEqual
+        fp = openfile('msg_30.txt')
+        try:
+            msg = email.message_from_file(fp)
+        finally:
+            fp.close()
+        container1 = msg.get_payload(0)
+        eq(container1.get_default_type(), 'message/rfc822')
+        eq(container1.get_content_type(), 'message/rfc822')
+        container2 = msg.get_payload(1)
+        eq(container2.get_default_type(), 'message/rfc822')
+        eq(container2.get_content_type(), 'message/rfc822')
+        container1a = container1.get_payload(0)
+        eq(container1a.get_default_type(), 'text/plain')
+        eq(container1a.get_content_type(), 'text/plain')
+        container2a = container2.get_payload(0)
+        eq(container2a.get_default_type(), 'text/plain')
+        eq(container2a.get_content_type(), 'text/plain')
+
+    def test_default_type_with_explicit_container_type(self):
+        eq = self.assertEqual
+        fp = openfile('msg_28.txt')
+        try:
+            msg = email.message_from_file(fp)
+        finally:
+            fp.close()
+        container1 = msg.get_payload(0)
+        eq(container1.get_default_type(), 'message/rfc822')
+        eq(container1.get_content_type(), 'message/rfc822')
+        container2 = msg.get_payload(1)
+        eq(container2.get_default_type(), 'message/rfc822')
+        eq(container2.get_content_type(), 'message/rfc822')
+        container1a = container1.get_payload(0)
+        eq(container1a.get_default_type(), 'text/plain')
+        eq(container1a.get_content_type(), 'text/plain')
+        container2a = container2.get_payload(0)
+        eq(container2a.get_default_type(), 'text/plain')
+        eq(container2a.get_content_type(), 'text/plain')
+
+    def test_default_type_non_parsed(self):
+        eq = self.assertEqual
+        neq = self.ndiffAssertEqual
+        # Set up container
+        container = MIMEMultipart('digest', 'BOUNDARY')
+        container.epilogue = ''
+        # Set up subparts
+        subpart1a = MIMEText('message 1\n')
+        subpart2a = MIMEText('message 2\n')
+        subpart1 = MIMEMessage(subpart1a)
+        subpart2 = MIMEMessage(subpart2a)
+        container.attach(subpart1)
+        container.attach(subpart2)
+        eq(subpart1.get_content_type(), 'message/rfc822')
+        eq(subpart1.get_default_type(), 'message/rfc822')
+        eq(subpart2.get_content_type(), 'message/rfc822')
+        eq(subpart2.get_default_type(), 'message/rfc822')
+        neq(container.as_string(0), '''\
+Content-Type: multipart/digest; boundary="BOUNDARY"
+MIME-Version: 1.0
+
+--BOUNDARY
+Content-Type: message/rfc822
+MIME-Version: 1.0
+
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+message 1
+
+--BOUNDARY
+Content-Type: message/rfc822
+MIME-Version: 1.0
+
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+message 2
+
+--BOUNDARY--
+''')
+        del subpart1['content-type']
+        del subpart1['mime-version']
+        del subpart2['content-type']
+        del subpart2['mime-version']
+        eq(subpart1.get_content_type(), 'message/rfc822')
+        eq(subpart1.get_default_type(), 'message/rfc822')
+        eq(subpart2.get_content_type(), 'message/rfc822')
+        eq(subpart2.get_default_type(), 'message/rfc822')
+        neq(container.as_string(0), '''\
+Content-Type: multipart/digest; boundary="BOUNDARY"
+MIME-Version: 1.0
+
+--BOUNDARY
+
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+message 1
+
+--BOUNDARY
+
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+
+message 2
+
+--BOUNDARY--
+''')
+
+    def test_mime_attachments_in_constructor(self):
+        eq = self.assertEqual
+        text1 = MIMEText('')
+        text2 = MIMEText('')
+        msg = MIMEMultipart(_subparts=(text1, text2))
+        eq(len(msg.get_payload()), 2)
+        eq(msg.get_payload(0), text1)
+        eq(msg.get_payload(1), text2)
+
+
+
+# A general test of parser->model->generator idempotency.  IOW, read a message
+# in, parse it into a message object tree, then without touching the tree,
+# regenerate the plain text.  The original text and the transformed text
+# should be identical.  Note: that we ignore the Unix-From since that may
+# contain a changed date.
+class TestIdempotent(TestEmailBase):
+    def _msgobj(self, filename):
+        fp = openfile(filename)
+        try:
+            data = fp.read()
+        finally:
+            fp.close()
+        msg = email.message_from_string(data)
+        return msg, data
+
+    def _idempotent(self, msg, text):
+        eq = self.ndiffAssertEqual
+        s = StringIO()
+        g = Generator(s, maxheaderlen=0)
+        g.flatten(msg)
+        eq(text, s.getvalue())
+
+    def test_parse_text_message(self):
+        eq = self.assertEquals
+        msg, text = self._msgobj('msg_01.txt')
+        eq(msg.get_content_type(), 'text/plain')
+        eq(msg.get_content_maintype(), 'text')
+        eq(msg.get_content_subtype(), 'plain')
+        eq(msg.get_params()[1], ('charset', 'us-ascii'))
+        eq(msg.get_param('charset'), 'us-ascii')
+        eq(msg.preamble, None)
+        eq(msg.epilogue, None)
+        self._idempotent(msg, text)
+
+    def test_parse_untyped_message(self):
+        eq = self.assertEquals
+        msg, text = self._msgobj('msg_03.txt')
+        eq(msg.get_content_type(), 'text/plain')
+        eq(msg.get_params(), None)
+        eq(msg.get_param('charset'), None)
+        self._idempotent(msg, text)
+
+    def test_simple_multipart(self):
+        msg, text = self._msgobj('msg_04.txt')
+        self._idempotent(msg, text)
+
+    def test_MIME_digest(self):
+        msg, text = self._msgobj('msg_02.txt')
+        self._idempotent(msg, text)
+
+    def test_long_header(self):
+        msg, text = self._msgobj('msg_27.txt')
+        self._idempotent(msg, text)
+
+    def test_MIME_digest_with_part_headers(self):
+        msg, text = self._msgobj('msg_28.txt')
+        self._idempotent(msg, text)
+
+    def test_mixed_with_image(self):
+        msg, text = self._msgobj('msg_06.txt')
+        self._idempotent(msg, text)
+
+    def test_multipart_report(self):
+        msg, text = self._msgobj('msg_05.txt')
+        self._idempotent(msg, text)
+
+    def test_dsn(self):
+        msg, text = self._msgobj('msg_16.txt')
+        self._idempotent(msg, text)
+
+    def test_preamble_epilogue(self):
+        msg, text = self._msgobj('msg_21.txt')
+        self._idempotent(msg, text)
+
+    def test_multipart_one_part(self):
+        msg, text = self._msgobj('msg_23.txt')
+        self._idempotent(msg, text)
+
+    def test_multipart_no_parts(self):
+        msg, text = self._msgobj('msg_24.txt')
+        self._idempotent(msg, text)
+
+    def test_no_start_boundary(self):
+        msg, text = self._msgobj('msg_31.txt')
+        self._idempotent(msg, text)
+
+    def test_rfc2231_charset(self):
+        msg, text = self._msgobj('msg_32.txt')
+        self._idempotent(msg, text)
+
+    def test_more_rfc2231_parameters(self):
+        msg, text = self._msgobj('msg_33.txt')
+        self._idempotent(msg, text)
+
+    def test_text_plain_in_a_multipart_digest(self):
+        msg, text = self._msgobj('msg_34.txt')
+        self._idempotent(msg, text)
+
+    def test_nested_multipart_mixeds(self):
+        msg, text = self._msgobj('msg_12a.txt')
+        self._idempotent(msg, text)
+
+    def test_message_external_body_idempotent(self):
+        msg, text = self._msgobj('msg_36.txt')
+        self._idempotent(msg, text)
+
+    def test_content_type(self):
+        eq = self.assertEquals
+        unless = self.failUnless
+        # Get a message object and reset the seek pointer for other tests
+        msg, text = self._msgobj('msg_05.txt')
+        eq(msg.get_content_type(), 'multipart/report')
+        # Test the Content-Type: parameters
+        params = {}
+        for pk, pv in msg.get_params():
+            params[pk] = pv
+        eq(params['report-type'], 'delivery-status')
+        eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
+        eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
+        eq(msg.epilogue, '\n')
+        eq(len(msg.get_payload()), 3)
+        # Make sure the subparts are what we expect
+        msg1 = msg.get_payload(0)
+        eq(msg1.get_content_type(), 'text/plain')
+        eq(msg1.get_payload(), 'Yadda yadda yadda\n')
+        msg2 = msg.get_payload(1)
+        eq(msg2.get_content_type(), 'text/plain')
+        eq(msg2.get_payload(), 'Yadda yadda yadda\n')
+        msg3 = msg.get_payload(2)
+        eq(msg3.get_content_type(), 'message/rfc822')
+        self.failUnless(isinstance(msg3, Message))
+        payload = msg3.get_payload()
+        unless(isinstance(payload, list))
+        eq(len(payload), 1)
+        msg4 = payload[0]
+        unless(isinstance(msg4, Message))
+        eq(msg4.get_payload(), 'Yadda yadda yadda\n')
+
+    def test_parser(self):
+        eq = self.assertEquals
+        unless = self.failUnless
+        msg, text = self._msgobj('msg_06.txt')
+        # Check some of the outer headers
+        eq(msg.get_content_type(), 'message/rfc822')
+        # Make sure the payload is a list of exactly one sub-Message, and that
+        # that submessage has a type of text/plain
+        payload = msg.get_payload()
+        unless(isinstance(payload, list))
+        eq(len(payload), 1)
+        msg1 = payload[0]
+        self.failUnless(isinstance(msg1, Message))
+        eq(msg1.get_content_type(), 'text/plain')
+        self.failUnless(isinstance(msg1.get_payload(), str))
+        eq(msg1.get_payload(), '\n')
+
+
+
+# Test various other bits of the package's functionality
+class TestMiscellaneous(TestEmailBase):
+    def test_message_from_string(self):
+        fp = openfile('msg_01.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        msg = email.message_from_string(text)
+        s = StringIO()
+        # Don't wrap/continue long headers since we're trying to test
+        # idempotency.
+        g = Generator(s, maxheaderlen=0)
+        g.flatten(msg)
+        self.assertEqual(text, s.getvalue())
+
+    def test_message_from_file(self):
+        fp = openfile('msg_01.txt')
+        try:
+            text = fp.read()
+            fp.seek(0)
+            msg = email.message_from_file(fp)
+            s = StringIO()
+            # Don't wrap/continue long headers since we're trying to test
+            # idempotency.
+            g = Generator(s, maxheaderlen=0)
+            g.flatten(msg)
+            self.assertEqual(text, s.getvalue())
+        finally:
+            fp.close()
+
+    def test_message_from_string_with_class(self):
+        unless = self.failUnless
+        fp = openfile('msg_01.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        # Create a subclass
+        class MyMessage(Message):
+            pass
+
+        msg = email.message_from_string(text, MyMessage)
+        unless(isinstance(msg, MyMessage))
+        # Try something more complicated
+        fp = openfile('msg_02.txt')
+        try:
+            text = fp.read()
+        finally:
+            fp.close()
+        msg = email.message_from_string(text, MyMessage)
+        for subpart in msg.walk():
+            unless(isinstance(subpart, MyMessage))
+
+    def test_message_from_file_with_class(self):
+        unless = self.failUnless
+        # Create a subclass
+        class MyMessage(Message):
+            pass
+
+        fp = openfile('msg_01.txt')
+        try:
+            msg = email.message_from_file(fp, MyMessage)
+        finally:
+            fp.close()
+        unless(isinstance(msg, MyMessage))
+        # Try something more complicated
+        fp = openfile('msg_02.txt')
+        try:
+            msg = email.message_from_file(fp, MyMessage)
+        finally:
+            fp.close()
+        for subpart in msg.walk():
+            unless(isinstance(subpart, MyMessage))
+
+    def test__all__(self):
+        module = __import__('email')
+        # Can't use sorted() here due to Python 2.3 compatibility
+        all = module.__all__[:]
+        all.sort()
+        self.assertEqual(all, [
+            # Old names
+            'Charset', 'Encoders', 'Errors', 'Generator',
+            'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
+            'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
+            'MIMENonMultipart', 'MIMEText', 'Message',
+            'Parser', 'Utils', 'base64MIME',
+            # new names
+            'base64mime', 'charset', 'encoders', 'errors', 'generator',
+            'header', 'iterators', 'message', 'message_from_file',
+            'message_from_string', 'mime', 'parser',
+            'quopriMIME', 'quoprimime', 'utils',
+            ])
+
+    def test_formatdate(self):
+        now = time.time()
+        self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
+                         time.gmtime(now)[:6])
+
+    def test_formatdate_localtime(self):
+        now = time.time()
+        self.assertEqual(
+            utils.parsedate(utils.formatdate(now, localtime=True))[:6],
+            time.localtime(now)[:6])
+
+    def test_formatdate_usegmt(self):
+        now = time.time()
+        self.assertEqual(
+            utils.formatdate(now, localtime=False),
+            time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
+        self.assertEqual(
+            utils.formatdate(now, localtime=False, usegmt=True),
+            time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
+
+    def test_parsedate_none(self):
+        self.assertEqual(utils.parsedate(''), None)
+
+    def test_parsedate_compact(self):
+        # The FWS after the comma is optional
+        self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
+                         utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
+
+    def test_parsedate_no_dayofweek(self):
+        eq = self.assertEqual
+        eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
+           (2003, 2, 25, 13, 47, 26, 0, 1, 0, -28800))
+
+    def test_parsedate_compact_no_dayofweek(self):
+        eq = self.assertEqual
+        eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
+           (2003, 2, 5, 13, 47, 26, 0, 1, 0, -28800))
+
+    def test_parsedate_acceptable_to_time_functions(self):
+        eq = self.assertEqual
+        timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
+        t = int(time.mktime(timetup))
+        eq(time.localtime(t)[:6], timetup[:6])
+        eq(int(time.strftime('%Y', timetup)), 2003)
+        timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
+        t = int(time.mktime(timetup[:9]))
+        eq(time.localtime(t)[:6], timetup[:6])
+        eq(int(time.strftime('%Y', timetup[:9])), 2003)
+
+    def test_parseaddr_empty(self):
+        self.assertEqual(utils.parseaddr('<>'), ('', ''))
+        self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
+
+    def test_noquote_dump(self):
+        self.assertEqual(
+            utils.formataddr(('A Silly Person', 'person@dom.ain')),
+            'A Silly Person <person@dom.ain>')
+
+    def test_escape_dump(self):
+        self.assertEqual(
+            utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
+            r'"A \(Very\) Silly Person" <person@dom.ain>')
+        a = r'A \(Special\) Person'
+        b = 'person@dom.ain'
+        self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
+
+    def test_escape_backslashes(self):
+        self.assertEqual(
+            utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
+            r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
+        a = r'Arthur \Backslash\ Foobar'
+        b = 'person@dom.ain'
+        self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
+
+    def test_name_with_dot(self):
+        x = 'John X. Doe <jxd@example.com>'
+        y = '"John X. Doe" <jxd@example.com>'
+        a, b = ('John X. Doe', 'jxd@example.com')
+        self.assertEqual(utils.parseaddr(x), (a, b))
+        self.assertEqual(utils.parseaddr(y), (a, b))
+        # formataddr() quotes the name if there's a dot in it
+        self.assertEqual(utils.formataddr((a, b)), y)
+
+    def test_quote_dump(self):
+        self.assertEqual(
+            utils.formataddr(('A Silly; Person', 'person@dom.ain')),
+            r'"A Silly; Person" <person@dom.ain>')
+
+    def test_fix_eols(self):
+        eq = self.assertEqual
+        eq(utils.fix_eols('hello'), 'hello')
+        eq(utils.fix_eols('hello\n'), 'hello\r\n')
+        eq(utils.fix_eols('hello\r'), 'hello\r\n')
+        eq(utils.fix_eols('hello\r\n'), 'hello\r\n')
+        eq(utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
+
+    def test_charset_richcomparisons(self):
+        eq = self.assertEqual
+        ne = self.failIfEqual
+        cset1 = Charset()
+        cset2 = Charset()
+        eq(cset1, 'us-ascii')
+        eq(cset1, 'US-ASCII')
+        eq(cset1, 'Us-AsCiI')
+        eq('us-ascii', cset1)
+        eq('US-ASCII', cset1)
+        eq('Us-AsCiI', cset1)
+        ne(cset1, 'usascii')
+        ne(cset1, 'USASCII')
+        ne(cset1, 'UsAsCiI')
+        ne('usascii', cset1)
+        ne('USASCII', cset1)
+        ne('UsAsCiI', cset1)
+        eq(cset1, cset2)
+        eq(cset2, cset1)
+
+    def test_getaddresses(self):
+        eq = self.assertEqual
+        eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
+                               'Bud Person <bperson@dom.ain>']),
+           [('Al Person', 'aperson@dom.ain'),
+            ('Bud Person', 'bperson@dom.ain')])
+
+    def test_getaddresses_nasty(self):
+        eq = self.assertEqual
+        eq(utils.getaddresses(['foo: ;']), [('', '')])
+        eq(utils.getaddresses(
+           ['[]*-- =~$']),
+           [('', ''), ('', ''), ('', '*--')])
+        eq(utils.getaddresses(
+           ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
+           [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
+
+    def test_utils_quote_unquote(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.add_header('content-disposition', 'attachment',
+                       filename='foo\\wacky"name')
+        eq(msg.get_filename(), 'foo\\wacky"name')
+
+    def test_get_body_encoding_with_bogus_charset(self):
+        charset = Charset('not a charset')
+        self.assertEqual(charset.get_body_encoding(), 'base64')
+
+    def test_get_body_encoding_with_uppercase_charset(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg['Content-Type'] = 'text/plain; charset=UTF-8'
+        eq(msg['content-type'], 'text/plain; charset=UTF-8')
+        charsets = msg.get_charsets()
+        eq(len(charsets), 1)
+        eq(charsets[0], 'utf-8')
+        charset = Charset(charsets[0])
+        eq(charset.get_body_encoding(), 'base64')
+        msg.set_payload('hello world', charset=charset)
+        eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
+        eq(msg.get_payload(decode=True), 'hello world')
+        eq(msg['content-transfer-encoding'], 'base64')
+        # Try another one
+        msg = Message()
+        msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
+        charsets = msg.get_charsets()
+        eq(len(charsets), 1)
+        eq(charsets[0], 'us-ascii')
+        charset = Charset(charsets[0])
+        eq(charset.get_body_encoding(), encoders.encode_7or8bit)
+        msg.set_payload('hello world', charset=charset)
+        eq(msg.get_payload(), 'hello world')
+        eq(msg['content-transfer-encoding'], '7bit')
+
+    def test_charsets_case_insensitive(self):
+        lc = Charset('us-ascii')
+        uc = Charset('US-ASCII')
+        self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
+
+    def test_partial_falls_inside_message_delivery_status(self):
+        eq = self.ndiffAssertEqual
+        # The Parser interface provides chunks of data to FeedParser in 8192
+        # byte gulps.  SF bug #1076485 found one of those chunks inside
+        # message/delivery-status header block, which triggered an
+        # unreadline() of NeedMoreData.
+        msg = self._msgobj('msg_43.txt')
+        sfp = StringIO()
+        iterators._structure(msg, sfp)
+        eq(sfp.getvalue(), """\
+multipart/report
+    text/plain
+    message/delivery-status
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+        text/plain
+    text/rfc822-headers
+""")
+
+
+
+# Test the iterator/generators
+class TestIterators(TestEmailBase):
+    def test_body_line_iterator(self):
+        eq = self.assertEqual
+        neq = self.ndiffAssertEqual
+        # First a simple non-multipart message
+        msg = self._msgobj('msg_01.txt')
+        it = iterators.body_line_iterator(msg)
+        lines = list(it)
+        eq(len(lines), 6)
+        neq(EMPTYSTRING.join(lines), msg.get_payload())
+        # Now a more complicated multipart
+        msg = self._msgobj('msg_02.txt')
+        it = iterators.body_line_iterator(msg)
+        lines = list(it)
+        eq(len(lines), 43)
+        fp = openfile('msg_19.txt')
+        try:
+            neq(EMPTYSTRING.join(lines), fp.read())
+        finally:
+            fp.close()
+
+    def test_typed_subpart_iterator(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_04.txt')
+        it = iterators.typed_subpart_iterator(msg, 'text')
+        lines = []
+        subparts = 0
+        for subpart in it:
+            subparts += 1
+            lines.append(subpart.get_payload())
+        eq(subparts, 2)
+        eq(EMPTYSTRING.join(lines), """\
+a simple kind of mirror
+to reflect upon our own
+a simple kind of mirror
+to reflect upon our own
+""")
+
+    def test_typed_subpart_iterator_default_type(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_03.txt')
+        it = iterators.typed_subpart_iterator(msg, 'text', 'plain')
+        lines = []
+        subparts = 0
+        for subpart in it:
+            subparts += 1
+            lines.append(subpart.get_payload())
+        eq(subparts, 1)
+        eq(EMPTYSTRING.join(lines), """\
+
+Hi,
+
+Do you like this message?
+
+-Me
+""")
+
+
+
+class TestParsers(TestEmailBase):
+    def test_header_parser(self):
+        eq = self.assertEqual
+        # Parse only the headers of a complex multipart MIME document
+        fp = openfile('msg_02.txt')
+        try:
+            msg = HeaderParser().parse(fp)
+        finally:
+            fp.close()
+        eq(msg['from'], 'ppp-request@zzz.org')
+        eq(msg['to'], 'ppp@zzz.org')
+        eq(msg.get_content_type(), 'multipart/mixed')
+        self.failIf(msg.is_multipart())
+        self.failUnless(isinstance(msg.get_payload(), str))
+
+    def test_whitespace_continuation(self):
+        eq = self.assertEqual
+        # This message contains a line after the Subject: header that has only
+        # whitespace, but it is not empty!
+        msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: bperson@dom.ain
+Subject: the next line has a space on it
+\x20
+Date: Mon, 8 Apr 2002 15:09:19 -0400
+Message-ID: spam
+
+Here's the message body
+""")
+        eq(msg['subject'], 'the next line has a space on it\n ')
+        eq(msg['message-id'], 'spam')
+        eq(msg.get_payload(), "Here's the message body\n")
+
+    def test_whitespace_continuation_last_header(self):
+        eq = self.assertEqual
+        # Like the previous test, but the subject line is the last
+        # header.
+        msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: bperson@dom.ain
+Date: Mon, 8 Apr 2002 15:09:19 -0400
+Message-ID: spam
+Subject: the next line has a space on it
+\x20
+
+Here's the message body
+""")
+        eq(msg['subject'], 'the next line has a space on it\n ')
+        eq(msg['message-id'], 'spam')
+        eq(msg.get_payload(), "Here's the message body\n")
+
+    def test_crlf_separation(self):
+        eq = self.assertEqual
+        fp = openfile('msg_26.txt', mode='rb')
+        try:
+            msg = Parser().parse(fp)
+        finally:
+            fp.close()
+        eq(len(msg.get_payload()), 2)
+        part1 = msg.get_payload(0)
+        eq(part1.get_content_type(), 'text/plain')
+        eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
+        part2 = msg.get_payload(1)
+        eq(part2.get_content_type(), 'application/riscos')
+
+    def test_multipart_digest_with_extra_mime_headers(self):
+        eq = self.assertEqual
+        neq = self.ndiffAssertEqual
+        fp = openfile('msg_28.txt')
+        try:
+            msg = email.message_from_file(fp)
+        finally:
+            fp.close()
+        # Structure is:
+        # multipart/digest
+        #   message/rfc822
+        #     text/plain
+        #   message/rfc822
+        #     text/plain
+        eq(msg.is_multipart(), 1)
+        eq(len(msg.get_payload()), 2)
+        part1 = msg.get_payload(0)
+        eq(part1.get_content_type(), 'message/rfc822')
+        eq(part1.is_multipart(), 1)
+        eq(len(part1.get_payload()), 1)
+        part1a = part1.get_payload(0)
+        eq(part1a.is_multipart(), 0)
+        eq(part1a.get_content_type(), 'text/plain')
+        neq(part1a.get_payload(), 'message 1\n')
+        # next message/rfc822
+        part2 = msg.get_payload(1)
+        eq(part2.get_content_type(), 'message/rfc822')
+        eq(part2.is_multipart(), 1)
+        eq(len(part2.get_payload()), 1)
+        part2a = part2.get_payload(0)
+        eq(part2a.is_multipart(), 0)
+        eq(part2a.get_content_type(), 'text/plain')
+        neq(part2a.get_payload(), 'message 2\n')
+
+    def test_three_lines(self):
+        # A bug report by Andrew McNamara
+        lines = ['From: Andrew Person <aperson@dom.ain',
+                 'Subject: Test',
+                 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
+        msg = email.message_from_string(NL.join(lines))
+        self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
+
+    def test_strip_line_feed_and_carriage_return_in_headers(self):
+        eq = self.assertEqual
+        # For [ 1002475 ] email message parser doesn't handle \r\n correctly
+        value1 = 'text'
+        value2 = 'more text'
+        m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
+            value1, value2)
+        msg = email.message_from_string(m)
+        eq(msg.get('Header'), value1)
+        eq(msg.get('Next-Header'), value2)
+
+    def test_rfc2822_header_syntax(self):
+        eq = self.assertEqual
+        m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
+        msg = email.message_from_string(m)
+        eq(len(msg.keys()), 3)
+        keys = msg.keys()
+        keys.sort()
+        eq(keys, ['!"#QUX;~', '>From', 'From'])
+        eq(msg.get_payload(), 'body')
+
+    def test_rfc2822_space_not_allowed_in_header(self):
+        eq = self.assertEqual
+        m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
+        msg = email.message_from_string(m)
+        eq(len(msg.keys()), 0)
+
+    def test_rfc2822_one_character_header(self):
+        eq = self.assertEqual
+        m = 'A: first header\nB: second header\nCC: third header\n\nbody'
+        msg = email.message_from_string(m)
+        headers = msg.keys()
+        headers.sort()
+        eq(headers, ['A', 'B', 'CC'])
+        eq(msg.get_payload(), 'body')
+
+
+
+class TestBase64(unittest.TestCase):
+    def test_len(self):
+        eq = self.assertEqual
+        eq(base64mime.base64_len('hello'),
+           len(base64mime.encode('hello', eol='')))
+        for size in range(15):
+            if   size == 0 : bsize = 0
+            elif size <= 3 : bsize = 4
+            elif size <= 6 : bsize = 8
+            elif size <= 9 : bsize = 12
+            elif size <= 12: bsize = 16
+            else           : bsize = 20
+            eq(base64mime.base64_len('x'*size), bsize)
+
+    def test_decode(self):
+        eq = self.assertEqual
+        eq(base64mime.decode(''), '')
+        eq(base64mime.decode('aGVsbG8='), 'hello')
+        eq(base64mime.decode('aGVsbG8=', 'X'), 'hello')
+        eq(base64mime.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
+
+    def test_encode(self):
+        eq = self.assertEqual
+        eq(base64mime.encode(''), '')
+        eq(base64mime.encode('hello'), 'aGVsbG8=\n')
+        # Test the binary flag
+        eq(base64mime.encode('hello\n'), 'aGVsbG8K\n')
+        eq(base64mime.encode('hello\n', 0), 'aGVsbG8NCg==\n')
+        # Test the maxlinelen arg
+        eq(base64mime.encode('xxxx ' * 20, maxlinelen=40), """\
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
+eHh4eCB4eHh4IA==
+""")
+        # Test the eol argument
+        eq(base64mime.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
+eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
+eHh4eCB4eHh4IA==\r
+""")
+
+    def test_header_encode(self):
+        eq = self.assertEqual
+        he = base64mime.header_encode
+        eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
+        eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
+        # Test the charset option
+        eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
+        # Test the keep_eols flag
+        eq(he('hello\nworld', keep_eols=True),
+           '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
+        # Test the maxlinelen argument
+        eq(he('xxxx ' * 20, maxlinelen=40), """\
+=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
+ =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
+ =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
+ =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
+ =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
+ =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
+        # Test the eol argument
+        eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
+=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
+ =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
+ =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
+ =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
+ =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
+ =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
+
+
+
+class TestQuopri(unittest.TestCase):
+    def setUp(self):
+        self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
+                    [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
+                    [chr(x) for x in range(ord('0'), ord('9')+1)] + \
+                    ['!', '*', '+', '-', '/', ' ']
+        self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
+        assert len(self.hlit) + len(self.hnon) == 256
+        self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
+        self.blit.remove('=')
+        self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
+        assert len(self.blit) + len(self.bnon) == 256
+
+    def test_header_quopri_check(self):
+        for c in self.hlit:
+            self.failIf(quoprimime.header_quopri_check(c))
+        for c in self.hnon:
+            self.failUnless(quoprimime.header_quopri_check(c))
+
+    def test_body_quopri_check(self):
+        for c in self.blit:
+            self.failIf(quoprimime.body_quopri_check(c))
+        for c in self.bnon:
+            self.failUnless(quoprimime.body_quopri_check(c))
+
+    def test_header_quopri_len(self):
+        eq = self.assertEqual
+        hql = quoprimime.header_quopri_len
+        enc = quoprimime.header_encode
+        for s in ('hello', 'h@e@l@l@o@'):
+            # Empty charset and no line-endings.  7 == RFC chrome
+            eq(hql(s), len(enc(s, charset='', eol=''))-7)
+        for c in self.hlit:
+            eq(hql(c), 1)
+        for c in self.hnon:
+            eq(hql(c), 3)
+
+    def test_body_quopri_len(self):
+        eq = self.assertEqual
+        bql = quoprimime.body_quopri_len
+        for c in self.blit:
+            eq(bql(c), 1)
+        for c in self.bnon:
+            eq(bql(c), 3)
+
+    def test_quote_unquote_idempotent(self):
+        for x in range(256):
+            c = chr(x)
+            self.assertEqual(quoprimime.unquote(quoprimime.quote(c)), c)
+
+    def test_header_encode(self):
+        eq = self.assertEqual
+        he = quoprimime.header_encode
+        eq(he('hello'), '=?iso-8859-1?q?hello?=')
+        eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
+        # Test the charset option
+        eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
+        # Test the keep_eols flag
+        eq(he('hello\nworld', keep_eols=True), '=?iso-8859-1?q?hello=0Aworld?=')
+        # Test a non-ASCII character
+        eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
+        # Test the maxlinelen argument
+        eq(he('xxxx ' * 20, maxlinelen=40), """\
+=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
+ =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
+ =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
+ =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
+ =?iso-8859-1?q?x_xxxx_xxxx_?=""")
+        # Test the eol argument
+        eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
+=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
+ =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
+ =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
+ =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
+ =?iso-8859-1?q?x_xxxx_xxxx_?=""")
+
+    def test_decode(self):
+        eq = self.assertEqual
+        eq(quoprimime.decode(''), '')
+        eq(quoprimime.decode('hello'), 'hello')
+        eq(quoprimime.decode('hello', 'X'), 'hello')
+        eq(quoprimime.decode('hello\nworld', 'X'), 'helloXworld')
+
+    def test_encode(self):
+        eq = self.assertEqual
+        eq(quoprimime.encode(''), '')
+        eq(quoprimime.encode('hello'), 'hello')
+        # Test the binary flag
+        eq(quoprimime.encode('hello\r\nworld'), 'hello\nworld')
+        eq(quoprimime.encode('hello\r\nworld', 0), 'hello\nworld')
+        # Test the maxlinelen arg
+        eq(quoprimime.encode('xxxx ' * 20, maxlinelen=40), """\
+xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
+ xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
+x xxxx xxxx xxxx xxxx=20""")
+        # Test the eol argument
+        eq(quoprimime.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
+xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
+ xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
+x xxxx xxxx xxxx xxxx=20""")
+        eq(quoprimime.encode("""\
+one line
+
+two line"""), """\
+one line
+
+two line""")
+
+
+
+# Test the Charset class
+class TestCharset(unittest.TestCase):
+    def tearDown(self):
+        from email import charset as CharsetModule
+        try:
+            del CharsetModule.CHARSETS['fake']
+        except KeyError:
+            pass
+
+    def test_idempotent(self):
+        eq = self.assertEqual
+        # Make sure us-ascii = no Unicode conversion
+        c = Charset('us-ascii')
+        s = 'Hello World!'
+        sp = c.to_splittable(s)
+        eq(s, c.from_splittable(sp))
+        # test 8-bit idempotency with us-ascii
+        s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
+        sp = c.to_splittable(s)
+        eq(s, c.from_splittable(sp))
+
+    def test_body_encode(self):
+        eq = self.assertEqual
+        # Try a charset with QP body encoding
+        c = Charset('iso-8859-1')
+        eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
+        # Try a charset with Base64 body encoding
+        c = Charset('utf-8')
+        eq('aGVsbG8gd29ybGQ=\n', c.body_encode('hello world'))
+        # Try a charset with None body encoding
+        c = Charset('us-ascii')
+        eq('hello world', c.body_encode('hello world'))
+        # Try the convert argument, where input codec <> output codec
+        c = Charset('euc-jp')
+        # With apologies to Tokio Kikuchi ;)
+        try:
+            eq('\x1b$B5FCO;~IW\x1b(B',
+               c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
+            eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
+               c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
+        except LookupError:
+            # We probably don't have the Japanese codecs installed
+            pass
+        # Testing SF bug #625509, which we have to fake, since there are no
+        # built-in encodings where the header encoding is QP but the body
+        # encoding is not.
+        from email import charset as CharsetModule
+        CharsetModule.add_charset('fake', CharsetModule.QP, None)
+        c = Charset('fake')
+        eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
+
+    def test_unicode_charset_name(self):
+        charset = Charset(u'us-ascii')
+        self.assertEqual(str(charset), 'us-ascii')
+        self.assertRaises(errors.CharsetError, Charset, 'asc\xffii')
+
+
+
+# Test multilingual MIME headers.
+class TestHeader(TestEmailBase):
+    def test_simple(self):
+        eq = self.ndiffAssertEqual
+        h = Header('Hello World!')
+        eq(h.encode(), 'Hello World!')
+        h.append(' Goodbye World!')
+        eq(h.encode(), 'Hello World!  Goodbye World!')
+
+    def test_simple_surprise(self):
+        eq = self.ndiffAssertEqual
+        h = Header('Hello World!')
+        eq(h.encode(), 'Hello World!')
+        h.append('Goodbye World!')
+        eq(h.encode(), 'Hello World! Goodbye World!')
+
+    def test_header_needs_no_decoding(self):
+        h = 'no decoding needed'
+        self.assertEqual(decode_header(h), [(h, None)])
+
+    def test_long(self):
+        h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
+                   maxlinelen=76)
+        for l in h.encode(splitchars=' ').split('\n '):
+            self.failUnless(len(l) <= 76)
+
+    def test_multilingual(self):
+        eq = self.ndiffAssertEqual
+        g = Charset("iso-8859-1")
+        cz = Charset("iso-8859-2")
+        utf8 = Charset("utf-8")
+        g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
+        cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
+        utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
+        h = Header(g_head, g)
+        h.append(cz_head, cz)
+        h.append(utf8_head, utf8)
+        enc = h.encode()
+        eq(enc, """\
+=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
+ =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
+ =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
+ =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
+ =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
+ =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
+ =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
+ =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
+ =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
+ =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
+ =?utf-8?b?44CC?=""")
+        eq(decode_header(enc),
+           [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
+            (utf8_head, "utf-8")])
+        ustr = unicode(h)
+        eq(ustr.encode('utf-8'),
+           'Die Mieter treten hier ein werden mit einem Foerderband '
+           'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
+           'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
+           'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
+           'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
+           '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
+           '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
+           '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
+           '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
+           '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
+           '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
+           '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
+           '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
+           'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
+           'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
+           '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
+        # Test make_header()
+        newh = make_header(decode_header(enc))
+        eq(newh, enc)
+
+    def test_header_ctor_default_args(self):
+        eq = self.ndiffAssertEqual
+        h = Header()
+        eq(h, '')
+        h.append('foo', Charset('iso-8859-1'))
+        eq(h, '=?iso-8859-1?q?foo?=')
+
+    def test_explicit_maxlinelen(self):
+        eq = self.ndiffAssertEqual
+        hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
+        h = Header(hstr)
+        eq(h.encode(), '''\
+A very long line that must get split to something other than at the 76th
+ character boundary to test the non-default behavior''')
+        h = Header(hstr, header_name='Subject')
+        eq(h.encode(), '''\
+A very long line that must get split to something other than at the
+ 76th character boundary to test the non-default behavior''')
+        h = Header(hstr, maxlinelen=1024, header_name='Subject')
+        eq(h.encode(), hstr)
+
+    def test_us_ascii_header(self):
+        eq = self.assertEqual
+        s = 'hello'
+        x = decode_header(s)
+        eq(x, [('hello', None)])
+        h = make_header(x)
+        eq(s, h.encode())
+
+    def test_string_charset(self):
+        eq = self.assertEqual
+        h = Header()
+        h.append('hello', 'iso-8859-1')
+        eq(h, '=?iso-8859-1?q?hello?=')
+
+##    def test_unicode_error(self):
+##        raises = self.assertRaises
+##        raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
+##        raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
+##        h = Header()
+##        raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
+##        raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
+##        raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
+
+    def test_utf8_shortest(self):
+        eq = self.assertEqual
+        h = Header(u'p\xf6stal', 'utf-8')
+        eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
+        h = Header(u'\u83ca\u5730\u6642\u592b', 'utf-8')
+        eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
+
+    def test_bad_8bit_header(self):
+        raises = self.assertRaises
+        eq = self.assertEqual
+        x = 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
+        raises(UnicodeError, Header, x)
+        h = Header()
+        raises(UnicodeError, h.append, x)
+        eq(str(Header(x, errors='replace')), x)
+        h.append(x, errors='replace')
+        eq(str(h), x)
+
+    def test_encoded_adjacent_nonencoded(self):
+        eq = self.assertEqual
+        h = Header()
+        h.append('hello', 'iso-8859-1')
+        h.append('world')
+        s = h.encode()
+        eq(s, '=?iso-8859-1?q?hello?= world')
+        h = make_header(decode_header(s))
+        eq(h.encode(), s)
+
+    def test_whitespace_eater(self):
+        eq = self.assertEqual
+        s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
+        parts = decode_header(s)
+        eq(parts, [('Subject:', None), ('\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), ('zz.', None)])
+        hdr = make_header(parts)
+        eq(hdr.encode(),
+           'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
+
+    def test_broken_base64_header(self):
+        raises = self.assertRaises
+        s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
+        raises(errors.HeaderParseError, decode_header, s)
+
+
+
+# Test RFC 2231 header parameters (en/de)coding
+class TestRFC2231(TestEmailBase):
+    def test_get_param(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_29.txt')
+        eq(msg.get_param('title'),
+           ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
+        eq(msg.get_param('title', unquote=False),
+           ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
+
+    def test_set_param(self):
+        eq = self.assertEqual
+        msg = Message()
+        msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
+                      charset='us-ascii')
+        eq(msg.get_param('title'),
+           ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
+        msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
+                      charset='us-ascii', language='en')
+        eq(msg.get_param('title'),
+           ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
+        msg = self._msgobj('msg_01.txt')
+        msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
+                      charset='us-ascii', language='en')
+        eq(msg.as_string(), """\
+Return-Path: <bbb@zzz.org>
+Delivered-To: bbb@zzz.org
+Received: by mail.zzz.org (Postfix, from userid 889)
+\tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
+From: bbb@ddd.com (John X. Doe)
+To: bbb@zzz.org
+Subject: This is a test message
+Date: Fri, 4 May 2001 14:05:44 -0400
+Content-Type: text/plain; charset=us-ascii;
+\ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
+
+
+Hi,
+
+Do you like this message?
+
+-Me
+""")
+
+    def test_del_param(self):
+        eq = self.ndiffAssertEqual
+        msg = self._msgobj('msg_01.txt')
+        msg.set_param('foo', 'bar', charset='us-ascii', language='en')
+        msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
+            charset='us-ascii', language='en')
+        msg.del_param('foo', header='Content-Type')
+        eq(msg.as_string(), """\
+Return-Path: <bbb@zzz.org>
+Delivered-To: bbb@zzz.org
+Received: by mail.zzz.org (Postfix, from userid 889)
+\tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
+From: bbb@ddd.com (John X. Doe)
+To: bbb@zzz.org
+Subject: This is a test message
+Date: Fri, 4 May 2001 14:05:44 -0400
+Content-Type: text/plain; charset="us-ascii";
+\ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
+
+
+Hi,
+
+Do you like this message?
+
+-Me
+""")
+
+    def test_rfc2231_get_content_charset(self):
+        eq = self.assertEqual
+        msg = self._msgobj('msg_32.txt')
+        eq(msg.get_content_charset(), 'us-ascii')
+
+    def test_rfc2231_no_language_or_charset(self):
+        m = '''\
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
+Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
+
+'''
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_param('NAME'),
+                         (None, None, 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm'))
+
+    def test_rfc2231_no_language_or_charset_in_filename(self):
+        m = '''\
+Content-Disposition: inline;
+\tfilename*0="This%20is%20even%20more%20";
+\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
+\tfilename*2="is it not.pdf"
+
+'''
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_filename(),
+                         'This is even more ***fun*** is it not.pdf')
+
+    def test_rfc2231_no_language_or_charset_in_boundary(self):
+        m = '''\
+Content-Type: multipart/alternative;
+\tboundary*0="This%20is%20even%20more%20";
+\tboundary*1="%2A%2A%2Afun%2A%2A%2A%20";
+\tboundary*2="is it not.pdf"
+
+'''
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_boundary(),
+                         'This is even more ***fun*** is it not.pdf')
+
+    def test_rfc2231_no_language_or_charset_in_charset(self):
+        # This is a nonsensical charset value, but tests the code anyway
+        m = '''\
+Content-Type: text/plain;
+\tcharset*0="This%20is%20even%20more%20";
+\tcharset*1="%2A%2A%2Afun%2A%2A%2A%20";
+\tcharset*2="is it not.pdf"
+
+'''
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_content_charset(),
+                         'this is even more ***fun*** is it not.pdf')
+
+    def test_rfc2231_unknown_encoding(self):
+        m = """\
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename*0=X-UNKNOWN''myfile.txt
+
+"""
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_filename(), 'myfile.txt')
+
+
+
+def _testclasses():
+    mod = sys.modules[__name__]
+    return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
+
+
+def suite():
+    suite = unittest.TestSuite()
+    for testclass in _testclasses():
+        suite.addTest(unittest.makeSuite(testclass))
+    return suite
+
+
+def test_main():
+    for testclass in _testclasses():
+        run_unittest(testclass)
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
new file mode 100644
index 0000000..250eb19
--- /dev/null
+++ b/Lib/email/utils.py
@@ -0,0 +1,306 @@
+# Copyright (C) 2001-2006 Python Software Foundation
+# Author: Barry Warsaw
+# Contact: email-sig@python.org
+
+"""Miscellaneous utilities."""
+
+__all__ = [
+    'collapse_rfc2231_value',
+    'decode_params',
+    'decode_rfc2231',
+    'encode_rfc2231',
+    'formataddr',
+    'formatdate',
+    'getaddresses',
+    'make_msgid',
+    'parseaddr',
+    'parsedate',
+    'parsedate_tz',
+    'unquote',
+    ]
+
+import os
+import re
+import time
+import base64
+import random
+import socket
+import warnings
+from cStringIO import StringIO
+
+from email._parseaddr import quote
+from email._parseaddr import AddressList as _AddressList
+from email._parseaddr import mktime_tz
+
+# We need wormarounds for bugs in these methods in older Pythons (see below)
+from email._parseaddr import parsedate as _parsedate
+from email._parseaddr import parsedate_tz as _parsedate_tz
+
+from quopri import decodestring as _qdecode
+
+# Intrapackage imports
+from email.encoders import _bencode, _qencode
+
+COMMASPACE = ', '
+EMPTYSTRING = ''
+UEMPTYSTRING = u''
+CRLF = '\r\n'
+
+specialsre = re.compile(r'[][\\()<>@,:;".]')
+escapesre = re.compile(r'[][\\()"]')
+
+
+
+# Helpers
+
+def _identity(s):
+    return s
+
+
+def _bdecode(s):
+    # We can't quite use base64.encodestring() since it tacks on a "courtesy
+    # newline".  Blech!
+    if not s:
+        return s
+    value = base64.decodestring(s)
+    if not s.endswith('\n') and value.endswith('\n'):
+        return value[:-1]
+    return value
+
+
+
+def fix_eols(s):
+    """Replace all line-ending characters with \r\n."""
+    # Fix newlines with no preceding carriage return
+    s = re.sub(r'(?<!\r)\n', CRLF, s)
+    # Fix carriage returns with no following newline
+    s = re.sub(r'\r(?!\n)', CRLF, s)
+    return s
+
+
+
+def formataddr(pair):
+    """The inverse of parseaddr(), this takes a 2-tuple of the form
+    (realname, email_address) and returns the string value suitable
+    for an RFC 2822 From, To or Cc header.
+
+    If the first element of pair is false, then the second element is
+    returned unmodified.
+    """
+    name, address = pair
+    if name:
+        quotes = ''
+        if specialsre.search(name):
+            quotes = '"'
+        name = escapesre.sub(r'\\\g<0>', name)
+        return '%s%s%s <%s>' % (quotes, name, quotes, address)
+    return address
+
+
+
+def getaddresses(fieldvalues):
+    """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
+    all = COMMASPACE.join(fieldvalues)
+    a = _AddressList(all)
+    return a.addresslist
+
+
+
+ecre = re.compile(r'''
+  =\?                   # literal =?
+  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
+  \?                    # literal ?
+  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
+  \?                    # literal ?
+  (?P<atom>.*?)         # non-greedy up to the next ?= is the atom
+  \?=                   # literal ?=
+  ''', re.VERBOSE | re.IGNORECASE)
+
+
+
+def formatdate(timeval=None, localtime=False, usegmt=False):
+    """Returns a date string as specified by RFC 2822, e.g.:
+
+    Fri, 09 Nov 2001 01:08:47 -0000
+
+    Optional timeval if given is a floating point time value as accepted by
+    gmtime() and localtime(), otherwise the current time is used.
+
+    Optional localtime is a flag that when True, interprets timeval, and
+    returns a date relative to the local timezone instead of UTC, properly
+    taking daylight savings time into account.
+
+    Optional argument usegmt means that the timezone is written out as
+    an ascii string, not numeric one (so "GMT" instead of "+0000"). This
+    is needed for HTTP, and is only used when localtime==False.
+    """
+    # Note: we cannot use strftime() because that honors the locale and RFC
+    # 2822 requires that day and month names be the English abbreviations.
+    if timeval is None:
+        timeval = time.time()
+    if localtime:
+        now = time.localtime(timeval)
+        # Calculate timezone offset, based on whether the local zone has
+        # daylight savings time, and whether DST is in effect.
+        if time.daylight and now[-1]:
+            offset = time.altzone
+        else:
+            offset = time.timezone
+        hours, minutes = divmod(abs(offset), 3600)
+        # Remember offset is in seconds west of UTC, but the timezone is in
+        # minutes east of UTC, so the signs differ.
+        if offset > 0:
+            sign = '-'
+        else:
+            sign = '+'
+        zone = '%s%02d%02d' % (sign, hours, minutes // 60)
+    else:
+        now = time.gmtime(timeval)
+        # Timezone offset is always -0000
+        if usegmt:
+            zone = 'GMT'
+        else:
+            zone = '-0000'
+    return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
+        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],
+        now[2],
+        ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],
+        now[0], now[3], now[4], now[5],
+        zone)
+
+
+
+def make_msgid(idstring=None):
+    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
+
+    <20020201195627.33539.96671@nightshade.la.mastaler.com>
+
+    Optional idstring if given is a string used to strengthen the
+    uniqueness of the message id.
+    """
+    timeval = time.time()
+    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
+    pid = os.getpid()
+    randint = random.randrange(100000)
+    if idstring is None:
+        idstring = ''
+    else:
+        idstring = '.' + idstring
+    idhost = socket.getfqdn()
+    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
+    return msgid
+
+
+
+# These functions are in the standalone mimelib version only because they've
+# subsequently been fixed in the latest Python versions.  We use this to worm
+# around broken older Pythons.
+def parsedate(data):
+    if not data:
+        return None
+    return _parsedate(data)
+
+
+def parsedate_tz(data):
+    if not data:
+        return None
+    return _parsedate_tz(data)
+
+
+def parseaddr(addr):
+    addrs = _AddressList(addr).addresslist
+    if not addrs:
+        return '', ''
+    return addrs[0]
+
+
+# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.
+def unquote(str):
+    """Remove quotes from a string."""
+    if len(str) > 1:
+        if str.startswith('"') and str.endswith('"'):
+            return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
+        if str.startswith('<') and str.endswith('>'):
+            return str[1:-1]
+    return str
+
+
+
+# RFC2231-related functions - parameter encoding and decoding
+def decode_rfc2231(s):
+    """Decode string according to RFC 2231"""
+    import urllib
+    parts = s.split("'", 2)
+    if len(parts) == 1:
+        return None, None, urllib.unquote(s)
+    charset, language, s = parts
+    return charset, language, urllib.unquote(s)
+
+
+def encode_rfc2231(s, charset=None, language=None):
+    """Encode string according to RFC 2231.
+
+    If neither charset nor language is given, then s is returned as-is.  If
+    charset is given but not language, the string is encoded using the empty
+    string for language.
+    """
+    import urllib
+    s = urllib.quote(s, safe='')
+    if charset is None and language is None:
+        return s
+    if language is None:
+        language = ''
+    return "%s'%s'%s" % (charset, language, s)
+
+
+rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$')
+
+def decode_params(params):
+    """Decode parameters list according to RFC 2231.
+
+    params is a sequence of 2-tuples containing (content type, string value).
+    """
+    new_params = []
+    # maps parameter's name to a list of continuations
+    rfc2231_params = {}
+    # params is a sequence of 2-tuples containing (content_type, string value)
+    name, value = params[0]
+    new_params.append((name, value))
+    # Cycle through each of the rest of the parameters.
+    for name, value in params[1:]:
+        value = unquote(value)
+        mo = rfc2231_continuation.match(name)
+        if mo:
+            name, num = mo.group('name', 'num')
+            if num is not None:
+                num = int(num)
+            rfc2231_param1 = rfc2231_params.setdefault(name, [])
+            rfc2231_param1.append((num, value))
+        else:
+            new_params.append((name, '"%s"' % quote(value)))
+    if rfc2231_params:
+        for name, continuations in rfc2231_params.items():
+            value = []
+            # Sort by number
+            continuations.sort()
+            # And now append all values in num order
+            for num, continuation in continuations:
+                value.append(continuation)
+            charset, language, value = decode_rfc2231(EMPTYSTRING.join(value))
+            new_params.append(
+                (name, (charset, language, '"%s"' % quote(value))))
+    return new_params
+
+def collapse_rfc2231_value(value, errors='replace',
+                           fallback_charset='us-ascii'):
+    if isinstance(value, tuple):
+        rawval = unquote(value[2])
+        charset = value[0] or 'us-ascii'
+        try:
+            return unicode(rawval, charset, errors)
+        except LookupError:
+            # XXX charset is unknown to Python.
+            return unicode(rawval, fallback_charset, errors)
+    else:
+        return unquote(value)
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index ceb7a3d..87572ca 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -170,7 +170,7 @@ class PyclbrTest(TestCase):
         cm('pydoc')
 
         # Tests for modules inside packages
-        cm('email.Parser')
+        cm('email.parser')
         cm('test.test_pyclbr')
 
 
-- 
cgit v0.12

