26 |
26 |
27 |
27 |
28 from email import MIMEBase |
28 from email import MIMEBase |
29 from email import MIMEMultipart |
29 from email import MIMEMultipart |
30 from email import MIMEText |
30 from email import MIMEText |
31 import mimetypes |
|
32 import types |
31 import types |
33 |
32 |
34 from google.appengine.api import api_base_pb |
33 from google.appengine.api import api_base_pb |
35 from google.appengine.api import apiproxy_stub_map |
34 from google.appengine.api import apiproxy_stub_map |
36 from google.appengine.api import mail_service_pb |
35 from google.appengine.api import mail_service_pb |
49 mail_service_pb.MailServiceError.INVALID_ATTACHMENT_TYPE: |
48 mail_service_pb.MailServiceError.INVALID_ATTACHMENT_TYPE: |
50 InvalidAttachmentTypeError, |
49 InvalidAttachmentTypeError, |
51 } |
50 } |
52 |
51 |
53 |
52 |
54 EXTENSION_WHITELIST = set([ |
53 EXTENSION_MIME_MAP = { |
55 'bmp', |
54 'asc': 'text/plain', |
56 'css', |
55 'bmp': 'image/x-ms-bmp', |
57 'csv', |
56 'css': 'text/css', |
58 'gif', |
57 'csv': 'text/csv', |
59 'html', 'htm', |
58 'diff': 'text/plain', |
60 'jpeg', 'jpg', 'jpe', |
59 'gif': 'image/gif', |
61 'pdf', |
60 'htm': 'text/html', |
62 'png', |
61 'html': 'text/html', |
63 'rss', |
62 'ics': 'text/calendar', |
64 'text', 'txt', 'asc', 'diff', 'pot', |
63 'jpe': 'image/jpeg', |
65 'tiff', 'tif', |
64 'jpeg': 'image/jpeg', |
66 'wbmp', |
65 'jpg': 'image/jpeg', |
67 ]) |
66 'pdf': 'application/pdf', |
|
67 'png': 'image/png', |
|
68 'pot': 'text/plain', |
|
69 'rss': 'text/rss+xml', |
|
70 'text': 'text/plain', |
|
71 'tif': 'image/tiff', |
|
72 'tiff': 'image/tiff', |
|
73 'txt': 'text/plain', |
|
74 'vcf': 'text/directory', |
|
75 'wbmp': 'image/vnd.wap.wbmp', |
|
76 } |
|
77 |
|
78 EXTENSION_WHITELIST = frozenset(EXTENSION_MIME_MAP.iterkeys()) |
68 |
79 |
69 |
80 |
70 def invalid_email_reason(email_address, field): |
81 def invalid_email_reason(email_address, field): |
71 """Determine reason why email is invalid |
82 """Determine reason why email is invalid |
72 |
83 |
232 message.send(make_sync_call) |
243 message.send(make_sync_call) |
233 |
244 |
234 SendMailToAdmins = send_mail_to_admins |
245 SendMailToAdmins = send_mail_to_admins |
235 |
246 |
236 |
247 |
|
248 def _GetMimeType(file_name): |
|
249 """Determine mime-type from file name. |
|
250 |
|
251 Parses file name and determines mime-type based on extension map. |
|
252 |
|
253 This method is not part of the public API and should not be used by |
|
254 applications. |
|
255 |
|
256 Args: |
|
257 file_name: File to determine extension for. |
|
258 |
|
259 Returns: |
|
260 Mime-type associated with file extension. |
|
261 |
|
262 Raises: |
|
263 InvalidAttachmentTypeError when the file name of an attachment. |
|
264 """ |
|
265 extension_index = file_name.rfind('.') |
|
266 if extension_index == -1: |
|
267 raise InvalidAttachmentTypeError( |
|
268 "File '%s' does not have an extension" % file_name) |
|
269 extension = file_name[extension_index + 1:] |
|
270 mime_type = EXTENSION_MIME_MAP.get(extension, None) |
|
271 if mime_type is None: |
|
272 raise InvalidAttachmentTypeError( |
|
273 "Extension '%s' is not supported." % extension) |
|
274 return mime_type |
|
275 |
|
276 |
237 def mail_message_to_mime_message(protocol_message): |
277 def mail_message_to_mime_message(protocol_message): |
238 """Generate a MIMEMultitype message from protocol buffer. |
278 """Generate a MIMEMultitype message from protocol buffer. |
239 |
279 |
240 Generates a complete MIME multi-part email object from a MailMessage |
280 Generates a complete MIME multi-part email object from a MailMessage |
241 protocol buffer. The body fields are sent as individual alternatives |
281 protocol buffer. The body fields are sent as individual alternatives |
247 Args: |
287 Args: |
248 message: Message PB to convert to MIMEMultitype. |
288 message: Message PB to convert to MIMEMultitype. |
249 |
289 |
250 Returns: |
290 Returns: |
251 MIMEMultitype representing the provided MailMessage. |
291 MIMEMultitype representing the provided MailMessage. |
|
292 |
|
293 Raises: |
|
294 InvalidAttachmentTypeError when the file name of an attachment |
252 """ |
295 """ |
253 parts = [] |
296 parts = [] |
254 if protocol_message.has_textbody(): |
297 if protocol_message.has_textbody(): |
255 parts.append(MIMEText.MIMEText(protocol_message.textbody())) |
298 parts.append(MIMEText.MIMEText(protocol_message.textbody())) |
256 if protocol_message.has_htmlbody(): |
299 if protocol_message.has_htmlbody(): |
262 else: |
305 else: |
263 payload = [MIMEMultipart.MIMEMultipart('alternative', _subparts=parts)] |
306 payload = [MIMEMultipart.MIMEMultipart('alternative', _subparts=parts)] |
264 |
307 |
265 result = MIMEMultipart.MIMEMultipart(_subparts=payload) |
308 result = MIMEMultipart.MIMEMultipart(_subparts=payload) |
266 for attachment in protocol_message.attachment_list(): |
309 for attachment in protocol_message.attachment_list(): |
267 mime_type, encoding = mimetypes.guess_type(attachment.filename()) |
310 file_name = attachment.filename() |
268 assert mime_type is not None |
311 mime_type = _GetMimeType(file_name) |
269 maintype, subtype = mime_type.split('/') |
312 maintype, subtype = mime_type.split('/') |
270 mime_attachment = MIMEBase.MIMEBase(maintype, subtype) |
313 mime_attachment = MIMEBase.MIMEBase(maintype, subtype) |
271 mime_attachment.add_header('Content-Disposition', |
314 mime_attachment.add_header('Content-Disposition', |
272 'attachment', |
315 'attachment', |
273 filename=attachment.filename()) |
316 filename=attachment.filename()) |
274 mime_attachment.set_charset(encoding) |
|
275 mime_attachment.set_payload(attachment.data()) |
317 mime_attachment.set_payload(attachment.data()) |
276 result.attach(mime_attachment) |
318 result.attach(mime_attachment) |
277 |
319 |
278 if protocol_message.to_size(): |
320 if protocol_message.to_size(): |
279 result['To'] = ', '.join(protocol_message.to_list()) |
321 result['To'] = ', '.join(protocol_message.to_list()) |
281 result['Cc'] = ', '.join(protocol_message.cc_list()) |
323 result['Cc'] = ', '.join(protocol_message.cc_list()) |
282 if protocol_message.bcc_size(): |
324 if protocol_message.bcc_size(): |
283 result['Bcc'] = ', '.join(protocol_message.bcc_list()) |
325 result['Bcc'] = ', '.join(protocol_message.bcc_list()) |
284 |
326 |
285 result['From'] = protocol_message.sender() |
327 result['From'] = protocol_message.sender() |
286 result['ReplyTo'] = protocol_message.replyto() |
328 result['Reply-To'] = protocol_message.replyto() |
287 result['Subject'] = protocol_message.subject() |
329 result['Subject'] = protocol_message.subject() |
288 |
330 |
289 return result |
331 return result |
290 |
332 |
291 MailMessageToMIMEMessage = mail_message_to_mime_message |
333 MailMessageToMIMEMessage = mail_message_to_mime_message |
374 if not hasattr(self, 'subject'): |
416 if not hasattr(self, 'subject'): |
375 raise MissingSubjectError() |
417 raise MissingSubjectError() |
376 if not hasattr(self, 'body') and not hasattr(self, 'html'): |
418 if not hasattr(self, 'body') and not hasattr(self, 'html'): |
377 raise MissingBodyError() |
419 raise MissingBodyError() |
378 if hasattr(self, 'attachments'): |
420 if hasattr(self, 'attachments'): |
379 for filename, data in _attachment_sequence(self.attachments): |
421 for file_name, data in _attachment_sequence(self.attachments): |
380 split_filename = filename.split('.') |
422 _GetMimeType(file_name) |
381 if len(split_filename) < 2: |
|
382 raise InvalidAttachmentTypeError() |
|
383 if split_filename[-1] not in EXTENSION_WHITELIST: |
|
384 raise InvalidAttachmentTypeError() |
|
385 mime_type, encoding = mimetypes.guess_type(filename) |
|
386 if mime_type is None: |
|
387 raise InvalidAttachmentTypeError() |
|
388 |
423 |
389 def CheckInitialized(self): |
424 def CheckInitialized(self): |
390 self.check_initialized() |
425 self.check_initialized() |
391 |
426 |
392 def is_initialized(self): |
427 def is_initialized(self): |