Mercurial > p > roundup > code
comparison roundup/mailgw.py @ 2631:2bbcfc80ba5b
MailGW.handle_message():
as config is used many times in this method, have a local variable
instead of going through self.instance.config each time;
change config attribute access to container (item) access;
where possible, avoid duplicate computing of config settings;
fix MAILGW_KEEP_QUOTED_TEXT and MAILGW_LEAVE_BODY_UNCHANGED -
config values are boolean now;
trim trailing spaces, fix vim modeline
| author | Alexander Smishlajev <a1s@users.sourceforge.net> |
|---|---|
| date | Mon, 26 Jul 2004 09:29:22 +0000 |
| parents | 58848e3b6bb8 |
| children | 1df7d4a41da4 |
comparison
equal
deleted
inserted
replaced
| 2630:a65bae7af6d1 | 2631:2bbcfc80ba5b |
|---|---|
| 23 Incoming messages are examined for multiple parts: | 23 Incoming messages are examined for multiple parts: |
| 24 . In a multipart/mixed message or part, each subpart is extracted and | 24 . In a multipart/mixed message or part, each subpart is extracted and |
| 25 examined. The text/plain subparts are assembled to form the textual | 25 examined. The text/plain subparts are assembled to form the textual |
| 26 body of the message, to be stored in the file associated with a "msg" | 26 body of the message, to be stored in the file associated with a "msg" |
| 27 class node. Any parts of other types are each stored in separate files | 27 class node. Any parts of other types are each stored in separate files |
| 28 and given "file" class nodes that are linked to the "msg" node. | 28 and given "file" class nodes that are linked to the "msg" node. |
| 29 . In a multipart/alternative message or part, we look for a text/plain | 29 . In a multipart/alternative message or part, we look for a text/plain |
| 30 subpart and ignore the other parts. | 30 subpart and ignore the other parts. |
| 31 | 31 |
| 32 Summary | 32 Summary |
| 33 ------- | 33 ------- |
| 34 The "summary" property on message nodes is taken from the first non-quoting | 34 The "summary" property on message nodes is taken from the first non-quoting |
| 35 section in the message body. The message body is divided into sections by | 35 section in the message body. The message body is divided into sections by |
| 36 blank lines. Sections where the second and all subsequent lines begin with | 36 blank lines. Sections where the second and all subsequent lines begin with |
| 37 a ">" or "|" character are considered "quoting sections". The first line of | 37 a ">" or "|" character are considered "quoting sections". The first line of |
| 38 the first non-quoting section becomes the summary of the message. | 38 the first non-quoting section becomes the summary of the message. |
| 39 | 39 |
| 40 Addresses | 40 Addresses |
| 41 --------- | 41 --------- |
| 42 All of the addresses in the To: and Cc: headers of the incoming message are | 42 All of the addresses in the To: and Cc: headers of the incoming message are |
| 43 looked up among the user nodes, and the corresponding users are placed in | 43 looked up among the user nodes, and the corresponding users are placed in |
| 46 node. The default handling for addresses that don't have corresponding | 46 node. The default handling for addresses that don't have corresponding |
| 47 users is to create new users with no passwords and a username equal to the | 47 users is to create new users with no passwords and a username equal to the |
| 48 address. (The web interface does not permit logins for users with no | 48 address. (The web interface does not permit logins for users with no |
| 49 passwords.) If we prefer to reject mail from outside sources, we can simply | 49 passwords.) If we prefer to reject mail from outside sources, we can simply |
| 50 register an auditor on the "user" class that prevents the creation of user | 50 register an auditor on the "user" class that prevents the creation of user |
| 51 nodes with no passwords. | 51 nodes with no passwords. |
| 52 | 52 |
| 53 Actions | 53 Actions |
| 54 ------- | 54 ------- |
| 55 The subject line of the incoming message is examined to determine whether | 55 The subject line of the incoming message is examined to determine whether |
| 56 the message is an attempt to create a new item or to discuss an existing | 56 the message is an attempt to create a new item or to discuss an existing |
| 57 item. A designator enclosed in square brackets is sought as the first thing | 57 item. A designator enclosed in square brackets is sought as the first thing |
| 58 on the subject line (after skipping any "Fwd:" or "Re:" prefixes). | 58 on the subject line (after skipping any "Fwd:" or "Re:" prefixes). |
| 59 | 59 |
| 60 If an item designator (class name and id number) is found there, the newly | 60 If an item designator (class name and id number) is found there, the newly |
| 61 created "msg" node is added to the "messages" property for that item, and | 61 created "msg" node is added to the "messages" property for that item, and |
| 62 any new "file" nodes are added to the "files" property for the item. | 62 any new "file" nodes are added to the "files" property for the item. |
| 63 | 63 |
| 64 If just an item class name is found there, we attempt to create a new item | 64 If just an item class name is found there, we attempt to create a new item |
| 65 of that class with its "messages" property initialized to contain the new | 65 of that class with its "messages" property initialized to contain the new |
| 66 "msg" node and its "files" property initialized to contain any new "file" | 66 "msg" node and its "files" property initialized to contain any new "file" |
| 67 nodes. | 67 nodes. |
| 68 | 68 |
| 69 Triggers | 69 Triggers |
| 70 -------- | 70 -------- |
| 71 Both cases may trigger detectors (in the first case we are calling the | 71 Both cases may trigger detectors (in the first case we are calling the |
| 72 set() method to add the message to the item's spool; in the second case we | 72 set() method to add the message to the item's spool; in the second case we |
| 73 are calling the create() method to create a new node). If an auditor raises | 73 are calling the create() method to create a new node). If an auditor raises |
| 74 an exception, the original message is bounced back to the sender with the | 74 an exception, the original message is bounced back to the sender with the |
| 75 explanatory message given in the exception. | 75 explanatory message given in the exception. |
| 76 | 76 |
| 77 $Id: mailgw.py,v 1.151 2004-07-14 01:12:25 richard Exp $ | 77 $Id: mailgw.py,v 1.152 2004-07-26 09:29:22 a1s Exp $ |
| 78 """ | 78 """ |
| 79 __docformat__ = 'restructuredtext' | 79 __docformat__ = 'restructuredtext' |
| 80 | 80 |
| 81 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri | 81 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri |
| 82 import time, random, sys | 82 import time, random, sys |
| 209 self.rewindbody() | 209 self.rewindbody() |
| 210 encoding = self.getencoding() | 210 encoding = self.getencoding() |
| 211 data = None | 211 data = None |
| 212 if encoding == 'base64': | 212 if encoding == 'base64': |
| 213 # BUG: is base64 really used for text encoding or | 213 # BUG: is base64 really used for text encoding or |
| 214 # are we inserting zip files here. | 214 # are we inserting zip files here. |
| 215 data = binascii.a2b_base64(self.fp.read()) | 215 data = binascii.a2b_base64(self.fp.read()) |
| 216 elif encoding == 'quoted-printable': | 216 elif encoding == 'quoted-printable': |
| 217 # the quopri module wants to work with files | 217 # the quopri module wants to work with files |
| 218 decoded = cStringIO.StringIO() | 218 decoded = cStringIO.StringIO() |
| 219 quopri.decode(self.fp, decoded) | 219 quopri.decode(self.fp, decoded) |
| 221 elif encoding == 'uuencoded': | 221 elif encoding == 'uuencoded': |
| 222 data = binascii.a2b_uu(self.fp.read()) | 222 data = binascii.a2b_uu(self.fp.read()) |
| 223 else: | 223 else: |
| 224 # take it as text | 224 # take it as text |
| 225 data = self.fp.read() | 225 data = self.fp.read() |
| 226 | 226 |
| 227 # Encode message to unicode | 227 # Encode message to unicode |
| 228 charset = rfc2822.unaliasCharset(self.getparam("charset")) | 228 charset = rfc2822.unaliasCharset(self.getparam("charset")) |
| 229 if charset: | 229 if charset: |
| 230 # Do conversion only if charset specified | 230 # Do conversion only if charset specified |
| 231 edata = unicode(data, charset).encode('utf-8') | 231 edata = unicode(data, charset).encode('utf-8') |
| 232 # Convert from dos eol to unix | 232 # Convert from dos eol to unix |
| 233 edata = edata.replace('\r\n', '\n') | 233 edata = edata.replace('\r\n', '\n') |
| 234 else: | 234 else: |
| 235 # Leave message content as is | 235 # Leave message content as is |
| 236 edata = data | 236 edata = data |
| 237 | 237 |
| 238 return edata | 238 return edata |
| 239 | 239 |
| 240 # General multipart handling: | 240 # General multipart handling: |
| 241 # Take the first text/plain part, anything else is considered an | 241 # Take the first text/plain part, anything else is considered an |
| 242 # attachment. | 242 # attachment. |
| 243 # multipart/mixed: | 243 # multipart/mixed: |
| 244 # Multiple "unrelated" parts. | 244 # Multiple "unrelated" parts. |
| 245 # multipart/Alternative (rfc 1521): | 245 # multipart/Alternative (rfc 1521): |
| 246 # Like multipart/mixed, except that we'd only want one of the | 246 # Like multipart/mixed, except that we'd only want one of the |
| 247 # alternatives. Generally a top-level part from MUAs sending HTML | 247 # alternatives. Generally a top-level part from MUAs sending HTML |
| 248 # mail - there will be a text/plain version. | 248 # mail - there will be a text/plain version. |
| 249 # multipart/signed (rfc 1847): | 249 # multipart/signed (rfc 1847): |
| 250 # The control information is carried in the second of the two | 250 # The control information is carried in the second of the two |
| 251 # required body parts. | 251 # required body parts. |
| 252 # ACTION: Default, so if content is text/plain we get it. | 252 # ACTION: Default, so if content is text/plain we get it. |
| 253 # multipart/encrypted (rfc 1847): | 253 # multipart/encrypted (rfc 1847): |
| 254 # The control information is carried in the first of the two | 254 # The control information is carried in the first of the two |
| 255 # required body parts. | 255 # required body parts. |
| 256 # ACTION: Not handleable as the content is encrypted. | 256 # ACTION: Not handleable as the content is encrypted. |
| 257 # multipart/related (rfc 1872, 2112, 2387): | 257 # multipart/related (rfc 1872, 2112, 2387): |
| 258 # The Multipart/Related content-type addresses the MIME | 258 # The Multipart/Related content-type addresses the MIME |
| 259 # representation of compound objects, usually HTML mail with embedded | 259 # representation of compound objects, usually HTML mail with embedded |
| 260 # images. Usually appears as an alternative. | 260 # images. Usually appears as an alternative. |
| 261 # ACTION: Default, if we must. | 261 # ACTION: Default, if we must. |
| 262 # multipart/report (rfc 1892): | 262 # multipart/report (rfc 1892): |
| 263 # e.g. mail system delivery status reports. | 263 # e.g. mail system delivery status reports. |
| 264 # ACTION: Default. Could be ignored or used for Delivery Notification | 264 # ACTION: Default. Could be ignored or used for Delivery Notification |
| 265 # flagging. | 265 # flagging. |
| 266 # multipart/form-data: | 266 # multipart/form-data: |
| 267 # For web forms only. | 267 # For web forms only. |
| 268 | 268 |
| 269 def extract_content(self, parent_type=None): | 269 def extract_content(self, parent_type=None): |
| 270 """Extract the body and the attachments recursively.""" | 270 """Extract the body and the attachments recursively.""" |
| 271 content_type = self.gettype() | 271 content_type = self.gettype() |
| 272 content = None | 272 content = None |
| 273 attachments = [] | 273 attachments = [] |
| 274 | 274 |
| 275 if content_type == 'text/plain': | 275 if content_type == 'text/plain': |
| 276 content = self.getbody() | 276 content = self.getbody() |
| 277 elif content_type[:10] == 'multipart/': | 277 elif content_type[:10] == 'multipart/': |
| 278 for part in self.getparts(): | 278 for part in self.getparts(): |
| 279 new_content, new_attach = part.extract_content(content_type) | 279 new_content, new_attach = part.extract_content(content_type) |
| 282 # otherwise make it an attachment. | 282 # otherwise make it an attachment. |
| 283 if not content: | 283 if not content: |
| 284 content = new_content | 284 content = new_content |
| 285 elif new_content: | 285 elif new_content: |
| 286 attachments.append(part.as_attachment()) | 286 attachments.append(part.as_attachment()) |
| 287 | 287 |
| 288 attachments.extend(new_attach) | 288 attachments.extend(new_attach) |
| 289 elif (parent_type == 'multipart/signed' and | 289 elif (parent_type == 'multipart/signed' and |
| 290 content_type == 'application/pgp-signature'): | 290 content_type == 'application/pgp-signature'): |
| 291 # ignore it so it won't be saved as an attachment | 291 # ignore it so it won't be saved as an attachment |
| 292 pass | 292 pass |
| 306 (?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*\s* # Re: | 306 (?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*\s* # Re: |
| 307 (?P<quote>")? # Leading " | 307 (?P<quote>")? # Leading " |
| 308 (\[(?P<classname>[^\d\s]+) # [issue.. | 308 (\[(?P<classname>[^\d\s]+) # [issue.. |
| 309 (?P<nodeid>\d+)? # ..1234] | 309 (?P<nodeid>\d+)? # ..1234] |
| 310 \])?\s* | 310 \])?\s* |
| 311 (?P<title>[^[]+)? # issue title | 311 (?P<title>[^[]+)? # issue title |
| 312 "? # Trailing " | 312 "? # Trailing " |
| 313 (\[(?P<args>.+?)\])? # [prop=value] | 313 (\[(?P<args>.+?)\])? # [prop=value] |
| 314 ''', re.IGNORECASE|re.VERBOSE) | 314 ''', re.IGNORECASE|re.VERBOSE) |
| 315 | 315 |
| 316 def __init__(self, instance, db, arguments={}): | 316 def __init__(self, instance, db, arguments={}): |
| 468 else: | 468 else: |
| 469 server.user(user) | 469 server.user(user) |
| 470 server.pass_(password) | 470 server.pass_(password) |
| 471 numMessages = len(server.list()[1]) | 471 numMessages = len(server.list()[1]) |
| 472 for i in range(1, numMessages+1): | 472 for i in range(1, numMessages+1): |
| 473 # retr: returns | 473 # retr: returns |
| 474 # [ pop response e.g. '+OK 459 octets', | 474 # [ pop response e.g. '+OK 459 octets', |
| 475 # [ array of message lines ], | 475 # [ array of message lines ], |
| 476 # number of octets ] | 476 # number of octets ] |
| 477 lines = server.retr(i)[1] | 477 lines = server.retr(i)[1] |
| 478 s = cStringIO.StringIO('\n'.join(lines)) | 478 s = cStringIO.StringIO('\n'.join(lines)) |
| 580 | 580 |
| 581 # detect Precedence: Bulk | 581 # detect Precedence: Bulk |
| 582 if (message.getheader('precedence', '') == 'bulk'): | 582 if (message.getheader('precedence', '') == 'bulk'): |
| 583 raise IgnoreBulk | 583 raise IgnoreBulk |
| 584 | 584 |
| 585 # config is used many times in this method. | |
| 586 # make local variable for easier access | |
| 587 config = self.instance.config | |
| 588 | |
| 585 # XXX Don't enable. This doesn't work yet. | 589 # XXX Don't enable. This doesn't work yet. |
| 586 # "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]" | 590 # "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]" |
| 587 # handle delivery to addresses like:tracker+issue25@some.dom.ain | 591 # handle delivery to addresses like:tracker+issue25@some.dom.ain |
| 588 # use the embedded issue number as our issue | 592 # use the embedded issue number as our issue |
| 589 # if hasattr(self.instance.config, 'EMAIL_ISSUE_ADDRESS_RE') and \ | 593 # issue_re = config['MAILGW_ISSUE_ADDRESS_RE'] |
| 590 # self.instance.config.EMAIL_ISSUE_ADDRESS_RE: | 594 # if issue_re: |
| 591 # issue_re = self.instance.config.EMAIL_ISSUE_ADDRESS_RE | |
| 592 # for header in ['to', 'cc', 'bcc']: | 595 # for header in ['to', 'cc', 'bcc']: |
| 593 # addresses = message.getheader(header, '') | 596 # addresses = message.getheader(header, '') |
| 594 # if addresses: | 597 # if addresses: |
| 595 # # FIXME, this only finds the first match in the addresses. | 598 # # FIXME, this only finds the first match in the addresses. |
| 596 # issue = re.search(issue_re, addresses, 'i') | 599 # issue = re.search(issue_re, addresses, 'i') |
| 627 otk_re = re.compile('-- key (?P<otk>[a-zA-Z0-9]{32})') | 630 otk_re = re.compile('-- key (?P<otk>[a-zA-Z0-9]{32})') |
| 628 otk = otk_re.search(m.group('title')) | 631 otk = otk_re.search(m.group('title')) |
| 629 if otk: | 632 if otk: |
| 630 self.db.confirm_registration(otk.group('otk')) | 633 self.db.confirm_registration(otk.group('otk')) |
| 631 subject = 'Your registration to %s is complete' % \ | 634 subject = 'Your registration to %s is complete' % \ |
| 632 self.instance.config.TRACKER_NAME | 635 config['TRACKER_NAME'] |
| 633 sendto = [from_list[0][1]] | 636 sendto = [from_list[0][1]] |
| 634 self.mailer.standard_message(sendto, subject, '') | 637 self.mailer.standard_message(sendto, subject, '') |
| 635 return | 638 return |
| 636 elif hasattr(self.instance.config, 'MAIL_DEFAULT_CLASS') and \ | |
| 637 self.instance.config.MAIL_DEFAULT_CLASS: | |
| 638 classname = self.instance.config.MAIL_DEFAULT_CLASS | |
| 639 else: | 639 else: |
| 640 # fail | 640 classname = config['MAILGW_DEFAULT_CLASS'] |
| 641 m = None | 641 if not classname: |
| 642 # fail | |
| 643 m = None | |
| 642 | 644 |
| 643 if not m: | 645 if not m: |
| 644 raise MailUsageError, """ | 646 raise MailUsageError, """ |
| 645 The message you sent to roundup did not contain a properly formed subject | 647 The message you sent to roundup did not contain a properly formed subject |
| 646 line. The subject must contain a class name or designator to indicate the | 648 line. The subject must contain a class name or designator to indicate the |
| 722 if self.arguments: | 724 if self.arguments: |
| 723 current_class = 'msg' | 725 current_class = 'msg' |
| 724 for option, propstring in self.arguments: | 726 for option, propstring in self.arguments: |
| 725 if option in ( '-C', '--class'): | 727 if option in ( '-C', '--class'): |
| 726 current_class = propstring.strip() | 728 current_class = propstring.strip() |
| 729 # XXX this is not flexible enough. | |
| 730 # we should chect for subclasses of these classes, | |
| 731 # not for the class name... | |
| 727 if current_class not in ('msg', 'file', 'user', 'issue'): | 732 if current_class not in ('msg', 'file', 'user', 'issue'): |
| 728 raise MailUsageError, ''' | 733 raise MailUsageError, ''' |
| 729 The mail gateway is not properly set up. Please contact | 734 The mail gateway is not properly set up. Please contact |
| 730 %s and have them fix the incorrect class specified as: | 735 %s and have them fix the incorrect class specified as: |
| 731 %s | 736 %s |
| 732 '''%(self.instance.config.ADMIN_EMAIL, current_class) | 737 ''' % (config['ADMIN_EMAIL'], current_class) |
| 733 if option in ('-S', '--set'): | 738 if option in ('-S', '--set'): |
| 734 if current_class == 'issue' : | 739 if current_class == 'issue' : |
| 735 errors, issue_props = setPropArrayFromString(self, | 740 errors, issue_props = setPropArrayFromString(self, |
| 736 cl, propstring.strip(), nodeid) | 741 cl, propstring.strip(), nodeid) |
| 737 elif current_class == 'file' : | 742 elif current_class == 'file' : |
| 749 if errors: | 754 if errors: |
| 750 raise MailUsageError, ''' | 755 raise MailUsageError, ''' |
| 751 The mail gateway is not properly set up. Please contact | 756 The mail gateway is not properly set up. Please contact |
| 752 %s and have them fix the incorrect properties: | 757 %s and have them fix the incorrect properties: |
| 753 %s | 758 %s |
| 754 '''%(self.instance.config.ADMIN_EMAIL, errors) | 759 '''%(config['ADMIN_EMAIL'], errors) |
| 755 | 760 |
| 756 # | 761 # |
| 757 # handle the users | 762 # handle the users |
| 758 # | 763 # |
| 759 # Don't create users if anonymous isn't allowed to register | 764 # Don't create users if anonymous isn't allowed to register |
| 801 # re-get the class with the new database connection | 806 # re-get the class with the new database connection |
| 802 cl = self.db.getclass(classname) | 807 cl = self.db.getclass(classname) |
| 803 | 808 |
| 804 # now update the recipients list | 809 # now update the recipients list |
| 805 recipients = [] | 810 recipients = [] |
| 806 tracker_email = self.instance.config.TRACKER_EMAIL.lower() | 811 tracker_email = config['TRACKER_EMAIL'].lower() |
| 807 for recipient in message.getaddrlist('to') + message.getaddrlist('cc'): | 812 for recipient in message.getaddrlist('to') + message.getaddrlist('cc'): |
| 808 r = recipient[1].strip().lower() | 813 r = recipient[1].strip().lower() |
| 809 if r == tracker_email or not r: | 814 if r == tracker_email or not r: |
| 810 continue | 815 continue |
| 811 | 816 |
| 847 messageid = message.getheader('message-id') | 852 messageid = message.getheader('message-id') |
| 848 inreplyto = message.getheader('in-reply-to') or '' | 853 inreplyto = message.getheader('in-reply-to') or '' |
| 849 # generate a messageid if there isn't one | 854 # generate a messageid if there isn't one |
| 850 if not messageid: | 855 if not messageid: |
| 851 messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), | 856 messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), |
| 852 classname, nodeid, self.instance.config.MAIL_DOMAIN) | 857 classname, nodeid, config['MAIL_DOMAIN']) |
| 853 | 858 |
| 854 # now handle the body - find the message | 859 # now handle the body - find the message |
| 855 content, attachments = message.extract_content() | 860 content, attachments = message.extract_content() |
| 856 if content is None: | 861 if content is None: |
| 857 raise MailUsageError, ''' | 862 raise MailUsageError, ''' |
| 858 Roundup requires the submission to be plain text. The message parser could | 863 Roundup requires the submission to be plain text. The message parser could |
| 859 not find a text/plain part to use. | 864 not find a text/plain part to use. |
| 860 ''' | 865 ''' |
| 861 | 866 |
| 862 # figure how much we should muck around with the email body | 867 # figure how much we should muck around with the email body |
| 863 keep_citations = getattr(self.instance.config, 'EMAIL_KEEP_QUOTED_TEXT', | 868 keep_citations = config['MAILGW_KEEP_QUOTED_TEXT'] |
| 864 'no') == 'yes' | 869 keep_body = config['MAILGW_LEAVE_BODY_UNCHANGED'] |
| 865 keep_body = getattr(self.instance.config, 'EMAIL_LEAVE_BODY_UNCHANGED', | |
| 866 'no') == 'yes' | |
| 867 | 870 |
| 868 # parse the body of the message, stripping out bits as appropriate | 871 # parse the body of the message, stripping out bits as appropriate |
| 869 summary, content = parseContent(content, keep_citations, | 872 summary, content = parseContent(content, keep_citations, |
| 870 keep_body) | 873 keep_body) |
| 871 content = content.strip() | 874 content = content.strip() |
| 872 | 875 |
| 873 # | 876 # |
| 874 # handle the attachments | 877 # handle the attachments |
| 875 # | 878 # |
| 876 if properties.has_key('files'): | 879 if properties.has_key('files'): |
| 877 files = [] | 880 files = [] |
| 878 for (name, mime_type, data) in attachments: | 881 for (name, mime_type, data) in attachments: |
| 893 props['files'] = fileprop | 896 props['files'] = fileprop |
| 894 else: | 897 else: |
| 895 # pre-load the files list | 898 # pre-load the files list |
| 896 props['files'] = files | 899 props['files'] = files |
| 897 | 900 |
| 898 # | 901 # |
| 899 # create the message if there's a message body (content) | 902 # create the message if there's a message body (content) |
| 900 # | 903 # |
| 901 if (content and properties.has_key('messages')): | 904 if (content and properties.has_key('messages')): |
| 902 try: | 905 try: |
| 903 message_id = self.db.msg.create(author=author, | 906 message_id = self.db.msg.create(author=author, |
| 940 # commit the changes to the DB | 943 # commit the changes to the DB |
| 941 self.db.commit() | 944 self.db.commit() |
| 942 | 945 |
| 943 return nodeid | 946 return nodeid |
| 944 | 947 |
| 945 | 948 |
| 946 def setPropArrayFromString(self, cl, propString, nodeid=None): | 949 def setPropArrayFromString(self, cl, propString, nodeid=None): |
| 947 ''' takes string of form prop=value,value;prop2=value | 950 ''' takes string of form prop=value,value;prop2=value |
| 948 and returns (error, prop[..]) | 951 and returns (error, prop[..]) |
| 949 ''' | 952 ''' |
| 950 props = {} | 953 props = {} |
| 1040 return 0 | 1043 return 0 |
| 1041 | 1044 |
| 1042 | 1045 |
| 1043 def parseContent(content, keep_citations, keep_body, | 1046 def parseContent(content, keep_citations, keep_body, |
| 1044 blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), | 1047 blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), |
| 1045 eol=re.compile(r'[\r\n]+'), | 1048 eol=re.compile(r'[\r\n]+'), |
| 1046 signature=re.compile(r'^[>|\s]*-- ?$'), | 1049 signature=re.compile(r'^[>|\s]*-- ?$'), |
| 1047 original_msg=re.compile(r'^[>|\s]*-----\s?Original Message\s?-----$')): | 1050 original_msg=re.compile(r'^[>|\s]*-----\s?Original Message\s?-----$')): |
| 1048 ''' The message body is divided into sections by blank lines. | 1051 ''' The message body is divided into sections by blank lines. |
| 1049 Sections where the second and all subsequent lines begin with a ">" | 1052 Sections where the second and all subsequent lines begin with a ">" |
| 1050 or "|" character are considered "quoting sections". The first line of | 1053 or "|" character are considered "quoting sections". The first line of |
| 1051 the first non-quoting section becomes the summary of the message. | 1054 the first non-quoting section becomes the summary of the message. |
| 1052 | 1055 |
| 1053 If keep_citations is true, then we keep the "quoting sections" in the | 1056 If keep_citations is true, then we keep the "quoting sections" in the |
| 1054 content. | 1057 content. |
| 1055 If keep_body is true, we even keep the signature sections. | 1058 If keep_body is true, we even keep the signature sections. |
| 1056 ''' | 1059 ''' |
| 1120 if not keep_body: | 1123 if not keep_body: |
| 1121 content = '\n\n'.join(l) | 1124 content = '\n\n'.join(l) |
| 1122 | 1125 |
| 1123 return summary, content | 1126 return summary, content |
| 1124 | 1127 |
| 1125 # vim: set filetype=python sts=4 sw=4 et si | 1128 # vim: set filetype=python sts=4 sw=4 et si : |
