source: trunk/email2trac.py.in @ 295

Last change on this file since 295 was 295, checked in by bas, 14 years ago

email2trac.py.in:

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