source: trunk/email2trac.py.in @ 309

Last change on this file since 309 was 309, checked in by bas, 11 years ago

email2trac.py.in:

  • added inline properties patch. To set ticket fields within an email, eg:

@owner: bas

Will set the owner of the ticket to bas, closes #171

  • Display a warning if it is a blog message and the plugin is not installed, see #175


  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 42.0 KB
Line 
1#!@PYTHON@
2# Copyright (C) 2002
3#
4# This file is part of the email2trac utils
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any
9# later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19#
20# For vi/emacs or other use tabstop=4 (vi: set ts=4)
21#
22"""
23email2trac.py -- Email tickets to Trac.
24
25A simple MTA filter to create Trac tickets from inbound emails.
26
27Copyright 2005, Daniel Lundin <daniel@edgewall.com>
28Copyright 2005, Edgewall Software
29
30Authors:
31  Bas van der Vlies <basv@sara.nl>
32  Walter de Jong <walter@sara.nl>
33
34The scripts reads emails from stdin and inserts directly into a Trac database.
35
36How to use
37----------
38 * See https://subtrac.sara.nl/oss/email2trac/
39
40 * Create an config file:
41    [DEFAULT]                        # REQUIRED
42    project      : /data/trac/test   # REQUIRED
43    debug        : 1                 # OPTIONAL, if set print some DEBUG info
44    trac_version : 0.10              # OPTIONAL, default is 0.11
45
46    [jouvin]                         # OPTIONAL project declaration, if set both fields necessary
47    project      : /data/trac/jouvin # use -p|--project jouvin. 
48       
49 * default config file is : /etc/email2trac.conf
50
51 * Commandline opions:
52                -h,--help
53                -f,--file  <configuration file>
54                -n,--dry-run
55                -p, --project <project name>
56                -t, --ticket_prefix <name>
57
58SVN Info:
59        $Id: email2trac.py.in 309 2010-02-02 15:39:19Z bas $
60"""
61import os
62import sys
63import string
64import getopt
65import stat
66import time
67import email
68import email.Iterators
69import email.Header
70import re
71import urllib
72import unicodedata
73from stat import *
74import mimetypes
75import traceback
76
77
78# Will fail where unavailable, e.g. Windows
79#
80try:
81    import syslog
82    SYSLOG_AVAILABLE = True
83except ImportError:
84    SYSLOG_AVAILABLE = False
85
86from datetime import tzinfo, timedelta, datetime
87from trac import config as trac_config
88
89# Some global variables
90#
91trac_default_version = '0.11'
92m = None
93
94# A UTC class needed for trac version 0.11, added by
95# tbaschak at ktc dot mb dot ca
96#
97class UTC(tzinfo):
98        """UTC"""
99        ZERO = timedelta(0)
100        HOUR = timedelta(hours=1)
101       
102        def utcoffset(self, dt):
103                return self.ZERO
104               
105        def tzname(self, dt):
106                return "UTC"
107               
108        def dst(self, dt):
109                return self.ZERO
110
111
112class TicketEmailParser(object):
113        env = None
114        comment = '> '
115   
116        def __init__(self, env, parameters, version):
117                self.env = env
118
119                # Database connection
120                #
121                self.db = None
122
123                # Save parameters
124                #
125                self.parameters = parameters
126
127                # Some useful mail constants
128                #
129                self.email_name = None
130                self.email_addr = None
131                self.email_from = None
132                self.author     = None
133                self.id         = None
134               
135                self.STRIP_CONTENT_TYPES = list()
136
137                self.VERSION = version
138                self.DRY_RUN = parameters['dry_run']
139
140                self.get_config = self.env.config.get
141
142                if parameters.has_key('umask'):
143                        os.umask(int(parameters['umask'], 8))
144
145                if parameters.has_key('debug'):
146                        self.DEBUG = int(parameters['debug'])
147                else:
148                        self.DEBUG = 0
149
150                if parameters.has_key('mailto_link'):
151                        self.MAILTO = int(parameters['mailto_link'])
152                        if parameters.has_key('mailto_cc'):
153                                self.MAILTO_CC = parameters['mailto_cc']
154                        else:
155                                self.MAILTO_CC = ''
156                else:
157                        self.MAILTO = 0
158
159                if parameters.has_key('spam_level'):
160                        self.SPAM_LEVEL = int(parameters['spam_level'])
161                else:
162                        self.SPAM_LEVEL = 0
163
164                if parameters.has_key('spam_header'):
165                        self.SPAM_HEADER = parameters['spam_header']
166                else:
167                        self.SPAM_HEADER = 'X-Spam-Score'
168
169                if parameters.has_key('email_quote'):
170                        self.EMAIL_QUOTE = str(parameters['email_quote'])
171                else:   
172                        self.EMAIL_QUOTE = '> '
173
174                if parameters.has_key('email_header'):
175                        self.EMAIL_HEADER = int(parameters['email_header'])
176                else:
177                        self.EMAIL_HEADER = 0
178
179                if parameters.has_key('alternate_notify_template'):
180                        self.notify_template = str(parameters['alternate_notify_template'])
181                else:
182                        self.notify_template = None
183
184                if parameters.has_key('alternate_notify_template_update'):
185                        self.notify_template_update = str(parameters['alternate_notify_template_update'])
186                else:
187                        self.notify_template_update = None
188
189                if parameters.has_key('reply_all'):
190                        self.REPLY_ALL = int(parameters['reply_all'])
191                else:
192                        self.REPLY_ALL = 0
193
194                if parameters.has_key('ticket_update'):
195                        self.TICKET_UPDATE = int(parameters['ticket_update'])
196                else:
197                        self.TICKET_UPDATE = 0
198
199                if parameters.has_key('drop_spam'):
200                        self.DROP_SPAM = int(parameters['drop_spam'])
201                else:
202                        self.DROP_SPAM = 0
203
204                if parameters.has_key('verbatim_format'):
205                        self.VERBATIM_FORMAT = int(parameters['verbatim_format'])
206                else:
207                        self.VERBATIM_FORMAT = 1
208
209                if parameters.has_key('reflow'):
210                        self.REFLOW = int(parameters['reflow'])
211                else:
212                        self.REFLOW = 1
213
214                if parameters.has_key('drop_alternative_html_version'):
215                        self.DROP_ALTERNATIVE_HTML_VERSION = int(parameters['drop_alternative_html_version'])
216                else:
217                        self.DROP_ALTERNATIVE_HTML_VERSION = 0
218
219                if parameters.has_key('strip_signature'):
220                        self.STRIP_SIGNATURE = int(parameters['strip_signature'])
221                else:
222                        self.STRIP_SIGNATURE = 0
223
224                if parameters.has_key('strip_quotes'):
225                        self.STRIP_QUOTES = int(parameters['strip_quotes'])
226                else:
227                        self.STRIP_QUOTES = 0
228
229                self.properties = dict()
230                if parameters.has_key('inline_properties'):
231                        self.INLINE_PROPERTIES = int(parameters['inline_properties'])
232                else:
233                        self.INLINE_PROPERTIES = 0
234
235                if parameters.has_key('use_textwrap'):
236                        self.USE_TEXTWRAP = int(parameters['use_textwrap'])
237                else:
238                        self.USE_TEXTWRAP = 0
239
240                if parameters.has_key('binhex'):
241                        self.STRIP_CONTENT_TYPES.append('application/mac-binhex40')
242
243                if parameters.has_key('applesingle'):
244                        self.STRIP_CONTENT_TYPES.append('application/applefile')
245
246                if parameters.has_key('appledouble'):
247                        self.STRIP_CONTENT_TYPES.append('application/applefile')
248
249                if parameters.has_key('strip_content_types'):
250                        items = parameters['strip_content_types'].split(',')
251                        for item in items:
252                                self.STRIP_CONTENT_TYPES.append(item.strip())
253
254                self.WORKFLOW = None
255                if parameters.has_key('workflow'):
256                        self.WORKFLOW = parameters['workflow']
257
258                # Use OS independend functions
259                #
260                self.TMPDIR = os.path.normcase('/tmp')
261                if parameters.has_key('tmpdir'):
262                        self.TMPDIR = os.path.normcase(str(parameters['tmpdir']))
263
264                if parameters.has_key('ignore_trac_user_settings'):
265                        self.IGNORE_TRAC_USER_SETTINGS = int(parameters['ignore_trac_user_settings'])
266                else:
267                        self.IGNORE_TRAC_USER_SETTINGS = 0
268
269                if parameters.has_key('subject_field_separator'):
270                        self.SUBJECT_FIELD_SEPARATOR = parameters['subject_field_separator'].strip()
271                else:
272                        self.SUBJECT_FIELD_SEPARATOR = '&'
273
274                self.trac_smtp_from = self.get_config('notification', 'smtp_from')
275
276        def spam(self, message):
277                """
278                # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
279                # Note if Spam_level then '*' are included
280                """
281                spam = False
282                if message.has_key(self.SPAM_HEADER):
283                        spam_l = string.split(message[self.SPAM_HEADER])
284
285                        try:
286                                number = spam_l[0].count('*')
287                        except IndexError, detail:
288                                number = 0
289                               
290                        if number >= self.SPAM_LEVEL:
291                                spam = True
292                               
293                # treat virus mails as spam
294                #
295                elif message.has_key('X-Virus-found'):                 
296                        spam = True
297
298                # How to handle SPAM messages
299                #
300                if self.DROP_SPAM and spam:
301                        if self.DEBUG > 2 :
302                                print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
303
304                        return 'drop'   
305
306                elif spam:
307
308                        return 'Spam'   
309
310                else:
311
312                        return False
313
314        def email_header_acl(self, keyword, header_field, default):
315                """
316                This function wil check if the email address is allowed or denied
317                to send mail to the ticket list
318            """
319                try:
320                        mail_addresses = self.parameters[keyword]
321
322                        # Check if we have an empty string
323                        #
324                        if not mail_addresses:
325                                return default
326
327                except KeyError, detail:
328                        if self.DEBUG > 2 :
329                                print 'TD: %s not defined, all messages are allowed.' %(keyword)
330
331                        return default
332
333                mail_addresses = string.split(mail_addresses, ',')
334
335                for entry in mail_addresses:
336                        entry = entry.strip()
337                        TO_RE = re.compile(entry, re.VERBOSE|re.IGNORECASE)
338                        result =  TO_RE.search(header_field)
339                        if result:
340                                return True
341
342                return False
343
344        def email_to_unicode(self, message_str):
345                """
346                Email has 7 bit ASCII code, convert it to unicode with the charset
347        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
348                understands it.
349                """
350                results =  email.Header.decode_header(message_str)
351                s = None
352                for text,format in results:
353                        if format:
354                                try:
355                                        temp = unicode(text, format)
356                                except UnicodeError, detail:
357                                        # This always works
358                                        #
359                                        temp = unicode(text, 'iso-8859-15')
360                                except LookupError, detail:
361                                        #text = 'ERROR: Could not find charset: %s, please install' %format
362                                        #temp = unicode(text, 'iso-8859-15')
363                                        temp = message_str
364                                       
365                        else:
366                                temp = string.strip(text)
367                                temp = unicode(text, 'iso-8859-15')
368
369                        if s:
370                                s = '%s %s' %(s, temp)
371                        else:
372                                s = '%s' %temp
373
374                #s = s.encode('utf-8')
375                return s
376
377        def debug_body(self, message_body, tempfile=False):
378                if tempfile:
379                        import tempfile
380                        body_file = tempfile.mktemp('.email2trac')
381                else:
382                        body_file = os.path.join(self.TMPDIR, 'body.txt')
383
384                print 'TD: writing body (%s)' % body_file
385                fx = open(body_file, 'wb')
386                if not message_body:
387                        message_body = '(None)'
388
389                message_body = message_body.encode('utf-8')
390                #message_body = unicode(message_body, 'iso-8859-15')
391
392                fx.write(message_body)
393                fx.close()
394                try:
395                        os.chmod(body_file,S_IRWXU|S_IRWXG|S_IRWXO)
396                except OSError:
397                        pass
398
399        def debug_attachments(self, message_parts):
400                n = 0
401                for part in message_parts:
402                        # Skip inline text parts
403                        if not isinstance(part, tuple):
404                                continue
405                               
406                        (original, filename, part) = part
407
408                        n = n + 1
409                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
410                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
411
412                        part_file = os.path.join(self.TMPDIR, filename)
413                        #part_file = '/var/tmp/part%d' % n
414                        print 'TD: writing part%d (%s)' % (n,part_file)
415                        fx = open(part_file, 'wb')
416                        text = part.get_payload(decode=1)
417                        if not text:
418                                text = '(None)'
419                        fx.write(text)
420                        fx.close()
421                        try:
422                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
423                        except OSError:
424                                pass
425
426        def email_header_txt(self, m):
427                """
428                Display To and CC addresses in description field
429                """
430                s = ''
431                #if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
432                if m['To'] and len(m['To']) > 0:
433                        s = "'''To:''' %s\r\n" %(m['To'])
434                if m['Cc'] and len(m['Cc']) > 0:
435                        s = "%s'''Cc:''' %s\r\n" % (s, m['Cc'])
436
437                return  self.email_to_unicode(s)
438
439
440        def get_sender_info(self, message):
441                """
442                Get the default author name and email address from the message
443                """
444
445                self.email_to = self.email_to_unicode(message['to'])
446                self.to_name, self.to_email_addr = email.Utils.parseaddr (self.email_to)
447
448                self.email_from = self.email_to_unicode(message['from'])
449                self.email_name, self.email_addr  = email.Utils.parseaddr(self.email_from)
450
451                ## Trac can not handle author's name that contains spaces
452                #  and forbid the ticket email address as author field
453
454                if self.email_addr == self.trac_smtp_from:
455                        self.author = "email2trac"
456                else:
457                        self.author = self.email_addr
458
459                if self.IGNORE_TRAC_USER_SETTINGS:
460                        return
461
462                # Is this a registered user, use email address as search key:
463                # result:
464                #   u : login name
465                #   n : Name that the user has set in the settings tab
466                #   e : email address that the user has set in the settings tab
467                #
468                users = [ (u,n,e) for (u, n, e) in self.env.get_known_users(self.db)
469                        if e and (e.lower() == self.email_addr.lower()) ]
470
471                if len(users) == 1:
472                        self.email_from = users[0][0]
473                        self.author = users[0][0]
474
475        def set_reply_fields(self, ticket, message):
476                """
477                Set all the right fields for a new ticket
478                """
479                if self.DEBUG:
480                        print 'TD: set_reply_fields'
481
482                ## Only use name or email adress
483                #ticket['reporter'] = self.email_from
484                ticket['reporter'] = self.author
485
486
487                # Put all CC-addresses in ticket CC field
488                #
489                if self.REPLY_ALL:
490
491                        email_cc = ''
492
493                        cc_addrs = email.Utils.getaddresses( message.get_all('cc', []) )
494
495                        if not cc_addrs:
496                                return
497
498                        ## Build a list of forbidden CC addresses
499                        #
500                        #to_addrs = email.Utils.getaddresses( message.get_all('to', []) )
501                        #to_list = list()
502                        #for n,e in to_addrs:
503                        #       to_list.append(e)
504                               
505                        # Remove reporter email address if notification is
506                        # on
507                        #
508                        if self.notification:
509                                try:
510                                        cc_addrs.remove((self.author, self.email_addr))
511                                except ValueError, detail:
512                                        pass
513
514                        for name,addr in cc_addrs:
515               
516                                ## Prevent mail loop
517                                #
518                                #if addr in to_list:
519
520                                if addr == self.trac_smtp_from:
521                                        if self.DEBUG:
522                                                print "Skipping %s mail address for CC-field" %(addr)
523                                        continue
524
525                                if email_cc:
526                                        email_cc = '%s, %s' %(email_cc, addr)
527                                else:
528                                        email_cc = addr
529
530                        if email_cc:
531                                if self.DEBUG:
532                                        print 'TD: set_reply_fields: %s' %email_cc
533
534                                ticket['cc'] = self.email_to_unicode(email_cc)
535
536        def save_email_for_debug(self, message, tempfile=False):
537
538                if tempfile:
539                        import tempfile
540                        msg_file = tempfile.mktemp('.email2trac')
541                else:
542                        #msg_file = '/var/tmp/msg.txt'
543                        msg_file = os.path.join(self.TMPDIR, 'msg.txt')
544
545                print 'TD: saving email to %s' % msg_file
546                fx = open(msg_file, 'wb')
547                fx.write('%s' % message)
548                fx.close()
549                try:
550                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
551                except OSError:
552                        pass
553
554                message_parts = self.get_message_parts(message)
555                message_parts = self.unique_attachment_names(message_parts)
556                body_text = self.body_text(message_parts)
557                self.debug_body(body_text, True)
558                self.debug_attachments(message_parts)
559
560        def str_to_dict(self, s):
561                """
562                Transfrom a string of the form [<key>=<value>]+ to dict[<key>] = <value>
563                """
564
565                fields = string.split(s, self.SUBJECT_FIELD_SEPARATOR)
566
567                result = dict()
568                for field in fields:
569                        try:
570                                index, value = string.split(field, '=')
571
572                                # We can not change the description of a ticket via the subject
573                                # line. The description is the body of the email
574                                #
575                                if index.lower() in ['description']:
576                                        continue
577
578                                if value:
579                                        result[index.lower()] = value
580
581                        except ValueError:
582                                pass
583                return result
584
585        def update_ticket_fields(self, ticket, user_dict, use_default=None):
586                """
587                This will update the ticket fields. It will check if the
588                given fields are known and if the right values are specified
589                It will only update the ticket field value:
590                        - If the field is known
591                        - If the value supplied is valid for the ticket field.
592                          If not then there are two options:
593                           1) Skip the value (use_default=None)
594                           2) Set default value for field (use_default=1)
595                """
596                if self.DEBUG:
597                        print "TD: update_ticket_fields"
598
599                # Build a system dictionary from the ticket fields
600                # with field as index and option as value
601                #
602                sys_dict = dict()
603                for field in ticket.fields:
604                        try:
605                                sys_dict[field['name']] = field['options']
606
607                        except KeyError:
608                                sys_dict[field['name']] = None
609                                pass
610
611                ## Check user supplied fields an compare them with the
612                # system one's
613                #
614                for field,value in user_dict.items():
615                        if self.DEBUG >= 10:
616                                print  'user_field\t %s = %s' %(field,value)
617
618                        ## To prevent mail loop
619                        #
620                        if field == 'cc':
621
622                                cc_list = user_dict['cc'].split(',')
623
624                                if self.trac_smtp_from in cc_list:
625                                        if self.DEBUG > 10:
626                                                print 'TD: MAIL LOOP: %s is not allowed as CC address' %(self.trac_smtp_from)
627                                        cc_list.remove(self.trac_smtp_from)
628
629                                value = ','.join(cc_list)
630                               
631
632                        if sys_dict.has_key(field):
633
634                                # Check if value is an allowed system option, if TypeError then
635                                # every value is allowed
636                                #
637                                try:
638                                        if value in sys_dict[field]:
639                                                ticket[field] = value
640                                        else:
641                                                # Must we set a default if value is not allowed
642                                                #
643                                                if use_default:
644                                                        value = self.get_config('ticket', 'default_%s' %(field) )
645                                                        ticket[field] = value
646
647                                except TypeError:
648                                        ticket[field] = value
649
650                                if self.DEBUG >= 10:
651                                        print  'ticket_field\t %s = %s' %(field,  ticket[field])
652                                       
653        def ticket_update(self, m, id, spam):
654                """
655                If the current email is a reply to an existing ticket, this function
656                will append the contents of this email to that ticket, instead of
657                creating a new one.
658                """
659                if self.DEBUG:
660                        print "TD: ticket_update: %s" %id
661
662                # Must we update ticket fields
663                #
664                update_fields = dict()
665                try:
666                        id, keywords = string.split(id, '?')
667
668                        # Skip the last ':' character
669                        #
670                        keywords = keywords[:-1]
671                        update_fields = self.str_to_dict(keywords)
672
673                        # Strip '#'
674                        #
675                        self.id = int(id[1:])
676
677                except ValueError:
678                        # Strip '#' and ':'
679                        #
680                        self.id = int(id[1:-1])
681
682
683                # When is the change committed
684                #
685                if self.VERSION == 0.11:
686                        utc = UTC()
687                        when = datetime.now(utc)
688                else:
689                        when = int(time.time())
690
691                try:
692                        tkt = Ticket(self.env, self.id, self.db)
693                except util.TracError, detail:
694                        # Not a valid ticket
695                        self.id = None
696                        return False
697
698                # How many changes has this ticket
699                cnum = len(tkt.get_changelog())
700
701
702                # reopen the ticket if it is was closed
703                # We must use the ticket workflow framework
704                #
705                if tkt['status'] in ['closed']:
706
707                        #print controller.actions['reopen']
708                        #
709                        # As reference 
710                        # req = Mock(href=Href('/'), abs_href=Href('http://www.example.com/'), authname='anonymous', perm=MockPerm(), args={})
711                        #
712                        #a = controller.render_ticket_action_control(req, tkt, 'reopen')
713                        #print 'controller : ', a
714                        #
715                        #b = controller.get_all_status()
716                        #print 'get all status: ', b
717                        #
718                        #b = controller.get_ticket_changes(req, tkt, 'reopen')
719                        #print 'get_ticket_changes :', b
720
721                        if self.WORKFLOW and (self.VERSION in [0.11]) :
722                                from trac.ticket.default_workflow import ConfigurableTicketWorkflow
723                                from trac.test import Mock, MockPerm
724
725                                req = Mock(authname='anonymous', perm=MockPerm(), args={})
726
727                                controller = ConfigurableTicketWorkflow(self.env)
728                                fields = controller.get_ticket_changes(req, tkt, self.WORKFLOW)
729
730                                if self.DEBUG:
731                                        print 'TD: Workflow ticket update fields: ', fields
732
733                                for key in fields.keys():
734                                        tkt[key] = fields[key]
735
736                        else:
737                                tkt['status'] = 'reopened'
738                                tkt['resolution'] = ''
739
740                # Must we update some ticket fields properties via subjectline
741                #
742                if update_fields:
743                        self.update_ticket_fields(tkt, update_fields)
744
745                message_parts = self.get_message_parts(m)
746                message_parts = self.unique_attachment_names(message_parts)
747
748                # Must we update some ticket fields properties via body_text
749                #
750                if self.properties:
751                                self.update_ticket_fields(tkt, self.properties)
752
753                if self.EMAIL_HEADER:
754                        message_parts.insert(0, self.email_header_txt(m))
755
756                body_text = self.body_text(message_parts)
757
758                if body_text.strip() or update_fields or self.properties:
759                        if self.DRY_RUN:
760                                print 'DRY_RUN: tkt.save_changes(self.author, body_text, ticket_change_number) ', self.author, cnum
761                        else:
762                                tkt.save_changes(self.author, body_text, when, None, str(cnum))
763                       
764
765                if self.VERSION  == 0.9:
766                        s = self.attachments(message_parts, True)
767                else:
768                        s = self.attachments(message_parts)
769
770                if self.notification and not spam:
771                        self.notify(tkt, False, when)
772
773                return True
774
775        def set_ticket_fields(self, ticket):
776                """
777                set the ticket fields to value specified
778                        - /etc/email2trac.conf with <prefix>_<field>
779                        - trac default values, trac.ini
780                """
781                user_dict = dict()
782
783                for field in ticket.fields:
784
785                        name = field['name']
786
787                        # skip some fields like resolution
788                        #
789                        if name in [ 'resolution' ]:
790                                continue
791
792                        # default trac value
793                        #
794                        if not field.get('custom'):
795                                value = self.get_config('ticket', 'default_%s' %(name) )
796                        else:
797                                value = field.get('value')
798                                options = field.get('options')
799                                if value and options and value not in options:
800                                        value = options[int(value)]
801
802                        if self.DEBUG > 10:
803                                print 'trac.ini name %s = %s' %(name, value)
804
805                        prefix = self.parameters['ticket_prefix']
806                        try:
807                                value = self.parameters['%s_%s' %(prefix, name)]
808                                if self.DEBUG > 10:
809                                        print 'email2trac.conf %s = %s ' %(name, value)
810
811                        except KeyError, detail:
812                                pass
813               
814                        if self.DEBUG:
815                                print 'user_dict[%s] = %s' %(name, value)
816
817                        user_dict[name] = value
818
819                self.update_ticket_fields(ticket, user_dict, use_default=1)
820
821                # Set status ticket
822                #`
823                ticket['status'] = 'new'
824
825
826
827        def new_ticket(self, msg, subject, spam, set_fields = None):
828                """
829                Create a new ticket
830                """
831                if self.DEBUG:
832                        print "TD: new_ticket"
833
834                tkt = Ticket(self.env)
835                self.set_ticket_fields(tkt)
836
837                # Old style setting for component, will be removed
838                #
839                if spam:
840                        tkt['component'] = 'Spam'
841
842                elif self.parameters.has_key('component'):
843                        tkt['component'] = self.parameters['component']
844
845                if not msg['Subject']:
846                        tkt['summary'] = u'(No subject)'
847                else:
848                        tkt['summary'] = subject
849
850                self.set_reply_fields(tkt, msg)
851
852                if set_fields:
853                        rest, keywords = string.split(set_fields, '?')
854
855                        if keywords:
856                                update_fields = self.str_to_dict(keywords)
857                                self.update_ticket_fields(tkt, update_fields)
858
859                # produce e-mail like header
860                #
861                head = ''
862                if self.EMAIL_HEADER > 0:
863                        head = self.email_header_txt(msg)
864
865                message_parts = self.get_message_parts(msg)
866
867                # Must we update some ticket fields properties via body_text
868                #
869                if self.properties:
870                                self.update_ticket_fields(tkt, self.properties)
871
872                if self.DEBUG:
873                        print 'TD: self.get_message_parts ',
874                        print message_parts
875
876                message_parts = self.unique_attachment_names(message_parts)
877                if self.DEBUG:
878                        print 'TD: self.unique_attachment_names',
879                        print message_parts
880               
881                if self.EMAIL_HEADER > 0:
882                        message_parts.insert(0, self.email_header_txt(msg))
883                       
884                body_text = self.body_text(message_parts)
885
886                tkt['description'] = body_text
887
888                #when = int(time.time())
889                #
890                utc = UTC()
891                when = datetime.now(utc)
892
893                if not self.DRY_RUN:
894                        self.id = tkt.insert()
895       
896                changed = False
897                comment = ''
898
899                # some routines in trac are dependend on ticket id     
900                # like alternate notify template
901                #
902                if self.notify_template:
903                        tkt['id'] = self.id
904                        changed = True
905
906                ## Rewrite the description if we have mailto enabled
907                #
908                if self.MAILTO:
909                        changed = True
910                        comment = u'\nadded mailto line\n'
911                        mailto = self.html_mailto_link( m['Subject'], body_text)
912
913                        tkt['description'] = u'%s\r\n%s%s\r\n' \
914                                %(head, mailto, body_text)
915       
916                ## Save the attachments to the ticket   
917                #
918                has_attachments =  self.attachments(message_parts)
919
920                #  Disabled
921                #if has_attachments:
922                #       changed = True
923                #       comment = '%s\n%s\n' %(comment, has_attachments)
924
925                if changed:
926                        if self.DRY_RUN:
927                                print 'DRY_RUN: tkt.save_changes(self.author, comment) ', self.author
928                        else:
929                                tkt.save_changes(self.author, comment)
930                                #print tkt.get_changelog(self.db, when)
931
932                if self.notification and not spam:
933                        self.notify(tkt, True)
934
935
936        def blog(self, id):
937                """
938                The blog create/update function
939                """
940                # import the modules
941                #
942                from tracfullblog.core import FullBlogCore
943                from tracfullblog.model import BlogPost, BlogCommen
944
945                # instantiate blog core
946                blog = FullBlogCore(self.env)
947                req = ''
948               
949                if id:
950
951                        # update blog
952                        #
953                        comment = BlogComment(self.env, id)
954                        comment.author = self.author
955                        comment.comment = self.get_body_text(m)
956                        blog.create_comment(req, comment)
957
958                else:
959                        # create blog
960                        #
961                        import time
962                        post = BlogPost(self.env, 'blog_'+time.strftime("%Y%m%d%H%M%S", time.gmtime()))
963
964                        #post = BlogPost(self.env, blog._get_default_postname(self.env))
965                       
966                        post.author = self.author
967                        post.title = self.email_to_unicode(m['Subject'])
968                        post.body = self.get_body_text(m)
969                       
970                        blog.create_post(req, post, self.author, u'Created by email2trac', False)
971
972        def save_message_for_debug(self):
973                """
974                Save the messages. So we can use it for debug purposes
975                """
976               
977
978        def parse(self, fp):
979                global m
980
981                m = email.message_from_file(fp)
982               
983                if not m:
984                        if self.DEBUG:
985                                print "TD: This is not a valid email message format"
986                        return
987                       
988                # Work around lack of header folding in Python; see http://bugs.python.org/issue4696
989                m.replace_header('Subject', m['Subject'].replace('\r', '').replace('\n', ''))
990
991                if self.DEBUG > 1:        # save the entire e-mail message text
992                        self.save_email_for_debug(m, True)
993
994                self.db = self.env.get_db_cnx()
995                self.get_sender_info(m)
996
997                if not self.email_header_acl('white_list', self.email_addr, True):
998                        if self.DEBUG > 1 :
999                                print 'Message rejected : %s not in white list' %(self.email_addr)
1000                        return False
1001
1002                if self.email_header_acl('black_list', self.email_addr, False):
1003                        if self.DEBUG > 1 :
1004                                print 'Message rejected : %s in black list' %(self.email_addr)
1005                        return False
1006
1007                if not self.email_header_acl('recipient_list', self.to_email_addr, True):
1008                        if self.DEBUG > 1 :
1009                                print 'Message rejected : %s not in recipient list' %(self.to_email_addr)
1010                        return False
1011
1012                # If drop the message
1013                #
1014                if self.spam(m) == 'drop':
1015                        return False
1016
1017                elif self.spam(m) == 'spam':
1018                        spam_msg = True
1019                else:
1020                        spam_msg = False
1021
1022                if self.get_config('notification', 'smtp_enabled') in ['true']:
1023                        self.notification = 1
1024                else:
1025                        self.notification = 0
1026
1027
1028                # Check if  FullBlogPlugin is installed
1029                #
1030                blog_enabled = None
1031                if self.get_config('components', 'tracfullblog.*') in ['enabled']:
1032                        blog_enabled = True
1033                       
1034                if not m['Subject']:
1035                        return False
1036                else:
1037                        subject  = self.email_to_unicode(m['Subject'])         
1038
1039                #
1040                # [hic] #1529: Re: LRZ
1041                # [hic] #1529?owner=bas,priority=medium: Re: LRZ
1042                #
1043                TICKET_RE = re.compile(r"""
1044                        (?P<blog>blog:(?P<blog_id>\w*))
1045                        |(?P<new_fields>[#][?].*)
1046                        |(?P<reply>[#][\d]+:)
1047                        |(?P<reply_fields>[#][\d]+\?.*?:)
1048                        """, re.VERBOSE)
1049
1050                # Find out if this is a ticket or a blog
1051                #
1052                result =  TICKET_RE.search(subject)
1053
1054                if result:
1055                        if result.group('blog'):
1056                                if blog_enabled:
1057                                        self.blog(result.group('blog_id'))
1058                                else:
1059                                        if self.DEBUG:
1060                                                print "Fullblog plugin is not installed"
1061                                                return
1062
1063                        # update ticket + fields
1064                        #
1065                        if result.group('reply_fields') and self.TICKET_UPDATE:
1066                                self.ticket_update(m, result.group('reply_fields'), spam_msg)
1067
1068                        # Update ticket
1069                        #
1070                        elif result.group('reply') and self.TICKET_UPDATE:
1071                                self.ticket_update(m, result.group('reply'), spam_msg)
1072
1073                        # New ticket + fields
1074                        #
1075                        elif result.group('new_fields'):
1076                                self.new_ticket(m, subject[:result.start('new_fields')], spam_msg, result.group('new_fields'))
1077
1078                # Create ticket
1079                #
1080                else:
1081                        self.new_ticket(m, subject, spam_msg)
1082
1083        def strip_signature(self, text):
1084                """
1085                Strip signature from message, inspired by Mailman software
1086                """
1087                body = []
1088                for line in text.splitlines():
1089                        if line == '-- ':
1090                                break
1091                        body.append(line)
1092
1093                return ('\n'.join(body))
1094
1095        def reflow(self, text, delsp = 0):
1096                """
1097                Reflow the message based on the format="flowed" specification (RFC 3676)
1098                """
1099                flowedlines = []
1100                quotelevel = 0
1101                prevflowed = 0
1102
1103                for line in text.splitlines():
1104                        from re import match
1105                       
1106                        # Figure out the quote level and the content of the current line
1107                        m = match('(>*)( ?)(.*)', line)
1108                        linequotelevel = len(m.group(1))
1109                        line = m.group(3)
1110
1111                        # Determine whether this line is flowed
1112                        if line and line != '-- ' and line[-1] == ' ':
1113                                flowed = 1
1114                        else:
1115                                flowed = 0
1116
1117                        if flowed and delsp and line and line[-1] == ' ':
1118                                line = line[:-1]
1119
1120                        # If the previous line is flowed, append this line to it
1121                        if prevflowed and line != '-- ' and linequotelevel == quotelevel:
1122                                flowedlines[-1] += line
1123                        # Otherwise, start a new line
1124                        else:
1125                                flowedlines.append('>' * linequotelevel + line)
1126
1127                        prevflowed = flowed
1128                       
1129
1130                return '\n'.join(flowedlines)
1131
1132        def strip_quotes(self, text):
1133                """
1134                Strip quotes from message by Nicolas Mendoza
1135                """
1136                body = []
1137                for line in text.splitlines():
1138                        if line.startswith(self.EMAIL_QUOTE):
1139                                continue
1140                        body.append(line)
1141
1142                return ('\n'.join(body))
1143
1144        def inline_properties(self, text):
1145                """
1146                Parse text if we use inline keywords to set ticket fields
1147                """
1148                if self.DEBUG:
1149                        print 'TD: inline_properties function'
1150
1151                properties = dict()
1152                body = list()
1153
1154                INLINE_EXP = re.compile('\s*[@]\s*([a-zA-Z]+)\s*:(.*)$')
1155
1156                for line in text.splitlines():
1157                        match = INLINE_EXP.match(line)
1158                        if match:
1159                                print match.groups()
1160                                keyword, value = match.groups()
1161                                self.properties[keyword] = value.strip()
1162                        else:
1163                                body.append(line)
1164                               
1165                return '\n'.join(body)
1166
1167
1168        def wrap_text(self, text, replace_whitespace = False):
1169                """
1170                Will break a lines longer then given length into several small
1171                lines of size given length
1172                """
1173                import textwrap
1174
1175                LINESEPARATOR = '\n'
1176                reformat = ''
1177
1178                for s in text.split(LINESEPARATOR):
1179                        tmp = textwrap.fill(s,self.USE_TEXTWRAP)
1180                        if tmp:
1181                                reformat = '%s\n%s' %(reformat,tmp)
1182                        else:
1183                                reformat = '%s\n' %reformat
1184
1185                return reformat
1186
1187                # Python2.4 and higher
1188                #
1189                #return LINESEPARATOR.join(textwrap.fill(s,width) for s in str.split(LINESEPARATOR))
1190                #
1191
1192
1193        def get_message_parts(self, msg):
1194                """
1195                parses the email message and returns a list of body parts and attachments
1196                body parts are returned as strings, attachments are returned as tuples of (filename, Message object)
1197                """
1198                message_parts = list()
1199       
1200                ALTERNATIVE_MULTIPART = False
1201
1202                for part in msg.walk():
1203                        if self.DEBUG:
1204                                print 'TD: Message part: Main-Type: %s' % part.get_content_maintype()
1205                                print 'TD: Message part: Content-Type: %s' % part.get_content_type()
1206
1207                        ## Check content type
1208                        #
1209                        if part.get_content_type() in self.STRIP_CONTENT_TYPES:
1210
1211                                if self.DEBUG:
1212                                        print "TD: A %s attachment named '%s' was skipped" %(part.get_content_type(), part.get_filename())
1213
1214                                continue
1215
1216                        ## Catch some mulitpart execptions
1217                        #
1218                        if part.get_content_type() == 'multipart/alternative':
1219                                ALTERNATIVE_MULTIPART = True
1220                                continue
1221
1222                        ## Skip multipart containers
1223                        #
1224                        if part.get_content_maintype() == 'multipart':
1225                                if self.DEBUG:
1226                                        print "TD: Skipping multipart container"
1227                                continue
1228                       
1229                        ## 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"
1230                        #
1231                        inline = self.inline_part(part)
1232
1233                        ## Drop HTML message
1234                        #
1235                        if ALTERNATIVE_MULTIPART and self.DROP_ALTERNATIVE_HTML_VERSION:
1236                                if part.get_content_type() == 'text/html':
1237                                        if self.DEBUG:
1238                                                print "TD: Skipping alternative HTML message"
1239
1240                                        ALTERNATIVE_MULTIPART = False
1241                                        continue
1242
1243                        ## Inline text parts are where the body is
1244                        #
1245                        if part.get_content_type() == 'text/plain' and inline:
1246                                if self.DEBUG:
1247                                        print 'TD:               Inline body part'
1248
1249                                # Try to decode, if fails then do not decode
1250                                #
1251                                body_text = part.get_payload(decode=1)
1252                                if not body_text:                       
1253                                        body_text = part.get_payload(decode=0)
1254
1255                                format = email.Utils.collapse_rfc2231_value(part.get_param('Format', 'fixed')).lower()
1256                                delsp = email.Utils.collapse_rfc2231_value(part.get_param('DelSp', 'no')).lower()
1257
1258                                if self.REFLOW and not self.VERBATIM_FORMAT and format == 'flowed':
1259                                        body_text = self.reflow(body_text, delsp == 'yes')
1260       
1261                                if self.STRIP_SIGNATURE:
1262                                        body_text = self.strip_signature(body_text)
1263
1264                                if self.STRIP_QUOTES:
1265                                        body_text = self.strip_quotes(body_text)
1266
1267                                if self.INLINE_PROPERTIES:
1268                                        body_text = self.inline_properties(body_text)
1269
1270                                if self.USE_TEXTWRAP:
1271                                        body_text = self.wrap_text(body_text)
1272
1273                                ## Get contents charset (iso-8859-15 if not defined in mail headers)
1274                                #
1275                                charset = part.get_content_charset()
1276                                if not charset:
1277                                        charset = 'iso-8859-15'
1278
1279                                try:
1280                                        ubody_text = unicode(body_text, charset)
1281
1282                                except UnicodeError, detail:
1283                                        ubody_text = unicode(body_text, 'iso-8859-15')
1284
1285                                except LookupError, detail:
1286                                        ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset)
1287
1288                                if self.VERBATIM_FORMAT:
1289                                        message_parts.append('{{{\r\n%s\r\n}}}' %ubody_text)
1290                                else:
1291                                        message_parts.append('%s' %ubody_text)
1292                        else:
1293                                if self.DEBUG:
1294                                        print 'TD:               Filename: %s' % part.get_filename()
1295
1296                                message_parts.append((part.get_filename(), part))
1297
1298                return message_parts
1299               
1300        def unique_attachment_names(self, message_parts):
1301                """
1302                """
1303                renamed_parts = []
1304                attachment_names = set()
1305
1306                for part in message_parts:
1307                       
1308                        ## If not an attachment, leave it alone
1309                        #
1310                        if not isinstance(part, tuple):
1311                                renamed_parts.append(part)
1312                                continue
1313                               
1314                        (filename, part) = part
1315
1316                        ## If no filename, use a default one
1317                        #
1318                        if not filename:
1319                                filename = 'untitled-part'
1320
1321                                # Guess the extension from the content type, use non strict mode
1322                                # some additional non-standard but commonly used MIME types
1323                                # are also recognized
1324                                #
1325                                ext = mimetypes.guess_extension(part.get_content_type(), False)
1326                                if not ext:
1327                                        ext = '.bin'
1328
1329                                filename = '%s%s' % (filename, ext)
1330
1331# We now use the attachment insert function
1332#
1333                        ## Discard relative paths in attachment names
1334                        #
1335                        #filename = filename.replace('\\', '/').replace(':', '/')
1336                        #filename = os.path.basename(filename)
1337                        #
1338                        # We try to normalize the filename to utf-8 NFC if we can.
1339                        # Files uploaded from OS X might be in NFD.
1340                        # Check python version and then try it
1341                        #
1342                        #if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
1343                        #       try:
1344                        #               filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
1345                        #       except TypeError:
1346                        #               pass
1347
1348                        # Make the filename unique for this ticket
1349                        num = 0
1350                        unique_filename = filename
1351                        dummy_filename, ext = os.path.splitext(filename)
1352
1353                        while unique_filename in attachment_names or self.attachment_exists(unique_filename):
1354                                num += 1
1355                                unique_filename = "%s-%s%s" % (dummy_filename, num, ext)
1356                               
1357                        if self.DEBUG:
1358                                print 'TD: Attachment with filename %s will be saved as %s' % (filename, unique_filename)
1359
1360                        attachment_names.add(unique_filename)
1361
1362                        renamed_parts.append((filename, unique_filename, part))
1363       
1364                return renamed_parts
1365                       
1366        def inline_part(self, part):
1367                return part.get_param('inline', None, 'Content-Disposition') == '' or not part.has_key('Content-Disposition')
1368               
1369                       
1370        def attachment_exists(self, filename):
1371
1372                if self.DEBUG:
1373                        print "TD: attachment_exists: Ticket number : %s, Filename : %s" %(self.id, filename)
1374
1375                # We have no valid ticket id
1376                #
1377                if not self.id:
1378                        return False
1379
1380                try:
1381                        att = attachment.Attachment(self.env, 'ticket', self.id, filename)
1382                        return True
1383                except attachment.ResourceNotFound:
1384                        return False
1385                       
1386        def body_text(self, message_parts):
1387                body_text = []
1388               
1389                for part in message_parts:
1390                        # Plain text part, append it
1391                        if not isinstance(part, tuple):
1392                                body_text.extend(part.strip().splitlines())
1393                                body_text.append("")
1394                                continue
1395                               
1396                        (original, filename, part) = part
1397                        inline = self.inline_part(part)
1398                       
1399                        if part.get_content_maintype() == 'image' and inline:
1400                                body_text.append('[[Image(%s)]]' % filename)
1401                                body_text.append("")
1402                        else:
1403                                body_text.append('[attachment:"%s"]' % filename)
1404                                body_text.append("")
1405                               
1406                body_text = '\r\n'.join(body_text)
1407                return body_text
1408
1409        def notify(self, tkt, new=True, modtime=0):
1410                """
1411                A wrapper for the TRAC notify function. So we can use templates
1412                """
1413                if self.DRY_RUN:
1414                                print 'DRY_RUN: self.notify(tkt, True) ', self.author
1415                                return
1416                try:
1417                        # create false {abs_}href properties, to trick Notify()
1418                        #
1419                        if not self.VERSION == 0.11:
1420                                self.env.abs_href = Href(self.get_config('project', 'url'))
1421                                self.env.href = Href(self.get_config('project', 'url'))
1422
1423                        tn = TicketNotifyEmail(self.env)
1424
1425                        if self.notify_template:
1426
1427                                if self.VERSION == 0.11:
1428
1429                                        from trac.web.chrome import Chrome
1430
1431                                        if self.notify_template_update and not new:
1432                                                tn.template_name = self.notify_template_update
1433                                        else:
1434                                                tn.template_name = self.notify_template
1435
1436                                        tn.template = Chrome(tn.env).load_template(tn.template_name, method='text')
1437                                               
1438                                else:
1439
1440                                        tn.template_name = self.notify_template;
1441
1442                        tn.notify(tkt, new, modtime)
1443
1444                except Exception, e:
1445                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(self.id, e)
1446
1447        def html_mailto_link(self, subject, body):
1448                """
1449                This function returns a HTML mailto tag with the ticket id and author email address
1450                """
1451                if not self.author:
1452                        author = self.email_addr
1453                else:   
1454                        author = self.author
1455
1456                # use urllib to escape the chars
1457                #
1458                s = 'mailto:%s?Subject=%s&Cc=%s' %(
1459                       urllib.quote(self.email_addr),
1460                           urllib.quote('Re: #%s: %s' %(self.id, subject)),
1461                           urllib.quote(self.MAILTO_CC)
1462                           )
1463
1464                s = '\r\n{{{\r\n#!html\r\n<a\r\n href="%s">Reply to: %s\r\n</a>\r\n}}}\r\n' %(s, author)
1465                return s
1466
1467        def attachments(self, message_parts, update=False):
1468                '''
1469                save any attachments as files in the ticket's directory
1470                '''
1471                if self.DRY_RUN:
1472                        print "DRY_RUN: no attachments saved"
1473                        return ''
1474
1475                count = 0
1476
1477                # Get Maxium attachment size
1478                #
1479                max_size = int(self.get_config('attachment', 'max_size'))
1480                status   = ''
1481               
1482                for part in message_parts:
1483                        # Skip body parts
1484                        if not isinstance(part, tuple):
1485                                continue
1486                               
1487                        (original, filename, part) = part
1488                        #
1489                        # Must be tuneables HvB
1490                        #
1491                        path, fd =  util.create_unique_file(os.path.join(self.TMPDIR, filename))
1492                        text = part.get_payload(decode=1)
1493                        if not text:
1494                                text = '(None)'
1495                        fd.write(text)
1496                        fd.close()
1497
1498                        # get the file_size
1499                        #
1500                        stats = os.lstat(path)
1501                        file_size = stats[stat.ST_SIZE]
1502
1503                        # Check if the attachment size is allowed
1504                        #
1505                        if (max_size != -1) and (file_size > max_size):
1506                                status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \
1507                                        %(status, original, file_size, max_size)
1508
1509                                os.unlink(path)
1510                                continue
1511                        else:
1512                                count = count + 1
1513                                       
1514                        # Insert the attachment
1515                        #
1516                        fd = open(path, 'rb')
1517                        att = attachment.Attachment(self.env, 'ticket', self.id)
1518
1519                        # This will break the ticket_update system, the body_text is vaporized
1520                        # ;-(
1521                        #
1522                        if not update:
1523                                att.author = self.author
1524                                att.description = self.email_to_unicode('Added by email2trac')
1525
1526                        att.insert(filename, fd, file_size)
1527
1528                        #except  util.TracError, detail:
1529                        #       print detail
1530
1531                        # Remove the created temporary filename
1532                        #
1533                        fd.close()
1534                        os.unlink(path)
1535
1536                # Return how many attachments
1537                #
1538                status = 'This message has %d attachment(s)\n%s' %(count, status)
1539                return status
1540
1541
1542def mkdir_p(dir, mode):
1543        '''do a mkdir -p'''
1544
1545        arr = string.split(dir, '/')
1546        path = ''
1547        for part in arr:
1548                path = '%s/%s' % (path, part)
1549                try:
1550                        stats = os.stat(path)
1551                except OSError:
1552                        os.mkdir(path, mode)
1553
1554def ReadConfig(file, name):
1555        """
1556        Parse the config file
1557        """
1558        if not os.path.isfile(file):
1559                print 'File %s does not exist' %file
1560                sys.exit(1)
1561
1562        config = trac_config.Configuration(file)
1563
1564        # Use given project name else use defaults
1565        #
1566        if name:
1567                sections = config.sections()
1568                if not name in sections:
1569                        print "Not a valid project name: %s" %name
1570                        print "Valid names: %s" %sections
1571                        sys.exit(1)
1572
1573                project =  dict()
1574                for option, value in  config.options(name):
1575                        project[option] = value
1576
1577        else:
1578                # use some trac internals to get the defaults
1579                #
1580                project = config.parser.defaults()
1581
1582        return project
1583
1584
1585if __name__ == '__main__':
1586        # Default config file
1587        #
1588        configfile = '@email2trac_conf@'
1589        project = ''
1590        component = ''
1591        ticket_prefix = 'default'
1592        dry_run = None
1593
1594        ENABLE_SYSLOG = 0
1595
1596
1597        SHORT_OPT = 'chf:np:t:'
1598        LONG_OPT  =  ['component=', 'dry-run', 'help', 'file=', 'project=', 'ticket_prefix=']
1599
1600        try:
1601                opts, args = getopt.getopt(sys.argv[1:], SHORT_OPT, LONG_OPT)
1602        except getopt.error,detail:
1603                print __doc__
1604                print detail
1605                sys.exit(1)
1606       
1607        project_name = None
1608        for opt,value in opts:
1609                if opt in [ '-h', '--help']:
1610                        print __doc__
1611                        sys.exit(0)
1612                elif opt in ['-c', '--component']:
1613                        component = value
1614                elif opt in ['-f', '--file']:
1615                        configfile = value
1616                elif opt in ['-n', '--dry-run']:
1617                        dry_run = True
1618                elif opt in ['-p', '--project']:
1619                        project_name = value
1620                elif opt in ['-t', '--ticket_prefix']:
1621                        ticket_prefix = value
1622       
1623        settings = ReadConfig(configfile, project_name)
1624        if not settings.has_key('project'):
1625                print __doc__
1626                print 'No Trac project is defined in the email2trac config file.'
1627                sys.exit(1)
1628       
1629        if component:
1630                settings['component'] = component
1631
1632        # The default prefix for ticket values in email2trac.conf
1633        #
1634        settings['ticket_prefix'] = ticket_prefix
1635        settings['dry_run'] = dry_run
1636       
1637        if settings.has_key('trac_version'):
1638                version = settings['trac_version']
1639        else:
1640                version = trac_default_version
1641
1642
1643        #debug HvB
1644        #print settings
1645
1646        try:
1647                if version == '0.9':
1648                        from trac import attachment
1649                        from trac.env import Environment
1650                        from trac.ticket import Ticket
1651                        from trac.web.href import Href
1652                        from trac import util
1653                        from trac.Notify import TicketNotifyEmail
1654                elif version == '0.10':
1655                        from trac import attachment
1656                        from trac.env import Environment
1657                        from trac.ticket import Ticket
1658                        from trac.web.href import Href
1659                        from trac import util
1660                        #
1661                        # return  util.text.to_unicode(str)
1662                        #
1663                        # see http://projects.edgewall.com/trac/changeset/2799
1664                        from trac.ticket.notification import TicketNotifyEmail
1665                        from trac import config as trac_config
1666                elif version == '0.11':
1667                        from trac import attachment
1668                        from trac.env import Environment
1669                        from trac.ticket import Ticket
1670                        from trac.web.href import Href
1671                        from trac import config as trac_config
1672                        from trac import util
1673
1674
1675                        #
1676                        # return  util.text.to_unicode(str)
1677                        #
1678                        # see http://projects.edgewall.com/trac/changeset/2799
1679                        from trac.ticket.notification import TicketNotifyEmail
1680                else:
1681                        print 'TRAC version %s is not supported' %version
1682                        sys.exit(1)
1683                       
1684                if settings.has_key('enable_syslog'):
1685                        if SYSLOG_AVAILABLE:
1686                                ENABLE_SYSLOG =  float(settings['enable_syslog'])
1687
1688
1689                # Must be set before environment is created
1690                #
1691                if settings.has_key('python_egg_cache'):
1692                        python_egg_cache = str(settings['python_egg_cache'])
1693                        os.environ['PYTHON_EGG_CACHE'] = python_egg_cache
1694
1695                env = Environment(settings['project'], create=0)
1696                tktparser = TicketEmailParser(env, settings, float(version))
1697                tktparser.parse(sys.stdin)
1698
1699        # Catch all errors ans log to SYSLOG if we have enabled this
1700        # else stdout
1701        #
1702        except Exception, error:
1703                if ENABLE_SYSLOG:
1704                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
1705
1706                        etype, evalue, etb = sys.exc_info()
1707                        for e in traceback.format_exception(etype, evalue, etb):
1708                                syslog.syslog(e)
1709
1710                        syslog.closelog()
1711                else:
1712                        traceback.print_exc()
1713
1714                if m:
1715                        tktparser.save_email_for_debug(m, True)
1716
1717                sys.exit(1)
1718# EOB
Note: See TracBrowser for help on using the repository browser.