Skip to content

Commit

Permalink
Added timeout when resizing an image
Browse files Browse the repository at this point in the history
refs [ENG-827](https://linear.app/tryghost/issue/ENG-827/馃悰-crash-on-resizing-animated-gif)

Added a timeout to the image resizing middleware to prevent crashes when an
image is taking too long to resize. When the timeout is reached and the image
has not been resized, the middleware will return the original image
  • Loading branch information
mike182uk committed Apr 29, 2024
1 parent 2eb6f86 commit 611b531
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 12 deletions.
12 changes: 10 additions & 2 deletions ghost/core/core/frontend/web/middleware/handle-image-sizes.js
Expand Up @@ -9,9 +9,10 @@ const {imageSize} = require('../../../server/lib/image');

const SIZE_PATH_REGEX = /^\/size\/([^/]+)\//;
const FORMAT_PATH_REGEX = /^\/format\/([^./]+)\//;

const TRAILING_SLASH_REGEX = /\/+$/;

const RESIZE_TIMEOUT_SECONDS = 10;

module.exports = function handleImageSizes(req, res, next) {
// In admin we need to read images and calculate the average color (blocked by CORS otherwise)
res.setHeader('Access-Control-Allow-Origin', '*');
Expand Down Expand Up @@ -123,7 +124,12 @@ module.exports = function handleImageSizes(req, res, next) {
if (originalImageBuffer.length <= 0) {
throw new NoContentError();
}
return imageTransform.resizeFromBuffer(originalImageBuffer, {withoutEnlargement: requestUrlFileExtension !== '.svg', ...imageDimensionConfig, format});
return imageTransform.resizeFromBuffer(originalImageBuffer, {
withoutEnlargement: requestUrlFileExtension !== '.svg',
...imageDimensionConfig,
format,
timeout: RESIZE_TIMEOUT_SECONDS
});
})
.then((resizedImageBuffer) => {
return storageInstance.saveRaw(resizedImageBuffer, req.url);
Expand All @@ -147,3 +153,5 @@ module.exports = function handleImageSizes(req, res, next) {
next(err);
});
};

module.exports.RESIZE_TIMEOUT_SECONDS = RESIZE_TIMEOUT_SECONDS;
2 changes: 1 addition & 1 deletion ghost/core/package.json
Expand Up @@ -94,7 +94,7 @@
"@tryghost/html-to-plaintext": "0.0.0",
"@tryghost/http-cache-utils": "0.1.11",
"@tryghost/i18n": "0.0.0",
"@tryghost/image-transform": "1.2.11",
"@tryghost/image-transform": "1.3.0",
"@tryghost/importer-handler-content-files": "0.0.0",
"@tryghost/importer-revue": "0.0.0",
"@tryghost/job-manager": "0.0.0",
Expand Down
Expand Up @@ -294,6 +294,47 @@ describe('handleImageSizes middleware', function () {
});
});

it('redirects if timeout is exceeded', function (done) {
sinon.stub(imageTransform, 'canTransformFiles').returns(true);

dummyStorage.exists = async function () {
return false;
};

dummyStorage.read = async function () {
return buffer;
};

const error = new Error('Resize timeout');
error.code = 'IMAGE_PROCESSING';

resizeFromBufferStub.throws(error);

const fakeReq = {
url: '/size/w1000/blank.png',
originalUrl: '/blog/content/images/size/w1000/blank.png'
};

const fakeRes = {
redirect(url) {
try {
url.should.equal('/blog/content/images/blank.png');
} catch (e) {
return done(e);
}
done();
},
setHeader() {}
};

handleImageSizes(fakeReq, fakeRes, function next(err) {
if (err) {
return done(err);
}
done(new Error('Should not have called next'));
});
});

