Libkleo

checksumdefinition.cpp
1/* -*- mode: c++; c-basic-offset:4 -*-
2 checksumdefinition.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include <config-libkleo.h>
11
12#include "checksumdefinition.h"
13
14#include "kleoexception.h"
15
16#include <libkleo_debug.h>
17
18#include <KConfig>
19#include <KConfigGroup>
20#include <KLocalizedString>
21#include <KSharedConfig>
22#include <KShell>
23
24#include <QByteArray>
25#include <QCoreApplication>
26#include <QDebug>
27#include <QFileInfo>
28#include <QMutex>
29#include <QProcess>
30#include <QRegularExpression>
31#include <QStandardPaths>
32
33#ifdef stdin
34#undef stdin // pah..
35#endif
36
37using namespace Kleo;
38
39static QMutex installPathMutex;
40Q_GLOBAL_STATIC(QString, _installPath)
41QString ChecksumDefinition::installPath()
42{
43 const QMutexLocker locker(&installPathMutex);
44 QString *const ip = _installPath();
45 if (ip->isEmpty()) {
48 } else {
49 qCWarning(LIBKLEO_LOG) << "checksumdefinition.cpp: installPath() called before QCoreApplication was constructed";
50 }
51 }
52 return *ip;
53}
54void ChecksumDefinition::setInstallPath(const QString &ip)
55{
56 const QMutexLocker locker(&installPathMutex);
57 *_installPath() = ip;
58}
59
60// Checksum Definition #N groups
61static const QLatin1StringView ID_ENTRY("id");
62static const QLatin1StringView NAME_ENTRY("Name");
63static const QLatin1StringView CREATE_COMMAND_ENTRY("create-command");
64static const QLatin1StringView VERIFY_COMMAND_ENTRY("verify-command");
65static const QLatin1StringView FILE_PATTERNS_ENTRY("file-patterns");
66static const QLatin1StringView OUTPUT_FILE_ENTRY("output-file");
67static const QLatin1StringView FILE_PLACEHOLDER("%f");
68static const QLatin1StringView INSTALLPATH_PLACEHOLDER("%I");
69static const QLatin1StringView NULL_SEPARATED_STDIN_INDICATOR("0|");
70static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|');
71
72// ChecksumOperations group
73static const QLatin1StringView CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id");
74
75namespace
76{
77
78class ChecksumDefinitionError : public Kleo::Exception
79{
80 const QString m_id;
81
82public:
83 ChecksumDefinitionError(const QString &id, const QString &message)
84 : Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in checksum definition %1: %2", id, message), MessageOnly)
85 , m_id(id)
86 {
87 }
88 ~ChecksumDefinitionError() throw() override
89 {
90 }
91
92 const QString &checksumDefinitionId() const
93 {
94 return m_id;
95 }
96};
97
98}
99
100static QString try_extensions(const QString &path)
101{
102 static const char exts[][4] = {
103 "",
104 "exe",
105 "bat",
106 "bin",
107 "cmd",
108 };
109 static const size_t numExts = sizeof exts / sizeof *exts;
110 for (unsigned int i = 0; i < numExts; ++i) {
111 const QFileInfo fi(path + QLatin1Char('.') + QLatin1StringView(exts[i]));
112 if (fi.exists()) {
113 return fi.filePath();
114 }
115 }
116 return QString();
117}
118
119static void parse_command(QString cmdline,
120 const QString &id,
121 const QString &whichCommand,
122 QString *command,
123 QStringList *prefix,
124 QStringList *suffix,
125 ChecksumDefinition::ArgumentPassingMethod *method)
126{
127 Q_ASSERT(prefix);
128 Q_ASSERT(suffix);
129 Q_ASSERT(method);
130
131 KShell::Errors errors;
132 QStringList l;
133
134 if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) {
135 *method = ChecksumDefinition::NullSeparatedInputFile;
136 cmdline.remove(0, 2);
137 } else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) {
138 *method = ChecksumDefinition::NewlineSeparatedInputFile;
139 cmdline.remove(0, 1);
140 } else {
141 *method = ChecksumDefinition::CommandLine;
142 }
143 if (*method != ChecksumDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) {
144 throw ChecksumDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand));
145 }
146 cmdline
147 .replace(FILE_PLACEHOLDER, QLatin1StringView("__files_go_here__")) //
148 .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__"));
150 l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER);
151 static const QRegularExpression regExpression(QLatin1StringView(".*__path_goes_here__.*"));
152 if (l.indexOf(regExpression) >= 0) {
153 l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ChecksumDefinition::installPath());
154 }
155 if (errors == KShell::BadQuoting) {
156 throw ChecksumDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand));
157 }
158 if (errors == KShell::FoundMeta) {
159 throw ChecksumDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand));
160 }
161 qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << l;
162 if (l.empty()) {
163 throw ChecksumDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand));
164 }
165 const QFileInfo fi1(l.front());
166 if (fi1.isAbsolute()) {
167 *command = try_extensions(l.front());
168 } else {
169 *command = QStandardPaths::findExecutable(fi1.fileName());
170 }
171 if (command->isEmpty()) {
172 throw ChecksumDefinitionError(id, i18n("'%1' empty or not found", whichCommand));
173 }
174 const int idx1 = l.indexOf(FILE_PLACEHOLDER);
175 if (idx1 < 0) {
176 // none -> append
177 *prefix = l.mid(1);
178 } else {
179 *prefix = l.mid(1, idx1 - 1);
180 *suffix = l.mid(idx1 + 1);
181 }
182 switch (*method) {
183 case ChecksumDefinition::CommandLine:
184 qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix;
185 break;
186 case ChecksumDefinition::NewlineSeparatedInputFile:
187 qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find | " << *command << *prefix;
188 break;
189 case ChecksumDefinition::NullSeparatedInputFile:
190 qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find -print0 | " << *command << *prefix;
191 break;
192 case ChecksumDefinition::NumArgumentPassingMethods:
193 Q_ASSERT(!"Should not happen");
194 break;
195 }
196}
197
198namespace
199{
200
201class KConfigBasedChecksumDefinition : public ChecksumDefinition
202{
203public:
204 explicit KConfigBasedChecksumDefinition(const KConfigGroup &group)
205 : ChecksumDefinition(group.readEntryUntranslated(ID_ENTRY),
206 group.readEntry(NAME_ENTRY),
207 group.readEntry(OUTPUT_FILE_ENTRY),
208 group.readEntry(FILE_PATTERNS_ENTRY, QStringList()))
209 {
210 if (id().isEmpty()) {
211 throw ChecksumDefinitionError(group.name(), i18n("'id' entry is empty/missing"));
212 }
213 if (outputFileName().isEmpty()) {
214 throw ChecksumDefinitionError(id(), i18n("'output-file' entry is empty/missing"));
215 }
216 if (patterns().empty()) {
217 throw ChecksumDefinitionError(id(), i18n("'file-patterns' entry is empty/missing"));
218 }
219
220 // create-command
221 ArgumentPassingMethod method;
222 parse_command(group.readEntry(CREATE_COMMAND_ENTRY),
223 id(),
224 CREATE_COMMAND_ENTRY,
225 &m_createCommand,
226 &m_createPrefixArguments,
227 &m_createPostfixArguments,
228 &method);
229 setCreateCommandArgumentPassingMethod(method);
230
231 // verify-command
232 parse_command(group.readEntry(VERIFY_COMMAND_ENTRY),
233 id(),
234 VERIFY_COMMAND_ENTRY,
235 &m_verifyCommand,
236 &m_verifyPrefixArguments,
237 &m_verifyPostfixArguments,
238 &method);
239 setVerifyCommandArgumentPassingMethod(method);
240 }
241
242private:
243 QString doGetCreateCommand() const override
244 {
245 return m_createCommand;
246 }
247 QStringList doGetCreateArguments(const QStringList &files) const override
248 {
249 return m_createPrefixArguments + files + m_createPostfixArguments;
250 }
251 QString doGetVerifyCommand() const override
252 {
253 return m_verifyCommand;
254 }
255 QStringList doGetVerifyArguments(const QStringList &files) const override
256 {
257 return m_verifyPrefixArguments + files + m_verifyPostfixArguments;
258 }
259
260private:
261 QString m_createCommand, m_verifyCommand;
262 QStringList m_createPrefixArguments, m_createPostfixArguments;
263 QStringList m_verifyPrefixArguments, m_verifyPostfixArguments;
264};
265
266}
267
268ChecksumDefinition::ChecksumDefinition(const QString &id, const QString &label, const QString &outputFileName, const QStringList &patterns)
269 : m_id(id)
270 , m_label(label.isEmpty() ? id : label)
271 , m_outputFileName(outputFileName)
272 , m_patterns(patterns)
273 , m_createMethod(CommandLine)
274 , m_verifyMethod(CommandLine)
275{
276}
277
278ChecksumDefinition::~ChecksumDefinition()
279{
280}
281
282QString ChecksumDefinition::createCommand() const
283{
284 return doGetCreateCommand();
285}
286
287QString ChecksumDefinition::verifyCommand() const
288{
289 return doGetVerifyCommand();
290}
291
292#if 0
293QStringList ChecksumDefinition::createCommandArguments(const QStringList &files) const
294{
295 return doGetCreateArguments(files);
296}
297
298QStringList ChecksumDefinition::verifyCommandArguments(const QStringList &files) const
299{
300 return doGetVerifyArguments(files);
301}
302#endif
303
304static QByteArray make_input(const QStringList &files, char sep)
305{
306 QByteArray result;
307 for (const QString &file : files) {
308#ifdef Q_OS_WIN
309 result += file.toUtf8();
310#else
311 result += QFile::encodeName(file);
312#endif
313 result += sep;
314 }
315 return result;
316}
317
318static bool start_command(QProcess *p,
319 const char *functionName,
320 const QString &cmd,
321 const QStringList &args,
322 const QStringList &files,
323 ChecksumDefinition::ArgumentPassingMethod method)
324{
325 if (!p) {
326 qCWarning(LIBKLEO_LOG) << functionName << ": process == NULL";
327 return false;
328 }
329
330 switch (method) {
331 case ChecksumDefinition::NumArgumentPassingMethods:
332 Q_ASSERT(!"Should not happen");
333
334 case ChecksumDefinition::CommandLine:
335 qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' '));
336 p->start(cmd, args, QIODevice::ReadOnly);
337 return true;
338
339 case ChecksumDefinition::NewlineSeparatedInputFile:
340 case ChecksumDefinition::NullSeparatedInputFile:
341 qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' '));
342 p->start(cmd, args, QIODevice::ReadWrite);
343 if (!p->waitForStarted()) {
344 return false;
345 }
346 const char sep = method == ChecksumDefinition::NewlineSeparatedInputFile ? '\n' : '\0';
347 const QByteArray stdin = make_input(files, sep);
348 if (p->write(stdin) != stdin.size()) {
349 return false;
350 }
352 return true;
353 }
354
355 return false; // make compiler happy
356}
357
358bool ChecksumDefinition::startCreateCommand(QProcess *p, const QStringList &files) const
359{
360 return start_command(p,
361 Q_FUNC_INFO,
362 doGetCreateCommand(),
363 m_createMethod == CommandLine ? doGetCreateArguments(files) : doGetCreateArguments(QStringList()),
364 files,
365 m_createMethod);
366}
367
368bool ChecksumDefinition::startVerifyCommand(QProcess *p, const QStringList &files) const
369{
370 return start_command(p,
371 Q_FUNC_INFO,
372 doGetVerifyCommand(),
373 m_verifyMethod == CommandLine ? doGetVerifyArguments(files) : doGetVerifyArguments(QStringList()),
374 files,
375 m_verifyMethod);
376}
377
378// static
379std::vector<std::shared_ptr<ChecksumDefinition>> ChecksumDefinition::getChecksumDefinitions()
380{
381 QStringList errors;
382 return getChecksumDefinitions(errors);
383}
384
385// static
386std::vector<std::shared_ptr<ChecksumDefinition>> ChecksumDefinition::getChecksumDefinitions(QStringList &errors)
387{
388 std::vector<std::shared_ptr<ChecksumDefinition>> result;
389 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
390 const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Checksum Definition #")));
391 result.reserve(groups.size());
392 for (const QString &group : groups) {
393 try {
394 const std::shared_ptr<ChecksumDefinition> ad(new KConfigBasedChecksumDefinition(KConfigGroup(config, group)));
395 result.push_back(ad);
396 } catch (const std::exception &e) {
397 qDebug() << e.what();
398 errors.push_back(QString::fromLocal8Bit(e.what()));
399 } catch (...) {
400 errors.push_back(i18n("Caught unknown exception in group %1", group));
401 }
402 }
403 return result;
404}
405
406// static
407std::shared_ptr<ChecksumDefinition>
408ChecksumDefinition::getDefaultChecksumDefinition(const std::vector<std::shared_ptr<ChecksumDefinition>> &checksumDefinitions)
409{
410 const KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("ChecksumOperations"));
411 const QString checksumDefinitionId = group.readEntry(CHECKSUM_DEFINITION_ID_ENTRY, QStringLiteral("sha256sum"));
412
413 if (!checksumDefinitionId.isEmpty()) {
414 for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) {
415 if (cd && cd->id() == checksumDefinitionId) {
416 return cd;
417 }
418 }
419 }
420 if (!checksumDefinitions.empty()) {
421 return checksumDefinitions.front();
422 } else {
423 return std::shared_ptr<ChecksumDefinition>();
424 }
425}
426
427// static
428void ChecksumDefinition::setDefaultChecksumDefinition(const std::shared_ptr<ChecksumDefinition> &checksumDefinition)
429{
430 if (!checksumDefinition) {
431 return;
432 }
433 KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("ChecksumOperations"));
434 group.writeEntry(CHECKSUM_DEFINITION_ID_ENTRY, checksumDefinition->id());
435 group.sync();
436}
QString name() const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool sync() override
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
qsizetype size() const const
QString applicationDirPath()
QCoreApplication * instance()
QByteArray encodeName(const QString &fileName)
qint64 write(const QByteArray &data)
QList< T > mid(qsizetype pos, qsizetype length) const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
void closeWriteChannel()
void start(OpenMode mode)
bool waitForStarted(int msecs)
QString findExecutable(const QString &executableName, const QStringList &paths)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
QChar & front()
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QString join(QChar separator) const const
QStringList & replaceInStrings(QStringView before, QStringView after, Qt::CaseSensitivity cs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:11 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.