mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 02:42:52 +00:00
371 lines
8.9 KiB
C++
371 lines
8.9 KiB
C++
/*
|
|
* Copyright (C) 2007,2008,2009,2010,2011,2012,2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
|
|
*/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "gpgproc.h"
|
|
|
|
#include "kgpgsettings.h"
|
|
|
|
#include <KDebug>
|
|
#include <KProcess>
|
|
#include <KStandardDirs>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QTextCodec>
|
|
|
|
class GnupgBinary {
|
|
public:
|
|
GnupgBinary();
|
|
|
|
const QString &binary() const;
|
|
void setBinary(const QString &executable);
|
|
const QStringList &standardArguments() const;
|
|
unsigned int version() const;
|
|
bool supportsDebugLevel() const;
|
|
|
|
private:
|
|
QString m_binary;
|
|
QStringList m_standardArguments;
|
|
unsigned int m_version;
|
|
bool m_useDebugLevel;
|
|
};
|
|
|
|
GnupgBinary::GnupgBinary()
|
|
: m_version(0),
|
|
m_useDebugLevel(false)
|
|
{
|
|
}
|
|
|
|
const QString &GnupgBinary::binary() const
|
|
{
|
|
return m_binary;
|
|
}
|
|
|
|
/**
|
|
* @brief check if GnuPG returns an error for this arguments
|
|
* @param executable the GnuPG executable to call
|
|
* @param arguments the arguments to pass to executable
|
|
*
|
|
* The arguments will be used together with "--version", so they should not
|
|
* be any commands.
|
|
*/
|
|
static bool checkGnupgArguments(const QString &executable, const QStringList &arguments)
|
|
{
|
|
KProcess gpg;
|
|
|
|
// We ignore the output anyway, just make sure it doesn't clutter the output of
|
|
// the parent process. Simplify the handling by putting all trash in one can.
|
|
gpg.setOutputChannelMode(KProcess::MergedChannels);
|
|
|
|
QStringList allArguments = arguments;
|
|
allArguments << QLatin1String("--version");
|
|
gpg.setProgram(executable, allArguments);
|
|
|
|
return (gpg.execute() == 0);
|
|
}
|
|
|
|
static QString getGpgProcessHome(const QString &binary)
|
|
{
|
|
GPGProc process(0, binary);
|
|
process << QLatin1String( "--version" );
|
|
process.start();
|
|
process.waitForFinished(-1);
|
|
|
|
if (process.exitCode() == 255) {
|
|
return QString();
|
|
}
|
|
|
|
QString line;
|
|
while (process.readln(line) != -1) {
|
|
if (line.startsWith(QLatin1String("Home: "))) {
|
|
line.remove(0, 6);
|
|
return line.trimmed();
|
|
}
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
|
|
void GnupgBinary::setBinary(const QString &executable)
|
|
{
|
|
kDebug(2100) << "checking version of GnuPG executable" << executable;
|
|
// must be set first as gpgVersionString() uses GPGProc to parse the output
|
|
m_binary = executable;
|
|
const QString verstr = GPGProc::gpgVersionString(executable);
|
|
m_version = GPGProc::gpgVersion(verstr);
|
|
kDebug(2100) << "version is" << verstr << m_version;
|
|
|
|
m_useDebugLevel = (m_version > 0x20000);
|
|
|
|
const QString gpgConfigFile = KGpgSettings::gpgConfigPath();
|
|
|
|
m_standardArguments.clear();
|
|
m_standardArguments << QLatin1String( "--no-secmem-warning" )
|
|
<< QLatin1String( "--no-tty" )
|
|
<< QLatin1String("--no-greeting");
|
|
|
|
if (!gpgConfigFile.isEmpty()) {
|
|
m_standardArguments << QLatin1String("--options")
|
|
<< gpgConfigFile;
|
|
|
|
// Check if the config file is in the default home directory
|
|
// of the binary. If it isn't add --homedir to command line also.
|
|
QString gpgdir = GPGProc::getGpgHome(executable);
|
|
gpgdir.chop(1); // remove trailing '/' as QFileInfo returns string without it
|
|
QFileInfo confFile(gpgConfigFile);
|
|
if (confFile.absolutePath() != gpgdir)
|
|
m_standardArguments << QLatin1String("--homedir")
|
|
<< confFile.absolutePath();
|
|
}
|
|
|
|
QStringList debugLevelArguments(QLatin1String("--debug-level"));
|
|
debugLevelArguments << QLatin1String("none");
|
|
if (checkGnupgArguments(executable, debugLevelArguments))
|
|
m_standardArguments << debugLevelArguments;
|
|
}
|
|
|
|
const QStringList& GnupgBinary::standardArguments() const
|
|
{
|
|
return m_standardArguments;
|
|
}
|
|
|
|
unsigned int GnupgBinary::version() const
|
|
{
|
|
return m_version;
|
|
}
|
|
|
|
bool GnupgBinary::supportsDebugLevel() const
|
|
{
|
|
return m_useDebugLevel;
|
|
}
|
|
|
|
K_GLOBAL_STATIC(GnupgBinary, lastBinary)
|
|
|
|
GPGProc::GPGProc(QObject *parent, const QString &binary)
|
|
: KLineBufferedProcess(parent)
|
|
{
|
|
resetProcess(binary);
|
|
}
|
|
|
|
GPGProc::~GPGProc()
|
|
{
|
|
}
|
|
|
|
void
|
|
GPGProc::resetProcess(const QString &binary)
|
|
{
|
|
GnupgBinary *bin = lastBinary;
|
|
QString executable;
|
|
if (binary.isEmpty())
|
|
executable = KGpgSettings::gpgBinaryPath();
|
|
else
|
|
executable = binary;
|
|
|
|
if (bin->binary() != executable)
|
|
bin->setBinary(executable);
|
|
|
|
setProgram(executable, bin->standardArguments());
|
|
|
|
setOutputChannelMode(OnlyStdoutChannel);
|
|
|
|
disconnect(SIGNAL(finished(int,QProcess::ExitStatus)));
|
|
disconnect(SIGNAL(lineReadyStandardOutput()));
|
|
}
|
|
|
|
void GPGProc::start()
|
|
{
|
|
// make sure there is exactly one connection from us to that signal
|
|
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished()), Qt::UniqueConnection);
|
|
connect(this, SIGNAL(lineReadyStandardOutput()), this, SLOT(received()), Qt::UniqueConnection);
|
|
KProcess::start();
|
|
}
|
|
|
|
void GPGProc::received()
|
|
{
|
|
emit readReady();
|
|
}
|
|
|
|
void GPGProc::finished()
|
|
{
|
|
emit processExited();
|
|
}
|
|
|
|
int GPGProc::readln(QString &line, const bool colons)
|
|
{
|
|
QByteArray a;
|
|
if (!readLineStandardOutput(&a))
|
|
return -1;
|
|
|
|
line = recode(a, colons, m_codec);
|
|
|
|
return line.length();
|
|
}
|
|
|
|
int GPGProc::readln(QStringList &l)
|
|
{
|
|
QString s;
|
|
|
|
int len = readln(s);
|
|
if (len < 0)
|
|
return len;
|
|
|
|
l = s.split(QLatin1Char( ':' ));
|
|
|
|
for (int i = 0; i < l.count(); ++i)
|
|
{
|
|
int j = 0;
|
|
while ((j = l[i].indexOf(QLatin1String( "\\x3a" ), j, Qt::CaseInsensitive)) >= 0)
|
|
{
|
|
l[i].replace(j, 4, QLatin1Char( ':' ));
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return l.count();
|
|
}
|
|
|
|
QString
|
|
GPGProc::recode(QByteArray a, const bool colons, const QByteArray &codec)
|
|
{
|
|
const char *textcodec = codec.isEmpty() ? "utf8" : codec.constData();
|
|
int pos = 0;
|
|
|
|
while ((pos = a.indexOf("\\x", pos)) >= 0) {
|
|
if (pos > a.length() - 4)
|
|
break;
|
|
|
|
const QByteArray pattern(a.mid(pos, 4));
|
|
const QByteArray hexnum(pattern.right(2));
|
|
bool ok;
|
|
char n[2];
|
|
n[0] = hexnum.toUShort(&ok, 16);
|
|
n[1] = '\0'; // to use n as a 0-terminated string
|
|
if (!ok) {
|
|
// skip this occurrence
|
|
pos += 2;
|
|
continue;
|
|
}
|
|
|
|
// QLatin1Char( ':' ) must be skipped, it is used as column delimiter
|
|
// since it is pure ascii it can be replaced in QString.
|
|
if (!colons && (n[0] == ':' )) {
|
|
pos += 3;
|
|
continue;
|
|
}
|
|
|
|
// it is likely to find the same byte sequence more than once
|
|
int npos = pos;
|
|
do {
|
|
a.replace(npos, 4, n);
|
|
} while ((npos = a.indexOf(pattern, npos)) >= 0);
|
|
}
|
|
|
|
return QTextCodec::codecForName(textcodec)->toUnicode(a);
|
|
}
|
|
|
|
bool
|
|
GPGProc::setCodec(const QByteArray &codec)
|
|
{
|
|
const QList<QByteArray> codecs = QTextCodec::availableCodecs();
|
|
if (!codecs.contains(codec))
|
|
return false;
|
|
|
|
m_codec = codec;
|
|
|
|
return true;
|
|
}
|
|
|
|
int GPGProc::gpgVersion(const QString &vstr)
|
|
{
|
|
if (vstr.isEmpty())
|
|
return -1;
|
|
|
|
QStringList values(vstr.split(QLatin1Char( '.' )));
|
|
if (values.count() < 3)
|
|
return -2;
|
|
|
|
return (0x10000 * values[0].toInt() + 0x100 * values[1].toInt() + values[2].toInt());
|
|
}
|
|
|
|
QString GPGProc::gpgVersionString(const QString &binary)
|
|
{
|
|
GPGProc process(0, binary);
|
|
process << QLatin1String( "--version" );
|
|
process.start();
|
|
process.waitForFinished(-1);
|
|
|
|
if (process.exitCode() == 255)
|
|
return QString();
|
|
|
|
QString line;
|
|
if (process.readln(line) != -1)
|
|
return line.simplified().section(QLatin1Char( ' ' ), -1);
|
|
else
|
|
return QString();
|
|
}
|
|
|
|
QString GPGProc::getGpgStartupError(const QString &binary)
|
|
{
|
|
GPGProc process(0, binary);
|
|
process << QLatin1String( "--version" );
|
|
process.start();
|
|
process.waitForFinished(-1);
|
|
|
|
QString result;
|
|
|
|
while (process.hasLineStandardError()) {
|
|
QByteArray tmp;
|
|
process.readLineStandardError(&tmp);
|
|
tmp += '\n';
|
|
result += QString::fromUtf8(tmp);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QString GPGProc::getGpgHome(const QString &binary)
|
|
{
|
|
// First try: if environment is set GnuPG will use that directory
|
|
// We can use this directly without starting a new process
|
|
QByteArray env(qgetenv("GNUPGHOME"));
|
|
QString gpgHome;
|
|
if (!env.isEmpty()) {
|
|
gpgHome = QLatin1String( env );
|
|
} else if (!binary.isEmpty()) {
|
|
// Second try: start GnuPG and ask what it is
|
|
gpgHome = getGpgProcessHome(binary);
|
|
}
|
|
|
|
// Third try: guess what it is.
|
|
if (gpgHome.isEmpty()) {
|
|
#ifdef Q_OS_WIN32 //krazy:exclude=cpp
|
|
gpgHome = qgetenv("APPDATA") + QLatin1String( "/gnupg/" );
|
|
gpgHome.replace(QLatin1Char( '\\' ), QLatin1Char( '/' ));
|
|
#else
|
|
gpgHome = QDir::homePath() + QLatin1String( "/.gnupg/" );
|
|
#endif
|
|
}
|
|
|
|
gpgHome.replace(QLatin1String( "//" ), QLatin1String( "/" ));
|
|
|
|
if (!gpgHome.endsWith(QLatin1Char( '/' )))
|
|
gpgHome.append(QLatin1Char( '/' ));
|
|
|
|
if (gpgHome.startsWith(QLatin1String("~/")))
|
|
gpgHome.replace(0, 1, QDir::homePath());
|
|
|
|
KStandardDirs::makeDir(gpgHome, 0700);
|
|
return gpgHome;
|
|
}
|
|
|
|
#include "gpgproc.moc"
|