paul@31 | 1 | # -*- coding: iso-8859-1 -*- |
paul@31 | 2 | """ |
paul@31 | 3 | MoinMoin - FetchMessages Action |
paul@31 | 4 | |
paul@31 | 5 | @copyright: 2012, 2013 by Paul Boddie <paul@boddie.org.uk> |
paul@31 | 6 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@31 | 7 | """ |
paul@31 | 8 | |
paul@31 | 9 | from MoinSupport import getMetadata, writeHeaders |
paul@31 | 10 | from MoinMessage import Message |
paul@31 | 11 | from MoinMessageSupport import MoinMessageAction |
paul@31 | 12 | from email.mime.text import MIMEText |
paul@31 | 13 | from email.parser import Parser |
paul@31 | 14 | from itertools import islice |
paul@31 | 15 | |
paul@31 | 16 | try: |
paul@31 | 17 | from cStringIO import StringIO |
paul@31 | 18 | except ImportError: |
paul@31 | 19 | from StringIO import StringIO |
paul@31 | 20 | |
paul@31 | 21 | Dependencies = ['pages'] |
paul@31 | 22 | |
paul@31 | 23 | class FetchMessages(MoinMessageAction): |
paul@31 | 24 | |
paul@31 | 25 | "A handler for requests accessing messages." |
paul@31 | 26 | |
paul@31 | 27 | def handle_message_content(self, content): |
paul@31 | 28 | |
paul@31 | 29 | "Handle the given message 'content'." |
paul@31 | 30 | |
paul@31 | 31 | request = self.request |
paul@31 | 32 | |
paul@31 | 33 | # NOTE: Could employ a more accurate content type. |
paul@31 | 34 | |
paul@31 | 35 | if not content.get_content_type() == "text/plain": |
paul@31 | 36 | writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") |
paul@31 | 37 | request.write("The content does not appear to be a request for messages.") |
paul@31 | 38 | return |
paul@31 | 39 | |
paul@31 | 40 | # Obtain commands from the payload, returning a collection of messages. |
paul@31 | 41 | |
paul@42 | 42 | commands = content.get_payload(decode=True) |
paul@31 | 43 | |
paul@31 | 44 | # Build a container for the responses. |
paul@31 | 45 | |
paul@31 | 46 | message = Message() |
paul@31 | 47 | |
paul@31 | 48 | # Process each command, using RFC 1939 (POP3) as inspiration. |
paul@31 | 49 | |
paul@31 | 50 | for command in commands.split("\n"): |
paul@31 | 51 | command = command.strip() |
paul@31 | 52 | |
paul@31 | 53 | # Get the command and arguments. |
paul@31 | 54 | |
paul@31 | 55 | command_parts = command.split(None, 1) |
paul@31 | 56 | cmd = command_parts[0] |
paul@31 | 57 | |
paul@31 | 58 | # A request to count the messages is returned in a part. |
paul@31 | 59 | |
paul@31 | 60 | if cmd == "STAT": |
paul@31 | 61 | result = str(len(self.store)) |
paul@31 | 62 | part = MIMEText(result, "plain") |
paul@31 | 63 | part["Request-Type"] = "STAT" |
paul@31 | 64 | part["Request-Status"] = "OK" |
paul@31 | 65 | message.add_update(part) |
paul@31 | 66 | |
paul@31 | 67 | # A request for specific messages returns each message in its own |
paul@31 | 68 | # part. |
paul@31 | 69 | |
paul@31 | 70 | elif cmd in ("RETR", "DELE"): |
paul@31 | 71 | |
paul@31 | 72 | try: |
paul@31 | 73 | # Either select all. |
paul@31 | 74 | |
paul@31 | 75 | if len(command_parts) == 1: |
paul@31 | 76 | count = None |
paul@31 | 77 | |
paul@31 | 78 | # Or select a particular number. |
paul@31 | 79 | |
paul@31 | 80 | else: |
paul@31 | 81 | count = int(parameters[1]) |
paul@31 | 82 | |
paul@31 | 83 | except ValueError: |
paul@31 | 84 | part = MIMEText(command, "plain") |
paul@31 | 85 | part["Request-Type"] = cmd |
paul@31 | 86 | part["Request-Status"] = "ERR" |
paul@31 | 87 | message.add_update(part) |
paul@31 | 88 | |
paul@31 | 89 | else: |
paul@31 | 90 | # A request for specific messages returns each message |
paul@31 | 91 | # in its own part within a collection part. |
paul@31 | 92 | |
paul@31 | 93 | if cmd == "RETR": |
paul@31 | 94 | container = Message() |
paul@31 | 95 | |
paul@31 | 96 | for message_text in islice(iter(self.store), count): |
paul@31 | 97 | message_item = Parser().parse(StringIO(message_text)) |
paul@31 | 98 | container.add_update(message_item) |
paul@31 | 99 | |
paul@31 | 100 | # Convert the container to a proper multipart section. |
paul@31 | 101 | |
paul@31 | 102 | message.add_update(container.get_payload()) |
paul@31 | 103 | |
paul@31 | 104 | # A request to delete messages is performed immediately. |
paul@31 | 105 | |
paul@31 | 106 | elif cmd == "DELE": |
paul@31 | 107 | keys = self.store.keys()[:count] |
paul@31 | 108 | keys.sort() |
paul@31 | 109 | |
paul@31 | 110 | for key in keys: |
paul@31 | 111 | del self.store[key] |
paul@31 | 112 | |
paul@31 | 113 | part = MIMEText(result, "plain") |
paul@31 | 114 | part["Request-Type"] = cmd |
paul@31 | 115 | part["Request-Status"] = "OK" |
paul@31 | 116 | message.add_update(part) |
paul@31 | 117 | |
paul@31 | 118 | # Handle invalid commands. |
paul@31 | 119 | |
paul@31 | 120 | elif cmd: |
paul@31 | 121 | part = MIMEText(result, "plain") |
paul@31 | 122 | part["Request-Type"] = cmd |
paul@31 | 123 | part["Request-Status"] = "ERR" |
paul@31 | 124 | message.add_update(part) |
paul@31 | 125 | |
paul@31 | 126 | # Write the response. |
paul@31 | 127 | |
paul@31 | 128 | request.write(message.get_payload().as_string()) |
paul@31 | 129 | return 1, None |
paul@31 | 130 | |
paul@31 | 131 | # Action function. |
paul@31 | 132 | |
paul@31 | 133 | def execute(pagename, request): |
paul@31 | 134 | FetchMessages(pagename, request).do_action() # instead of render |
paul@31 | 135 | |
paul@31 | 136 | # vim: tabstop=4 expandtab shiftwidth=4 |