@@ -31,6 +31,20 @@ import { calculateMD5 } from "./calculate_md5.js";
3131import { calculateSHA256 } from "./calculate_sha256.js" ;
3232import { DecryptStream } from "./decrypt_stream.js" ;
3333
34+ /**
35+ * @typedef {typeof AES128Cipher | typeof AES256Cipher | typeof ARCFourCipher
36+ * | typeof NullCipher} CipherConstructors
37+ */
38+
39+ /**
40+ * @callback ResolveCipher
41+ * Find the appropriate cipher class based on the filter name.
42+ * @param {Name | null } [filterName]
43+ * Name.
44+ * @returns {CipherConstructors }
45+ * Cipher constructor.
46+ */
47+
3448class ARCFourCipher {
3549 a = 0 ;
3650
@@ -737,13 +751,46 @@ class PDF20 extends PDFBase {
737751}
738752
739753class CipherTransform {
740- constructor ( stringCipherConstructor , streamCipherConstructor ) {
741- this . StringCipherConstructor = stringCipherConstructor ;
742- this . StreamCipherConstructor = streamCipherConstructor ;
754+ /** @type {Map<string, CipherConstructors> } */
755+ #cipherCache = new Map ( ) ;
756+
757+ /**
758+ * @param {ResolveCipher } resolveCipher
759+ * Resolve a cipher constructor from a crypt filter name.
760+ * @param {Name | null } [stringFilterName]
761+ * Default crypt filter for strings.
762+ * @param {Name | null } [streamFilterName]
763+ * Default crypt filter for streams.
764+ */
765+ constructor ( resolveCipher , stringFilterName = null , streamFilterName = null ) {
766+ this . resolveCipher = resolveCipher ;
767+ this . streamFilterName = streamFilterName ;
768+ this . stringFilterName = stringFilterName ;
743769 }
744770
745- createStream ( stream , length ) {
746- const cipher = new this . StreamCipherConstructor ( ) ;
771+ /**
772+ * @param {Name | null } [filterName]
773+ * Crypt filter name.
774+ * @returns {CipherConstructors }
775+ * Cipher constructor.
776+ */
777+ #getCipher( filterName = null ) {
778+ const key = filterName instanceof Name ? filterName . name : "__default__" ;
779+
780+ return this . #cipherCache. getOrInsertComputed ( key , ( ) =>
781+ this . resolveCipher ( filterName )
782+ ) ;
783+ }
784+
785+ /**
786+ * @param {BaseStream } stream
787+ * @param {number | null } length
788+ * @param {Name | null } [cryptFilterName]
789+ * @returns {DecryptStream }
790+ */
791+ createStream ( stream , length , cryptFilterName = null ) {
792+ const Cipher = this . #getCipher( cryptFilterName || this . streamFilterName ) ;
793+ const cipher = new Cipher ( ) ;
747794 return new DecryptStream (
748795 stream ,
749796 length ,
@@ -754,14 +801,16 @@ class CipherTransform {
754801 }
755802
756803 decryptString ( s ) {
757- const cipher = new this . StringCipherConstructor ( ) ;
804+ const Cipher = this . #getCipher( this . stringFilterName ) ;
805+ const cipher = new Cipher ( ) ;
758806 let data = stringToBytes ( s ) ;
759807 data = cipher . decryptBlock ( data , true ) ;
760808 return bytesToString ( data ) ;
761809 }
762810
763811 encryptString ( s ) {
764- const cipher = new this . StringCipherConstructor ( ) ;
812+ const Cipher = this . #getCipher( this . stringFilterName ) ;
813+ const cipher = new Cipher ( ) ;
765814 if ( cipher instanceof AESBaseCipher ) {
766815 // Append some chars equal to "16 - (M mod 16)"
767816 // where M is the string length (see section 7.6.2 in PDF specification)
@@ -986,41 +1035,6 @@ class CipherTransformFactory {
9861035 return hash . subarray ( 0 , Math . min ( n + 5 , 16 ) ) ;
9871036 }
9881037
989- #buildCipherConstructor( cf , name , num , gen , key ) {
990- if ( ! ( name instanceof Name ) ) {
991- throw new FormatError ( "Invalid crypt filter name." ) ;
992- }
993- const self = this ;
994- const cryptFilter = cf . get ( name . name ) ;
995- const cfm = cryptFilter ?. get ( "CFM" ) ;
996-
997- if ( ! cfm || cfm . name === "None" ) {
998- return function ( ) {
999- return new NullCipher ( ) ;
1000- } ;
1001- }
1002- if ( cfm . name === "V2" ) {
1003- return function ( ) {
1004- return new ARCFourCipher (
1005- self . #buildObjectKey( num , gen , key , /* isAes = */ false )
1006- ) ;
1007- } ;
1008- }
1009- if ( cfm . name === "AESV2" ) {
1010- return function ( ) {
1011- return new AES128Cipher (
1012- self . #buildObjectKey( num , gen , key , /* isAes = */ true )
1013- ) ;
1014- } ;
1015- }
1016- if ( cfm . name === "AESV3" ) {
1017- return function ( ) {
1018- return new AES256Cipher ( key ) ;
1019- } ;
1020- }
1021- throw new FormatError ( "Unknown crypto method" ) ;
1022- }
1023-
10241038 constructor ( dict , fileId , password ) {
10251039 const filter = dict . get ( "Filter" ) ;
10261040 if ( ! isName ( filter , "Standard" ) ) {
@@ -1185,43 +1199,74 @@ class CipherTransformFactory {
11851199 }
11861200 }
11871201
1202+ /**
1203+ * @param {number } num
1204+ * Object number.
1205+ * @param {number } gen
1206+ * Generation number.
1207+ * @returns {CipherTransform }
1208+ * Cipher transform.
1209+ */
11881210 createCipherTransform ( num , gen ) {
11891211 if ( this . algorithm === 4 || this . algorithm === 5 ) {
1190- return new CipherTransform (
1191- this . #buildCipherConstructor(
1192- this . cf ,
1193- this . strf ,
1194- num ,
1195- gen ,
1196- this . encryptionKey
1197- ) ,
1198- this . #buildCipherConstructor(
1199- this . cf ,
1200- this . stmf ,
1201- num ,
1202- gen ,
1203- this . encryptionKey
1204- )
1205- ) ;
1212+ /** @type {ResolveCipher } */
1213+ const resolveCipher = filterName => {
1214+ if ( ! ( filterName instanceof Name ) ) {
1215+ throw new FormatError ( "Invalid crypt filter name." ) ;
1216+ }
1217+ const cryptFilter = this . cf . get ( filterName . name ) ;
1218+ const cfm = cryptFilter ?. get ( "CFM" ) ;
1219+
1220+ if ( ! cfm || cfm . name === "None" ) {
1221+ return NullCipher ;
1222+ }
1223+ if ( cfm . name === "V2" ) {
1224+ return ARCFourCipher . bind (
1225+ null ,
1226+ this . #buildObjectKey(
1227+ num ,
1228+ gen ,
1229+ this . encryptionKey ,
1230+ /* isAes = */ false
1231+ )
1232+ ) ;
1233+ }
1234+ if ( cfm . name === "AESV2" ) {
1235+ return AES128Cipher . bind (
1236+ null ,
1237+ this . #buildObjectKey(
1238+ num ,
1239+ gen ,
1240+ this . encryptionKey ,
1241+ /* isAes = */ true
1242+ )
1243+ ) ;
1244+ }
1245+ if ( cfm . name === "AESV3" ) {
1246+ return AES256Cipher . bind ( null , this . encryptionKey ) ;
1247+ }
1248+ throw new FormatError ( "Unknown crypto method" ) ;
1249+ } ;
1250+
1251+ return new CipherTransform ( resolveCipher , this . strf , this . stmf ) ;
12061252 }
1253+
12071254 // algorithms 1 and 2
1208- const key = this . #buildObjectKey(
1209- num ,
1210- gen ,
1211- this . encryptionKey ,
1212- /* isAes = */ false
1213- ) ;
1214- const cipherConstructor = function ( ) {
1215- return new ARCFourCipher ( key ) ;
1216- } ;
1217- return new CipherTransform ( cipherConstructor , cipherConstructor ) ;
1255+ /** @type {ResolveCipher } */
1256+ const resolveCipher = ( ) =>
1257+ ARCFourCipher . bind (
1258+ null ,
1259+ this . #buildObjectKey( num , gen , this . encryptionKey , /* isAes = */ false )
1260+ ) ;
1261+ return new CipherTransform ( resolveCipher ) ;
12181262 }
12191263}
12201264
12211265export {
12221266 AES128Cipher ,
12231267 AES256Cipher ,
12241268 ARCFourCipher ,
1269+ CipherTransform ,
12251270 CipherTransformFactory ,
12261271 PDF17 ,
12271272 PDF20 ,
0 commit comments