Add CLI tests and improve coding style and i18n
The CLI module was lacking unit test coverage and showed some severe coding style violations, which this patch addresses. In addition, all uses of qCritical() with untranslatble raw char* sequences were removed in favor of proper locale strings. These are written to STDERR through QTextStreams and support output redirection for testing purposes. With this change, error messages don't depend on the global Qt logging settings and targets anymore and go directly to the terminal or into a file if needed. This patch also fixes a bug discovered during unit test development, where the extract command would just dump the raw XML contents without decrypting embedded Salsa20-protected values first, making the XML export mostly useless, since passwords are scrambled. Lastly, all CLI commands received a dedicated -h/--help option.
This commit is contained in:
236
src/core/Bootstrap.cpp
Normal file
236
src/core/Bootstrap.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Bootstrap.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Translator.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <aclapi.h> // for createWindowsDACL()
|
||||
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
|
||||
#endif
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
/**
|
||||
* When QNetworkAccessManager is instantiated it regularly starts polling
|
||||
* all network interfaces to see if anything changes and if so, what. This
|
||||
* creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >=
|
||||
* when on a wifi connection.
|
||||
* So here we disable it for lack of better measure.
|
||||
* This will also cause this message: QObject::startTimer: Timers cannot
|
||||
* have negative intervals
|
||||
* For more info see:
|
||||
* - https://bugreports.qt.io/browse/QTBUG-40332
|
||||
* - https://bugreports.qt.io/browse/QTBUG-46015
|
||||
*/
|
||||
static inline void applyEarlyQNetworkAccessManagerWorkaround()
|
||||
{
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform early application bootstrapping such as setting up search paths,
|
||||
* configuration OS security properties, and loading translators.
|
||||
* A QApplication object has to be instantiated before calling this function.
|
||||
*/
|
||||
void bootstrapApplication()
|
||||
{
|
||||
#ifdef QT_NO_DEBUG
|
||||
disableCoreDumps();
|
||||
#endif
|
||||
setupSearchPaths();
|
||||
applyEarlyQNetworkAccessManagerWorkaround();
|
||||
Translator::installTranslators();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Don't show menu icons on OSX
|
||||
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the main window's state after launch
|
||||
*
|
||||
* @param mainWindow the main window whose state to restore
|
||||
*/
|
||||
void restoreMainWindowState(MainWindow& mainWindow)
|
||||
{
|
||||
// start minimized if configured
|
||||
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
|
||||
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
|
||||
#ifndef Q_OS_LINUX
|
||||
if (minimizeOnStartup) {
|
||||
#else
|
||||
// On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at
|
||||
// the same time (which would happen if both minimize on startup and minimize to tray are set)
|
||||
// since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough.
|
||||
if (minimizeOnStartup && !minimizeToTray) {
|
||||
#endif
|
||||
mainWindow.setWindowState(Qt::WindowMinimized);
|
||||
}
|
||||
if (!(minimizeOnStartup && minimizeToTray)) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
|
||||
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
|
||||
for (const QString& filename : fileNames) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename)) {
|
||||
mainWindow.openDatabase(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
void disableCoreDumps()
|
||||
{
|
||||
// default to true
|
||||
// there is no point in printing a warning if this is not implemented on the platform
|
||||
bool success = true;
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#endif
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
success = success && createWindowsDACL();
|
||||
#endif
|
||||
|
||||
if (!success) {
|
||||
qWarning("Unable to disable core dumps.");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// This function grants the user associated with the process token minimal access rights and
|
||||
// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and
|
||||
// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory().
|
||||
// We do this using a discretionary access control list (DACL). Effectively this prevents
|
||||
// crash dumps and disallows other processes from accessing our memory. This works as long
|
||||
// as you do not have admin privileges, since then you are able to grant yourself the
|
||||
// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL.
|
||||
//
|
||||
bool createWindowsDACL()
|
||||
{
|
||||
bool bSuccess = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Process token and user
|
||||
HANDLE hToken = nullptr;
|
||||
PTOKEN_USER pTokenUser = nullptr;
|
||||
DWORD cbBufferSize = 0;
|
||||
|
||||
// Access control list
|
||||
PACL pACL = nullptr;
|
||||
DWORD cbACL = 0;
|
||||
|
||||
// Open the access token associated with the calling process
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Retrieve the token information in a TOKEN_USER structure
|
||||
GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize);
|
||||
|
||||
pTokenUser = static_cast<PTOKEN_USER>(HeapAlloc(GetProcessHeap(), 0, cbBufferSize));
|
||||
if (pTokenUser == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!IsValidSid(pTokenUser->User.Sid)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Calculate the amount of memory that must be allocated for the DACL
|
||||
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
|
||||
|
||||
// Create and initialize an ACL
|
||||
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
|
||||
if (pACL == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Add allowed access control entries, everything else is denied
|
||||
if (!AddAccessAllowedAce(
|
||||
pACL,
|
||||
ACL_REVISION,
|
||||
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process
|
||||
pTokenUser->User.Sid // pointer to the trustee's SID
|
||||
)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Set discretionary access control list
|
||||
bSuccess = ERROR_SUCCESS
|
||||
== SetSecurityInfo(GetCurrentProcess(), // object handle
|
||||
SE_KERNEL_OBJECT, // type of object
|
||||
DACL_SECURITY_INFORMATION, // change only the objects DACL
|
||||
nullptr,
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
if (pACL != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pACL);
|
||||
}
|
||||
if (pTokenUser != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pTokenUser);
|
||||
}
|
||||
if (hToken != nullptr) {
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
#endif
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
void setupSearchPaths()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Make sure Windows doesn't load DLLs from the current working directory
|
||||
SetDllDirectoryA("");
|
||||
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Bootstrap
|
||||
34
src/core/Bootstrap.h
Normal file
34
src/core/Bootstrap.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef KEEPASSXC_BOOTSTRAP_H
|
||||
#define KEEPASSXC_BOOTSTRAP_H
|
||||
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
void bootstrapApplication();
|
||||
void restoreMainWindowState(MainWindow& mainWindow);
|
||||
void disableCoreDumps();
|
||||
bool createWindowsDACL();
|
||||
void setupSearchPaths();
|
||||
};
|
||||
|
||||
|
||||
#endif //KEEPASSXC_BOOTSTRAP_H
|
||||
@@ -47,7 +47,7 @@ Database::Database()
|
||||
, m_emitModified(false)
|
||||
, m_uuid(QUuid::createUuid())
|
||||
{
|
||||
m_data.cipher = KeePass2::CIPHER_AES;
|
||||
m_data.cipher = KeePass2::CIPHER_AES256;
|
||||
m_data.compressionAlgo = CompressionGZip;
|
||||
|
||||
// instantiate default AES-KDF with legacy KDBX3 flag set
|
||||
@@ -501,14 +501,14 @@ Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer<con
|
||||
return db;
|
||||
}
|
||||
|
||||
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename)
|
||||
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename, FILE* outputDescriptor, FILE* errorDescriptor)
|
||||
{
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
QTextStream outputTextStream(stdout);
|
||||
QTextStream errorTextStream(stderr);
|
||||
QTextStream out(outputDescriptor);
|
||||
QTextStream err(errorDescriptor);
|
||||
|
||||
outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
|
||||
outputTextStream.flush();
|
||||
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
|
||||
out.flush();
|
||||
|
||||
QString line = Utils::getPassword();
|
||||
auto passwordKey = QSharedPointer<PasswordKey>::create();
|
||||
@@ -518,11 +518,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam
|
||||
if (!keyFilename.isEmpty()) {
|
||||
auto fileKey = QSharedPointer<FileKey>::create();
|
||||
QString errorMessage;
|
||||
// LCOV_EXCL_START
|
||||
if (!fileKey->load(keyFilename, &errorMessage)) {
|
||||
errorTextStream << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage);
|
||||
errorTextStream << endl;
|
||||
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fileKey->type() != FileKey::Hashed) {
|
||||
err << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
|
||||
"unsupported in the future.\n\n"
|
||||
"Please consider generating a new key file.") << endl;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
compositeKey->addKey(fileKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,8 @@ public:
|
||||
|
||||
static Database* databaseByUuid(const QUuid& uuid);
|
||||
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key);
|
||||
static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString(""));
|
||||
static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = {},
|
||||
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
|
||||
|
||||
signals:
|
||||
void groupDataChanged(Group* group);
|
||||
|
||||
@@ -80,21 +80,21 @@ QString PasswordGenerator::generatePassword() const
|
||||
QString password;
|
||||
|
||||
if (m_flags & CharFromEveryGroup) {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
int pos = randomGen()->randomUInt(groups[i].size());
|
||||
for (const auto& group : groups) {
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(group.size()));
|
||||
|
||||
password.append(groups[i][pos]);
|
||||
password.append(group[pos]);
|
||||
}
|
||||
|
||||
for (int i = groups.size(); i < m_length; i++) {
|
||||
int pos = randomGen()->randomUInt(passwordChars.size());
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(passwordChars.size()));
|
||||
|
||||
password.append(passwordChars[pos]);
|
||||
}
|
||||
|
||||
// shuffle chars
|
||||
for (int i = (password.size() - 1); i >= 1; i--) {
|
||||
int j = randomGen()->randomUInt(i + 1);
|
||||
int j = randomGen()->randomUInt(static_cast<quint32>(i + 1));
|
||||
|
||||
QChar tmp = password[i];
|
||||
password[i] = password[j];
|
||||
@@ -102,7 +102,7 @@ QString PasswordGenerator::generatePassword() const
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < m_length; i++) {
|
||||
int pos = randomGen()->randomUInt(passwordChars.size());
|
||||
int pos = randomGen()->randomUInt(static_cast<quint32>(passwordChars.size()));
|
||||
|
||||
password.append(passwordChars[pos]);
|
||||
}
|
||||
@@ -111,21 +111,6 @@ QString PasswordGenerator::generatePassword() const
|
||||
return password;
|
||||
}
|
||||
|
||||
int PasswordGenerator::getbits() const
|
||||
{
|
||||
const QVector<PasswordGroup> groups = passwordGroups();
|
||||
|
||||
int bits = 0;
|
||||
QVector<QChar> passwordChars;
|
||||
for (const PasswordGroup& group : groups) {
|
||||
bits += group.size();
|
||||
}
|
||||
|
||||
bits *= m_length;
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
bool PasswordGenerator::isValid() const
|
||||
{
|
||||
if (m_classes == 0) {
|
||||
@@ -138,11 +123,8 @@ bool PasswordGenerator::isValid() const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (passwordGroups().size() == 0) {
|
||||
return false;
|
||||
}
|
||||
return !passwordGroups().isEmpty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<PasswordGroup> PasswordGenerator::passwordGroups() const
|
||||
@@ -298,9 +280,9 @@ QVector<PasswordGroup> PasswordGenerator::passwordGroups() const
|
||||
j = group.indexOf(ch);
|
||||
}
|
||||
}
|
||||
if (group.size() > 0) {
|
||||
if (!group.isEmpty()) {
|
||||
passwordGroups.replace(i, group);
|
||||
i++;
|
||||
++i;
|
||||
} else {
|
||||
passwordGroups.remove(i);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
QString generatePassword() const;
|
||||
int getbits() const;
|
||||
|
||||
static const int DefaultLength = 16;
|
||||
static const char* DefaultExcludedChars;
|
||||
|
||||
@@ -18,19 +18,20 @@
|
||||
*/
|
||||
|
||||
#include "Tools.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Translator.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QIODevice>
|
||||
#include <QImageReader>
|
||||
#include <QLocale>
|
||||
#include <QStringList>
|
||||
#include <cctype>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <aclapi.h> // for SetSecurityInfo()
|
||||
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
|
||||
#include <windows.h> // for Sleep()
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
@@ -56,294 +57,161 @@
|
||||
|
||||
namespace Tools
|
||||
{
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision)
|
||||
{
|
||||
constexpr auto kibibyte = 1024;
|
||||
double size = bytes;
|
||||
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision)
|
||||
{
|
||||
constexpr auto kibibyte = 1024;
|
||||
double size = bytes;
|
||||
QStringList units = QStringList() << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB";
|
||||
int i = 0;
|
||||
int maxI = units.size() - 1;
|
||||
|
||||
QStringList units = QStringList() << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB";
|
||||
int i = 0;
|
||||
int maxI = units.size() - 1;
|
||||
|
||||
while ((size >= kibibyte) && (i < maxI)) {
|
||||
size /= kibibyte;
|
||||
i++;
|
||||
}
|
||||
|
||||
return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
|
||||
while ((size >= kibibyte) && (i < maxI)) {
|
||||
size /= kibibyte;
|
||||
i++;
|
||||
}
|
||||
|
||||
bool hasChild(const QObject* parent, const QObject* child)
|
||||
{
|
||||
if (!parent || !child) {
|
||||
return false;
|
||||
}
|
||||
return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
|
||||
}
|
||||
|
||||
const QObjectList children = parent->children();
|
||||
for (QObject* c : children) {
|
||||
if (child == c || hasChild(c, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool hasChild(const QObject* parent, const QObject* child)
|
||||
{
|
||||
if (!parent || !child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
||||
{
|
||||
QByteArray buffer;
|
||||
buffer.resize(size);
|
||||
|
||||
qint64 readResult = device->read(buffer.data(), size);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
buffer.resize(readResult);
|
||||
data = buffer;
|
||||
const QObjectList children = parent->children();
|
||||
for (QObject* c : children) {
|
||||
if (child == c || hasChild(c, child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data)
|
||||
{
|
||||
QByteArray result;
|
||||
qint64 readBytes = 0;
|
||||
qint64 readResult;
|
||||
do {
|
||||
result.resize(result.size() + 16384);
|
||||
readResult = device->read(result.data() + readBytes, result.size() - readBytes);
|
||||
if (readResult > 0) {
|
||||
readBytes += readResult;
|
||||
}
|
||||
} while (readResult > 0);
|
||||
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
result.resize(static_cast<int>(readBytes));
|
||||
data = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QString imageReaderFilter()
|
||||
{
|
||||
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
|
||||
QStringList formatsStringList;
|
||||
|
||||
for (const QByteArray& format : formats) {
|
||||
for (int i = 0; i < format.size(); i++) {
|
||||
if (!QChar(format.at(i)).isLetterOrNumber()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
formatsStringList.append("*." + QString::fromLatin1(format).toLower());
|
||||
}
|
||||
|
||||
return formatsStringList.join(" ");
|
||||
}
|
||||
|
||||
bool isHex(const QByteArray& ba)
|
||||
{
|
||||
for (const unsigned char c : ba) {
|
||||
if (!std::isxdigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
||||
{
|
||||
QByteArray buffer;
|
||||
buffer.resize(size);
|
||||
|
||||
qint64 readResult = device->read(buffer.data(), size);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
buffer.resize(readResult);
|
||||
data = buffer;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isBase64(const QByteArray& ba)
|
||||
{
|
||||
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
|
||||
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
|
||||
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
||||
|
||||
return regexp.exactMatch(base64);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data)
|
||||
{
|
||||
QByteArray result;
|
||||
qint64 readBytes = 0;
|
||||
qint64 readResult;
|
||||
do {
|
||||
result.resize(result.size() + 16384);
|
||||
readResult = device->read(result.data() + readBytes, result.size() - readBytes);
|
||||
if (readResult > 0) {
|
||||
readBytes += readResult;
|
||||
}
|
||||
}
|
||||
while (readResult > 0);
|
||||
|
||||
void sleep(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
if (readResult == -1) {
|
||||
return false;
|
||||
} else {
|
||||
result.resize(static_cast<int>(readBytes));
|
||||
data = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
QString imageReaderFilter()
|
||||
{
|
||||
const QList<QByteArray> formats = QImageReader::supportedImageFormats();
|
||||
QStringList formatsStringList;
|
||||
|
||||
for (const QByteArray& format : formats) {
|
||||
for (int i = 0; i < format.size(); i++) {
|
||||
if (!QChar(format.at(i)).isLetterOrNumber()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
formatsStringList.append("*." + QString::fromLatin1(format).toLower());
|
||||
}
|
||||
|
||||
return formatsStringList.join(" ");
|
||||
}
|
||||
|
||||
bool isHex(const QByteArray& ba)
|
||||
{
|
||||
for (const unsigned char c : ba) {
|
||||
if (!std::isxdigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isBase64(const QByteArray& ba)
|
||||
{
|
||||
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
|
||||
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
|
||||
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
||||
|
||||
return regexp.exactMatch(base64);
|
||||
}
|
||||
|
||||
void sleep(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Sleep(uint(ms));
|
||||
Sleep(uint(ms));
|
||||
#else
|
||||
timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
|
||||
nanosleep(&ts, nullptr);
|
||||
timespec ts;
|
||||
ts.tv_sec = ms/1000;
|
||||
ts.tv_nsec = (ms%1000)*1000*1000;
|
||||
nanosleep(&ts, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void wait(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
void wait(int ms)
|
||||
{
|
||||
Q_ASSERT(ms >= 0);
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
if (ms == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
if (ms <= 50) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
|
||||
sleep(qMax(ms - static_cast<int>(timer.elapsed()), 0));
|
||||
} else {
|
||||
int timeLeft;
|
||||
do {
|
||||
timeLeft = ms - timer.elapsed();
|
||||
if (timeLeft > 0) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft);
|
||||
sleep(10);
|
||||
}
|
||||
} while (!timer.hasExpired(ms));
|
||||
if (ms <= 50) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
|
||||
sleep(qMax(ms - static_cast<int>(timer.elapsed()), 0));
|
||||
} else {
|
||||
int timeLeft;
|
||||
do {
|
||||
timeLeft = ms - timer.elapsed();
|
||||
if (timeLeft > 0) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft);
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
while (!timer.hasExpired(ms));
|
||||
}
|
||||
|
||||
void disableCoreDumps()
|
||||
{
|
||||
// default to true
|
||||
// there is no point in printing a warning if this is not implemented on the platform
|
||||
bool success = true;
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#endif
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
success = success && createWindowsDACL();
|
||||
#endif
|
||||
|
||||
if (!success) {
|
||||
qWarning("Unable to disable core dumps.");
|
||||
}
|
||||
}
|
||||
|
||||
void setupSearchPaths()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// Make sure Windows doesn't load DLLs from the current working directory
|
||||
SetDllDirectoryA("");
|
||||
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// This function grants the user associated with the process token minimal access rights and
|
||||
// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and
|
||||
// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory().
|
||||
// We do this using a discretionary access control list (DACL). Effectively this prevents
|
||||
// crash dumps and disallows other processes from accessing our memory. This works as long
|
||||
// as you do not have admin privileges, since then you are able to grant yourself the
|
||||
// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL.
|
||||
//
|
||||
bool createWindowsDACL()
|
||||
{
|
||||
bool bSuccess = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Process token and user
|
||||
HANDLE hToken = nullptr;
|
||||
PTOKEN_USER pTokenUser = nullptr;
|
||||
DWORD cbBufferSize = 0;
|
||||
|
||||
// Access control list
|
||||
PACL pACL = nullptr;
|
||||
DWORD cbACL = 0;
|
||||
|
||||
// Open the access token associated with the calling process
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Retrieve the token information in a TOKEN_USER structure
|
||||
GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize);
|
||||
|
||||
pTokenUser = static_cast<PTOKEN_USER>(HeapAlloc(GetProcessHeap(), 0, cbBufferSize));
|
||||
if (pTokenUser == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!IsValidSid(pTokenUser->User.Sid)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Calculate the amount of memory that must be allocated for the DACL
|
||||
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
|
||||
|
||||
// Create and initialize an ACL
|
||||
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
|
||||
if (pACL == nullptr) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Add allowed access control entries, everything else is denied
|
||||
if (!AddAccessAllowedAce(
|
||||
pACL,
|
||||
ACL_REVISION,
|
||||
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process
|
||||
pTokenUser->User.Sid // pointer to the trustee's SID
|
||||
)) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// Set discretionary access control list
|
||||
bSuccess = ERROR_SUCCESS
|
||||
== SetSecurityInfo(GetCurrentProcess(), // object handle
|
||||
SE_KERNEL_OBJECT, // type of object
|
||||
DACL_SECURITY_INFORMATION, // change only the objects DACL
|
||||
nullptr,
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
if (pACL != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pACL);
|
||||
}
|
||||
if (pTokenUser != nullptr) {
|
||||
HeapFree(GetProcessHeap(), 0, pTokenUser);
|
||||
}
|
||||
if (hToken != nullptr) {
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
#endif
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
@@ -30,31 +30,27 @@ class QIODevice;
|
||||
|
||||
namespace Tools
|
||||
{
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision = 2);
|
||||
bool hasChild(const QObject* parent, const QObject* child);
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data);
|
||||
QString imageReaderFilter();
|
||||
bool isHex(const QByteArray& ba);
|
||||
bool isBase64(const QByteArray& ba);
|
||||
void sleep(int ms);
|
||||
void wait(int ms);
|
||||
|
||||
QString humanReadableFileSize(qint64 bytes, quint32 precision = 2);
|
||||
bool hasChild(const QObject* parent, const QObject* child);
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384);
|
||||
bool readAllFromDevice(QIODevice* device, QByteArray& data);
|
||||
QString imageReaderFilter();
|
||||
bool isHex(const QByteArray& ba);
|
||||
bool isBase64(const QByteArray& ba);
|
||||
void sleep(int ms);
|
||||
void wait(int ms);
|
||||
void disableCoreDumps();
|
||||
void setupSearchPaths();
|
||||
bool createWindowsDACL();
|
||||
template <typename RandomAccessIterator, typename T>
|
||||
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
|
||||
{
|
||||
RandomAccessIterator it = std::lower_bound(begin, end, value);
|
||||
|
||||
template <typename RandomAccessIterator, typename T>
|
||||
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
|
||||
{
|
||||
RandomAccessIterator it = std::lower_bound(begin, end, value);
|
||||
|
||||
if ((it == end) || (value < *it)) {
|
||||
return end;
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
if ((it == end) || (value < *it)) {
|
||||
return end;
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
|
||||
Reference in New Issue
Block a user