diff --git a/Milter/cache.py b/Milter/cache.py
index 82bf046..8d7a959 100644
--- a/Milter/cache.py
+++ b/Milter/cache.py
@@ -48,7 +48,7 @@
from __future__ import print_function
import time
-from plock import PLock
+from Milter.plock import PLock
class AddrCache(object):
time_format = '%Y%b%d %H:%M:%S %Z'
@@ -132,8 +132,8 @@ class AddrCache(object):
if not ts or ts > too_old:
return res
del self.cache[lsender]
- raise KeyError, sender
- except KeyError,x:
+ raise KeyError(sender)
+ except KeyError as x:
try:
user,host = sender.split('@',1)
return self.__getitem__(host)
diff --git a/Milter/greysql.py b/Milter/greysql.py
index b85a4d7..4c290b1 100644
--- a/Milter/greysql.py
+++ b/Milter/greysql.py
@@ -2,7 +2,10 @@ import time
import logging
import urllib
import sqlite3
-import thread
+try:
+ import thread
+except:
+ import _thread as thread
from datetime import datetime
log = logging.getLogger('milter.greylist')
diff --git a/Milter/plock.py b/Milter/plock.py
index bcbe51a..f551df2 100644
--- a/Milter/plock.py
+++ b/Milter/plock.py
@@ -11,7 +11,7 @@ class PLock(object):
self.basename = basename
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."
self.unlock()
if not lockname:
@@ -21,7 +21,7 @@ class PLock(object):
st = os.stat(self.basename)
mode |= st.st_mode
except OSError: pass
- u = os.umask(0002)
+ u = os.umask(0o2)
try:
fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode)
finally:
@@ -46,7 +46,7 @@ class PLock(object):
def commit(self,backname=None):
"Commit update transaction with optional backup file."
if not self.fp:
- raise IOError,"File not locked"
+ raise IOError("File not locked")
self.fp.close()
self.fp = None
if backname:
diff --git a/Milter/pyip6.py b/Milter/pyip6.py
index ea62496..e28f7c9 100644
--- a/Milter/pyip6.py
+++ b/Milter/pyip6.py
@@ -85,7 +85,7 @@ def inet_pton(p):
::1.2.3.4.5
"""
if p == '::':
- return '\0'*16
+ return b'\0'*16
s = p
m = RE_IP4.search(s)
try:
diff --git a/Milter/test.py b/Milter/test.py
index 095b76c..5077f4c 100644
--- a/Milter/test.py
+++ b/Milter/test.py
@@ -2,7 +2,7 @@
# A test framework for milters
from __future__ import print_function
-import rfc822
+import mime
try:
from StringIO import StringIO
except:
@@ -62,11 +62,11 @@ class TestBase(object):
self._body.write(chunk)
self._bodyreplaced = True
else:
- raise IOError,"replacebody not called from eom()"
+ raise IOError("replacebody not called from eom()")
def chgfrom(self,sender,params=None):
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._envfromchanged = True
self._sender = sender
@@ -83,7 +83,7 @@ class TestBase(object):
# work for a %milter
def chgheader(self,field,idx,value):
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))
if value == '':
del self._msg[field]
@@ -93,19 +93,19 @@ class TestBase(object):
def addheader(self,field,value,idx=-1):
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._msg[field] = value
self._headerschanged = True
def delrcpt(self,rcpt):
if not self._body:
- raise IOError,"delrcpt not called from eom()"
+ raise IOError("delrcpt not called from eom()")
self._delrcpt.append(rcpt)
def addrcpt(self,rcpt):
if not self._body:
- raise IOError,"addrcpt not called from eom()"
+ raise IOError("addrcpt not called from eom()")
self._addrcpt.append(rcpt)
## Save the reply codes and messages in self._reply.
@@ -143,34 +143,21 @@ class TestBase(object):
self._headerschanged = False
self._reply = None
self._sender = '<%s>'%sender
- msg = rfc822.Message(fp)
+ msg = mime.message_from_file(fp)
rc = self.envfrom(self._sender)
if rc != Milter.CONTINUE: return rc
for rcpt in (rcpt,) + rcpts:
rc = self.envrcpt('<%s>'%rcpt)
if rc != Milter.CONTINUE: return rc
- line = None
- for h in msg.headers:
- 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])
+ for h,val in msg.items():
+ rc = self.header(h,val)
if rc != Milter.CONTINUE: return rc
rc = self.eoh()
if rc != Milter.CONTINUE: return rc
+ header,body = msg.as_bytes().split(b'\n\n',1)
+ bfp = StringIO(body)
while 1:
- buf = fp.read(8192)
+ buf = bfp.read(8192)
if len(buf) == 0: break
rc = self.body(buf)
if rc != Milter.CONTINUE: return rc
@@ -179,12 +166,9 @@ class TestBase(object):
rc = self.eom()
if self._bodyreplaced:
body = self._body.getvalue()
- else:
- msg.rewindbody()
- body = msg.fp.read()
self._body = StringIO()
- self._body.writelines(msg.headers)
- self._body.write('\n')
+ self._body.write(header)
+ self._body.write('\n\n')
self._body.write(body)
return rc
diff --git a/Milter/utils.py b/Milter/utils.py
index 563d17c..cd27b00 100644
--- a/Milter/utils.py
+++ b/Milter/utils.py
@@ -5,13 +5,11 @@
import re
import struct
import socket
-import email.Errors
+import email.errors
+from email.header import decode_header
import email.base64mime
from fnmatch import fnmatchcase
-from email.Header import decode_header
from binascii import a2b_base64
-#import email.Utils
-import rfc822
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)
@@ -56,8 +54,8 @@ if hasattr(socket,'has_ipv6') and socket.has_ipv6:
else:
from pyip6 import inet_ntop, inet_pton
-MASK = 0xFFFFFFFFL
-MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL
+MASK = 0xFFFFFFFF
+MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
def cidr(i,n,mask=MASK):
return ~(mask >> n) & mask & i
@@ -119,7 +117,7 @@ def iniplist(ipaddr,iplist):
return False
## Split email into Fullname and address.
-# This replaces email.Utils.parseaddr but fixes
+# This replaces email.utils.parseaddr but fixes
# some tricky test cases.
# Additional tricky cases are still broken. Patches welcome.
#
@@ -139,8 +137,8 @@ def parseaddr(t):
>>> parseaddr('a(WRONG)@b')
('WRONG', 'a@b')
"""
- #return email.Utils.parseaddr(t)
- res = rfc822.parseaddr(t)
+ #return email.utils.parseaddr(t)
+ res = email.utils.parseaddr(t)
# dirty fix for some broken cases
if not res[0]:
pos = t.find('<')
@@ -149,7 +147,7 @@ def parseaddr(t):
pos1 = addrspec.rfind(':')
if pos1 > 0:
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]:
pos = t.find('<')
if pos > 0 and t[-1] == '>':
@@ -157,7 +155,7 @@ def parseaddr(t):
pos1 = addrspec.rfind(':')
if pos1 > 0:
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
## Fix email.base64mime.decode to add any missing padding
@@ -227,5 +225,5 @@ def parse_header(val):
except UnicodeDecodeError: pass
except LookupError: pass
except ValueError: pass
- except email.Errors.HeaderParseError: pass
+ except email.errors.HeaderParseError: pass
return val
diff --git a/mime.py b/mime.py
index c23da72..8106d2e 100644
--- a/mime.py
+++ b/mime.py
@@ -103,15 +103,12 @@ import Milter
import zipfile
import email
-import email.Message
-from email.Message import Message
-from email.Generator import Generator
-from email.Utils import quote
-from email import Utils
-from email.Parser import Parser
-from email import Errors
+from email.message import Message
+from email.generator import Generator
+from email.utils import quote
-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.
# Embedded zip files are recursively expanded.
@@ -154,19 +151,17 @@ def unquote(s):
return s[1:-1]
return s
-from types import TupleType
-
def _unquotevalue(value):
- if isinstance(value, TupleType):
+ if isinstance(value, tuple):
return value[0], value[1], unquote(value[2])
else:
return unquote(value)
#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.
@@ -207,7 +202,7 @@ class MimeMessage(Message):
interpret as a name - and hence decide to execute this message."""
names = []
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
newvalue = _unquotevalue(val)
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
return Milter.CONTINUE
-import email.Iterators
-
def check_attachments(msg,check):
"""Scan attachments.
msg MimeMessage
@@ -402,18 +395,21 @@ class _defang:
# emulate old defang function
defang = _defang()
-import sgmllib
+try:
+ from html.parser import HTMLParser
+except:
+ from sgmllib import SGMLParser as HTMLParser
import re
declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\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
derived classes to implement exceptional processing for selected cases.
"""
def __init__(self,out):
- sgmllib.SGMLParser.__init__(self)
+ HTMLParser.__init__(self)
self.out = out
def handle_comment(self,comment):
@@ -444,7 +440,7 @@ class SGMLFilter(sgmllib.SGMLParser):
self.out.write("" % data)
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)
# Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft
@@ -545,5 +541,5 @@ if __name__ == '__main__':
for fname in sys.argv[1:]:
fp = open(fname)
msg = message_from_file(fp)
- email.Iterators._structure(msg)
+ email.iterators._structure(msg)
check_attachments(msg,_list_attach)
diff --git a/sample.py b/sample.py
index c4fc033..7f92994 100644
--- a/sample.py
+++ b/sample.py
@@ -11,7 +11,6 @@ try:
from StringIO import StringIO
except:
from io import StringIO
-import rfc822
import mime
import Milter
import tempfile
@@ -141,19 +140,16 @@ class sampleMilter(Milter.Milter):
self.log("Temp file:",self.tempname)
self.tempname = None # prevent removal of original message copy
# copy defanged message to a temp file
- out = tempfile.TemporaryFile()
- try:
+ with tempfile.TemporaryFile() as out:
msg.dump(out)
out.seek(0)
- msg = rfc822.Message(out)
- msg.rewindbody()
+ msg = mime.message_from_file(out)
+ fp = StringIO(msg.as_bytes().split(b'\n\n',1)[1])
while 1:
- buf = out.read(8192)
+ buf = fp.read(8192)
if len(buf) == 0: break
self.replacebody(buf) # feed modified message to sendmail
return Milter.ACCEPT # ACCEPT modified message
- finally:
- out.close()
return Milter.TEMPFAIL
def close(self):
diff --git a/testmime.py b/testmime.py
index 28c7c16..03189bf 100644
--- a/testmime.py
+++ b/testmime.py
@@ -37,7 +37,10 @@ except:
import email
import sys
import Milter
-from email import Errors
+try:
+ from email import Errors as errors
+except:
+ from email import errors
samp1_txt1 = """Dear Agent 1
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 sys.hexversion < 0x02040000:
self.fail('should get boundary error parsing bad rfc822 attachment')
- except Errors.BoundaryError:
+ except errors.BoundaryError:
pass
def testDefang(self,vname='virus1',part=1,
diff --git a/testsample.py b/testsample.py
index 285dee3..5bff76a 100644
--- a/testsample.py
+++ b/testsample.py
@@ -2,7 +2,6 @@ import unittest
import Milter
import sample
import mime
-import rfc822
from Milter.test import TestBase
class TestMilter(TestBase,sample.sampleMilter):