Envelope and header values consistently decoded from utf-8. See RFC 8616.
This commit is contained in:
@@ -31,7 +31,7 @@ PROJECT_NAME = pymilter
|
|||||||
# This could be handy for archiving the generated documentation or
|
# This could be handy for archiving the generated documentation or
|
||||||
# if some version control system is used.
|
# if some version control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 1.0.2
|
PROJECT_NUMBER = 1.0.5
|
||||||
|
|
||||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
||||||
# base path where the generated documentation will be put.
|
# base path where the generated documentation will be put.
|
||||||
|
|||||||
+8
-3
@@ -349,13 +349,18 @@ class Base(object):
|
|||||||
# @since 0.9.2
|
# @since 0.9.2
|
||||||
@nocallback
|
@nocallback
|
||||||
def data(self): return CONTINUE
|
def data(self): return CONTINUE
|
||||||
## Called with bytes for header callback.
|
## Called with bytes by default global header callback.
|
||||||
# Converts to unicode with surrogate escape. Can be overriden
|
# @param fld name decoded as ascii
|
||||||
# to pass bytes to @link #header the header callback @endlink.
|
# @param val field value as bytes
|
||||||
|
# @since 1.0.5
|
||||||
|
# Converts from utf-8 to unicode with surrogate escape. Can be overriden
|
||||||
|
# to pass bytes to @link #header the header callback @endlink instead.
|
||||||
def header_bytes(self,fld,val):
|
def header_bytes(self,fld,val):
|
||||||
s = val.decode(encoding='utf-8',errors='surrogateescape')
|
s = val.decode(encoding='utf-8',errors='surrogateescape')
|
||||||
return self.header(fld,s)
|
return self.header(fld,s)
|
||||||
## Called for each header field in the message body.
|
## Called for each header field in the message body.
|
||||||
|
# @param field name decoded as ascii
|
||||||
|
# @param value field value decoded as utf-8 on python3
|
||||||
@nocallback
|
@nocallback
|
||||||
def header(self,field,value): return CONTINUE
|
def header(self,field,value): return CONTINUE
|
||||||
## Called at the blank line that terminates the header fields.
|
## Called at the blank line that terminates the header fields.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"""A parser for SGML, using the derived class as a static DTD."""
|
"""A parser for SGML, using the derived class as a static DTD."""
|
||||||
|
|
||||||
# XXX This only supports those SGML features used by HTML.
|
# XXX This only supports those SGML features used by HTML.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
web:
|
web:
|
||||||
doxygen
|
doxygen
|
||||||
test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-milter-devel doc/html/milter_api
|
test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-milter-devel doc/html/milter_api
|
||||||
rsync -ravKk doc/html/ bmsi.com:/var/www/html/pymilter
|
rsync -ravKk doc/html/ pymilter.org:/var/www/html/milter/pymilter
|
||||||
cd doc/html; zip -r ../../doc .
|
cd doc/html; zip -r ../../doc .
|
||||||
|
|
||||||
VERSION=1.0.4
|
VERSION=1.0.5
|
||||||
PKG=pymilter-$(VERSION)
|
PKG=pymilter-$(VERSION)
|
||||||
SRCTAR=$(PKG).tar.gz
|
SRCTAR=$(PKG).tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -675,6 +675,7 @@ milter_wrap_header(SMFICTX *ctx, char *headerf, char *headerv) {
|
|||||||
c = _get_context(ctx);
|
c = _get_context(ctx);
|
||||||
if (!c) return SMFIS_TEMPFAIL;
|
if (!c) return SMFIS_TEMPFAIL;
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
/* pass val as bytes so Milter.Base.header_bytes can do surrogate escape. */
|
||||||
arglist = Py_BuildValue("(Osy)", c, headerf, headerv);
|
arglist = Py_BuildValue("(Osy)", c, headerf, headerv);
|
||||||
#else
|
#else
|
||||||
arglist = Py_BuildValue("(Oss)", c, headerf, headerv);
|
arglist = Py_BuildValue("(Oss)", c, headerf, headerv);
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ class sampleMilter(Milter.Milter):
|
|||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
print("%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id),end=None)
|
print("%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id),end=None)
|
||||||
for i in msg: print(i,end=None)
|
for i in msg:
|
||||||
|
try:
|
||||||
|
print(i,end=None)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
s = i.encode(encoding='utf-8',errors='surrogateescape')
|
||||||
|
print(s,end=None)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -104,7 +109,7 @@ class sampleMilter(Milter.Milter):
|
|||||||
if lname in ('subject','x-mailer'):
|
if lname in ('subject','x-mailer'):
|
||||||
self.log('%s: %s' % (name,val))
|
self.log('%s: %s' % (name,val))
|
||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.write(("%s: %s\n" % (name,val)).encode()) # add header to buffer
|
self.fp.write(("%s: %s\n" % (name,val)).encode(errors='surrogateescape')) # add header to buffer
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ libdirs = ["/usr/lib/libmilter"] # needed for Debian
|
|||||||
modules = ["mime"]
|
modules = ["mime"]
|
||||||
|
|
||||||
# NOTE: importing Milter to obtain version fails when milter.so not built
|
# NOTE: importing Milter to obtain version fails when milter.so not built
|
||||||
setup(name = "pymilter", version = '1.0.4',
|
setup(name = "pymilter", version = '1.0.5',
|
||||||
description="Python interface to sendmail milter API",
|
description="Python interface to sendmail milter API",
|
||||||
long_description="""\
|
long_description="""\
|
||||||
This is a python extension module to enable python scripts to
|
This is a python extension module to enable python scripts to
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## To roll your own milter, create a class that extends Milter.
|
## To roll your own milter, create a class that extends Milter.
|
||||||
# See the pymilter project at http://bmsi.com/python/milter.html
|
# This is a useless example to show basic features of Milter.
|
||||||
# based on Sendmail's milter API
|
# See the pymilter project at https://pymilter.org based
|
||||||
|
# on Sendmail's milter API
|
||||||
# This code is open-source on the same terms as Python.
|
# This code is open-source on the same terms as Python.
|
||||||
|
|
||||||
## Milter calls methods of your class at milter events.
|
## Milter calls methods of your class at milter events.
|
||||||
@@ -10,21 +11,22 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import Milter
|
import Milter
|
||||||
try:
|
try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO as BytesIO
|
||||||
except:
|
except:
|
||||||
from io import StringIO
|
from io import BytesIO
|
||||||
import time
|
import time
|
||||||
import email
|
import email
|
||||||
import sys
|
import sys
|
||||||
from socket import AF_INET, AF_INET6
|
from socket import AF_INET, AF_INET6
|
||||||
from Milter.utils import parse_addr
|
from Milter.utils import parse_addr
|
||||||
if True:
|
if True:
|
||||||
|
# for logging process - usually not needed
|
||||||
from multiprocessing import Process as Thread, Queue
|
from multiprocessing import Process as Thread, Queue
|
||||||
else:
|
else:
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
|
|
||||||
logq = Queue(maxsize=4)
|
logq = None
|
||||||
|
|
||||||
class myMilter(Milter.Base):
|
class myMilter(Milter.Base):
|
||||||
|
|
||||||
@@ -78,9 +80,10 @@ class myMilter(Milter.Base):
|
|||||||
# NOTE: self.fp is only an *internal* copy of message data. You
|
# NOTE: self.fp is only an *internal* copy of message data. You
|
||||||
# must use addheader, chgheader, replacebody to change the message
|
# must use addheader, chgheader, replacebody to change the message
|
||||||
# on the MTA.
|
# on the MTA.
|
||||||
self.fp = StringIO()
|
self.fp = BytesIO()
|
||||||
self.canon_from = '@'.join(parse_addr(mailfrom))
|
self.canon_from = '@'.join(parse_addr(mailfrom))
|
||||||
self.fp.write('From %s %s\n' % (self.canon_from,time.ctime()))
|
self.fp.write(b'From %s %s\n' % (self.canon_from.encode(),
|
||||||
|
time.ctime().encode()))
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
|
||||||
@@ -95,12 +98,12 @@ class myMilter(Milter.Base):
|
|||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def header(self, name, hval):
|
def header(self, name, hval):
|
||||||
self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer
|
self.fp.write(b'%s: %s\n' % (name.encode(),hval.encode())) # add header to buffer
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
self.fp.write("\n") # terminate headers
|
self.fp.write(b'\n') # terminate headers
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
@@ -110,7 +113,7 @@ class myMilter(Milter.Base):
|
|||||||
|
|
||||||
def eom(self):
|
def eom(self):
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
msg = email.message_from_file(self.fp)
|
msg = email.message_from_binary_file(self.fp)
|
||||||
# many milter functions can only be called from eom()
|
# many milter functions can only be called from eom()
|
||||||
# example of adding a Bcc:
|
# example of adding a Bcc:
|
||||||
self.addrcpt('<%s>' % 'spy@example.com')
|
self.addrcpt('<%s>' % 'spy@example.com')
|
||||||
@@ -128,13 +131,14 @@ class myMilter(Milter.Base):
|
|||||||
## === Support Functions ===
|
## === Support Functions ===
|
||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
logq.put((msg,self.id,time.time()))
|
t = (msg,self.id,time.time())
|
||||||
|
if logq:
|
||||||
|
logq.put(t)
|
||||||
|
else:
|
||||||
|
# logmsg(*t)
|
||||||
|
pass
|
||||||
|
|
||||||
def background():
|
def logmsg(msg,id,ts):
|
||||||
while True:
|
|
||||||
t = logq.get()
|
|
||||||
if not t: break
|
|
||||||
msg,id,ts = t
|
|
||||||
print("%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
|
print("%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
|
||||||
end=None)
|
end=None)
|
||||||
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
|
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
|
||||||
@@ -142,6 +146,12 @@ def background():
|
|||||||
print()
|
print()
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def background():
|
||||||
|
while True:
|
||||||
|
t = logq.get()
|
||||||
|
if not t: break
|
||||||
|
logmsg(*t)
|
||||||
|
|
||||||
## ===
|
## ===
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -163,4 +173,7 @@ def main():
|
|||||||
print("%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S'))
|
print("%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S'))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# You probably do not need a logging process, but if you do, this
|
||||||
|
# is one way to do it.
|
||||||
|
logq = Queue(maxsize=4)
|
||||||
main()
|
main()
|
||||||
+19
-1
@@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import Milter
|
import Milter
|
||||||
import sample
|
import sample
|
||||||
|
import template
|
||||||
import mime
|
import mime
|
||||||
import zipfile
|
import zipfile
|
||||||
from Milter.test import TestBase
|
from Milter.test import TestBase
|
||||||
@@ -21,6 +22,23 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
self.zf.close()
|
self.zf.close()
|
||||||
self.zf = None
|
self.zf = None
|
||||||
|
|
||||||
|
def testTemplate(self,fname='test2'):
|
||||||
|
ctx = TestCtx()
|
||||||
|
Milter.factory = template.myMilter
|
||||||
|
ctx._setsymval('{auth_authen}','batman')
|
||||||
|
ctx._setsymval('{auth_type}','batcomputer')
|
||||||
|
ctx._setsymval('j','mailhost')
|
||||||
|
count = 10
|
||||||
|
while count > 0:
|
||||||
|
rc = ctx._connect(helo='milter-template.example.org')
|
||||||
|
self.assertEquals(rc,Milter.CONTINUE)
|
||||||
|
with open('test/'+fname,'rb') as fp:
|
||||||
|
rc = ctx._feedFile(fp)
|
||||||
|
milter = ctx.getpriv()
|
||||||
|
self.assertFalse(ctx._bodyreplaced,"Message body replaced")
|
||||||
|
ctx._close()
|
||||||
|
count -= 1
|
||||||
|
|
||||||
def testHeader(self,fname='utf8'):
|
def testHeader(self,fname='utf8'):
|
||||||
ctx = TestCtx()
|
ctx = TestCtx()
|
||||||
Milter.factory = sample.sampleMilter
|
Milter.factory = sample.sampleMilter
|
||||||
@@ -28,7 +46,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
ctx._setsymval('{auth_type}','batcomputer')
|
ctx._setsymval('{auth_type}','batcomputer')
|
||||||
ctx._setsymval('j','mailhost')
|
ctx._setsymval('j','mailhost')
|
||||||
rc = ctx._connect()
|
rc = ctx._connect()
|
||||||
self.assertTrue(rc == Milter.CONTINUE)
|
self.assertEquals(rc,Milter.CONTINUE)
|
||||||
with open('test/'+fname,'rb') as fp:
|
with open('test/'+fname,'rb') as fp:
|
||||||
rc = ctx._feedFile(fp)
|
rc = ctx._feedFile(fp)
|
||||||
milter = ctx.getpriv()
|
milter = ctx.getpriv()
|
||||||
|
|||||||
Reference in New Issue
Block a user