[CalendarServer-changes] [13319] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 18 08:01:49 PDT 2014


Revision: 13319
          http://trac.calendarserver.org//changeset/13319
Author:   cdaboo at apple.com
Date:     2014-04-18 08:01:49 -0700 (Fri, 18 Apr 2014)
Log Message:
-----------
Reinstate MaximumAttachmentSize limit.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-apple.plist
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/conf/caldavd.plist
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
    CalendarServer/trunk/txdav/caldav/icalendarstore.py

Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/conf/caldavd-apple.plist	2014-04-18 15:01:49 UTC (rev 13319)
@@ -167,8 +167,12 @@
 
     <!-- User quota (in bytes) [0 = no quota] applies to attachments only -->
     <key>UserQuota</key>
-    <integer>104857600</integer><!-- 100Mb -->
+    <integer>104857600</integer> <!-- 100Mb -->
 
+    <!-- Maximum size for a single attachment (in bytes) [0 = no limit] -->
+    <key>MaximumAttachmentSize</key>
+    <integer>10485760</integer> <!-- 10Mb -->
+
     <!-- Maximum number of calendars/address books allowed in a home -->
     <!-- 0 for no limit -->
     <key>MaxCollectionsPerHome</key>

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2014-04-18 15:01:49 UTC (rev 13319)
@@ -145,8 +145,12 @@
 
     <!-- User quota (in bytes) [0 = no quota] applies to attachments only -->
     <key>UserQuota</key>
-    <integer>104857600</integer><!-- 100Mb -->
+    <integer>104857600</integer> <!-- 100Mb -->
 
+    <!-- Maximum size for a single attachment (in bytes) [0 = no limit] -->
+    <key>MaximumAttachmentSize</key>
+    <integer>10485760</integer> <!-- 10Mb -->
+
     <!-- Maximum number of calendars/address books allowed in a home -->
     <!-- 0 for no limit -->
     <key>MaxCollectionsPerHome</key>

Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/conf/caldavd.plist	2014-04-18 15:01:49 UTC (rev 13319)
@@ -126,6 +126,10 @@
     <key>UserQuota</key>
     <integer>104857600</integer> <!-- 100Mb -->
 
+    <!-- Maximum size for a single attachment (in bytes) [0 = no limit] -->
+    <key>MaximumAttachmentSize</key>
+    <integer>10485760</integer> <!-- 10Mb -->
+
     <!-- Maximum number of calendars/address books allowed in a home -->
     <!-- 0 for no limit -->
     <key>MaxCollectionsPerHome</key>

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -350,7 +350,8 @@
     #
 
     # Attachments
-    "UserQuota": 104857600, # User attachment quota (in bytes)
+    "UserQuota": 104857600, # User attachment quota (in bytes - default 100MB)
+    "MaximumAttachmentSize": 10485760, # Maximum size for a single attachment (in bytes - default 10MB)
 
     # Resource data
     "MaxCollectionsPerHome": 50, # Maximum number of calendars/address books allowed in a home

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -60,7 +60,7 @@
     InvalidPerUserDataMerge,
     AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation,
     ShareeAllowedError, DuplicatePrivateCommentsError, InvalidSplit
-)
+, AttachmentSizeTooLarge)
 from txdav.carddav.iaddressbookstore import (
     KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
 )
@@ -2123,6 +2123,11 @@
 
         try:
             yield t.loseConnection()
+        except AttachmentSizeTooLarge:
+            raise HTTPError(
+                ErrorResponse(FORBIDDEN,
+                              (caldav_namespace, "max-attachment-size"))
+            )
         except QuotaExceeded:
             raise HTTPError(
                 ErrorResponse(INSUFFICIENT_STORAGE_SPACE,
@@ -3176,6 +3181,12 @@
                     "The action parameter in the request-URI is not valid",
                 ))
 
