Make TestBase members private, fix getsymlist misspelling.
This commit is contained in:
+5
-5
@@ -227,7 +227,7 @@ class Base(object):
|
|||||||
# Some optional actions may be disabled by calling milter.set_flags(), or
|
# Some optional actions may be disabled by calling milter.set_flags(), or
|
||||||
# by overriding the negotiate callback. The bits include:
|
# by overriding the negotiate callback. The bits include:
|
||||||
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
|
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
|
||||||
# CHGHDRS,QUARANTINE,CHGFROM,SETSMLIST</code>.
|
# CHGHDRS,QUARANTINE,CHGFROM,SETSYMLIST</code>.
|
||||||
# The <code>Milter.CURR_ACTS</code> bitmask is all actions
|
# The <code>Milter.CURR_ACTS</code> bitmask is all actions
|
||||||
# known when the milter module was compiled.
|
# known when the milter module was compiled.
|
||||||
# Application code can also inspect this field to determine
|
# Application code can also inspect this field to determine
|
||||||
@@ -440,7 +440,7 @@ class Base(object):
|
|||||||
## Tell the MTA which macro names will be used.
|
## Tell the MTA which macro names will be used.
|
||||||
# This information can reduce the size of messages received from sendmail,
|
# This information can reduce the size of messages received from sendmail,
|
||||||
# and hence could reduce bandwidth between sendmail and your milter where
|
# and hence could reduce bandwidth between sendmail and your milter where
|
||||||
# that is a factor. The <code>Milter.SETSMLIST</code> action flag must be
|
# that is a factor. The <code>Milter.SETSYMLIST</code> action flag must be
|
||||||
# set.
|
# set.
|
||||||
#
|
#
|
||||||
# May only be called from negotiate callback.
|
# May only be called from negotiate callback.
|
||||||
@@ -448,9 +448,9 @@ class Base(object):
|
|||||||
# @param stage the protocol stage to set to macro list for,
|
# @param stage the protocol stage to set to macro list for,
|
||||||
# one of the M_* constants defined in Milter
|
# one of the M_* constants defined in Milter
|
||||||
# @param macros a string with a space delimited list of macros
|
# @param macros a string with a space delimited list of macros
|
||||||
def setsmlist(self,stage,macros):
|
def setsymlist(self,stage,macros):
|
||||||
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
|
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
||||||
if type(macros) in (list,tuple):
|
if type(macros) != str:
|
||||||
macros = ' '.join(macros)
|
macros = ' '.join(macros)
|
||||||
return self._ctx.setsmlist(stage,macros)
|
return self._ctx.setsmlist(stage,macros)
|
||||||
|
|
||||||
|
|||||||
+70
-23
@@ -5,31 +5,52 @@ import rfc822
|
|||||||
import StringIO
|
import StringIO
|
||||||
import Milter
|
import Milter
|
||||||
|
|
||||||
##
|
## Test mixin for unit testing milter applications.
|
||||||
#
|
# This mixin overrides many Milter.MilterBase methods
|
||||||
|
# with stub versions that simply record what was done.
|
||||||
|
# @since 0.9.8
|
||||||
class TestBase(object):
|
class TestBase(object):
|
||||||
|
|
||||||
_protocol = 0
|
_protocol = 0
|
||||||
def __init__(self):
|
def __init__(self,logfile='test/milter.log'):
|
||||||
self.logfp = open("test/milter.log","a")
|
self.logfp = open(logfile,"a")
|
||||||
self._delrcpt = [] # record deleted rcpts for testing
|
## List of recipients deleted
|
||||||
self._addrcpt = [] # record added rcpts for testing
|
self._delrcpt = []
|
||||||
|
## List of recipients added
|
||||||
|
self._addrcpt = []
|
||||||
|
## Macros defined
|
||||||
self._macros = { }
|
self._macros = { }
|
||||||
|
## The message body.
|
||||||
|
self._body = None
|
||||||
|
## True if the milter replaced the message body.
|
||||||
|
self._bodyreplaced = False
|
||||||
|
## True if the milter changed any headers.
|
||||||
|
self._headerschanged = False
|
||||||
|
## Reply codes and messages set by milter
|
||||||
|
self._reply = None
|
||||||
|
## The rfc822 message object for the current email being fed to the milter.
|
||||||
|
self._msg = None
|
||||||
|
self._symlist = [ None, None, None, None, None, None, None ]
|
||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
for i in msg: print >>self.logfp, i,
|
for i in msg: print >>self.logfp, i,
|
||||||
print >>self.logfp
|
print >>self.logfp
|
||||||
|
|
||||||
def setsymval(self,name,val,step=None):
|
## Set a macro value.
|
||||||
|
# These are retrieved by the milter with getsymval.
|
||||||
|
# @param name the macro name, as passed to getsymval
|
||||||
|
# @param val the macro value
|
||||||
|
def setsymval(self,name,val):
|
||||||
self._macros[name] = val
|
self._macros[name] = val
|
||||||
|
|
||||||
def getsymval(self,name):
|
def getsymval(self,name):
|
||||||
|
# FIXME: track stage, and use _symlist
|
||||||
return self._macros.get(name,'')
|
return self._macros.get(name,'')
|
||||||
|
|
||||||
def replacebody(self,chunk):
|
def replacebody(self,chunk):
|
||||||
if self._body:
|
if self._body:
|
||||||
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()"
|
||||||
|
|
||||||
@@ -43,14 +64,14 @@ class TestBase(object):
|
|||||||
del self._msg[field]
|
del self._msg[field]
|
||||||
else:
|
else:
|
||||||
self._msg[field] = value
|
self._msg[field] = value
|
||||||
self.headerschanged = True
|
self._headerschanged = True
|
||||||
|
|
||||||
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:
|
||||||
@@ -62,17 +83,36 @@ class TestBase(object):
|
|||||||
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.
|
||||||
def setreply(self,rcode,xcode,*msg):
|
def setreply(self,rcode,xcode,*msg):
|
||||||
self.reply = (rcode,xcode) + msg
|
self._reply = (rcode,xcode) + msg
|
||||||
|
|
||||||
def feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com"):
|
def setsymlist(self,stage,macros):
|
||||||
|
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
||||||
|
if type(macros) != str:
|
||||||
|
macros = ' '.join(macros)
|
||||||
|
self._symlist[stage] = macros
|
||||||
|
|
||||||
|
## Feed a file like object to the milter. Calls envfrom, envrcpt for
|
||||||
|
# each recipient, header for each header field, body for each body
|
||||||
|
# block, and finally eom. A return code from the milter other than
|
||||||
|
# CONTINUE returns immediately with that return code.
|
||||||
|
#
|
||||||
|
# This is a convenience method, a test could invoke the callbacks
|
||||||
|
# in sequence on its own - and for some complex tests, this may
|
||||||
|
# be necessary.
|
||||||
|
# @param fp the file with rfc2822 message stream
|
||||||
|
# @param sender the MAIL FROM
|
||||||
|
# @param rcpt RCPT TO - additional recipients may follow
|
||||||
|
def feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com",*rcpts):
|
||||||
self._body = None
|
self._body = None
|
||||||
self.bodyreplaced = False
|
self._bodyreplaced = False
|
||||||
self.headerschanged = False
|
self._headerschanged = False
|
||||||
self.reply = None
|
self._reply = None
|
||||||
msg = rfc822.Message(fp)
|
msg = rfc822.Message(fp)
|
||||||
rc = self.envfrom('<%s>'%sender)
|
rc = self.envfrom('<%s>'%sender)
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
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
|
line = None
|
||||||
@@ -103,7 +143,7 @@ class TestBase(object):
|
|||||||
self._msg = msg
|
self._msg = msg
|
||||||
self._body = StringIO.StringIO()
|
self._body = StringIO.StringIO()
|
||||||
rc = self.eom()
|
rc = self.eom()
|
||||||
if self.bodyreplaced:
|
if self._bodyreplaced:
|
||||||
body = self._body.getvalue()
|
body = self._body.getvalue()
|
||||||
else:
|
else:
|
||||||
msg.rewindbody()
|
msg.rewindbody()
|
||||||
@@ -114,17 +154,24 @@ class TestBase(object):
|
|||||||
self._body.write(body)
|
self._body.write(body)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
def feedMsg(self,fname,sender="spam@adv.com",rcpt="victim@lamb.com"):
|
## Feed an email contained in a file to the milter.
|
||||||
fp = open('test/'+fname,'r')
|
# This is a convenience method that invokes @link #feedFile feedFile @endlink.
|
||||||
rc = self.feedFile(fp,sender,rcpt)
|
# @param sender MAIL FROM
|
||||||
fp.close()
|
# @param rcpts RCPT TO, multiple recipients may be supplied
|
||||||
return rc
|
def feedMsg(self,fname,sender="spam@adv.com",*rcpts):
|
||||||
|
with open('test/'+fname,'r') as fp:
|
||||||
|
return self.feedFile(fp,sender,*rcpts)
|
||||||
|
|
||||||
|
## Call the connect and helo callbacks.
|
||||||
|
# The helo callback is not called if connect does not return CONTINUE.
|
||||||
|
# @param host the hostname passed to the connect callback
|
||||||
|
# @param helo the hostname passed to the helo callback
|
||||||
|
# @param ip the IP address passed to the connect callback
|
||||||
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
||||||
self._body = None
|
self._body = None
|
||||||
self.bodyreplaced = False
|
self._bodyreplaced = False
|
||||||
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
||||||
if rc != Milter.CONTINUE and rc != Milter.ACCEPT:
|
if rc != Milter.CONTINUE:
|
||||||
self.close()
|
self.close()
|
||||||
return rc
|
return rc
|
||||||
rc = self.hello(helo)
|
rc = self.hello(helo)
|
||||||
|
|||||||
+12
-2
@@ -54,7 +54,12 @@ class milterContext(object):
|
|||||||
def chgfrom(self,sender,param=None): pass
|
def chgfrom(self,sender,param=None): pass
|
||||||
## Tell the MTA which macro values we are interested in for a given stage.
|
## Tell the MTA which macro values we are interested in for a given stage.
|
||||||
# Of interest only when you need to squeeze a few more bytes of bandwidth.
|
# Of interest only when you need to squeeze a few more bytes of bandwidth.
|
||||||
def setsmlist(self,stage,macrolist): pass
|
# It may only be called from the negotiate callback.
|
||||||
|
# The protocol stages are
|
||||||
|
# M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT, M_DATA, M_EOM, M_EOH.
|
||||||
|
# Calls <a href="https://www.milter.org/developers/api/smfi_setsymlist">smfi_setsymlist</a>.
|
||||||
|
# @param stage protocol stage in which the macro list should be used
|
||||||
|
def setsymlist(self,stage,macrolist): pass
|
||||||
|
|
||||||
class error(Exception): pass
|
class error(Exception): pass
|
||||||
|
|
||||||
@@ -77,7 +82,12 @@ def set_abort_callback(cb): pass
|
|||||||
def set_close_callback(cb): pass
|
def set_close_callback(cb): pass
|
||||||
|
|
||||||
## Sets the return code for untrapped Python exceptions during a callback.
|
## Sets the return code for untrapped Python exceptions during a callback.
|
||||||
# Must be one of TEMPFAIL,REJECT,CONTINUE
|
# Must be one of TEMPFAIL,REJECT,CONTINUE. The default is TEMPFAIL.
|
||||||
|
# You should not depend on this handler. Your application should
|
||||||
|
# have its own top level exception handler for each callback. You can
|
||||||
|
# then choose your own reply message, log the stack track were you please,
|
||||||
|
# and so on. However, if you miss one, this last ditch handler will
|
||||||
|
# print a standard stack trace to sys.stderr, and return to sendmail.
|
||||||
def set_exception_policy(code): pass
|
def set_exception_policy(code): pass
|
||||||
|
|
||||||
## Register python milter with libmilter.
|
## Register python milter with libmilter.
|
||||||
|
|||||||
+13
-10
@@ -35,6 +35,9 @@ $ python setup.py help
|
|||||||
libraries=["milter","smutil","resolv"]
|
libraries=["milter","smutil","resolv"]
|
||||||
|
|
||||||
* $Log$
|
* $Log$
|
||||||
|
* Revision 1.33 2013/03/09 00:25:23 customdesigned
|
||||||
|
* Better untrapped exception message. const char for doc comments.
|
||||||
|
*
|
||||||
* Revision 1.32 2013/01/13 01:46:16 customdesigned
|
* Revision 1.32 2013/01/13 01:46:16 customdesigned
|
||||||
* Doc updates.
|
* Doc updates.
|
||||||
*
|
*
|
||||||
@@ -1490,23 +1493,23 @@ milter_progress(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SMFIF_SETSMLIST
|
#ifdef SMFIF_SETSYMLIST
|
||||||
static const char milter_setsmlist__doc__[] =
|
static const char milter_setsymlist__doc__[] =
|
||||||
"setsmlist(stage,macrolist) -> None\n\
|
"setsymlist(stage,macrolist) -> None\n\
|
||||||
Tell the MTA which macro values we are interested in for a given stage";
|
Tell the MTA which macro values we are interested in for a given stage";
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
milter_setsmlist(PyObject *self, PyObject *args) {
|
milter_setsymlist(PyObject *self, PyObject *args) {
|
||||||
SMFICTX *ctx;
|
SMFICTX *ctx;
|
||||||
PyThreadState *t;
|
PyThreadState *t;
|
||||||
int stage = 0;
|
int stage = 0;
|
||||||
char *smlist = 0;
|
char *smlist = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "is:setsmlist",&stage, &smlist)) return NULL;
|
if (!PyArg_ParseTuple(args, "is:setsymlist",&stage, &smlist)) return NULL;
|
||||||
ctx = _find_context(self);
|
ctx = _find_context(self);
|
||||||
if (ctx == NULL) return NULL;
|
if (ctx == NULL) return NULL;
|
||||||
t = PyEval_SaveThread();
|
t = PyEval_SaveThread();
|
||||||
return _thread_return(t,smfi_setsmlist(ctx,stage,smlist),
|
return _thread_return(t,smfi_setsymlist(ctx,stage,smlist),
|
||||||
"cannot set macro list");
|
"cannot set macro list");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1530,8 +1533,8 @@ static PyMethodDef context_methods[] = {
|
|||||||
#ifdef SMFIF_CHGFROM
|
#ifdef SMFIF_CHGFROM
|
||||||
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
|
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
|
||||||
#endif
|
#endif
|
||||||
#ifdef SMFIF_SETSMLIST
|
#ifdef SMFIF_SETSYMLIST
|
||||||
{ "setsmlist", milter_setsmlist, METH_VARARGS, milter_setsmlist__doc__},
|
{ "setsymlist", milter_setsymlist, METH_VARARGS, milter_setsymlist__doc__},
|
||||||
#endif
|
#endif
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
@@ -1654,8 +1657,8 @@ initmilter(void) {
|
|||||||
#ifdef SMFIF_CHGFROM
|
#ifdef SMFIF_CHGFROM
|
||||||
setitem(d,"CHGFROM",SMFIF_CHGFROM);
|
setitem(d,"CHGFROM",SMFIF_CHGFROM);
|
||||||
#endif
|
#endif
|
||||||
#ifdef SMFIF_SETSMLIST
|
#ifdef SMFIF_SETSYMLIST
|
||||||
setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
|
setitem(d,"SETSYMLIST",SMFIF_SETSYMLIST);
|
||||||
setitem(d,"M_CONNECT",SMFIM_CONNECT);/* connect */
|
setitem(d,"M_CONNECT",SMFIM_CONNECT);/* connect */
|
||||||
setitem(d,"M_HELO",SMFIM_HELO); /* HELO/EHLO */
|
setitem(d,"M_HELO",SMFIM_HELO); /* HELO/EHLO */
|
||||||
setitem(d,"M_ENVFROM",SMFIM_ENVFROM);/* MAIL From */
|
setitem(d,"M_ENVFROM",SMFIM_ENVFROM);/* MAIL From */
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
|
|||||||
rm -rf $RPM_BUILD_ROOT
|
rm -rf $RPM_BUILD_ROOT
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sat Mar 9 2013 Stuart Gathman <stuart@bmsi.com> 0.9.8-1
|
||||||
|
- Add Milter.test module for unit testing milters.
|
||||||
|
- Fix typo that prevented setsymlist from being active.
|
||||||
|
- Change untrapped exception message to:
|
||||||
|
- "pymilter: untrapped exception in milter app"
|
||||||
|
|
||||||
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1
|
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1
|
||||||
- Raise RuntimeError when result != CONTINUE for @noreply and @nocallback
|
- Raise RuntimeError when result != CONTINUE for @noreply and @nocallback
|
||||||
- Remove redundant table in miltermodule
|
- Remove redundant table in miltermodule
|
||||||
|
|||||||
+6
-6
@@ -19,7 +19,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
self.failUnless(rc == Milter.CONTINUE)
|
self.failUnless(rc == Milter.CONTINUE)
|
||||||
rc = milter.feedMsg(fname)
|
rc = milter.feedMsg(fname)
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
self.failUnless(milter._bodyreplaced,"Message body not replaced")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
@@ -34,7 +34,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
milter.connect('somehost')
|
milter.connect('somehost')
|
||||||
rc = milter.feedMsg(fname)
|
rc = milter.feedMsg(fname)
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
@@ -44,17 +44,17 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
milter.connect('somehost')
|
milter.connect('somehost')
|
||||||
rc = milter.feedMsg('samp1')
|
rc = milter.feedMsg('samp1')
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.")
|
||||||
rc = milter.feedMsg("virus3")
|
rc = milter.feedMsg("virus3")
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
self.failUnless(milter._bodyreplaced,"Message body not replaced")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/virus3.tstout","w").write(fp.getvalue())
|
open("test/virus3.tstout","w").write(fp.getvalue())
|
||||||
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
||||||
rc = milter.feedMsg("virus6")
|
rc = milter.feedMsg("virus6")
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
self.failUnless(milter._bodyreplaced,"Message body not replaced")
|
||||||
self.failUnless(milter.headerschanged,"Message headers not adjusted")
|
self.failUnless(milter._headerschanged,"Message headers not adjusted")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/virus6.tstout","w").write(fp.getvalue())
|
open("test/virus6.tstout","w").write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user