diff --git a/src/Image.cc b/src/Image.cc index 0147f878f..6b957fa8a 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -19,6 +19,14 @@ typedef struct { } gif_data_t; #endif +#ifdef HAVE_JPEG +#include + +struct canvas_jpeg_error_mgr: jpeg_error_mgr { + jmp_buf setjmp_buffer; +}; +#endif + /* * Read closure used by loadFromBuffer. */ @@ -752,6 +760,17 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) { return CAIRO_STATUS_SUCCESS; } +/* + * Callback to recover from jpeg errors + */ + +METHODDEF(void) canvas_jpeg_error_exit (j_common_ptr cinfo) { + canvas_jpeg_error_mgr *cjerr = static_cast(cinfo->err); + + // Return control to the setjmp point + longjmp(cjerr->setjmp_buffer, 1); +} + #if CAIRO_VERSION_MINOR >= 10 /* @@ -764,8 +783,19 @@ Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) { // TODO: remove this duplicate logic // JPEG setup struct jpeg_decompress_struct args; - struct jpeg_error_mgr err; + struct canvas_jpeg_error_mgr err; + args.err = jpeg_std_error(&err); + args.err->error_exit = canvas_jpeg_error_exit; + + // Establish the setjmp return context for canvas_jpeg_error_exit to use + if (setjmp(err.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_decompress(&args); + return CAIRO_STATUS_READ_ERROR; + } + jpeg_create_decompress(&args); jpeg_mem_src(&args, buf, len); @@ -858,8 +888,19 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { // TODO: remove this duplicate logic // JPEG setup struct jpeg_decompress_struct args; - struct jpeg_error_mgr err; + struct canvas_jpeg_error_mgr err; + args.err = jpeg_std_error(&err); + args.err->error_exit = canvas_jpeg_error_exit; + + // Establish the setjmp return context for canvas_jpeg_error_exit to use + if (setjmp(err.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_decompress(&args); + return CAIRO_STATUS_READ_ERROR; + } + jpeg_create_decompress(&args); jpeg_mem_src(&args, buf, len); @@ -883,8 +924,19 @@ Image::loadJPEG(FILE *stream) { if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG. // JPEG setup struct jpeg_decompress_struct args; - struct jpeg_error_mgr err; + struct canvas_jpeg_error_mgr err; + args.err = jpeg_std_error(&err); + args.err->error_exit = canvas_jpeg_error_exit; + + // Establish the setjmp return context for canvas_jpeg_error_exit to use + if (setjmp(err.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_decompress(&args); + return CAIRO_STATUS_READ_ERROR; + } + jpeg_create_decompress(&args); jpeg_stdio_src(&args, stream); diff --git a/test/fixtures/chrome.jpg b/test/fixtures/chrome.jpg new file mode 100644 index 000000000..29fd36ae8 Binary files /dev/null and b/test/fixtures/chrome.jpg differ diff --git a/test/image.test.js b/test/image.test.js index ae39d6207..8f7116f6e 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -5,10 +5,12 @@ var Canvas = require('../') , Image = Canvas.Image - , assert = require('assert'); + , assert = require('assert') + , fs = require('fs'); var png_checkers = __dirname + '/fixtures/checkers.png'; var png_clock = __dirname + '/fixtures/clock.png'; +var jpg_chrome = __dirname + '/fixtures/chrome.jpg' describe('Image', function () { it('should require new', function () { @@ -206,4 +208,35 @@ describe('Image', function () { assert.equal(img.src, png_clock + 's3'); assert.equal(onerrorCalled, 0); }); -}); + + it('does not crash on invalid images', function () { + function tryImage (src) { + var img = new Image() + img.src = src + // if we came this far we didn't crash! + } + + function withIncreasedByte (source, index) { + var copy = source.slice(0) + + copy[index] += 1 + + return copy + } + + var source = fs.readFileSync(jpg_chrome) + + tryImage(withIncreasedByte(source, 0)) + tryImage(withIncreasedByte(source, 1)) + tryImage(withIncreasedByte(source, 1060)) + tryImage(withIncreasedByte(source, 1061)) + tryImage(withIncreasedByte(source, 1062)) + tryImage(withIncreasedByte(source, 1063)) + tryImage(withIncreasedByte(source, 1064)) + tryImage(withIncreasedByte(source, 1065)) + tryImage(withIncreasedByte(source, 1066)) + tryImage(withIncreasedByte(source, 1067)) + tryImage(withIncreasedByte(source, 1068)) + tryImage(withIncreasedByte(source, 1069)) + }) +})