summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorMark Haines <mark.haines@matrix.org>2014-12-11 14:19:32 +0000
committerMark Haines <mark.haines@matrix.org>2014-12-11 14:19:32 +0000
commitd80d505b1f70eae128990ce1a9517e5c5edead73 (patch)
treeeeec77127889207fae0b44b5e95360cd953ee202 /synapse
parentdoc the thumbnail methods (diff)
downloadsynapse-d80d505b1f70eae128990ce1a9517e5c5edead73.tar.xz
Limit the size of images that are thumbnailed serverside. Limit the size of file that a server will download from a remote server
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/errors.py1
-rw-r--r--synapse/config/repository.py5
-rw-r--r--synapse/http/matrixfederationclient.py25
-rw-r--r--synapse/media/v1/base_resource.py18
4 files changed, 43 insertions, 6 deletions
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 581439ceb3..e250b9b211 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -34,6 +34,7 @@ class Codes(object):
     LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
     CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
     CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
+    TOO_LARGE = "M_TOO_LARGE"
 
 
 class CodeMessageException(Exception):
diff --git a/synapse/config/repository.py b/synapse/config/repository.py
index 6eec930a03..f1b7b1b74e 100644
--- a/synapse/config/repository.py
+++ b/synapse/config/repository.py
@@ -20,6 +20,7 @@ class ContentRepositoryConfig(Config):
     def __init__(self, args):
         super(ContentRepositoryConfig, self).__init__(args)
         self.max_upload_size = self.parse_size(args.max_upload_size)
+        self.max_image_pixels = self.parse_size(args.max_image_pixels)
         self.media_store_path = self.ensure_directory(args.media_store_path)
 
     def parse_size(self, string):
@@ -41,3 +42,7 @@ class ContentRepositoryConfig(Config):
         db_group.add_argument(
             "--media-store-path", default=cls.default_path("media_store")
         )
+        db_group.add_argument(
+            "--max-image-pixels", default="32M",
+            help="Maximum number of pixels that will be thumbnailed"
+        )
diff --git a/synapse/http/matrixfederationclient.py b/synapse/http/matrixfederationclient.py
index f05269cdfb..8f4db59c75 100644
--- a/synapse/http/matrixfederationclient.py
+++ b/synapse/http/matrixfederationclient.py
@@ -26,7 +26,7 @@ from synapse.util.logcontext import PreserveLoggingContext
 
 from syutil.jsonutil import encode_canonical_json
 
-from synapse.api.errors import CodeMessageException, SynapseError
+from synapse.api.errors import CodeMessageException, SynapseError, Codes
 
 from syutil.crypto.jsonsign import sign_json
 
@@ -289,7 +289,7 @@ class MatrixFederationHttpClient(object):
 
     @defer.inlineCallbacks
     def get_file(self, destination, path, output_stream, args={},
-                 retry_on_dns_fail=True):
+                 retry_on_dns_fail=True, max_size=None):
         """GETs a file from a given homeserver
         Args:
             destination (str): The remote server to send the HTTP request to.
@@ -325,7 +325,11 @@ class MatrixFederationHttpClient(object):
 
         headers = dict(response.headers.getAllRawHeaders())
 
-        length = yield _readBodyToFile(response, output_stream)
+        try:
+            length = yield _readBodyToFile(response, output_stream, max_size)
+        except:
+            logger.exception("Failed to download body")
+            raise
 
         defer.returnValue((length, headers))
 
@@ -337,14 +341,23 @@ class MatrixFederationHttpClient(object):
 
 
 class _ReadBodyToFileProtocol(protocol.Protocol):
-    def __init__(self, stream, deferred):
+    def __init__(self, stream, deferred, max_size):
         self.stream = stream
         self.deferred = deferred
         self.length = 0
+        self.max_size = max_size
 
     def dataReceived(self, data):
         self.stream.write(data)
         self.length += len(data)
+        if self.max_size is not None and self.length >= self.max_size:
+            self.deferred.errback(SynapseError(
+                502,
+                "Requested file is too large > %r bytes" % (self.max_size,),
+                Codes.TOO_LARGE,
+            ))
+            self.deferred = defer.Deferred()
+            self.transport.loseConnection()
 
     def connectionLost(self, reason):
         if reason.check(ResponseDone):
@@ -353,9 +366,9 @@ class _ReadBodyToFileProtocol(protocol.Protocol):
             self.deferred.errback(reason)
 
 
-def _readBodyToFile(response, stream):
+def _readBodyToFile(response, stream, max_size):
     d = defer.Deferred()
-    response.deliverBody(_ReadBodyToFileProtocol(stream, d))
+    response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
     return d
 
 
diff --git a/synapse/media/v1/base_resource.py b/synapse/media/v1/base_resource.py
index 8c62ecd597..77b05c6548 100644
--- a/synapse/media/v1/base_resource.py
+++ b/synapse/media/v1/base_resource.py
@@ -43,6 +43,7 @@ class BaseMediaResource(Resource):
         self.server_name = hs.hostname
         self.store = hs.get_datastore()
         self.max_upload_size = hs.config.max_upload_size
+        self.max_image_pixels = hs.config.max_image_pixels
         self.filepaths = filepaths
 
     @staticmethod
@@ -143,6 +144,7 @@ class BaseMediaResource(Resource):
                 ))
                 length, headers = yield self.client.get_file(
                     server_name, request_path, output_stream=f,
+                    max_size=self.max_upload_size,
                 )
             media_type = headers["Content-Type"][0]
             time_now_ms = self.clock.time_msec()
@@ -226,6 +228,14 @@ class BaseMediaResource(Resource):
         thumbnailer = Thumbnailer(input_path)
         m_width = thumbnailer.width
         m_height = thumbnailer.height
+
+        if m_width * m_height >= self.max_image_pixels:
+            logger.info(
+                "Image too large to thumbnail %r x %r > %r"
+                m_width, m_height, self.max_image_pixels
+            )
+            return
+
         scales = set()
         crops = set()
         for r_width, r_height, r_method, r_type in requirements:
@@ -281,6 +291,14 @@ class BaseMediaResource(Resource):
         thumbnailer = Thumbnailer(input_path)
         m_width = thumbnailer.width
         m_height = thumbnailer.height
+
+        if m_width * m_height >= self.max_image_pixels:
+            logger.info(
+                "Image too large to thumbnail %r x %r > %r"
+                m_width, m_height, self.max_image_pixels
+            )
+            return
+
         scales = set()
         crops = set()
         for r_width, r_height, r_method, r_type in requirements: