[Python] Mime Mail 클래스
#! /usr/bin/python
# Copyright (C) 2000 Mag. Christian Tanzer. All rights reserved
# Glasauergasse 32, A--1130 Wien, Austria. tanzer@swing.co.at
# ****************************************************************************
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# ****************************************************************************
#
#++
# Name
# Mime Mail
#
# Purpose
# Model a MIME mail
#
# Revision Dates
# 10-Jan-2000 (CT) Creation
# 11-Jan-2000 (CT) Creation continued
# 1-Feb-2000 (CT) `formatted': don't format parts with subtype `html'
#
#--
import mimetools
import quopri
import re
import string
from binascii import a2b uu, a2b base64, a2b hqx
from mimify import *
from multifile import MultiFile
from cStringIO import StringIO
def a2b qp (data) :
"""Return a block of binary quoted `data' as string"""
result = StringIO ()
quopri.decode (StringIO (data), result)
return result.getvalue ()
# end def a2b qp
class Mime Mail (mimetools.Message) :
"""MIME mail: represents the headers and parts of a mime mail"""
file name = ""
def init (self, file, seekable = 0) :
"""Create a new message instance from `file'.
`file' can be the name of a file or any input object supporting
the `readline' method (see documentation rfc822.Message).
"""
if type (file) == type ("") :
self.file name = file
file = open (file, "r")
seekable = 1
mimetools.Message. init (self, file, seekable)
self.parts = []
self. leader = ""
self.mfile = mfile = MultiFile (file, seekable)
if self.maintype == "multipart" :
self.multipart = 1
self.boundary = boundary = self.getparam ("boundary")
mfile.push (boundary)
head = mfile.read ()
if self. add part (head) :
self. leader = ""
else :
self. leader = head
mfile.next ()
while not mfile.last :
self. add part (mfile.read ())
mfile.next ()
else :
self.multipart = 0
self.boundary = ""
text = mfile.read ()
if string.strip (text) :
self.parts.append (text)
if self.file name :
self.fp.close ()
# end def init
def getattr (self, name) :
if name == "attachement name" :
return self. attachement name ()
elif name == "encoding" :
return self.getencoding ()
elif name == "receiver address" :
return self. address (self.receiver header name) [1]
elif name == "receiver name" :
return mime decode header (self. address
(self.receiver header name) [0]
)
elif name == "sender address" :
return self. address (self.sender header name) [1]
elif name == "sender name" :
return mime decode header (self. address
(self.sender header name) [0]
)
else :
raise AttributeError, name
# end def getattr
def add part (self, text) :
msg = Mime Mail (StringIO (text))
if msg.type == "message/rfc822" and len (msg.parts) == 1:
msg.parts [0] = Mime Mail (StringIO (msg.parts [0]))
msg.multipart = 1
msg.boundary = msg.getparam ("boundary") or ""
msg. leader = ""
if msg :
self.parts.append (msg)
return msg
# end def add part
def write (self, file, write headers = 1, as bindary = 0) :
"""Write message `self' to `file', which must be a writable object
with file-semantic or a filename.
"""
if type (file) == type ("") :
file = open (file, "w")
if write headers and self.headers :
file.write (string.join (self.headers, ""))
file.write ("\n")
if self.multipart :
file.write (self. leader)
for p in self.parts :
if not p : continue
if self.boundary :
file.write (self.mfile.section divider (self.boundary))
file.write ("\n")
p.write (file)
if self.boundary :
file.write (self.mfile.end marker (self.boundary))
file.write ("\n")
else :
if as bindary :
file.write (self.as binary ())
else :
file.write (self.parts [0])
# end def write
date header name = ("date", "delivery-date")
sender header name = ("from", "reply-to", "return-path")
receiver header name = ("to", "cc")
def address (self, header names) :
for n in header names :
result = self.getaddr (n)
if type (result [0]) == type ("") : return result
return (None, None)
# end def sender
def all headers (self, header names) :
for n in header names :
result = self.getallmatchingheaders (n)
if result :
pat = re.compile (r"^%s\s*:\s*" % re.escape (n), re.I)
result = map (lambda h, p = pat : p.sub ("", h), result)
return (n, result)
return ("", ())
# end def all headers
def first header (self, header names) :
for n in header names :
result = self.getheader (n)
if result : return (n, result)
return ("", "")
# end def first header
name pat = re.compile ('''name="(?P<name> [^"]+)"''', re.X)
def attachement name (self) :
result = self.getparam ("name")
if not result :
for h in self.headers :
match = self.name pat.search (h)
if match :
return match.group ("name")
return result
# end def attachement name
def nonzero (self) :
return len (self.headers) or (self.parts and len (self.parts [0])) or 0
# end def nonzero
def str (self) :
if self.multipart :
return "Multipart message from %s" % self.getheader ("From")
else :
return self.parts [0]
# end def str
def repr (self) :
return self.type
# end def repr
a2b converter = { "base64" : a2b base64
, "binhex4" : a2b hqx
, "quoted-printable" : a2b qp
, "uuencode" : a2b uu
, "x-uuencode" : a2b uu
}
def as binary (self) :
"""Returns all parts converted from mime encodings to binary strings."""
if self.multipart :
return map (lambda p : p.as binary (), self.parts)
else :
if not self.parts : return ""
converter = self.a2b converter.get (self.encoding)
if converter :
return converter (self.parts [0])
return self.parts [0]
# end def convert to binary
def formatted (self, separator length = 79, n = "") :
"""Returns a string containing the mail in a format suitable for
printing.
"""
result = []
header = self. formatted headers ()
if header :
result.append (header)
leader = string.strip (self. leader)
if leader :
if header :
result.append ("\n" + ("-" * separator length) + "\n")
result.append (a2b qp (leader))
if self.multipart :
i = 0
for p in self.parts :
i = i + 1
if n :
pn = "%s.%s" % (n, i)
else :
pn = i
if ( p.maintype not in ("text", "message", "multipart")
or p.subtype in ("html", )) :
r = string.join (p.headers, "")
else :
r = p.formatted (separator length, pn)
r = string.strip (r)
if r :
ph = "\n%s part %s " % ("-" * 5, pn)
result.append ("%s-%s" % (ph, "-" * (separator length - len (ph))))
result.append (r)
else :
if header or leader :
result.append ("\n" + ("-" * separator length) + "\n")
result.append (self.as binary ())
return string.join (filter (None, result) or "", "\n")
# end def formatted
def formatted headers (self) :
result = []
(dn, date) = self. first header (self.date header name)
(fn, snd) = self. all headers (self.sender header name)
(tn, rcv) = self. all headers (self.receiver header name)
subject = self.getheader ("subject")
result.append (self. formatted header ("Date", "", date))
result.append (self. formatted address ("From", snd))
result.append (self. formatted address ("To", rcv))
result.append (self. formatted header ("Subject", "", subject))
return string.join (filter (None, result) or "", "\n")
# end def formatted headers
label width = 8
ws pat = re.compile (r"\s+")
def formatted header (self, label, continuation tail, * lines) :
if not (lines and lines [0]) : return
if len (label) > self.label width :
print "Label `%s' too long: %s > %s" % \
(label [:40], len (label), self.label width)
return
lines = map (mime decode header, lines)
head = "%-*s: " % (self.label width, label)
tail = self. break lines ( 79 - len (head) - len (continuation tail), lines)
result = "%s%s" % (head, string.join ( tail or ""
, "%s\n%*s"
% ( continuation tail
, len (head), " "
)
)
)
return result
# end def formatted header
def formatted address (self, label, lines) :
line = self.ws pat.sub (" ", string.strip (string.join (lines, " ")))
lines = tuple (string.split (line, ","))
return apply (self. formatted header, (label, ",") + lines)
# end def formatted address
def break lines (self, space, lines) :
result = []
for l in lines :
l = self.ws pat.sub (" ", string.strip (l))
if not l : continue
while len (l) > space :
i = string.rfind (l, " ", 0, space)
if i < 0 : i = len (l)
result.append (l [:i])
l = l [i + 1:]
if l :
result.append (l)
return result
# end def break lines
# end class Mime Mail
-------------------------------------------------------------------------------
# Copyright (C) 2000 Mag. Christian Tanzer. All rights reserved
# Glasauergasse 32, A--1130 Wien, Austria. tanzer@swing.co.at
# ****************************************************************************
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# ****************************************************************************
#
#++
# Name
# Mime Mail
#
# Purpose
# Model a MIME mail
#
# Revision Dates
# 10-Jan-2000 (CT) Creation
# 11-Jan-2000 (CT) Creation continued
# 1-Feb-2000 (CT) `formatted': don't format parts with subtype `html'
#
#--
import mimetools
import quopri
import re
import string
from binascii import a2b uu, a2b base64, a2b hqx
from mimify import *
from multifile import MultiFile
from cStringIO import StringIO
def a2b qp (data) :
"""Return a block of binary quoted `data' as string"""
result = StringIO ()
quopri.decode (StringIO (data), result)
return result.getvalue ()
# end def a2b qp
class Mime Mail (mimetools.Message) :
"""MIME mail: represents the headers and parts of a mime mail"""
file name = ""
def init (self, file, seekable = 0) :
"""Create a new message instance from `file'.
`file' can be the name of a file or any input object supporting
the `readline' method (see documentation rfc822.Message).
"""
if type (file) == type ("") :
self.file name = file
file = open (file, "r")
seekable = 1
mimetools.Message. init (self, file, seekable)
self.parts = []
self. leader = ""
self.mfile = mfile = MultiFile (file, seekable)
if self.maintype == "multipart" :
self.multipart = 1
self.boundary = boundary = self.getparam ("boundary")
mfile.push (boundary)
head = mfile.read ()
if self. add part (head) :
self. leader = ""
else :
self. leader = head
mfile.next ()
while not mfile.last :
self. add part (mfile.read ())
mfile.next ()
else :
self.multipart = 0
self.boundary = ""
text = mfile.read ()
if string.strip (text) :
self.parts.append (text)
if self.file name :
self.fp.close ()
# end def init
def getattr (self, name) :
if name == "attachement name" :
return self. attachement name ()
elif name == "encoding" :
return self.getencoding ()
elif name == "receiver address" :
return self. address (self.receiver header name) [1]
elif name == "receiver name" :
return mime decode header (self. address
(self.receiver header name) [0]
)
elif name == "sender address" :
return self. address (self.sender header name) [1]
elif name == "sender name" :
return mime decode header (self. address
(self.sender header name) [0]
)
else :
raise AttributeError, name
# end def getattr
def add part (self, text) :
msg = Mime Mail (StringIO (text))
if msg.type == "message/rfc822" and len (msg.parts) == 1:
msg.parts [0] = Mime Mail (StringIO (msg.parts [0]))
msg.multipart = 1
msg.boundary = msg.getparam ("boundary") or ""
msg. leader = ""
if msg :
self.parts.append (msg)
return msg
# end def add part
def write (self, file, write headers = 1, as bindary = 0) :
"""Write message `self' to `file', which must be a writable object
with file-semantic or a filename.
"""
if type (file) == type ("") :
file = open (file, "w")
if write headers and self.headers :
file.write (string.join (self.headers, ""))
file.write ("\n")
if self.multipart :
file.write (self. leader)
for p in self.parts :
if not p : continue
if self.boundary :
file.write (self.mfile.section divider (self.boundary))
file.write ("\n")
p.write (file)
if self.boundary :
file.write (self.mfile.end marker (self.boundary))
file.write ("\n")
else :
if as bindary :
file.write (self.as binary ())
else :
file.write (self.parts [0])
# end def write
date header name = ("date", "delivery-date")
sender header name = ("from", "reply-to", "return-path")
receiver header name = ("to", "cc")
def address (self, header names) :
for n in header names :
result = self.getaddr (n)
if type (result [0]) == type ("") : return result
return (None, None)
# end def sender
def all headers (self, header names) :
for n in header names :
result = self.getallmatchingheaders (n)
if result :
pat = re.compile (r"^%s\s*:\s*" % re.escape (n), re.I)
result = map (lambda h, p = pat : p.sub ("", h), result)
return (n, result)
return ("", ())
# end def all headers
def first header (self, header names) :
for n in header names :
result = self.getheader (n)
if result : return (n, result)
return ("", "")
# end def first header
name pat = re.compile ('''name="(?P<name> [^"]+)"''', re.X)
def attachement name (self) :
result = self.getparam ("name")
if not result :
for h in self.headers :
match = self.name pat.search (h)
if match :
return match.group ("name")
return result
# end def attachement name
def nonzero (self) :
return len (self.headers) or (self.parts and len (self.parts [0])) or 0
# end def nonzero
def str (self) :
if self.multipart :
return "Multipart message from %s" % self.getheader ("From")
else :
return self.parts [0]
# end def str
def repr (self) :
return self.type
# end def repr
a2b converter = { "base64" : a2b base64
, "binhex4" : a2b hqx
, "quoted-printable" : a2b qp
, "uuencode" : a2b uu
, "x-uuencode" : a2b uu
}
def as binary (self) :
"""Returns all parts converted from mime encodings to binary strings."""
if self.multipart :
return map (lambda p : p.as binary (), self.parts)
else :
if not self.parts : return ""
converter = self.a2b converter.get (self.encoding)
if converter :
return converter (self.parts [0])
return self.parts [0]
# end def convert to binary
def formatted (self, separator length = 79, n = "") :
"""Returns a string containing the mail in a format suitable for
printing.
"""
result = []
header = self. formatted headers ()
if header :
result.append (header)
leader = string.strip (self. leader)
if leader :
if header :
result.append ("\n" + ("-" * separator length) + "\n")
result.append (a2b qp (leader))
if self.multipart :
i = 0
for p in self.parts :
i = i + 1
if n :
pn = "%s.%s" % (n, i)
else :
pn = i
if ( p.maintype not in ("text", "message", "multipart")
or p.subtype in ("html", )) :
r = string.join (p.headers, "")
else :
r = p.formatted (separator length, pn)
r = string.strip (r)
if r :
ph = "\n%s part %s " % ("-" * 5, pn)
result.append ("%s-%s" % (ph, "-" * (separator length - len (ph))))
result.append (r)
else :
if header or leader :
result.append ("\n" + ("-" * separator length) + "\n")
result.append (self.as binary ())
return string.join (filter (None, result) or "", "\n")
# end def formatted
def formatted headers (self) :
result = []
(dn, date) = self. first header (self.date header name)
(fn, snd) = self. all headers (self.sender header name)
(tn, rcv) = self. all headers (self.receiver header name)
subject = self.getheader ("subject")
result.append (self. formatted header ("Date", "", date))
result.append (self. formatted address ("From", snd))
result.append (self. formatted address ("To", rcv))
result.append (self. formatted header ("Subject", "", subject))
return string.join (filter (None, result) or "", "\n")
# end def formatted headers
label width = 8
ws pat = re.compile (r"\s+")
def formatted header (self, label, continuation tail, * lines) :
if not (lines and lines [0]) : return
if len (label) > self.label width :
print "Label `%s' too long: %s > %s" % \
(label [:40], len (label), self.label width)
return
lines = map (mime decode header, lines)
head = "%-*s: " % (self.label width, label)
tail = self. break lines ( 79 - len (head) - len (continuation tail), lines)
result = "%s%s" % (head, string.join ( tail or ""
, "%s\n%*s"
% ( continuation tail
, len (head), " "
)
)
)
return result
# end def formatted header
def formatted address (self, label, lines) :
line = self.ws pat.sub (" ", string.strip (string.join (lines, " ")))
lines = tuple (string.split (line, ","))
return apply (self. formatted header, (label, ",") + lines)
# end def formatted address
def break lines (self, space, lines) :
result = []
for l in lines :
l = self.ws pat.sub (" ", string.strip (l))
if not l : continue
while len (l) > space :
i = string.rfind (l, " ", 0, space)
if i < 0 : i = len (l)
result.append (l [:i])
l = l [i + 1:]
if l :
result.append (l)
return result
# end def break lines
# end class Mime Mail
-------------------------------------------------------------------------------