Skip to content

Commit 8abe081

Browse files
allexzanderUwe Runtemund
authored andcommitted
Do not upload modified PDF file while it is open in Kofax PowerPDF on Windows. Prevents signature verification failure.
Signed-off-by: alex-z <[email protected]> Signed-off-by: Uwe Runtemund <[email protected]>
1 parent 666a751 commit 8abe081

File tree

7 files changed

+151
-1
lines changed

7 files changed

+151
-1
lines changed

src/common/utility.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ Q_DECLARE_LOGGING_CATEGORY(lcUtility)
5050
* @{
5151
*/
5252
namespace Utility {
53+
struct ProcessInfosForOpenFile {
54+
ulong processId;
55+
QString processName;
56+
};
57+
/**
58+
* @brief Queries the OS for processes that are keeping the file open(using it)
59+
*
60+
* @param filePath absolute file path
61+
* @return list of ProcessInfosForOpenFile
62+
*/
63+
OCSYNC_EXPORT QVector<ProcessInfosForOpenFile> queryProcessInfosKeepingFileOpen(const QString &filePath);
64+
5365
OCSYNC_EXPORT int rand();
5466
OCSYNC_EXPORT void sleep(int sec);
5567
OCSYNC_EXPORT void usleep(int usec);

src/common/utility_mac.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131

3232
namespace OCC {
3333

34+
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
35+
{
36+
Q_UNUSED(filePath)
37+
return {};
38+
}
39+
3440
void Utility::setupFavLink(const QString &folder)
3541
{
3642
// Finder: Place under "Places"/"Favorites" on the left sidebar

src/common/utility_unix.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131

3232
namespace OCC {
3333

34+
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
35+
{
36+
Q_UNUSED(filePath)
37+
return {};
38+
}
39+
3440
void Utility::setupFavLink(const QString &folder)
3541
{
3642
// Nautilus: add to ~/.gtk-bookmarks

src/common/utility_win.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
#include <comdef.h>
2424
#include <Lmcons.h>
25+
#include <psapi.h>
26+
#include <RestartManager.h>
2527
#include <shlguid.h>
2628
#include <shlobj.h>
2729
#include <string>
@@ -43,6 +45,72 @@ static const char runPathC[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\C
4345

4446
namespace OCC {
4547

48+
QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
49+
{
50+
QVector<ProcessInfosForOpenFile> results;
51+
52+
DWORD restartManagerSession = 0;
53+
WCHAR restartManagerSessionKey[CCH_RM_SESSION_KEY + 1] = {0};
54+
auto errorStatus = RmStartSession(&restartManagerSession, 0, restartManagerSessionKey);
55+
if (errorStatus != ERROR_SUCCESS) {
56+
return results;
57+
}
58+
59+
LPCWSTR files[] = {reinterpret_cast<LPCWSTR>(filePath.utf16())};
60+
errorStatus = RmRegisterResources(restartManagerSession, 1, files, 0, NULL, 0, NULL);
61+
if (errorStatus != ERROR_SUCCESS) {
62+
RmEndSession(restartManagerSession);
63+
return results;
64+
}
65+
66+
DWORD rebootReasons = 0;
67+
UINT rmProcessInfosNeededCount = 0;
68+
std::vector<RM_PROCESS_INFO> rmProcessInfos;
69+
auto rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
70+
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);
71+
72+
if (errorStatus == ERROR_MORE_DATA) {
73+
rmProcessInfos.resize(rmProcessInfosNeededCount, {});
74+
rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
75+
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);
76+
}
77+
78+
if (errorStatus != ERROR_SUCCESS || rmProcessInfos.empty()) {
79+
RmEndSession(restartManagerSession);
80+
return results;
81+
}
82+
83+
for (size_t i = 0; i < rmProcessInfos.size(); ++i) {
84+
const auto processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rmProcessInfos[i].Process.dwProcessId);
85+
if (!processHandle) {
86+
continue;
87+
}
88+
89+
FILETIME ftCreate, ftExit, ftKernel, ftUser;
90+
91+
if (!GetProcessTimes(processHandle, &ftCreate, &ftExit, &ftKernel, &ftUser)
92+
|| CompareFileTime(&rmProcessInfos[i].Process.ProcessStartTime, &ftCreate) != 0) {
93+
CloseHandle(processHandle);
94+
continue;
95+
}
96+
97+
WCHAR processFullPath[MAX_PATH];
98+
DWORD processFullPathLength = MAX_PATH;
99+
if (QueryFullProcessImageNameW(processHandle, 0, processFullPath, &processFullPathLength) && processFullPathLength <= MAX_PATH) {
100+
const auto processFullPathString = QDir::fromNativeSeparators(QString::fromWCharArray(processFullPath));
101+
const QFileInfo fileInfoForProcess(processFullPathString);
102+
const auto processName = fileInfoForProcess.fileName();
103+
if (!processName.isEmpty()) {
104+
results.push_back(Utility::ProcessInfosForOpenFile{rmProcessInfos[i].Process.dwProcessId, processName});
105+
}
106+
}
107+
CloseHandle(processHandle);
108+
}
109+
RmEndSession(restartManagerSession);
110+
111+
return results;
112+
}
113+
46114
void Utility::setupFavLink(const QString &folder)
47115
{
48116
// First create a Desktop.ini so that the folder and favorite link show our application's icon.

src/csync/ConfigureChecks.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if (NOT LINUX)
3131
endif (NOT LINUX)
3232

3333
if(WIN32)
34-
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32)
34+
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32 Rstrtmgr)
3535
endif()
3636

3737
check_function_exists(utimes HAVE_UTIMES)

src/libsync/discovery.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
#include "csync_exclude.h"
3636
#include "csync.h"
3737

38+
namespace
39+
{
40+
constexpr const char *editorNamesForDelayedUpload[] = {"PowerPDF"};
41+
constexpr const char *fileExtensionsToCheckIfOpenForSigning[] = {".pdf"};
42+
constexpr auto delayIntervalForSyncRetryForOpenedForSigningFilesSeconds = 60;
43+
}
3844

3945
namespace OCC {
4046

@@ -1043,6 +1049,19 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
10431049
item->_status = SyncFileItem::Status::NormalError;
10441050
}
10451051

1052+
{
1053+
const auto foundEditorsKeepingFileBusy = queryEditorsKeepingFileBusy(item, path);
1054+
if (!foundEditorsKeepingFileBusy.isEmpty()) {
1055+
item->_instruction = CSYNC_INSTRUCTION_ERROR;
1056+
const auto editorsString = foundEditorsKeepingFileBusy.join(", ");
1057+
qCInfo(lcDisco) << "Failed, because it is open in the editor." << item->_file << "direction" << item->_direction << editorsString;
1058+
item->_errorString = tr("Could not upload file, because it is open in \"%1\".").arg(editorsString);
1059+
item->_status = SyncFileItem::Status::SoftError;
1060+
_discoveryData->_anotherSyncNeeded = true;
1061+
_discoveryData->_filesNeedingScheduledSync.insert(path._original, delayIntervalForSyncRetryForOpenedForSigningFilesSeconds);
1062+
}
1063+
}
1064+
10461065
if (dbEntry.isValid() && item->isDirectory()) {
10471066
item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(dbEntry._e2eEncryptionStatus);
10481067
if (item->isEncrypted()) {
@@ -1860,6 +1879,43 @@ bool ProcessDirectoryJob::isRename(const QString &originalPath) const
18601879
*/
18611880
}
18621881

1882+
QStringList ProcessDirectoryJob::queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const
1883+
{
1884+
QStringList matchingEditorsKeepingFileBusy;
1885+
1886+
if (item->isDirectory() || item->_direction != SyncFileItem::Up) {
1887+
return matchingEditorsKeepingFileBusy;
1888+
}
1889+
1890+
const auto isMatchingFileExtension = std::find_if(std::cbegin(fileExtensionsToCheckIfOpenForSigning), std::cend(fileExtensionsToCheckIfOpenForSigning),
1891+
[path](const auto &matchingExtension) {
1892+
return path._local.endsWith(matchingExtension, Qt::CaseInsensitive);
1893+
}) != std::cend(fileExtensionsToCheckIfOpenForSigning);
1894+
1895+
if (!isMatchingFileExtension) {
1896+
return matchingEditorsKeepingFileBusy;
1897+
}
1898+
1899+
const QString fullLocalPath(_discoveryData->_localDir + path._local);
1900+
const auto editorsKeepingFileBusy = Utility::queryProcessInfosKeepingFileOpen(fullLocalPath);
1901+
1902+
for (const auto &detectedEditorName : editorsKeepingFileBusy) {
1903+
const auto isMatchingEditorFound = std::find_if(std::cbegin(editorNamesForDelayedUpload), std::cend(editorNamesForDelayedUpload),
1904+
[detectedEditorName](const auto &matchingEditorName) {
1905+
return detectedEditorName.processName.startsWith(matchingEditorName, Qt::CaseInsensitive);
1906+
}) != std::cend(editorNamesForDelayedUpload);
1907+
if (isMatchingEditorFound) {
1908+
matchingEditorsKeepingFileBusy.push_back(detectedEditorName.processName);
1909+
}
1910+
}
1911+
1912+
if (!matchingEditorsKeepingFileBusy.isEmpty()) {
1913+
matchingEditorsKeepingFileBusy.push_back("PowerPDF.exe");
1914+
}
1915+
1916+
return matchingEditorsKeepingFileBusy;
1917+
}
1918+
18631919
auto ProcessDirectoryJob::checkMovePermissions(RemotePermissions srcPerm, const QString &srcPath,
18641920
bool isDirectory)
18651921
-> MovePermissionResult

src/libsync/discovery.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ class ProcessDirectoryJob : public QObject
191191

192192
[[nodiscard]] bool isRename(const QString &originalPath) const;
193193

194+
[[nodiscard]] QStringList queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const;
195+
194196
struct MovePermissionResult
195197
{
196198
// whether moving/renaming the source is ok

0 commit comments

Comments
 (0)