Skip to content

Commit dfa36cd

Browse files
author
Justus Pendleton
committed
config option to limit nosy attachments based on size
reworking of patch [SF#772323] from Philipp Gortan It tries to avoid reading the file contents just to get the file size but that was too hard for metakit backends. They don't inherit from blobfiles.FileStorage which makes it more challenging. Really that backend should be reworked to inherit from FileStorage. I'm not sure I like the default being sys.maxint. Maybe have 0 == unlimited? But what if someone really wanted to set it to 0 to mean "don't attach anything"?
1 parent b6d956c commit dfa36cd

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

roundup/configuration.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Roundup Issue Tracker configuration support
22
#
3-
# $Id: configuration.py,v 1.46 2007-09-02 06:48:13 forsberg Exp $
3+
# $Id: configuration.py,v 1.47 2007-09-03 17:14:08 jpend Exp $
44
#
55
__docformat__ = "restructuredtext"
66

@@ -744,6 +744,10 @@ def str2value(self, value):
744744
"\"multiple\" then a separate email is sent to each\n"
745745
"recipient. If \"single\" then a single email is sent with\n"
746746
"each recipient as a CC address."),
747+
(IntegerNumberOption, "max_attachment_size", sys.maxint,
748+
"Attachments larger than the given number of bytes\n"
749+
"won't be attached to nosy mails. They will be replaced by\n"
750+
"a link to the tracker's download page for the file.")
747751
), "Nosy messages sending"),
748752
)
749753

roundup/roundupdb.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1717
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1818
#
19-
# $Id: roundupdb.py,v 1.129 2007-09-01 16:30:11 forsberg Exp $
19+
# $Id: roundupdb.py,v 1.130 2007-09-03 17:14:08 jpend Exp $
2020

2121
"""Extending hyperdb with types specific to issue-tracking.
2222
"""
2323
__docformat__ = 'restructuredtext'
2424

2525
import re, os, smtplib, socket, time, random
2626
import cStringIO, base64, quopri, mimetypes
27+
import os.path
2728

2829
from rfc2822 import encode_header
2930

@@ -322,6 +323,29 @@ def send_message(self, nodeid, msgid, note, sendto, from_address=None,
322323
if msgid is not None:
323324
m.append(messages.get(msgid, 'content', ''))
324325

326+
# get the files for this message
327+
message_files = []
328+
if msgid :
329+
for fileid in messages.get(msgid, 'files') :
330+
# try to avoid reading in the file contents just to check the size
331+
# backends that inherit from blobfiles.FileStorage have a filename class
332+
if hasattr(self.db, 'filename'):
333+
filename = self.db.filename('file', fileid, None)
334+
filesize = os.path.getsize(filename)
335+
else:
336+
# metakit doesn't inherit from FileStorage so we read the
337+
# full file contents to get the size :-/
338+
filesize = len(self.db.file.get(fileid, 'content'))
339+
340+
if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE:
341+
message_files.append(fileid)
342+
else:
343+
base = self.db.config.TRACKER_WEB
344+
link = "".join((base, files.classname, fileid))
345+
filename = files.get(fileid, 'name')
346+
m.append(_("File '%(filename)s' not attached - you can "
347+
"download it from %(link)s." % locals()))
348+
325349
# add the change note
326350
if note:
327351
m.append(note)
@@ -340,12 +364,6 @@ def send_message(self, nodeid, msgid, note, sendto, from_address=None,
340364
quopri.encode(content, content_encoded, 0)
341365
content_encoded = content_encoded.getvalue()
342366

343-
# get the files for this message
344-
if msgid is None:
345-
message_files = None
346-
else:
347-
message_files = messages.get(msgid, 'files')
348-
349367
# make sure the To line is always the same (for testing mostly)
350368
sendto.sort()
351369

test/db_test_base.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: db_test_base.py,v 1.88 2007-08-31 15:44:03 jpend Exp $
18+
# $Id: db_test_base.py,v 1.89 2007-09-03 17:14:09 jpend Exp $
1919

20-
import unittest, os, shutil, errno, imp, sys, time, pprint, sets
20+
import unittest, os, shutil, errno, imp, sys, time, pprint, sets, base64
2121

2222
from roundup.hyperdb import String, Password, Link, Multilink, Date, \
2323
Interval, DatabaseError, Boolean, Number, Node
24+
from roundup.mailer import Mailer
2425
from roundup import date, password, init, instance, configuration, support
2526

2627
from mocknull import MockNull
@@ -69,8 +70,8 @@ def setupSchema(db, create, module):
6970
priority = module.Class(db, "priority", name=String(), order=String())
7071
priority.setkey("name")
7172
user = module.Class(db, "user", username=String(), password=Password(),
72-
assignable=Boolean(), age=Number(), roles=String(),
73-
supervisor=Link('user'))
73+
assignable=Boolean(), age=Number(), roles=String(), address=String(),
74+
supervisor=Link('user'),realname=String())
7475
user.setkey("username")
7576
file = module.FileClass(db, "file", name=String(), type=String(),
7677
comment=String(indexme="yes"), fooz=Password())
@@ -82,14 +83,18 @@ def setupSchema(db, create, module):
8283
stuff = module.Class(db, "stuff", stuff=String())
8384
session = module.Class(db, 'session', title=String())
8485
msg = module.FileClass(db, "msg", date=Date(),
85-
author=Link("user", do_journal='no'))
86+
author=Link("user", do_journal='no'),
87+
files=Multilink('file'), inreplyto=String(),
88+
messageid=String(),
89+
recipients=Multilink("user", do_journal='no')
90+
)
8691
session.disableJournalling()
8792
db.post_init()
8893
if create:
8994
user.create(username="admin", roles='Admin',
9095
password=password.Password('sekrit'))
9196
user.create(username="fred", roles='User',
92-
password=password.Password('sekrit'))
97+
password=password.Password('sekrit'), address='[email protected]')
9398
status.create(name="unread")
9499
status.create(name="in-progress")
95100
status.create(name="testing")
@@ -917,14 +922,15 @@ def testForcedReindexing(self):
917922

