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