|
|
|
/* Copyright 2017 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
|
|
value: true
|
|
|
|
});
|
|
|
|
exports.Parser = exports.Linearization = exports.Lexer = undefined;
|
|
|
|
|
|
|
|
var _stream = require('./stream');
|
|
|
|
|
|
|
|
var _util = require('../shared/util');
|
|
|
|
|
|
|
|
var _primitives = require('./primitives');
|
|
|
|
|
|
|
|
var _ccitt_stream = require('./ccitt_stream');
|
|
|
|
|
|
|
|
var _jbig2_stream = require('./jbig2_stream');
|
|
|
|
|
|
|
|
var MAX_LENGTH_TO_CACHE = 1000;
|
|
|
|
var Parser = function ParserClosure() {
|
|
|
|
function Parser(lexer, allowStreams, xref, recoveryMode) {
|
|
|
|
this.lexer = lexer;
|
|
|
|
this.allowStreams = allowStreams;
|
|
|
|
this.xref = xref;
|
|
|
|
this.recoveryMode = recoveryMode || false;
|
|
|
|
this.imageCache = Object.create(null);
|
|
|
|
this.refill();
|
|
|
|
}
|
|
|
|
Parser.prototype = {
|
|
|
|
refill: function Parser_refill() {
|
|
|
|
this.buf1 = this.lexer.getObj();
|
|
|
|
this.buf2 = this.lexer.getObj();
|
|
|
|
},
|
|
|
|
shift: function Parser_shift() {
|
|
|
|
if ((0, _primitives.isCmd)(this.buf2, 'ID')) {
|
|
|
|
this.buf1 = this.buf2;
|
|
|
|
this.buf2 = null;
|
|
|
|
} else {
|
|
|
|
this.buf1 = this.buf2;
|
|
|
|
this.buf2 = this.lexer.getObj();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
tryShift: function Parser_tryShift() {
|
|
|
|
try {
|
|
|
|
this.shift();
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof _util.MissingDataException) {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getObj: function Parser_getObj(cipherTransform) {
|
|
|
|
var buf1 = this.buf1;
|
|
|
|
this.shift();
|
|
|
|
if (buf1 instanceof _primitives.Cmd) {
|
|
|
|
switch (buf1.cmd) {
|
|
|
|
case 'BI':
|
|
|
|
return this.makeInlineImage(cipherTransform);
|
|
|
|
case '[':
|
|
|
|
var array = [];
|
|
|
|
while (!(0, _primitives.isCmd)(this.buf1, ']') && !(0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
array.push(this.getObj(cipherTransform));
|
|
|
|
}
|
|
|
|
if ((0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
if (!this.recoveryMode) {
|
|
|
|
throw new _util.FormatError('End of file inside array');
|
|
|
|
}
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
this.shift();
|
|
|
|
return array;
|
|
|
|
case '<<':
|
|
|
|
var dict = new _primitives.Dict(this.xref);
|
|
|
|
while (!(0, _primitives.isCmd)(this.buf1, '>>') && !(0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
if (!(0, _primitives.isName)(this.buf1)) {
|
|
|
|
(0, _util.info)('Malformed dictionary: key must be a name object');
|
|
|
|
this.shift();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var key = this.buf1.name;
|
|
|
|
this.shift();
|
|
|
|
if ((0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dict.set(key, this.getObj(cipherTransform));
|
|
|
|
}
|
|
|
|
if ((0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
if (!this.recoveryMode) {
|
|
|
|
throw new _util.FormatError('End of file inside dictionary');
|
|
|
|
}
|
|
|
|
return dict;
|
|
|
|
}
|
|
|
|
if ((0, _primitives.isCmd)(this.buf2, 'stream')) {
|
|
|
|
return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
|
|
|
|
}
|
|
|
|
this.shift();
|
|
|
|
return dict;
|
|
|
|
default:
|
|
|
|
return buf1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Number.isInteger(buf1)) {
|
|
|
|
var num = buf1;
|
|
|
|
if (Number.isInteger(this.buf1) && (0, _primitives.isCmd)(this.buf2, 'R')) {
|
|
|
|
var ref = new _primitives.Ref(num, this.buf1);
|
|
|
|
this.shift();
|
|
|
|
this.shift();
|
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
if ((0, _util.isString)(buf1)) {
|
|
|
|
var str = buf1;
|
|
|
|
if (cipherTransform) {
|
|
|
|
str = cipherTransform.decryptString(str);
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
return buf1;
|
|
|
|
},
|
|
|
|
findDefaultInlineStreamEnd: function findDefaultInlineStreamEnd(stream) {
|
|
|
|
var E = 0x45,
|
|
|
|
I = 0x49,
|
|
|
|
SPACE = 0x20,
|
|
|
|
LF = 0xA,
|
|
|
|
CR = 0xD;
|
|
|
|
var n = 10,
|
|
|
|
NUL = 0x0;
|
|
|
|
var startPos = stream.pos,
|
|
|
|
state = 0,
|
|
|
|
ch = void 0,
|
|
|
|
maybeEIPos = void 0;
|
|
|
|
while ((ch = stream.getByte()) !== -1) {
|
|
|
|
if (state === 0) {
|
|
|
|
state = ch === E ? 1 : 0;
|
|
|
|
} else if (state === 1) {
|
|
|
|
state = ch === I ? 2 : 0;
|
|
|
|
} else {
|
|
|
|
(0, _util.assert)(state === 2);
|
|
|
|
if (ch === SPACE || ch === LF || ch === CR) {
|
|
|
|
maybeEIPos = stream.pos;
|
|
|
|
var followingBytes = stream.peekBytes(n);
|
|
|
|
for (var i = 0, ii = followingBytes.length; i < ii; i++) {
|
|
|
|
ch = followingBytes[i];
|
|
|
|
if (ch === NUL && followingBytes[i + 1] !== NUL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) {
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state === 2) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ch === -1) {
|
|
|
|
(0, _util.warn)('findDefaultInlineStreamEnd: ' + 'Reached the end of the stream without finding a valid EI marker');
|
|
|
|
if (maybeEIPos) {
|
|
|
|
(0, _util.warn)('... trying to recover by using the last "EI" occurrence.');
|
|
|
|
stream.skip(-(stream.pos - maybeEIPos));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stream.pos - 4 - startPos;
|
|
|
|
},
|
|
|
|
|
|
|
|
findDCTDecodeInlineStreamEnd: function Parser_findDCTDecodeInlineStreamEnd(stream) {
|
|
|
|
var startPos = stream.pos,
|
|
|
|
foundEOI = false,
|
|
|
|
b,
|
|
|
|
markerLength,
|
|
|
|
length;
|
|
|
|
while ((b = stream.getByte()) !== -1) {
|
|
|
|
if (b !== 0xFF) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (stream.getByte()) {
|
|
|
|
case 0x00:
|
|
|
|
break;
|
|
|
|
case 0xFF:
|
|
|
|
stream.skip(-1);
|
|
|
|
break;
|
|
|
|
case 0xD9:
|
|
|
|
foundEOI = true;
|
|
|
|
break;
|
|
|
|
case 0xC0:
|
|
|
|
case 0xC1:
|
|
|
|
case 0xC2:
|
|
|
|
case 0xC3:
|
|
|
|
case 0xC5:
|
|
|
|
case 0xC6:
|
|
|
|
case 0xC7:
|
|
|
|
case 0xC9:
|
|
|
|
case 0xCA:
|
|
|
|
case 0xCB:
|
|
|
|
case 0xCD:
|
|
|
|
case 0xCE:
|
|
|
|
case 0xCF:
|
|
|
|
case 0xC4:
|
|
|
|
case 0xCC:
|
|
|
|
case 0xDA:
|
|
|
|
case 0xDB:
|
|
|
|
case 0xDC:
|
|
|
|
case 0xDD:
|
|
|
|
case 0xDE:
|
|
|
|
case 0xDF:
|
|
|
|
case 0xE0:
|
|
|
|
case 0xE1:
|
|
|
|
case 0xE2:
|
|
|
|
case 0xE3:
|
|
|
|
case 0xE4:
|
|
|
|
case 0xE5:
|
|
|
|
case 0xE6:
|
|
|
|
case 0xE7:
|
|
|
|
case 0xE8:
|
|
|
|
case 0xE9:
|
|
|
|
case 0xEA:
|
|
|
|
case 0xEB:
|
|
|
|
case 0xEC:
|
|
|
|
case 0xED:
|
|
|
|
case 0xEE:
|
|
|
|
case 0xEF:
|
|
|
|
case 0xFE:
|
|
|
|
markerLength = stream.getUint16();
|
|
|
|
if (markerLength > 2) {
|
|
|
|
stream.skip(markerLength - 2);
|
|
|
|
} else {
|
|
|
|
stream.skip(-2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (foundEOI) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
length = stream.pos - startPos;
|
|
|
|
if (b === -1) {
|
|
|
|
(0, _util.warn)('Inline DCTDecode image stream: ' + 'EOI marker not found, searching for /EI/ instead.');
|
|
|
|
stream.skip(-length);
|
|
|
|
return this.findDefaultInlineStreamEnd(stream);
|
|
|
|
}
|
|
|
|
this.inlineStreamSkipEI(stream);
|
|
|
|
return length;
|
|
|
|
},
|
|
|
|
findASCII85DecodeInlineStreamEnd: function Parser_findASCII85DecodeInlineStreamEnd(stream) {
|
|
|
|
var TILDE = 0x7E,
|
|
|
|
GT = 0x3E;
|
|
|
|
var startPos = stream.pos,
|
|
|
|
ch,
|
|
|
|
length;
|
|
|
|
while ((ch = stream.getByte()) !== -1) {
|
|
|
|
if (ch === TILDE && stream.peekByte() === GT) {
|
|
|
|
stream.skip();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
length = stream.pos - startPos;
|
|
|
|
if (ch === -1) {
|
|
|
|
(0, _util.warn)('Inline ASCII85Decode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
|
|
|
|
stream.skip(-length);
|
|
|
|
return this.findDefaultInlineStreamEnd(stream);
|
|
|
|
}
|
|
|
|
this.inlineStreamSkipEI(stream);
|
|
|
|
return length;
|
|
|
|
},
|
|
|
|
findASCIIHexDecodeInlineStreamEnd: function Parser_findASCIIHexDecodeInlineStreamEnd(stream) {
|
|
|
|
var GT = 0x3E;
|
|
|
|
var startPos = stream.pos,
|
|
|
|
ch,
|
|
|
|
length;
|
|
|
|
while ((ch = stream.getByte()) !== -1) {
|
|
|
|
if (ch === GT) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
length = stream.pos - startPos;
|
|
|
|
if (ch === -1) {
|
|
|
|
(0, _util.warn)('Inline ASCIIHexDecode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
|
|
|
|
stream.skip(-length);
|
|
|
|
return this.findDefaultInlineStreamEnd(stream);
|
|
|
|
}
|
|
|
|
this.inlineStreamSkipEI(stream);
|
|
|
|
return length;
|
|
|
|
},
|
|
|
|
inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) {
|
|
|
|
var E = 0x45,
|
|
|
|
I = 0x49;
|
|
|
|
var state = 0,
|
|
|
|
ch;
|
|
|
|
while ((ch = stream.getByte()) !== -1) {
|
|
|
|
if (state === 0) {
|
|
|
|
state = ch === E ? 1 : 0;
|
|
|
|
} else if (state === 1) {
|
|
|
|
state = ch === I ? 2 : 0;
|
|
|
|
} else if (state === 2) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
|
|
|
|
var lexer = this.lexer;
|
|
|
|
var stream = lexer.stream;
|
|
|
|
var dict = new _primitives.Dict(this.xref);
|
|
|
|
while (!(0, _primitives.isCmd)(this.buf1, 'ID') && !(0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
if (!(0, _primitives.isName)(this.buf1)) {
|
|
|
|
throw new _util.FormatError('Dictionary key must be a name object');
|
|
|
|
}
|
|
|
|
var key = this.buf1.name;
|
|
|
|
this.shift();
|
|
|
|
if ((0, _primitives.isEOF)(this.buf1)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dict.set(key, this.getObj(cipherTransform));
|
|
|
|
}
|
|
|
|
var filter = dict.get('Filter', 'F'),
|
|
|
|
filterName;
|
|
|
|
if ((0, _primitives.isName)(filter)) {
|
|
|
|
filterName = filter.name;
|
|
|
|
} else if (Array.isArray(filter)) {
|
|
|
|
var filterZero = this.xref.fetchIfRef(filter[0]);
|
|
|
|
if ((0, _primitives.isName)(filterZero)) {
|
|
|
|
filterName = filterZero.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var startPos = stream.pos,
|
|
|
|
length,
|
|
|
|
i,
|
|
|
|
ii;
|
|
|
|
if (filterName === 'DCTDecode' || filterName === 'DCT') {
|
|
|
|
length = this.findDCTDecodeInlineStreamEnd(stream);
|
|
|
|
} else if (filterName === 'ASCII85Decode' || filterName === 'A85') {
|
|
|
|
length = this.findASCII85DecodeInlineStreamEnd(stream);
|
|
|
|
} else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') {
|
|
|
|
length = this.findASCIIHexDecodeInlineStreamEnd(stream);
|
|
|
|
} else {
|
|
|
|
length = this.findDefaultInlineStreamEnd(stream);
|
|
|
|
}
|
|
|
|
var imageStream = stream.makeSubStream(startPos, length, dict);
|
|
|
|
var adler32;
|
|
|
|
if (length < MAX_LENGTH_TO_CACHE) {
|
|
|
|
var imageBytes = imageStream.getBytes();
|
|
|
|
imageStream.reset();
|
|
|
|
var a = 1;
|
|
|
|
var b = 0;
|
|
|
|
for (i = 0, ii = imageBytes.length; i < ii; ++i) {
|
|
|
|
a += imageBytes[i] & 0xff;
|
|
|
|
b += a;
|
|
|
|
}
|
|
|
|
adler32 = b % 65521 << 16 | a % 65521;
|
|
|
|
var cacheEntry = this.imageCache[adler32];
|
|
|
|
if (cacheEntry !== undefined) {
|
|
|
|
this.buf2 = _primitives.Cmd.get('EI');
|
|
|
|
this.shift();
|
|
|
|
cacheEntry.reset();
|
|
|
|
return cacheEntry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cipherTransform) {
|
|
|
|
imageStream = cipherTransform.createStream(imageStream, length);
|
|
|
|
}
|
|
|
|
imageStream = this.filter(imageStream, dict, length);
|
|
|
|
imageStream.dict = dict;
|
|
|
|
if (adler32 !== undefined) {
|
|
|
|
imageStream.cacheKey = 'inline_' + length + '_' + adler32;
|
|
|
|
this.imageCache[adler32] = imageStream;
|
|
|
|
}
|
|
|
|
this.buf2 = _primitives.Cmd.get('EI');
|
|
|
|
this.shift();
|
|
|
|
return imageStream;
|
|
|
|
},
|
|
|
|
makeStream: function Parser_makeStream(dict, cipherTransform) {
|
|
|
|
var lexer = this.lexer;
|
|
|
|
var stream = lexer.stream;
|
|
|
|
lexer.skipToNextLine();
|
|
|
|
var pos = stream.pos - 1;
|
|
|
|
var length = dict.get('Length');
|
|
|
|
if (!Number.isInteger(length)) {
|
|
|
|
(0, _util.info)('Bad ' + length + ' attribute in stream');
|
|
|
|
length = 0;
|
|
|
|
}
|
|
|
|
stream.pos = pos + length;
|
|
|
|
lexer.nextChar();
|
|
|
|
if (this.tryShift() && (0, _primitives.isCmd)(this.buf2, 'endstream')) {
|
|
|
|
this.shift();
|
|
|
|
} else {
|
|
|
|
stream.pos = pos;
|
|
|
|
var SCAN_BLOCK_SIZE = 2048;
|
|
|
|
var ENDSTREAM_SIGNATURE_LENGTH = 9;
|
|
|
|
var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D];
|
|
|
|
var skipped = 0,
|
|
|
|
found = false,
|
|
|
|
i,
|
|
|
|
j;
|
|
|
|
while (stream.pos < stream.end) {
|
|
|
|
var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
|
|
|
|
var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
|
|
|
|
if (scanLength <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
found = false;
|
|
|
|
i = 0;
|
|
|
|
while (i < scanLength) {
|
|
|
|
j = 0;
|
|
|
|
while (j < ENDSTREAM_SIGNATURE_LENGTH && scanBytes[i + j] === ENDSTREAM_SIGNATURE[j]) {
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
skipped += i;
|
|
|
|
stream.pos += i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
skipped += scanLength;
|
|
|
|
stream.pos += scanLength;
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
throw new _util.FormatError('Missing endstream');
|
|
|
|
}
|
|
|
|
length = skipped;
|
|
|
|
lexer.nextChar();
|
|
|
|
this.shift();
|
|
|
|
this.shift();
|
|
|
|
}
|
|
|
|
this.shift();
|
|
|
|
stream = stream.makeSubStream(pos, length, dict);
|
|
|
|
if (cipherTransform) {
|
|
|
|
stream = cipherTransform.createStream(stream, length);
|
|
|
|
}
|
|
|
|
stream = this.filter(stream, dict, length);
|
|
|
|
stream.dict = dict;
|
|
|
|
return stream;
|
|
|
|
},
|
|
|
|
filter: function Parser_filter(stream, dict, length) {
|
|
|
|
var filter = dict.get('Filter', 'F');
|
|
|
|
var params = dict.get('DecodeParms', 'DP');
|
|
|
|
if ((0, _primitives.isName)(filter)) {
|
|
|
|
if (Array.isArray(params)) {
|
|
|
|
(0, _util.warn)('/DecodeParms should not contain an Array, ' + 'when /Filter contains a Name.');
|
|
|
|
}
|
|
|
|
return this.makeFilter(stream, filter.name, length, params);
|
|
|
|
}
|
|
|
|
var maybeLength = length;
|
|
|
|
if (Array.isArray(filter)) {
|
|
|
|
var filterArray = filter;
|
|
|
|
var paramsArray = params;
|
|
|
|
for (var i = 0, ii = filterArray.length; i < ii; ++i) {
|
|
|
|
filter = this.xref.fetchIfRef(filterArray[i]);
|
|
|
|
if (!(0, _primitives.isName)(filter)) {
|
|
|
|
throw new _util.FormatError('Bad filter name: ' + filter);
|
|
|
|
}
|
|
|
|
params = null;
|
|
|
|
if (Array.isArray(paramsArray) && i in paramsArray) {
|
|
|
|
params = this.xref.fetchIfRef(paramsArray[i]);
|
|
|
|
}
|
|
|
|
stream = this.makeFilter(stream, filter.name, maybeLength, params);
|
|
|
|
maybeLength = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stream;
|
|
|
|
},
|
|
|
|
makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
|
|
|
|
if (maybeLength === 0) {
|
|
|
|
(0, _util.warn)('Empty "' + name + '" stream.');
|
|
|
|
return new _stream.NullStream(stream);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
var xrefStreamStats = this.xref.stats.streamTypes;
|
|
|
|
if (name === 'FlateDecode' || name === 'Fl') {
|
|
|
|
xrefStreamStats[_util.StreamType.FLATE] = true;
|
|
|
|
if (params) {
|
|
|
|
return new _stream.PredictorStream(new _stream.FlateStream(stream, maybeLength), maybeLength, params);
|
|
|
|
}
|
|
|
|
return new _stream.FlateStream(stream, maybeLength);
|
|
|
|
}
|
|
|
|
if (name === 'LZWDecode' || name === 'LZW') {
|
|
|
|
xrefStreamStats[_util.StreamType.LZW] = true;
|
|
|
|
var earlyChange = 1;
|
|
|
|
if (params) {
|
|
|
|
if (params.has('EarlyChange')) {
|
|
|
|
earlyChange = params.get('EarlyChange');
|
|
|
|
}
|
|
|
|
return new _stream.PredictorStream(new _stream.LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
|
|
|
|
}
|
|
|
|
return new _stream.LZWStream(stream, maybeLength, earlyChange);
|
|
|
|
}
|
|
|
|
if (name === 'DCTDecode' || name === 'DCT') {
|
|
|
|
xrefStreamStats[_util.StreamType.DCT] = true;
|
|
|
|
return new _stream.JpegStream(stream, maybeLength, stream.dict, params);
|
|
|
|
}
|
|
|
|
if (name === 'JPXDecode' || name === 'JPX') {
|
|
|
|
xrefStreamStats[_util.StreamType.JPX] = true;
|
|
|
|
return new _stream.JpxStream(stream, maybeLength, stream.dict, params);
|
|
|
|
}
|
|
|
|
if (name === 'ASCII85Decode' || name === 'A85') {
|
|
|
|
xrefStreamStats[_util.StreamType.A85] = true;
|
|
|
|
return new _stream.Ascii85Stream(stream, maybeLength);
|
|
|
|
}
|
|
|
|
if (name === 'ASCIIHexDecode' || name === 'AHx') {
|
|
|
|
xrefStreamStats[_util.StreamType.AHX] = true;
|
|
|
|
return new _stream.AsciiHexStream(stream, maybeLength);
|
|
|
|
}
|
|
|
|
if (name === 'CCITTFaxDecode' || name === 'CCF') {
|
|
|
|
xrefStreamStats[_util.StreamType.CCF] = true;
|
|
|
|
return new _ccitt_stream.CCITTFaxStream(stream, maybeLength, params);
|
|
|
|
}
|
|
|
|
if (name === 'RunLengthDecode' || name === 'RL') {
|
|
|
|
xrefStreamStats[_util.StreamType.RL] = true;
|
|
|
|
return new _stream.RunLengthStream(stream, maybeLength);
|
|
|
|
}
|
|
|
|
if (name === 'JBIG2Decode') {
|
|
|
|
xrefStreamStats[_util.StreamType.JBIG] = true;
|
|
|
|
return new _jbig2_stream.Jbig2Stream(stream, maybeLength, stream.dict, params);
|
|
|
|
}
|
|
|
|
(0, _util.warn)('filter "' + name + '" not supported yet');
|
|
|
|
return stream;
|
|
|
|
} catch (ex) {
|
|
|
|
if (ex instanceof _util.MissingDataException) {
|
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
(0, _util.warn)('Invalid stream: \"' + ex + '\"');
|
|
|
|
return new _stream.NullStream(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return Parser;
|
|
|
|
}();
|
|
|
|
var Lexer = function LexerClosure() {
|
|
|
|
function Lexer(stream, knownCommands) {
|
|
|
|
this.stream = stream;
|
|
|
|
this.nextChar();
|
|
|
|
this.strBuf = [];
|
|
|
|
this.knownCommands = knownCommands;
|
|
|
|
}
|
|
|
|
var specialChars = [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
function toHexDigit(ch) {
|
|
|
|
if (ch >= 0x30 && ch <= 0x39) {
|
|
|
|
return ch & 0x0F;
|
|
|
|
}
|
|
|
|
if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
|
|
|
|
return (ch & 0x0F) + 9;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
Lexer.prototype = {
|
|
|
|
nextChar: function Lexer_nextChar() {
|
|
|
|
return this.currentChar = this.stream.getByte();
|
|
|
|
},
|
|
|
|
peekChar: function Lexer_peekChar() {
|
|
|
|
return this.stream.peekByte();
|
|
|
|
},
|
|
|
|
getNumber: function Lexer_getNumber() {
|
|
|
|
var ch = this.currentChar;
|
|
|
|
var eNotation = false;
|
|
|
|
var divideBy = 0;
|
|
|
|
var sign = 1;
|
|
|
|
if (ch === 0x2D) {
|
|
|
|
sign = -1;
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x2D) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
} else if (ch === 0x2B) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
if (ch === 0x2E) {
|
|
|
|
divideBy = 10;
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
if (ch === 0x0A || ch === 0x0D) {
|
|
|
|
do {
|
|
|
|
ch = this.nextChar();
|
|
|
|
} while (ch === 0x0A || ch === 0x0D);
|
|
|
|
}
|
|
|
|
if (ch < 0x30 || ch > 0x39) {
|
|
|
|
throw new _util.FormatError('Invalid number: ' + String.fromCharCode(ch) + ' (charCode ' + ch + ')');
|
|
|
|
}
|
|
|
|
var baseValue = ch - 0x30;
|
|
|
|
var powerValue = 0;
|
|
|
|
var powerValueSign = 1;
|
|
|
|
while ((ch = this.nextChar()) >= 0) {
|
|
|
|
if (0x30 <= ch && ch <= 0x39) {
|
|
|
|
var currentDigit = ch - 0x30;
|
|
|
|
if (eNotation) {
|
|
|
|
powerValue = powerValue * 10 + currentDigit;
|
|
|
|
} else {
|
|
|
|
if (divideBy !== 0) {
|
|
|
|
divideBy *= 10;
|
|
|
|
}
|
|
|
|
baseValue = baseValue * 10 + currentDigit;
|
|
|
|
}
|
|
|
|
} else if (ch === 0x2E) {
|
|
|
|
if (divideBy === 0) {
|
|
|
|
divideBy = 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (ch === 0x2D) {
|
|
|
|
(0, _util.warn)('Badly formatted number');
|
|
|
|
} else if (ch === 0x45 || ch === 0x65) {
|
|
|
|
ch = this.peekChar();
|
|
|
|
if (ch === 0x2B || ch === 0x2D) {
|
|
|
|
powerValueSign = ch === 0x2D ? -1 : 1;
|
|
|
|
this.nextChar();
|
|
|
|
} else if (ch < 0x30 || ch > 0x39) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
eNotation = true;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (divideBy !== 0) {
|
|
|
|
baseValue /= divideBy;
|
|
|
|
}
|
|
|
|
if (eNotation) {
|
|
|
|
baseValue *= Math.pow(10, powerValueSign * powerValue);
|
|
|
|
}
|
|
|
|
return sign * baseValue;
|
|
|
|
},
|
|
|
|
getString: function Lexer_getString() {
|
|
|
|
var numParen = 1;
|
|
|
|
var done = false;
|
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
|
|
|
var ch = this.nextChar();
|
|
|
|
while (true) {
|
|
|
|
var charBuffered = false;
|
|
|
|
switch (ch | 0) {
|
|
|
|
case -1:
|
|
|
|
(0, _util.warn)('Unterminated string');
|
|
|
|
done = true;
|
|
|
|
break;
|
|
|
|
case 0x28:
|
|
|
|
++numParen;
|
|
|
|
strBuf.push('(');
|
|
|
|
break;
|
|
|
|
case 0x29:
|
|
|
|
if (--numParen === 0) {
|
|
|
|
this.nextChar();
|
|
|
|
done = true;
|
|
|
|
} else {
|
|
|
|
strBuf.push(')');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x5C:
|
|
|
|
ch = this.nextChar();
|
|
|
|
switch (ch) {
|
|
|
|
case -1:
|
|
|
|
(0, _util.warn)('Unterminated string');
|
|
|
|
done = true;
|
|
|
|
break;
|
|
|
|
case 0x6E:
|
|
|
|
strBuf.push('\n');
|
|
|
|
break;
|
|
|
|
case 0x72:
|
|
|
|
strBuf.push('\r');
|
|
|
|
break;
|
|
|
|
case 0x74:
|
|
|
|
strBuf.push('\t');
|
|
|
|
break;
|
|
|
|
case 0x62:
|
|
|
|
strBuf.push('\b');
|
|
|
|
break;
|
|
|
|
case 0x66:
|
|
|
|
strBuf.push('\f');
|
|
|
|
break;
|
|
|
|
case 0x5C:
|
|
|
|
case 0x28:
|
|
|
|
case 0x29:
|
|
|
|
strBuf.push(String.fromCharCode(ch));
|
|
|
|
break;
|
|
|
|
case 0x30:
|
|
|
|
case 0x31:
|
|
|
|
case 0x32:
|
|
|
|
case 0x33:
|
|
|
|
case 0x34:
|
|
|
|
case 0x35:
|
|
|
|
case 0x36:
|
|
|
|
case 0x37:
|
|
|
|
var x = ch & 0x0F;
|
|
|
|
ch = this.nextChar();
|
|
|
|
charBuffered = true;
|
|
|
|
if (ch >= 0x30 && ch <= 0x37) {
|
|
|
|
x = (x << 3) + (ch & 0x0F);
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch >= 0x30 && ch <= 0x37) {
|
|
|
|
charBuffered = false;
|
|
|
|
x = (x << 3) + (ch & 0x0F);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strBuf.push(String.fromCharCode(x));
|
|
|
|
break;
|
|
|
|
case 0x0D:
|
|
|
|
if (this.peekChar() === 0x0A) {
|
|
|
|
this.nextChar();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x0A:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
strBuf.push(String.fromCharCode(ch));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
strBuf.push(String.fromCharCode(ch));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!charBuffered) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strBuf.join('');
|
|
|
|
},
|
|
|
|
getName: function Lexer_getName() {
|
|
|
|
var ch, previousCh;
|
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
|
|
|
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
|
|
|
|
if (ch === 0x23) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (specialChars[ch]) {
|
|
|
|
(0, _util.warn)('Lexer_getName: ' + 'NUMBER SIGN (#) should be followed by a hexadecimal number.');
|
|
|
|
strBuf.push('#');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var x = toHexDigit(ch);
|
|
|
|
if (x !== -1) {
|
|
|
|
previousCh = ch;
|
|
|
|
ch = this.nextChar();
|
|
|
|
var x2 = toHexDigit(ch);
|
|
|
|
if (x2 === -1) {
|
|
|
|
(0, _util.warn)('Lexer_getName: Illegal digit (' + String.fromCharCode(ch) + ') in hexadecimal number.');
|
|
|
|
strBuf.push('#', String.fromCharCode(previousCh));
|
|
|
|
if (specialChars[ch]) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
strBuf.push(String.fromCharCode(ch));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
strBuf.push(String.fromCharCode(x << 4 | x2));
|
|
|
|
} else {
|
|
|
|
strBuf.push('#', String.fromCharCode(ch));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
strBuf.push(String.fromCharCode(ch));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (strBuf.length > 127) {
|
|
|
|
(0, _util.warn)('name token is longer than allowed by the spec: ' + strBuf.length);
|
|
|
|
}
|
|
|
|
return _primitives.Name.get(strBuf.join(''));
|
|
|
|
},
|
|
|
|
getHexString: function Lexer_getHexString() {
|
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
|
|
|
var ch = this.currentChar;
|
|
|
|
var isFirstHex = true;
|
|
|
|
var firstDigit;
|
|
|
|
var secondDigit;
|
|
|
|
while (true) {
|
|
|
|
if (ch < 0) {
|
|
|
|
(0, _util.warn)('Unterminated hex string');
|
|
|
|
break;
|
|
|
|
} else if (ch === 0x3E) {
|
|
|
|
this.nextChar();
|
|
|
|
break;
|
|
|
|
} else if (specialChars[ch] === 1) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
if (isFirstHex) {
|
|
|
|
firstDigit = toHexDigit(ch);
|
|
|
|
if (firstDigit === -1) {
|
|
|
|
(0, _util.warn)('Ignoring invalid character "' + ch + '" in hex string');
|
|
|
|
ch = this.nextChar();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
secondDigit = toHexDigit(ch);
|
|
|
|
if (secondDigit === -1) {
|
|
|
|
(0, _util.warn)('Ignoring invalid character "' + ch + '" in hex string');
|
|
|
|
ch = this.nextChar();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
strBuf.push(String.fromCharCode(firstDigit << 4 | secondDigit));
|
|
|
|
}
|
|
|
|
isFirstHex = !isFirstHex;
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strBuf.join('');
|
|
|
|
},
|
|
|
|
getObj: function Lexer_getObj() {
|
|
|
|
var comment = false;
|
|
|
|
var ch = this.currentChar;
|
|
|
|
while (true) {
|
|
|
|
if (ch < 0) {
|
|
|
|
return _primitives.EOF;
|
|
|
|
}
|
|
|
|
if (comment) {
|
|
|
|
if (ch === 0x0A || ch === 0x0D) {
|
|
|
|
comment = false;
|
|
|
|
}
|
|
|
|
} else if (ch === 0x25) {
|
|
|
|
comment = true;
|
|
|
|
} else if (specialChars[ch] !== 1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
switch (ch | 0) {
|
|
|
|
case 0x30:
|
|
|
|
case 0x31:
|
|
|
|
case 0x32:
|
|
|
|
case 0x33:
|
|
|
|
case 0x34:
|
|
|
|
case 0x35:
|
|
|
|
case 0x36:
|
|
|
|
case 0x37:
|
|
|
|
case 0x38:
|
|
|
|
case 0x39:
|
|
|
|
case 0x2B:
|
|
|
|
case 0x2D:
|
|
|
|
case 0x2E:
|
|
|
|
return this.getNumber();
|
|
|
|
case 0x28:
|
|
|
|
return this.getString();
|
|
|
|
case 0x2F:
|
|
|
|
return this.getName();
|
|
|
|
case 0x5B:
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get('[');
|
|
|
|
case 0x5D:
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get(']');
|
|
|
|
case 0x3C:
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x3C) {
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get('<<');
|
|
|
|
}
|
|
|
|
return this.getHexString();
|
|
|
|
case 0x3E:
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x3E) {
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get('>>');
|
|
|
|
}
|
|
|
|
return _primitives.Cmd.get('>');
|
|
|
|
case 0x7B:
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get('{');
|
|
|
|
case 0x7D:
|
|
|
|
this.nextChar();
|
|
|
|
return _primitives.Cmd.get('}');
|
|
|
|
case 0x29:
|
|
|
|
this.nextChar();
|
|
|
|
throw new _util.FormatError('Illegal character: ' + ch);
|
|
|
|
}
|
|
|
|
var str = String.fromCharCode(ch);
|
|
|
|
var knownCommands = this.knownCommands;
|
|
|
|
var knownCommandFound = knownCommands && knownCommands[str] !== undefined;
|
|
|
|
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
|
|
|
|
var possibleCommand = str + String.fromCharCode(ch);
|
|
|
|
if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (str.length === 128) {
|
|
|
|
throw new _util.FormatError('Command token too long: ' + str.length);
|
|
|
|
}
|
|
|
|
str = possibleCommand;
|
|
|
|
knownCommandFound = knownCommands && knownCommands[str] !== undefined;
|
|
|
|
}
|
|
|
|
if (str === 'true') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (str === 'false') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (str === 'null') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return _primitives.Cmd.get(str);
|
|
|
|
},
|
|
|
|
skipToNextLine: function Lexer_skipToNextLine() {
|
|
|
|
var ch = this.currentChar;
|
|
|
|
while (ch >= 0) {
|
|
|
|
if (ch === 0x0D) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x0A) {
|
|
|
|
this.nextChar();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (ch === 0x0A) {
|
|
|
|
this.nextChar();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return Lexer;
|
|
|
|
}();
|
|
|
|
var Linearization = {
|
|
|
|
create: function LinearizationCreate(stream) {
|
|
|
|
function getInt(name, allowZeroValue) {
|
|
|
|
var obj = linDict.get(name);
|
|
|
|
if (Number.isInteger(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
throw new Error('The "' + name + '" parameter in the linearization ' + 'dictionary is invalid.');
|
|
|
|
}
|
|
|
|
function getHints() {
|
|
|
|
var hints = linDict.get('H'),
|
|
|
|
hintsLength,
|
|
|
|
item;
|
|
|
|
if (Array.isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) {
|
|
|
|
for (var index = 0; index < hintsLength; index++) {
|
|
|
|
if (!(Number.isInteger(item = hints[index]) && item > 0)) {
|
|
|
|
throw new Error('Hint (' + index + ') in the linearization dictionary is invalid.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hints;
|
|
|
|
}
|
|
|
|
throw new Error('Hint array in the linearization dictionary is invalid.');
|
|
|
|
}
|
|
|
|
var parser = new Parser(new Lexer(stream), false, null);
|
|
|
|
var obj1 = parser.getObj();
|
|
|
|
var obj2 = parser.getObj();
|
|
|
|
var obj3 = parser.getObj();
|
|
|
|
var linDict = parser.getObj();
|
|
|
|
var obj, length;
|
|
|
|
if (!(Number.isInteger(obj1) && Number.isInteger(obj2) && (0, _primitives.isCmd)(obj3, 'obj') && (0, _primitives.isDict)(linDict) && (0, _util.isNum)(obj = linDict.get('Linearized')) && obj > 0)) {
|
|
|
|
return null;
|
|
|
|
} else if ((length = getInt('L')) !== stream.length) {
|
|
|
|
throw new Error('The "L" parameter in the linearization dictionary ' + 'does not equal the stream length.');
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
length: length,
|
|
|
|
hints: getHints(),
|
|
|
|
objectNumberFirst: getInt('O'),
|
|
|
|
endFirst: getInt('E'),
|
|
|
|
numPages: getInt('N'),
|
|
|
|
mainXRefEntriesOffset: getInt('T'),
|
|
|
|
pageFirst: linDict.has('P') ? getInt('P', true) : 0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
exports.Lexer = Lexer;
|
|
|
|
exports.Linearization = Linearization;
|
|
|
|
exports.Parser = Parser;
|