diff --git a/docs/changelog.md b/docs/changelog.md index 985f2b64..924f3f29 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -18,6 +18,9 @@ Requires libvips v8.12.0 [#2976](https://github.com/lovell/sharp/pull/2976) [@driannaude](https://github.com/driannaude) +* Expose `unlimited` option for SVG and PNG input, switches off safety features. + [#2984](https://github.com/lovell/sharp/issues/2984) + ## v0.29 - *circle* Requires libvips v8.11.3 diff --git a/lib/constructor.js b/lib/constructor.js index 210ea4a2..6e726624 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -103,6 +103,7 @@ const debuglog = util.debuglog('sharp'); * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). + * @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG). * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible. * This can reduce memory usage and might improve performance on some systems. * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000. diff --git a/lib/input.js b/lib/input.js index 5dd2f0e9..177f07fc 100644 --- a/lib/input.js +++ b/lib/input.js @@ -9,9 +9,9 @@ const sharp = require('./sharp'); * @private */ function _inputOptionsFromObject (obj) { - const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd } = obj; - return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined) - ? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd } + const { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd } = obj; + return [raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined) + ? { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd } : undefined; } @@ -23,6 +23,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { const inputDescriptor = { failOnError: true, limitInputPixels: Math.pow(0x3FFF, 2), + unlimited: false, sequentialRead: false }; if (is.string(input)) { @@ -83,6 +84,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels); } } + // unlimited + if (is.defined(inputOptions.unlimited)) { + if (is.bool(inputOptions.unlimited)) { + inputDescriptor.unlimited = inputOptions.unlimited; + } else { + throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited); + } + } // sequentialRead if (is.defined(inputOptions.sequentialRead)) { if (is.bool(inputOptions.sequentialRead)) { diff --git a/src/common.cc b/src/common.cc index 5608598f..b8d66ac1 100644 --- a/src/common.cc +++ b/src/common.cc @@ -132,6 +132,8 @@ namespace sharp { descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels"); // Allow switch from random to sequential access descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; + // Remove safety features and allow unlimited SVG/PNG input + descriptor->unlimited = AttrAsBool(input, "unlimited"); return descriptor; } @@ -328,7 +330,7 @@ namespace sharp { vips::VOption *option = VImage::option() ->set("access", descriptor->access) ->set("fail", descriptor->failOnError); - if (imageType == ImageType::SVG) { + if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) { option->set("unlimited", TRUE); } if (imageType == ImageType::SVG || imageType == ImageType::PDF) { @@ -403,7 +405,7 @@ namespace sharp { vips::VOption *option = VImage::option() ->set("access", descriptor->access) ->set("fail", descriptor->failOnError); - if (imageType == ImageType::SVG) { + if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) { option->set("unlimited", TRUE); } if (imageType == ImageType::SVG || imageType == ImageType::PDF) { diff --git a/src/common.h b/src/common.h index e4dd244a..cc0bd8af 100644 --- a/src/common.h +++ b/src/common.h @@ -50,6 +50,7 @@ namespace sharp { char *buffer; bool failOnError; int limitInputPixels; + bool unlimited; VipsAccess access; size_t bufferLength; bool isBuffer; @@ -75,6 +76,7 @@ namespace sharp { buffer(nullptr), failOnError(TRUE), limitInputPixels(0x3FFF * 0x3FFF), + unlimited(FALSE), access(VIPS_ACCESS_RANDOM), bufferLength(0), isBuffer(FALSE), diff --git a/test/unit/io.js b/test/unit/io.js index 628e2f89..e0266316 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -648,6 +648,19 @@ describe('Input/output', function () { }); }); + describe('Switch off safety limits for PNG/SVG input', () => { + it('Valid', () => { + assert.doesNotThrow(() => { + sharp({ unlimited: true }); + }); + }); + it('Invalid', () => { + assert.throws(() => { + sharp({ unlimited: -1 }); + }, /Expected boolean for unlimited but received -1 of type number/); + }); + }); + describe('Limit pixel count of input image', () => { it('Invalid fails - negative', () => { assert.throws(() => { @@ -671,7 +684,9 @@ describe('Input/output', function () { sharp(fixtures.inputJpg) .metadata() .then(({ width, height }) => - sharp(fixtures.inputJpg, { limitInputPixels: width * height }).toBuffer() + sharp(fixtures.inputJpg, { limitInputPixels: width * height }) + .resize(2) + .toBuffer() ) );