diff --git a/src/core/crypto.js b/src/core/crypto.js index f30d9a69a..dba8f1064 100644 --- a/src/core/crypto.js +++ b/src/core/crypto.js @@ -31,11 +31,12 @@ var PasswordException = sharedUtil.PasswordException; var PasswordResponses = sharedUtil.PasswordResponses; var bytesToString = sharedUtil.bytesToString; +var warn = sharedUtil.warn; var error = sharedUtil.error; +var assert = sharedUtil.assert; var isInt = sharedUtil.isInt; var stringToBytes = sharedUtil.stringToBytes; var utf8StringToString = sharedUtil.utf8StringToString; -var warn = sharedUtil.warn; var Name = corePrimitives.Name; var isName = corePrimitives.isName; var isDict = corePrimitives.isDict; @@ -1932,6 +1933,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var cfDict = dict.get('CF'); var streamCryptoName = dict.get('StmF'); if (isDict(cfDict) && isName(streamCryptoName)) { + cfDict.suppressEncryption = true; // See comment below. var handlerDict = cfDict.get(streamCryptoName.name); keyLength = (handlerDict && handlerDict.get('Length')) || 128; if (keyLength < 40) { @@ -2013,7 +2015,15 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { this.encryptionKey = encryptionKey; if (algorithm >= 4) { - this.cf = dict.get('CF'); + var cf = dict.get('CF'); + if (isDict(cf)) { + // The 'CF' dictionary itself should not be encrypted, and by setting + // `suppressEncryption` we can prevent an infinite loop inside of + // `XRef_fetchUncompressed` if the dictionary contains indirect objects + // (fixes issue7665.pdf). + cf.suppressEncryption = true; + } + this.cf = cf; this.stmf = dict.get('StmF') || identityName; this.strf = dict.get('StrF') || identityName; this.eff = dict.get('EFF') || this.stmf; @@ -2041,6 +2051,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } function buildCipherConstructor(cf, name, num, gen, key) { + assert(isName(name), 'Invalid crypt filter name.'); var cryptFilter = cf.get(name.name); var cfm; if (cryptFilter !== null && cryptFilter !== undefined) { diff --git a/src/core/obj.js b/src/core/obj.js index e37a0eea3..a839d95e3 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -633,6 +633,11 @@ var XRef = (function XRefClosure() { if (encrypt) { var ids = trailerDict.get('ID'); var fileId = (ids && ids.length) ? ids[0] : ''; + // The 'Encrypt' dictionary itself should not be encrypted, and by + // setting `suppressEncryption` we can prevent an infinite loop inside + // of `XRef_fetchUncompressed` if the dictionary contains indirect + // objects (fixes issue7665.pdf). + encrypt.suppressEncryption = true; this.encrypt = new CipherTransformFactory(encrypt, fileId, this.password); } @@ -1079,11 +1084,11 @@ var XRef = (function XRefClosure() { return null; }, - fetchIfRef: function XRef_fetchIfRef(obj) { + fetchIfRef: function XRef_fetchIfRef(obj, suppressEncryption) { if (!isRef(obj)) { return obj; } - return this.fetch(obj); + return this.fetch(obj, suppressEncryption); }, fetch: function XRef_fetch(ref, suppressEncryption) { @@ -1201,11 +1206,11 @@ var XRef = (function XRefClosure() { return xrefEntry; }, - fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) { + fetchIfRefAsync: function XRef_fetchIfRefAsync(obj, suppressEncryption) { if (!isRef(obj)) { return Promise.resolve(obj); } - return this.fetchAsync(obj); + return this.fetchAsync(obj, suppressEncryption); }, fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) { diff --git a/src/core/primitives.js b/src/core/primitives.js index 6a17e990a..b27222134 100644 --- a/src/core/primitives.js +++ b/src/core/primitives.js @@ -73,6 +73,7 @@ var Dict = (function DictClosure() { this.map = Object.create(null); this.xref = xref; this.objId = null; + this.suppressEncryption = false; this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict } @@ -84,40 +85,40 @@ var Dict = (function DictClosure() { // automatically dereferences Ref objects get: function Dict_get(key1, key2, key3) { var value; - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') { - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; } if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') { - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; } value = this.map[key3] || null; - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; }, // Same as get(), but returns a promise and uses fetchIfRefAsync(). getAsync: function Dict_getAsync(key1, key2, key3) { var value; - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') { if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); } if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') { if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); } value = this.map[key3] || null; if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); }, @@ -125,7 +126,7 @@ var Dict = (function DictClosure() { // Same as get(), but dereferences all elements if the result is an Array. getArray: function Dict_getArray(key1, key2, key3) { var value = this.get(key1, key2, key3); - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (!isArray(value) || !xref) { return value; } @@ -134,7 +135,7 @@ var Dict = (function DictClosure() { if (!isRef(value[i])) { continue; } - value[i] = xref.fetch(value[i]); + value[i] = xref.fetch(value[i], suppressEncryption); } return value; }, diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 792230050..5559a84b4 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -39,6 +39,7 @@ !issue7492.pdf !issue7544.pdf !issue7598.pdf +!issue7665.pdf !filled-background.pdf !ArabicCIDTrueType.pdf !ThuluthFeatures.pdf diff --git a/test/pdfs/issue7665.pdf b/test/pdfs/issue7665.pdf new file mode 100644 index 000000000..c51c873a8 Binary files /dev/null and b/test/pdfs/issue7665.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 8b4a6064a..35ea04fd3 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1044,6 +1044,14 @@ "rounds": 1, "type": "eq" }, + { "id": "issue7665", + "file": "pdfs/issue7665.pdf", + "md5": "f1199d16195a61e8232e2d1e742ed46b", + "link": false, + "rounds": 1, + "type": "load", + "about": "Encrypted file with indirect objects in the /Encrypt dictionary." + }, { "id": "protectip", "file": "pdfs/protectip.pdf", "md5": "676e7a7b8f96d04825361832b1838a93",