Code

Fixed a backlog of bug reports, and worked on python 2.3 compatibility:
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 6 Feb 2003 05:43:49 +0000 (05:43 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 6 Feb 2003 05:43:49 +0000 (05:43 +0000)
- fixed templating filter function arguments (sf bug 678911)
- fixed multiselect in searching (sf bug 676874)
- fixed parsing of content-disposition filenames (sf bug 675116)
- added 'h' to roundup-server optarg list (sf bug 674070)
- fixed doc for db.history in anydbm and rdbms_common (sf bug 679221)
- fixed timelog example so it handles new issues (sf bug 678908)
- handle missing os.fork() (sf bug 681046)
- fixed roundup-reminder (sf bug 681042)
- fixed int assumptions about Number values (sf bug 677762)
- added warning filter for "FutureWarning: hex/oct constants > sys.maxint will
  return positive values..." (literal 0xffff0000 in portalocker.py)
- fixed ZPT code generating SyntaxWarning for assignment to None

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1487 57a73879-2fb5-44c3-a270-3262357dd7e2

17 files changed:
CHANGES.txt
COPYING.txt
README.txt
doc/customizing.txt
doc/index.txt
roundup/admin.py
roundup/backends/back_anydbm.py
roundup/backends/locking.py
roundup/backends/rdbms_common.py
roundup/cgi/PageTemplates/TALES.py
roundup/cgi/TAL/TALInterpreter.py
roundup/cgi/client.py
roundup/cgi/templating.py
roundup/mailgw.py
roundup/scripts/roundup_server.py
scripts/roundup-reminder
test/test_mailgw.py

index 07990c7704ec1c658c9eae4a1001a553a698f445..937b7a5ed7cdc10a1b56b98ef0a85085c6f4fd95 100644 (file)
@@ -30,12 +30,25 @@ are given with the most recent entry first.
 - more proper sorting/grouping on mulitilink properties. Sorting is performed
   not only by number of links, but also by links itself. This makes usable
   grouping e.g. by topic multilink
-
-2003-??-?? 0.5.5
-- fixed rdbms searching by ID (sf bug 666615)
-- detect corrupted index and raise semi-useful exception (sf bug 666767)
-- open server logfile unbuffered
-- revert StringHTMLProperty to not hyperlink text by default
+- fixed templating filter function arguments (sf bug 678911)
+- fixed multiselect in searching (sf bug 676874)
+- fixed parsing of content-disposition filenames (sf bug 675116)
+- added 'h' to roundup-server optarg list (sf bug 674070)
+- fixed doc for db.history in anydbm and rdbms_common (sf bug 679221)
+- fixed timelog example so it handles new issues (sf bug 678908)
+- handle missing os.fork() (sf bug 681046)
+- fixed roundup-reminder (sf bug 681042)
+- fixed int assumptions about Number values (sf bug 677762)
+- added warning filter for "FutureWarning: hex/oct constants > sys.maxint will
+  return positive values..." (literal 0xffff0000 in portalocker.py)
+- fixed ZPT code generating SyntaxWarning for assignment to None
+
+
+2003-??-?? 0.5.6
+- changes appear in 0.6.0
+
+2003-01-24 0.5.5
+- changes appear in 0.6.0
 
 
 2003-01-10 0.5.4
index f518e8a1753d1a7a7f6ce1bc41311b82e9db8df6..0f06922c74cc956d39bc6a6f31beec96d6d48d22 100644 (file)
@@ -1,3 +1,6 @@
+Roundup Licensing
+-----------------
+
 Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,11 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 
+
 Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
 
 This module is free software, and you may redistribute it and/or modify
-under the same terms as Python, so long as this copyright message and
-disclaimer are retained in their original form.
+under the same terms as Python 2.1 (the PSF LICENSE AGREEMENT), so long
+as this copyright message and disclaimer are retained in their original form.
 
 IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
 DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
@@ -35,6 +39,22 @@ FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
 BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
-The stylesheet included with this package has been copied from the Zope
-management interface and presumably belongs to Digital Creations.
+
+PageTemplates Licensing
+-----------------------
+
+Portions of this code (roundup.cgi.PageTemplates, roundup.cgi.TAL and
+roundup.cgi.ZTUtils) have been copied from Zope. They have been modified in
+the following manner:
+
+- removal of unit tests, Zope-specific code and support files from 
+  PageTemplates: PageTemplateFile.py, ZPythonExpr.py, ZRPythonExpr.py,
+  ZopePageTemplate.py, examples, help, tests, CHANGES.txt, HISTORY.txt,
+  version.txt and www. From TAL: DummyEngine.py, HISTORY.txt, CHANGES.txt,
+  benchmark, driver.py, markbench.py, ndiff.py, runtest.py, setpath.py,
+  tests and timer.py. From ZTUtils: SimpleTree.py, Zope.py, CHANGES.txt and
+  HISTORY.txt.
+- editing to remove dependencies on Zope modules (see files for change notes)
+
+The license for this code is in doc/ZPL.txt.
 
index 808be60a4a0ecd75868012bfa756ac55fb0f51de..3a4e8337b806c632b1862cc1c068c0307ba8c299 100644 (file)
@@ -16,38 +16,5 @@ See the index.txt file in the "doc" directory.
 License
 =======
 
-Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
-This module is free software, and you may redistribute it and/or modify
-under the same terms as Python, so long as this copyright message and
-disclaimer are retained in their original form.
-
-IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
-DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OF THIS CODE, EVEN IF BIZAR SOFTWARE PTY LTD HAS BEEN ADVISED
-OF THE POSSIBILITY OF SUCH DAMAGE.
-
-BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
-BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
-BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
-SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
-
-
-PageTemplates Licensing
------------------------
-
-Portions of this code (roundup.cgi.PageTemplates, roundup.cgi.TAL and
-roundup.cgi.ZTUtils) have been copied from Zope. They have been modified in
-the following manner:
-
-- removal of unit tests, Zope-specific code and support files from 
-  PageTemplates: PageTemplateFile.py, ZPythonExpr.py, ZRPythonExpr.py,
-  ZopePageTemplate.py, examples, help, tests, CHANGES.txt, HISTORY.txt,
-  version.txt and www. From TAL: DummyEngine.py, HISTORY.txt, CHANGES.txt,
-  benchmark, driver.py, markbench.py, ndiff.py, runtest.py, setpath.py,
-  tests and timer.py. From ZTUtils: SimpleTree.py, Zope.py, CHANGES.txt and
-  HISTORY.txt.
-- editing to remove dependencies on Zope modules (see files for change notes)
-
-The license for this code is in doc/ZPL.txt.
+See COPYING.txt
 
index eb6b8adceeed1c70a49388a0f576c9aea12d2d35..a1b46ac3172f61e75c3bedf0cab5843160cf18f0 100644 (file)
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.72 $
+:Version: $Revision: 1.73 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -2558,6 +2558,7 @@ able to give a summary of the total time spent on a particular issue.
         ''' 
         actions = client.Client.actions + (
             ('edit_with_timelog', 'timelogEditAction'),
+            ('new_with_timelog', 'timelogEditAction'),
         )
 
         def timelogEditAction(self):
@@ -2587,7 +2588,10 @@ able to give a summary of the total time spent on a particular issue.
                     self.form.list.append(MiniFieldStorage('times', entry))
 
             # punt to the normal edit action
-            return self.editItemAction()
+            if self.nodeid:
+                return self.editItemAction()
+            else:
+                return self.newItemAction()
    
    you add this code to your Client class in your tracker's ``interfaces.py``
    file. Locate the section that looks like::
@@ -2621,14 +2625,15 @@ able to give a summary of the total time spent on a particular issue.
         <input type="submit" name="submit" value="Submit Changes">
       </tal:block>
       <tal:block tal:condition="not:context/id">
-        <input type="hidden" name=":action" value="new">
+        <input type="hidden" name=":action" value="new_with_timelog">
         <input type="submit" name="submit" value="Submit New Issue">
       </tal:block>
      </td>
     </tr>
 
    The important change is setting the action to "edit_with_timelog" for
-   edit operations (where the item exists)
+   edit operations (where the item exists) and "new_with_timelog" for
+   creations operations.
 
 6. We want to display a total of the time log times that have been accumulated
    for an issue. To do this, we'll need to actually write some Python code,
index 4774962267d4786889f4c6d2b92479498e802c81..37ad702e7cb1464680ede6cde5897f9586ff8326 100644 (file)
@@ -55,6 +55,7 @@ Duncan Booth,
 Seb Brezel,
 Titus Brown,
 Roch'e Compaan,
+Hernan Martinez Foffani,
 Engelbert Gruber,
 Juergen Hermann,
 Tobias Hunger,
index 88cbace2c8e6e3bb253a4d1ec896c38eadd70d07..8dfde0d0d23bf70579f6a0cde1412a970a825169 100644 (file)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: admin.py,v 1.35 2002-10-03 06:56:28 richard Exp $
+# $Id: admin.py,v 1.36 2003-02-06 05:43:47 richard Exp $
 
 '''Administration commands for maintaining Roundup trackers.
 '''
@@ -497,7 +497,7 @@ Command help:
                 elif isinstance(proptype, hyperdb.Boolean):
                     props[key] = value.lower() in ('yes', 'true', 'on', '1')
                 elif isinstance(proptype, hyperdb.Number):
-                    props[key] = int(value)
+                    props[key] = float(value)
 
             # try the set
             try:
@@ -682,7 +682,7 @@ Command help:
             elif isinstance(proptype, hyperdb.Boolean):
                 props[propname] = value.lower() in ('yes', 'true', 'on', '1')
             elif isinstance(proptype, hyperdb.Number):
-                props[propname] = int(value)
+                props[propname] = float(value)
 
         # check for the key property
         propname = cl.getkey()
index c106f2f2292a715e5dc5b0f9688a54402956bd2a..681698b389d29fe04aef13b101c938d224745e18 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.99 2003-02-03 11:14:16 kedder Exp $
+#$Id: back_anydbm.py,v 1.100 2003-02-06 05:43:47 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -1347,7 +1347,7 @@ class Class(hyperdb.Class):
 
         The returned list contains tuples of the form
 
-            (date, tag, action, params)
+            (nodeid, date, tag, action, params)
 
         'date' is a Timestamp object specifying the time of the change and
         'tag' is the journaltag specified when the database was opened.
index 6c64618a1a0960106f9e3a4efd53d0ba877ccd16..634c14c79aee0fb57dfb713fe913db56931fb8bd 100644 (file)
@@ -19,7 +19,7 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-# $Id: locking.py,v 1.2 2002-09-10 00:11:50 richard Exp $
+# $Id: locking.py,v 1.3 2003-02-06 05:43:47 richard Exp $
 
 '''This module provides a generic interface to acquire and release
 exclusive access to a file.
@@ -27,6 +27,11 @@ exclusive access to a file.
 It should work on Unix and Windows.
 '''
 
+import warnings
+warnings.filterwarnings("ignore",
+    r'hex/oct constants > sys\.maxint .*', FutureWarning,
+    'portalocker', 0)
+
 import portalocker
 
 def acquire_lock(path, block=1):
index 30fd3712fe5221a99941bf3c842fbcba1e3d1d20..002f5a721fceaac5f8901343d8bca066fa6873e6 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.29 2003-01-15 22:17:19 kedder Exp $
+# $Id: rdbms_common.py,v 1.30 2003-02-06 05:43:47 richard Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -1540,7 +1540,7 @@ class Class(hyperdb.Class):
 
         The returned list contains tuples of the form
 
-            (date, tag, action, params)
+            (nodeid, date, tag, action, params)
 
         'date' is a Timestamp object specifying the time of the change and
         'tag' is the journaltag specified when the database was opened.
index 229064c79400243730bf00e90245906fba84e843..ef1343663370d77eea8fe4e6c8c67659bd432635 100644 (file)
@@ -19,7 +19,7 @@ Modified for Roundup 0.5 release:
 - changed imports to import from roundup.cgi
 """
 
-__version__='$Revision: 1.4 $'[11:-2]
+__version__='$Revision: 1.5 $'[11:-2]
 
 import re, sys
 from roundup.cgi import ZTUtils
@@ -228,7 +228,7 @@ class Context:
     evaluateValue = evaluate
     evaluateBoolean = evaluate
 
-    def evaluateText(self, expr, None=None):
+    def evaluateText(self, expr):
         text = self.evaluate(expr)
         if text is Default or text is None:
             return text
index be6d11e6b85f6b67f8349cf4e2ddd451cf05c63e..341b8612b378899ed47a02ad34662eb7ff8b1a7a 100644 (file)
@@ -168,7 +168,7 @@ class TALInterpreter:
 
     bytecode_handlers = {}
 
-    def interpret(self, program, None=None):
+    def interpret(self, program):
         oldlevel = self.level
         self.level = oldlevel + 1
         handlers = self.dispatch
index 108e52cf4a425b42fbb2811dbf26c897beb4dbe4..aa9a3a6cd84537e11513cd9e293ad33c4c470cd0 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.75 2003-02-03 00:01:44 richard Exp $
+# $Id: client.py,v 1.76 2003-02-06 05:43:47 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -945,7 +945,15 @@ class Client:
         props = self.db.classes[self.classname].getprops()
         for key in self.form.keys():
             if not props.has_key(key): continue
-            if not self.form[key].value: continue
+            if isinstance(self.form[key], type([])):
+                # search for at least one entry which is not empty
+                for minifield in self.form[key]:
+                    if minifield.value:
+                        break
+                else:
+                    continue
+            else:
+                if not self.form[key].value: continue
             self.form.value.append(cgi.MiniFieldStorage(':filter', key))
 
         # handle saving the query params
@@ -1369,7 +1377,7 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
             elif isinstance(proptype, hyperdb.Boolean):
                 value = value.lower() in ('yes', 'true', 'on', '1')
             elif isinstance(proptype, hyperdb.Number):
-                value = int(value)
+                value = float(value)
         else:
             # if we're creating, just don't include this property
             if not nodeid:
index 145b3b68b66265c13bb97dc9755f6eb540d7642b..d69f68e937d14dcc65d6441c220da82dde05417d 100644 (file)
@@ -391,6 +391,10 @@ class HTMLClass(HTMLPermissions):
             filterspec = request.filterspec
             sort = request.sort
             group = request.group
+        else:
+            filterspec = {}
+            sort = (None,None)
+            group = (None,None)
         if self.classname == 'user':
             klass = HTMLUser
         else:
index 962d9b865e379772c49f1c53a9dc84d4512e8a0c..83ccce00001ae06cace065da72202e2629b930ee 100644 (file)
@@ -73,12 +73,12 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.108 2003-01-27 16:32:46 kedder Exp $
+$Id: mailgw.py,v 1.109 2003-02-06 05:43:47 richard Exp $
 '''
 
 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
 import time, random, sys
-import traceback, MimeWriter
+import traceback, MimeWriter, rfc822
 import hyperdb, date, password
 
 import rfc2822
@@ -113,6 +113,26 @@ def initialiseSecurity(security):
         description="User may use the email interface")
     security.addPermissionToRole('Admin', p)
 
