mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-22 12:09:50 +08:00
finish initial support for rc4 encryption with blank user password
This commit is contained in:
@@ -3,17 +3,17 @@
|
|||||||
//using System;
|
//using System;
|
||||||
//using System.Diagnostics;
|
//using System.Diagnostics;
|
||||||
//using System.IO;
|
//using System.IO;
|
||||||
using Xunit;
|
//using Xunit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class for testing files which are not checked in to source control.
|
/// A class for testing files which are not checked in to source control.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LocalTests
|
public class LocalTests
|
||||||
{
|
{
|
||||||
[Fact]
|
//[Fact]
|
||||||
public void Tests()
|
//public void Tests()
|
||||||
{
|
//{
|
||||||
// var files = new[]{ @"C:\Users\eliot\Downloads\Encrypted1.pdf" };
|
// var files = new[] { @"C:\Users\eliot\Downloads\Encrypted1.pdf" };
|
||||||
|
|
||||||
// foreach (var file in files)
|
// foreach (var file in files)
|
||||||
// {
|
// {
|
||||||
@@ -34,6 +34,6 @@
|
|||||||
// throw new InvalidOperationException($"Error parsing: {Path.GetFileName(file)}.", ex);
|
// throw new InvalidOperationException($"Error parsing: {Path.GetFileName(file)}.", ex);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content;
|
using Content;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Fields;
|
using Fields;
|
||||||
using Filters;
|
using Filters;
|
||||||
@@ -21,13 +20,11 @@
|
|||||||
{
|
{
|
||||||
private readonly IPdfTokenScanner tokenScanner;
|
private readonly IPdfTokenScanner tokenScanner;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
|
|
||||||
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider, IEncryptionHandler encryptionHandler)
|
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider)
|
||||||
{
|
{
|
||||||
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
||||||
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
|
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
|
||||||
this.encryptionHandler = encryptionHandler ?? throw new ArgumentNullException(nameof(encryptionHandler));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -215,7 +212,7 @@
|
|||||||
}
|
}
|
||||||
else if (DirectObjectFinder.TryGet(textValueToken, tokenScanner, out StreamToken valueStreamToken))
|
else if (DirectObjectFinder.TryGet(textValueToken, tokenScanner, out StreamToken valueStreamToken))
|
||||||
{
|
{
|
||||||
textValue = OtherEncodings.BytesAsLatin1String(valueStreamToken.Decode(filterProvider, encryptionHandler).ToArray());
|
textValue = OtherEncodings.BytesAsLatin1String(valueStreamToken.Decode(filterProvider).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using CrossReference;
|
using CrossReference;
|
||||||
@@ -23,6 +24,8 @@
|
|||||||
0x64, 0x53, 0x69, 0x7A
|
0x64, 0x53, 0x69, 0x7A
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly HashSet<IndirectReference> previouslyDecrypted = new HashSet<IndirectReference>();
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private readonly EncryptionDictionary encryptionDictionary;
|
private readonly EncryptionDictionary encryptionDictionary;
|
||||||
|
|
||||||
@@ -67,43 +70,110 @@
|
|||||||
? 5
|
? 5
|
||||||
: encryptionDictionary.KeyLength.GetValueOrDefault() / 8;
|
: encryptionDictionary.KeyLength.GetValueOrDefault() / 8;
|
||||||
|
|
||||||
encryptionKey = CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int) encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision,
|
encryptionKey = CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int)encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision,
|
||||||
length, documentIdBytes, encryptionDictionary.EncryptMetadata);
|
length, documentIdBytes, encryptionDictionary.EncryptMetadata);
|
||||||
|
|
||||||
|
useAes = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<byte> Decrypt(StreamToken stream)
|
public IToken Decrypt(IndirectReference reference, IToken token)
|
||||||
{
|
{
|
||||||
if (encryptionDictionary == null)
|
if (token == null)
|
||||||
{
|
{
|
||||||
return stream?.Data;
|
throw new ArgumentNullException(nameof(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream == null)
|
token = DecryptInternal(reference, token);
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(stream));
|
previouslyDecrypted.Add(reference);
|
||||||
|
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException($"Encryption is not supported yet. Encryption used in document was: {encryptionDictionary.Dictionary}.");
|
private IToken DecryptInternal(IndirectReference reference, IToken token)
|
||||||
|
{
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case StreamToken stream:
|
||||||
|
{
|
||||||
|
if (stream.StreamDictionary.TryGet(NameToken.Type, out NameToken typeName))
|
||||||
|
{
|
||||||
|
if (NameToken.Xref.Equals(typeName))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Decrypt(IndirectReference reference, IToken token)
|
if (!encryptionDictionary.EncryptMetadata && NameToken.Metadata.Equals(typeName))
|
||||||
{
|
{
|
||||||
if (token is StreamToken stream)
|
return token;
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (token is StringToken stringToken)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
// TODO: check unencrypted metadata
|
||||||
}
|
}
|
||||||
else if (token is DictionaryToken dictionary)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
var streamDictionary = (DictionaryToken)DecryptInternal(reference, stream.StreamDictionary);
|
||||||
else if (token is ArrayToken array)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
var decrypted = DecryptData(stream.Data.ToArray(), reference);
|
||||||
|
|
||||||
|
token = new StreamToken(streamDictionary, decrypted);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case StringToken stringToken:
|
||||||
|
{
|
||||||
|
var data = OtherEncodings.StringAsLatin1Bytes(stringToken.Data);
|
||||||
|
|
||||||
|
var decrypted = DecryptData(data, reference);
|
||||||
|
|
||||||
|
token = new StringToken(OtherEncodings.BytesAsLatin1String(decrypted));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DictionaryToken dictionary:
|
||||||
|
{
|
||||||
|
// PDFBOX-2936: avoid orphan /CF dictionaries found in US govt "I-" files
|
||||||
|
if (dictionary.TryGet(NameToken.Cf, out _))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSignatureDictionary = dictionary.TryGet(NameToken.Type, out NameToken typeName)
|
||||||
|
&& (typeName.Equals(NameToken.Sig) || typeName.Equals(NameToken.DocTimeStamp));
|
||||||
|
|
||||||
|
foreach (var keyValuePair in dictionary.Data)
|
||||||
|
{
|
||||||
|
if (isSignatureDictionary && keyValuePair.Key == NameToken.Contents.Data)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyValuePair.Value is StringToken || keyValuePair.Value is ArrayToken || keyValuePair.Value is DictionaryToken)
|
||||||
|
{
|
||||||
|
var inner = DecryptInternal(reference, keyValuePair.Value);
|
||||||
|
dictionary = dictionary.With(keyValuePair.Key, inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = dictionary;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ArrayToken array:
|
||||||
|
{
|
||||||
|
var result = new IToken[array.Length];
|
||||||
|
|
||||||
|
for (var i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
result[i] = DecryptInternal(reference, array.Data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
token = new ArrayToken(result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] DecryptData(byte[] data, IndirectReference reference)
|
private byte[] DecryptData(byte[] data, IndirectReference reference)
|
||||||
@@ -134,12 +204,12 @@
|
|||||||
var finalKey = new byte[encryptionKey.Length + 5 + (useAes ? 4 : 0)];
|
var finalKey = new byte[encryptionKey.Length + 5 + (useAes ? 4 : 0)];
|
||||||
Array.Copy(encryptionKey, finalKey, encryptionKey.Length);
|
Array.Copy(encryptionKey, finalKey, encryptionKey.Length);
|
||||||
|
|
||||||
finalKey[encryptionKey.Length] = (byte) reference.ObjectNumber;
|
finalKey[encryptionKey.Length] = (byte)reference.ObjectNumber;
|
||||||
finalKey[encryptionKey.Length + 1] = (byte) (reference.ObjectNumber >> 1);
|
finalKey[encryptionKey.Length + 1] = (byte)(reference.ObjectNumber >> 8);
|
||||||
finalKey[encryptionKey.Length + 2] = (byte) (reference.ObjectNumber >> 2);
|
finalKey[encryptionKey.Length + 2] = (byte)(reference.ObjectNumber >> 16);
|
||||||
|
|
||||||
finalKey[encryptionKey.Length + 3] = (byte) reference.Generation;
|
finalKey[encryptionKey.Length + 3] = (byte)reference.Generation;
|
||||||
finalKey[encryptionKey.Length + 4] = (byte) (reference.Generation >> 1);
|
finalKey[encryptionKey.Length + 4] = (byte)(reference.Generation >> 8);
|
||||||
|
|
||||||
// 2. If using the AES algorithm extend the encryption key by 4 bytes by adding the value "sAlT".
|
// 2. If using the AES algorithm extend the encryption key by 4 bytes by adding the value "sAlT".
|
||||||
if (useAes)
|
if (useAes)
|
||||||
@@ -195,32 +265,28 @@
|
|||||||
using (var md5 = MD5.Create())
|
using (var md5 = MD5.Create())
|
||||||
{
|
{
|
||||||
// 2. Initialize the MD5 hash function and pass the result of step 1 as input to this function.
|
// 2. Initialize the MD5 hash function and pass the result of step 1 as input to this function.
|
||||||
var has = md5.ComputeHash(passwordFull);
|
UpdateMd5(md5, passwordFull);
|
||||||
|
|
||||||
// 3. Pass the value of the encryption dictionary's owner key entry to the MD5 hash function.
|
// 3. Pass the value of the encryption dictionary's owner key entry to the MD5 hash function.
|
||||||
var has1 = md5.ComputeHash(ownerKey);
|
UpdateMd5(md5, ownerKey);
|
||||||
|
|
||||||
// 4. Treat the value of the P entry as an unsigned 4-byte integer.
|
// 4. Treat the value of the P entry as an unsigned 4-byte integer.
|
||||||
var unsigned = (uint) permissions;
|
var unsigned = (uint)permissions;
|
||||||
var permissionsBytes = new []
|
|
||||||
{
|
|
||||||
(byte) (unsigned),
|
|
||||||
(byte) (unsigned >> 8),
|
|
||||||
(byte) (unsigned >> 16),
|
|
||||||
(byte) (unsigned >> 24)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. Pass these bytes to the MD5 hash function, low-order byte first.
|
// 4. Pass these bytes to the MD5 hash function, low-order byte first.
|
||||||
var has2 = md5.ComputeHash(permissionsBytes);
|
UpdateMd5(md5, new[] { (byte)(unsigned) });
|
||||||
|
UpdateMd5(md5, new[] { (byte)(unsigned >> 8) });
|
||||||
|
UpdateMd5(md5, new[] { (byte)(unsigned >> 16) });
|
||||||
|
UpdateMd5(md5, new[] { (byte)(unsigned >> 24) });
|
||||||
|
|
||||||
// 5. Pass the first element of the file's file identifier array to the hash.
|
// 5. Pass the first element of the file's file identifier array to the hash.
|
||||||
var has3 = md5.ComputeHash(documentId);
|
UpdateMd5(md5, documentId);
|
||||||
|
|
||||||
// 6. (Revision 4 or greater) If document metadata is not being encrypted, pass 4 bytes
|
// 6. (Revision 4 or greater) If document metadata is not being encrypted, pass 4 bytes
|
||||||
// with the value 0xFFFFFFFF to the MD5 hash function.
|
// with the value 0xFFFFFFFF to the MD5 hash function.
|
||||||
if (revision >= 4)
|
if (revision >= 4)
|
||||||
{
|
{
|
||||||
md5.ComputeHash(new byte[] {0xFF, 0xFF, 0xFF, 0xFF});
|
UpdateMd5(md5, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Do the following 50 times: Take the output from the previous MD5 hash and
|
// 7. Do the following 50 times: Take the output from the previous MD5 hash and
|
||||||
@@ -235,11 +301,13 @@
|
|||||||
|
|
||||||
for (var i = 0; i < 50; i++)
|
for (var i = 0; i < 50; i++)
|
||||||
{
|
{
|
||||||
md5.ComputeHash(input, 0, n);
|
UpdateMd5(md5, input.Take(n).ToArray());
|
||||||
input = md5.Hash;
|
input = md5.Hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
md5.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0);
|
||||||
|
|
||||||
var result = new byte[length];
|
var result = new byte[length];
|
||||||
|
|
||||||
Array.Copy(md5.Hash, result, length);
|
Array.Copy(md5.Hash, result, length);
|
||||||
@@ -248,6 +316,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void UpdateMd5(MD5 md5, byte[] data)
|
||||||
|
{
|
||||||
|
md5.TransformBlock(data, 0, data.Length, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] GetPaddedPassword(byte[] password)
|
private static byte[] GetPaddedPassword(byte[] password)
|
||||||
{
|
{
|
||||||
if (password == null || password.Length == 0)
|
if (password == null || password.Length == 0)
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
namespace UglyToad.PdfPig.Encryption
|
namespace UglyToad.PdfPig.Encryption
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -8,9 +7,6 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IEncryptionHandler
|
internal interface IEncryptionHandler
|
||||||
{
|
{
|
||||||
/// <summary>
|
IToken Decrypt(IndirectReference reference, IToken token);
|
||||||
/// Decrypt the contents of the stream if encryption is applied.
|
|
||||||
/// </summary>
|
|
||||||
IReadOnlyList<byte> Decrypt(StreamToken stream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
namespace UglyToad.PdfPig.Encryption
|
namespace UglyToad.PdfPig.Encryption
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
internal class NoOpEncryptionHandler : IEncryptionHandler
|
internal class NoOpEncryptionHandler : IEncryptionHandler
|
||||||
@@ -11,9 +10,9 @@
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<byte> Decrypt(StreamToken stream)
|
public IToken Decrypt(IndirectReference reference, IToken token)
|
||||||
{
|
{
|
||||||
return stream.Data;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,7 +4,6 @@
|
|||||||
using SystemFonts;
|
using SystemFonts;
|
||||||
using Cmap;
|
using Cmap;
|
||||||
using Encodings;
|
using Encodings;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using IO;
|
using IO;
|
||||||
@@ -23,7 +22,6 @@
|
|||||||
private readonly ILog log;
|
private readonly ILog log;
|
||||||
private readonly IPdfTokenScanner pdfScanner;
|
private readonly IPdfTokenScanner pdfScanner;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly CMapCache cMapCache;
|
private readonly CMapCache cMapCache;
|
||||||
private readonly FontDescriptorFactory fontDescriptorFactory;
|
private readonly FontDescriptorFactory fontDescriptorFactory;
|
||||||
private readonly TrueTypeFontParser trueTypeFontParser;
|
private readonly TrueTypeFontParser trueTypeFontParser;
|
||||||
@@ -31,7 +29,6 @@
|
|||||||
private readonly ISystemFontFinder systemFontFinder;
|
private readonly ISystemFontFinder systemFontFinder;
|
||||||
|
|
||||||
public TrueTypeFontHandler(ILog log, IPdfTokenScanner pdfScanner, IFilterProvider filterProvider,
|
public TrueTypeFontHandler(ILog log, IPdfTokenScanner pdfScanner, IFilterProvider filterProvider,
|
||||||
IEncryptionHandler encryptionHandler,
|
|
||||||
CMapCache cMapCache,
|
CMapCache cMapCache,
|
||||||
FontDescriptorFactory fontDescriptorFactory,
|
FontDescriptorFactory fontDescriptorFactory,
|
||||||
TrueTypeFontParser trueTypeFontParser,
|
TrueTypeFontParser trueTypeFontParser,
|
||||||
@@ -40,7 +37,6 @@
|
|||||||
{
|
{
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
this.cMapCache = cMapCache;
|
this.cMapCache = cMapCache;
|
||||||
this.fontDescriptorFactory = fontDescriptorFactory;
|
this.fontDescriptorFactory = fontDescriptorFactory;
|
||||||
this.trueTypeFontParser = trueTypeFontParser;
|
this.trueTypeFontParser = trueTypeFontParser;
|
||||||
@@ -89,7 +85,7 @@
|
|||||||
{
|
{
|
||||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
||||||
|
|
||||||
var decodedUnicodeCMap = toUnicode.Decode(filterProvider, encryptionHandler);
|
var decodedUnicodeCMap = toUnicode.Decode(filterProvider);
|
||||||
|
|
||||||
if (decodedUnicodeCMap != null)
|
if (decodedUnicodeCMap != null)
|
||||||
{
|
{
|
||||||
@@ -129,7 +125,7 @@
|
|||||||
|
|
||||||
var fontFileStream = DirectObjectFinder.Get<StreamToken>(descriptor.FontFile.ObjectKey, pdfScanner);
|
var fontFileStream = DirectObjectFinder.Get<StreamToken>(descriptor.FontFile.ObjectKey, pdfScanner);
|
||||||
|
|
||||||
var fontFile = fontFileStream.Decode(filterProvider, encryptionHandler);
|
var fontFile = fontFileStream.Decode(filterProvider);
|
||||||
|
|
||||||
var font = trueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile)));
|
var font = trueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile)));
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
using CidFonts;
|
using CidFonts;
|
||||||
using Cmap;
|
using Cmap;
|
||||||
using Composite;
|
using Composite;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using IO;
|
using IO;
|
||||||
@@ -19,17 +18,14 @@
|
|||||||
private readonly CidFontFactory cidFontFactory;
|
private readonly CidFontFactory cidFontFactory;
|
||||||
private readonly CMapCache cMapCache;
|
private readonly CMapCache cMapCache;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly IPdfTokenScanner scanner;
|
private readonly IPdfTokenScanner scanner;
|
||||||
|
|
||||||
public Type0FontHandler(CidFontFactory cidFontFactory, CMapCache cMapCache, IFilterProvider filterProvider,
|
public Type0FontHandler(CidFontFactory cidFontFactory, CMapCache cMapCache, IFilterProvider filterProvider,
|
||||||
IEncryptionHandler encryptionHandler,
|
|
||||||
IPdfTokenScanner scanner)
|
IPdfTokenScanner scanner)
|
||||||
{
|
{
|
||||||
this.cidFontFactory = cidFontFactory;
|
this.cidFontFactory = cidFontFactory;
|
||||||
this.cMapCache = cMapCache;
|
this.cMapCache = cMapCache;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
this.scanner = scanner;
|
this.scanner = scanner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +68,7 @@
|
|||||||
|
|
||||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeValue, scanner);
|
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeValue, scanner);
|
||||||
|
|
||||||
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider, encryptionHandler);
|
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider);
|
||||||
|
|
||||||
if (decodedUnicodeCMap != null)
|
if (decodedUnicodeCMap != null)
|
||||||
{
|
{
|
||||||
@@ -151,7 +147,7 @@
|
|||||||
}
|
}
|
||||||
else if (value is StreamToken stream)
|
else if (value is StreamToken stream)
|
||||||
{
|
{
|
||||||
var decoded = stream.Decode(filterProvider, encryptionHandler);
|
var decoded = stream.Decode(filterProvider);
|
||||||
|
|
||||||
var cmap = cMapCache.Parse(new ByteArrayInputBytes(decoded), false);
|
var cmap = cMapCache.Parse(new ByteArrayInputBytes(decoded), false);
|
||||||
|
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Parser.Handlers
|
namespace UglyToad.PdfPig.Fonts.Parser.Handlers
|
||||||
{
|
{
|
||||||
using System.Linq;
|
|
||||||
using Cmap;
|
using Cmap;
|
||||||
using CompactFontFormat;
|
using CompactFontFormat;
|
||||||
using Encodings;
|
using Encodings;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using IO;
|
using IO;
|
||||||
@@ -22,14 +20,12 @@
|
|||||||
private readonly IPdfTokenScanner pdfScanner;
|
private readonly IPdfTokenScanner pdfScanner;
|
||||||
private readonly CMapCache cMapCache;
|
private readonly CMapCache cMapCache;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly FontDescriptorFactory fontDescriptorFactory;
|
private readonly FontDescriptorFactory fontDescriptorFactory;
|
||||||
private readonly IEncodingReader encodingReader;
|
private readonly IEncodingReader encodingReader;
|
||||||
private readonly Type1FontParser type1FontParser;
|
private readonly Type1FontParser type1FontParser;
|
||||||
private readonly CompactFontFormatParser compactFontFormatParser;
|
private readonly CompactFontFormatParser compactFontFormatParser;
|
||||||
|
|
||||||
public Type1FontHandler(IPdfTokenScanner pdfScanner, CMapCache cMapCache, IFilterProvider filterProvider,
|
public Type1FontHandler(IPdfTokenScanner pdfScanner, CMapCache cMapCache, IFilterProvider filterProvider,
|
||||||
IEncryptionHandler encryptionHandler,
|
|
||||||
FontDescriptorFactory fontDescriptorFactory,
|
FontDescriptorFactory fontDescriptorFactory,
|
||||||
IEncodingReader encodingReader,
|
IEncodingReader encodingReader,
|
||||||
Type1FontParser type1FontParser,
|
Type1FontParser type1FontParser,
|
||||||
@@ -38,7 +34,6 @@
|
|||||||
this.pdfScanner = pdfScanner;
|
this.pdfScanner = pdfScanner;
|
||||||
this.cMapCache = cMapCache;
|
this.cMapCache = cMapCache;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
this.fontDescriptorFactory = fontDescriptorFactory;
|
this.fontDescriptorFactory = fontDescriptorFactory;
|
||||||
this.encodingReader = encodingReader;
|
this.encodingReader = encodingReader;
|
||||||
this.type1FontParser = type1FontParser;
|
this.type1FontParser = type1FontParser;
|
||||||
@@ -93,7 +88,7 @@
|
|||||||
{
|
{
|
||||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
||||||
|
|
||||||
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider, encryptionHandler);
|
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider);
|
||||||
|
|
||||||
if (decodedUnicodeCMap != null)
|
if (decodedUnicodeCMap != null)
|
||||||
{
|
{
|
||||||
@@ -130,7 +125,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = stream.Decode(filterProvider, encryptionHandler);
|
var bytes = stream.Decode(filterProvider);
|
||||||
|
|
||||||
// We have a Compact Font Format font rather than an Adobe Type 1 Font.
|
// We have a Compact Font Format font rather than an Adobe Type 1 Font.
|
||||||
if (stream.StreamDictionary.TryGet(NameToken.Subtype, out NameToken subTypeName)
|
if (stream.StreamDictionary.TryGet(NameToken.Subtype, out NameToken subTypeName)
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
using Cmap;
|
using Cmap;
|
||||||
using Core;
|
using Core;
|
||||||
using Encodings;
|
using Encodings;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using Geometry;
|
using Geometry;
|
||||||
@@ -18,17 +17,14 @@
|
|||||||
{
|
{
|
||||||
private readonly CMapCache cMapCache;
|
private readonly CMapCache cMapCache;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly IEncodingReader encodingReader;
|
private readonly IEncodingReader encodingReader;
|
||||||
private readonly IPdfTokenScanner scanner;
|
private readonly IPdfTokenScanner scanner;
|
||||||
|
|
||||||
public Type3FontHandler(IPdfTokenScanner scanner, CMapCache cMapCache, IFilterProvider filterProvider,
|
public Type3FontHandler(IPdfTokenScanner scanner, CMapCache cMapCache, IFilterProvider filterProvider,
|
||||||
IEncryptionHandler encryptionHandler,
|
|
||||||
IEncodingReader encodingReader)
|
IEncodingReader encodingReader)
|
||||||
{
|
{
|
||||||
this.cMapCache = cMapCache;
|
this.cMapCache = cMapCache;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
this.encodingReader = encodingReader;
|
this.encodingReader = encodingReader;
|
||||||
this.scanner = scanner;
|
this.scanner = scanner;
|
||||||
}
|
}
|
||||||
@@ -50,7 +46,7 @@
|
|||||||
{
|
{
|
||||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, scanner);
|
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, scanner);
|
||||||
|
|
||||||
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider, encryptionHandler);
|
var decodedUnicodeCMap = toUnicode?.Decode(filterProvider);
|
||||||
|
|
||||||
if (decodedUnicodeCMap != null)
|
if (decodedUnicodeCMap != null)
|
||||||
{
|
{
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CidFonts;
|
using CidFonts;
|
||||||
using CompactFontFormat;
|
using CompactFontFormat;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using Geometry;
|
using Geometry;
|
||||||
@@ -23,21 +22,18 @@
|
|||||||
private readonly TrueTypeFontParser trueTypeFontParser;
|
private readonly TrueTypeFontParser trueTypeFontParser;
|
||||||
private readonly CompactFontFormatParser compactFontFormatParser;
|
private readonly CompactFontFormatParser compactFontFormatParser;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly IPdfTokenScanner pdfScanner;
|
private readonly IPdfTokenScanner pdfScanner;
|
||||||
|
|
||||||
public CidFontFactory(IPdfTokenScanner pdfScanner, FontDescriptorFactory descriptorFactory,
|
public CidFontFactory(IPdfTokenScanner pdfScanner, FontDescriptorFactory descriptorFactory,
|
||||||
TrueTypeFontParser trueTypeFontParser,
|
TrueTypeFontParser trueTypeFontParser,
|
||||||
CompactFontFormatParser compactFontFormatParser,
|
CompactFontFormatParser compactFontFormatParser,
|
||||||
IFilterProvider filterProvider,
|
IFilterProvider filterProvider)
|
||||||
IEncryptionHandler encryptionHandler)
|
|
||||||
{
|
{
|
||||||
this.pdfScanner = pdfScanner;
|
this.pdfScanner = pdfScanner;
|
||||||
this.descriptorFactory = descriptorFactory;
|
this.descriptorFactory = descriptorFactory;
|
||||||
this.trueTypeFontParser = trueTypeFontParser;
|
this.trueTypeFontParser = trueTypeFontParser;
|
||||||
this.compactFontFormatParser = compactFontFormatParser;
|
this.compactFontFormatParser = compactFontFormatParser;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICidFont Generate(DictionaryToken dictionary, bool isLenientParsing)
|
public ICidFont Generate(DictionaryToken dictionary, bool isLenientParsing)
|
||||||
@@ -109,7 +105,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fontFile = fontFileStream.Decode(filterProvider, encryptionHandler);
|
var fontFile = fontFileStream.Decode(filterProvider);
|
||||||
|
|
||||||
switch (descriptor.FontFile.FileType)
|
switch (descriptor.FontFile.FileType)
|
||||||
{
|
{
|
||||||
@@ -130,7 +126,7 @@
|
|||||||
|
|
||||||
if (subtypeName == NameToken.CidFontType0C)
|
if (subtypeName == NameToken.CidFontType0C)
|
||||||
{
|
{
|
||||||
var bytes = str.Decode(filterProvider, encryptionHandler);
|
var bytes = str.Decode(filterProvider);
|
||||||
var font = compactFontFormatParser.Parse(new CompactFontFormatData(bytes));
|
var font = compactFontFormatParser.Parse(new CompactFontFormatData(bytes));
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
@@ -302,7 +298,7 @@
|
|||||||
|
|
||||||
var stream = DirectObjectFinder.Get<StreamToken>(entry, pdfScanner);
|
var stream = DirectObjectFinder.Get<StreamToken>(entry, pdfScanner);
|
||||||
|
|
||||||
var bytes = stream.Decode(filterProvider, encryptionHandler);
|
var bytes = stream.Decode(filterProvider);
|
||||||
|
|
||||||
return new CharacterIdentifierToGlyphIndexMap(bytes);
|
return new CharacterIdentifierToGlyphIndexMap(bytes);
|
||||||
}
|
}
|
||||||
|
@@ -22,20 +22,17 @@
|
|||||||
private readonly IPdfTokenScanner pdfScanner;
|
private readonly IPdfTokenScanner pdfScanner;
|
||||||
private readonly IResourceStore resourceStore;
|
private readonly IResourceStore resourceStore;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
private readonly IEncryptionHandler encryptionHandler;
|
|
||||||
private readonly IPageContentParser pageContentParser;
|
private readonly IPageContentParser pageContentParser;
|
||||||
private readonly XObjectFactory xObjectFactory;
|
private readonly XObjectFactory xObjectFactory;
|
||||||
private readonly ILog log;
|
private readonly ILog log;
|
||||||
|
|
||||||
public PageFactory(IPdfTokenScanner pdfScanner, IResourceStore resourceStore, IFilterProvider filterProvider,
|
public PageFactory(IPdfTokenScanner pdfScanner, IResourceStore resourceStore, IFilterProvider filterProvider,
|
||||||
IEncryptionHandler encryptionHandler,
|
|
||||||
IPageContentParser pageContentParser,
|
IPageContentParser pageContentParser,
|
||||||
XObjectFactory xObjectFactory,
|
XObjectFactory xObjectFactory,
|
||||||
ILog log)
|
ILog log)
|
||||||
{
|
{
|
||||||
this.resourceStore = resourceStore;
|
this.resourceStore = resourceStore;
|
||||||
this.filterProvider = filterProvider;
|
this.filterProvider = filterProvider;
|
||||||
this.encryptionHandler = encryptionHandler;
|
|
||||||
this.pageContentParser = pageContentParser;
|
this.pageContentParser = pageContentParser;
|
||||||
this.xObjectFactory = xObjectFactory;
|
this.xObjectFactory = xObjectFactory;
|
||||||
this.log = log;
|
this.log = log;
|
||||||
@@ -88,7 +85,7 @@
|
|||||||
throw new InvalidOperationException($"Could not find the contents for object {obj}.");
|
throw new InvalidOperationException($"Could not find the contents for object {obj}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes.AddRange(contentStream.Decode(filterProvider, encryptionHandler));
|
bytes.AddRange(contentStream.Decode(filterProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
content = GetContent(bytes, cropBox, userSpaceUnit, isLenientParsing);
|
content = GetContent(bytes, cropBox, userSpaceUnit, isLenientParsing);
|
||||||
@@ -102,7 +99,7 @@
|
|||||||
throw new InvalidOperationException("Failed to parse the content for the page: " + number);
|
throw new InvalidOperationException("Failed to parse the content for the page: " + number);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = contentStream.Decode(filterProvider, encryptionHandler);
|
var bytes = contentStream.Decode(filterProvider);
|
||||||
|
|
||||||
content = GetContent(bytes, cropBox, userSpaceUnit, isLenientParsing);
|
content = GetContent(bytes, cropBox, userSpaceUnit, isLenientParsing);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
namespace UglyToad.PdfPig.Parser.Parts.CrossReference
|
namespace UglyToad.PdfPig.Parser.Parts.CrossReference
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Encryption;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Filters;
|
using Filters;
|
||||||
using PdfPig.CrossReference;
|
using PdfPig.CrossReference;
|
||||||
@@ -22,7 +21,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CrossReferenceTablePart Parse(long streamOffset, StreamToken stream)
|
public CrossReferenceTablePart Parse(long streamOffset, StreamToken stream)
|
||||||
{
|
{
|
||||||
var decoded = stream.Decode(filterProvider, NoOpEncryptionHandler.Instance);
|
var decoded = stream.Decode(filterProvider);
|
||||||
|
|
||||||
var fieldSizes = new CrossReferenceStreamFieldSize(stream.StreamDictionary);
|
var fieldSizes = new CrossReferenceStreamFieldSize(stream.StreamDictionary);
|
||||||
|
|
||||||
|
@@ -107,25 +107,25 @@
|
|||||||
|
|
||||||
var rootDictionary = ParseTrailer(crossReferenceTable, isLenientParsing, pdfScanner, out var encryptionDictionary);
|
var rootDictionary = ParseTrailer(crossReferenceTable, isLenientParsing, pdfScanner, out var encryptionDictionary);
|
||||||
|
|
||||||
var encryptionHandler = new EncryptionHandler(encryptionDictionary, crossReferenceTable.Trailer, string.Empty);
|
var encryptionHandler = encryptionDictionary != null ? (IEncryptionHandler)new EncryptionHandler(encryptionDictionary, crossReferenceTable.Trailer, string.Empty)
|
||||||
|
: NoOpEncryptionHandler.Instance;
|
||||||
|
|
||||||
pdfScanner.UpdateEncryptionHandler(encryptionHandler);
|
pdfScanner.UpdateEncryptionHandler(encryptionHandler);
|
||||||
|
|
||||||
var cidFontFactory = new CidFontFactory(pdfScanner, fontDescriptorFactory, trueTypeFontParser, compactFontFormatParser, filterProvider, encryptionHandler);
|
var cidFontFactory = new CidFontFactory(pdfScanner, fontDescriptorFactory, trueTypeFontParser, compactFontFormatParser, filterProvider);
|
||||||
var encodingReader = new EncodingReader(pdfScanner);
|
var encodingReader = new EncodingReader(pdfScanner);
|
||||||
|
|
||||||
var fontFactory = new FontFactory(log, new Type0FontHandler(cidFontFactory,
|
var fontFactory = new FontFactory(log, new Type0FontHandler(cidFontFactory,
|
||||||
cMapCache,
|
cMapCache,
|
||||||
filterProvider, encryptionHandler, pdfScanner),
|
filterProvider, pdfScanner),
|
||||||
new TrueTypeFontHandler(log, pdfScanner, filterProvider, encryptionHandler, cMapCache, fontDescriptorFactory, trueTypeFontParser, encodingReader, new SystemFontFinder(new TrueTypeFontParser())),
|
new TrueTypeFontHandler(log, pdfScanner, filterProvider, cMapCache, fontDescriptorFactory, trueTypeFontParser, encodingReader, new SystemFontFinder(new TrueTypeFontParser())),
|
||||||
new Type1FontHandler(pdfScanner, cMapCache, filterProvider, encryptionHandler, fontDescriptorFactory, encodingReader,
|
new Type1FontHandler(pdfScanner, cMapCache, filterProvider, fontDescriptorFactory, encodingReader,
|
||||||
new Type1FontParser(new Type1EncryptedPortionParser()), compactFontFormatParser),
|
new Type1FontParser(new Type1EncryptedPortionParser()), compactFontFormatParser),
|
||||||
new Type3FontHandler(pdfScanner, cMapCache, filterProvider, encryptionHandler, encodingReader));
|
new Type3FontHandler(pdfScanner, cMapCache, filterProvider, encodingReader));
|
||||||
|
|
||||||
var resourceContainer = new ResourceContainer(pdfScanner, fontFactory);
|
var resourceContainer = new ResourceContainer(pdfScanner, fontFactory);
|
||||||
|
|
||||||
var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider,
|
var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider,
|
||||||
encryptionHandler,
|
|
||||||
new PageContentParser(new ReflectionGraphicsStateOperationFactory()),
|
new PageContentParser(new ReflectionGraphicsStateOperationFactory()),
|
||||||
new XObjectFactory(), log);
|
new XObjectFactory(), log);
|
||||||
var informationFactory = new DocumentInformationFactory();
|
var informationFactory = new DocumentInformationFactory();
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
var caching = new ParsingCachingProviders(bruteForceSearcher, resourceContainer);
|
var caching = new ParsingCachingProviders(bruteForceSearcher, resourceContainer);
|
||||||
|
|
||||||
var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider, encryptionHandler);
|
var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider);
|
||||||
|
|
||||||
return new PdfDocument(log, inputBytes, version, crossReferenceTable, isLenientParsing, caching, pageFactory, catalog, information,
|
return new PdfDocument(log, inputBytes, version, crossReferenceTable, isLenientParsing, caching, pageFactory, catalog, information,
|
||||||
encryptionDictionary,
|
encryptionDictionary,
|
||||||
|
@@ -158,6 +158,8 @@
|
|||||||
token = readTokens[readTokens.Count - 1];
|
token = readTokens[readTokens.Count - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token = encryptionHandler.Decrypt(reference, token);
|
||||||
|
|
||||||
CurrentToken = new ObjectToken(startPosition, reference, token);
|
CurrentToken = new ObjectToken(startPosition, reference, token);
|
||||||
|
|
||||||
objectLocationProvider.UpdateOffset(reference, startPosition);
|
objectLocationProvider.UpdateOffset(reference, startPosition);
|
||||||
@@ -537,7 +539,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read the N integers
|
// Read the N integers
|
||||||
var bytes = new ByteArrayInputBytes(stream.Decode(filterProvider, encryptionHandler));
|
var bytes = new ByteArrayInputBytes(stream.Decode(filterProvider));
|
||||||
|
|
||||||
var scanner = new CoreTokenScanner(bytes);
|
var scanner = new CoreTokenScanner(bytes);
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Encryption;
|
|
||||||
using Filters;
|
using Filters;
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@
|
|||||||
Data = data ?? throw new ArgumentNullException(nameof(data));
|
Data = data ?? throw new ArgumentNullException(nameof(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IReadOnlyList<byte> Decode(IFilterProvider filterProvider, IEncryptionHandler encryptionHandler)
|
internal IReadOnlyList<byte> Decode(IFilterProvider filterProvider)
|
||||||
{
|
{
|
||||||
lock (lockObject)
|
lock (lockObject)
|
||||||
{
|
{
|
||||||
@@ -50,7 +49,7 @@
|
|||||||
|
|
||||||
var filters = filterProvider.GetFilters(StreamDictionary);
|
var filters = filterProvider.GetFilters(StreamDictionary);
|
||||||
|
|
||||||
var transform = encryptionHandler.Decrypt(this);
|
var transform = Data;
|
||||||
for (var i = 0; i < filters.Count; i++)
|
for (var i = 0; i < filters.Count; i++)
|
||||||
{
|
{
|
||||||
transform = filters[i].Decode(transform, StreamDictionary, i);
|
transform = filters[i].Decode(transform, StreamDictionary, i);
|
||||||
|
Reference in New Issue
Block a user