def verifyInput(self, parms, pmap): '''Check that the given input is valid.''' errmsg = UserInterface.UserInterface.verifyInput(self, parms, pmap) if pmap != parm_ini_map: return errmsg slist = list(parms['pop3proxy_remote_servers']) plist = list(parms['pop3proxy_listen_ports']) if len(slist) != len(plist): errmsg += _('<li>The number of POP3 proxy ports specified ' \ 'must match the number of servers specified</li>\n') plist.sort() for p in range(len(plist)-1): try: if plist[p] == plist[p+1]: errmsg += _('<li>All POP3 port numbers must be unique</li>') break except IndexError: pass slist = list(parms['smtpproxy_remote_servers']) plist = list(parms['smtpproxy_listen_ports']) if len(slist) != len(plist): errmsg += _('<li>The number of SMTP proxy ports specified ' \ 'must match the number of servers specified</li>\n') plist.sort() for p in range(len(plist)-1): try: if plist[p] == plist[p+1]: errmsg += _('<li>All SMTP port numbers must be unique</li>') break except IndexError: pass return errmsg
def onShowclues(self, key, subject, tokens='0'): """Show clues for a message - linked from the Review page.""" tokens = bool(int(tokens)) # needs the int, as bool('0') is True self._writePreamble(_("Message clues"), parent=('review', _('Review'))) sourceCorpus = None message = None if self.state.unknownCorpus.get(key) is not None: sourceCorpus = self.state.unknownCorpus elif self.state.hamCorpus.get(key) is not None: sourceCorpus = self.state.hamCorpus elif self.state.spamCorpus.get(key) is not None: sourceCorpus = self.state.spamCorpus if sourceCorpus is not None: message = sourceCorpus.get(key).as_string() if message is not None: # For Macs? message = message.replace('\r\n', '\n').replace('\r', '\n') results = self._buildCluesTable(message, subject, tokens) del results.classifyAnother self.write(results) else: self.write(_("<p>Can't find message %r. Maybe it expired.</p>") % key) self._writePostamble()
def onHome(self): """Serve up the homepage.""" stateDict = self.classifier.__dict__.copy() stateDict["warning"] = "" stateDict.update(self.classifier.__dict__) statusTable = self.html.statusTable.clone() del statusTable.proxyDetails statusTable.configurationLink += "<br /> " \ " " + _("You can also <a href='filterfolders'>configure" \ " folders to filter</a><br />and " \ "<a href='trainingfolders'>Configure folders to" \ " train</a>") findBox = self._buildBox(_('Word query'), 'query.gif', self.html.wordQuery) if not options["html_ui", "display_adv_find"]: del findBox.advanced content = (self._buildBox(_('Status and Configuration'), 'status.gif', statusTable % stateDict)+ self._buildTrainBox() + self._buildClassifyBox() + findBox ) self._writePreamble(_("Home")) self.write(content) self._writePostamble()
def onFilterfolders(self): self._writePreamble(_("Select Filter Folders")) self._login_to_imap() available_folders = [] for imap in self.imaps: if imap and imap.logged_in: available_folders.extend(imap.folder_list()) if not available_folders: content = self._buildBox(_("Error"), None, _("No folders available")) self.write(content) self._writePostamble() return content = self.html.configForm.clone() content.configFormContent = "" content.introduction = _("This page allows you to change " \ "which folders are filtered, and " \ "where filtered mail ends up.") content.config_submit.value = _("Save Filter Folders") content.optionsPathname = optionsPathname for opt in ("unsure_folder", "spam_folder", "filter_folders"): folderBox = self._buildFolderBox("imap", opt, available_folders) content.configFormContent += folderBox self.write(content) self._writePostamble()
def onTrainingfolders(self): self._writePreamble(_("Select Training Folders")) self._login_to_imap() available_folders = [] for imap in self.imaps: if imap and imap.logged_in: available_folders.extend(imap.folder_list()) if not available_folders: content = self._buildBox(_("Error"), None, _("No folders available")) self.write(content) self._writePostamble() return content = self.html.configForm.clone() content.configFormContent = "" content.introduction = _("This page allows you to change " \ "which folders contain mail to " \ "train Spambayes.") content.config_submit.value = _("Save Training Folders") content.optionsPathname = optionsPathname for opt in ("ham_train_folders", "spam_train_folders"): folderBox = self._buildFolderBox("imap", opt, available_folders) content.configFormContent += folderBox self.write(content) self._writePostamble()
def onChangeopts(self, **parms): backup = self.parm_ini_map if parms["how"] == _("Save Training Folders") or \ parms["how"] == _("Save Filter Folders"): del parms["how"] self.parm_ini_map = () for opt, value in parms.items(): del parms[opt] if opt[-len(value):] == value: opt = opt[:-len(value)] self.parm_ini_map += ("imap", opt), key = "imap_" + opt if parms.has_key(key): parms[key] += ',' + value else: parms[key] = value UserInterface.UserInterface.onChangeopts(self, **parms) self.parm_ini_map = backup
def onHome(self): """Serve up the homepage.""" stateDict = self.classifier.__dict__.copy() stateDict["warning"] = "" stateDict.update(self.classifier.__dict__) statusTable = self.html.statusTable.clone() del statusTable.proxyDetails # This could be a bit more modular statusTable.configurationLink += "<br /> " \ " " + _("You can also <a href='filterfolders'>configure" \ " folders to filter</a><br />and " \ "<a href='trainingfolders'>Configure folders to" \ " train</a>") findBox = self._buildBox(_('Word query'), 'query.gif', self.html.wordQuery) if not options["html_ui", "display_adv_find"]: del findBox.advanced content = (self._buildBox(_('Status and Configuration'), 'status.gif', statusTable % stateDict)+ self._buildTrainBox() + self._buildClassifyBox() + findBox ) self._writePreamble(_("Home")) self.write(content) self._writePostamble()
def _appendMessages(self, table, keyedMessageInfo, label, sort_order, reverse=False): """Appends the rows of a table of messages to 'table'.""" stripe = 0 keyedMessageInfo = self._sortMessages(keyedMessageInfo, sort_order, reverse) nrows = options["html_ui", "rows_per_section"] for key, messageInfo in keyedMessageInfo[:nrows]: unused, unused, messageInfo.received = self._getTimeRange(self._keyToTimestamp(key)) row = self.html.reviewRow.clone() try: score = messageInfo.score except ValueError: score = None if label == _("Spam"): if score is not None and score > options["html_ui", "spam_discard_level"]: r_att = getattr(row, "discard") else: r_att = getattr(row, options["html_ui", "default_spam_action"]) elif label == _("Ham"): if score is not None and score < options["html_ui", "ham_discard_level"]: r_att = getattr(row, "discard") else: r_att = getattr(row, options["html_ui", "default_ham_action"]) else: r_att = getattr(row, options["html_ui", "default_unsure_action"]) setattr(r_att, "checked", 1) row.optionalHeadersValues = "" # make way for real list for header in options["html_ui", "display_headers"]: header = header.lower() text = getattr(messageInfo, "%sHeader" % (header,)) if header == "subject": h = self.html.reviewRow.linkedHeaderValue.clone() h.text.title = messageInfo.bodySummary h.text.href = "view?key=%s&corpus=%s" % (key, label) else: h = self.html.reviewRow.headerValue.clone() h.text = text row.optionalHeadersValues += h if options["html_ui", "display_score"]: if isinstance(messageInfo.score, types.StringTypes): row.score_ = messageInfo.score else: row.score_ = "%.2f%%" % (messageInfo.score,) else: del row.score_ if options["html_ui", "display_received_time"]: row.received_ = messageInfo.received else: del row.received_ subj_list = [] for c in messageInfo.subjectHeader: subj_list.append("%%%s" % (hex(ord(c))[2:],)) subj = "".join(subj_list) row.classify.href = "showclues?key=%s&subject=%s" % (key, subj) row.tokens.href = "showclues?key=%s&subject=%s&tokens=1" % (key, subj) setattr(row, "class", ["stripe_on", "stripe_off"][stripe]) # Grr! setattr(row, "onMouseOut", ["this.className='stripe_on';", "this.className='stripe_off';"][stripe]) row = str(row).replace("TYPE", label).replace("KEY", key) table += row stripe = stripe ^ 1
def onTrain(self, file, text, which): """Train on an uploaded or pasted message.""" self._writePreamble(_("Train")) content = file or text isSpam = which == _("Train as Spam") if file: content = self._convertToMbox(content) content = content.replace("\r\n", "\n").replace("\r", "\n") messages = self._convertUploadToMessageList(content) if isSpam: desired_corpus = "spamCorpus" else: desired_corpus = "hamCorpus" if hasattr(self, desired_corpus): corpus = getattr(self, desired_corpus) else: if hasattr(self, "state"): corpus = getattr(self.state, desired_corpus) setattr(self, desired_corpus, corpus) self.msg_name_func = self.state.getNewMessageName else: if isSpam: fn = storage.get_pathname_option("Storage", "spam_cache") else: fn = storage.get_pathname_option("Storage", "ham_cache") storage.ensureDir(fn) if options["Storage", "cache_use_gzip"]: factory = FileCorpus.GzipFileMessageFactory() else: factory = FileCorpus.FileMessageFactory() age = options["Storage", "cache_expiry_days"] * 24 * 60 * 60 corpus = FileCorpus.ExpiryFileCorpus(age, factory, fn, "[0123456789\-]*", cacheSize=20) setattr(self, desired_corpus, corpus) class UniqueNamer(object): count = -1 def generate_name(self): self.count += 1 return "%10.10d-%d" % (long(time.time()), self.count) Namer = UniqueNamer() self.msg_name_func = Namer.generate_name self.write("<b>" + _("Training") + "...</b>\n") self.flush() for message in messages: key = self.msg_name_func() msg = corpus.makeMessage(key, message) msg.setId(key) corpus.addMessage(msg) msg.RememberTrained(isSpam) self.stats.RecordTraining(not isSpam) self._doSave() self.write(_("%sOK. Return %sHome%s or train again:%s") % ("<p>", "<a href='home'>", "</a", "</p>")) self.write(self._buildTrainBox()) self._writePostamble()
def onStats(self): """Provide statistics about previous SpamBayes activity.""" self._writePreamble(_("Statistics")) if self.stats: stats = self.stats.GetStats(use_html=True) stats = self._buildBox(_("Statistics"), None, "<br/><br/>".join(stats)) else: stats = self._buildBox(_("Statistics"), None, _("Statistics not available")) self.write(stats) self._writePostamble(help_topic="stats")
def _verifyEnteredDetails(self, from_addr, subject, message): """Ensure that the user didn't just send the form message, and at least changed the fields.""" if from_addr.startswith(_("[YOUR EMAIL ADDRESS]")): return False if message.endswith(_("[DESCRIBE YOUR PROBLEM HERE]")): return False if subject.endswith(_("[PROBLEM SUMMARY]")): return False return True
def onAdvancedconfig(self): html = self._buildConfigPage(self.advanced_options_map) html.title = _("Home > Advanced Configuration") html.pagename = _("> Advanced Configuration") html.adv_button.name.value = _("Back to basic configuration") html.adv_button.action = "config" html.config_submit.value = _("Save advanced options") html.restore.value = _("Restore advanced options defaults") del html.exp_button self.writeOKHeaders("text/html") self.write(html)
def onExperimentalconfig(self): html = self._buildConfigPage(experimental_ini_map) html.title = _("Home > Experimental Configuration") html.pagename = _("> Experimental Configuration") html.adv_button.name.value = _("Back to basic configuration") html.adv_button.action = "config" html.config_submit.value = _("Save experimental options") html.restore.value = _("Restore experimental options defaults (all off)") del html.exp_button self.writeOKHeaders("text/html") self.write(html)
def _makeMessageInfo(self, message): """Given an email.Message, return an object with subjectHeader, bodySummary and other header (as needed) attributes. These objects are passed into appendMessages by onReview - passing email.Message objects directly uses too much memory. """ message.delNotations() subjectHeader = message["Subject"] or "(none)" headers = {"subject": subjectHeader} for header in options["html_ui", "display_headers"]: headers[header.lower()] = message[header] or "(none)" score = message[options["Headers", "score_header_name"]] if score: op = score.find("(") if op >= 0: score = score[:op] try: score = float(score) * 100 except ValueError: score = "Err" # Let the user know something is wrong. else: score = "?" try: part = typed_subpart_iterator(message, "text", "plain").next() text = part.get_payload() except StopIteration: try: part = typed_subpart_iterator(message, "text", "html").next() text = part.get_payload() text, unused = tokenizer.crack_html_style(text) text, unused = tokenizer.crack_html_comment(text) text = tokenizer.html_re.sub(" ", text) text = _("(this message only has an HTML body)\n") + text except StopIteration: text = _("(this message has no text body)") if type(text) == type([]): # gotta be a 'right' way to do this text = _("(this message is a digest of %s messages)") % (len(text)) elif text is None: text = _("(this message has no body)") else: text = text.replace(" ", " ") # Else they'll be quoted text = re.sub(r"(\s)\s+", r"\1", text) # Eg. multiple blank lines text = text.strip() class _MessageInfo: pass messageInfo = _MessageInfo() for headerName, headerValue in headers.items(): headerValue = self._trimHeader(headerValue, 45, True) setattr(messageInfo, "%sHeader" % (headerName,), headerValue) messageInfo.score = score messageInfo.bodySummary = self._trimHeader(text, 200) return messageInfo
def onPluginconfig(self): html = self._buildConfigPage(self.plugin.plugin_map) html.title = _('Home > Plugin Configuration') html.pagename = _('> Plugin Configuration') html.plugin_button.name.value = _("Back to basic configuration") html.plugin_button.action = "config" html.config_submit.value = _("Save plugin options") html.restore.value = _("Restore plugin options defaults") del html.exp_button del html.adv_button self.writeOKHeaders('text/html') self.write(html)
def verifyInput(self, parms, pmap): """Check that the given input is valid.""" errmsg = "" for name, value in parms.items(): if name[-2:-1] == "-": if parms.has_key(name[:-2]): parms[name[:-2]] += (value,) else: parms[name[:-2]] = (value,) del parms[name] for sect, opt in pmap: if opt is None: nice_section_name = sect continue if sect == "Headers" and opt in ("notate_to", "notate_subject"): valid_input = ( options["Headers", "header_ham_string"], options["Headers", "header_spam_string"], options["Headers", "header_unsure_string"], ) else: valid_input = options.valid_input(sect, opt) html_key = sect + "_" + opt if not parms.has_key(html_key): value = () entered_value = "None" else: value = parms[html_key] entered_value = value if options.is_boolean(sect, opt): if value == _("No"): value = False elif value == _("Yes"): value = True if options.multiple_values_allowed(sect, opt) and value == "": value = () value = options.convert(sect, opt, value) if not options.is_valid(sect, opt, value): errmsg += _("<li>'%s' is not a value valid for [%s] %s") % ( entered_value, nice_section_name, options.display_name(sect, opt), ) if isinstance(valid_input, types.TupleType): errmsg += _(". Valid values are: ") for valid in valid_input: errmsg += str(valid) + "," errmsg = errmsg[:-1] # cut last ',' errmsg += "</li>" parms[html_key] = value return errmsg
class XMLRPCUI(PluginUI): plugin_map = ( (_('XML-RPC Options'), None), ('Plugin', 'xmlrpc_path'), ('Plugin', 'xmlrpc_host'), ('Plugin', 'xmlrpc_port'), )
def verifyInput(self, parms, pmap): '''Check that the given input is valid.''' # Most of the work here is done by the parent class, but # we have a few extra checks errmsg = UserInterface.UserInterface.verifyInput(self, parms, pmap) if pmap != parm_ini_map: return errmsg # check for equal number of pop3servers and ports slist = list(parms['pop3proxy_remote_servers']) plist = list(parms['pop3proxy_listen_ports']) if len(slist) != len(plist): errmsg += _('<li>The number of POP3 proxy ports specified ' \ 'must match the number of servers specified</li>\n') # check for duplicate ports plist.sort() for p in range(len(plist) - 1): try: if plist[p] == plist[p + 1]: errmsg += _( '<li>All POP3 port numbers must be unique</li>') break except IndexError: pass # check for equal number of smtpservers and ports slist = list(parms['smtpproxy_remote_servers']) plist = list(parms['smtpproxy_listen_ports']) if len(slist) != len(plist): errmsg += _('<li>The number of SMTP proxy ports specified ' \ 'must match the number of servers specified</li>\n') # check for duplicate ports plist.sort() for p in range(len(plist) - 1): try: if plist[p] == plist[p + 1]: errmsg += _( '<li>All SMTP port numbers must be unique</li>') break except IndexError: pass return errmsg
def onView(self, key, corpus): """View a message - linked from the Review page.""" self._writePreamble(_("View message"), parent=('review', _('Review'))) sourceCorpus = None message = None if state.unknownCorpus.get(key) is not None: sourceCorpus = state.unknownCorpus elif state.hamCorpus.get(key) is not None: sourceCorpus = state.hamCorpus elif state.spamCorpus.get(key) is not None: sourceCorpus = state.spamCorpus if sourceCorpus is not None: message = sourceCorpus.get(key) if message is not None: self.write("<pre>%s</pre>" % cgi.escape(message.as_string())) else: self.write( _("<p>Can't find message %r. Maybe it expired.</p>") % key) self._writePostamble()
def onHome(self): """Serve up the homepage.""" self.state.buildStatusStrings() stateDict = self.state.__dict__.copy() stateDict.update(self.state.bayes.__dict__) statusTable = self.html.statusTable.clone() findBox = self._buildBox(_('Word query'), 'query.gif', self.html.wordQuery) if not options["html_ui", "display_adv_find"]: del findBox.advanced content = (self._buildBox(_('Status and Configuration'), 'status.gif', statusTable % stateDict) + self._buildBox(_('Train on proxied messages'), 'train.gif', self.html.reviewText) + self._buildTrainBox() + self._buildClassifyBox() + findBox + self._buildBox( _('Find message'), 'query.gif', self.html.findMessage)) self._writePreamble(_("Home")) self.write(content) self._writePostamble(help_topic="home_proxy")
def onChangeopts(self, **parms): backup = self.parm_ini_map if parms["how"] == _("Save Training Folders") or \ parms["how"] == _("Save Filter Folders"): del parms["how"] self.parm_ini_map = () for opt, value in parms.items(): del parms[opt] # Under strange circumstances this could break, # so if we can think of a better way to do this, # that would be nice. if opt[-len(value):] == value: opt = opt[:-len(value)] self.parm_ini_map += ("imap", opt), key = "imap_" + opt if parms.has_key(key): parms[key] += ',' + value else: parms[key] = value UserInterface.UserInterface.onChangeopts(self, **parms) self.parm_ini_map = backup
def onUpload(self, file): """Save a message for later training - used by Skip's proxytee.py.""" # Convert platform-specific line endings into unix-style. file = file.replace('\r\n', '\n').replace('\r', '\n') # Get a message list from the upload and write it into the cache. messages = self._convertUploadToMessageList(file) for m in messages: messageName = state.getNewMessageName() message = state.unknownCorpus.makeMessage(messageName, m) state.unknownCorpus.addMessage(message) # Return a link Home. self.write(_("<p>OK. Return <a href='home'>Home</a>.</p>"))
def onShowclues(self, key, subject, tokens='0'): """Show clues for a message - linked from the Review page.""" tokens = bool(int(tokens)) # needs the int, as bool('0') is True self._writePreamble(_("Message clues"), parent=('review', _('Review'))) sourceCorpus = None message = None if state.unknownCorpus.get(key) is not None: sourceCorpus = state.unknownCorpus elif state.hamCorpus.get(key) is not None: sourceCorpus = state.hamCorpus elif state.spamCorpus.get(key) is not None: sourceCorpus = state.spamCorpus if sourceCorpus is not None: message = sourceCorpus.get(key).as_string() if message is not None: message = message.replace('\r\n', '\n').replace('\r', '\n') # For Macs results = self._buildCluesTable(message, subject, tokens) del results.classifyAnother self.write(results) else: self.write( _("<p>Can't find message %r. Maybe it expired.</p>") % key) self._writePostamble()
def buildStatusStrings(self): """Build the status message(s) to display on the home page of the web interface.""" nspam = self.bayes.nspam nham = self.bayes.nham if nspam > 10 and nham > 10: db_ratio = nham / float(nspam) if db_ratio > 5.0: self.warning = _("Warning: you have much more ham than " \ "spam - SpamBayes works best with " \ "approximately even numbers of ham and " \ "spam.") elif db_ratio < (1 / 5.0): self.warning = _("Warning: you have much more spam than " \ "ham - SpamBayes works best with " \ "approximately even numbers of ham and " \ "spam.") else: self.warning = "" elif nspam > 0 or nham > 0: self.warning = _("Database only has %d good and %d spam - " \ "you should consider performing additional " \ "training.") % (nham, nspam) else: self.warning = _("Database has no training information. " \ "SpamBayes will classify all messages as " \ "'unsure', ready for you to train.") # Add an additional warning message if the user's thresholds are # truly odd. spam_cut = options["Categorization", "spam_cutoff"] ham_cut = options["Categorization", "ham_cutoff"] if spam_cut < 0.5: self.warning += _("<br/>Warning: we do not recommend " \ "setting the spam threshold less than 0.5.") if ham_cut > 0.5: self.warning += _("<br/>Warning: we do not recommend " \ "setting the ham threshold greater than 0.5.") if ham_cut > spam_cut: self.warning += _("<br/>Warning: your ham threshold is " \ "<b>higher</b> than your spam threshold. " \ "Results are unpredictable.")
def stop(): # Shutdown as though through the web UI. This will save the DB, allow # any open proxy connections to complete, etc. from urllib import urlopen, urlencode urlopen('http://localhost:%d/save' % state.uiPort, urlencode({'how': _('Save & shutdown')})).read()
('Headers', 'notate_subject'), ('Storage Options', None), ('Storage', 'persistent_storage_file'), ('Storage', 'messageinfo_storage_file'), ('Storage', 'cache_messages'), ('Storage', 'no_cache_bulk_ham'), ('Storage', 'no_cache_large_messages'), ('Statistics Options', None), ('Categorization', 'ham_cutoff'), ('Categorization', 'spam_cutoff'), ) # Like the above, but these are the options that will be offered on the # advanced configuration page. adv_map = ( (_('Statistics Options'), None), ('Classifier', 'max_discriminators'), ('Classifier', 'minimum_prob_strength'), ('Classifier', 'unknown_word_prob'), ('Classifier', 'unknown_word_strength'), ('Classifier', 'use_bigrams'), (_('Header Options'), None), ('Headers', 'include_score'), ('Headers', 'header_score_digits'), ('Headers', 'header_score_logarithm'), ('Headers', 'include_thermostat'), ('Headers', 'include_evidence'), ('Headers', 'clue_mailheader_cutoff'), (_('Storage Options'), None), ('Storage', 'persistent_use_database'), ('Storage', 'cache_expiry_days'),
def onReview(self, **params): """Present a list of message for (re)training.""" # Train/discard sumbitted messages. self._writePreamble("Review") id = '' numTrained = 0 numDeferred = 0 if params.get('go') != _('Refresh'): for key, value in params.items(): if key.startswith('classify:'): old_class, id = key.split(':')[1:3] if value == _('spam'): targetCorpus = state.spamCorpus stats_as_ham = False elif value == _('ham'): targetCorpus = state.hamCorpus stats_as_ham = True elif value == _('discard'): targetCorpus = None try: state.unknownCorpus.removeMessage(\ state.unknownCorpus[id]) except KeyError: pass # Must be a reload. else: # defer targetCorpus = None numDeferred += 1 if targetCorpus: sourceCorpus = None if state.unknownCorpus.get(id) is not None: sourceCorpus = state.unknownCorpus elif state.hamCorpus.get(id) is not None: sourceCorpus = state.hamCorpus elif state.spamCorpus.get(id) is not None: sourceCorpus = state.spamCorpus if sourceCorpus is not None: try: # fromCache is a fix for sf #851785. # See the comments in Corpus.py targetCorpus.takeMessage(id, sourceCorpus, fromCache=True) if numTrained == 0: self.write(_("<p><b>Training... ")) self.flush() numTrained += 1 self.stats.RecordTraining(\ stats_as_ham, old_class=old_class) except KeyError: pass # Must be a reload. # Report on any training, and save the database if there was any. if numTrained > 0: plural = '' if numTrained == 1: response = "Trained on one message. " else: response = "Trained on %d messages. " % (numTrained, ) self._doSave() self.write(response) self.write("<br> ") title = "" keys = [] sourceCorpus = state.unknownCorpus # If any messages were deferred, show the same page again. if numDeferred > 0: start = self._keyToTimestamp(id) # Else after submitting a whole page, display the prior page or the # next one. Derive the day of the submitted page from the ID of the # last processed message. elif id: start = self._keyToTimestamp(id) unused, unused, prior, unused, next = self._buildReviewKeys(start) if prior: start = prior else: start = next # Else if they've hit Previous or Next, display that page. elif params.get('go') == _('Next day'): start = self._keyToTimestamp(params['next']) elif params.get('go') == _('Previous day'): start = self._keyToTimestamp(params['prior']) # Else if an id has been specified, just show that message # Else if search criteria have been specified, show the messages # that match those criteria. elif params.get('find') is not None: prior = next = 0 keys = set() # so we don't end up with duplicates push = keys.add try: max_results = int(params['max_results']) except ValueError: max_results = 1 key = params['find'] if params.has_key('ignore_case'): ic = True else: ic = False error = False if key == "": error = True page = _("<p>You must enter a search string.</p>") else: if len(keys) < max_results and \ params.has_key('id'): if state.unknownCorpus.get(key): push((key, state.unknownCorpus)) elif state.hamCorpus.get(key): push((key, state.hamCorpus)) elif state.spamCorpus.get(key): push((key, state.spamCorpus)) if params.has_key('subject') or params.has_key('body') or \ params.has_key('headers'): # This is an expensive operation, so let the user know # that something is happening. self.write(_('<p>Searching...</p>')) for corp in [ state.unknownCorpus, state.hamCorpus, state.spamCorpus ]: for k in corp.keys(): if len(keys) >= max_results: break msg = corp[k] msg.load() if params.has_key('subject'): subj = str(msg['Subject']) if self._contains(subj, key, ic): push((k, corp)) if params.has_key('body'): # For [ 906581 ] Assertion failed in search # subject. Can the headers be a non-string? msg_body = msg.as_string() msg_body = msg_body[msg_body.index('\r\n\r\n' ):] if self._contains(msg_body, key, ic): push((k, corp)) if params.has_key('headers'): for nm, val in msg.items(): # For [ 906581 ] Assertion failed in # search subject. Can the headers be # a non-string? nm = str(nm) val = str(val) if self._contains(nm, key, ic) or \ self._contains(val, key, ic): push((k, corp)) if len(keys): if len(keys) == 1: title = _("Found message") else: title = _("Found messages") keys = list(keys) else: page = _("<p>Could not find any matching messages. " \ "Maybe they expired?</p>") title = _("Did not find message") box = self._buildBox(title, 'status.gif', page) self.write(box) self.write( self._buildBox(_('Find message'), 'query.gif', self.html.findMessage)) self._writePostamble() return # Else show the most recent day's page, as decided by _buildReviewKeys. else: start = 0 # Build the lists of messages: spams, hams and unsure. if len(keys) == 0: keys, date, prior, this, next = self._buildReviewKeys(start) keyedMessageInfo = { options["Headers", "header_unsure_string"]: [], options["Headers", "header_ham_string"]: [], options["Headers", "header_spam_string"]: [], } invalid_keys = [] for key in keys: if isinstance(key, types.TupleType): key, sourceCorpus = key else: sourceCorpus = state.unknownCorpus # Parse the message, get the judgement header and build a message # info object for each message. message = sourceCorpus[key] try: message.load() except IOError: # Someone has taken this file away from us. It was # probably a virus protection program, so that's ok. # Don't list it in the review, though. invalid_keys.append(key) continue judgement = message[options["Headers", "classification_header_name"]] if judgement is None: judgement = options["Headers", "header_unsure_string"] else: judgement = judgement.split(';')[0].strip() messageInfo = self._makeMessageInfo(message) keyedMessageInfo[judgement].append((key, messageInfo)) for key in invalid_keys: keys.remove(key) # Present the list of messages in their groups in reverse order of # appearance, by default, or according to the specified sort order. if keys: page = self.html.reviewtable.clone() if prior: page.prior.value = prior del page.priorButton.disabled if next: page.next.value = next del page.nextButton.disabled templateRow = page.reviewRow.clone() # The decision about whether to reverse the sort # order has to go here, because _sortMessages gets called # thrice, and so the ham list would end up sorted backwards. sort_order = params.get('sort') if self.previous_sort == sort_order: reverse = True self.previous_sort = None else: reverse = False self.previous_sort = sort_order page.table = "" # To make way for the real rows. for header, label in ((options["Headers", "header_unsure_string"], 'Unsure'), (options["Headers", "header_ham_string"], 'Ham'), (options["Headers", "header_spam_string"], 'Spam')): messages = keyedMessageInfo[header] if messages: sh = self.html.reviewSubHeader.clone() # Setup the header row sh.optionalHeaders = '' h = self.html.headerHeader.clone() for disp_header in options["html_ui", "display_headers"]: h.headerLink.href = 'review?sort=%sHeader' % \ (disp_header.lower(),) h.headerName = disp_header.title() sh.optionalHeaders += h if not options["html_ui", "display_score"]: del sh.score_header if not options["html_ui", "display_received_time"]: del sh.received_header subHeader = str(sh) subHeader = subHeader.replace('TYPE', label) page.table += self.html.blankRow page.table += subHeader self._appendMessages(page.table, messages, label, sort_order, reverse) page.table += self.html.trainRow if title == "": title = _("Untrained messages received on %s") % date box = self._buildBox(title, None, page) # No icon, to save space. else: page = _("<p>There are no untrained messages to display. " \ "Return <a href='home'>Home</a>, or " \ "<a href='review'>check again</a>.</p>") title = _("No untrained messages") box = self._buildBox(title, 'status.gif', page) self.write(box) self._writePostamble(help_topic="review")
__author__ = "Tony Meyer <*****@*****.**>, Tim Stone" __credits__ = "All the Spambayes folk." import cgi from spambayes import UserInterface from spambayes.Options import options, optionsPathname, _ # These are the options that will be offered on the configuration page. # If the option is None, then the entry is a header and the following # options will appear in a new box on the configuration page. # These are also used to generate http request parameters and template # fields/variables. parm_map = ( (_('IMAP Options'), None), ('imap', 'server'), ('imap', 'username'), # to display, or not to display; that is the question! # if we show this here, it's in plain text for everyone to # see (and worse - if we don't restrict connections to # localhost, it's available for the world to see) # on the other hand, we have to be able to enter it somehow... ('imap', 'password'), ('imap', 'use_ssl'), (_('Header Options'), None), ('Headers', 'notate_to'), ('Headers', 'notate_subject'), (_('Storage Options'), None), ('Storage', 'persistent_storage_file'), ('Storage', 'messageinfo_storage_file'),
def _login_to_imap_server(self, imap, i): if imap and imap.logged_in: return imap if imap is None or not imap.connected: try: server = options["imap", "server"][i] except KeyError: content = self._buildBox(_("Error"), None, _("Please check server/port details.")) self.write(content) return None if server.find(':') > -1: server, port = server.split(':', 1) port = int(port) else: if options["imap", "use_ssl"]: port = 993 else: port = 143 imap = self.imap_session_class(server, port) if not imap.connected: # Failed to connect. content = self._buildBox(_("Error"), None, _("Please check server/port details.")) self.write(content) return None usernames = options["imap", "username"] if not usernames: content = self._buildBox(_("Error"), None, _("Must specify username first.")) self.write(content) return None if not self.imap_pwds: self.imap_pwd = options["imap", "password"] if not self.imap_pwds: content = self._buildBox(_("Error"), None, _("Must specify password first.")) self.write(content) return None try: imap.login(usernames[i], self.imap_pwds[i]) except KeyError: content = self._buildBox(_("Error"), None, _("Please check username/password details.")) self.write(content) return None except LoginFailure, e: content = self._buildBox(_("Error"), None, str(e)) self.write(content) return None