Fix lots of py3isms. Email package is borked in py3, however.

This commit is contained in:
Stuart D. Gathman
2016-09-21 17:24:37 -04:00
parent 70fa47dac6
commit bae79a4f1c
10 changed files with 62 additions and 83 deletions
+3 -3
View File
@@ -48,7 +48,7 @@
from __future__ import print_function from __future__ import print_function
import time import time
from plock import PLock from Milter.plock import PLock
class AddrCache(object): class AddrCache(object):
time_format = '%Y%b%d %H:%M:%S %Z' time_format = '%Y%b%d %H:%M:%S %Z'
@@ -132,8 +132,8 @@ class AddrCache(object):
if not ts or ts > too_old: if not ts or ts > too_old:
return res return res
del self.cache[lsender] del self.cache[lsender]
raise KeyError, sender raise KeyError(sender)
except KeyError,x: except KeyError as x:
try: try:
user,host = sender.split('@',1) user,host = sender.split('@',1)
return self.__getitem__(host) return self.__getitem__(host)
+4 -1
View File
@@ -2,7 +2,10 @@ import time
import logging import logging
import urllib import urllib
import sqlite3 import sqlite3
import thread try:
import thread
except:
import _thread as thread
from datetime import datetime from datetime import datetime
log = logging.getLogger('milter.greylist') log = logging.getLogger('milter.greylist')
+3 -3
View File
@@ -11,7 +11,7 @@ class PLock(object):
self.basename = basename self.basename = basename
self.fp = None self.fp = None
def lock(self,lockname=None,mode=0660,strict_perms=False): def lock(self,lockname=None,mode=0o660,strict_perms=False):
"Start an update transaction. Return FILE to write new version." "Start an update transaction. Return FILE to write new version."
self.unlock() self.unlock()
if not lockname: if not lockname:
@@ -21,7 +21,7 @@ class PLock(object):
st = os.stat(self.basename) st = os.stat(self.basename)
mode |= st.st_mode mode |= st.st_mode
except OSError: pass except OSError: pass
u = os.umask(0002) u = os.umask(0o2)
try: try:
fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode) fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode)
finally: finally:
@@ -46,7 +46,7 @@ class PLock(object):
def commit(self,backname=None): def commit(self,backname=None):
"Commit update transaction with optional backup file." "Commit update transaction with optional backup file."
if not self.fp: if not self.fp:
raise IOError,"File not locked" raise IOError("File not locked")
self.fp.close() self.fp.close()
self.fp = None self.fp = None
if backname: if backname:
+1 -1
View File
@@ -85,7 +85,7 @@ def inet_pton(p):
::1.2.3.4.5 ::1.2.3.4.5
""" """
if p == '::': if p == '::':
return '\0'*16 return b'\0'*16
s = p s = p
m = RE_IP4.search(s) m = RE_IP4.search(s)
try: try:
+15 -31
View File
@@ -2,7 +2,7 @@
# A test framework for milters # A test framework for milters
from __future__ import print_function from __future__ import print_function
import rfc822 import mime
try: try:
from StringIO import StringIO from StringIO import StringIO
except: except:
@@ -62,11 +62,11 @@ class TestBase(object):
self._body.write(chunk) self._body.write(chunk)
self._bodyreplaced = True self._bodyreplaced = True
else: else:
raise IOError,"replacebody not called from eom()" raise IOError("replacebody not called from eom()")
def chgfrom(self,sender,params=None): def chgfrom(self,sender,params=None):
if not self._body: if not self._body:
raise IOError,"chgfrom not called from eom()" raise IOError("chgfrom not called from eom()")
self.log('chgfrom: sender=%s' % (sender)) self.log('chgfrom: sender=%s' % (sender))
self._envfromchanged = True self._envfromchanged = True
self._sender = sender self._sender = sender
@@ -83,7 +83,7 @@ class TestBase(object):
# work for a %milter # work for a %milter
def chgheader(self,field,idx,value): def chgheader(self,field,idx,value):
if not self._body: if not self._body:
raise IOError,"chgheader not called from eom()" raise IOError("chgheader not called from eom()")
self.log('chgheader: %s[%d]=%s' % (field,idx,value)) self.log('chgheader: %s[%d]=%s' % (field,idx,value))
if value == '': if value == '':
del self._msg[field] del self._msg[field]
@@ -93,19 +93,19 @@ class TestBase(object):
def addheader(self,field,value,idx=-1): def addheader(self,field,value,idx=-1):
if not self._body: if not self._body:
raise IOError,"addheader not called from eom()" raise IOError("addheader not called from eom()")
self.log('addheader: %s=%s' % (field,value)) self.log('addheader: %s=%s' % (field,value))
self._msg[field] = value self._msg[field] = value
self._headerschanged = True self._headerschanged = True
def delrcpt(self,rcpt): def delrcpt(self,rcpt):
if not self._body: if not self._body:
raise IOError,"delrcpt not called from eom()" raise IOError("delrcpt not called from eom()")
self._delrcpt.append(rcpt) self._delrcpt.append(rcpt)
def addrcpt(self,rcpt): def addrcpt(self,rcpt):
if not self._body: if not self._body:
raise IOError,"addrcpt not called from eom()" raise IOError("addrcpt not called from eom()")
self._addrcpt.append(rcpt) self._addrcpt.append(rcpt)
## Save the reply codes and messages in self._reply. ## Save the reply codes and messages in self._reply.
@@ -143,34 +143,21 @@ class TestBase(object):
self._headerschanged = False self._headerschanged = False
self._reply = None self._reply = None
self._sender = '<%s>'%sender self._sender = '<%s>'%sender
msg = rfc822.Message(fp) msg = mime.message_from_file(fp)
rc = self.envfrom(self._sender) rc = self.envfrom(self._sender)
if rc != Milter.CONTINUE: return rc if rc != Milter.CONTINUE: return rc
for rcpt in (rcpt,) + rcpts: for rcpt in (rcpt,) + rcpts:
rc = self.envrcpt('<%s>'%rcpt) rc = self.envrcpt('<%s>'%rcpt)
if rc != Milter.CONTINUE: return rc if rc != Milter.CONTINUE: return rc
line = None for h,val in msg.items():
for h in msg.headers: rc = self.header(h,val)
if h[:1].isspace():
line = line + h
continue
if not line:
line = h
continue
s = line.split(': ',1)
if len(s) > 1: val = s[1].strip()
else: val = ''
rc = self.header(s[0],val)
if rc != Milter.CONTINUE: return rc
line = h
if line:
s = line.split(': ',1)
rc = self.header(s[0],s[1])
if rc != Milter.CONTINUE: return rc if rc != Milter.CONTINUE: return rc
rc = self.eoh() rc = self.eoh()
if rc != Milter.CONTINUE: return rc if rc != Milter.CONTINUE: return rc
header,body = msg.as_bytes().split(b'\n\n',1)
bfp = StringIO(body)
while 1: while 1:
buf = fp.read(8192) buf = bfp.read(8192)
if len(buf) == 0: break if len(buf) == 0: break
rc = self.body(buf) rc = self.body(buf)
if rc != Milter.CONTINUE: return rc if rc != Milter.CONTINUE: return rc
@@ -179,12 +166,9 @@ class TestBase(object):
rc = self.eom() rc = self.eom()
if self._bodyreplaced: if self._bodyreplaced:
body = self._body.getvalue() body = self._body.getvalue()
else:
msg.rewindbody()
body = msg.fp.read()
self._body = StringIO() self._body = StringIO()
self._body.writelines(msg.headers) self._body.write(header)
self._body.write('\n') self._body.write('\n\n')
self._body.write(body) self._body.write(body)
return rc return rc
+10 -12
View File
@@ -5,13 +5,11 @@
import re import re
import struct import struct
import socket import socket
import email.Errors import email.errors
from email.header import decode_header
import email.base64mime import email.base64mime
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from email.Header import decode_header
from binascii import a2b_base64 from binascii import a2b_base64
#import email.Utils
import rfc822
dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE) dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE)
PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4) PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)
@@ -56,8 +54,8 @@ if hasattr(socket,'has_ipv6') and socket.has_ipv6:
else: else:
from pyip6 import inet_ntop, inet_pton from pyip6 import inet_ntop, inet_pton
MASK = 0xFFFFFFFFL MASK = 0xFFFFFFFF
MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
def cidr(i,n,mask=MASK): def cidr(i,n,mask=MASK):
return ~(mask >> n) & mask & i return ~(mask >> n) & mask & i
@@ -119,7 +117,7 @@ def iniplist(ipaddr,iplist):
return False return False
## Split email into Fullname and address. ## Split email into Fullname and address.
# This replaces <code>email.Utils.parseaddr</code> but fixes # This replaces <code>email.utils.parseaddr</code> but fixes
# some <a href="http://bugs.python.org/issue1025395">tricky test cases</a>. # some <a href="http://bugs.python.org/issue1025395">tricky test cases</a>.
# Additional tricky cases are still broken. Patches welcome. # Additional tricky cases are still broken. Patches welcome.
# #
@@ -139,8 +137,8 @@ def parseaddr(t):
>>> parseaddr('a(WRONG)@b') >>> parseaddr('a(WRONG)@b')
('WRONG', 'a@b') ('WRONG', 'a@b')
""" """
#return email.Utils.parseaddr(t) #return email.utils.parseaddr(t)
res = rfc822.parseaddr(t) res = email.utils.parseaddr(t)
# dirty fix for some broken cases # dirty fix for some broken cases
if not res[0]: if not res[0]:
pos = t.find('<') pos = t.find('<')
@@ -149,7 +147,7 @@ def parseaddr(t):
pos1 = addrspec.rfind(':') pos1 = addrspec.rfind(':')
if pos1 > 0: if pos1 > 0:
addrspec = addrspec[pos1+1:] addrspec = addrspec[pos1+1:]
return rfc822.parseaddr('"%s" <%s>' % (t[:pos].strip(),addrspec)) return email.utils.parseaddr('"%s" <%s>' % (t[:pos].strip(),addrspec))
if not res[1]: if not res[1]:
pos = t.find('<') pos = t.find('<')
if pos > 0 and t[-1] == '>': if pos > 0 and t[-1] == '>':
@@ -157,7 +155,7 @@ def parseaddr(t):
pos1 = addrspec.rfind(':') pos1 = addrspec.rfind(':')
if pos1 > 0: if pos1 > 0:
addrspec = addrspec[pos1+1:] addrspec = addrspec[pos1+1:]
return rfc822.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec)) return email.utils.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec))
return res return res
## Fix email.base64mime.decode to add any missing padding ## Fix email.base64mime.decode to add any missing padding
@@ -227,5 +225,5 @@ def parse_header(val):
except UnicodeDecodeError: pass except UnicodeDecodeError: pass
except LookupError: pass except LookupError: pass
except ValueError: pass except ValueError: pass
except email.Errors.HeaderParseError: pass except email.errors.HeaderParseError: pass
return val return val
+17 -21
View File
@@ -103,15 +103,12 @@ import Milter
import zipfile import zipfile
import email import email
import email.Message from email.message import Message
from email.Message import Message from email.generator import Generator
from email.Generator import Generator from email.utils import quote
from email.Utils import quote
from email import Utils
from email.Parser import Parser
from email import Errors
from types import ListType,StringType if not getattr(Message,'as_bytes',None):
Message.as_bytes = Message.as_string
## Return a list of filenames in a zip file. ## Return a list of filenames in a zip file.
# Embedded zip files are recursively expanded. # Embedded zip files are recursively expanded.
@@ -154,19 +151,17 @@ def unquote(s):
return s[1:-1] return s[1:-1]
return s return s
from types import TupleType
def _unquotevalue(value): def _unquotevalue(value):
if isinstance(value, TupleType): if isinstance(value, tuple):
return value[0], value[1], unquote(value[2]) return value[0], value[1], unquote(value[2])
else: else:
return unquote(value) return unquote(value)
#email.Message._unquotevalue = _unquotevalue #email.Message._unquotevalue = _unquotevalue
from email.Message import _parseparam from email.message import _parseparam
## Enhance email.Message ## Enhance email.message.Message
# #
# Tracks modifications to headers of body or any part independently. # Tracks modifications to headers of body or any part independently.
@@ -207,7 +202,7 @@ class MimeMessage(Message):
interpret as a name - and hence decide to execute this message.""" interpret as a name - and hence decide to execute this message."""
names = [] names = []
for attr,val in self._get_params_preserve([],'content-type'): for attr,val in self._get_params_preserve([],'content-type'):
if isinstance(val, TupleType): if isinstance(val, tuple):
# It's an RFC 2231 encoded parameter # It's an RFC 2231 encoded parameter
newvalue = _unquotevalue(val) newvalue = _unquotevalue(val)
if val[0]: if val[0]:
@@ -355,8 +350,6 @@ def check_name(msg,savname=None,ckname=check_ext,scan_zip=False):
msg["Content-Type"] = "text/plain; name="+name msg["Content-Type"] = "text/plain; name="+name
return Milter.CONTINUE return Milter.CONTINUE
import email.Iterators
def check_attachments(msg,check): def check_attachments(msg,check):
"""Scan attachments. """Scan attachments.
msg MimeMessage msg MimeMessage
@@ -402,18 +395,21 @@ class _defang:
# emulate old defang function # emulate old defang function
defang = _defang() defang = _defang()
import sgmllib try:
from html.parser import HTMLParser
except:
from sgmllib import SGMLParser as HTMLParser
import re import re
declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*') declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*')
declstringlit = re.compile(r'(\'[^\']*\'|"[^"]*")\s*') declstringlit = re.compile(r'(\'[^\']*\'|"[^"]*")\s*')
class SGMLFilter(sgmllib.SGMLParser): class SGMLFilter(HTMLParser):
"""Parse HTML and pass through all constructs unchanged. It is intended for """Parse HTML and pass through all constructs unchanged. It is intended for
derived classes to implement exceptional processing for selected cases. derived classes to implement exceptional processing for selected cases.
""" """
def __init__(self,out): def __init__(self,out):
sgmllib.SGMLParser.__init__(self) HTMLParser.__init__(self)
self.out = out self.out = out
def handle_comment(self,comment): def handle_comment(self,comment):
@@ -444,7 +440,7 @@ class SGMLFilter(sgmllib.SGMLParser):
self.out.write("<!%s>" % data) self.out.write("<!%s>" % data)
def write(self,buf): def write(self,buf):
"Act like a writer. Why doesn't SGMLParser do this by default?" "Act like a writer. Why doesn't HTMLParser do this by default?"
self.feed(buf) self.feed(buf)
# Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft # Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft
@@ -545,5 +541,5 @@ if __name__ == '__main__':
for fname in sys.argv[1:]: for fname in sys.argv[1:]:
fp = open(fname) fp = open(fname)
msg = message_from_file(fp) msg = message_from_file(fp)
email.Iterators._structure(msg) email.iterators._structure(msg)
check_attachments(msg,_list_attach) check_attachments(msg,_list_attach)
+4 -8
View File
@@ -11,7 +11,6 @@ try:
from StringIO import StringIO from StringIO import StringIO
except: except:
from io import StringIO from io import StringIO
import rfc822
import mime import mime
import Milter import Milter
import tempfile import tempfile
@@ -141,19 +140,16 @@ class sampleMilter(Milter.Milter):
self.log("Temp file:",self.tempname) self.log("Temp file:",self.tempname)
self.tempname = None # prevent removal of original message copy self.tempname = None # prevent removal of original message copy
# copy defanged message to a temp file # copy defanged message to a temp file
out = tempfile.TemporaryFile() with tempfile.TemporaryFile() as out:
try:
msg.dump(out) msg.dump(out)
out.seek(0) out.seek(0)
msg = rfc822.Message(out) msg = mime.message_from_file(out)
msg.rewindbody() fp = StringIO(msg.as_bytes().split(b'\n\n',1)[1])
while 1: while 1:
buf = out.read(8192) buf = fp.read(8192)
if len(buf) == 0: break if len(buf) == 0: break
self.replacebody(buf) # feed modified message to sendmail self.replacebody(buf) # feed modified message to sendmail
return Milter.ACCEPT # ACCEPT modified message return Milter.ACCEPT # ACCEPT modified message
finally:
out.close()
return Milter.TEMPFAIL return Milter.TEMPFAIL
def close(self): def close(self):
+5 -2
View File
@@ -37,7 +37,10 @@ except:
import email import email
import sys import sys
import Milter import Milter
from email import Errors try:
from email import Errors as errors
except:
from email import errors
samp1_txt1 = """Dear Agent 1 samp1_txt1 = """Dear Agent 1
I hope you can read this. Whenever you write label it P.B.S kids. I hope you can read this. Whenever you write label it P.B.S kids.
@@ -77,7 +80,7 @@ class MimeTestCase(unittest.TestCase):
# if message is modified, output is readable by mail clients # if message is modified, output is readable by mail clients
if sys.hexversion < 0x02040000: if sys.hexversion < 0x02040000:
self.fail('should get boundary error parsing bad rfc822 attachment') self.fail('should get boundary error parsing bad rfc822 attachment')
except Errors.BoundaryError: except errors.BoundaryError:
pass pass
def testDefang(self,vname='virus1',part=1, def testDefang(self,vname='virus1',part=1,
-1
View File
@@ -2,7 +2,6 @@ import unittest
import Milter import Milter
import sample import sample
import mime import mime
import rfc822
from Milter.test import TestBase from Milter.test import TestBase
class TestMilter(TestBase,sample.sampleMilter): class TestMilter(TestBase,sample.sampleMilter):