mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-18 12:20:15 +01:00
Handling of encrypted documents - basic functionality for standard encryption handler revision 2,3,4
This commit is contained in:
parent
0a6e7bb866
commit
0434a70de5
@ -33,8 +33,9 @@
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFDocumentReader::PDFDocumentReader() :
|
||||
m_successfull(true)
|
||||
PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) :
|
||||
m_successfull(true),
|
||||
m_getPasswordCallback(getPasswordCallback)
|
||||
{
|
||||
|
||||
}
|
||||
@ -277,18 +278,42 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
||||
}
|
||||
const PDFDictionary* trailerDictionary = trailerDictionaryObject.getDictionary();
|
||||
|
||||
// Read the document ID
|
||||
QByteArray id;
|
||||
const PDFObject& idArrayObject = trailerDictionary->get("ID");
|
||||
if (idArrayObject.isArray())
|
||||
{
|
||||
const PDFArray* idArray = idArrayObject.getArray();
|
||||
if (idArray->getCount() > 0)
|
||||
{
|
||||
const PDFObject& idArrayItem = idArray->getItem(0);
|
||||
if (idArrayItem.isString())
|
||||
{
|
||||
id = idArrayItem.getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PDFObject encryptObject = trailerDictionary->get("Encrypt");
|
||||
if (encryptObject.isReference())
|
||||
{
|
||||
PDFObjectReference encryptObjectReference = encryptObject.getReference();
|
||||
if (encryptObjectReference.objectNumber < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation)
|
||||
if (static_cast<size_t>(encryptObjectReference.objectNumber) < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation)
|
||||
{
|
||||
encryptObject = objects[encryptObjectReference.objectNumber].object;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the security handler
|
||||
PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject);
|
||||
PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id);
|
||||
PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->authenticate(m_getPasswordCallback);
|
||||
|
||||
if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed ||
|
||||
authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
|
||||
{
|
||||
// TODO: If user cancels it, do not display error message.
|
||||
throw PDFParserException(PDFTranslationContext::tr("Authorization failed. Bad password provided."));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// SECURITY - security handler created
|
||||
|
@ -36,7 +36,7 @@ class PDFFORQTLIBSHARED_EXPORT PDFDocumentReader
|
||||
Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader)
|
||||
|
||||
public:
|
||||
explicit PDFDocumentReader();
|
||||
explicit PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback);
|
||||
|
||||
constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete;
|
||||
constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete;
|
||||
@ -91,6 +91,9 @@ private:
|
||||
|
||||
/// Version of the scanned file
|
||||
PDFVersion m_version;
|
||||
|
||||
/// Callback to obtain password from the user
|
||||
std::function<QString(bool*)> m_getPasswordCallback;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
@ -19,11 +19,22 @@
|
||||
#include "pdfexception.h"
|
||||
|
||||
#include <openssl/rc4.h>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject)
|
||||
// Padding password
|
||||
static constexpr std::array<uint8_t, 32> PDFPasswordPadding = {
|
||||
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
|
||||
};
|
||||
|
||||
PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id)
|
||||
{
|
||||
if (encryptionDictionaryObject.isNull())
|
||||
{
|
||||
@ -215,7 +226,337 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj
|
||||
}
|
||||
}
|
||||
|
||||
int R = getInt(dictionary, "R", true);
|
||||
if (R < 2 || R > 6 || R == 5)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(R));
|
||||
}
|
||||
handler.m_R = R;
|
||||
|
||||
auto readByteArray = [dictionary](const char* key, int size)
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
const PDFObject& object = dictionary->get(key);
|
||||
if (object.isString())
|
||||
{
|
||||
result = object.getString();
|
||||
|
||||
if (result.size() != size)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Expected %1 characters long string in entry '%2'. Provided length is %3.").arg(size).arg(QString::fromLatin1(key)).arg(result.size()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Expected %1 characters long string in entry '%2'.").arg(size).arg(QString::fromLatin1(key)));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
handler.m_O = readByteArray("O", (R != 6) ? 32 : 48);
|
||||
handler.m_U = readByteArray("U", (R != 6) ? 32 : 48);
|
||||
|
||||
handler.m_permissions = static_cast<uint32_t>(static_cast<int>(getInt(dictionary, "P", true)));
|
||||
|
||||
if (R == 6)
|
||||
{
|
||||
handler.m_OE = readByteArray("OE", 32);
|
||||
handler.m_UE = readByteArray("UE", 32);
|
||||
handler.m_Perms = readByteArray("Perms", 16);
|
||||
}
|
||||
|
||||
const PDFObject& encryptMetadataObject = dictionary->get("EncryptMetadata");
|
||||
if (encryptMetadataObject.isBool())
|
||||
{
|
||||
handler.m_encryptMetadata = encryptMetadataObject.getBool();
|
||||
}
|
||||
|
||||
handler.m_ID = id;
|
||||
|
||||
return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler)));
|
||||
}
|
||||
|
||||
PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate(const std::function<QString(bool*)>& getPasswordCallback)
|
||||
{
|
||||
QByteArray password;
|
||||
bool passwordObtained = true;
|
||||
|
||||
while (passwordObtained)
|
||||
{
|
||||
switch (m_R)
|
||||
{
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
// Try to authorize by owner password
|
||||
{
|
||||
QByteArray userPassword = createUserPasswordFromOwnerPassword(password);
|
||||
QByteArray fileEncryptionKey = createFileEncryptionKey(userPassword);
|
||||
QByteArray U = createEntryValueU_r234(fileEncryptionKey);
|
||||
|
||||
if (U == m_U)
|
||||
{
|
||||
// We have authorized owner access
|
||||
return AuthorizationResult::OwnerAuthorized;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to authorize user password
|
||||
QByteArray fileEncryptionKey = createFileEncryptionKey(password);
|
||||
QByteArray U = createEntryValueU_r234(fileEncryptionKey);
|
||||
|
||||
if (U == m_U)
|
||||
{
|
||||
// We have authorized owner access
|
||||
return AuthorizationResult::UserAuthorized;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 6:
|
||||
{
|
||||
// TODO: Implement revision 6 encryption
|
||||
return AuthorizationResult::Failed;
|
||||
}
|
||||
|
||||
default:
|
||||
return AuthorizationResult::Failed;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
|
||||
password = getPasswordCallback(&passwordObtained).toUtf8();
|
||||
}
|
||||
|
||||
return AuthorizationResult::Cancelled;
|
||||
}
|
||||
|
||||
QByteArray PDFStandardSecurityHandler::createFileEncryptionKey(const QByteArray& password) const
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
switch (m_R)
|
||||
{
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
std::array<uint8_t, 32> paddedPassword = createPaddedPassword32(password);
|
||||
uint32_t transformedPermissions = qToLittleEndian(m_permissions);
|
||||
|
||||
MD5_CTX context = { };
|
||||
MD5_Init(&context);
|
||||
MD5_Update(&context, paddedPassword.data(), paddedPassword.size());
|
||||
MD5_Update(&context, m_O.constData(), m_O.size());
|
||||
MD5_Update(&context, &transformedPermissions, sizeof(transformedPermissions));
|
||||
MD5_Update(&context, m_ID.constData(), m_ID.size());
|
||||
|
||||
if (!m_encryptMetadata)
|
||||
{
|
||||
constexpr uint32_t value = 0xFFFFFFFF;
|
||||
MD5_Update(&context, &value, sizeof(value));
|
||||
}
|
||||
|
||||
std::array<uint8_t, MD5_DIGEST_LENGTH> fileEncryptionKey;
|
||||
MD5_Final(fileEncryptionKey.data(), &context);
|
||||
|
||||
const int keyByteLength = m_keyLength / 8;
|
||||
if (keyByteLength > MD5_DIGEST_LENGTH)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").arg(keyByteLength).arg(MD5_DIGEST_LENGTH));
|
||||
}
|
||||
|
||||
if (m_R >= 3)
|
||||
{
|
||||
for (int i = 0; i < 50; ++i)
|
||||
{
|
||||
|
||||
MD5_Init(&context);
|
||||
MD5_Update(&context, fileEncryptionKey.data(), keyByteLength);
|
||||
MD5_Final(fileEncryptionKey.data(), &context);
|
||||
}
|
||||
}
|
||||
|
||||
result.resize(keyByteLength);
|
||||
std::copy_n(fileEncryptionKey.cbegin(), keyByteLength, result.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
case 6:
|
||||
{
|
||||
// TODO: Implement revision 6 key
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray PDFStandardSecurityHandler::createEntryValueU_r234(const QByteArray& fileEncryptionKey) const
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
switch (m_R)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
RC4_KEY key = { };
|
||||
RC4_set_key(&key, fileEncryptionKey.size(), reinterpret_cast<const unsigned char*>(fileEncryptionKey.data()));
|
||||
|
||||
result.resize(static_cast<int>(PDFPasswordPadding.size()));
|
||||
RC4(&key, PDFPasswordPadding.size(), PDFPasswordPadding.data(), reinterpret_cast<unsigned char*>(result.data()));
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
std::array<uint8_t, MD5_DIGEST_LENGTH> hash;
|
||||
|
||||
MD5_CTX context = { };
|
||||
MD5_Init(&context);
|
||||
MD5_Update(&context, PDFPasswordPadding.data(), PDFPasswordPadding.size());
|
||||
MD5_Update(&context, m_ID.data(), m_ID.size());
|
||||
MD5_Final(hash.data(), &context);
|
||||
|
||||
RC4_KEY key = { };
|
||||
RC4_set_key(&key, fileEncryptionKey.size(), reinterpret_cast<const unsigned char*>(fileEncryptionKey.data()));
|
||||
|
||||
std::array<uint8_t, MD5_DIGEST_LENGTH> encryptedHash;
|
||||
RC4(&key, hash.size(), hash.data(), reinterpret_cast<unsigned char*>(encryptedHash.data()));
|
||||
|
||||
QByteArray transformedKey = fileEncryptionKey;
|
||||
for (int i = 1; i <= 19; ++i)
|
||||
{
|
||||
for (int j = 0, keySize = fileEncryptionKey.size(); j < keySize; ++j)
|
||||
{
|
||||
transformedKey[j] = static_cast<uint8_t>(fileEncryptionKey[j]) ^ static_cast<uint8_t>(i);
|
||||
}
|
||||
|
||||
RC4_set_key(&key, transformedKey.size(), reinterpret_cast<const unsigned char*>(transformedKey.data()));
|
||||
RC4(&key, encryptedHash.size(), encryptedHash.data(), reinterpret_cast<unsigned char*>(encryptedHash.data()));
|
||||
}
|
||||
|
||||
// We do a hack here. In the PDF's specification, it is written, that arbitrary 16 bytes
|
||||
// are appended to the 16 bytes result. We use the last 16 bytes of the U entry, because we
|
||||
// want to compare byte arrays entirely (otherwise we must compare only 16 bytes to authenticate
|
||||
// user password).
|
||||
result = m_U;
|
||||
std::copy_n(encryptedHash.begin(), encryptedHash.size(), result.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray PDFStandardSecurityHandler::createUserPasswordFromOwnerPassword(const QByteArray& password) const
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
std::array<uint8_t, 32> paddedPassword = createPaddedPassword32(password);
|
||||
std::array<uint8_t, MD5_DIGEST_LENGTH> hash;
|
||||
|
||||
MD5_CTX context = { };
|
||||
MD5_Init(&context);
|
||||
MD5_Update(&context, paddedPassword.data(), paddedPassword.size());
|
||||
MD5_Final(hash.data(), &context);
|
||||
|
||||
const int keyByteLength = m_keyLength / 8;
|
||||
if (keyByteLength > MD5_DIGEST_LENGTH)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").arg(keyByteLength).arg(MD5_DIGEST_LENGTH));
|
||||
}
|
||||
|
||||
if (m_R >= 3)
|
||||
{
|
||||
for (int i = 0; i < 50; ++i)
|
||||
{
|
||||
|
||||
MD5_Init(&context);
|
||||
MD5_Update(&context, hash.data(), keyByteLength);
|
||||
MD5_Final(hash.data(), &context);
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_R)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
RC4_KEY key = { };
|
||||
RC4_set_key(&key, keyByteLength, reinterpret_cast<const unsigned char*>(hash.data()));
|
||||
result.resize(m_O.size());
|
||||
RC4(&key, m_O.size(), reinterpret_cast<const unsigned char*>(m_O.data()), reinterpret_cast<unsigned char*>(result.data()));
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
QByteArray buffer = m_O;
|
||||
QByteArray transformedKey;
|
||||
transformedKey.resize(keyByteLength);
|
||||
std::copy_n(hash.data(), keyByteLength, transformedKey.data());
|
||||
|
||||
for (int i = 19; i >= 0; --i)
|
||||
{
|
||||
for (int j = 0, keySize = transformedKey.size(); j < keySize; ++j)
|
||||
{
|
||||
transformedKey[j] = static_cast<uint8_t>(hash[j]) ^ static_cast<uint8_t>(i);
|
||||
}
|
||||
|
||||
RC4_KEY key = { };
|
||||
RC4_set_key(&key, transformedKey.size(), reinterpret_cast<const unsigned char*>(transformedKey.data()));
|
||||
RC4(&key, buffer.size(), reinterpret_cast<const unsigned char*>(buffer.data()), reinterpret_cast<unsigned char*>(buffer.data()));
|
||||
}
|
||||
|
||||
result = buffer;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> PDFStandardSecurityHandler::createPaddedPassword32(const QByteArray& password) const
|
||||
{
|
||||
std::array<uint8_t, 32> result = { };
|
||||
|
||||
int copiedBytes = qMin<int>(static_cast<int>(result.size()), password.size());
|
||||
auto it = result.begin();
|
||||
|
||||
for (int i = 0; i < copiedBytes; ++i)
|
||||
{
|
||||
*it++ = static_cast<uint8_t>(password[i]);
|
||||
}
|
||||
|
||||
auto itPadding = PDFPasswordPadding.cbegin();
|
||||
for (; it != result.cend();)
|
||||
{
|
||||
Q_ASSERT(itPadding != PDFPasswordPadding.cend());
|
||||
*it++ = *itPadding++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
@ -73,14 +74,24 @@ public:
|
||||
explicit PDFSecurityHandler() = default;
|
||||
virtual ~PDFSecurityHandler() = default;
|
||||
|
||||
enum class AuthorizationResult
|
||||
{
|
||||
UserAuthorized,
|
||||
OwnerAuthorized,
|
||||
Failed,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
virtual EncryptionMode getMode() const = 0;
|
||||
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>& getPasswordCallback) = 0;
|
||||
|
||||
/// Creates a security handler from the object. If object is null, then
|
||||
/// "None" security handler is created. If error occurs, then exception is thrown.
|
||||
/// \param encryptionDictionaryObject Encryption dictionary object
|
||||
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject);
|
||||
/// \param id First part of the id of the document
|
||||
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||
|
||||
private:
|
||||
protected:
|
||||
/// Version of the encryption, shall be a number from 1 to 5, according the
|
||||
/// PDF specification. Other values are invalid.
|
||||
int m_V = 0;
|
||||
@ -107,6 +118,7 @@ class PDFNoneSecurityHandler : public PDFSecurityHandler
|
||||
{
|
||||
public:
|
||||
virtual EncryptionMode getMode() const { return EncryptionMode::None; }
|
||||
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>&) override { return AuthorizationResult::OwnerAuthorized; }
|
||||
};
|
||||
|
||||
/// Specifies the security using standard security handler (see PDF specification
|
||||
@ -115,9 +127,67 @@ class PDFStandardSecurityHandler : public PDFSecurityHandler
|
||||
{
|
||||
public:
|
||||
virtual EncryptionMode getMode() const { return EncryptionMode::Standard; }
|
||||
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>& getPasswordCallback) override;
|
||||
|
||||
struct AuthorizationData
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
friend static PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||
|
||||
/// Creates file encryption key from passed password, based on the revision
|
||||
/// \param password Password to be used to create file encryption key
|
||||
/// \note Password must be in PDFDocEncoding for revision 4 or earlier,
|
||||
/// otherwise it must be encoded in UTF-8.
|
||||
QByteArray createFileEncryptionKey(const QByteArray& password) const;
|
||||
|
||||
/// Creates entry value U based on the file encryption key. This function
|
||||
/// is valid only for revisions 2, 3 and 4.
|
||||
/// \param fileEncryptionKey File encryption key
|
||||
QByteArray createEntryValueU_r234(const QByteArray& fileEncryptionKey) const;
|
||||
|
||||
/// Creates user password from the owner password. User password must be then
|
||||
/// authenticated.
|
||||
QByteArray createUserPasswordFromOwnerPassword(const QByteArray& password) const;
|
||||
|
||||
/// Creates 32-byte padded password from the passed password. If password is empty,
|
||||
/// then padding password is returned.
|
||||
std::array<uint8_t, 32> createPaddedPassword32(const QByteArray& password) const;
|
||||
|
||||
/// Revision number of standard security number
|
||||
int m_R = 0;
|
||||
|
||||
/// 32 byte string if revision number is 4 or less, or 48 byte string,
|
||||
/// if revision number is 6, based on both owner and user passwords,
|
||||
/// used for authenticate owner, and create a file encryption key.
|
||||
QByteArray m_O;
|
||||
|
||||
/// 32 byte string if revision number is 4 or less, or 48 byte string,
|
||||
/// if revision number is 6, based on both owner and user passwords,
|
||||
/// used for authenticate owner, and create a file encryption key.
|
||||
QByteArray m_U;
|
||||
|
||||
/// For revision number 6 only. 32 bytes string based on both owner
|
||||
/// and user password, that shall be used to compute file encryption key.
|
||||
QByteArray m_OE;
|
||||
|
||||
/// For revision number 6 only. 32 bytes string based on both owner
|
||||
/// and user password, that shall be used to compute file encryption key.
|
||||
QByteArray m_UE;
|
||||
|
||||
/// What operations shall be permitted, when document is opened with user access.
|
||||
uint32_t m_permissions = 0;
|
||||
|
||||
/// For revision number 6 only. 16 byte encrypted version of permissions.
|
||||
QByteArray m_Perms;
|
||||
|
||||
/// Optional, meaningfull only if revision number is 4 or 5.
|
||||
bool m_encryptMetadata = true;
|
||||
|
||||
/// First part of the id of the document
|
||||
QByteArray m_ID;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <QTreeView>
|
||||
#include <QLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
|
||||
namespace pdfviewer
|
||||
{
|
||||
@ -268,9 +269,15 @@ void PDFViewerMainWindow::openDocument(const QString& fileName)
|
||||
// First close old document
|
||||
closeDocument();
|
||||
|
||||
// Password callback
|
||||
auto getPasswordCallback = [this](bool* ok) -> QString
|
||||
{
|
||||
return QInputDialog::getText(this, tr("Encrypted document"), tr("Enter password to acces document content"), QLineEdit::Password, QString(), ok);
|
||||
};
|
||||
|
||||
// Try to open a new document
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
pdf::PDFDocumentReader reader;
|
||||
pdf::PDFDocumentReader reader(qMove(getPasswordCallback));
|
||||
pdf::PDFDocument document = reader.readFromFile(fileName);
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user