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:
parent
80441346a3
commit
bf4166e6c9
@ -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
|
||||
|
1
test/pdfs/issue8614.pdf.link
Normal file
1
test/pdfs/issue8614.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/mozilla/pdf.js/files/1125123/OBW-OVK.pdf
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user