1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - SendMessage Action 4 5 @copyright: 2012, 2013, 2014 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin.action import ActionBase 10 from MoinMoin.Page import Page 11 from MoinMoin.wikiutil import escape, MimeType 12 13 from MoinMessage import GPG, MoinMessageError, Message, sendMessage, timestamp, \ 14 as_string 15 from MoinMessageSupport import get_signing_users, get_recipients, get_relays, \ 16 get_recipient_details, get_signing_identity, \ 17 MoinMessageRecipientError, OutgoingHTMLFormatter 18 from MoinSupport import * 19 from ItemSupport import ItemStore 20 21 from email.mime.base import MIMEBase 22 from email.mime.image import MIMEImage 23 from email.mime.multipart import MIMEMultipart 24 from email.mime.text import MIMEText 25 26 class SendMessage(ActionBase, ActionSupport): 27 28 "An action that can send a message to another site." 29 30 def get_form_html(self, buttons_html): 31 32 "Present an interface for message sending." 33 34 _ = self._ 35 request = self.request 36 form = self.get_form() 37 38 message = form.get("message", [""])[0] 39 recipient = form.get("recipient", [""])[0] 40 format = form.get("format", ["wiki"])[0] 41 preview = form.get("preview") 42 action = form.get("send-action", ["send"])[0] 43 44 # Get a list of potential recipients. 45 46 recipients = get_recipients(request, fetching=False) 47 48 # Prepare the recipients list, selecting the specified recipients. 49 50 recipients_list = [] 51 52 if recipients: 53 recipients_list += self.get_option_list(recipient, recipients) or [] 54 55 recipients_list.sort() 56 57 # Prepare any preview. 58 59 parser_cls = getParserClass(request, format) 60 request.formatter.setPage(self.page) 61 preview_output = preview and formatText(message, request, request.formatter, inhibit_p=False, parser_cls=parser_cls) or "" 62 63 # Fill in the fields and labels. 64 65 d = { 66 "buttons_html" : buttons_html, 67 "format_label" : escape(_("Message format")), 68 "format" : escattr(format), 69 "recipient_label" : escape(_("Recipient")), 70 "recipients_list" : "\n".join(recipients_list), 71 "message_label" : escape(_("Message text")), 72 "message_default" : escape(message), 73 "preview_label" : escattr(_("Preview message")), 74 "preview_output" : preview_output, 75 "send_label" : escape(_("Send message immediately")), 76 "send_selected" : self._get_selected("send", action), 77 "queue_label" : escape(_("Queue message for sending")), 78 "queue_selected" : self._get_selected("queue", action), 79 } 80 81 # Prepare the output HTML. 82 83 html = ''' 84 <table> 85 <tr> 86 <td class="label"><label>%(recipient_label)s</label></td> 87 <td> 88 <select name="recipient"> 89 %(recipients_list)s 90 </select> 91 </td> 92 </tr> 93 <tr> 94 <td class="label"><label>%(format_label)s</label></td> 95 <td> 96 <input name="format" type="text" value="%(format)s" size="20" /> 97 </td> 98 </tr> 99 <tr> 100 <td class="label"><label>%(message_label)s</label></td> 101 <td> 102 <textarea name="message" cols="60" rows="10">%(message_default)s</textarea> 103 </td> 104 </tr> 105 <tr> 106 <td></td> 107 <td class="buttons"> 108 <input name="preview" type="submit" value="%(preview_label)s" /> 109 </td> 110 </tr> 111 <tr> 112 <td></td> 113 <td class="moinmessage-preview"> 114 %(preview_output)s 115 </td> 116 </tr> 117 <tr> 118 <td></td> 119 <td> 120 <select name="send-action"> 121 <option value="send" %(send_selected)s>%(send_label)s</option> 122 <option value="queue" %(queue_selected)s>%(queue_label)s</option> 123 </select> 124 </td> 125 </tr> 126 <tr> 127 <td></td> 128 <td class="buttons"> 129 %(buttons_html)s 130 </td> 131 </tr> 132 </table>''' % d 133 134 return html 135 136 def do_action(self): 137 138 "Attempt to send the message." 139 140 _ = self._ 141 request = self.request 142 form = self.get_form() 143 144 text = form.get("message", [None])[0] 145 recipient = form.get("recipient", [None])[0] 146 format = form.get("format", ["wiki"])[0] 147 action = form.get("send-action", ["send"])[0] 148 149 queue = action == "queue" 150 151 if not text: 152 return 0, _("A message must be given.") 153 154 if not recipient: 155 return 0, _("A recipient must be given.") 156 157 homedir = self.get_homedir() 158 if not homedir: 159 return 0, _("MoinMessage has not been set up: a GPG homedir is not defined.") 160 161 gpg = GPG(homedir) 162 163 # Construct a message from the request. 164 165 message = Message() 166 167 container = MIMEMultipart("related") 168 container["Update-Action"] = "store" 169 container["To"] = recipient 170 171 # Add the message body and any attachments. 172 173 parser_cls = getParserClass(request, format) 174 175 # Determine whether alternative output types are produced and, if so, 176 # bundle them in a multipart/alternative part. 177 178 output_types = getParserOutputTypes(parser_cls) 179 180 if len(output_types) > 1: 181 alternatives = MIMEMultipart("alternative") 182 container.attach(alternatives) 183 else: 184 alternatives = container 185 186 # Produce each of the representations. 187 188 for output_type in output_types: 189 190 # HTML must be processed to identify attachments. 191 192 if output_type == "text/html": 193 fmt = OutgoingHTMLFormatter(request) 194 fmt.setPage(request.page) 195 body = formatText(text, request, fmt, inhibit_p=False, parser_cls=parser_cls) 196 else: 197 body = formatTextForOutputType(text, request, parser_cls, output_type) 198 199 maintype, subtype = output_type.split("/", 1) 200 if maintype == "text": 201 part = MIMEText(body.encode("utf-8"), subtype, "utf-8") 202 else: 203 part = MIMEBase(maintype, subtype) 204 part.set_payload(body) 205 206 alternatives.attach(part) 207 208 # Produce any identified attachments. 209 210 for pos, (path, filename) in enumerate(fmt.attachments): 211 212 # Obtain the attachment content. 213 214 f = open(path, "rb") 215 try: 216 body = f.read() 217 finally: 218 f.close() 219 220 # Determine the attachment type. 221 222 mimetype = MimeType(filename=filename) 223 224 # NOTE: Support a limited set of explicit part types for now. 225 226 if mimetype.major == "image": 227 part = MIMEImage(body, mimetype.minor, **mimetype.params) 228 elif mimetype.major == "text": 229 part = MIMEText(body, mimetype.minor, mimetype.charset, **mimetype.params) 230 else: 231 part = MIMEApplication(body, mimetype.minor, **mimetype.params) 232 233 # Label the attachment and include it in the message. 234 235 part["Content-ID"] = "attachment%d" % pos 236 container.attach(part) 237 238 message.add_update(container) 239 240 # Get the sender details for signing messages. 241 # This is not the same as the details for authenticating users in the 242 # PostMessage action since the fingerprints refer to public keys. 243 244 signing_users = get_signing_users(request) 245 signer = signing_users and signing_users.get(request.user.name) 246 247 # Get the recipient details. 248 249 try: 250 parameters = get_recipient_details(request, recipient) 251 except MoinMessageRecipientError, exc: 252 return 0, exc.message 253 254 type = parameters["type"] 255 location = parameters["location"] 256 257 # Obtain the actual location if a relay is specified. 258 259 if type == "relay": 260 relays = get_relays(request) 261 if not relays: 262 return 0, _("No relays are defined for MoinMessage, but one is specified for the recipient.") 263 if not relays.has_key(location): 264 return 0, _("The relay specified for the recipient is not defined.") 265 266 location = relays[location] 267 268 # Sign, encrypt and send the message. 269 270 message = message.get_payload() 271 272 if not queue and type in ("url", "relay"): 273 try: 274 if signer: 275 message = gpg.signMessage(message, signer) 276 277 message = gpg.encryptMessage(message, parameters["fingerprint"]) 278 signer = get_signing_identity(request, type) 279 280 if signer: 281 timestamp(message) 282 message["Update-Action"] = "store" 283 message = gpg.signMessage(message, signer) 284 285 sendMessage(message, location) 286 287 except MoinMessageError, exc: 288 return 0, "%s: %s" % (_("The message could not be prepared and sent"), exc) 289 290 # Or queue the message on the specified page. 291 292 elif type == "page": 293 page = Page(request, location) 294 outbox = ItemStore(page, "messages", "message-locks") 295 outbox.append(as_string(message)) 296 297 # Or queue the message in a special outbox. 298 299 else: 300 outbox = ItemStore(request.page, "outgoing-messages", "outgoing-message-locks") 301 outbox.append(as_string(message)) 302 303 return 1, _("Message sent!") 304 305 def get_homedir(self): 306 307 "Locate the GPG home directory." 308 309 return getattr(self.request.cfg, "moinmessage_gpg_homedir") 310 311 # Action function. 312 313 def execute(pagename, request): 314 SendMessage(pagename, request).render() 315 316 # vim: tabstop=4 expandtab shiftwidth=4