+        except AttachmentSizeTooLarge:
+            raise HTTPError(
+                ErrorResponse(FORBIDDEN,
+                              (caldav_namespace, "max-attachment-size"))
+            )
+
         except QuotaExceeded:
             raise HTTPError(ErrorResponse(
                 INSUFFICIENT_STORAGE_SPACE,

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -46,7 +46,7 @@
 from twistedcaldav.config import config
 from twistedcaldav.ical import InvalidICalendarDataError
 
-from txdav.caldav.icalendarstore import IAttachment
+from txdav.caldav.icalendarstore import IAttachment, AttachmentSizeTooLarge
 from txdav.caldav.icalendarstore import ICalendar, ICalendarObject
 from txdav.caldav.icalendarstore import ICalendarHome
 
@@ -803,6 +803,13 @@
         newSize = self._file.tell()
         # FIXME: do anything
         self._file.close()
+
+        # Check max size for attachment
+        if newSize > config.MaximumAttachmentSize:
+            self._path.remove()
+            return fail(AttachmentSizeTooLarge())
+
+        # Check overall user quota
         allowed = home.quotaAllowedBytes()
         if allowed is not None and allowed < (home.quotaUsedBytes()
                                               + (newSize - oldSize)):

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -77,7 +77,8 @@
     ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
     InvalidDefaultCalendar, \
     InvalidAttachmentOperation, DuplicatePrivateCommentsError, \
-    TimeRangeUpperLimit, TimeRangeLowerLimit, InvalidSplit
+    TimeRangeUpperLimit, TimeRangeLowerLimit, InvalidSplit, \
+    AttachmentSizeTooLarge
 from txdav.caldav.icalendarstore import QuotaExceeded
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
     CommonObjectResource, ECALENDARTYPE
@@ -4053,6 +4054,15 @@
         oldSize = self._attachment.size()
         newSize = self._file.tell()
         self._file.close()
+
+        # Check max size for attachment
+        if newSize > config.MaximumAttachmentSize:
+            self._path.remove()
+            if self._creating:
+                yield self._attachment._internalRemove()
+            raise AttachmentSizeTooLarge()
+
+        # Check overall user quota
         allowed = home.quotaAllowedBytes()
         if allowed is not None and allowed < ((yield home.quotaUsedBytes())
                                               + (newSize - oldSize)):

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -36,7 +36,7 @@
 from txdav.caldav.datastore.test.common import CaptureProtocol
 from txdav.caldav.datastore.test.util import buildCalendarStore
 from txdav.caldav.icalendarstore import IAttachmentStorageTransport, IAttachment, \
-    QuotaExceeded
+    QuotaExceeded, AttachmentSizeTooLarge
 from txdav.common.datastore.sql_tables import schema
 from txdav.common.datastore.test.util import CommonCommonTests, \
     populateCalendarsFrom, deriveQuota, withSpecialQuota
@@ -523,6 +523,77 @@
         yield checkOriginal()
 
 
+    @inlineCallbacks
+    def exceedSizeTest(self, getit):
+        """
+        If too many bytes are passed to the transport returned by
+        L{ICalendarObject.createAttachmentWithName},
+        L{IAttachmentStorageTransport.loseConnection} will return a L{Deferred}
+        that fails with L{AttachmentSizeTooLarge}.
+        """
+        attachment = yield getit()
+        t = attachment.store(MimeType("text", "x-fixture"), "")
+        sample = "all work and no play makes jack a dull boy"
+        chunk = (sample * (config.MaximumAttachmentSize / len(sample)))
+
+        t.write(chunk)
+        t.writeSequence([chunk, chunk])
+
+        d = t.loseConnection()
+        yield self.failUnlessFailure(d, AttachmentSizeTooLarge)
+
+
+    @inlineCallbacks
+    def test_exceedSizeNew(self):
+        """
+        When size is exceeded on a new attachment, that attachment will no
+        longer exist.
+        """
+
+        self.patch(config, "MaximumAttachmentSize", 100)
+        obj = yield self.calendarObjectUnderTest()
+        yield self.exceedSizeTest(
+            lambda: obj.createAttachmentWithName("too-big.attachment")
+        )
+        self.assertEquals((yield obj.attachments()), [])
+        yield self.commit()
+        obj = yield self.calendarObjectUnderTest()
+        self.assertEquals((yield obj.attachments()), [])
+
+
+    @inlineCallbacks
+    def test_exceedSizeReplace(self):
+        """
+        When size is exceeded while replacing an attachment, that attachment's
+        contents will not be replaced.
+        """
+        self.patch(config, "MaximumAttachmentSize", 100)
+        obj = yield self.calendarObjectUnderTest()
+        create = lambda: obj.createAttachmentWithName("exists.attachment")
+        get = lambda: obj.attachmentWithName("exists.attachment")
+        attachment = yield create()
+        t = attachment.store(MimeType("text", "x-fixture"), "")
+        sampleData = "a reasonably sized attachment"
+        t.write(sampleData)
+        yield t.loseConnection()
+        yield self.exceedSizeTest(get)
+        @inlineCallbacks
+        def checkOriginal():
+            actual = yield self.attachmentToString(attachment)
+            expected = sampleData
+            # note: 60 is less than len(expected); trimming is just to make
+            # the error message look sane when the test fails.
+            actual = actual[:60]
+            self.assertEquals(actual, expected)
+        yield checkOriginal()
+        yield self.commit()
+        # Make sure that things go back to normal after a commit of that
+        # transaction.
+        obj = yield self.calendarObjectUnderTest()
+        attachment = yield get()
+        yield checkOriginal()
+
+
     def test_removeAttachmentWithName(self, refresh=lambda x: x):
         """
         L{ICalendarObject.removeAttachmentWithName} will remove the calendar
@@ -1061,6 +1132,76 @@
         yield checkOriginal()
 
 
+    @inlineCallbacks
+    def exceedSizeTest(self, getit):
+        """
+        If too many bytes are passed to the transport returned by
+        L{ICalendarObject.createAttachmentWithName},
+        L{IAttachmentStorageTransport.loseConnection} will return a L{Deferred}
+        that fails with L{AttachmentSizeTooLarge}.
+        """
+        attachment = yield getit()
+        t = attachment.store(MimeType("text", "x-fixture"), "")
+        sample = "all work and no play makes jack a dull boy"
+        chunk = (sample * (config.MaximumAttachmentSize / len(sample)))
+
+        t.write(chunk)
+        t.writeSequence([chunk, chunk])
+
+        d = t.loseConnection()
+        yield self.failUnlessFailure(d, AttachmentSizeTooLarge)
+
+
+    @inlineCallbacks
+    def test_exceedSizeNew(self):
+        """
+        When size is exceeded on a new attachment, that attachment will no
+        longer exist.
+        """
+        self.patch(config, "MaximumAttachmentSize", 100)
+        obj = yield self.calendarObjectUnderTest()
+        yield self.exceedSizeTest(
+            lambda: obj.createAttachmentWithName("too-big.attachment")
+        )
+        self.assertEquals((yield obj.attachments()), [])
+        yield self.commit()
+        obj = yield self.calendarObjectUnderTest()
+        self.assertEquals((yield obj.attachments()), [])
+
+
+    @inlineCallbacks
+    def test_exceedSizeReplace(self):
+        """
+        When size is exceeded while replacing an attachment, that attachment's
+        contents will not be replaced.
+        """
+        self.patch(config, "MaximumAttachmentSize", 100)
+        obj = yield self.calendarObjectUnderTest()
+        create = lambda: obj.createAttachmentWithName("exists.attachment")
+        get = lambda: obj.attachmentWithName("exists.attachment")
+        attachment = yield create()
+        t = attachment.store(MimeType("text", "x-fixture"), "")
+        sampleData = "a reasonably sized attachment"
+        t.write(sampleData)
+        yield t.loseConnection()
+        yield self.exceedSizeTest(get)
+        @inlineCallbacks
+        def checkOriginal():
+            actual = yield self.attachmentToString(attachment)
+            expected = sampleData
+            # note: 60 is less than len(expected); trimming is just to make
+            # the error message look sane when the test fails.
+            actual = actual[:60]
+            self.assertEquals(actual, expected)
+        yield checkOriginal()
+        yield self.commit()
+        # Make sure that things go back to normal after a commit of that
+        # transaction.
+        obj = yield self.calendarObjectUnderTest()
+        attachment = yield get()
+        yield checkOriginal()
+
+
     def test_removeManagedAttachmentWithID(self, refresh=lambda x: x):
         """
         L{ICalendarObject.removeManagedAttachmentWithID} will remove the calendar

Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py	2014-04-18 15:01:12 UTC (rev 13318)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py	2014-04-18 15:01:49 UTC (rev 13319)
@@ -775,6 +775,13 @@
 
 
 
+class AttachmentSizeTooLarge(Exception):
+    """
+    Unable to store an attachment because it is too large.
+    """
+
+
+
 class AttachmentStoreValidManagedID(Exception):
     """
     Specified attachment managed-id is not valid.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140418/0f644fd1/attachment-0001.html>


More information about the calendarserver-changes mailing list