Wizards: Do some input validation on repository URLs

Fixes: QTCREATORBUG-18935
Change-Id: Ie2103cbe2899ea23caaedd4a6350c78b5f380ab9
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: André Hartmann <aha_1980@gmx.de>
This commit is contained in:
Christian Kandeler
2019-07-23 16:15:57 +02:00
parent 00bdb007ee
commit df8ef72aec
13 changed files with 95 additions and 50 deletions

View File

@@ -40,7 +40,9 @@
"name": "Repo", "name": "Repo",
"trDisplayName": "Repository:", "trDisplayName": "Repository:",
"mandatory": true, "mandatory": true,
"type": "LineEdit" "type": "LineEdit",
"isComplete": "%{JS: Vcs.isValidRepoUrl('%{vcsId}', '%{Repo}')}",
"trIncompleteMessage": "Repository URL is not valid"
}, },
{ {
"name": "Sp1", "name": "Sp1",

View File

@@ -38,7 +38,9 @@
"name": "Repo", "name": "Repo",
"trDisplayName": "Module:", "trDisplayName": "Module:",
"mandatory": true, "mandatory": true,
"type": "LineEdit" "type": "LineEdit",
"isComplete": "%{JS: Vcs.isValidRepoUrl('%{vcsId}', '%{Repo}')}",
"trIncompleteMessage": "Repository URL is not valid"
}, },
{ {
"name": "Sp1", "name": "Sp1",

View File

@@ -39,7 +39,9 @@
{ {
"name": "Repo", "name": "Repo",
"trDisplayName": "Repository:", "trDisplayName": "Repository:",
"type": "LineEdit" "type": "LineEdit",
"isComplete": "%{JS: Vcs.isValidRepoUrl('%{vcsId}', '%{Repo}')}",
"trIncompleteMessage": "Repository URL is not valid"
}, },
{ {
"name": "Branch", "name": "Branch",

View File

@@ -39,7 +39,9 @@
"name": "Repo", "name": "Repo",
"trDisplayName": "Repository:", "trDisplayName": "Repository:",
"mandatory": true, "mandatory": true,
"type": "LineEdit" "type": "LineEdit",
"isComplete": "%{JS: Vcs.isValidRepoUrl('%{vcsId}', '%{Repo}')}",
"trIncompleteMessage": "Repository URL is not valid"
}, },
{ {
"name": "Sp1", "name": "Sp1",

View File

@@ -39,7 +39,9 @@
"name": "Repo", "name": "Repo",
"trDisplayName": "Repository:", "trDisplayName": "Repository:",
"mandatory": true, "mandatory": true,
"type": "LineEdit" "type": "LineEdit",
"isComplete": "%{JS: Vcs.isValidRepoUrl('%{vcsId}', '%{Repo}')}",
"trIncompleteMessage": "Repository URL is not valid"
}, },
{ {
"name": "Sp1", "name": "Sp1",

View File

@@ -26,9 +26,12 @@
#include "iversioncontrol.h" #include "iversioncontrol.h"
#include "vcsmanager.h" #include "vcsmanager.h"
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QRegularExpression>
#include <QStringList> #include <QStringList>
/*! /*!
@@ -90,6 +93,50 @@ ShellCommand *IVersionControl::createInitialCheckoutCommand(const QString &url,
return nullptr; return nullptr;
} }
IVersionControl::RepoUrl::RepoUrl(const QString &location)
{
if (location.isEmpty())
return;
// Check for local remotes (refer to the root or relative path)
// On Windows, local paths typically starts with <drive>:
auto locationIsOnWindowsDrive = [&location] {
if (!Utils::HostOsInfo::isWindowsHost() || location.size() < 2)
return false;
const QChar drive = location.at(0).toLower();
return drive >= 'a' && drive <= 'z' && location.at(1) == ':';
};
if (location.startsWith("file://") || location.startsWith('/') || location.startsWith('.')
|| locationIsOnWindowsDrive()) {
protocol = "file";
path = QDir::fromNativeSeparators(location.startsWith("file://")
? location.mid(7) : location);
isValid = true;
return;
}
// TODO: Why not use QUrl?
static const QRegularExpression remotePattern(
"^(?:(?<protocol>[^:]+)://)?(?:(?<user>[^@]+)@)?(?<host>[^:/]+)"
"(?::(?<port>\\d+))?:?(?<path>.*)$");
const QRegularExpressionMatch match = remotePattern.match(location);
if (!match.hasMatch())
return;
bool ok = false;
protocol = match.captured("protocol");
userName = match.captured("user");
host = match.captured("host");
port = match.captured("port").toUShort(&ok);
path = match.captured("path");
isValid = !host.isEmpty() && (ok || match.captured("port").isEmpty());
}
IVersionControl::RepoUrl IVersionControl::getRepoUrl(const QString &location) const
{
return RepoUrl(location);
}
QString IVersionControl::vcsTopic(const QString &topLevel) QString IVersionControl::vcsTopic(const QString &topLevel)
{ {
return m_topicCache ? m_topicCache->topic(topLevel) : QString(); return m_topicCache ? m_topicCache->topic(topLevel) : QString();

View File

@@ -217,6 +217,19 @@ public:
const QString &localName, const QString &localName,
const QStringList &extraArgs); const QStringList &extraArgs);
class RepoUrl {
public:
RepoUrl(const QString &location);
QString protocol;
QString userName;
QString host;
QString path;
quint16 port = 0;
bool isValid = false;
};
virtual RepoUrl getRepoUrl(const QString &location) const;
signals: signals:
void repositoryChanged(const QString &repository); void repositoryChanged(const QString &repository);
void filesChanged(const QStringList &files); void filesChanged(const QStringList &files);

View File

@@ -3458,44 +3458,10 @@ void GitClient::StashInfo::end()
m_stashResult = NotStashed; m_stashResult = NotStashed;
} }
// GitRemote GitRemote::GitRemote(const QString &location) : Core::IVersionControl::RepoUrl(location)
GitRemote::GitRemote(const QString &url)
{ {
static const QRegularExpression remotePattern( if (isValid && protocol == "file")
"^(?:(?<protocol>[^:]+)://)?(?:(?<user>[^@]+)@)?(?<host>[^:/]+)"
"(?::(?<port>\\d+))?:?(?<path>.*)$");
if (url.isEmpty())
return;
// Check for local remotes (refer to the root or relative path)
// On Windows, local paths typically starts with <drive>:
auto startsWithWindowsDrive = [](const QString &url) {
if (!HostOsInfo::isWindowsHost() || url.size() < 2)
return false;
const QChar drive = url.at(0).toLower();
return drive >= 'a' && drive <= 'z' && url.at(1) == ':';
};
if (url.startsWith("file://") || url.startsWith('/') || url.startsWith('.')
|| startsWithWindowsDrive(url)) {
protocol = "file";
path = QDir::fromNativeSeparators(url.startsWith("file://") ? url.mid(7) : url);
isValid = QDir(path).exists() || QDir(path + ".git").exists(); isValid = QDir(path).exists() || QDir(path + ".git").exists();
return;
}
const QRegularExpressionMatch match = remotePattern.match(url);
if (!match.hasMatch())
return;
bool ok = false;
protocol = match.captured("protocol");
userName = match.captured("user");
host = match.captured("host");
port = match.captured("port").toUShort(&ok);
path = match.captured("path");
isValid = ok || match.captured("port").isEmpty();
} }
} // namespace Internal } // namespace Internal

View File

@@ -29,6 +29,7 @@
#include "commitdata.h" #include "commitdata.h"
#include <coreplugin/editormanager/ieditor.h> #include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/iversioncontrol.h>
#include <vcsbase/vcsbaseclient.h> #include <vcsbase/vcsbaseclient.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
@@ -381,16 +382,10 @@ private:
QFutureSynchronizer<void> m_synchronizer; // for commit updates QFutureSynchronizer<void> m_synchronizer; // for commit updates
}; };
class GitRemote { class GitRemote : public Core::IVersionControl::RepoUrl
{
public: public:
GitRemote(const QString &url); GitRemote(const QString &location);
QString protocol;
QString userName;
QString host;
QString path;
quint16 port = 0;
bool isValid = false;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -163,6 +163,11 @@ Core::ShellCommand *GitVersionControl::createInitialCheckoutCommand(const QStrin
return command; return command;
} }
GitVersionControl::RepoUrl GitVersionControl::getRepoUrl(const QString &location) const
{
return GitRemote(location);
}
QStringList GitVersionControl::additionalToolsPath() const QStringList GitVersionControl::additionalToolsPath() const
{ {
QStringList res = m_client->settings().searchPathList(); QStringList res = m_client->settings().searchPathList();

View File

@@ -63,6 +63,8 @@ public:
const QString &localName, const QString &localName,
const QStringList &extraArgs) final; const QStringList &extraArgs) final;
RepoUrl getRepoUrl(const QString &location) const override;
QStringList additionalToolsPath() const final; QStringList additionalToolsPath() const final;
void emitFilesChanged(const QStringList &); void emitFilesChanged(const QStringList &);

View File

@@ -45,5 +45,11 @@ QString VcsJsExtension::displayName(const QString &vcsId) const
return vc ? vc->displayName() : QString(); return vc ? vc->displayName() : QString();
} }
bool VcsJsExtension::isValidRepoUrl(const QString &vcsId, const QString &location) const
{
const IVersionControl * const vc = VcsManager::versionControl(Id::fromString(vcsId));
return vc && vc->getRepoUrl(location).isValid;
}
} // namespace Internal } // namespace Internal
} // namespace VcsBase } // namespace VcsBase

View File

@@ -37,6 +37,7 @@ class VcsJsExtension : public QObject
public: public:
Q_INVOKABLE bool isConfigured(const QString &vcsId) const; Q_INVOKABLE bool isConfigured(const QString &vcsId) const;
Q_INVOKABLE QString displayName(const QString &vcsId) const; Q_INVOKABLE QString displayName(const QString &vcsId) const;
Q_INVOKABLE bool isValidRepoUrl(const QString &vcsId, const QString &location) const;
}; };
} // namespace Internal } // namespace Internal