mirror of
https://github.com/element-hq/synapse
synced 2024-09-01 18:00:21 +00:00
Limit the size of images that are thumbnailed serverside. Limit the size of file that a server will download from a remote server
This commit is contained in:
parent
ead8fc5e38
commit
d80d505b1f
4 changed files with 43 additions and 6 deletions
|
@ -34,6 +34,7 @@ class Codes(object):
|
||||||
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||||
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||||
|
TOO_LARGE = "M_TOO_LARGE"
|
||||||
|
|
||||||
|
|
||||||
class CodeMessageException(Exception):
|
class CodeMessageException(Exception):
|
||||||
|
|
|
@ -20,6 +20,7 @@ class ContentRepositoryConfig(Config):
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
super(ContentRepositoryConfig, self).__init__(args)
|
super(ContentRepositoryConfig, self).__init__(args)
|
||||||
self.max_upload_size = self.parse_size(args.max_upload_size)
|
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)
|
self.media_store_path = self.ensure_directory(args.media_store_path)
|
||||||
|
|
||||||
def parse_size(self, string):
|
def parse_size(self, string):
|
||||||
|
@ -41,3 +42,7 @@ class ContentRepositoryConfig(Config):
|
||||||
db_group.add_argument(
|
db_group.add_argument(
|
||||||
"--media-store-path", default=cls.default_path("media_store")
|
"--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"
|
||||||
|
)
|
||||||
|
|
|
@ -26,7 +26,7 @@ from synapse.util.logcontext import PreserveLoggingContext
|
||||||
|
|
||||||
from syutil.jsonutil import encode_canonical_json
|
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
|
from syutil.crypto.jsonsign import sign_json
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ class MatrixFederationHttpClient(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_file(self, destination, path, output_stream, args={},
|
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
|
"""GETs a file from a given homeserver
|
||||||
Args:
|
Args:
|
||||||
destination (str): The remote server to send the HTTP request to.
|
destination (str): The remote server to send the HTTP request to.
|
||||||
|
@ -325,7 +325,11 @@ class MatrixFederationHttpClient(object):
|
||||||
|
|
||||||
headers = dict(response.headers.getAllRawHeaders())
|
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))
|
defer.returnValue((length, headers))
|
||||||
|
|
||||||
|
@ -337,14 +341,23 @@ class MatrixFederationHttpClient(object):
|
||||||
|
|
||||||
|
|
||||||
class _ReadBodyToFileProtocol(protocol.Protocol):
|
class _ReadBodyToFileProtocol(protocol.Protocol):
|
||||||
def __init__(self, stream, deferred):
|
def __init__(self, stream, deferred, max_size):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.deferred = deferred
|
self.deferred = deferred
|
||||||
self.length = 0
|
self.length = 0
|
||||||
|
self.max_size = max_size
|
||||||
|
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data):
|
||||||
self.stream.write(data)
|
self.stream.write(data)
|
||||||
self.length += len(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):
|
def connectionLost(self, reason):
|
||||||
if reason.check(ResponseDone):
|
if reason.check(ResponseDone):
|
||||||
|
@ -353,9 +366,9 @@ class _ReadBodyToFileProtocol(protocol.Protocol):
|
||||||
self.deferred.errback(reason)
|
self.deferred.errback(reason)
|
||||||
|
|
||||||
|
|
||||||
def _readBodyToFile(response, stream):
|
def _readBodyToFile(response, stream, max_size):
|
||||||
d = defer.Deferred()
|
d = defer.Deferred()
|
||||||
response.deliverBody(_ReadBodyToFileProtocol(stream, d))
|
response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ class BaseMediaResource(Resource):
|
||||||
self.server_name = hs.hostname
|
self.server_name = hs.hostname
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.max_upload_size = hs.config.max_upload_size
|
self.max_upload_size = hs.config.max_upload_size
|
||||||
|
self.max_image_pixels = hs.config.max_image_pixels
|
||||||
self.filepaths = filepaths
|
self.filepaths = filepaths
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -143,6 +144,7 @@ class BaseMediaResource(Resource):
|
||||||
))
|
))
|
||||||
length, headers = yield self.client.get_file(
|
length, headers = yield self.client.get_file(
|
||||||
server_name, request_path, output_stream=f,
|
server_name, request_path, output_stream=f,
|
||||||
|
max_size=self.max_upload_size,
|
||||||
)
|
)
|
||||||
media_type = headers["Content-Type"][0]
|
media_type = headers["Content-Type"][0]
|
||||||
time_now_ms = self.clock.time_msec()
|
time_now_ms = self.clock.time_msec()
|
||||||
|
@ -226,6 +228,14 @@ class BaseMediaResource(Resource):
|
||||||
thumbnailer = Thumbnailer(input_path)
|
thumbnailer = Thumbnailer(input_path)
|
||||||
m_width = thumbnailer.width
|
m_width = thumbnailer.width
|
||||||
m_height = thumbnailer.height
|
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()
|
scales = set()
|
||||||
crops = set()
|
crops = set()
|
||||||
for r_width, r_height, r_method, r_type in requirements:
|
for r_width, r_height, r_method, r_type in requirements:
|
||||||
|
@ -281,6 +291,14 @@ class BaseMediaResource(Resource):
|
||||||
thumbnailer = Thumbnailer(input_path)
|
thumbnailer = Thumbnailer(input_path)
|
||||||
m_width = thumbnailer.width
|
m_width = thumbnailer.width
|
||||||
m_height = thumbnailer.height
|
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()
|
scales = set()
|
||||||
crops = set()
|
crops = set()
|
||||||
for r_width, r_height, r_method, r_type in requirements:
|
for r_width, r_height, r_method, r_type in requirements:
|
||||||
|
|
Loading…
Reference in a new issue