Skip to content

Commit

Permalink
Add secondGuessSourceContentType mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Apr 14, 2016
1 parent a8941f5 commit 6268507
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 53 deletions.
150 changes: 97 additions & 53 deletions lib/processImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,70 +104,114 @@ module.exports = function (options) {

if (contentType && (contentType.indexOf('image/') === 0 || isMetadataRequest)) {
var contentLengthHeaderValue = res.getHeader('Content-Length');
var filterInfosAndTargetFormat = getFilterInfosAndTargetContentTypeFromQueryString(queryString, _.defaults({

var sourceMetadata = {
contentType: contentType,
filesize: contentLengthHeaderValue && parseInt(contentLengthHeaderValue, 10),
etag: res.getHeader('ETag')
};

function makeFilterInfosAndTargetFormat() {
return getFilterInfosAndTargetContentTypeFromQueryString(queryString, _.defaults({
allowOperation: options.allowOperation,
sourceFilePath: options.root && Path.resolve(options.root, req.url.substr(1)),
sourceMetadata: {
contentType: contentType,
filesize: contentLengthHeaderValue && parseInt(contentLengthHeaderValue, 10),
etag: res.getHeader('ETag')
}
}, options)),
targetContentType = filterInfosAndTargetFormat.targetContentType;
sourceMetadata: sourceMetadata
}, options));
}

var filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();

if (filterInfosAndTargetFormat.filterInfos.length === 0) {
return res.unhijack(true);
}
if (targetContentType) {
res.setHeader('Content-Type', targetContentType);
}
res.removeHeader('Content-Length');
var oldETag = res.getHeader('ETag'),
newETag;
if (oldETag) {
newETag = oldETag.replace(/"$/g, '-processimage"');
res.setHeader('ETag', newETag);

if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
return res.status(304).end();
}
}
try {
filterInfosAndTargetFormat.filterInfos.forEach(function (filterInfo) {
var filter = filterInfo.create();
if (Array.isArray(filter)) {
Array.prototype.push.apply(filters, filter);
} else {
filters.push(filter);
if (options.secondGuessSourceContentType) {
var endOrCloseOrErrorBeforeFirstDataChunkListener = function (err) {
res.unhijack();
if (err) {
next(500);
}
};
res.once('error', endOrCloseOrErrorBeforeFirstDataChunkListener);
res.once('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
res.once('data', function (firstChunk) {
res.removeListener('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
res.removeListener('close', endOrCloseOrErrorBeforeFirstDataChunkListener);
var detectedContentType;
if (firstChunk[0] === 0x47 && firstChunk[1] === 0x49 && firstChunk[2] === 0x46) {
detectedContentType = 'image/gif';
} else if (firstChunk[0] === 0xff && firstChunk[1] === 0xd8) {
detectedContentType = 'image/jpeg';
} else if (firstChunk[0] === 0x89 && firstChunk[1] === 0x50 && firstChunk[2] === 0x4e && firstChunk[3] === 0x47) {
detectedContentType = 'image/png';
} else if (firstChunk[0] === 0x42 && firstChunk[1] === 0x4d) {
detectedContentType = 'image/bmp';
}
if (detectedContentType && detectedContentType !== sourceMetadata.contentType) {
sourceMetadata.contentType = detectedContentType;
filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();
}
startProcessing(firstChunk);
});
} catch (e) {
return handleError(new httpErrors.BadRequest(e));
} else {
startProcessing();
}
if (options.debug) {
// Only used by the test suite to assert that the right engine is used to process gifs:
res.setHeader('X-Express-Processimage', filterInfosAndTargetFormat.filterInfos.map(function (filterInfo) {
return filterInfo.operationName;
}).join(','));
}
for (var i = 0 ; i < filters.length ; i += 1) {
if (i < filters.length - 1) {
filters[i].pipe(filters[i + 1]);

function startProcessing(optionalFirstChunk) {
res.once('error', function () {
res.unhijack();
next(500);
});
res.once('close', cleanUp);
var targetContentType = filterInfosAndTargetFormat.targetContentType;
if (targetContentType) {
res.setHeader('Content-Type', targetContentType);
}
// Some of the filters appear to emit error more than once:
filters[i].once('error', handleError);
}
res.removeHeader('Content-Length');
var oldETag = res.getHeader('ETag'),
newETag;
if (oldETag) {
newETag = oldETag.replace(/"$/g, '-processimage"');
res.setHeader('ETag', newETag);

res.pipe(filters[0]);
filters[filters.length - 1].on('end', function () {
hasEnded = true;
cleanUp();
}).pipe(res);
if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
return res.status(304).end();
}
}
try {
filterInfosAndTargetFormat.filterInfos.forEach(function (filterInfo) {
var filter = filterInfo.create();
if (Array.isArray(filter)) {
Array.prototype.push.apply(filters, filter);
} else {
filters.push(filter);
}
});
} catch (e) {
return handleError(new httpErrors.BadRequest(e));
}
if (options.debug) {
// Only used by the test suite to assert that the right engine is used to process gifs:
res.setHeader('X-Express-Processimage', filterInfosAndTargetFormat.filterInfos.map(function (filterInfo) {
return filterInfo.operationName;
}).join(','));
}
if (optionalFirstChunk) {
filters[0].write(optionalFirstChunk);
}
for (var i = 0 ; i < filters.length ; i += 1) {
if (i < filters.length - 1) {
filters[i].pipe(filters[i + 1]);
}
// Some of the filters appear to emit error more than once:
filters[i].once('error', handleError);
}

res.once('error', function () {
res.unhijack();
next(500);
});
res.once('close', cleanUp);
res.pipe(filters[0]);
filters[filters.length - 1].on('end', function () {
hasEnded = true;
cleanUp();
}).pipe(res);
}
} else {
res.unhijack();
}
Expand Down
57 changes: 57 additions & 0 deletions test/processImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -837,4 +837,61 @@ describe('express-processimage', function () {
});
});
});

describe('with secondGuessSourceContentType=true', function () {
beforeEach(function () {
config.secondGuessSourceContentType = true;
});
it('should recover gracefully when attempting to process a wrongly named jpeg', function () {
config.debug = true;
return expect('GET /reallyajpeg.gif?resize=40,35', 'to yield response', {
headers: {
'X-Express-Processimage': 'sharp'
},
body: expect.it('to have metadata satisfying', {
format: 'JPEG',
size: { width: 40, height: 35 }
})
});
});

it('should recover gracefully when attempting to process a wrongly named png', function () {
config.debug = true;
return expect('GET /reallyapng.gif?resize=40,35', 'to yield response', {
headers: {
'X-Express-Processimage': 'sharp'
},
body: expect.it('to have metadata satisfying', {
format: 'PNG',
size: { width: 40, height: 35 }
})
});
});

it('should recover gracefully when attempting to process a wrongly named gif', function () {
config.debug = true;
return expect('GET /reallyagif.jpeg?resize=40,35', 'to yield response', {
headers: {
'X-Express-Processimage': 'gifsicle'
},
body: expect.it('to have metadata satisfying', {
format: 'GIF',
size: { width: 35, height: 35 }
})
});
});

it('should recover gracefully when attempting to process a wrongly named bmp', function () {
config.debug = true;
return expect('GET /reallyabmp.gif?gm&resize=40,35', 'to yield response', {
headers: {
'X-Express-Processimage': 'gm'
},
body: expect.it('to have metadata satisfying', {
format: 'BMP',
size: { width: 40, height: 25 }
})
});
});
});
});
Binary file added testdata/reallyabmp.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testdata/reallyagif.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testdata/reallyajpeg.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testdata/reallyapng.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6268507

Please sign in to comment.