Skip to content

Commit

Permalink
Added timeout when resizing an image (#20087)
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 30, 2024
1 parent 3d6fae3 commit 4cd85ab
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 4cd85ab

Please sign in to comment.