mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Adjusting password
This commit is contained in:
		| @@ -34,7 +34,7 @@ namespace pdf | ||||
| { | ||||
|  | ||||
| PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) : | ||||
|     m_successfull(true), | ||||
|     m_result(Result::OK), | ||||
|     m_getPasswordCallback(getPasswordCallback) | ||||
| { | ||||
|  | ||||
| @@ -56,13 +56,13 @@ PDFDocument PDFDocumentReader::readFromFile(const QString& fileName) | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_successfull = false; | ||||
|             m_result = Result::Failed; | ||||
|             m_errorMessage = tr("File '%1' cannot be opened for reading. %1").arg(file.errorString()); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         m_successfull = false; | ||||
|         m_result = Result::Failed; | ||||
|         m_errorMessage = tr("File '%1' doesn't exist.").arg(fileName); | ||||
|     } | ||||
|  | ||||
| @@ -82,7 +82,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device) | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             m_successfull = false; | ||||
|             m_result = Result::Failed; | ||||
|             m_errorMessage = tr("Device is not opened for reading."); | ||||
|         } | ||||
|     } | ||||
| @@ -94,7 +94,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device) | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         m_successfull = false; | ||||
|         m_result = Result::Failed; | ||||
|         m_errorMessage = tr("Can't open device for reading."); | ||||
|     } | ||||
|  | ||||
| @@ -246,7 +246,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|         { | ||||
|             Q_ASSERT(entry.type == PDFXRefTable::EntryType::Occupied); | ||||
|  | ||||
|             if (m_successfull) | ||||
|             if (m_result == Result::OK) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
| @@ -259,7 +259,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|                 catch (PDFParserException exception) | ||||
|                 { | ||||
|                     QMutexLocker lock(&m_mutex); | ||||
|                     m_successfull = false; | ||||
|                     m_result = Result::Failed; | ||||
|                     m_errorMessage = exception.getMessage(); | ||||
|                 } | ||||
|             } | ||||
| @@ -318,10 +318,15 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|         PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id); | ||||
|         PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->authenticate(m_getPasswordCallback); | ||||
|  | ||||
|         if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed || | ||||
|             authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled) | ||||
|         if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled) | ||||
|         { | ||||
|             // User cancelled the document reading | ||||
|             m_result = Result::Cancelled; | ||||
|             return PDFDocument(); | ||||
|         } | ||||
|  | ||||
|         if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed) | ||||
|         { | ||||
|             // TODO: If user cancels it, do not display error message. | ||||
|             throw PDFParserException(PDFTranslationContext::tr("Authorization failed. Bad password provided.")); | ||||
|         } | ||||
|  | ||||
| @@ -340,7 +345,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|  | ||||
|         auto processObjectStream = [this, &getObject, &objectFetcher, &objects, &objectStreamEntries] (const PDFObjectReference& objectStreamReference) | ||||
|         { | ||||
|             if (!m_successfull) | ||||
|             if (m_result != Result::OK) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| @@ -423,7 +428,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|             catch (PDFParserException exception) | ||||
|             { | ||||
|                 QMutexLocker lock(&m_mutex); | ||||
|                 m_successfull = false; | ||||
|                 m_result = Result::Failed; | ||||
|                 m_errorMessage = exception.getMessage(); | ||||
|             } | ||||
|         }; | ||||
| @@ -436,7 +441,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|     } | ||||
|     catch (PDFParserException parserException) | ||||
|     { | ||||
|         m_successfull = false; | ||||
|         m_result = Result::Failed; | ||||
|         m_errorMessage = parserException.getMessage(); | ||||
|     } | ||||
|  | ||||
| @@ -445,7 +450,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) | ||||
|  | ||||
| void PDFDocumentReader::reset() | ||||
| { | ||||
|     m_successfull = true; | ||||
|     m_result = Result::OK; | ||||
|     m_errorMessage = QString(); | ||||
|     m_version = PDFVersion(); | ||||
| } | ||||
|   | ||||
| @@ -43,6 +43,13 @@ public: | ||||
|     constexpr inline PDFDocumentReader& operator=(const PDFDocumentReader&) = delete; | ||||
|     constexpr inline PDFDocumentReader& operator=(PDFDocumentReader&&) = delete; | ||||
|  | ||||
|     enum class Result | ||||
|     { | ||||
|         OK,         ///< Document was successfully loaded | ||||
|         Failed,     ///< Error occured during document reading | ||||
|         Cancelled   ///< User cancelled document reading | ||||
|     }; | ||||
|  | ||||
|     /// Reads a PDF document from the specified file. If file doesn't exist, | ||||
|     /// cannot be opened or contain invalid pdf, empty PDF file is returned. | ||||
|     /// No exception is thrown. | ||||
| @@ -58,8 +65,8 @@ public: | ||||
|     /// PDF is read, then empty PDF document is returned. No exception is thrown. | ||||
|     PDFDocument readFromBuffer(const QByteArray& buffer); | ||||
|  | ||||
|     /// Returns true, if document was successfully read from device | ||||
|     bool isSuccessfull() const { return m_successfull; } | ||||
|     /// Returns result code for reading document from the device | ||||
|     Result getReadingResult() const { return m_result; } | ||||
|  | ||||
|     /// Returns error message, if document reading was unsuccessfull | ||||
|     const QString& getErrorMessage() const { return m_errorMessage; } | ||||
| @@ -83,8 +90,8 @@ private: | ||||
|     /// (providing thread safety) | ||||
|     QMutex m_mutex; | ||||
|  | ||||
|     /// This bool flag is set, if pdf document was successfully read from the device | ||||
|     std::atomic<bool> m_successfull; | ||||
|     /// Result of document reading from the device | ||||
|     std::atomic<Result> m_result; | ||||
|  | ||||
|     /// In case if error occurs, it is stored here | ||||
|     QString m_errorMessage; | ||||
|   | ||||
| @@ -1871,6 +1871,33 @@ QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding enc | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::Encoding encoding) | ||||
| { | ||||
|     QByteArray result; | ||||
|  | ||||
|     const encoding::EncodingTable* table = getTableForEncoding(encoding); | ||||
|     Q_ASSERT(table); | ||||
|  | ||||
|     result.reserve(string.size()); | ||||
|     for (QChar character : string) | ||||
|     { | ||||
|         ushort unicode = character.unicode(); | ||||
|         unsigned char converted = 0; | ||||
|  | ||||
|         for (int i = 0; i < table->size(); ++i) | ||||
|         { | ||||
|             if (unicode == (*table)[static_cast<unsigned char>(i)]) | ||||
|             { | ||||
|                 converted = i; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         result.push_back(converted); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| QString PDFEncoding::convertTextString(const QByteArray& stream) | ||||
| { | ||||
|     if (hasUnicodeLeadMarkings(stream)) | ||||
|   | ||||
| @@ -59,6 +59,14 @@ public: | ||||
|     /// \returns Converted unicode string | ||||
|     static QString convert(const QByteArray& stream, Encoding encoding); | ||||
|  | ||||
|     /// Converts unicode string to the byte array using the specified encoding. | ||||
|     /// It performs reverse functionality than function \p convert. If the character | ||||
|     /// in the encoding is not found, then it is converted to character code 0. | ||||
|     /// \param string String to be converted | ||||
|     /// \param encoding Encoding used in the conversion | ||||
|     /// \sa convert | ||||
|     static QByteArray convertToEncoding(const QString& string, Encoding encoding); | ||||
|  | ||||
|     /// Convert text string to the unicode string, using either PDFDocEncoding, | ||||
|     /// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If | ||||
|     /// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
|  | ||||
| #include "pdfsecurityhandler.h" | ||||
| #include "pdfexception.h" | ||||
| #include "pdfencoding.h" | ||||
|  | ||||
| #include <openssl/rc4.h> | ||||
| #include <openssl/md5.h> | ||||
| @@ -369,8 +370,8 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate | ||||
|                         AES_KEY key = { }; | ||||
|                         AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key); | ||||
|                         unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { }; | ||||
|                         m_authorizationData.fileEncryptionKey.resize(m_OE.size()); | ||||
|                         AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_OE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT); | ||||
|                         m_authorizationData.fileEncryptionKey.resize(m_UE.size()); | ||||
|                         AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_UE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_UE.size(), &key, aesInitializationVector, AES_DECRYPT); | ||||
|  | ||||
|                         // We have authorized owner access | ||||
|                         m_authorizationData.authorizationResult =  AuthorizationResult::UserAuthorized; | ||||
| @@ -380,6 +381,36 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate | ||||
|                 // Stop, if we authorized the document usage | ||||
|                 if (m_authorizationData.isAuthorized()) | ||||
|                 { | ||||
|                     // According the PDF specification, we must also check, if flags are not manipulated. | ||||
|                     Q_ASSERT(m_Perms.size() == AES_BLOCK_SIZE); | ||||
|                     AES_KEY key = { }; | ||||
|                     AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_authorizationData.fileEncryptionKey.size() * 8, &key); | ||||
|                     QByteArray decodedPerms(m_Perms.size(), char(0)); | ||||
|                     AES_ecb_encrypt(reinterpret_cast<const unsigned char*>(m_Perms.data()), reinterpret_cast<unsigned char*>(decodedPerms.data()), &key, AES_DECRYPT); | ||||
|  | ||||
|                     // 1) Checks, if bytes 9, 10, 11 are 'a', 'd', 'b' | ||||
|                     if (decodedPerms[9] != 'a' || decodedPerms[10] != 'd' || decodedPerms[11] != 'b') | ||||
|                     { | ||||
|                         throw PDFParserException(PDFTranslationContext::tr("Permissions entry in the Encryption dictionary is invalid.")); | ||||
|                     } | ||||
|  | ||||
|                     // 2) Verify, that bytes 0-3 are valid permissions entry | ||||
|                     const uint32_t permissions = qFromLittleEndian(*reinterpret_cast<const uint32_t*>(decodedPerms.data())); | ||||
|                     if (permissions != m_permissions) | ||||
|                     { | ||||
|                         throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); | ||||
|                     } | ||||
|  | ||||
|                     // 3) Verify, that byte 8 is 'T' or 'F' and is equal to EncryptMetadata entry | ||||
|                     if (decodedPerms[8] != 'T' && decodedPerms[8] != 'F') | ||||
|                     { | ||||
|                         throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); | ||||
|                     } | ||||
|                     if ((decodedPerms[8] == 'T') != m_encryptMetadata) | ||||
|                     { | ||||
|                         throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); | ||||
|                     } | ||||
|  | ||||
|                     return m_authorizationData.authorizationResult; | ||||
|                 } | ||||
|  | ||||
| @@ -390,8 +421,7 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate | ||||
|                 return AuthorizationResult::Failed; | ||||
|         } | ||||
|  | ||||
|         // TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding! | ||||
|         password = getPasswordCallback(&passwordObtained).toUtf8(); | ||||
|         password = adjustPassword(getPasswordCallback(&passwordObtained)); | ||||
|     } | ||||
|  | ||||
|     return AuthorizationResult::Cancelled; | ||||
| @@ -767,4 +797,114 @@ PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parsePa | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| QByteArray PDFStandardSecurityHandler::adjustPassword(const QString& password) | ||||
| { | ||||
|     QByteArray result; | ||||
|  | ||||
|     switch (m_R) | ||||
|     { | ||||
|         case 2: | ||||
|         case 3: | ||||
|         case 4: | ||||
|         { | ||||
|             // According to the PDF specification, convert string to PDFDocEncoding encoding | ||||
|             result = PDFEncoding::convertToEncoding(password, PDFEncoding::Encoding::PDFDoc); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case 6: | ||||
|         { | ||||
|             // According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites: | ||||
|             //      - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm) | ||||
|             //      - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings) | ||||
|             // | ||||
|             // Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC | ||||
|  | ||||
|             QString preparedPassword; | ||||
|             preparedPassword.reserve(password.size()); | ||||
|  | ||||
|             // RFC 4013 Section 2.1, use mapping | ||||
|  | ||||
|             for (const QChar character : password) | ||||
|             { | ||||
|                 if (isUnicodeMappedToNothing(character.unicode())) | ||||
|                 { | ||||
|                     // Mapped to nothing | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (isUnicodeNonAsciiSpaceCharacter(character.unicode())) | ||||
|                 { | ||||
|                     // Map to space character | ||||
|                     preparedPassword += QChar(QChar::Space); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     preparedPassword += character; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // RFC 4013, Section 2.2, normalization to KC | ||||
|             preparedPassword = preparedPassword.normalized(QString::NormalizationForm_KC); | ||||
|  | ||||
|             // We don't do other checks. We will transform password to the UTF-8 encoding | ||||
|             // and according the PDF specification, we take only first 127 characters. | ||||
|             result = preparedPassword.toUtf8().left(127); | ||||
|         } | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| bool PDFStandardSecurityHandler::isUnicodeNonAsciiSpaceCharacter(ushort unicode) | ||||
| { | ||||
|     switch (unicode) | ||||
|     { | ||||
|         case 0x00A0: | ||||
|         case 0x1680: | ||||
|         case 0x2000: | ||||
|         case 0x2001: | ||||
|         case 0x2002: | ||||
|         case 0x2003: | ||||
|         case 0x2004: | ||||
|         case 0x2005: | ||||
|         case 0x2006: | ||||
|         case 0x2007: | ||||
|         case 0x2008: | ||||
|         case 0x2009: | ||||
|         case 0x200A: | ||||
|         case 0x200B: | ||||
|         case 0x202F: | ||||
|         case 0x205F: | ||||
|         case 0x3000: | ||||
|             return true; | ||||
|  | ||||
|         default: | ||||
|             return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool PDFStandardSecurityHandler::isUnicodeMappedToNothing(ushort unicode) | ||||
| { | ||||
|     switch (unicode) | ||||
|     { | ||||
|         case 0x00AD: | ||||
|         case 0x034F: | ||||
|         case 0x1806: | ||||
|         case 0x180B: | ||||
|         case 0x180C: | ||||
|         case 0x180D: | ||||
|         case 0x200B: | ||||
|         case 0x200C: | ||||
|         case 0x200D: | ||||
|             return true; | ||||
|  | ||||
|         default: | ||||
|             return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| }   // namespace pdf | ||||
|   | ||||
| @@ -176,6 +176,19 @@ private: | ||||
|     /// Parses parts of the user/owner data (U/O values of the encryption dictionary) | ||||
|     UserOwnerData_r6 parseParts(const QByteArray& data) const; | ||||
|  | ||||
|     /// Adjusts the password according to the PDF specification | ||||
|     QByteArray adjustPassword(const QString& password); | ||||
|  | ||||
|     /// Returns true, if character with unicode code is non-ascii space character | ||||
|     /// according the RFC 3454, section C.1.2 | ||||
|     /// \param unicode Unicode code to be tested | ||||
|     static bool isUnicodeNonAsciiSpaceCharacter(ushort unicode); | ||||
|  | ||||
|     /// Returns true, if character with unicode code is mapped to nothing, | ||||
|     /// according the RFC 3454, section B.1 | ||||
|     /// \param unicode Unicode code to be tested | ||||
|     static bool isUnicodeMappedToNothing(ushort unicode); | ||||
|  | ||||
|     /// Revision number of standard security number | ||||
|     int m_R = 0; | ||||
|  | ||||
|   | ||||
| @@ -281,7 +281,9 @@ void PDFViewerMainWindow::openDocument(const QString& fileName) | ||||
|     pdf::PDFDocument document = reader.readFromFile(fileName); | ||||
|     QApplication::restoreOverrideCursor(); | ||||
|  | ||||
|     if (reader.isSuccessfull()) | ||||
|     switch (reader.getReadingResult()) | ||||
|     { | ||||
|         case pdf::PDFDocumentReader::Result::OK: | ||||
|         { | ||||
|             // Mark current directory as this | ||||
|             QFileInfo fileInfo(fileName); | ||||
| @@ -292,10 +294,17 @@ void PDFViewerMainWindow::openDocument(const QString& fileName) | ||||
|             setDocument(m_pdfDocument.data()); | ||||
|  | ||||
|             statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(fileName), 4000); | ||||
|             break; | ||||
|         } | ||||
|     else | ||||
|  | ||||
|         case pdf::PDFDocumentReader::Result::Failed: | ||||
|         { | ||||
|             QMessageBox::critical(this, tr("PDF Viewer"), tr("Document read error: %1").arg(reader.getErrorMessage())); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdf::PDFDocumentReader::Result::Cancelled: | ||||
|             break; // Do nothing, user cancelled the document reading | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user