Attempt to handle DNL (Define Number of Lines) markers when parsing JPEG images (issue 8614)

Please refer to the specification, found at https://www.w3.org/Graphics/JPEG/itu-t81.pdf#page=49

Given how the JPEG decoder is currently implemented, we need to know the value of the scanLines parameter (among others) *before* parsing of the SOS (Start of Scan) data begins.
Hence the best solution I could come up with here, is to re-parse the image in the *hopefully* rare case of JPEG images that include a DNL (Define Number of Lines) marker.

Fixes 8614.
This commit is contained in:
Jonas Jenwald 2018-02-01 20:43:01 +01:00
parent 80441346a3
commit bf4166e6c9
3 changed files with 62 additions and 8 deletions

View File

@ -28,6 +28,19 @@ let JpegError = (function JpegErrorClosure() {
return JpegError;
})();
let DNLMarkerError = (function DNLMarkerErrorClosure() {
function DNLMarkerError(message, scanLines) {
this.message = message;
this.scanLines = scanLines;
}
DNLMarkerError.prototype = new Error();
DNLMarkerError.prototype.name = 'DNLMarkerError';
DNLMarkerError.constructor = DNLMarkerError;
return DNLMarkerError;
})();
/**
* This code was forked from https://github.com/notmasteryet/jpgjs.
* The original version was created by GitHub user notmasteryet.
@ -112,7 +125,8 @@ var JpegImage = (function JpegImageClosure() {
}
function decodeScan(data, offset, frame, components, resetInterval,
spectralStart, spectralEnd, successivePrev, successive) {
spectralStart, spectralEnd, successivePrev, successive,
parseDNLMarker = false) {
var mcusPerLine = frame.mcusPerLine;
var progressive = frame.progressive;
@ -127,6 +141,14 @@ var JpegImage = (function JpegImageClosure() {
if (bitsData === 0xFF) {
var nextByte = data[offset++];
if (nextByte) {
if (nextByte === 0xDC && parseDNLMarker) { // DNL == 0xFFDC
offset += 2; // Skip data length.
const scanLines = (data[offset++] << 8) | data[offset++];
if (scanLines > 0 && scanLines !== frame.scanLines) {
throw new DNLMarkerError(
'Found DNL marker (0xFFDC) while parsing scan data', scanLines);
}
}
throw new JpegError(
`unexpected marker ${((bitsData << 8) | nextByte).toString(16)}`);
}
@ -635,7 +657,7 @@ var JpegImage = (function JpegImageClosure() {
}
JpegImage.prototype = {
parse: function parse(data) {
parse(data, { dnlScanLines = null, } = {}) {
function readUint16() {
var value = (data[offset] << 8) | data[offset + 1];
@ -685,6 +707,7 @@ var JpegImage = (function JpegImageClosure() {
var jfif = null;
var adobe = null;
var frame, resetInterval;
let numSOSMarkers = 0;
var quantizationTables = [];
var huffmanTablesAC = [], huffmanTablesDC = [];
var fileMarker = readUint16();
@ -781,7 +804,8 @@ var JpegImage = (function JpegImageClosure() {
frame.extended = (fileMarker === 0xFFC1);
frame.progressive = (fileMarker === 0xFFC2);
frame.precision = data[offset++];
frame.scanLines = readUint16();
const sofScanLines = readUint16();
frame.scanLines = dnlScanLines || sofScanLines;
frame.samplesPerLine = readUint16();
frame.components = [];
frame.componentIds = {};
@ -839,6 +863,12 @@ var JpegImage = (function JpegImageClosure() {
break;
case 0xFFDA: // SOS (Start of Scan)
// A DNL marker (0xFFDC), if it exists, is only allowed at the end
// of the first scan segment and may only occur once in an image.
// Furthermore, to prevent an infinite loop, do *not* attempt to
// parse DNL markers during re-parsing of the JPEG scan data.
const parseDNLMarker = (++numSOSMarkers) === 1 && !dnlScanLines;
readUint16(); // scanLength
var selectorsCount = data[offset++];
var components = [], component;
@ -853,11 +883,26 @@ var JpegImage = (function JpegImageClosure() {
var spectralStart = data[offset++];
var spectralEnd = data[offset++];
var successiveApproximation = data[offset++];
var processed = decodeScan(data, offset,
frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation >> 4, successiveApproximation & 15);
offset += processed;
try {
var processed = decodeScan(data, offset,
frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation >> 4, successiveApproximation & 15,
parseDNLMarker);
offset += processed;
} catch (ex) {
if (ex instanceof DNLMarkerError) {
warn('Attempting to re-parse JPEG image using "scanLines" ' +
'parameter found in DNL marker (0xFFDC) segment.');
return this.parse(data, { dnlScanLines: ex.scanLines, });
}
throw ex;
}
break;
case 0xFFDC: // DNL (Define Number of Lines)
// Ignore the marker, since it's being handled in `decodeScan`.
offset += 4;
break;
case 0xFFFF: // Fill bytes

View File

@ -0,0 +1 @@
https://github.com/mozilla/pdf.js/files/1125123/OBW-OVK.pdf

View File

@ -3194,6 +3194,14 @@
"link": true,
"type": "eq"
},
{ "id": "issue8614",
"file": "pdfs/issue8614.pdf",
"md5": "7e8b66cf674ac2b79d6b267d0c6f2fa2",
"rounds": 1,
"link": true,
"lastPage": 1,
"type": "eq"
},
{ "id": "bug1108753",
"file": "pdfs/bug1108753.pdf",
"md5": "a7aaf92d55b4602afb0ca3d75198b56b",