+def getparam(str, param):
+    ''' From the rfc822 "header" string, extract "param" if it appears.
+    '''
+    if ';' not in str:
+        return None
+    str = str[str.index(';'):]
+    while str[:1] == ';':
+        str = str[1:]
+        if ';' in str:
+            # XXX Should parse quotes!
+            end = str.index(';')
+        else:
+            end = len(str)
+        f = str[:end]
+        if '=' in f:
+            i = f.index('=')
+            if f[:i].strip().lower() == param:
+                return rfc822.unquote(f[i+1:].strip())
+    return None
+
 class Message(mimetools.Message):
     ''' subclass mimetools.Message so we can retrieve the parts of the
         message...
@@ -713,6 +733,7 @@ Subject was: "%s"
                 elif subtype == 'multipart/alternative':
                     # Search for text/plain in message with attachment and
                     # alternative text representation
+                    # skip over intro to first boundary
                     part.getPart()
                     while 1:
                         # get the next part
@@ -730,7 +751,7 @@ Subject was: "%s"
                     if not name:
                         disp = part.getheader('content-disposition', None)
                         if disp:
-                            name = disp.getparam('filename')
+                            name = getparam(disp, 'filename')
                             if name:
                                 name = name.strip()
                     # this is just an attachment
@@ -946,7 +967,7 @@ def setPropArrayFromString(self, cl, propString, nodeid = None):
             props[propname] = value.lower() in ('yes', 'true', 'on', '1')
         elif isinstance(proptype, hyperdb.Number):
             value = value.strip()
-            props[propname] = int(value)
+            props[propname] = float(value)
     return errors, props
 
 
index 9f3a26a2c0248e587aa8aec165ebb25717086c57..afe9d5edead6b4c96705337df4dee7a33af33a70 100644 (file)
@@ -16,7 +16,7 @@
 # 
 """ HTTP Server that serves roundup.
 
-$Id: roundup_server.py,v 1.17 2003-01-13 02:44:42 richard Exp $
+$Id: roundup_server.py,v 1.18 2003-02-06 05:43:49 richard Exp $
 """
 
 # python version check
@@ -247,7 +247,7 @@ def run():
     try:
         # handle the command-line args
         try:
-            optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:d:l:')
+            optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:d:l:h')
         except getopt.GetoptError, e:
             usage(str(e))
 
@@ -301,6 +301,10 @@ def run():
 
     # fork?
     if pidfile:
+        if not hasattr(os, 'fork'):
+            print "Sorry, you can't run the server as a daemon on this" \
+                'Operating System'
+            sys.exit(0)
         daemonize(pidfile)
 
     # redirect stdout/stderr to our logfile
index 8bc9576a6e6a9c098d5ad54576cd0b40905beff3..733d233a8f4b4d0fb792009ef30b1ce01e613f7c 100755 (executable)
@@ -19,7 +19,7 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-# $Id: roundup-reminder,v 1.4 2002-09-10 03:01:19 richard Exp $
+# $Id: roundup-reminder,v 1.5 2003-02-06 05:43:49 richard Exp $
 
 '''
 Simple script that emails all users of a tracker with the issues that
@@ -32,7 +32,7 @@ Note: The instance that this script was designed for has a modified schema!
       You will want to modify this script to customise it for your own schema!
 '''
 
-import cStringIO, MimeWriter, smtplib
+import sys, cStringIO, MimeWriter, smtplib
 from roundup import instance, date
 
 # open the instance
@@ -132,7 +132,7 @@ and click on "My Issues". Do NOT respond to this message.
             creation = creation_date.pretty()
         if not timeliness_id: timeliness_id = ' '
         title = db.issue.get(issue_id, 'title')
-        issue_id = '<a href="%sissue%s">%s</a>'%(db.config.ISSUE_TRACKER_WEB,
+        issue_id = '<a href="%sissue%s">%s</a>'%(db.config.TRACKER_WEB,
             issue_id, issue_id)
         colour = colours.get(timeliness, '')
         print >>body, '''<tr%s><td>%s</td><td>%s</td><td>%s</td>
index 5fed0029dc83f2217b1a48cad58741b589f7f589..a2785ea3ca01dee9d25740595c50c522376fe8e4 100644 (file)
@@ -8,7 +8,7 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: test_mailgw.py,v 1.38 2003-01-15 22:17:20 kedder Exp $
+# $Id: test_mailgw.py,v 1.39 2003-02-06 05:43:49 richard Exp $
 
 import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
 
