Do not upload modified PDF file while it is open in Kofax PowerPDF on Windows. Prevents signature verification failure.

Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
alex-z 2024-02-10 13:56:34 +01:00 committed by Matthieu Gallien
parent 39611b9728
commit b2aca219fc
7 changed files with 151 additions and 1 deletions

View File

@ -50,6 +50,18 @@ Q_DECLARE_LOGGING_CATEGORY(lcUtility)
* @{
*/
namespace Utility {
struct ProcessInfosForOpenFile {
ulong processId;
QString processName;
};
/**
* @brief Queries the OS for processes that are keeping the file open(using it)
*
* @param filePath absolute file path
* @return list of ProcessInfosForOpenFile
*/
OCSYNC_EXPORT QVector<ProcessInfosForOpenFile> queryProcessInfosKeepingFileOpen(const QString &filePath);
OCSYNC_EXPORT int rand();
OCSYNC_EXPORT void sleep(int sec);
OCSYNC_EXPORT void usleep(int usec);

View File

@ -31,6 +31,12 @@
namespace OCC {
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
Q_UNUSED(filePath)
return {};
}
void Utility::setupFavLink(const QString &folder)
{
// Finder: Place under "Places"/"Favorites" on the left sidebar

View File

@ -31,6 +31,12 @@
namespace OCC {
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
Q_UNUSED(filePath)
return {};
}
void Utility::setupFavLink(const QString &folder)
{
// Nautilus: add to ~/.gtk-bookmarks

View File

@ -22,6 +22,8 @@
#include <comdef.h>
#include <Lmcons.h>
#include <psapi.h>
#include <RestartManager.h>
#include <shlguid.h>
#include <shlobj.h>
#include <string>
@ -43,6 +45,72 @@ static const char runPathC[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\C
namespace OCC {
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
QVector<ProcessInfosForOpenFile> results;
DWORD restartManagerSession = 0;
WCHAR restartManagerSessionKey[CCH_RM_SESSION_KEY + 1] = {0};
auto errorStatus = RmStartSession(&restartManagerSession, 0, restartManagerSessionKey);
if (errorStatus != ERROR_SUCCESS) {
return results;
}
LPCWSTR files[] = {reinterpret_cast<LPCWSTR>(filePath.utf16())};
errorStatus = RmRegisterResources(restartManagerSession, 1, files, 0, NULL, 0, NULL);
if (errorStatus != ERROR_SUCCESS) {
RmEndSession(restartManagerSession);
return results;
}
DWORD rebootReasons = 0;
UINT rmProcessInfosNeededCount = 0;
std::vector<RM_PROCESS_INFO> rmProcessInfos;
auto rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);
if (errorStatus == ERROR_MORE_DATA) {
rmProcessInfos.resize(rmProcessInfosNeededCount, {});
rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);
}
if (errorStatus != ERROR_SUCCESS || rmProcessInfos.empty()) {
RmEndSession(restartManagerSession);
return results;
}
for (size_t i = 0; i < rmProcessInfos.size(); ++i) {
const auto processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rmProcessInfos[i].Process.dwProcessId);
if (!processHandle) {
continue;
}
FILETIME ftCreate, ftExit, ftKernel, ftUser;
if (!GetProcessTimes(processHandle, &ftCreate, &ftExit, &ftKernel, &ftUser)
|| CompareFileTime(&rmProcessInfos[i].Process.ProcessStartTime, &ftCreate) != 0) {
CloseHandle(processHandle);
continue;
}
WCHAR processFullPath[MAX_PATH];
DWORD processFullPathLength = MAX_PATH;
if (QueryFullProcessImageNameW(processHandle, 0, processFullPath, &processFullPathLength) && processFullPathLength <= MAX_PATH) {
const auto processFullPathString = QDir::fromNativeSeparators(QString::fromWCharArray(processFullPath));
const QFileInfo fileInfoForProcess(processFullPathString);
const auto processName = fileInfoForProcess.fileName();
if (!processName.isEmpty()) {
results.push_back(Utility::ProcessInfosForOpenFile{rmProcessInfos[i].Process.dwProcessId, processName});
}
}
CloseHandle(processHandle);
}
RmEndSession(restartManagerSession);
return results;
}
void Utility::setupFavLink(const QString &folder)
{
// First create a Desktop.ini so that the folder and favorite link show our application's icon.

View File

@ -31,7 +31,7 @@ if (NOT LINUX)
endif (NOT LINUX)
if(WIN32)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32 Rstrtmgr)
endif()
check_function_exists(utimes HAVE_UTIMES)

View File

@ -34,6 +34,12 @@
#include "csync_exclude.h"
#include "csync.h"
namespace
{
constexpr const char *editorNamesForDelayedUpload[] = {"PowerPDF"};
constexpr const char *fileExtensionsToCheckIfOpenForSigning[] = {".pdf"};
constexpr auto delayIntervalForSyncRetryForOpenedForSigningFilesSeconds = 60;
}
namespace OCC {
@ -1022,6 +1028,19 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
item->_status = SyncFileItem::Status::NormalError;
}
{
const auto foundEditorsKeepingFileBusy = queryEditorsKeepingFileBusy(item, path);
if (!foundEditorsKeepingFileBusy.isEmpty()) {
item->_instruction = CSYNC_INSTRUCTION_ERROR;
const auto editorsString = foundEditorsKeepingFileBusy.join(", ");
qCInfo(lcDisco) << "Failed, because it is open in the editor." << item->_file << "direction" << item->_direction << editorsString;
item->_errorString = tr("Could not upload file, because it is open in \"%1\".").arg(editorsString);
item->_status = SyncFileItem::Status::SoftError;
_discoveryData->_anotherSyncNeeded = true;
_discoveryData->_filesNeedingScheduledSync.insert(path._original, delayIntervalForSyncRetryForOpenedForSigningFilesSeconds);
}
}
if (dbEntry.isValid() && item->isDirectory()) {
item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(dbEntry._e2eEncryptionStatus);
if (item->isEncrypted()) {
@ -1839,6 +1858,43 @@ bool ProcessDirectoryJob::isRename(const QString &originalPath) const
*/
}
QStringList ProcessDirectoryJob::queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const
{
QStringList matchingEditorsKeepingFileBusy;
if (item->isDirectory() || item->_direction != SyncFileItem::Up) {
return matchingEditorsKeepingFileBusy;
}
const auto isMatchingFileExtension = std::find_if(std::cbegin(fileExtensionsToCheckIfOpenForSigning), std::cend(fileExtensionsToCheckIfOpenForSigning),
[path](const auto &matchingExtension) {
return path._local.endsWith(matchingExtension, Qt::CaseInsensitive);
}) != std::cend(fileExtensionsToCheckIfOpenForSigning);
if (!isMatchingFileExtension) {
return matchingEditorsKeepingFileBusy;
}
const QString fullLocalPath(_discoveryData->_localDir + path._local);
const auto editorsKeepingFileBusy = Utility::queryProcessInfosKeepingFileOpen(fullLocalPath);
for (const auto &detectedEditorName : editorsKeepingFileBusy) {
const auto isMatchingEditorFound = std::find_if(std::cbegin(editorNamesForDelayedUpload), std::cend(editorNamesForDelayedUpload),
[detectedEditorName](const auto &matchingEditorName) {
return detectedEditorName.processName.startsWith(matchingEditorName, Qt::CaseInsensitive);
}) != std::cend(editorNamesForDelayedUpload);
if (isMatchingEditorFound) {
matchingEditorsKeepingFileBusy.push_back(detectedEditorName.processName);
}
}
if (!matchingEditorsKeepingFileBusy.isEmpty()) {
matchingEditorsKeepingFileBusy.push_back("PowerPDF.exe");
}
return matchingEditorsKeepingFileBusy;
}
auto ProcessDirectoryJob::checkMovePermissions(RemotePermissions srcPerm, const QString &srcPath,
bool isDirectory)
-> MovePermissionResult

View File

@ -191,6 +191,8 @@ private:
[[nodiscard]] bool isRename(const QString &originalPath) const;
[[nodiscard]] QStringList queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const;
struct MovePermissionResult
{
// whether moving/renaming the source is ok