diff --git a/src/plugins/git/gerrit/authenticationdialog.cpp b/src/plugins/git/gerrit/authenticationdialog.cpp index 53ad6b1fac3..e54510df5a5 100644 --- a/src/plugins/git/gerrit/authenticationdialog.cpp +++ b/src/plugins/git/gerrit/authenticationdialog.cpp @@ -25,7 +25,7 @@ #include "authenticationdialog.h" #include "ui_authenticationdialog.h" -#include "gerritparameters.h" +#include "gerritserver.h" #include #include diff --git a/src/plugins/git/gerrit/gerrit.pri b/src/plugins/git/gerrit/gerrit.pri index b4e843b2410..6e430c538ea 100644 --- a/src/plugins/git/gerrit/gerrit.pri +++ b/src/plugins/git/gerrit/gerrit.pri @@ -6,7 +6,8 @@ SOURCES += \ $$PWD/gerritoptionspage.cpp \ $$PWD/gerritparameters.cpp \ $$PWD/gerritplugin.cpp \ - $$PWD/gerritpushdialog.cpp + $$PWD/gerritpushdialog.cpp \ + $$PWD/gerritserver.cpp HEADERS += \ $$PWD/authenticationdialog.h \ @@ -16,7 +17,8 @@ HEADERS += \ $$PWD/gerritoptionspage.h \ $$PWD/gerritparameters.h \ $$PWD/gerritplugin.h \ - $$PWD/gerritpushdialog.h + $$PWD/gerritpushdialog.h \ + $$PWD/gerritserver.h FORMS += \ $$PWD/authenticationdialog.ui \ diff --git a/src/plugins/git/gerrit/gerritmodel.h b/src/plugins/git/gerrit/gerritmodel.h index fb124417e62..863a2c97c16 100644 --- a/src/plugins/git/gerrit/gerritmodel.h +++ b/src/plugins/git/gerrit/gerritmodel.h @@ -26,6 +26,7 @@ #pragma once #include "gerritparameters.h" +#include "gerritserver.h" #include #include diff --git a/src/plugins/git/gerrit/gerritoptionspage.cpp b/src/plugins/git/gerrit/gerritoptionspage.cpp index ab07f40bc0f..c61fa682212 100644 --- a/src/plugins/git/gerrit/gerritoptionspage.cpp +++ b/src/plugins/git/gerrit/gerritoptionspage.cpp @@ -25,6 +25,7 @@ #include "gerritoptionspage.h" #include "gerritparameters.h" +#include "gerritserver.h" #include #include diff --git a/src/plugins/git/gerrit/gerritparameters.cpp b/src/plugins/git/gerrit/gerritparameters.cpp index 86f8acde28e..b98f36b896b 100644 --- a/src/plugins/git/gerrit/gerritparameters.cpp +++ b/src/plugins/git/gerrit/gerritparameters.cpp @@ -25,19 +25,11 @@ #include "gerritparameters.h" #include "gerritplugin.h" -#include "authenticationdialog.h" -#include "../gitplugin.h" -#include "../gitclient.h" -#include #include #include -#include #include -#include -#include -#include #include #include @@ -54,14 +46,10 @@ static const char portFlagKeyC[] = "PortFlag"; static const char sshKeyC[] = "Ssh"; static const char curlKeyC[] = "Curl"; static const char httpsKeyC[] = "Https"; -static const char defaultHostC[] = "codereview.qt-project.org"; static const char savedQueriesKeyC[] = "SavedQueries"; -static const char accountUrlC[] = "/accounts/self"; static const char defaultPortFlag[] = "-p"; -enum { defaultPort = 29418 }; - static inline QString detectApp(const char *defaultExe) { const QString defaultApp = HostOsInfo::withExecutableSuffix(QLatin1String(defaultExe)); @@ -100,37 +88,6 @@ static inline QString detectSsh() return detectApp("ssh"); } -bool GerritUser::isSameAs(const GerritUser &other) const -{ - if (!userName.isEmpty() && !other.userName.isEmpty()) - return userName == other.userName; - if (!fullName.isEmpty() && !other.fullName.isEmpty()) - return fullName == other.fullName; - return false; -} - -GerritServer::GerritServer() - : host(defaultHostC) - , port(defaultPort) -{ -} - -GerritServer::GerritServer(const QString &host, unsigned short port, - const QString &userName, HostType type) - : host(host) - , port(port) - , type(type) -{ - user.userName = userName; -} - -bool GerritServer::operator==(const GerritServer &other) const -{ - if (port && other.port && port != other.port) - return false; - return host == other.host && user.isSameAs(other.user) && type == other.type; -} - void GerritParameters::setPortFlagBySshType() { bool isPlink = false; @@ -147,150 +104,6 @@ GerritParameters::GerritParameters() { } -QString GerritServer::hostArgument() const -{ - if (!authenticated || user.userName.isEmpty()) - return host; - return user.userName + '@' + host; -} - -QString GerritServer::url(UrlType urlType) const -{ - QString protocol; - switch (type) { - case Ssh: protocol = "ssh"; break; - case Http: protocol = "http"; break; - case Https: protocol = "https"; break; - } - QString res = protocol + "://"; - if (type == Ssh || urlType != DefaultUrl) - res += hostArgument(); - else - res += host; - if (port) - res += ':' + QString::number(port); - if (type != Ssh) { - res += rootPath; - if (authenticated && urlType == RestUrl) - res += "/a"; - } - return res; -} - -bool GerritServer::fillFromRemote(const QString &remote, const GerritParameters ¶meters) -{ - static const QRegularExpression remotePattern( - "^(?:(?[^:]+)://)?(?:(?[^@]+)@)?(?[^:/]+)" - "(?::(?\\d+))?:?(?/.*)$"); - - // Skip local remotes (refer to the root or relative path) - if (remote.isEmpty() || remote.startsWith('/') || remote.startsWith('.')) - return false; - // On Windows, local paths typically starts with : - if (HostOsInfo::isWindowsHost() && remote[1] == ':') - return false; - QRegularExpressionMatch match = remotePattern.match(remote); - if (!match.hasMatch()) - return false; - const QString protocol = match.captured("protocol"); - if (protocol == "https") - type = GerritServer::Https; - else if (protocol == "http") - type = GerritServer::Http; - else if (protocol.isEmpty() || protocol == "ssh") - type = GerritServer::Ssh; - else - return false; - const QString userName = match.captured("user"); - user.userName = userName.isEmpty() ? parameters.server.user.userName : userName; - host = match.captured("host"); - port = match.captured("port").toUShort(); - if (host.contains("github.com")) // Clearly not gerrit - return false; - if (type != GerritServer::Ssh) { - curlBinary = parameters.curl; - if (curlBinary.isEmpty() || !QFile::exists(curlBinary)) - return false; - rootPath = match.captured("path"); - // Strip the last part of the path, which is always the repo name - // The rest of the path needs to be inspected to find the root path - // (can be http://example.net/review) - ascendPath(); - if (!resolveRoot()) - return false; - } - return true; -} - -QStringList GerritServer::curlArguments() -{ - // -k - insecure - do not validate certificate - // -f - fail silently on server error - // -n - use credentials from ~/.netrc (or ~/_netrc on Windows) - // -sS - silent, except server error (no progress) - // --basic, --digest - try both authentication types - return {"-kfnsS", "--basic", "--digest"}; -} - -int GerritServer::testConnection() -{ - static Git::Internal::GitClient *const client = Git::Internal::GitPlugin::client(); - const QStringList arguments = curlArguments() << (url(RestUrl) + accountUrlC); - const SynchronousProcessResponse resp = client->vcsFullySynchronousExec( - QString(), FileName::fromString(curlBinary), arguments, - Core::ShellCommand::NoOutput); - if (resp.result == SynchronousProcessResponse::Finished) { - QString output = resp.stdOut(); - output.remove(0, output.indexOf('\n')); // Strip first line - QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8()); - if (!doc.isNull()) - user.fullName = doc.object().value("name").toString(); - return 200; - } - const QRegularExpression errorRegexp("returned error: (\\d+)"); - QRegularExpressionMatch match = errorRegexp.match(resp.stdErr()); - if (match.hasMatch()) - return match.captured(1).toInt(); - return 400; -} - -bool GerritServer::setupAuthentication() -{ - AuthenticationDialog dialog(this); - if (!dialog.exec()) - return false; - authenticated = dialog.isAuthenticated(); - return true; -} - -bool GerritServer::ascendPath() -{ - const int lastSlash = rootPath.lastIndexOf('/'); - if (lastSlash == -1) - return false; - rootPath = rootPath.left(lastSlash); - return true; -} - -bool GerritServer::resolveRoot() -{ - for (;;) { - switch (testConnection()) { - case 200: - return true; - case 401: - return setupAuthentication(); - case 404: - if (!ascendPath()) - return false; - break; - default: // unknown error - fail - return false; - } - } - return false; -} - bool GerritParameters::equals(const GerritParameters &rhs) const { return server == rhs.server && ssh == rhs.ssh && curl == rhs.curl && https == rhs.https; @@ -319,11 +132,11 @@ void GerritParameters::saveQueries(QSettings *s) const void GerritParameters::fromSettings(const QSettings *s) { const QString rootKey = QLatin1String(settingsGroupC) + '/'; - server.host = s->value(rootKey + hostKeyC, defaultHostC).toString(); + server.host = s->value(rootKey + hostKeyC, GerritServer::defaultHost()).toString(); server.user.userName = s->value(rootKey + userKeyC, QString()).toString(); ssh = s->value(rootKey + sshKeyC, QString()).toString(); curl = s->value(rootKey + curlKeyC).toString(); - server.port = s->value(rootKey + portKeyC, QVariant(int(defaultPort))).toInt(); + server.port = ushort(s->value(rootKey + portKeyC, QVariant(GerritServer::defaultPort)).toInt()); portFlag = s->value(rootKey + portFlagKeyC, defaultPortFlag).toString(); savedQueries = s->value(rootKey + savedQueriesKeyC, QString()).toString() .split(','); diff --git a/src/plugins/git/gerrit/gerritparameters.h b/src/plugins/git/gerrit/gerritparameters.h index c95b83de497..7f08f92967e 100644 --- a/src/plugins/git/gerrit/gerritparameters.h +++ b/src/plugins/git/gerrit/gerritparameters.h @@ -25,6 +25,8 @@ #pragma once +#include "gerritserver.h" + #include QT_FORWARD_DECLARE_CLASS(QSettings) @@ -32,58 +34,6 @@ QT_FORWARD_DECLARE_CLASS(QSettings) namespace Gerrit { namespace Internal { -class GerritParameters; - -class GerritUser -{ -public: - bool isSameAs(const GerritUser &other) const; - - QString userName; - QString fullName; - QString email; -}; - -class GerritServer -{ -public: - enum HostType - { - Http, - Https, - Ssh - }; - - enum UrlType - { - DefaultUrl, - UrlWithHttpUser, - RestUrl - }; - - GerritServer(); - GerritServer(const QString &host, unsigned short port, const QString &userName, HostType type); - bool operator==(const GerritServer &other) const; - QString hostArgument() const; - QString url(UrlType urlType = DefaultUrl) const; - bool fillFromRemote(const QString &remote, const GerritParameters ¶meters); - int testConnection(); - static QStringList curlArguments(); - - QString host; - GerritUser user; - QString rootPath; // for http - unsigned short port = 0; - HostType type = Ssh; - bool authenticated = true; - -private: - QString curlBinary; - bool setupAuthentication(); - bool ascendPath(); - bool resolveRoot(); -}; - class GerritParameters { public: diff --git a/src/plugins/git/gerrit/gerritserver.cpp b/src/plugins/git/gerrit/gerritserver.cpp new file mode 100644 index 00000000000..addd917ee70 --- /dev/null +++ b/src/plugins/git/gerrit/gerritserver.cpp @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Orgad Shaneh . +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "authenticationdialog.h" +#include "gerritparameters.h" +#include "gerritserver.h" +#include "../gitplugin.h" +#include "../gitclient.h" + +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace Gerrit { +namespace Internal { + +static const char defaultHostC[] = "codereview.qt-project.org"; +static const char accountUrlC[] = "/accounts/self"; + +bool GerritUser::isSameAs(const GerritUser &other) const +{ + if (!userName.isEmpty() && !other.userName.isEmpty()) + return userName == other.userName; + if (!fullName.isEmpty() && !other.fullName.isEmpty()) + return fullName == other.fullName; + return false; +} + +GerritServer::GerritServer() + : host(defaultHost()) + , port(defaultPort) +{ +} + +GerritServer::GerritServer(const QString &host, unsigned short port, + const QString &userName, HostType type) + : host(host) + , port(port) + , type(type) +{ + user.userName = userName; +} + +bool GerritServer::operator==(const GerritServer &other) const +{ + if (port && other.port && port != other.port) + return false; + return host == other.host && user.isSameAs(other.user) && type == other.type; +} + +QString GerritServer::defaultHost() +{ + return QLatin1String(defaultHostC); +} + +QString GerritServer::hostArgument() const +{ + if (!authenticated || user.userName.isEmpty()) + return host; + return user.userName + '@' + host; +} + +QString GerritServer::url(UrlType urlType) const +{ + QString protocol; + switch (type) { + case Ssh: protocol = "ssh"; break; + case Http: protocol = "http"; break; + case Https: protocol = "https"; break; + } + QString res = protocol + "://"; + if (type == Ssh || urlType != DefaultUrl) + res += hostArgument(); + else + res += host; + if (port) + res += ':' + QString::number(port); + if (type != Ssh) { + res += rootPath; + if (authenticated && urlType == RestUrl) + res += "/a"; + } + return res; +} + +bool GerritServer::fillFromRemote(const QString &remote, const GerritParameters ¶meters) +{ + static const QRegularExpression remotePattern( + "^(?:(?[^:]+)://)?(?:(?[^@]+)@)?(?[^:/]+)" + "(?::(?\\d+))?:?(?/.*)$"); + + // Skip local remotes (refer to the root or relative path) + if (remote.isEmpty() || remote.startsWith('/') || remote.startsWith('.')) + return false; + // On Windows, local paths typically starts with : + if (HostOsInfo::isWindowsHost() && remote[1] == ':') + return false; + QRegularExpressionMatch match = remotePattern.match(remote); + if (!match.hasMatch()) + return false; + const QString protocol = match.captured("protocol"); + if (protocol == "https") + type = GerritServer::Https; + else if (protocol == "http") + type = GerritServer::Http; + else if (protocol.isEmpty() || protocol == "ssh") + type = GerritServer::Ssh; + else + return false; + const QString userName = match.captured("user"); + user.userName = userName.isEmpty() ? parameters.server.user.userName : userName; + host = match.captured("host"); + port = match.captured("port").toUShort(); + if (host.contains("github.com")) // Clearly not gerrit + return false; + if (type != GerritServer::Ssh) { + curlBinary = parameters.curl; + if (curlBinary.isEmpty() || !QFile::exists(curlBinary)) + return false; + rootPath = match.captured("path"); + // Strip the last part of the path, which is always the repo name + // The rest of the path needs to be inspected to find the root path + // (can be http://example.net/review) + ascendPath(); + if (!resolveRoot()) + return false; + } + return true; +} + +QStringList GerritServer::curlArguments() +{ + // -k - insecure - do not validate certificate + // -f - fail silently on server error + // -n - use credentials from ~/.netrc (or ~/_netrc on Windows) + // -sS - silent, except server error (no progress) + // --basic, --digest - try both authentication types + return {"-kfnsS", "--basic", "--digest"}; +} + +int GerritServer::testConnection() +{ + static Git::Internal::GitClient *const client = Git::Internal::GitPlugin::client(); + const QStringList arguments = curlArguments() << (url(RestUrl) + accountUrlC); + const SynchronousProcessResponse resp = client->vcsFullySynchronousExec( + QString(), FileName::fromString(curlBinary), arguments, + Core::ShellCommand::NoOutput); + if (resp.result == SynchronousProcessResponse::Finished) { + QString output = resp.stdOut(); + output.remove(0, output.indexOf('\n')); // Strip first line + QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8()); + if (!doc.isNull()) + user.fullName = doc.object().value("name").toString(); + return 200; + } + const QRegularExpression errorRegexp("returned error: (\\d+)"); + QRegularExpressionMatch match = errorRegexp.match(resp.stdErr()); + if (match.hasMatch()) + return match.captured(1).toInt(); + return 400; +} + +bool GerritServer::setupAuthentication() +{ + AuthenticationDialog dialog(this); + if (!dialog.exec()) + return false; + authenticated = dialog.isAuthenticated(); + return true; +} + +bool GerritServer::ascendPath() +{ + const int lastSlash = rootPath.lastIndexOf('/'); + if (lastSlash == -1) + return false; + rootPath = rootPath.left(lastSlash); + return true; +} + +bool GerritServer::resolveRoot() +{ + for (;;) { + switch (testConnection()) { + case 200: + return true; + case 401: + return setupAuthentication(); + case 404: + if (!ascendPath()) + return false; + break; + default: // unknown error - fail + return false; + } + } + return false; +} + +} // namespace Internal +} // namespace Gerrit diff --git a/src/plugins/git/gerrit/gerritserver.h b/src/plugins/git/gerrit/gerritserver.h new file mode 100644 index 00000000000..b24c0248514 --- /dev/null +++ b/src/plugins/git/gerrit/gerritserver.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Orgad Shaneh . +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Gerrit { +namespace Internal { + +class GerritParameters; + +class GerritUser +{ +public: + bool isSameAs(const GerritUser &other) const; + + QString userName; + QString fullName; + QString email; +}; + +class GerritServer +{ +public: + enum { defaultPort = 29418 }; + + enum HostType + { + Http, + Https, + Ssh + }; + + enum UrlType + { + DefaultUrl, + UrlWithHttpUser, + RestUrl + }; + + GerritServer(); + GerritServer(const QString &host, unsigned short port, const QString &userName, HostType type); + bool operator==(const GerritServer &other) const; + static QString defaultHost(); + QString hostArgument() const; + QString url(UrlType urlType = DefaultUrl) const; + bool fillFromRemote(const QString &remote, const GerritParameters ¶meters); + int testConnection(); + static QStringList curlArguments(); + + QString host; + GerritUser user; + QString rootPath; // for http + unsigned short port = 0; + HostType type = Ssh; + bool authenticated = true; + +private: + QString curlBinary; + bool setupAuthentication(); + bool ascendPath(); + bool resolveRoot(); +}; + +} // namespace Internal +} // namespace Gerrit diff --git a/src/plugins/git/git.qbs b/src/plugins/git/git.qbs index a8c32d387a5..8c87dab277d 100644 --- a/src/plugins/git/git.qbs +++ b/src/plugins/git/git.qbs @@ -93,6 +93,8 @@ QtcPlugin { "gerritparameters.h", "gerritplugin.cpp", "gerritplugin.h", + "gerritserver.cpp", + "gerritserver.h", "gerritpushdialog.cpp", "gerritpushdialog.h", "gerritpushdialog.ui",