it('continues if file exists', function (done) {
dummyStorage.exists = async function (path) {
if (path === '/size/w1000/blank.png') {
Expand Down Expand Up @@ -526,7 +567,12 @@ describe('handleImageSizes middleware', function () {
return done(err);
}
try {
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: false, width: 1000, format: 'png'}).should.be.true();
resizeFromBufferStub.calledOnceWithExactly(buffer, {
withoutEnlargement: false,
width: 1000,
format: 'png',
timeout: handleImageSizes.RESIZE_TIMEOUT_SECONDS
}).should.be.true();
typeStub.calledOnceWithExactly('png').should.be.true();
} catch (e) {
return done(e);
Expand Down Expand Up @@ -561,7 +607,12 @@ describe('handleImageSizes middleware', function () {
return done(err);
}
try {
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: true, width: 1000, format: 'webp'}).should.be.true();
resizeFromBufferStub.calledOnceWithExactly(buffer, {
withoutEnlargement: true,
width: 1000,
format: 'webp',
timeout: handleImageSizes.RESIZE_TIMEOUT_SECONDS
}).should.be.true();
typeStub.calledOnceWithExactly('webp').should.be.true();
} catch (e) {
return done(e);
Expand Down Expand Up @@ -596,7 +647,12 @@ describe('handleImageSizes middleware', function () {
return done(err);
}
try {
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: true, width: 1000, format: 'avif'}).should.be.true();
resizeFromBufferStub.calledOnceWithExactly(buffer, {
withoutEnlargement: true,
width: 1000,
format: 'avif',
timeout: handleImageSizes.RESIZE_TIMEOUT_SECONDS
}).should.be.true();
typeStub.calledOnceWithExactly('image/avif').should.be.true();
} catch (e) {
return done(e);
Expand Down Expand Up @@ -631,7 +687,12 @@ describe('handleImageSizes middleware', function () {
return done(err);
}
try {
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: true, width: 1000, format: 'webp'}).should.be.true();
resizeFromBufferStub.calledOnceWithExactly(buffer, {
withoutEnlargement: true,
width: 1000,
format: 'webp',
timeout: handleImageSizes.RESIZE_TIMEOUT_SECONDS
}).should.be.true();
typeStub.calledOnceWithExactly('webp').should.be.true();
} catch (e) {
return done(e);
Expand Down Expand Up @@ -666,7 +727,12 @@ describe('handleImageSizes middleware', function () {
return done(err);
}
try {
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: true, width: 1000, format: 'gif'}).should.be.true();
resizeFromBufferStub.calledOnceWithExactly(buffer, {
withoutEnlargement: true,
width: 1000,
format: 'gif',
timeout: handleImageSizes.RESIZE_TIMEOUT_SECONDS
}).should.be.true();
typeStub.calledOnceWithExactly('gif').should.be.true();
} catch (e) {
return done(e);
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -7066,10 +7066,10 @@
"@tryghost/errors" "^1.3.1"
"@tryghost/request" "^1.0.3"

"@tryghost/image-transform@1.2.11":
version "1.2.11"
resolved "https://registry.yarnpkg.com/@tryghost/image-transform/-/image-transform-1.2.11.tgz#82463d97f8747db6db70165a04e824eed6791fee"
integrity sha512-O4DRZw3lXj9E4LCV8Mm/gMchhyH9rq4/4h4f4+8tb/dhanz7DMhP5yXHH4WBooF2SG1HWV/XITVSY1erFYQFyA==
"@tryghost/image-transform@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@tryghost/image-transform/-/image-transform-1.3.0.tgz#e68e630c4e42e1af193a18894a864e9e2da9fc1f"
integrity sha512-WQFSMh1eAWbZqiheMR5TuTOTll4RDRKZ5p/JIOvejiBzgu/OLBLGKrFKpPb9JSVySTFxpj1dInKmMu8z1so7nQ==
dependencies:
"@tryghost/errors" "^1.2.26"
fs-extra "^11.0.0"
Expand Down

0 comments on commit 611b531

Please sign in to comment.