@@ -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,48 @@ class PDF20 extends PDFBase {
737751}
738752
739753class CipherTransform {
740- constructor ( stringCipherConstructor , streamCipherConstructor ) {
741- this . StringCipherConstructor = stringCipherConstructor ;
742- this . StreamCipherConstructor = streamCipherConstructor ;
754+ /**
755+ * @param {ResolveCipher } resolveCipher
756+ * Resolve a cipher constructor from a crypt filter name.
757+ * @param {Name | null } [stringFilterName]
758+ * Default crypt filter for strings.
759+ * @param {Name | null } [streamFilterName]
760+ * Default crypt filter for streams.
761+ */
762+ constructor ( resolveCipher , stringFilterName = null , streamFilterName = null ) {
763+ /** @type {Map<string, CipherConstructors> } */
764+ this . cipherCache = new Map ( ) ;
765+ this . resolveCipher = resolveCipher ;
766+ this . streamFilterName = streamFilterName ;
767+ this . stringFilterName = stringFilterName ;
743768 }
744769
745- createStream ( stream , length ) {
746- const cipher = new this . StreamCipherConstructor ( ) ;
770+ /**
771+ * @param {Name | null } [filterName]
772+ * Crypt filter name.
773+ * @returns {CipherConstructors }
774+ * Cipher constructor.
775+ */
776+ #getCipher( filterName = null ) {
777+ const key = filterName ? filterName . name : "__default__" ;
778+
779+ let Cipher = this . cipherCache . get ( key ) ;
780+ if ( ! Cipher ) {
781+ Cipher = this . resolveCipher ( filterName ) ;
782+ this . cipherCache . set ( key , Cipher ) ;
783+ }
784+ return Cipher ;
785+ }
786+
787+ /**
788+ * @param {BaseStream } stream
789+ * @param {number | null } length
790+ * @param {Name | null } [cryptFilterName]
791+ * @returns {DecryptStream }
792+ */
793+ createStream ( stream , length , cryptFilterName = null ) {
794+ const Cipher = this . #getCipher( cryptFilterName || this . streamFilterName ) ;
795+ const cipher = new Cipher ( ) ;
747796 return new DecryptStream (
748797 stream ,
749798 length ,
@@ -754,14 +803,16 @@ class CipherTransform {
754803 }
755804
756805 decryptString ( s ) {
757- const cipher = new this . StringCipherConstructor ( ) ;
806+ const Cipher = this . #getCipher( this . stringFilterName ) ;
807+ const cipher = new Cipher ( ) ;
758808 let data = stringToBytes ( s ) ;
759809 data = cipher . decryptBlock ( data , true ) ;
760810 return bytesToString ( data ) ;
761811 }
762812
763813 encryptString ( s ) {
764- const cipher = new this . StringCipherConstructor ( ) ;
814+ const Cipher = this . #getCipher( this . stringFilterName ) ;
815+ const cipher = new Cipher ( ) ;
765816 if ( cipher instanceof AESBaseCipher ) {
766817 // Append some chars equal to "16 - (M mod 16)"
767818 // where M is the string length (see section 7.6.2 in PDF specification)
@@ -986,41 +1037,6 @@ class CipherTransformFactory {
9861037 return hash . subarray ( 0 , Math . min ( n + 5 , 16 ) ) ;
9871038 }
9881039
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-
10241040 constructor ( dict , fileId , password ) {
10251041 const filter = dict . get ( "Filter" ) ;
10261042 if ( ! isName ( filter , "Standard" ) ) {
@@ -1185,43 +1201,74 @@ class CipherTransformFactory {
11851201 }
11861202 }
11871203
1204+ /**
1205+ * @param {number } num
1206+ * Object number.
1207+ * @param {number } gen
1208+ * Generation number.
1209+ * @returns {CipherTransform }
1210+ * Cipher transform.
1211+ */
11881212 createCipherTransform ( num , gen ) {
11891213 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- ) ;
1214+ /** @type {ResolveCipher } */
1215+ const resolveCipher = filterName => {
1216+ if ( ! ( filterName instanceof Name ) ) {
1217+ throw new FormatError ( "Invalid crypt filter name." ) ;
1218+ }
1219+ const cryptFilter = this . cf . get ( filterName . name ) ;
1220+ const cfm = cryptFilter ?. get ( "CFM" ) ;
1221+
1222+ if ( ! cfm || cfm . name === "None" ) {
1223+ return NullCipher ;
1224+ }
1225+ if ( cfm . name === "V2" ) {
1226+ return ARCFourCipher . bind (
1227+ null ,
1228+ this . #buildObjectKey(
1229+ num ,
1230+ gen ,
1231+ this . encryptionKey ,
1232+ /* isAes = */ false
1233+ )
1234+ ) ;
1235+ }
1236+ if ( cfm . name === "AESV2" ) {
1237+ return AES128Cipher . bind (
1238+ null ,
1239+ this . #buildObjectKey(
1240+ num ,
1241+ gen ,
1242+ this . encryptionKey ,
1243+ /* isAes = */ true
1244+ )
1245+ ) ;
1246+ }
1247+ if ( cfm . name === "AESV3" ) {
1248+ return AES256Cipher . bind ( null , this . encryptionKey ) ;
1249+ }
1250+ throw new FormatError ( "Unknown crypto method" ) ;
1251+ } ;
1252+
1253+ return new CipherTransform ( resolveCipher , this . strf , this . stmf ) ;
12061254 }
1255+
12071256 // 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 ) ;
1257+ /** @type {ResolveCipher } */
1258+ const resolveCipher = ( ) =>
1259+ ARCFourCipher . bind (
1260+ null ,
1261+ this . #buildObjectKey( num , gen , this . encryptionKey , /* isAes = */ false )
1262+ ) ;
1263+ return new CipherTransform ( resolveCipher ) ;
12181264 }
12191265}
12201266
12211267export {
12221268 AES128Cipher ,
12231269 AES256Cipher ,
12241270 ARCFourCipher ,
1271+ CipherTransform ,
12251272 CipherTransformFactory ,
12261273 PDF17 ,
12271274 PDF20 ,
0 commit comments