918923
def testIndexingOnImport(self):
919924
msgcontent = 'Glrk'
920-
msgid = self.db.msg.import_list(['content'], [repr(msgcontent)])
925+
msgid = self.db.msg.import_list(['content', 'files', 'recipients'],
926+
[repr(msgcontent), '[]', '[]'])
921927
msg_filename = self.db.filename(self.db.msg.classname, msgid,
922928
create=1)
923929
support.ensureParentsExist(msg_filename)
924930
msg_file = open(msg_filename, 'w')
925931
msg_file.write(msgcontent)
926932
msg_file.close()
927-
933+
928934

929935
filecontent = 'Brrk'
930936
fileid = self.db.file.import_list(['content'], [repr(filecontent)])
@@ -933,7 +939,7 @@ def testIndexingOnImport(self):
933939
support.ensureParentsExist(file_filename)
934940
file_file = open(file_filename, 'w')
935941
file_file.write(filecontent)
936-
file_file.close()
942+
file_file.close()
937943

938944
title = 'Bzzt'
939945
nodeid = self.db.issue.import_list(['title', 'messages', 'files',
@@ -958,7 +964,7 @@ def testIndexingOnImport(self):
958964
self.assertEquals(self.db.indexer.search([filecontent], self.db.issue),
959965
{str(nodeid):{'files':[str(fileid)]}})
960966

961-
967+
962968

963969
#
964970
# searching tests follow
@@ -1240,8 +1246,8 @@ def testFilteringStringSort(self):
12401246

12411247
def testFilteringMultilinkSort(self):
12421248
# 1: [] Reverse: 1: []
1243-
# 2: [] 2: []
1244-
# 3: ['admin','fred'] 3: ['fred','admin']
1249+
# 2: [] 2: []
1250+
# 3: ['admin','fred'] 3: ['fred','admin']
12451251
# 4: ['admin','bleep','fred'] 4: ['fred','bleep','admin']
12461252
# Note the sort order for the multilink doen't change when
12471253
# reversing the sort direction due to the re-sorting of the
@@ -1532,7 +1538,7 @@ def testFilteringTransitiveMultilinkSort(self):
15321538
['1', '2', '3', '4', '5', '8', '6', '7'])
15331539
ae(filt(None, {}, [('+','messages.author'), ('+','messages')]),
15341540
['6', '7', '8', '5', '4', '3', '1', '2'])
1535-
# The following will sort by
1541+
# The following will sort by
15361542
# author.supervisor.username and then by
15371543
# author.username
15381544
# I've resited the tempation to implement recursive orderprop
@@ -1723,6 +1729,38 @@ def testAddRemoveProperty(self):
17231729
'nosy', 'priority', 'spam', 'status', 'superseder'])
17241730
self.assertEqual(self.db.issue.list(), ['1'])
17251731

1732+
def testNosyMail(self) :
1733+
"""Creates one issue with two attachments, one smaller and one larger
1734+
than the set max_attachment_size.
1735+
"""
1736+
db = self.db
1737+
db.config.NOSY_MAX_ATTACHMENT_SIZE = 4096
1738+
res = dict(mail_to = None, mail_msg = None)
1739+
def dummy_snd(s, to, msg, res=res) :
1740+
res["mail_to"], res["mail_msg"] = to, msg
1741+
backup, Mailer.smtp_send = Mailer.smtp_send, dummy_snd
1742+
try :
1743+
f1 = db.file.create(name="test1.txt", content="x" * 20)
1744+
f2 = db.file.create(name="test2.txt", content="y" * 5000)
1745+
m = db.msg.create(content="one two", author="admin",
1746+
files = [f1, f2])
1747+
i = db.issue.create(title='spam', files = [f1, f2],
1748+
messages = [m], nosy = [db.user.lookup("fred")])
1749+
1750+
db.issue.nosymessage(i, m, {})
1751+
mail_msg = res["mail_msg"].getvalue()
1752+
self.assertEqual(res["mail_to"], ["[email protected]"])
1753+
self.failUnless("From: admin" in mail_msg)
1754+
self.failUnless("Subject: [issue1] spam" in mail_msg)
1755+
self.failUnless("New submission from admin" in mail_msg)
1756+
self.failUnless("one two" in mail_msg)
1757+
self.failIf("File 'test1.txt' not attached" in mail_msg)
1758+
self.failUnless(base64.b64encode("xxx") in mail_msg)
1759+
self.failUnless("File 'test2.txt' not attached" in mail_msg)
1760+
self.failIf(base64.b64encode("yyy") in mail_msg)
1761+
finally :
1762+
Mailer.smtp_send = backup
1763+
17261764
class ROTest(MyTestCase):
17271765
def setUp(self):
17281766
# remove previous test, ignore errors

0 commit comments

Comments
 (0)