@@ -780,6 +780,41 @@ http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1
 _______________________________________________________________________
 ''')
 
+    def testContentDisposition(self):
+        self.doNewIssue()
+        message = cStringIO.StringIO('''Content-Type: text/plain;
+  charset="iso-8859-1"
+From: mary <mary@test>
+To: issue_tracker@your.tracker.email.domain.example
+Message-Id: <followup_dummy_id>
+In-Reply-To: <dummy_test_message_id>
+Subject: [issue1] Testing...
+Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE" 
+Content-Disposition: inline 
+--bCsyhTFzCvuiizWE 
+Content-Type: text/plain; charset=us-ascii 
+Content-Disposition: inline 
+
+test attachment binary 
+
+--bCsyhTFzCvuiizWE 
+Content-Type: application/octet-stream 
+Content-Disposition: attachment; filename="main.dvi" 
+
+xxxxxx 
+
+--bCsyhTFzCvuiizWE--
+''')
+        handler = self.instance.MailGW(self.instance, self.db)
+        handler.trapExceptions = 0
+        handler.main(message)
+        messages = self.db.issue.get('1', 'messages')
+        messages.sort()
+        file = self.db.msg.get(messages[-1], 'files')[0]
+        self.assertEqual(self.db.file.get(file, 'name'), 'main.dvi')
+
     def testFollowupStupidQuoting(self):
         self.doNewIssue()