[Spambayes-checkins] spambayes pop3proxy.py,1.9,1.10

Richie Hindle richiehindle@users.sourceforge.net
Tue Nov 5 22:18:59 2002


Update of /cvsroot/spambayes/spambayes
In directory usw-pr-cvs1:/tmp/cvs-serv23270

Modified Files:
	pop3proxy.py 
Log Message:
First cut of the HTML user interface - see the docstring for -b and -u.
Now reads the classification header and its values from the options.
Added TOP support to the test server (to make 40tude Dialog happy).


Index: pop3proxy.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/pop3proxy.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -C2 -d -r1.9 -r1.10
*** pop3proxy.py	2 Nov 2002 21:00:21 -0000	1.9
--- pop3proxy.py	5 Nov 2002 22:18:56 -0000	1.10
***************
*** 15,19 ****
              -p FILE : use the named data file
              -d      : the file is a DBM file rather than a pickle
!             -l port : listen on this port number (default 110)
  
      pop3proxy -t
--- 15,22 ----
              -p FILE : use the named data file
              -d      : the file is a DBM file rather than a pickle
!             -l port : proxy listens on this port number (default 110)
!             -u port : User interface listens on this port number
!                       (default 8880; Browse http://localhost:8880/)
!             -b      : Launch a web browser showing the user interface.
  
      pop3proxy -t
***************
*** 35,40 ****
  
  
! import sys, re, operator, errno, getopt, cPickle, time
! import socket, asyncore, asynchat
  import classifier, tokenizer, hammie
  from Options import options
--- 38,43 ----
  
  
! import sys, re, operator, errno, getopt, cPickle, cStringIO, time
! import socket, asyncore, asynchat, cgi, urlparse, webbrowser
  import classifier, tokenizer, hammie
  from Options import options
***************
*** 42,47 ****
  # HEADER_EXAMPLE is the longest possible header - the length of this one
  # is added to the size of each message.
! HEADER_FORMAT = '%s: %%s\r\n' % hammie.DISPHEADER
! HEADER_EXAMPLE = '%s: Unsure\r\n' % hammie.DISPHEADER
  
  
--- 45,57 ----
  # HEADER_EXAMPLE is the longest possible header - the length of this one
  # is added to the size of each message.
! HEADER_FORMAT = '%s: %%s\r\n' % options.hammie_header_name
! HEADER_EXAMPLE = '%s: xxxxxxxxxxxxxxxxxxxx\r\n' % options.hammie_header_name
! 
! # This keeps the global status of the module - the command-line options,
! # how many mails have been classified, how many active connections there
! # are, and so on.
! class Status:
!     pass
! status = Status()
  
  
***************
*** 61,65 ****
          self.set_socket(s, socketMap)
          self.set_reuse_addr()
!         print "Listening on port %d." % port
          self.bind(('', port))
          self.listen(5)
--- 71,75 ----
          self.set_socket(s, socketMap)
          self.set_reuse_addr()
!         print "%s listening on port %d." % (self.__class__.__name__, port)
          self.bind(('', port))
          self.listen(5)
***************
*** 73,80 ****
              self.factory(*args)
  
  
! class POP3ProxyBase(asynchat.async_chat):
      """An async dispatcher that understands POP3 and proxies to a POP3
!     server, calling `self.onTransaction( request, response )` for each
      transaction. Responses are not un-byte-stuffed before reaching
      self.onTransaction() (they probably should be for a totally generic
--- 83,107 ----
              self.factory(*args)
  
+ class BrighterAsyncChat(asynchat.async_chat):
+     """An asynchat.async_chat that doesn't give spurious warnings on
+     receiving an incoming connection, and lets SystemExit cause an
+     exit."""
  
!     def handle_connect(self):
!         """Suppress the asyncore "unhandled connect event" warning."""
!         pass
! 
!     def handle_error(self):
!         """Let SystemExit cause an exit."""
!         type, v, t = sys.exc_info()
!         if type == SystemExit:
!             raise
!         else:
!             asynchat.async_chat.handle_error(self)
! 
! 
! class POP3ProxyBase(BrighterAsyncChat):
      """An async dispatcher that understands POP3 and proxies to a POP3
!     server, calling `self.onTransaction(request, response)` for each
      transaction. Responses are not un-byte-stuffed before reaching
      self.onTransaction() (they probably should be for a totally generic
***************
*** 88,92 ****
  
      def __init__(self, clientSocket, serverName, serverPort):
!         asynchat.async_chat.__init__(self, clientSocket)
          self.request = ''
          self.set_terminator('\r\n')
--- 115,119 ----
  
      def __init__(self, clientSocket, serverName, serverPort):
!         BrighterAsyncChat.__init__(self, clientSocket)
          self.request = ''
          self.set_terminator('\r\n')
***************
*** 96,103 ****
          self.push(self.serverIn.readline())
  
-     def handle_connect(self):
-         """Suppress the asyncore "unhandled connect event" warning."""
-         pass
- 
      def onTransaction(self, command, args, response):
          """Overide this.  Takes the raw request and the response, and
--- 123,126 ----
***************
*** 221,232 ****
              self.close_when_done()
  
-     def handle_error(self):
-         """Let SystemExit cause an exit."""
-         type, v, t = sys.exc_info()
-         if type == SystemExit:
-             raise
-         else:
-             asynchat.async_chat.handle_error(self)
- 
  
  class BayesProxyListener(Listener):
--- 244,247 ----
***************
*** 276,279 ****
--- 291,296 ----
          self.handlers = {'STAT': self.onStat, 'LIST': self.onList,
                           'RETR': self.onRetr, 'TOP': self.onTop}
+         status.totalSessions += 1
+         status.activeSessions += 1
  
      def send(self, data):
***************
*** 290,293 ****
--- 307,314 ----
          return data
  
+     def close(self):
+         status.activeSessions -= 1
+         POP3ProxyBase.close(self)
+     
      def onTransaction(self, command, args, response):
          """Takes the raw request and response, and returns the
***************
*** 343,352 ****
              # Now find the spam disposition and add the header.
              prob = self.bayes.spamprob(tokenizer.tokenize(message))
              if prob < options.ham_cutoff:
!                 disposition = "No"
              elif prob > options.spam_cutoff:
!                 disposition = "Yes"
              else:
!                 disposition = "Unsure"
              
              headers, body = re.split(r'\n\r?\n', response, 1)
--- 364,381 ----
              # Now find the spam disposition and add the header.
              prob = self.bayes.spamprob(tokenizer.tokenize(message))
+             if command == 'RETR':
+                 status.numEmails += 1
              if prob < options.ham_cutoff:
!                 disposition = options.header_ham_string
!                 if command == 'RETR':
!                     status.numHams += 1
              elif prob > options.spam_cutoff:
!                 disposition = options.header_spam_string
!                 if command == 'RETR':
!                     status.numSpams += 1
              else:
!                 disposition = options.header_unsure_string
!                 if command == 'RETR':
!                     status.numUnsure += 1
              
              headers, body = re.split(r'\n\r?\n', response, 1)
***************
*** 368,372 ****
  
  
! def main(serverName, serverPort, proxyPort, pickleName, useDB):
      """Runs the proxy forever or until a 'KILL' command is received or
      someone hits Ctrl+Break."""
--- 397,646 ----
  
  
! class UserInterfaceListener(Listener):
!     """Listens for incoming web browser connections and spins off
!     UserInterface objects to serve them."""
! 
!     def __init__(self, uiPort, bayes):
!         uiArgs = (bayes,)
!         Listener.__init__(self, uiPort, UserInterface, uiArgs)
! 
! 
! # Until the user interface has had a wider audience, I won't pollute the
! # project with .gif files and the like.  Here's the viking helmet.
! import base64
! helmet = base64.decodestring(
! """R0lGODlhIgAYAPcAAEJCRlVTVGNaUl5eXmtaVm9lXGtrZ3NrY3dvZ4d0Znt3dImHh5R+a6GDcJyU
! jrSdjaWlra2tra2tta+3ur2trcC9t7W9ysDDyMbGzsbS3r3W78bW78be78be973e/8bn/86pjNav
! kc69re/Lrc7Ly9ba4vfWveTh5M7e79be79bn797n7+fr6+/v5+/v7/f3787e987n987n/9bn99bn
! /9bv/97n997v++fv9+f3/+/v9+/3//f39/f/////9////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAB4ALAAAAAAiABgA
! AAj+AD0IHEiwoMGDA2XI8PBhxg2EECN+YJHjwwccOz5E3FhQBgseMmK44KGRo0kaLHzQENljoUmO
! NE74uGHDxQ8aL2GmzFHzZs6NNFr8yKHC5sOfEEUOVcHiR8aNFksi/LCCx1KZPXAilLHBAoYMMSB6
! 9DEUhsyhUgl+wOBAwQIHFsIapGpzaIcTVnvcSOsBhgUFBgYUMKAgAgqNH2J0aPjxR9YPJerqlYEi
! w4YYExQM2FygwIHCKVBgiBChBIsXP5wu3HD2Bw8MC2JD0CygAIHOnhU4cLDA7QWrqfd6iBE5dQsH
! BgJvHiDgNoID0A88V6AAAQSyjl16QIHXBwnNAwDIBAhAwDmDBAjQHyiAIPkC7DnUljhxwkGAAQHE
! B+icIAGD8+clUMByCNjUUkEdlHCBAvflF0BtB/zHQAMSCjhYYBXsoFVBMWAQWH4AAFBbAg2UWOID
! FK432AEO2ABRBwtsFuKDBTSAYgMghBDCAwwgwB4CClQAQ0R/4RciAQjYyMADIIwwAggN+PeWBTPw
! VdAHHEjA4IMR8ojjCCaEEGUCFcygnUQxaEndbhBAwKQIFVAAgQMQHPZTBxrkqUEHfHLAAZ+AdgBR
! QAAAOw==""")
! 
! 
! class UserInterface(BrighterAsyncChat):
!     """Serves the HTML user interface of the proxy."""
! 
!     header = """<html><head><title>Spambayes proxy: %s</title>
!              <style>
!              body { font: 90%% arial, swiss, helvetica }
!              table { font: 90%% arial, swiss, helvetica }
!              form { margin: 0 }
!              .banner { background: #c0e0ff; padding=5; padding-left: 15 }
!              .header { font-size: 133%% }
!              .content { margin: 15 }
!              .sectiontable { border: 1px solid #808080; width: 95%% }
!              .sectionheading { background: fffae0; padding-left: 1ex; 
!                                border-bottom: 1px solid #808080;
!                                font-weight: bold }
!              .sectionbody { padding: 1em }
!              </style>
!              </head>\n"""
! 
!     bodyStart = """<body style='margin: 0'>
!                 <div class='banner'>
!                 <img src='/helmet.gif' align='absmiddle'>
!                 <span class='header'>Spambayes proxy: %s</span></div>
!                 <div class='content'>\n"""
! 
!     footer = """</div>
!              <form action='/shutdown'>
!              <table width='100%%' cellspacing='0'>
!              <tr><td class='banner'>&nbsp;Spambayes Proxy, %s.
!              <a href='http://www.spambayes.org/'>Spambayes.org</a></td>
!              <td align='right' class='banner'>
!              <input type='submit' value='Shutdown now'>
!              </td></tr></table></form>\n"""
! 
!     pageSection = """<table class='sectiontable' cellspacing='0'>
!                   <tr><td class='sectionheading'>%s</td></tr>
!                   <tr><td class='sectionbody'>%s</td></tr></table>
!                   &nbsp;<br>\n"""
!     
!     wordQuery = """<form action='/wordquery'>
!                 <input name='word' type='text' size='30'>
!                 <input type='submit' value='Tell me about this word'>
!                 </form>"""
!     
!     def __init__(self, clientSocket, bayes):
!         BrighterAsyncChat.__init__(self, clientSocket)
!         self.bayes = bayes
!         self.request = ''
!         self.set_terminator('\r\n\r\n')
!         self.helmet = helmet
! 
!     def collect_incoming_data(self, data):
!         """Asynchat override."""
!         self.request = self.request + data
! 
!     def found_terminator(self):
!         """Asynchat override.
!         Read and parse the HTTP request and call an on<Command> handler."""
!         requestLine, headers = self.request.split('\r\n', 1)
!         try:
!             method, url, version = requestLine.strip().split()
!         except ValueError:
!             self.pushError(400, "Malformed request: '%s'" % requestLine)  # XXX: 400??
!             self.close_when_done()
!         else:
!             method = method.upper()
!             _, _, path, _, query, _ = urlparse.urlparse(url)
!             params = cgi.parse_qs(query, keep_blank_values=True)
!             if self.get_terminator() == '\r\n\r\n' and method == 'POST':
!                 # We need to read a body; set a numeric async_chat terminator.
!                 match = re.search(r'(?i)content-length:\s*(\d+)', headers)
!                 self.set_terminator(int(match.group(1)))
!                 self.request = self.request + '\r\n\r\n'
!                 return
!     
!             if type(self.get_terminator()) is type(1):
!                 # We've just read the body of a POSTed request.
!                 self.set_terminator('\r\n\r\n')
!                 body = self.request.split('\r\n\r\n', 1)[1]
!                 match = re.search(r'(?i)content-type:\s*([^\r\n]+)', headers)
!                 contentTypeHeader = match.group(1)
!                 contentType, pdict = cgi.parse_header(contentTypeHeader)
!                 if contentType == 'multipart/form-data':
!                     # multipart/form-data - probably a file upload.
!                     bodyFile = cStringIO.StringIO(body)
!                     params.update(cgi.parse_multipart(bodyFile, pdict))
!                 else:
!                     # A normal x-www-form-urlencoded.
!                     params.update(cgi.parse_qs(body, keep_blank_values=True))
!             
!             # Convert the cgi params into a simple dictionary.
!             plainParams = {}
!             for name, value in params.iteritems():
!                 plainParams[name] = value[0]
!             self.onRequest(path, plainParams)
!             self.close_when_done()
! 
!     def onRequest(self, path, params):
!         """Handles a decoded HTTP request."""
!         if path == '/':
!             path = '/Home'
!         
!         if path == '/helmet.gif':
!             self.pushOKHeaders('image/gif')
!             self.push(self.helmet)
!         else:
!             try:
!                 name = path[1:].capitalize()
!                 handler = getattr(self, 'on' + name)
!             except AttributeError:
!                 self.pushError(404, "Not found: '%s'" % url)
!             else:
!                 # This is a request for a valid page; run the handler.
!                 self.pushOKHeaders('text/html')
!                 self.pushPreamble(name)
!                 handler(params)
!                 timeString = time.asctime(time.localtime())
!                 self.push(self.footer % timeString)
!     
!     def pushOKHeaders(self, contentType):
!         self.push("HTTP/1.0 200 OK\r\n")
!         self.push("Content-Type: %s\r\n" % contentType)
!         self.push("\r\n")
! 
!     def pushError(self, code, message):
!         self.push("HTTP/1.0 %d Error\r\n" % code)
!         self.push("Content-Type: text/html\r\n")
!         self.push("\r\n")
!         self.push("<html><body><p>%d %s</p></body></html>" % (code, message))
!     
!     def pushPreamble(self, name):
!         self.push(self.header % name)
!         if name == 'Home':
!             homeLink = name
!         else:
!             homeLink = "<a href='/'>Home</a> > %s" % name
!         self.push(self.bodyStart % homeLink)
! 
!     def onHome(self, params):
!         summary = """POP3 proxy running on port <b>%(proxyPort)d</b>,
!                   proxying to <b>%(serverName)s:%(serverPort)d</b>.<br>
!                   Active POP3 conversations: <b>%(activeSessions)d</b>.<br>
!                   POP3 conversations this session:
!                     <b>%(totalSessions)d</b>.<br>
!                   Emails classified this session: <b>%(numSpams)d</b> spam,
!                     <b>%(numHams)d</b> ham, <b>%(numUnsure)d</b> unsure.
!                   """ % status.__dict__
!         
!         train = """<form action='/upload' method='POST'
!                     enctype='multipart/form-data'>
!                 Either upload a message file:
!                 <input type='file' name='file'><br>
!                 Or paste the whole message (incuding headers) here:<br>
!                 <textarea name='text' rows='3' cols='60'></textarea><br>
!                 Is this message
!                 <input type='radio' name='which' value='ham'>Ham</input> or
!                 <input type='radio'
!                        name='which' value='spam' checked>Spam</input>?<br>
!                 <input type='submit' value='Train on this message'>
!                 </form>"""
!         
!         body = (self.pageSection % ('Status', summary) +
!                 self.pageSection % ('Word query', self.wordQuery) +
!                 self.pageSection % ('Train', train))
!         self.push(body)
! 
!     def onShutdown(self, params):
!         self.push("<p><b>Shutdown.</b> Goodbye.</p>")
!         self.push(' ')  # Acts as a flush for small buffers.
!         self.shutdown(2)
!         self.close()
!         raise SystemExit
! 
!     def onUpload(self, params):
!         message = params.get('file') or params.get('text')            
!         isSpam = (params['which'] == 'spam')
!         self.bayes.learn(tokenizer.tokenize(message), isSpam, True)
!         self.push("""<p>Trained on your message. Saving database...</p>""")
!         self.push(" ")  # Flush... must find out how to do this properly...
!         if not status.useDB and status.pickleName:
!             fp = open(status.pickleName, 'wb')
!             cPickle.dump(self.bayes, fp, 1)
!             fp.close()
!         self.push("<p>Done.</p><p><a href='/'>Home</a></p>")
! 
!     def onWordquery(self, params):
!         word = params['word']
!         try:
!             # Must be a better way to get __dict__ for a new-style class...
!             wi = self.bayes.wordinfo[word]
!             members = dict(map(lambda n: (n, getattr(wi, n)), wi.__slots__))
!             members['atime'] = time.asctime(time.localtime(members['atime']))
!             info = """Number of spam messages: <b>%(spamcount)d</b>.<br>
!                    Number of ham messages: <b>%(hamcount)d</b>.<br>
!                    Number of times used to classify: <b>%(killcount)s</b>.<br>
!                    Probability that a message containing this word is spam:
!                    <b>%(spamprob)f</b>.<br>
!                    Last used: <b>%(atime)s</b>.<br>""" % members
!         except KeyError:
!             info = "'%s' does not appear in the database." % word
!         
!         body = (self.pageSection % ("Statistics for '%s':" % word, info) +
!                 self.pageSection % ('Word query', self.wordQuery))
!         self.push(body)
! 
! 
! def main(serverName, serverPort, proxyPort,
!          uiPort, launchUI, pickleName, useDB):
      """Runs the proxy forever or until a 'KILL' command is received or
      someone hits Ctrl+Break."""
***************
*** 375,378 ****
--- 649,655 ----
      print "Done."
      BayesProxyListener(serverName, serverPort, proxyPort, bayes)
+     UserInterfaceListener(uiPort, bayes)
+     if launchUI:
+         webbrowser.open_new("http://localhost:%d/" % uiPort)
      asyncore.loop()
  
***************
*** 424,430 ****
  
  
! class TestPOP3Server(asynchat.async_chat):
!     """Minimal POP3 server, for testing purposes.  Doesn't support TOP
!     or UIDL.  USER, PASS, APOP, DELE and RSET simply return "+OK"
      without doing anything.  Also understands the 'KILL' command, to
      kill it.  The mail content is the example messages above.
--- 701,707 ----
  
  
! class TestPOP3Server(BrighterAsyncChat):
!     """Minimal POP3 server, for testing purposes.  Doesn't support
!     UIDL.  USER, PASS, APOP, DELE and RSET simply return "+OK"
      without doing anything.  Also understands the 'KILL' command, to
      kill it.  The mail content is the example messages above.
***************
*** 434,439 ****
          # Grumble: asynchat.__init__ doesn't take a 'map' argument,
          # hence the two-stage construction.
!         asynchat.async_chat.__init__(self)
!         asynchat.async_chat.set_socket(self, clientSocket, socketMap)
          self.maildrop = [spam1, good1]
          self.set_terminator('\r\n')
--- 711,716 ----
          # Grumble: asynchat.__init__ doesn't take a 'map' argument,
          # hence the two-stage construction.
!         BrighterAsyncChat.__init__(self)
!         BrighterAsyncChat.set_socket(self, clientSocket, socketMap)
          self.maildrop = [spam1, good1]
          self.set_terminator('\r\n')
***************
*** 442,453 ****
          self.handlers = {'STAT': self.onStat,
                           'LIST': self.onList,
!                          'RETR': self.onRetr}
          self.push("+OK ready\r\n")
          self.request = ''
  
-     def handle_connect(self):
-         """Suppress the asyncore "unhandled connect event" warning."""
-         pass
- 
      def collect_incoming_data(self, data):
          """Asynchat override."""
--- 719,727 ----
          self.handlers = {'STAT': self.onStat,
                           'LIST': self.onList,
!                          'RETR': self.onRetr,
!                          'TOP': self.onTop}
          self.push("+OK ready\r\n")
          self.request = ''
  
      def collect_incoming_data(self, data):
          """Asynchat override."""
***************
*** 466,469 ****
--- 740,745 ----
                  self.close_when_done()
              if command == 'KILL':
+                 self.shutdown(2)
+                 self.close()
                  raise SystemExit
          else:
***************
*** 472,483 ****
          self.request = ''
  
-     def handle_error(self):
-         """Let SystemExit cause an exit."""
-         type, v, t = sys.exc_info()
-         if type == SystemExit:
-             raise
-         else:
-             asynchat.async_chat.handle_error(self)
- 
      def onStat(self, command, args):
          """POP3 STAT command."""
--- 748,751 ----
***************
*** 502,514 ****
              return '\r\n'.join(returnLines) + '\r\n'
  
!     def onRetr(self, command, args):
!         """POP3 RETR command."""
!         number = int(args)
          if 0 < number <= len(self.maildrop):
              message = self.maildrop[number-1]
              return "+OK\r\n%s\r\n.\r\n" % message
          else:
              return "-ERR no such message\r\n"
  
      def onUnknown(self, command, args):
          """Unknown POP3 command."""
--- 770,793 ----
              return '\r\n'.join(returnLines) + '\r\n'
  
!     def _getMessage(self, number, maxLines):
!         """Implements the POP3 RETR and TOP commands."""
          if 0 < number <= len(self.maildrop):
              message = self.maildrop[number-1]
+             headers, body = message.split('\n\n', 1)
+             bodyLines = body.split('\n')[:maxLines]
+             message = headers + '\r\n\r\n' + '\n'.join(bodyLines)
              return "+OK\r\n%s\r\n.\r\n" % message
          else:
              return "-ERR no such message\r\n"
  
+     def onRetr(self, command, args):
+         """POP3 RETR command."""
+         return self._getMessage(int(args), 12345)
+ 
+     def onTop(self, command, args):
+         """POP3 RETR command."""
+         number, lines = map(int, args.split())
+         return self._getMessage(number, lines)
+ 
      def onUnknown(self, command, args):
          """Unknown POP3 command."""
***************
*** 564,568 ****
          while response.find('\n.\r\n') == -1:
              response = response + proxy.recv(1000)
!         assert response.find(hammie.DISPHEADER) != -1
  
      # Kill the proxy and the test server.
--- 843,847 ----
          while response.find('\n.\r\n') == -1:
              response = response + proxy.recv(1000)
!         assert response.find(options.hammie_header_name) != -1
  
      # Kill the proxy and the test server.
***************
*** 580,592 ****
      # Read the arguments.
      try:
!         opts, args = getopt.getopt(sys.argv[1:], 'htdp:l:')
      except getopt.error, msg:
          print >>sys.stderr, str(msg) + '\n\n' + __doc__
          sys.exit()
  
!     pickleName = hammie.DEFAULTDB
!     proxyPort = 110
!     useDB = False
!     runTestServer = False
      for opt, arg in opts:
          if opt == '-h':
--- 859,880 ----
      # Read the arguments.
      try:
!         opts, args = getopt.getopt(sys.argv[1:], 'htdbp:l:u:')
      except getopt.error, msg:
          print >>sys.stderr, str(msg) + '\n\n' + __doc__
          sys.exit()
  
!     status.pickleName = hammie.DEFAULTDB
!     status.proxyPort = 110
!     status.uiPort = 8880
!     status.serverPort = 110
!     status.useDB = False
!     status.runTestServer = False
!     status.launchUI = False
!     status.totalSessions = 0
!     status.activeSessions = 0
!     status.numEmails = 0
!     status.numSpams = 0
!     status.numHams = 0
!     status.numUnsure = 0
      for opt, arg in opts:
          if opt == '-h':
***************
*** 594,604 ****
              sys.exit()
          elif opt == '-t':
!             runTestServer = True
          elif opt == '-d':
!             useDB = True
          elif opt == '-p':
!             pickleName = arg
          elif opt == '-l':
!             proxyPort = int(arg)
              
      # Do whatever we've been asked to do...
--- 882,896 ----
              sys.exit()
          elif opt == '-t':
!             status.runTestServer = True
!         elif opt == '-b':
!             status.launchUI = True
          elif opt == '-d':
!             status.useDB = True
          elif opt == '-p':
!             status.pickleName = arg
          elif opt == '-l':
!             status.proxyPort = int(arg)
!         elif opt == '-u':
!             status.uiPort = int(arg)
              
      # Do whatever we've been asked to do...
***************
*** 608,623 ****
          print "Self-test passed."   # ...else it would have asserted.
  
!     elif runTestServer:
          print "Running a test POP3 server on port 8110..."
          TestListener()
          asyncore.loop()
  
!     elif len(args) == 1:
!         # Named POP3 server, default port.
!         main(args[0], 110, proxyPort, pickleName, useDB)
! 
!     elif len(args) == 2:
!         # Named POP3 server, named port.
!         main(args[0], int(args[1]), proxyPort, pickleName, useDB)
  
      else:
--- 900,915 ----
          print "Self-test passed."   # ...else it would have asserted.
  
!     elif status.runTestServer:
          print "Running a test POP3 server on port 8110..."
          TestListener()
          asyncore.loop()
  
!     elif 1 <= len(args) <= 2:
!         # Normal usage, with optional server port number.
!         status.serverName = args[0]
!         if len(args) == 2:
!             status.serverPort = int(args[1])
!         main(status.serverName, status.serverPort, status.proxyPort,
!              status.uiPort, status.launchUI, status.pickleName, status.useDB)
  
      else: