Changeset 236 for trunk/email2trac.py.in
- Timestamp:
- 12/05/08 06:03:24 (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/email2trac.py.in
r234 r236 155 155 os.umask(int(parameters['umask'], 8)) 156 156 157 if parameters.has_key('quote_attachment_filenames'): 158 self.QUOTE_ATTACHMENT_FILENAMES = int(parameters['quote_attachment_filenames']) 159 else: 160 self.QUOTE_ATTACHMENT_FILENAMES = 1 161 157 162 if parameters.has_key('debug'): 158 163 self.DEBUG = int(parameters['debug']) … … 355 360 return str 356 361 357 def debug_attachments(self, message): 362 def debug_body(self, message_body, tempfile=False): 363 if tempfile: 364 import tempfile 365 body_file = tempfile.mktemp('.email2trac') 366 else: 367 body_file = os.path.join(self.TMPDIR, 'body.txt') 368 369 print 'TD: writing body (%s)' % body_file 370 fx = open(body_file, 'wb') 371 if not message_body: 372 message_body = '(None)' 373 fx.write(message_body) 374 fx.close() 375 try: 376 os.chmod(body_file,S_IRWXU|S_IRWXG|S_IRWXO) 377 except OSError: 378 pass 379 380 def debug_attachments(self, message_parts): 358 381 n = 0 359 for part in message .walk():360 if part.get_content_maintype() == 'multipart': # multipart/* is just a container361 print 'TD: multipart container'382 for part in message_parts: 383 # Skip inline text parts 384 if not isinstance(part, tuple): 362 385 continue 386 387 (filename, part) = part 363 388 364 389 n = n + 1 … … 366 391 print 'TD: part%d: filename: %s' % (n, part.get_filename()) 367 392 368 if part.is_multipart(): 369 print 'TD: this part is multipart' 370 payload = part.get_payload(decode=1) 371 print 'TD: payload:', payload 372 else: 373 print 'TD: this part is not multipart' 374 375 file = 'part%d' %n 376 part_file = os.path.join(self.TMPDIR, file) 393 part_file = os.path.join(self.TMPDIR, filename) 377 394 #part_file = '/var/tmp/part%d' % n 378 395 print 'TD: writing part%d (%s)' % (n,part_file) … … 643 660 self.update_ticket_fields(tkt, update_fields) 644 661 645 body_text = self.get_body_text(m) 662 message_parts = self.get_message_parts(m) 663 message_parts = self.unique_attachment_names(message_parts, tkt) 646 664 647 665 if self.EMAIL_HEADER: 648 head = self.email_header_txt(m) 649 body_text = u"%s\r\n%s" %(head, body_text) 666 message_parts.insert(0, self.email_header_txt(m)) 667 668 body_text = self.body_text(message_parts) 650 669 651 670 if body_text.strip(): … … 655 674 656 675 if self.VERSION == 0.9: 657 str = self.attachments(m , tkt, True)658 else: 659 str = self.attachments(m , tkt)676 str = self.attachments(message_parts, tkt, True) 677 else: 678 str = self.attachments(message_parts, tkt) 660 679 661 680 if self.notification and not spam: … … 755 774 head = self.email_header_txt(msg) 756 775 757 body_text = self.get_body_text(msg) 758 759 tkt['description'] = '%s\r\n%s' \ 760 %(head, body_text) 776 message_parts = self.get_message_parts(msg) 777 message_parts = self.unique_attachment_names(message_parts) 778 779 if self.EMAIL_HEADER > 0: 780 message_parts.insert(0, self.email_header_txt(msg)) 781 782 body_text = self.body_text(message_parts) 783 784 tkt['description'] = body_text 761 785 762 786 #when = int(time.time()) … … 785 809 %(head, mailto, body_text) 786 810 787 str = self.attachments(m sg, tkt)811 str = self.attachments(message_parts, tkt) 788 812 if str: 789 813 changed = True … … 815 839 816 840 if self.DEBUG > 1: # save the entire e-mail message text 841 message_parts = self.get_message_parts(m) 842 message_parts = self.unique_attachment_names(message_parts) 817 843 self.save_email_for_debug(m, True) 818 self.debug_attachments(m) 844 body_text = self.body_text(message_parts) 845 self.debug_body(body_text, True) 846 self.debug_attachments(message_parts) 819 847 820 848 self.db = self.env.get_db_cnx() … … 946 974 947 975 948 def get_body_text(self, msg): 949 """ 950 put the message text in the ticket description or in the changes field. 951 message text can be plain text or html or something else 952 """ 953 has_description = 0 954 encoding = True 955 ubody_text = u'No plain text message' 976 def get_message_parts(self, msg): 977 """ 978 parses the email message and returns a list of body parts and attachments 979 body parts are returned as strings, attachments are returned as tuples of (filename, Message object) 980 """ 981 message_parts = [] 982 956 983 for part in msg.walk(): 957 958 984 # 'multipart/*' is a container for multipart messages 959 985 # 986 if self.DEBUG: 987 print 'TD: Message part: Content-Type: %s' % part.get_content_type() 988 960 989 if part.get_content_maintype() == 'multipart': 961 990 continue 962 991 963 if part.get_content_type() == 'text/plain': 992 # Check if this is an inline part. It's inline if there is co Cont-Disp header, or if there is one and it says "inline" 993 inline = self.inline_part(part) 994 995 # Inline text parts are where the body is 996 if part.get_content_type() == 'text/plain' and inline: 997 if self.DEBUG: 998 print 'TD: Inline body part' 999 964 1000 # Try to decode, if fails then do not decode 965 1001 # … … 998 1034 ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset) 999 1035 1000 elif part.get_content_type() == 'text/html': 1001 ubody_text = '(see attachment for HTML mail message)' 1002 1036 if self.VERBATIM_FORMAT: 1037 message_parts.append('{{{\r\n%s\r\n}}}' %ubody_text) 1038 else: 1039 message_parts.append('%s' %ubody_text) 1003 1040 else: 1004 ubody_text = '(see attachment for message)' 1005 1006 has_description = 1 1007 break # we have the description, so break 1008 1009 if not has_description: 1010 ubody_text = '(see attachment for message)' 1011 1012 # A patch so that the web-interface will not update the description 1013 # field of a ticket 1014 # 1015 ubody_text = ('\r\n'.join(ubody_text.splitlines())) 1016 1017 # If we can unicode it try to encode it for trac 1018 # else we a lot of garbage 1019 # 1020 #if encoding: 1021 # ubody_text = ubody_text.encode('utf-8') 1022 1023 if self.VERBATIM_FORMAT: 1024 ubody_text = '{{{\r\n%s\r\n}}}' %ubody_text 1025 else: 1026 ubody_text = '%s' %ubody_text 1027 1028 return ubody_text 1041 if self.DEBUG: 1042 print 'TD: Filename: %s' % part.get_filename() 1043 1044 message_parts.append((part.get_filename(), part)) 1045 1046 return message_parts 1047 1048 def unique_attachment_names(self, message_parts, tkt = None): 1049 renamed_parts = [] 1050 attachment_names = set() 1051 for part in message_parts: 1052 1053 # If not an attachment, leave it alone 1054 if not isinstance(part, tuple): 1055 renamed_parts.append(part) 1056 continue 1057 1058 (filename, part) = part 1059 # Decode the filename 1060 if filename: 1061 filename = self.email_to_unicode(filename) 1062 # If no name, use a default one 1063 else: 1064 filename = 'untitled-part' 1065 1066 # Guess the extension from the content type 1067 ext = mimetypes.guess_extension(part.get_content_type()) 1068 if not ext: 1069 ext = '.bin' 1070 1071 filename = '%s%s' % (filename, ext) 1072 1073 # Discard relative paths in attachment names 1074 filename = filename.replace('\\', '/').replace(':', '/') 1075 filename = os.path.basename(filename) 1076 1077 # We try to normalize the filename to utf-8 NFC if we can. 1078 # Files uploaded from OS X might be in NFD. 1079 # Check python version and then try it 1080 # 1081 if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3): 1082 try: 1083 filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 1084 except TypeError: 1085 pass 1086 1087 if self.QUOTE_ATTACHMENT_FILENAMES: 1088 filename = urllib.quote(filename) 1089 1090 # Make the filename unique for this ticket 1091 num = 0 1092 unique_filename = filename 1093 filename, ext = os.path.splitext(filename) 1094 1095 while unique_filename in attachment_names or self.attachment_exists(tkt, unique_filename): 1096 num += 1 1097 unique_filename = "%s-%s%s" % (filename, num, ext) 1098 1099 if self.DEBUG: 1100 print 'TD: Attachment with filename %s will be saved as %s' % (filename, unique_filename) 1101 1102 attachment_names.add(unique_filename) 1103 1104 renamed_parts.append((filename, unique_filename, part)) 1105 1106 return renamed_parts 1107 1108 def inline_part(self, part): 1109 return part.get_param('inline', None, 'Content-Disposition') == '' or not part.has_key('Content-Disposition') 1110 1111 1112 def attachment_exists(self, tkt, filename): 1113 if tkt is None: 1114 return False 1115 1116 try: 1117 Attachment(self.env, 'ticket', tkt['id'], filename) 1118 return True 1119 except ResourceNotFound: 1120 return False 1121 1122 def body_text(self, message_parts): 1123 body_text = [] 1124 1125 for part in message_parts: 1126 # Plain text part, append it 1127 if not isinstance(part, tuple): 1128 body_text.extend(part.strip().splitlines()) 1129 body_text.append("") 1130 continue 1131 1132 (original, filename, part) = part 1133 inline = self.inline_part(part) 1134 1135 if part.get_content_maintype() == 'image' and inline: 1136 body_text.append('[[Image(%s)]]' % filename) 1137 body_text.append("") 1138 else: 1139 body_text.append('[attachment:"%s"]' % filename) 1140 body_text.append("") 1141 1142 body_text = '\r\n'.join(body_text) 1143 return body_text 1029 1144 1030 1145 def notify(self, tkt , new=True, modtime=0): … … 1087 1202 return str 1088 1203 1089 def attachments(self, message , ticket, update=False):1204 def attachments(self, message_parts, ticket, update=False): 1090 1205 ''' 1091 1206 save any attachments as files in the ticket's directory … … 1099 1214 max_size = int(self.get_config('attachment', 'max_size')) 1100 1215 status = '' 1101 1102 for part in message.walk(): 1103 if part.get_content_maintype() == 'multipart': # multipart/* is just a container 1216 1217 for part in message_parts: 1218 # Skip body parts 1219 if not isinstance(part, tuple): 1104 1220 continue 1105 1106 if not first: # first content is the message 1107 first = 1 1108 if part.get_content_type() == 'text/plain': # if first is text, is was already put in the description 1109 continue 1110 1111 filename = part.get_filename() 1112 if not filename: 1113 number = number + 1 1114 filename = 'part%04d' % number 1115 1116 ext = mimetypes.guess_extension(part.get_content_type()) 1117 if not ext: 1118 ext = '.bin' 1119 1120 filename = '%s%s' % (filename, ext) 1121 else: 1122 filename = self.email_to_unicode(filename) 1123 1124 # From the trac code 1125 # 1126 filename = filename.replace('\\', '/').replace(':', '/') 1127 filename = os.path.basename(filename) 1128 1129 # We try to normalize the filename to utf-8 NFC if we can. 1130 # Files uploaded from OS X might be in NFD. 1131 # Check python version and then try it 1132 # 1133 if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3): 1134 try: 1135 filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 1136 except TypeError: 1137 pass 1138 1139 url_filename = urllib.quote(filename) 1221 1222 (original, filename, part) = part 1140 1223 # 1141 1224 # Must be tuneables HvB 1142 1225 # 1143 path, fd = util.create_unique_file(os.path.join(self.TMPDIR, url_filename))1226 path, fd = util.create_unique_file(os.path.join(self.TMPDIR, filename)) 1144 1227 text = part.get_payload(decode=1) 1145 1228 if not text: … … 1157 1240 if (max_size != -1) and (file_size > max_size): 1158 1241 status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \ 1159 %(status, filename, file_size, max_size)1242 %(status, original, file_size, max_size) 1160 1243 1161 1244 os.unlink(path) … … 1176 1259 att.description = self.email_to_unicode('Added by email2trac') 1177 1260 1178 att.insert( url_filename, fd, file_size)1261 att.insert(filename, fd, file_size) 1179 1262 #except util.TracError, detail: 1180 1263 # print detail
Note: See TracChangeset
for help on using the changeset viewer.