diff --git a/.travis.yml b/.travis.yml index e7e3fca5f..e969c0b83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,10 @@ language: node_js +before_install: + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo add-apt-repository -y ppa:mc3man/trusty-media; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get update; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install -y ffmpeg; fi + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update; fi + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install ffmpeg; fi install: - npm install --build-from-source node_js: @@ -18,4 +24,4 @@ addons: - g++-4.9 env: - CXX=g++-4.9 -sudo: false +sudo: required diff --git a/lib/image.js b/lib/image.js index f373d849c..0a8d080e3 100644 --- a/lib/image.js +++ b/lib/image.js @@ -10,10 +10,12 @@ * Module dependencies. */ -const bindings = require('./bindings') -const Image = module.exports = bindings.Image -const http = require("http") -const https = require("https") +const bindings = require('./bindings'); +const Image = module.exports = bindings.Image; +const fs = require('fs'); +const cp = require('child_process'); +const http = require('http'); +const https = require('https'); Object.defineProperty(Image.prototype, 'src', { /** @@ -33,8 +35,7 @@ Object.defineProperty(Image.prototype, 'src', { // 'base64' must come before the comma const isBase64 = val.lastIndexOf('base64', commaI) const content = val.slice(commaI + 1) - this.source = Buffer.from(content, isBase64 ? 'base64' : 'utf8') - this._originalSource = val + setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val); } else if (/^\s*https?:\/\//.test(val)) { // remote URL const onerror = err => { if (typeof this.onerror === 'function') { @@ -52,17 +53,14 @@ Object.defineProperty(Image.prototype, 'src', { const buffers = [] res.on('data', buffer => buffers.push(buffer)) res.on('end', () => { - this.source = Buffer.concat(buffers) - this._originalSource = undefined + setSource(this, Buffer.concat(buffers)); }) }).on('error', onerror) } else { // local file path assumed - this.source = val - this._originalSource = undefined + setSource(this, val); } } else if (Buffer.isBuffer(val)) { - this.source = val - this._originalSource = undefined + setSource(this, val); } }, @@ -90,3 +88,47 @@ Image.prototype.inspect = function(){ + (this.complete ? ' complete' : '') + ']'; }; + +function setSource(img, val, origSrc){ + var {onload, onerror} = img; + var isBuff = Buffer.isBuffer(val); + + img.onload = () => { + restore(); + if(onload) img.onload(); + }; + + img.onerror = err => { + img.onerror = () => { + restore(); + if(onerror) img.onerror(err); + }; + + var buff = cp.spawnSync('ffmpeg', [ + '-hide_banner', + '-i', isBuff ? '-' : val, + + '-vframes', '1', + '-f', 'apng', + '-', + ], { + input: isBuff ? val : undefined, + }).stdout; + + if(buff === null || buff.length === 0){ + img.onerror(); + return; + } + + origSrc = val; + img.source = buff; + }; + + img.source = val; + + function restore(){ + img.onload = onload; + img.onerror = onerror; + img._originalSource = origSrc; + } +} diff --git a/test/fixtures/159-crash1.jpg b/test/fixtures/159-crash1.jpg index 0f3bbd0a7..99e33418c 100644 Binary files a/test/fixtures/159-crash1.jpg and b/test/fixtures/159-crash1.jpg differ diff --git a/test/fixtures/obscure/1160.jpg b/test/fixtures/obscure/1160.jpg new file mode 100644 index 000000000..752891810 Binary files /dev/null and b/test/fixtures/obscure/1160.jpg differ diff --git a/test/fixtures/obscure/122.png b/test/fixtures/obscure/122.png new file mode 100644 index 000000000..d8899c68b Binary files /dev/null and b/test/fixtures/obscure/122.png differ diff --git a/test/fixtures/obscure/562.webp b/test/fixtures/obscure/562.webp new file mode 100644 index 000000000..a60d0a531 Binary files /dev/null and b/test/fixtures/obscure/562.webp differ diff --git a/test/fixtures/obscure/73.png b/test/fixtures/obscure/73.png new file mode 100644 index 000000000..ac34a6b81 Binary files /dev/null and b/test/fixtures/obscure/73.png differ diff --git a/test/fixtures/obscure/953.bmp b/test/fixtures/obscure/953.bmp new file mode 100644 index 000000000..e607826d4 Binary files /dev/null and b/test/fixtures/obscure/953.bmp differ diff --git a/test/image.test.js b/test/image.test.js index bac0d6605..c0a0e7af8 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -12,6 +12,7 @@ const Image = require('../').Image const assert = require('assert') const assertRejects = require('assert-rejects') const fs = require('fs') +const path = require('path') const png_checkers = `${__dirname}/fixtures/checkers.png` const png_clock = `${__dirname}/fixtures/clock.png` @@ -268,4 +269,45 @@ describe('Image', function () { return Promise.all(corruptSources.map(src => loadImage(src).catch(() => null))) }) + + describe('Obscure image formats', () => { + var imgsDir = path.join(__dirname, '/fixtures/obscure'); + var getNum = name => +name.match(/^\d+/)[0]; + + var imgNames = fs.readdirSync(imgsDir).sort((n1, n2) => { + n1 = getNum(n1); + n2 = getNum(n2); + return (n1 > n2) - (n1 < n2); + }); + + var test = description => { + var imgName = imgNames.shift(); + var imgPath = path.join(imgsDir, imgName); + var num = getNum(imgName); + + it(`${description} (#${num})`, done => { + var img = new Image(); + + img.onload = () => { + assert.strictEqual(img.width, 64); + assert.strictEqual(img.height, 64); + done(); + }; + + img.onerror = err => { + throw err; + }; + + img.src = imgPath; + }); + }; + + [ + 'Dubious gAMA chunk', + 'PNG with cHRM chunk', + 'WEBP format', + 'BMP format', + 'JPEG with precision set to 12', + ].forEach(test); + }); })