diff --git a/doc/configuration.md b/doc/configuration.md index 7d8621e6..7a350996 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -54,6 +54,10 @@ Any options you add here will be passed through to the resolver you implement. F Probably safe to leave these as-is unless you care about something very specific. See the [Developer Notes](develop.md#image-transformations) for when this may not be the case. The exceptions are `kdu_expand` and `kdu_libs` in the `[transforms.jp2]` (see [Installing Dependencies](dependencies.md) step 2) or if you're not concerned about color profiles (see next). +If you are seeing `DecompressionBombError`s thrown for very large source images, try setting the `pil_max_image_pixels` property here. This error occurs for images that are larger than **2x** PIL's MAX_IMAGE_PIXELS property, which is `1024 * 1024 * 1024 // 4 // 3 = 89478485` by default (and so the error is thrown for images larger than `2 * 89478485 = 178956970` pixels). Note that PIL will still log a `DecompressionBombWarning` will still be logged if the image is bigger than **1x** the configured value. + +If `pil_max_image_pixels` is set to `0`, `PIL.Image.MAX_IMAGE_PIXELS` is set to `None` and there is no limit on image size. + ### map_profile_to_srgb You can tell Loris to map embedded color profiles to sRGB with the following settings in your transformer: diff --git a/etc/loris2.conf b/etc/loris2.conf index ca73aa97..cd5d2222 100644 --- a/etc/loris2.conf +++ b/etc/loris2.conf @@ -107,6 +107,12 @@ dither_bitonal_images = False # To enable TIFF output, add "tif" here: target_formats = ['jpg','png','gif','webp'] +# By default PIL throws a DecompressionBombError for images that are larger than +# 2x its MAX_IMAGE_PIXELS property (this limit is 2 * 89478485 = 178956970px). +# This property can be overridden by this config value. If set to 0, MAX_IMAGE_PIXELS +# is set to `None` and there is no limit on image size. +# pil_max_image_pixels = 250000000 + [[jpg]] impl = 'JPG_Transformer' diff --git a/loris/webapp.py b/loris/webapp.py index 54f2db1a..9a7d17a5 100755 --- a/loris/webapp.py +++ b/loris/webapp.py @@ -19,6 +19,7 @@ sys.path.append('.') from configobj import ConfigObj +from PIL import Image from werkzeug.http import parse_date, http_date from werkzeug.wrappers import ( @@ -379,13 +380,21 @@ def _load_transformers(self): tforms = self.app_configs['transforms'] source_formats = [k for k in tforms if isinstance(tforms[k], dict)] self.logger.debug('Source formats: %r', source_formats) - global_tranform_options = dict((k, v) for k, v in tforms.items() if not isinstance(v, dict)) - self.logger.debug('Global transform options: %r', global_tranform_options) + global_transform_options = dict((k, v) for k, v in tforms.items() if not isinstance(v, dict)) + self.logger.debug('Global transform options: %r', global_transform_options) + + pil_max_image_pixels = tforms.get('pil_max_image_pixels', Image.MAX_IMAGE_PIXELS) + if pil_max_image_pixels != 0: + Image.MAX_IMAGE_PIXELS = pil_max_image_pixels + self.logger.debug('PIL maximum image pixels set to: %d', pil_max_image_pixels) + else: + Image.MAX_IMAGE_PIXELS = None + self.logger.debug('PIL maximum image pixels limit removed.') transformers = {} for sf in source_formats: # merge [transforms] options and [transforms][source_format]] options - config = dict(list(self.app_configs['transforms'][sf].items()) + list(global_tranform_options.items())) + config = dict(list(self.app_configs['transforms'][sf].items()) + list(global_transform_options.items())) transformers[sf] = self._load_transformer(config) return transformers diff --git a/tests/transforms_t.py b/tests/transforms_t.py index 4339961f..03a2c97c 100644 --- a/tests/transforms_t.py +++ b/tests/transforms_t.py @@ -2,6 +2,7 @@ import operator import pytest +from PIL import Image from loris import transforms from loris.loris_exception import ConfigError @@ -344,3 +345,17 @@ def test_can_transform_transparent_png_as_nontransparent_format(self): ident = 'png_with_transparency.png' request_path = '/%s/full/full/0/default.jpg' % ident self.request_image_from_client(request_path) + + def test_respects_pil_max_image_pixels(self): + default_max_pixels = Image.MAX_IMAGE_PIXELS + try: + config = get_debug_config('kdu') + config['transforms']['pil_max_image_pixels'] = 1 + self.build_client_from_config(config) + with pytest.raises(Image.DecompressionBombError): + request_path = '/%s/full/300,300/0/default.jpg' % self.ident + self.request_image_from_client(request_path) + finally: + # Because we have to mutate a value in an external library + # it must be reset at the end of this test + Image.MAX_IMAGE_PIXELS = default_max_pixels