From 86342902e131fdfa21ee4072b3e55b18be978b48 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 16 Jul 2009 07:34:31 -0400 Subject: be-hand-mail now catches errors and sends appropriate responses. --- interfaces/email/interactive/be-handle-mail | 121 +++++++++++++++++++++------- 1 file changed, 94 insertions(+), 27 deletions(-) (limited to 'interfaces/email/interactive') diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail index 0eb3cce..243d337 100755 --- a/interfaces/email/interactive/be-handle-mail +++ b/interfaces/email/interactive/be-handle-mail @@ -46,18 +46,39 @@ BE_DIR = os.path.expanduser("~/src/fun/be/be.email") ALLOWED_COMMANDS = ["new", "comment", "list", "show", "help"] class InvalidEmail (ValueError): - def __init__(self, msg, message): + def __init__(self, msg, info, message): ValueError.__init__(self, message) self.msg = msg + self.info = info + def response(self): + ret = 1 + out_text = None + return (ret, out_text, self.stderr_msg(), self.info) + def stderr_msg(self): + err_text = [u"Invalid email (particular type unknown):\n", + unicode(self), u"", + send_pgp_mime.flatten(self.msg)] + return u"\n".join(err_text) class InvalidSubject (InvalidEmail): - pass + def stderr_msg(self): + err_text = u"\n".join([u"InvalidSubject:\n", + unicode(e), u"", + u"full subject was:", + e.msg["subject"]]) + return err_text class InvalidCommand (InvalidEmail): - def __init__(self, msg, command): + def __init__(self, msg, info, command): message = "Invalid command '%s'" % command - ValueError.__init__(self, msg, message) + InvalidEmail.__init__(self, msg, info, message) self.command = command + def stderr_msg(self): + err_text = u"\n".join([u"InvalidCommand:\n", + unicode(self), u"", + u"full subject was:", + self.msg["subject"]]) + return err_text def get_body_type(msg): for part in msg.walk(): @@ -66,30 +87,42 @@ def get_body_type(msg): return (part.get_payload(decode=1), part.get_content_type()) def run_message(msg_text): + """ + Attempt to execute the email given in the email string msg_text. + Raises assorted subclasses of InvalidEmail in the case of invalid + messages, otherwise return the exit code, stdout, and stderr + produced by the command, as well as a dictionary of information + gleaned from the email. + """ encoding = libbe.encoding.get_encoding() libbe.encoding.set_IO_stream_encodings(encoding) p=email.Parser.Parser() msg=p.parsestr(msg_text) - if "subject" not in msg: - raise InvalidSubject(msg, "Email must contain a subject") + info = {} + info["encoding"] = encoding author = send_pgp_mime.source_email(msg, return_realname=True) - author_name = author[0] - author_email = author[1] - author_addr = email.utils.formataddr((author_name, author_email)) + info["author_name"] = author[0] + info["author_email"] = author[1] + info["author_addr"] = email.utils.formataddr( + (info["author_name"], info["author_email"])) + info["message-id"] = msg["message-id"] if LOGFILE != None: f = file(LOGFILE, "w+") - f.write("handling %s\n" % (author_addr)) + f.write("handling %s\n" % (info["author_addr"])) f.write("\n%s\n\n" % msg_text) f.close() - id = msg["message-id"] + if "subject" not in msg: + raise InvalidSubject(msg, info, "Email must contain a subject") args = msg["subject"].split() if len(args) < 1 or args[0] != SUBJECT_COMMENT: - raise InvalidSubject(msg, "Subject must start with '%s '" % SUBJECT_COMMENT) + raise InvalidSubject( + msg, info, "Subject must start with '%s '" % SUBJECT_COMMENT) elif len(args) < 2: - raise InvalidCommand(msg, "") + raise InvalidCommand(msg, info, "") # don't accept blank commands command = args[1] + info["command"] = command if command not in ALLOWED_COMMANDS: raise InvalidCommand(msg, command) if len(args) > 2: @@ -110,6 +143,7 @@ def run_message(msg_text): if "--alt-id" not in args: command_args = ["--alt-id", msg["message-id"]] + command_args command_args.append(body) + info["command-args"] = command_args # catch stdout and stderr new_stdout = StringIO.StringIO() new_stderr = StringIO.StringIO() @@ -118,9 +152,19 @@ def run_message(msg_text): sys.stdout = new_stdout sys.stderr = new_stderr # run the command + err = None os.chdir(BE_DIR) - ret = libbe.cmdutil.execute(command, command_args, - manipulate_encodings=False) + try: + ret = libbe.cmdutil.execute(command, command_args, + manipulate_encodings=False) + except libbe.cmdutil.GetHelp: + print libbe.cmdutil.help(command) + except libbe.cmdutil.GetCompletions: + err = InvalidCommand(msg, info, "invalid option '--complete'") + except libbe.cmdutil.UsageError, e: + err = InvalidCommand(msg, info, e) + except libbe.cmdutil.UserError, e: + err = InvalidCommand(msg, info, e) # restore stdout and stderr sys.stdout.flush() sys.stderr.flush() @@ -128,36 +172,59 @@ def run_message(msg_text): sys.stderr = orig_stderr out_text = new_stdout.getvalue() err_text = new_stderr.getvalue() + if err != None: + raise err + return (ret, out_text, err_text, info) - author_addr = "wking" +def compose_response(ret, out_text, err_text, info): + info["author_addr"] = "wking" # for local testing + assert "encoding" in info + if "author_addr" not in info: + return None + if "command" not in info: + info["command"] = u"-BLANK-" + if "command_args" not in info: + info["command_args"] = [] response_header = [u"From: %s" % HANDLER_ADDRESS, - u"To: %s" % author_addr, + u"To: %s" % info["author_addr"], u"Date: %s" % libbe.utility.time_to_str(time.time()), - u"Content-Type: text/plain; charset=%s" % encoding, + u"Content-Type: text/plain; charset=%s"%info["encoding"], u"Content-Transfer-Encoding: 8bit", - u"In-reply-to: %s" % (id), - u"Subject: %s Re: %s" % (SUBJECT_COMMENT, command), + u"Subject: %s Re: %s"%(SUBJECT_COMMENT,info["command"]), ] + if "message-id" in info: + response_header.append(u"In-reply-to: %s" % info["message-id"]) response_body = [u"Results of running: (exit code %d)" % ret, - u" %s %s" % (command, " ".join(command_args)), - u""] # trailing endline on body - if len(out_text) > 0: + u" %s %s" % (info["command"], + u" ".join(info["command_args"]))] + if out_text != None and len(out_text) > 0: response_body.extend([u"", u"stdout:", u"", out_text]) - if len(err_text) > 0: + if err_text != None and len(err_text) > 0: response_body.extend([u"", u"stderr:", u"", err_text]) - response_body.append(u"") + response_body.append(u"") # trailing endline response_email = send_pgp_mime.Mail(u"\n".join(response_header), u"\n".join(response_body)) if LOGFILE != None: f = file(LOGFILE, "a+") - f.write("responding to %s: %s\n" % (author_addr, command)) + f.write("responding to %s: %s\n" + % (info["author_addr"], info["command"])) f.write("\n%s\n\n" % send_pgp_mime.flatten(response_email.plain())) f.close() return response_email def main(): msg_text = sys.stdin.read() - response_email = run_message(msg_text) + try: + ret,out_text,err_text,info = run_message(msg_text) + except InvalidEmail, e: + ret,out_text,err_text,info = e.response() + except Exception, e: + if LOGFILE != None: + f = file(LOGFILE, "a+") + f.write("Uncaught exception:\n%s\n" % (e,)) + f.close() + sys.exit(1) + response_email = compose_response(ret, out_text, err_text, info) send_pgp_mime.mail(response_email.plain(), send_pgp_mime.sendmail) if __name__ == "__main__": -- cgit