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 
37 using namespace Kleo;
38 
39 static QMutex installPathMutex;
40 Q_GLOBAL_STATIC(QString, _installPath)
41 QString 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 }
54 void ChecksumDefinition::setInstallPath(const QString &ip)
55 {
56  const QMutexLocker locker(&installPathMutex);
57  *_installPath() = ip;
58 }
59 
60 // Checksum Definition #N groups
61 static const QLatin1StringView ID_ENTRY("id");
62 static const QLatin1StringView NAME_ENTRY("Name");
63 static const QLatin1StringView CREATE_COMMAND_ENTRY("create-command");
64 static const QLatin1StringView VERIFY_COMMAND_ENTRY("verify-command");
65 static const QLatin1StringView FILE_PATTERNS_ENTRY("file-patterns");
66 static const QLatin1StringView OUTPUT_FILE_ENTRY("output-file");
67 static const QLatin1StringView FILE_PLACEHOLDER("%f");
68 static const QLatin1StringView INSTALLPATH_PLACEHOLDER("%I");
69 static const QLatin1StringView NULL_SEPARATED_STDIN_INDICATOR("0|");
70 static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|');
71 
72 // ChecksumOperations group
73 static const QLatin1StringView CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id");
74 
75 namespace
76 {
77 
78 class ChecksumDefinitionError : public Kleo::Exception
79 {
80  const QString m_id;
81 
82 public:
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 
100 static 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 
119 static 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 
198 namespace
199 {
200 
201 class KConfigBasedChecksumDefinition : public ChecksumDefinition
202 {
203 public:
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 
242 private:
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 
260 private:
261  QString m_createCommand, m_verifyCommand;
262  QStringList m_createPrefixArguments, m_createPostfixArguments;
263  QStringList m_verifyPrefixArguments, m_verifyPostfixArguments;
264 };
265 
266 }
267 
268 ChecksumDefinition::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 
278 ChecksumDefinition::~ChecksumDefinition()
279 {
280 }
281 
282 QString ChecksumDefinition::createCommand() const
283 {
284  return doGetCreateCommand();
285 }
286 
287 QString ChecksumDefinition::verifyCommand() const
288 {
289  return doGetVerifyCommand();
290 }
291 
292 #if 0
293 QStringList ChecksumDefinition::createCommandArguments(const QStringList &files) const
294 {
295  return doGetCreateArguments(files);
296 }
297 
298 QStringList ChecksumDefinition::verifyCommandArguments(const QStringList &files) const
299 {
300  return doGetVerifyArguments(files);
301 }
302 #endif
303 
304 static 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 
318 static 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  }
351  p->closeWriteChannel();
352  return true;
353  }
354 
355  return false; // make compiler happy
356 }
357 
358 bool 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 
368 bool 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
379 std::vector<std::shared_ptr<ChecksumDefinition>> ChecksumDefinition::getChecksumDefinitions()
380 {
381  QStringList errors;
382  return getChecksumDefinitions(errors);
383 }
384 
385 // static
386 std::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
407 std::shared_ptr<ChecksumDefinition>
408 ChecksumDefinition::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
428 void 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 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QStringList & replaceInStrings(QStringView before, QStringView after, Qt::CaseSensitivity cs)
QString readEntry(const char *key, const char *aDefault=nullptr) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QByteArray encodeName(const QString &fileName)
QString applicationDirPath()
void push_back(const T &value)
QChar front() const const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
QString findExecutable(const QString &executableName, const QStringList &paths)
void reserve(int alloc)
int size() const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
QString i18n(const char *text, const TYPE &arg...)
QString fromLocal8Bit(const char *str, int size)
QList< T > mid(int pos, int length) const const
bool isEmpty() const const
bool waitForStarted(int msecs)
typedef Exception
QCoreApplication * instance()
QString join(const QString &separator) const const
int indexOf(QStringView str, int from) const const
QString & replace(int position, int n, QChar after)
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString name() const
int size() const const
bool sync() override
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
void closeWriteChannel()
qint64 write(const char *data, qint64 maxSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:13 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.