: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
constructor(stringCipherConstructor, streamCipherConstructor) {
this.StringCipherConstructor = stringCipherConstructor;
this.StreamCipherConstructor = streamCipherConstructor;
createStream(stream, length) {
const cipher = new this.StreamCipherConstructor();
return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) {
return cipher.decryptBlock(data, finalize);
const cipher = new this.StringCipherConstructor();
let data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
const cipher = new this.StringCipherConstructor();
if (cipher instanceof AESBaseCipher) {
const pad = 16 - strLen % 16;
s += String.fromCharCode(pad).repeat(pad);
const iv = new Uint8Array(16);
if (typeof crypto !== "undefined") {
crypto.getRandomValues(iv);
for (let i = 0; i < 16; i++) {
iv[i] = Math.floor(256 * Math.random());
let data = stringToBytes(s);
data = cipher.encrypt(data, iv);
const buf = new Uint8Array(16 + data.length);
return bytesToString(buf);
let data = stringToBytes(s);
data = cipher.encrypt(data);
return bytesToString(data);
class CipherTransformFactory {
static #defaultPasswordBytes = new Uint8Array([0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]);
#createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) {
const passwordLength = Math.min(127, password.length);
password = password.subarray(0, passwordLength);
const pdfAlgorithm = revision === 6 ? new PDF20() : new PDF17();
if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) {
return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
} else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) {
return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption);
#prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) {
const hashDataSize = 40 + ownerPassword.length + fileId.length;
const hashData = new Uint8Array(hashDataSize);
n = Math.min(32, password.length);
hashData[i] = password[i];
hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
for (j = 0, n = ownerPassword.length; j < n; ++j) {
hashData[i++] = ownerPassword[j];
hashData[i++] = flags & 0xff;
hashData[i++] = flags >> 8 & 0xff;
hashData[i++] = flags >> 16 & 0xff;
hashData[i++] = flags >>> 24 & 0xff;
for (j = 0, n = fileId.length; j < n; ++j) {
hashData[i++] = fileId[j];
if (revision >= 4 && !encryptMetadata) {
let hash = calculateMD5(hashData, 0, i);
const keyLengthInBytes = keyLength >> 3;
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, keyLengthInBytes);
const encryptionKey = hash.subarray(0, keyLengthInBytes);
for (i = 0; i < 32; ++i) {
hashData[i] = CipherTransformFactory.#defaultPasswordBytes[i];
for (j = 0, n = fileId.length; j < n; ++j) {
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
n = encryptionKey.length;
const derivedKey = new Uint8Array(n);
for (j = 1; j <= 19; ++j) {
for (let k = 0; k < n; ++k) {
derivedKey[k] = encryptionKey[k] ^ j;
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] !== checkData[j]) {
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(CipherTransformFactory.#defaultPasswordBytes);
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] !== checkData[j]) {
#decodeUserPassword(password, ownerPassword, revision, keyLength) {
const hashData = new Uint8Array(32);
const n = Math.min(32, password.length);
hashData[i] = password[i];
hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
let hash = calculateMD5(hashData, 0, i);
const keyLengthInBytes = keyLength >> 3;
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, hash.length);
let cipher, userPassword;
userPassword = ownerPassword;
const derivedKey = new Uint8Array(keyLengthInBytes);
for (j = 19; j >= 0; j--) {
for (let k = 0; k < keyLengthInBytes; ++k) {
derivedKey[k] = hash[k] ^ j;
cipher = new ARCFourCipher(derivedKey);
userPassword = cipher.encryptBlock(userPassword);
cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
userPassword = cipher.encryptBlock(ownerPassword);
#buildObjectKey(num, gen, encryptionKey, isAes = false) {
const key = new Uint8Array(encryptionKey.length + 9);
const n = encryptionKey.length;
for (i = 0; i < n; ++i) {
key[i] = encryptionKey[i];
key[i++] = num >> 8 & 0xff;
key[i++] = num >> 16 & 0xff;
key[i++] = gen >> 8 & 0xff;
const hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
#buildCipherConstructor(cf, name, num, gen, key) {
if (!(name instanceof Name)) {
throw new FormatError("Invalid crypt filter name.");
const cryptFilter = cf.get(name.name);
const cfm = cryptFilter?.get("CFM");
if (!cfm || cfm.name === "None") {
return new ARCFourCipher(self.#buildObjectKey(num, gen, key, false));
if (cfm.name === "AESV2") {
return new AES128Cipher(self.#buildObjectKey(num, gen, key, true));
if (cfm.name === "AESV3") {
return new AES256Cipher(key);
throw new FormatError("Unknown crypto method");
constructor(dict, fileId, password) {
const filter = dict.get("Filter");
if (!isName(filter, "Standard")) {
throw new FormatError("unknown encryption method");
this.filterName = filter.name;
const algorithm = dict.get("V");
if (!Number.isInteger(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) {
throw new FormatError("unsupported encryption algorithm");
this.algorithm = algorithm;
let keyLength = dict.get("Length");
const cfDict = dict.get("CF");
const streamCryptoName = dict.get("StmF");
if (cfDict instanceof Dict && streamCryptoName instanceof Name) {
cfDict.suppressEncryption = true;
const handlerDict = cfDict.get(streamCryptoName.name);
keyLength = handlerDict?.get("Length") || 128;
if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
throw new FormatError("invalid key length");
const ownerBytes = stringToBytes(dict.get("O")),
userBytes = stringToBytes(dict.get("U"));
const ownerPassword = ownerBytes.subarray(0, 32);
const userPassword = userBytes.subarray(0, 32);
const flags = dict.get("P");
const revision = dict.get("R");
const encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get("EncryptMetadata") !== false;
this.encryptMetadata = encryptMetadata;
const fileIdBytes = stringToBytes(fileId);
password = utf8StringToString(password);
warn("CipherTransformFactory: Unable to convert UTF8 encoded password.");
passwordBytes = stringToBytes(password);
encryptionKey = this.#prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
const ownerValidationSalt = ownerBytes.subarray(32, 40);
const ownerKeySalt = ownerBytes.subarray(40, 48);
const uBytes = userBytes.subarray(0, 48);
const userValidationSalt = userBytes.subarray(32, 40);
const userKeySalt = userBytes.subarray(40, 48);
const ownerEncryption = stringToBytes(dict.get("OE"));
const userEncryption = stringToBytes(dict.get("UE"));
const perms = stringToBytes(dict.get("Perms"));
encryptionKey = this.#createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms);
if (!encryptionKey && !password) {
throw new PasswordException("No password given", PasswordResponses.NEED_PASSWORD);
} else if (!encryptionKey && password) {
const decodedPassword = this.#decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength);
encryptionKey = this.#prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
throw new PasswordException("Incorrect Password", PasswordResponses.INCORRECT_PASSWORD);
this.encryptionKey = encryptionKey;
const cf = dict.get("CF");
if (cf instanceof Dict) {
cf.suppressEncryption = true;
this.stmf = dict.get("StmF") || Name.get("Identity");
this.strf = dict.get("StrF") || Name.get("Identity");
this.eff = dict.get("EFF") || this.stmf;
createCipherTransform(num, gen) {
if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform(this.#buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey), this.#buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey));
const key = this.#buildObjectKey(num, gen, this.encryptionKey, false);
const cipherConstructor = function () {
return new ARCFourCipher(key);
return new CipherTransform(cipherConstructor, cipherConstructor);
;// CONCATENATED MODULE: ./src/core/writer.js
async function writeObject(ref, obj, buffer, {
const transform = encrypt?.createCipherTransform(ref.num, ref.gen);
buffer.push(`${ref.num} ${ref.gen} obj\n`);
if (obj instanceof Dict) {
await writeDict(obj, buffer, transform);
} else if (obj instanceof BaseStream) {
await writeStream(obj, buffer, transform);
} else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
await writeArray(obj, buffer, transform);
buffer.push("\nendobj\n");
async function writeDict(dict, buffer, transform) {
for (const key of dict.getKeys()) {
buffer.push(` /${escapePDFName(key)} `);
await writeValue(dict.getRaw(key), buffer, transform);
async function writeStream(stream, buffer, transform) {
let bytes = stream.getBytes();
const [filter, params] = await Promise.all([dict.getAsync("Filter"), dict.getAsync("DecodeParms")]);
const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter;
const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode");
const MIN_LENGTH_FOR_COMPRESSING = 256;
if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) {
const cs = new CompressionStream("deflate");
const writer = cs.writable.getWriter();
const buf = await new Response(cs.readable).arrayBuffer();
bytes = new Uint8Array(buf);
let newFilter, newParams;
newFilter = Name.get("FlateDecode");
} else if (!isFilterZeroFlateDecode) {
newFilter = Array.isArray(filter) ? [Name.get("FlateDecode"), ...filter] : [Name.get("FlateDecode"), filter];
newParams = Array.isArray(params) ? [null, ...params] : [null, params];
dict.set("Filter", newFilter);
dict.set("DecodeParms", newParams);
info(`writeStream - cannot compress data: "${ex}".`);
let string = bytesToString(bytes);
string = transform.encryptString(string);
dict.set("Length", string.length);
await writeDict(dict, buffer, transform);
buffer.push(" stream\n", string, "\nendstream");
async function writeArray(array, buffer, transform) {
for (const val of array) {
await writeValue(val, buffer, transform);
async function writeValue(value, buffer, transform) {
if (value instanceof Name) {
buffer.push(`/${escapePDFName(value.name)}`);
} else if (value instanceof Ref) {
buffer.push(`${value.num} ${value.gen} R`);
} else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
await writeArray(value, buffer, transform);
} else if (typeof value === "string") {
value = transform.encryptString(value);
buffer.push(`(${escapeString(value)})`);
} else if (typeof value === "number") {
buffer.push(numberToString(value));
} else if (typeof value === "boolean") {
buffer.push(value.toString());
} else if (value instanceof Dict) {
await writeDict(value, buffer, transform);
} else if (value instanceof BaseStream) {
await writeStream(value, buffer, transform);
} else if (value === null) {
warn(`Unhandled value in writer: ${typeof value}, please file a bug.`);
function writeInt(number, size, offset, buffer) {
for (let i = size + offset - 1; i > offset - 1; i--) {
buffer[i] = number & 0xff;
function writeString(string, offset, buffer) {
for (let i = 0, len = string.length; i < len; i++) {
buffer[offset + i] = string.charCodeAt(i) & 0xff;
function computeMD5(filesize, xrefInfo) {
const time = Math.floor(Date.now() / 1000);
const filename = xrefInfo.filename || "";
const md5Buffer = [time.toString(), filename, filesize.toString()];
let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0);
for (const value of Object.values(xrefInfo.info)) {
md5BufferLen += value.length;
const array = new Uint8Array(md5BufferLen);
for (const str of md5Buffer) {
writeString(str, offset, array);
return bytesToString(calculateMD5(array));
function writeXFADataForAcroform(str, newRefs) {
const xml = new SimpleXMLParser({
const nodePath = parseXFAPath(path);
let node = xml.documentElement.searchNode(nodePath, 0);
if (!node && nodePath.length > 1) {
node = xml.documentElement.searchNode([nodePath.at(-1)], 0);
node.childNodes = Array.isArray(value) ? value.map(val => new SimpleDOMNode("value", val)) : [new SimpleDOMNode("#text", value)];
warn(`Node not found for path: ${path}`);