OlAddin.py
上传用户:lswyart
上传日期:2008-06-12
资源大小:3441k
文件大小:47k
源码类别:

杀毒

开发平台:

Visual C++

  1. #-----------------------------------------------------------------------------
  2. # Name:        OlAddin.py
  3. # Product:     ClamWin Free Antivirus
  4. #
  5. # Author:      alch [alch at users dot sourceforge dot net]
  6. #
  7. # Created:     2004/31/03
  8. # Copyright:   Copyright alch (c) 2004
  9. # Licence:     
  10. #   This program is free software; you can redistribute it and/or modify
  11. #   it under the terms of the GNU General Public License as published by
  12. #   the Free Software Foundation; either version 2 of the License, or
  13. #   (at your option) any later version.
  14. #   This program is distributed in the hope that it will be useful,
  15. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. #   GNU General Public License for more details.
  18. #   You should have received a copy of the GNU General Public License
  19. #   along with this program; if not, write to the Free Software
  20. #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21. #-----------------------------------------------------------------------------
  22. # ClamWin Outlook Addin
  23. # Parts of the code based on SpamBayes Source Code (Outlook2000/addin.py)
  24. # Thanks to Sean True and Mark Hammond
  25. # Copyright (C) 2002-2003 Python Software Foundation; All Rights Reserved
  26. # The Python Software Foundation (PSF) holds copyright on all material
  27. # in this project.  You may use it under the terms of the PSF license:
  28. # PSF LICENSE AGREEMENT FOR THE SPAMBAYES PROJECT
  29. # -----------------------------------------------
  30. # 1. This LICENSE AGREEMENT is between the Python Software Foundation
  31. # ("PSF"), and the Individual or Organization ("Licensee") accessing and
  32. # otherwise using the spambayes software ("Software") in source or binary
  33. # form and its associated documentation.
  34. # 2. Subject to the terms and conditions of this License Agreement, PSF
  35. # hereby grants Licensee a nonexclusive, royalty-free, world-wide
  36. # license to reproduce, analyze, test, perform and/or display publicly,
  37. # prepare derivative works, distribute, and otherwise use the Software
  38. # alone or in any derivative version, provided, however, that PSF's
  39. # License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
  40. # 2002-2003 Python Software Foundation; All Rights Reserved" are retained
  41. # the Software alone or in any derivative version prepared by Licensee.
  42. # 3. In the event Licensee prepares a derivative work that is based on
  43. # or incorporates the Software or any part thereof, and wants to make
  44. # the derivative work available to others as provided herein, then
  45. # Licensee hereby agrees to include in any such work a brief summary of
  46. # the changes made to the Software.
  47. # 4. PSF is making the Software available to Licensee on an "AS IS"
  48. # basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
  49. # IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
  50. # DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
  51. # FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
  52. # INFRINGE ANY THIRD PARTY RIGHTS.
  53. # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
  54. # SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
  55. # A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING THE SOFTWARE,
  56. # OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
  57. # 6. This License Agreement will automatically terminate upon a material
  58. # breach of its terms and conditions.
  59. # 7. Nothing in this License Agreement shall be deemed to create any
  60. # relationship of agency, partnership, or joint venture between PSF and
  61. # Licensee.  This License Agreement does not grant permission to use PSF
  62. # trademarks or trade name in a trademark sense to endorse or promote
  63. # products or services of Licensee, or any third party.
  64. # 8. By copying, installing or otherwise using the Software, Licensee
  65. # agrees to be bound by the terms and conditions of this License
  66. # Agreement.
  67. import SetUnicode
  68. import sys
  69. import os
  70. import re
  71. import tempfile
  72. import warnings
  73. import traceback
  74. import _winreg
  75. import Utils
  76. import Config
  77. import Process
  78. import SplashScreen
  79. # *sigh* - this is for the binary installer, and for the sake of one line
  80. # that is implicit anyway, I gave up
  81. import encodings
  82. try:
  83.     True, False
  84. except NameError:
  85.     # Maintain compatibility with Python 2.2
  86.     True, False = 1, 0
  87. # We have lots of locale woes.  The short story:
  88. # * Outlook/MAPI will change the locale on us as some predictable
  89. #   times - but also at unpredictable times.
  90. # * Python currently insists on "C" locale - if it isn't, subtle things break,
  91. #   such as floating point constants loaded from .pyc files.
  92. # * Our config files also want a consistent locale, so periods and commas
  93. #   are the same when they are read as when they are written.
  94. # So, at a few opportune times, we simply set it back.
  95. # We do it here as early as possible, before any imports that may see this
  96. #
  97. # See also [725466] Include a proper locale fix in Options.py,
  98. # assorted errors relating to strange math errors, and spambayes-dev archives,
  99. # starting July 23 2003.
  100. import locale
  101. locale.setlocale(locale.LC_NUMERIC, "C")
  102. from win32com import universal
  103. from win32com.server.exception import COMException
  104. from win32com.client import gencache, DispatchWithEvents, Dispatch
  105. import win32api
  106. import pythoncom
  107. from win32com.client import constants, getevents
  108. import win32gui, win32con, win32clipboard # for button images!
  109. from win32com.mapi import mapi, mapiutil, mapitags
  110. import RedirectStd
  111. _DEBUG=False
  112. def dbg_print(*args):    
  113.     if not _DEBUG:
  114.         return
  115.     else:
  116.         print args
  117.     
  118. # As MarkH assumed, and later found to back him up in:
  119. # http://www.slipstick.com/dev/comaddins.htm:
  120. # On building add-ins for multiple Outlook versions, Randy Byrne writes in
  121. # the microsoft.public.office.developer.com.add_ins newsgroup, "The best
  122. # practice is to compile your Add-in with OL2000 and MSO9.dll. Then your
  123. # add-in will work with both OL2000 and OL2002, and CommandBar events will
  124. # work with both versions. If you need to use any specific OL2002 or
  125. # Office 10.0 Object Library calls, you can use late binding to address
  126. # those issues. The CommandBar Events are the same in both Office
  127. # 2000 and Office XP."
  128. # So that is what we do: specify the minimum versions of the typelibs we
  129. # can work with - ie, Outlook 2000.
  130. # win32com generally checks the gencache is up to date (typelib hasn't
  131. # changed, makepy hasn't changed, etc), but when frozen we dont want to
  132. # do this - not just for perf, but because they don't always exist!
  133. bValidateGencache = not hasattr(sys, "frozen")
  134. # Generate support so we get complete support including events
  135. gencache.EnsureModule('{00062FFF-0000-0000-C000-000000000046}', 0, 9, 0,
  136.                         bForDemand=True, bValidateFile=bValidateGencache) # Outlook 9
  137. gencache.EnsureModule('{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}', 0, 2, 1,
  138.                         bForDemand=True, bValidateFile=bValidateGencache) # Office 9
  139. # We the "Addin Designer" typelib for its constants
  140. gencache.EnsureModule('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0,
  141.                         bForDemand=True, bValidateFile=bValidateGencache)
  142. # ... and also for its _IDTExtensibility2 vtable interface.
  143. universal.RegisterInterfaces('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0,
  144.                              ["_IDTExtensibility2"])
  145. try:
  146.     from win32com.client import CastTo, WithEvents
  147. except ImportError:
  148.     print "*" * 50
  149.     print "You appear to be running a win32all version pre 151, which is pretty old"
  150.     print "I'm afraid it is time to upgrade"
  151.     raise
  152. # we seem to have all the COM support we need - let's rock!
  153. # Button/Menu and other UI event handler classes
  154. class ButtonEvent:
  155.     def Init(self, handler, *args):
  156.         self.handler = handler
  157.         self.args = args
  158.     def Close(self):
  159.         self.handler = self.args = None
  160.     def OnClick(self, button, cancel):
  161.         # Callback from Outlook - locale may have changed.
  162.         locale.setlocale(locale.LC_NUMERIC, "C") # see locale comments above
  163.         self.handler(*self.args)
  164. # no ui yet, no commands
  165. def HelpAbout():
  166.     try:                           
  167.         curDir = Utils.GetCurrentDir(True)
  168.         Utils.SpawnPyOrExe(os.path.join(curDir, 'ClamWin'), ' --mode=about')        
  169.     except Exception, e:            
  170.         win32gui.MessageBox(GetWindow(), 'An error occured in ClamWin Free Antivirus About Box.n' + str(e), 'ClamWin Free Antivirus', win32con.MB_OK | win32con.MB_ICONERROR)
  171. # Helpers to work with images on buttons/toolbars.
  172. def SetButtonImage(button, fname):
  173.     # whew - http://support.microsoft.com/default.aspx?scid=KB;EN-US;q288771
  174.     # shows how to make a transparent bmp.
  175.     # Also note that the clipboard takes ownership of the handle -
  176.     # thus, we can not simply perform this load once and reuse the image.
  177.     # Hacks for the binary - we can get the bitmaps from resources.
  178.     
  179.     if not os.path.isabs(fname):
  180.         # images relative to the application path
  181.         fname = os.path.join(Utils.GetCurrentDir(False),"images", fname)
  182.         if not os.path.isfile(fname):
  183.             print "WARNING - Trying to use image '%s', but it doesn't exist" % (fname,)
  184.             return None
  185.         handle = win32gui.LoadImage(0, fname, win32con.IMAGE_BITMAP, 0, 0, win32con.LR_DEFAULTSIZE | win32con.LR_LOADFROMFILE)
  186.     win32clipboard.OpenClipboard()
  187.     win32clipboard.SetClipboardData(win32con.CF_BITMAP, handle)
  188.     win32clipboard.CloseClipboard()
  189.     button.Style = constants.msoButtonIconAndCaption
  190.     button.PasteFace()
  191.     
  192. def GetWindow():
  193.     hwnd = 0
  194.     try:
  195.         hwnd = win32gui.GetActiveWindow()
  196.     except:
  197.         hwnd = win32gui.GetForegroundWindow()   
  198.     return hwnd        
  199.     
  200. class ScanError(Exception):
  201.     def __init__(self, msg):
  202.         Exception.__init__(self, msg)        
  203. def ScanFile(path, config, attname):        
  204.     # initialise environment var TMPDIR
  205.     # for clamav    
  206.     try:
  207.         if os.getenv('TMPDIR') is None:
  208.             os.putenv('TMPDIR', tempfile.gettempdir().replace('\', '/'))
  209.                        #re.sub('([A-Za-z]):[/\\]', r'/cygdrive/1/', 
  210.                        #tempfile.gettempdir()).replace('\', '/'))
  211.         #Utils.SetCygwinTemp()
  212.     except Exception, e:
  213.         print str(e)                    
  214.             
  215.         
  216.     logfile = os.path.split(path)[0]+'\Virus Deleted by ClamWin.txt'
  217.     cmd = '--tempdir "%s"' % tempfile.gettempdir().replace('\', '/').rstrip('/')
  218.     path = path.replace('\', '/')
  219.     cmd = '--max-ratio=0 --stdout --database="%s" --log="%s" "%s"' % 
  220.             (config.Get('ClamAV', 'Database'), logfile, path)
  221.          
  222.     cmd = cmd.replace('\', '/')
  223.     cmd = '"%s" %s' % (config.Get('ClamAV', 'ClamScan'), cmd)
  224.                 
  225.     scanstatus = ''        
  226.     retcode = 1
  227.     proc = None
  228.     try:        
  229.         proc = Process.ProcessOpen(cmd)
  230.         retcode = proc.wait()   
  231.         dbg_print('scanning completed with %i' % retcode)  
  232.         # returns 100 if a damaged rar archive was found
  233.         if retcode >= 100 and retcode <= 106: 
  234.             dbg_print('damaged archive - ignoring')  
  235.             retcode = 0            
  236.     except Exception, e:
  237.         if proc is not None:
  238.             proc.close()
  239.         safe_remove(logfile)
  240.         raise ScanError('An Error occured whilst starting clamscan: %s' % str(e))
  241.         
  242.     if proc is not None:
  243.         proc.close()
  244.     if retcode == 0:
  245.         virusFound = False
  246.     # check the retrun Code
  247.     elif retcode == 1:
  248.         virusFound = True
  249.     else:
  250.         # error, raise an exception
  251.         try:
  252.             error = file(logfile, 'rt').read()
  253.             #error = re.sub('/cygdrive/([A-Za-z])/', r'1:/', error).replace('/', '\')
  254.             safe_remove(logfile)
  255.         except Exception, e:
  256.             raise ScanError('An Error occured reading clamscan report: %s' % str(e))
  257.         raise ScanError('An Error occured whilst scanning:n%s' % error)
  258.                     
  259.     # replace n's with rn's   
  260.     # so it can be shown in notepad
  261.     # also replace temp filename with real attachment name
  262.     try:
  263.         text = file(logfile, 'rt').read().replace('n', 'rn').replace(path, attname)        
  264.         file(logfile, 'wt').write(text)
  265.     except Exception, e:
  266.         safe_remove(logfile)
  267.         raise ScanError('An Error occured whilst converting clamscan report: %s' % str(e))    
  268.     return (virusFound, logfile)
  269.             
  270. # returns 0 if everything is okay, or number fo infected files                     
  271. def ScanMailItem(item, sending, added_attachments = None):     
  272.     if not item.Attachments.Count:
  273.         return 0
  274.     import win32gui, win32con   
  275.     
  276.     config_file = os.path.join(Utils.GetProfileDir(True),'ClamWin.conf')
  277.     if not os.path.isfile(config_file):
  278.         config_file = 'ClamWin.conf'                    
  279.     config = Config.Settings(config_file)    
  280.     config.Read()
  281.     
  282.     # get the virus database version from daily.cvd
  283.     # we will need it when deciding if the message should be rescanned;
  284.     
  285.     # after message is scanned the current daily.cvd version is saved;
  286.     # then when message is next accessed we compare the saved version
  287.     # with the current version and if they're different then 
  288.     # we rescan the message
  289.     
  290.     # disabled as it is of little use and causes outlook 
  291.     # to switch to RTF winmail.dat format
  292.     # when replying or forwarding a scanned message
  293.         
  294.     # virdb_ver = Utils.GetDBInfo(os.path.join(config.Get('ClamAV', 'Database'), 'daily.cvd'))[0]
  295.     # dbg_print('Daily.cvd Version: %s' % str(virdb_ver))
  296.     
  297.     # check that there are database files and display an error ballon if not
  298.     hasdb = Utils.CheckDatabase(config)      
  299.     if not hasdb:
  300.         if config.Get('UI', 'TrayNotify') == '1':
  301.             import win32gui
  302.             tray_notify_params = (('Virus Definitions Database Not Found! Please download it now.', 
  303.             -1, win32gui.NIIF_ERROR, 30000), None)
  304.             # show balloon
  305.             Utils.ShowBalloon(-1, tray_notify_params)
  306.             return 0
  307.     
  308.     dir = ''; path = ''; statusfile = ''        
  309.     infected = []; attachments = []
  310.     try:
  311.         attachments = item.Attachments            
  312.         
  313.         # see if the message has already been scanned
  314.         # disabled as it is of little use and causes outlook 
  315.   # to switch to RTF winmail.dat format
  316.         # when replying or forwarding a scanned message
  317.         #userProps = item.UserProperties    
  318.         #prop = userProps.Find('Scanned By ClamWin')
  319.         #if prop is not None:            
  320.         #    if prop.Value == virdb_ver:
  321.         #        dbg_print('ScanMailItem: Already Scanned')
  322.         #        return 0
  323.             
  324.         waitCursor = WaitCursor()              
  325.         for num in range(1, attachments.Count+1):
  326.             att = attachments.Item(num)
  327.             
  328.             # create a temporary folder to save the attachment to
  329.             dir = tempfile.mktemp()
  330.             os.mkdir(dir)
  331.             path = tempfile.mktemp(dir=dir)
  332.             dbg_print('ScanMailItem: saving attachment - ', path)
  333.             try:
  334.                 att.SaveAsFile(path)
  335.             except pythoncom.com_error, e:
  336.                 # ignore "Object not found" save errors
  337.                 # most likely the file won't be saved by outlook anyway
  338.                 hr, desc, exc, argErr = e                
  339.                 if exc[5] in (-2147221233, -2147024894, -2147467259): #0x8004010F, 0x80070002, 0x80040005
  340.                     dbg_print('error saving attachment to %s. Error: %s' % (path, str(exc)))
  341.                     safe_remove(dir)
  342.                     continue
  343.                 else:
  344.                     raise e
  345.         
  346.             try:
  347.                 attName = att.DisplayName.encode('ascii', 'replace')
  348.             except:
  349.                 attName = 'Attached File'
  350.                 
  351.             code, statusfile = ScanFile(path, config, attName)
  352.                             
  353.             # remove saved and scanned attachment  file
  354.             safe_remove(path)                              
  355.             if code == 0:            
  356.                 # no viruses found
  357.                 # remove the scan status file
  358.                 # along with temp dir
  359.                 safe_remove(statusfile, True)        
  360.             else:                            
  361.                 # virus detected
  362.                 if sending:     
  363.                     # for messages being sent display message box once and exit               
  364.                     try:                    
  365.                         msg = file(statusfile, 'rt').read()                                                
  366.                         # remove the scan status file
  367.                         # along with temp dir
  368.                         safe_remove(statusfile, True)
  369.                     except Exception, e:
  370.                         msg = 'ClamWin Free Antivirus could not scan file%sn.Error: %s' % (statusfile, str(e))                        
  371.                     win32gui.MessageBox(GetWindow(), msg, 'ClamWin Free Antivirus', win32con.MB_ICONERROR | win32con.MB_OK)
  372.                     return 1
  373.                 else:
  374.                     # bugfix [930909]
  375.                     # remove str(att.DisplayName) - was causing unicode woes
  376.                     infected.append((att, statusfile))
  377.         # remove infected attachments            
  378.         for info in infected:
  379.             dbg_print('ScanMailItem: removing attachment - ', info[0].DisplayName)
  380.             info[0].Delete()                            
  381.             
  382.         # add status files instead of the infected attachments
  383.         # can't have it in the for loop above because in some cases
  384.         # (like when a message is opened form the .msg file
  385.         # outlook saves the message when you add the attachment
  386.         # and screwes the indices            
  387.         for info in infected:
  388.             dbg_print('ScanMailItem: adding attachment - ', info[1])
  389.             attachments.Add(Source=info[1], Type=constants.olByValue)                                
  390.             # remove the scan status file
  391.             # along with temp dir
  392.             safe_remove(info[1], True)                   
  393.         # add persistent property to the message
  394.         # so we don't have to scan it in future
  395.         # we save the daily.cvd version        
  396.         # so message gets rescanned if database is updated
  397.         
  398.         # disabled as it is of little use and causes outlook 
  399.         # to switch to RTF winmail.dat format
  400.         # when replying or forwarding a scanned message
  401.         #if virdb_ver is not None:
  402.         #    prop = userProps.Add('Scanned By ClamWin', constants.olNumber, False)
  403.         #    prop.Value = virdb_ver
  404.         #    dbg_print('ScanMailItem: Saving MailItem')
  405.         #    try:
  406.      #      item.Save()           
  407.         #    except pythoncom.com_error, e:
  408.         #       # read only message store (hotmail, etc)
  409.         #        # ignore save errors
  410.         #        hr, desc, exc, argErr = e
  411.         #        if hr != -2147352567:
  412.         #            raise e
  413.         
  414.         if len(infected) > 0:                        
  415.             # for Outlook 2000 we need to display a message box in order to 
  416.             # warn a user becuase it will not change the attachments info in
  417.             # the event handlers
  418.             if int(item.Application.Version.split('.', 1)[0]) < 10:                
  419.                 msg = 'ClamWin Free Antivirus has detected a virus in the message attachments!'
  420.                 win32gui.MessageBox(GetWindow(), msg, 'Virus Detected!', win32con.MB_ICONERROR | win32con.MB_OK)                            
  421.             elif config.Get('UI', 'TrayNotify') == '1':       
  422.                 # show balloon in outlook 2002 +
  423.                 tray_notify_params = (('Virus has been detected in an email attachment! The attachment was replaced with the report file.', 1, 
  424.                                 win32gui.NIIF_ERROR, 30000),
  425.                 ('An error occured whilst scanning email message.', 0, 
  426.                 win32gui.NIIF_WARNING, 30000))
  427.                 Utils.ShowBalloon(1, tray_notify_params)
  428.             return len(infected)
  429.         else:
  430.             return 0                         
  431.     except Exception, e:
  432.         # cleanup any created files and folders
  433.         safe_remove(path)
  434.         safe_remove(statusfile)                
  435.         for info in infected:
  436.             safe_remove(info[1], True) 
  437.         safe_remove(dir)   
  438.         
  439.         # display error
  440.         win32gui.MessageBox(GetWindow(), str(e), 'ClamWin Free Antivirus', win32con.MB_OK | win32con.MB_ICONERROR)        
  441.         return True        
  442.     return len(infected)
  443. def safe_remove(path, removeLastDir = False):
  444.     try:
  445.         if os.path.isfile(path):
  446.             os.remove(path)  
  447.         else:
  448.             os.rmdir(path)  
  449.         if removeLastDir:
  450.             dir = os.path.split(path)[0]              
  451.             if os.path.exists(dir):
  452.                 os.rmdir(dir)              
  453.     except Exception, e:
  454.         print 'Could not remove file: %s. Error: %s' % (path, str(e))
  455. class WaitCursor:    
  456.     def __init__(self):
  457.         self._hCursor = win32gui.SetCursor(win32gui.LoadCursor(0, win32con.IDC_WAIT))    
  458.     def __del__(self):
  459.         win32gui.SetCursor(self._hCursor)    
  460. # Base Class for Explorer, Inspector and MailItem Outlook Objects           
  461. class ObjectWithEvents:
  462.     def Init(self, collection):    
  463.         self.collection = collection
  464.         
  465.     def OnClose(self):      
  466.         if hasattr(self.collection.host, 'DisconnectEventHandler'):
  467.             self.collection.host.DisconnectEventHandler(self.collection)   
  468.         self.collection._DoDeadObject(self)
  469.         self.collection = None        
  470.         self.close()         
  471.         # disconnect events.
  472. # EventSink Base Class for Explorers, Inspectors and MailItems Outlook Object Collections            
  473. class ObjectsEvent:
  474.     def Init(self, classWithEvents, host):
  475.         self.objects = []        
  476.         self.classWithEvents = classWithEvents        
  477.         # host object that created EventSink
  478.         self.host = host
  479.     def Close(self):        
  480.         while self.objects:
  481.             self._DoDeadObject(self.objects[0])
  482.         self.objects = None
  483.         self.close()
  484.     def _DoNewObject(self, obj):
  485.         obj = DispatchWithEvents(obj, self.classWithEvents)
  486.         obj.Init(self)
  487.         self.objects.append(obj)
  488.         return obj
  489.     def _DoDeadObject(self, obj):        
  490.         self.objects.remove(obj)
  491.         obj = None        
  492.             
  493.         
  494.         
  495. # A class that manages an "Outlook Explorer" - that is, a top-level window
  496. # All UI elements are managed here, and there is one instance per explorer.
  497. class ExplorerWithEvents(ObjectWithEvents):
  498.     def Init(self, explorers_collection):
  499.         dbg_print('ExplorerWithEvents:Init')
  500.         self.have_setup_ui = False        
  501.         self.event_handlers = []                
  502.         ObjectWithEvents.Init(self, explorers_collection)
  503.     def SetupUI(self):               
  504.         # find Help->About Outlook menu
  505.         aboutOutlook = self.CommandBars.FindControl(
  506.                             Type = constants.msoControlButton,
  507.                             Id = 927)
  508.         
  509.         if aboutOutlook is None:
  510.             return
  511.                 
  512.         popup = aboutOutlook.Parent
  513.         if popup is None:
  514.             return
  515.         # Add Help->About Clamwin menu item 
  516.         child = self._AddControl(popup,
  517.                        constants.msoControlButton,
  518.                        ButtonEvent, (HelpAbout, ),
  519.                        Caption="&About ClamWin Free Antivirus",
  520.                        TooltipText = "Shows the ClamWin About Box",
  521.                        Enabled = True,
  522.                        Visible=True,
  523.                        Tag = "ClamWin.About")
  524.         self.have_setup_ui = True
  525.                            
  526.     def _AddControl(self,
  527.                     parent, # who the control is added to
  528.                     control_type, # type of control to add.
  529.                     events_class, events_init_args, # class/Init() args
  530.                     **item_attrs): # extra control attributes.
  531.         assert item_attrs.has_key('Tag'), "Need a 'Tag' attribute!"
  532.         image_fname = None
  533.         if 'image' in item_attrs:
  534.             image_fname = item_attrs['image']
  535.             del item_attrs['image']
  536.         tag = item_attrs["Tag"]
  537.         item = self.CommandBars.FindControl(
  538.                         Type = control_type,
  539.                         Tag = tag)
  540.         if item is None:
  541.             # Now add the item itself to the parent.
  542.             try:
  543.                 item = parent.Controls.Add(Type=control_type, Temporary=True)
  544.             except pythoncom.com_error, e:               
  545.                 print "FAILED to add the toolbar item '%s' - %s" % (tag,e)
  546.                 return
  547.             if image_fname:
  548.                 # Eeek - only available in derived class.
  549.                 assert control_type == constants.msoControlButton
  550.                 but = CastTo(item, "_CommandBarButton")
  551.                 SetButtonImage(but, image_fname)
  552.             # Set the extra attributes passed in.
  553.             for attr, val in item_attrs.items():
  554.                 setattr(item, attr, val)
  555.         # didn't previously set this, and it seems to fix alot of problem - so
  556.         # we set it for every object, even existing ones.
  557.         item.OnAction = "<!" + OutlookAddin._reg_progid_ + ">"
  558.         # Hook events for the item, but only if we haven't already in some
  559.         # other explorer instance.
  560.         if events_class is not None and tag not in self.collection.button_event_map:
  561.             item = DispatchWithEvents(item, events_class)
  562.             item.Init(*events_init_args)
  563.             # We must remember the item itself, else the events get disconnected
  564.             # as the item destructs.
  565.             self.collection.button_event_map[tag] = item
  566.         return item
  567.     # The Outlook event handlers
  568.     def OnActivate(self): 
  569.         dbg_print('ExplorerWithEvents:OnActivate')
  570.         # See comments for OnNewExplorer below.
  571.         # *sigh* - OnActivate seems too early too for Outlook 2000,
  572.         # but Outlook 2003 seems to work here, and *not* the folder switch etc
  573.         # Outlook 2000 crashes when a second window is created and we use this
  574.         # event
  575.         # OnViewSwitch however seems useful, so we ignore this.
  576.         pass
  577.     def OnSelectionChange(self):         
  578.         dbg_print('ExplorerWithEvents:OnSelectionChange')
  579.         # See comments for OnNewExplorer below.
  580.         if not self.have_setup_ui:
  581.             self.SetupUI()
  582.         if self.IsPaneVisible(constants.olPreview) and 
  583.             self.Selection.Count == 1:            
  584.             item = self.Selection.Item(1)
  585.             if item.Class == constants.olMail and item.Sent:                                                  
  586.                 ScanMailItem(item, False)                       
  587.         
  588.     def OnClose(self):                                
  589.         dbg_print('ExplorerWithEvents:OnClose')
  590.         for event_handler in self.event_handlers:        
  591.             event_handler.Close()
  592.         self.event_handler = []        
  593.         ObjectWithEvents.OnClose(self)
  594.     def OnBeforeFolderSwitch(self, new_folder, cancel):
  595.         dbg_print('ExplorerWithEvents:OnBeforeFolderSwitch')
  596.         pass
  597.     def OnFolderSwitch(self):        
  598.         # Yet another work-around for our event timing woes.  This may
  599.         # be the first event ever seen for this explorer if, eg,
  600.         # "Outlook Today" is the initial Outlook view.
  601.         dbg_print('ExplorerWithEvents:OnFolderSwitch')
  602.         if not self.have_setup_ui:
  603.             self.SetupUI()
  604.     def OnBeforeViewSwitch(self, new_view, cancel):
  605.         dbg_print('ExplorerWithEvents:OnBeforeViewSwitch')
  606.         pass
  607.     def OnViewSwitch(self):        
  608.         dbg_print('ExplorerWithEvents:OnViewSwitch')
  609.         if not self.have_setup_ui:
  610.             self.SetupUI()
  611.             
  612.     
  613. # Events from our "Explorers" collection (not an Explorer instance)
  614. class ExplorersEvent(ObjectsEvent):
  615.     def Init(self, olAddin):
  616.         dbg_print('ExplorersEvent:Init')
  617.         self.button_event_map = {}
  618.         ObjectsEvent.Init(self, ExplorerWithEvents, olAddin)    
  619.     def _DoDeadObject(self, obj):
  620.         dbg_print('ExplorersEvent:_DoDeadObject')
  621.         ObjectsEvent._DoDeadObject(self, obj)
  622.         if len(self.objects)==0:            
  623.             # No more explorers - disconnect all events.
  624.             # (not doing this causes shutdown problems)
  625.             for tag, button in self.button_event_map.items():
  626.                 closer = getattr(button, "Close", None)
  627.                 if closer is not None:
  628.                     closer()
  629.             self.button_event_map = {}            
  630.     def OnNewExplorer(self, explorer):
  631.         # NOTE - Outlook has a bug, as confirmed by many on Usenet, in
  632.         # that OnNewExplorer is too early to access the CommandBars
  633.         # etc elements. We hack around this by putting the logic in
  634.         # the first OnActivate call of the explorer itself.
  635.         # Except that doesn't always work either - sometimes
  636.         # OnActivate will cause a crash when selecting "Open in New Window",
  637.         # so we tried OnSelectionChanges, which works OK until there is a
  638.         # view with no items (eg, Outlook Today) - so at the end of the
  639.         # day, we can never assume we have been initialized!        
  640.         dbg_print('ExplorersEvent:OnNewExplorer')
  641.         self._DoNewObject(explorer)
  642. # A class that manages an "Outlook Inspector" - that is, a message or contact window
  643. class InspectorWithEvents(ObjectWithEvents):
  644.     def Init(self, inspectors_collection):               
  645.         dbg_print('InspectorWithEvents:Init')
  646.         self.event_handlers = []        
  647.         item = self.CurrentItem        
  648.         if item.Class == constants.olMail:                                            
  649.             # Create EventHandler for MailItem
  650.             mailitem_events = WithEvents(item, MailItemsEvent)
  651.             mailitem_events.Init(self)        
  652.             mailitem_events._DoNewObject(item)            
  653.             self.event_handlers.append(mailitem_events)            
  654.         ObjectWithEvents.Init(self, inspectors_collection)
  655.     
  656.     # The Outlook event handlers
  657.     def OnActivate(self):
  658.         dbg_print('InspectorWithEvents:OnActivate')
  659.         pass                                                     
  660.         
  661.     def OnClose(self):     
  662.         dbg_print('InspectorWithEvents:OnClose')
  663.         for handler in self.event_handlers:        
  664.             handler.Close()
  665.         self.event_handlers = []
  666.         ObjectWithEvents.OnClose(self)
  667.         
  668.     def DisconnectEventHandler(self, obj):
  669.         dbg_print('InspectorWithEvents:DisconnectEventHandler')
  670.         obj.close()
  671.         self.event_handlers.remove(obj)      
  672.         
  673. # Events from our "Inspectors" collection
  674. class InspectorsEvent(ObjectsEvent):
  675.     def Init(self, host):          
  676.         dbg_print('InspectorsEvent:Init')
  677.         ObjectsEvent.Init(self, InspectorWithEvents, host)  
  678.         
  679.     def OnNewInspector(self, inspector):   
  680.         dbg_print('InspectorsEvent:OnNewInspector')
  681.         self._DoNewObject(inspector)  
  682.         
  683. # A class that manages an "Outlook MailItem" - that is, a message 
  684. class MailItemWithEvents(ObjectWithEvents):
  685.     def Init(self, items_collection):      
  686.         dbg_print('MailItemWithEvents:Init')
  687.         ObjectWithEvents.Init(self, items_collection)
  688.         self._scanned = False
  689.         self._close_inspector = False
  690.         self._num_infected = 0
  691.         
  692.     def OnClose(self, cancel):      
  693.         dbg_print('MailItemWithEvents:OnClose')
  694.         host = self.collection.host
  695.         ObjectWithEvents.OnClose(self)
  696.         if self._close_inspector:
  697.             # need to disconnect Inspectors Collection ebent handler because
  698.             # Outlook 2000 doesn't fire Inspector:Close event
  699.             # for sent messages and remains hanging around after exit
  700.             dbg_print('MailItemWithEvents:OnClose host.OnClose()')                    
  701.             host.OnClose()
  702.         
  703.         
  704.     def OnRead(self):  
  705.         dbg_print('MailItemWithEvents:OnRead')
  706.         # OnOpen event is not always fired
  707.         # only when a user double-clicks the message
  708.         # so we scan here and reinitialize attachmets
  709.         if self.Sent and not self._scanned:             
  710.             dbg_print('MailItemWithEvents:OnRead scanning')
  711.             self._scanned = True
  712.             self._num_infected = ScanMailItem(self, False)  
  713.             
  714.         
  715.     def OnOpen(self, cancel):  
  716.         dbg_print('MailItemWithEvents:OnOpen')
  717.         if not self._scanned and self.Sent :      
  718.             # in case OnRead has not been called
  719.             dbg_print('MailItemWithEvents:OnOpen scanning')
  720.             self._scanned = True
  721.             self._num_infected = ScanMailItem(self, False)  
  722.         elif  self._num_infected:
  723.             # a bit of trickery here - remove and reinsert attachments 
  724.             # so that bloody outlook shows our changes
  725.             dbg_print('MailItemWithEvents:OnOpen Scanned Earlier')
  726.             try:
  727.                 saved_attachments = []            
  728.                 for i in range(self.Attachments.Count - self._num_infected + 1, self.Attachments.Count + 1):
  729.                     att = self.Attachments.Item(i)
  730.                     # save attachment to a temp file
  731.                     name = att.DisplayName
  732.                     dir = tempfile.mktemp()
  733.                     os.mkdir(dir)
  734.                     filename = os.path.join(dir, name)
  735.                     att.SaveAsFile(filename)
  736.                     saved_attachments.append((att, filename))
  737.             
  738.                 for saved in saved_attachments:
  739.                     saved[0].Delete()
  740.                 
  741.                 for saved in saved_attachments:
  742.                     self.Attachments.Add(Source=saved[1], Type=constants.olByValue)
  743.                 for saved in saved_attachments:
  744.                     safe_remove(saved[1], True)                
  745.                 saved_attachments = []    
  746.                 
  747.                 try:
  748.                    self.Save()           
  749.                 except pythoncom.com_error, e:
  750.                     # read only message store (hotmail, etc
  751.                     # ignore save errors
  752.                     hr, desc, exc, argErr = e
  753.                     if hr != -2147352567:
  754.                         raise e                    
  755.             except Exception, e:
  756.                 for saved in saved_attachments:
  757.                     safe_remove(saved[1], True)
  758.                 msg = 'ClamWin Free Antivirus could not replace an attachment. Error: %s' % str(e)
  759.                 win32gui.MessageBox(GetWindow(), msg, 'ClamWin Free Antivirus!', win32con.MB_ICONERROR | win32con.MB_OK)                                                                                      
  760.     
  761.     def OnWrite(self, cancel):
  762.         dbg_print('MailItemWithEvents:OnWrite')
  763.         # disabled as it is of little use and causes outlook 
  764.         # to switch to RTF winmail.dat format
  765.         # when replying or forwarding a scanned message
  766.         #if self.Sent and not self._scanned:
  767.         #    prop = self.UserProperties.Find('Scanned By ClamWin')
  768.         #    if prop is not None:
  769.         #        prop.Delete()
  770.                                 
  771.     def OnSend(self, cancel):        
  772.         dbg_print('MailItemWithEvents:OnSend')
  773.         virus_found = (ScanMailItem(self, True) > 0)
  774.         cancel = virus_found
  775.         # disconnect Inspectors Collection event handler
  776.         # in OnClose
  777.         self._close_inspector = True
  778.         return not cancel
  779.         
  780. # Events from our "MailItems" collection
  781. class MailItemsEvent(ObjectsEvent):
  782.     def Init(self, host):  
  783.         dbg_print('MailItemsEvent:Init')
  784.         ObjectsEvent.Init(self, MailItemWithEvents, host)  
  785.         
  786.     def OnItemAdd(self, mailitem):               
  787.         dbg_print('MailItemsEvent:OnItemAdd')
  788.         self._DoNewObject(mailitem)  
  789.                     
  790. # The outlook Plugin COM object itself.
  791. class OutlookAddin(ObjectsEvent):
  792.     _com_interfaces_ = ['_IDTExtensibility2']
  793.     _public_methods_ = []
  794. #    _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
  795.     _reg_clsid_ = "{E77FA584-1433-4af3-800D-AEC49BCCCB11}"
  796.     _reg_progid_ = "ClamWin.OutlookAddin"
  797.     _reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
  798.     def __init__(self):        
  799.         self.application = None       
  800.         # check the debug flag iun the registry
  801.         global _DEBUG
  802.         try:
  803.             subkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\ClamWin')            
  804.             _DEBUG = int(_winreg.QueryValueEx(subkey, "Debug")[0])==1            
  805.         except:
  806.             _DEBUG = False                
  807.     def OnConnection(self, application, connectMode, addin, custom):            
  808.         dbg_print('OutlookAddin:OnConnection')
  809.         # Handle failures during initialization so that we are not
  810.         # automatically disabled by Outlook.
  811.         locale.setlocale(locale.LC_NUMERIC, "C") # see locale comments above
  812.         try:
  813.             self.application = application
  814.             self.event_handlers = [] # create at OnStartupComplete            
  815.             if connectMode == constants.ext_cm_AfterStartup:
  816.                 # We are being enabled after startup, which means we don't get
  817.                 # the 'OnStartupComplete()' event - call it manually so we
  818.                 # bootstrap code that can't happen until startup is complete.
  819.                 self.OnStartupComplete(None)
  820.         except:
  821.             print "Error connecting to Outlook!"
  822.             traceback.print_exc()
  823.             print "There was an error initializing the ClamWin addinrnrn"
  824.                 "Please re-start Outlook and try again."
  825.     def OnStartupComplete(self, custom):
  826.         dbg_print('OutlookAddin:OnStartupComplete')
  827.         Utils.CreateProfile()
  828.         # display SplashScreen
  829.         try:            
  830.             splash = os.path.join(Utils.GetCurrentDir(False), "img\Splash.bmp")
  831.             SplashScreen.ShowSplashScreen(splash, 5)
  832.         except Exception, e:           
  833.             print "An error occured whilst displaying the spashscreen %s. Error: %s." % (splash, str(e))
  834.         # Setup all our filters and hooks.  We used to do this in OnConnection,
  835.         # but a number of 'strange' bugs were reported which I suspect would
  836.         # go away if done during this later event - and this later place
  837.         # does seem more "correct" than the initial OnConnection event.
  838.         # Toolbar and other UI stuff must be setup once startup is complete.
  839.         explorers = self.application.Explorers
  840.         # and Explorers events so we know when new explorers spring into life.
  841.         explorers_events = WithEvents(explorers, ExplorersEvent)
  842.         explorers_events.Init(self)        
  843.         # And hook our UI elements to all existing explorers
  844.         for i in range(explorers.Count):
  845.             explorer = explorers.Item(i+1)
  846.             explorer = explorers_events._DoNewObject(explorer)
  847.             explorer.OnFolderSwitch()
  848.         self.event_handlers.append(explorers_events)
  849.         
  850.         inspectors = self.application.Inspectors
  851.         # and Inspectors events so we know when new inspectors spring into life.
  852.         inspectors_events = WithEvents(inspectors, InspectorsEvent)
  853.         inspectors_events.Init(self)        
  854.         # And elements to all existing inspectors
  855.         for i in range(inspectors.Count):
  856.             inspector = inspectors.Item(i+1)
  857.             inspector = inspectors_events._DoNewObject(inspector)
  858.         self.event_handlers.append(inspectors_events)
  859.         # release application object otherwise Outlook2003 won't
  860.         # shutdown if we are running in out-of-process EXE
  861.         self.application = None    
  862.         
  863.     def OnDisconnection(self, mode, custom):
  864.         dbg_print('ClamWin - Disconnecting from Outlook')
  865.         for handler in self.event_handlers:        
  866.             handler.Close()
  867.         self.event_handlers = []        
  868.         self.application = None
  869.         print "Addin terminating: %d COM client and %d COM servers exist." 
  870.               % (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
  871.         try:
  872.             # will be available if "python_d addin.py" is used to
  873.             # register the addin.
  874.             total_refs = sys.gettotalrefcount() # debug Python builds only
  875.             print "%d Python references exist" % (total_refs,)
  876.         except AttributeError:
  877.             pass
  878.         
  879.     def OnAddInsUpdate(self, custom):
  880.         pass
  881.     def OnBeginShutdown(self, custom):
  882.         dbg_print('OutlookAddin:OnBeginShutdown')
  883.         pass
  884. def _DoRegister(klass, root):
  885.     key = _winreg.CreateKey(root,
  886.                             "Software\Microsoft\Office\Outlook\Addins")
  887.     subkey = _winreg.CreateKey(key, klass._reg_progid_)
  888.     _winreg.SetValueEx(subkey, "CommandLineSafe", 0, _winreg.REG_DWORD, 0)
  889.     _winreg.SetValueEx(subkey, "LoadBehavior", 0, _winreg.REG_DWORD, 3)
  890.     _winreg.SetValueEx(subkey, "Description", 0, _winreg.REG_SZ, "ClamWin Free Antivirus")
  891.     _winreg.SetValueEx(subkey, "FriendlyName", 0, _winreg.REG_SZ, "ClamWin Free Antivirus")
  892. # Note that Addins can be registered either in HKEY_CURRENT_USER or
  893. # HKEY_LOCAL_MACHINE.  If the former, then:
  894. # * Only available for the user that installed the addin.
  895. # * Appears in the 'COM Addins' list, and can be removed by the user.
  896. # If HKEY_LOCAL_MACHINE:
  897. # * Available for every user who uses the machine.  This is useful for site
  898. #   admins, so it works with "roaming profiles" as users move around.
  899. # * Does not appear in 'COM Addins', and thus can not be disabled by the user.
  900. # Note that if the addin is registered in both places, it acts as if it is
  901. # only installed in HKLM - ie, does not appear in the addins list.
  902. # For this reason, the addin can be registered in HKEY_LOCAL_MACHINE
  903. # by executing 'regsvr32 /i:hkey_local_machine outlook_addin.dll'
  904. # (or 'python addin.py hkey_local_machine' for source code users.
  905. # Note to Binary Builders: You need py2exe dated 8-Dec-03+ for this to work.
  906. # Called when "regsvr32 /i:whatever" is used.  We support 'hkey_local_machine'
  907. # and hkey_current_user
  908. def DllInstall(bInstall, cmdline):
  909.     klass = OutlookAddin
  910.     if bInstall:
  911.         # Unregister the old installation, if one exists.
  912.         DllUnregisterServer()
  913.         rootkey = None
  914.         if cmdline.lower().find('hkey_local_machine')>=0:                    
  915.             rootkey = _winreg.HKEY_LOCAL_MACHINE
  916.             print "Registering (in HKEY_LOCAL_MACHINE)..."
  917.         elif cmdline.lower().find('hkey_current_user')>=0:                    
  918.             rootkey = _winreg.HKEY_CURRENT_USER
  919.             print "Registering (in HKEY_CURRENT_USER)..."
  920.         if rootkey is not None:
  921.             # Don't catch exceptions here - if it fails, the Dll registration
  922.             # must fail.
  923.             _DoRegister(klass, _winreg.HKEY_LOCAL_MACHINE)
  924.             print "Registration Complete"            
  925. def DllRegisterServer():
  926.     klass = OutlookAddin
  927.     
  928.     # *sigh* - we used to *also* register in HKLM, but as above, this makes
  929.     # things work like we are *only* installed in HKLM.  Thus, we explicitly
  930.     # remove the HKLM registration here (but it can be re-added - see the
  931.     # notes above.)
  932.     try:
  933.         _winreg.DeleteKey(_winreg.HKEY_LOCAL_MACHINE,
  934.                           "Software\Microsoft\Office\Outlook\Addins\" 
  935.                           + klass._reg_progid_)
  936.     except WindowsError:
  937.         pass
  938.     _DoRegister(klass, _winreg.HKEY_CURRENT_USER)
  939.     print "Registration complete."
  940. def DllUnregisterServer():
  941.     klass = OutlookAddin
  942.     # Try to remove the HKLM version.
  943.     try:
  944.         _winreg.DeleteKey(_winreg.HKEY_LOCAL_MACHINE,
  945.                           "Software\Microsoft\Office\Outlook\Addins\" 
  946.                           + klass._reg_progid_)
  947.     except WindowsError:
  948.         pass
  949.     # and again for current user.
  950.     try:
  951.         _winreg.DeleteKey(_winreg.HKEY_CURRENT_USER,
  952.                           "Software\Microsoft\Office\Outlook\Addins\" 
  953.                           + klass._reg_progid_)
  954.     except WindowsError:
  955.         pass
  956. if __name__ == '__main__':
  957.     if '--register' in sys.argv[1:] or 
  958.        '--unregister' in sys.argv[1:] or 
  959.        not hasattr(sys, "frozen"):
  960.         import win32com.server.register
  961.         win32com.server.register.UseCommandLine(OutlookAddin)    
  962.         if "--unregister" in sys.argv[1:]:
  963.             DllUnregisterServer()
  964.         else:
  965.             DllRegisterServer()
  966.         # Support 'hkey_local_machine' on the commandline, to work in
  967.         # the same way as 'regsvr32 /i:hkey_local_machine' does.
  968.         # regsvr32 calls it after DllRegisterServer, (and our registration
  969.         # logic relies on that) so we will too.
  970.         for a in sys.argv[1:]:
  971.             if a.lower()=='hkey_local_machine':
  972.                 DllInstall(True, 'hkey_local_machine')
  973.     elif hasattr(sys, "frozen"):
  974.         if sys.frozen == "exe":
  975.             # start the exe server.
  976.             from win32com.server import localserver
  977.             localserver.main()
  978. ##        #Some MAPI code, just in case we need it 
  979. ##        try:
  980. ##            iMsg = self.MAPIOBJECT.QueryInterface(mapi.IID_IMessage)
  981. ##            for num in range (0, self.Attachments.Count):                        
  982. ##                print "Deleting Attachment - ", num
  983. ##                iMsg.DeleteAttach(num, 0, None, 0)
  984. ##                deleted = True
  985. ##            if deleted:
  986. ##                iMsg.SaveChanges(0)
  987. ##            iMsg = None
  988. ##        except Exception, e:
  989. ##            print "MailItem OnRead Exception: ", str(e)
  990. ##for i in range(attachments.Count - num_infected+1, attachments.Count+1):
  991. ##    try:            
  992. ##        iMsg = item.MAPIOBJECT.QueryInterface(mapi.IID_IMessage)                                
  993. ##        print 'getting att', i               
  994. ##        att = attachments.Item(i)
  995. ##        print 'got att'
  996. ##        iAtt = att.MAPIOBJECT.QueryInterface(mapi.IID_IMAPIProp)                                
  997. ##        print 'got iAtt'
  998. ##        iAtt.SetProps([(mapitags.PR_ATTACH_FILENAME, att.DisplayName),])
  999. ##        print 'set PR_ATTACH_FILENAME'
  1000. ##        iAtt.SetProps([(mapitags.PR_ATTACH_LONG_FILENAME, att.DisplayName),])
  1001. ##        print 'set PR_ATTACH_LONG_FILENAME'
  1002. ##        iAtt.SaveChanges(mapi.KEEP_OPEN_READWRITE)
  1003. ##        print 'Saved Changes'
  1004. ##        iAtt = None; att = None
  1005. ##    except Exception, e:
  1006. ##        print "Could not set attachment display name: ", str(e)
  1007. ##