Remove QRegExp usage from QSslCertificate and QSslSocket
Change-Id: I81abe1ab2173af922fa4b5fad58d25fa602c523b Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
6a8132c8ee
commit
3af596402a
@ -48,14 +48,6 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//! [0]
|
||||
const auto certs = QSslCertificate::fromPath("C:/ssl/certificate.*.pem",
|
||||
QSsl::Pem, QRegExp::Wildcard);
|
||||
for (const QSslCertificate &cert : certs) {
|
||||
qDebug() << cert.issuerInfo(QSslCertificate::Organization);
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
const auto certs = QSslCertificate::fromPath("C:/ssl/certificate.*.pem",
|
||||
QSsl::Pem, QSslCertificate::Wildcard);
|
||||
|
@ -455,86 +455,6 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
|
||||
\since 5.0
|
||||
*/
|
||||
|
||||
#if QT_DEPRECATED_SINCE(5,15)
|
||||
/*!
|
||||
\obsolete
|
||||
|
||||
Searches all files in the \a path for certificates encoded in the
|
||||
specified \a format and returns them in a list. \a path must be a file
|
||||
or a pattern matching one or more files, as specified by \a syntax.
|
||||
|
||||
Example:
|
||||
|
||||
\snippet code/src_network_ssl_qsslcertificate.cpp 0
|
||||
|
||||
\sa fromData()
|
||||
*/
|
||||
QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
|
||||
QSsl::EncodingFormat format,
|
||||
QRegExp::PatternSyntax syntax)
|
||||
{
|
||||
// $, (,), *, +, ., ?, [, ,], ^, {, | and }.
|
||||
|
||||
// make sure to use the same path separators on Windows and Unix like systems.
|
||||
QString sourcePath = QDir::fromNativeSeparators(path);
|
||||
|
||||
// Find the path without the filename
|
||||
QString pathPrefix = sourcePath.left(sourcePath.lastIndexOf(QLatin1Char('/')));
|
||||
|
||||
// Check if the path contains any special chars
|
||||
int pos = -1;
|
||||
if (syntax == QRegExp::Wildcard)
|
||||
pos = pathPrefix.indexOf(QRegExp(QLatin1String("[*?[]")));
|
||||
else if (syntax != QRegExp::FixedString)
|
||||
pos = sourcePath.indexOf(QRegExp(QLatin1String("[\\$\\(\\)\\*\\+\\.\\?\\[\\]\\^\\{\\}\\|]")));
|
||||
if (pos != -1) {
|
||||
// there was a special char in the path so cut of the part containing that char.
|
||||
pathPrefix = pathPrefix.left(pos);
|
||||
const int lastIndexOfSlash = pathPrefix.lastIndexOf(QLatin1Char('/'));
|
||||
if (lastIndexOfSlash != -1)
|
||||
pathPrefix = pathPrefix.left(lastIndexOfSlash);
|
||||
else
|
||||
pathPrefix.clear();
|
||||
} else {
|
||||
// Check if the path is a file.
|
||||
if (QFileInfo(sourcePath).isFile()) {
|
||||
QFile file(sourcePath);
|
||||
QIODevice::OpenMode openMode = QIODevice::ReadOnly;
|
||||
if (format == QSsl::Pem)
|
||||
openMode |= QIODevice::Text;
|
||||
if (file.open(openMode))
|
||||
return QSslCertificate::fromData(file.readAll(), format);
|
||||
return QList<QSslCertificate>();
|
||||
}
|
||||
}
|
||||
|
||||
// Special case - if the prefix ends up being nothing, use "." instead.
|
||||
int startIndex = 0;
|
||||
if (pathPrefix.isEmpty()) {
|
||||
pathPrefix = QLatin1String(".");
|
||||
startIndex = 2;
|
||||
}
|
||||
|
||||
// The path can be a file or directory.
|
||||
QList<QSslCertificate> certs;
|
||||
QRegExp pattern(sourcePath, Qt::CaseSensitive, syntax);
|
||||
QDirIterator it(pathPrefix, QDir::Files, QDirIterator::FollowSymlinks | QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString filePath = startIndex == 0 ? it.next() : it.next().mid(startIndex);
|
||||
if (!pattern.exactMatch(filePath))
|
||||
continue;
|
||||
|
||||
QFile file(filePath);
|
||||
QIODevice::OpenMode openMode = QIODevice::ReadOnly;
|
||||
if (format == QSsl::Pem)
|
||||
openMode |= QIODevice::Text;
|
||||
if (file.open(openMode))
|
||||
certs += QSslCertificate::fromData(file.readAll(), format);
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
#endif // QT_DEPRECATED_SINCE(5,15)
|
||||
|
||||
/*!
|
||||
\since 5.15
|
||||
|
||||
|
@ -50,7 +50,6 @@
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qcryptographichash.h>
|
||||
#include <QtCore/qdatetime.h>
|
||||
#include <QtCore/qregexp.h>
|
||||
#include <QtCore/qsharedpointer.h>
|
||||
#include <QtCore/qmap.h>
|
||||
#include <QtNetwork/qssl.h>
|
||||
@ -142,19 +141,9 @@ public:
|
||||
QByteArray toDer() const;
|
||||
QString toText() const;
|
||||
|
||||
#if QT_DEPRECATED_SINCE(5,15)
|
||||
QT_DEPRECATED_X("Use the overload not using QRegExp") static QList<QSslCertificate> fromPath(
|
||||
const QString &path, QSsl::EncodingFormat format = QSsl::Pem,
|
||||
QRegExp::PatternSyntax syntax = QRegExp::FixedString);
|
||||
|
||||
static QList<QSslCertificate> fromPath(
|
||||
const QString &path, QSsl::EncodingFormat format,
|
||||
PatternSyntax syntax);
|
||||
#else
|
||||
static QList<QSslCertificate> fromPath(
|
||||
const QString &path, QSsl::EncodingFormat format = QSsl::Pem,
|
||||
PatternSyntax syntax = FixedString);
|
||||
#endif
|
||||
PatternSyntax syntax = PatternSyntax::FixedString);
|
||||
|
||||
static QList<QSslCertificate> fromDevice(
|
||||
QIODevice *device, QSsl::EncodingFormat format = QSsl::Pem);
|
||||
|
@ -1524,40 +1524,6 @@ QList<QSslCipher> QSslSocket::supportedCiphers()
|
||||
}
|
||||
#endif // #if QT_DEPRECATED_SINCE(5, 5)
|
||||
|
||||
/*!
|
||||
\deprecated
|
||||
|
||||
Use QSslConfiguration::addCaCertificates() instead.
|
||||
|
||||
Searches all files in the \a path for certificates encoded in the
|
||||
specified \a format and adds them to this socket's CA certificate
|
||||
database. \a path must be a file or a pattern matching one or more
|
||||
files, as specified by \a syntax. Returns \c true if one or more
|
||||
certificates are added to the socket's CA certificate database;
|
||||
otherwise returns \c false.
|
||||
|
||||
The CA certificate database is used by the socket during the
|
||||
handshake phase to validate the peer's certificate.
|
||||
|
||||
For more precise control, use addCaCertificate().
|
||||
|
||||
\sa addCaCertificate(), QSslCertificate::fromPath()
|
||||
*/
|
||||
bool QSslSocket::addCaCertificates(const QString &path, QSsl::EncodingFormat format,
|
||||
QRegExp::PatternSyntax syntax)
|
||||
{
|
||||
Q_D(QSslSocket);
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
QList<QSslCertificate> certs = QSslCertificate::fromPath(path, format, syntax);
|
||||
QT_WARNING_POP
|
||||
if (certs.isEmpty())
|
||||
return false;
|
||||
|
||||
d->configuration.caCertificates += certs;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\deprecated
|
||||
|
||||
@ -1644,29 +1610,6 @@ QList<QSslCertificate> QSslSocket::caCertificates() const
|
||||
}
|
||||
#endif // #if QT_DEPRECATED_SINCE(5, 5)
|
||||
|
||||
/*!
|
||||
\deprecated
|
||||
|
||||
Use QSslConfiguration::addCaCertificates() on the default QSslConfiguration instead.
|
||||
|
||||
Searches all files in the \a path for certificates with the
|
||||
specified \a encoding and adds them to the default CA certificate
|
||||
database. \a path can be an explicit file, or it can contain
|
||||
wildcards in the format specified by \a syntax. Returns \c true if
|
||||
any CA certificates are added to the default database.
|
||||
|
||||
Each SSL socket's CA certificate database is initialized to the
|
||||
default CA certificate database.
|
||||
|
||||
\sa QSslConfiguration::caCertificates(), QSslConfiguration::addCaCertificates(),
|
||||
QSslConfiguration::addCaCertificate()
|
||||
*/
|
||||
bool QSslSocket::addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat encoding,
|
||||
QRegExp::PatternSyntax syntax)
|
||||
{
|
||||
return QSslSocketPrivate::addDefaultCaCertificates(path, encoding, syntax);
|
||||
}
|
||||
|
||||
/*!
|
||||
\deprecated
|
||||
|
||||
@ -2515,28 +2458,6 @@ void QSslSocketPrivate::setDefaultCaCertificates(const QList<QSslCertificate> &c
|
||||
s_loadRootCertsOnDemand = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
bool QSslSocketPrivate::addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format,
|
||||
QRegExp::PatternSyntax syntax)
|
||||
{
|
||||
QSslSocketPrivate::ensureInitialized();
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
QList<QSslCertificate> certs = QSslCertificate::fromPath(path, format, syntax);
|
||||
QT_WARNING_POP
|
||||
if (certs.isEmpty())
|
||||
return false;
|
||||
|
||||
QMutexLocker locker(&globalData()->mutex);
|
||||
globalData()->config.detach();
|
||||
globalData()->config->caCertificates += certs;
|
||||
globalData()->dtlsConfig.detach();
|
||||
globalData()->dtlsConfig->caCertificates += certs;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
|
@ -43,7 +43,6 @@
|
||||
|
||||
#include <QtNetwork/qtnetworkglobal.h>
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qregexp.h>
|
||||
#include <QtCore/qvector.h>
|
||||
#ifndef QT_NO_SSL
|
||||
# include <QtNetwork/qtcpsocket.h>
|
||||
@ -208,8 +207,6 @@ public:
|
||||
|
||||
// CA settings.
|
||||
#if QT_DEPRECATED_SINCE(5, 15)
|
||||
QT_DEPRECATED_X("Use QSslConfiguration::addCaCertificates()") bool addCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem,
|
||||
QRegExp::PatternSyntax syntax = QRegExp::FixedString);
|
||||
QT_DEPRECATED_X("Use QSslConfiguration::addCaCertificate()") void addCaCertificate(const QSslCertificate &certificate);
|
||||
QT_DEPRECATED_X("Use QSslConfiguration::addCaCertificates()") void addCaCertificates(const QList<QSslCertificate> &certificates);
|
||||
#endif // QT_DEPRECATED_SINCE(5, 15)
|
||||
@ -218,8 +215,6 @@ public:
|
||||
QT_DEPRECATED_X("Use QSslConfiguration::caCertificates()") QList<QSslCertificate> caCertificates() const;
|
||||
#endif // QT_DEPRECATED_SINCE(5, 5)
|
||||
#if QT_DEPRECATED_SINCE(5, 15)
|
||||
QT_DEPRECATED static bool addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem,
|
||||
QRegExp::PatternSyntax syntax = QRegExp::FixedString);
|
||||
QT_DEPRECATED static void addDefaultCaCertificate(const QSslCertificate &certificate);
|
||||
QT_DEPRECATED static void addDefaultCaCertificates(const QList<QSslCertificate> &certificates);
|
||||
#endif // QT_DEPRECATED_SINCE(5, 15)
|
||||
|
@ -140,8 +140,6 @@ public:
|
||||
static QList<QSslCertificate> defaultCaCertificates();
|
||||
static QList<QSslCertificate> systemCaCertificates();
|
||||
static void setDefaultCaCertificates(const QList<QSslCertificate> &certs);
|
||||
static bool addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format,
|
||||
QRegExp::PatternSyntax syntax);
|
||||
static void addDefaultCaCertificate(const QSslCertificate &cert);
|
||||
static void addDefaultCaCertificates(const QList<QSslCertificate> &certs);
|
||||
Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QSslCertificate &cert,
|
||||
|
@ -84,8 +84,6 @@ private slots:
|
||||
void toPemOrDer_data();
|
||||
void toPemOrDer();
|
||||
void fromDevice();
|
||||
void fromPath_data();
|
||||
void fromPath();
|
||||
void fromPath_qregularexpression_data();
|
||||
void fromPath_qregularexpression();
|
||||
void certInfo();
|
||||
@ -545,88 +543,6 @@ void tst_QSslCertificate::fromDevice()
|
||||
QVERIFY(certs.isEmpty());
|
||||
}
|
||||
|
||||
void tst_QSslCertificate::fromPath_data()
|
||||
{
|
||||
QTest::addColumn<QString>("path");
|
||||
QTest::addColumn<int>("syntax");
|
||||
QTest::addColumn<bool>("pemencoding");
|
||||
QTest::addColumn<int>("numCerts");
|
||||
|
||||
QTest::newRow("empty fixed pem") << QString() << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("empty fixed der") << QString() << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("empty regexp pem") << QString() << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("empty regexp der") << QString() << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("empty wildcard pem") << QString() << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("empty wildcard der") << QString() << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"certificates\" fixed pem") << (testDataDir + "certificates") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"certificates\" fixed der") << (testDataDir + "certificates") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"certificates\" regexp pem") << (testDataDir + "certificates") << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("\"certificates\" regexp der") << (testDataDir + "certificates") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"certificates\" wildcard pem") << (testDataDir + "certificates") << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("\"certificates\" wildcard der") << (testDataDir + "certificates") << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"certificates/cert.pem\" fixed pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::FixedString) << true << 1;
|
||||
QTest::newRow("\"certificates/cert.pem\" fixed der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"certificates/cert.pem\" regexp pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::RegExp) << true << 1;
|
||||
QTest::newRow("\"certificates/cert.pem\" regexp der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"certificates/cert.pem\" wildcard pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::Wildcard) << true << 1;
|
||||
QTest::newRow("\"certificates/cert.pem\" wildcard der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"certificates/*\" fixed pem") << (testDataDir + "certificates/*") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"certificates/*\" fixed der") << (testDataDir + "certificates/*") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"certificates/*\" regexp pem") << (testDataDir + "certificates/*") << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("\"certificates/*\" regexp der") << (testDataDir + "certificates/*") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"certificates/*\" wildcard pem") << (testDataDir + "certificates/*") << int(QRegExp::Wildcard) << true << 7;
|
||||
QTest::newRow("\"certificates/ca*\" wildcard pem") << (testDataDir + "certificates/ca*") << int(QRegExp::Wildcard) << true << 1;
|
||||
QTest::newRow("\"certificates/cert*\" wildcard pem") << (testDataDir + "certificates/cert*") << int(QRegExp::Wildcard) << true << 4;
|
||||
QTest::newRow("\"certificates/cert-[sure]*\" wildcard pem") << (testDataDir + "certificates/cert-[sure]*") << int(QRegExp::Wildcard) << true << 3;
|
||||
QTest::newRow("\"certificates/cert-[not]*\" wildcard pem") << (testDataDir + "certificates/cert-[not]*") << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("\"certificates/*\" wildcard der") << (testDataDir + "certificates/*") << int(QRegExp::Wildcard) << false << 2;
|
||||
QTest::newRow("\"c*/c*.pem\" fixed pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"c*/c*.pem\" fixed der") << (testDataDir + "c*/c*.pem") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"c*/c*.pem\" regexp pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("\"c*/c*.pem\" regexp der") << (testDataDir + "c*/c*.pem") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"c*/c*.pem\" wildcard pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::Wildcard) << true << 5;
|
||||
QTest::newRow("\"c*/c*.pem\" wildcard der") << (testDataDir + "c*/c*.pem") << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" fixed pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" fixed der") << (testDataDir + "d*/c*.pem") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" regexp pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" regexp der") << (testDataDir + "d*/c*.pem") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" wildcard pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("\"d*/c*.pem\" wildcard der") << (testDataDir + "d*/c*.pem") << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"c.*/c.*.pem\" fixed pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"c.*/c.*.pem\" fixed der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"c.*/c.*.pem\" regexp pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::RegExp) << true << 5;
|
||||
QTest::newRow("\"c.*/c.*.pem\" regexp der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"c.*/c.*.pem\" wildcard pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("\"c.*/c.*.pem\" wildcard der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::Wildcard) << false << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" fixed pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" fixed der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::FixedString) << false << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" regexp pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::RegExp) << true << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" regexp der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::RegExp) << false << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" wildcard pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::Wildcard) << true << 0;
|
||||
QTest::newRow("\"d.*/c.*.pem\" wildcard der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::Wildcard) << false << 0;
|
||||
#ifdef Q_OS_LINUX
|
||||
QTest::newRow("absolute path wildcard pem") << (testDataDir + "certificates/*.pem") << int(QRegExp::Wildcard) << true << 7;
|
||||
#endif
|
||||
|
||||
QTest::newRow("trailing-whitespace") << (testDataDir + "more-certificates/trailing-whitespace.pem") << int(QRegExp::FixedString) << true << 1;
|
||||
QTest::newRow("no-ending-newline") << (testDataDir + "more-certificates/no-ending-newline.pem") << int(QRegExp::FixedString) << true << 1;
|
||||
QTest::newRow("malformed-just-begin") << (testDataDir + "more-certificates/malformed-just-begin.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
QTest::newRow("malformed-just-begin-no-newline") << (testDataDir + "more-certificates/malformed-just-begin-no-newline.pem") << int(QRegExp::FixedString) << true << 0;
|
||||
}
|
||||
|
||||
void tst_QSslCertificate::fromPath()
|
||||
{
|
||||
QFETCH(QString, path);
|
||||
QFETCH(int, syntax);
|
||||
QFETCH(bool, pemencoding);
|
||||
QFETCH(int, numCerts);
|
||||
|
||||
QCOMPARE(QSslCertificate::fromPath(path,
|
||||
pemencoding ? QSsl::Pem : QSsl::Der,
|
||||
QRegExp::PatternSyntax(syntax)).size(),
|
||||
numCerts);
|
||||
}
|
||||
|
||||
void tst_QSslCertificate::fromPath_qregularexpression_data()
|
||||
{
|
||||
QTest::addColumn<QString>("path");
|
||||
|
Loading…
x
Reference in New Issue
Block a user