diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0114b69 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI +on: + push: + branches: [master] + pull_request: # run on all PRs, not just PRs to a particular branch +jobs: + test: + strategy: + fail-fast: true + matrix: + nodeversion: [8.x, 10.x, 12.x, 14.x] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.nodeversion }} + - run: npm install + - run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c9045de..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - '8' - - '10' - - '12' - - '14' diff --git a/index.d.ts b/index.d.ts index 696cc7a..adbdd4b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,7 +10,7 @@ type UintArrRet = RawImageData; type ImageData = BufferRet | UintArrRet; type BufferLike = Buffer | Uint8Array | ArrayLike | Iterable | ArrayBuffer; -export declare function encode(imgData: RawImageData, quality?: number): BufferRet; +export declare function encode(imgData: RawImageData & {comments?: string[]}, quality?: number): BufferRet export declare function decode( jpegData: BufferLike, diff --git a/lib/decoder.js b/lib/decoder.js index 4452834..1982481 100644 --- a/lib/decoder.js +++ b/lib/decoder.js @@ -576,7 +576,9 @@ var JpegImage = (function jpegImage() { return array; } function prepareComponents(frame) { - var maxH = 0, maxV = 0; + // According to the JPEG standard, the sampling factor must be between 1 and 4 + // See https://github.com/libjpeg-turbo/libjpeg-turbo/blob/9abeff46d87bd201a952e276f3e4339556a403a3/libjpeg.txt#L1138-L1146 + var maxH = 1, maxV = 1; var component, componentId; for (componentId in frame.components) { if (frame.components.hasOwnProperty(componentId)) { @@ -745,6 +747,11 @@ var JpegImage = (function jpegImage() { var h = data[offset + 1] >> 4; var v = data[offset + 1] & 15; var qId = data[offset + 2]; + + if ( h <= 0 || v <= 0 ) { + throw new Error('Invalid sampling factor, expected values above 0'); + } + frame.componentsOrder.push(componentId); frame.components[componentId] = { h: h, @@ -1123,13 +1130,19 @@ function decode(jpegData, userOpts = {}) { if(decoder.comments.length > 0) { image["comments"] = decoder.comments; } - } catch (err){ - if (err instanceof RangeError){ + } catch (err) { + if (err instanceof RangeError) { throw new Error("Could not allocate enough memory for the image. " + "Required: " + bytesNeeded); - } else { - throw err; + } + + if (err instanceof ReferenceError) { + if (err.message === "Buffer is not defined") { + throw new Error("Buffer is not globally defined in this environment. " + + "Consider setting useTArray to true"); + } } + throw err; } decoder.copyToImageData(image, opts.formatAsRGBA); diff --git a/lib/encoder.js b/lib/encoder.js index bb17d3b..fdbc184 100644 --- a/lib/encoder.js +++ b/lib/encoder.js @@ -535,6 +535,20 @@ function JPEGEncoder(quality) { writeByte(std_ac_chrominance_values[p]); } } + + function writeCOM(comments) + { + if (typeof comments === "undefined" || comments.constructor !== Array) return; + comments.forEach(e => { + if (typeof e !== "string") return; + writeWord(0xFFFE); // marker + var l = e.length; + writeWord(l + 2); // length itself as well + var i; + for (i = 0; i < l; i++) + writeByte(e.charCodeAt(i)); + }); + } function writeSOS() { @@ -625,6 +639,7 @@ function JPEGEncoder(quality) { // Add JPEG headers writeWord(0xFFD8); // SOI writeAPP0(); + writeCOM(image.comments); writeAPP1(image.exifBuffer); writeDQT(); writeSOF0(image.width,image.height); @@ -782,7 +797,7 @@ function encode(imgData, qu) { return { data: data, width: imgData.width, - height: imgData.height + height: imgData.height, }; } diff --git a/test/fixtures/redbox_comment.jpg b/test/fixtures/redbox_comment.jpg new file mode 100644 index 0000000..a0c4235 Binary files /dev/null and b/test/fixtures/redbox_comment.jpg differ diff --git a/test/index.js b/test/index.js index 6944449..7f0edbb 100644 --- a/test/index.js +++ b/test/index.js @@ -7,10 +7,12 @@ function fixture(name) { return fs.readFileSync(path.join(__dirname, 'fixtures', name)); } -const SUPER_LARGE_JPEG_BASE64 = - '/9j/wJ39sP//DlKWvX+7xPlXkJa9f7v8DoDVAAD//zb6QAEAI2cBv3P/r4ADpX8Jf14AAAAAgCPE+VeQlr1/uwCAAAAVALNOjAGP2lIS'; +const SUPER_LARGE_JPEG_BASE64 = '/9j/wfFRBf//BdgC/9p/2P/E4d4='; + +const SUPER_LARGE_RESOLUTION_JPEG_BASE64 = '/9j/wfFR2PDh3g=='; const SUPER_LARGE_JPEG_BUFFER = Buffer.from(SUPER_LARGE_JPEG_BASE64, 'base64'); +const SUPER_LARGE_RESOLUTION_JPEG_BUFFER = Buffer.from(SUPER_LARGE_RESOLUTION_JPEG_BASE64, 'base64'); it('should be able read image with a bad e1 marker not preceeded by ff', function () { var jpegData = fixture('table-with-bad-e1.jpg'); @@ -179,6 +181,32 @@ it('should be able to create a JPEG from an array', function () { expect(jpegImageData.data).toEqual(expected); }); +it('should be able to create a JPEG from an array with comment', function () { + var width = 320, + height = 180; + var comments = ["First comment", "Second comment"]; + var frameData = new Buffer(width * height * 4); + var i = 0; + while (i < frameData.length) { + frameData[i++] = 0xff; // red + frameData[i++] = 0x00; // green + frameData[i++] = 0x00; // blue + frameData[i++] = 0xff; // alpha - ignored in JPEGs + } + var rawImageData = { + data: frameData, + width: width, + height: height, + comments: comments, + }; + var jpegImageData = jpeg.encode(rawImageData, 50); + expect(jpegImageData.width).toEqual(width); + expect(jpegImageData.height).toEqual(height); + var expected = fixture('redbox_comment.jpg'); + expect(jpegImageData.data).toEqual(expected); + expect(jpeg.decode(jpegImageData.data).comments).toEqual(['First comment', 'Second comment']); +}); + it('should be able to decode a JPEG into a typed array', function () { var jpegData = fixture('grumpycat.jpg'); var rawImageData = jpeg.decode(jpegData, {useTArray: true}); @@ -248,8 +276,8 @@ it('should be able to decode large images within memory limits', () => { // See https://github.com/eugeneware/jpeg-js/issues/53 it('should limit resolution exposure', function () { - expect(() => jpeg.decode(SUPER_LARGE_JPEG_BUFFER)).toThrow( - 'maxResolutionInMP limit exceeded by 141MP', + expect(() => jpeg.decode(SUPER_LARGE_RESOLUTION_JPEG_BUFFER)).toThrow( + 'maxResolutionInMP limit exceeded by 3405MP', ); }); @@ -262,3 +290,9 @@ it('should limit memory exposure', function () { var jpegData = fixture('grumpycat.jpg'); expect(() => jpeg.decode(jpegData)).not.toThrow(); }, 30000); + +// See https://github.com/jpeg-js/jpeg-js/issues/105 +it('invalid sampling factor should error out', function () { + expect(() => jpeg.decode(Buffer.from('/9j/wfFR2AD/UdgA/9r/3g==', 'base64')).toThrow( + 'Invalid sampling factor, expected values above 0')) +}); diff --git a/yarn.lock b/yarn.lock index 8be955b..5ea1834 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,9 +1477,9 @@ has-values@^1.0.0: kind-of "^4.0.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== html-encoding-sniffer@^1.0.2: version "1.0.2" @@ -2251,9 +2251,9 @@ lodash.sortby@^4.7.0: integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= lodash@^4.17.13, lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== lolex@^5.0.0: version "5.1.2" @@ -3443,9 +3443,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.0.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xml-name-validator@^3.0.0: version "3.0.0" @@ -3458,9 +3458,9 @@ xmlchars@^2.1.1: integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== yargs-parser@^18.1.1: version "18.1.3" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy