diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 6bcf4e4cec70..e3db6d8381be 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -124,6 +124,11 @@ void AccountState::setState(State state) // produced the 503. It's finished anyway and will delete itself. _connectionValidator.clear(); checkConnectivity(); + } else if (_state == Redirect) { + // Check if we are actually down for maintenance. + // To do this we must clear the connection validator that just + _connectionValidator.clear(); + checkConnectivity(); } if (oldState == Connected || _state == Connected) { emit isConnectedChanged(); @@ -345,7 +350,8 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta // Come online gradually from 503 or maintenance mode if (status == ConnectionValidator::Connected && (_connectionStatus == ConnectionValidator::ServiceUnavailable - || _connectionStatus == ConnectionValidator::MaintenanceMode)) { + || _connectionStatus == ConnectionValidator::MaintenanceMode) + || _connectionStatus == ConnectionValidator::StatusRedirect) { if (!_timeSinceMaintenanceOver.isValid()) { qCInfo(lcAccountState) << "AccountState reconnection: delaying for" << _maintenanceToConnectedDelay << "ms"; @@ -411,6 +417,10 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta _timeSinceMaintenanceOver.invalidate(); setState(MaintenanceMode); break; + case ConnectionValidator::StatusRedirect: + _timeSinceMaintenanceOver.invalidate(); + setState(Redirect); + break; case ConnectionValidator::Timeout: setState(NetworkError); updateRetryCount(); diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 8ca27b38c3d9..66531980a8c9 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -65,6 +65,10 @@ class AccountState : public QObject, public QSharedData /// don't bother the user too much and try again. ServiceUnavailable, + /// Connection is being redirected (likely a captive portal is in effect) + /// Do not proceed with connecting and check back later + Redirect, + /// Similar to ServiceUnavailable, but we know the server is down /// for maintenance MaintenanceMode, diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 515c47f8a974..7d3fe01050eb 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -63,7 +63,7 @@ void ConnectionValidator::checkServerAndAuth() // We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings) _account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); // use a queued invocation so we're as asynchronous as with the other code path - QMetaObject::invokeMethod(this, "slotCheckServerAndAuth", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "slotCheckRedirectCostFreeUrl", Qt::QueuedConnection); } } @@ -81,10 +81,21 @@ void ConnectionValidator::systemProxyLookupDone(const QNetworkProxy &proxy) } _account->networkAccessManager()->setProxy(proxy); - slotCheckServerAndAuth(); + slotCheckRedirectCostFreeUrl(); } // The actual check + +void ConnectionValidator::slotCheckRedirectCostFreeUrl() +{ + const auto checkJob = new CheckRedirectCostFreeUrlJob(_account, this); + checkJob->setTimeout(timeoutToUseMsec); + checkJob->setIgnoreCredentialFailure(true); + connect(checkJob, &CheckRedirectCostFreeUrlJob::timeout, this, &ConnectionValidator::slotJobTimeout); + connect(checkJob, &CheckRedirectCostFreeUrlJob::jobFinished, this, &ConnectionValidator::slotCheckRedirectCostFreeUrlFinished); + checkJob->start(); +} + void ConnectionValidator::slotCheckServerAndAuth() { auto *checkJob = new CheckServerJob(_account, this); @@ -96,6 +107,15 @@ void ConnectionValidator::slotCheckServerAndAuth() checkJob->start(); } +void ConnectionValidator::slotCheckRedirectCostFreeUrlFinished(int statusCode) +{ + if (statusCode != 204 && statusCode >= 301 && statusCode <= 307) { + reportResult(StatusRedirect); + return; + } + slotCheckServerAndAuth(); +} + void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &info) { // Newer servers don't disclose any version in status.php anymore diff --git a/src/gui/connectionvalidator.h b/src/gui/connectionvalidator.h index 38b685c972e9..bd955d4128e6 100644 --- a/src/gui/connectionvalidator.h +++ b/src/gui/connectionvalidator.h @@ -90,6 +90,7 @@ class ConnectionValidator : public QObject CredentialsWrong, // AuthenticationRequiredError SslError, // SSL handshake error, certificate rejected by user? StatusNotFound, // Error retrieving status.php + StatusRedirect, // 204 URL received one of redirect HTTP codes (301-307), possibly a captive portal ServiceUnavailable, // 503 on authed request MaintenanceMode, // maintenance enabled in status.php Timeout // actually also used for other errors on the authed request @@ -111,8 +112,12 @@ public slots: void connectionResult(OCC::ConnectionValidator::Status status, const QStringList &errors); protected slots: + void slotCheckRedirectCostFreeUrl(); + void slotCheckServerAndAuth(); + void slotCheckRedirectCostFreeUrlFinished(int statusCode); + void slotStatusFound(const QUrl &url, const QJsonObject &info); void slotNoStatusFound(QNetworkReply *reply); void slotJobTimeout(const QUrl &url); diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index cb6189cadc6b..cf352398bf15 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -50,6 +50,7 @@ namespace OCC { Q_LOGGING_CATEGORY(lcEtagJob, "nextcloud.sync.networkjob.etag", QtInfoMsg) Q_LOGGING_CATEGORY(lcLsColJob, "nextcloud.sync.networkjob.lscol", QtInfoMsg) Q_LOGGING_CATEGORY(lcCheckServerJob, "nextcloud.sync.networkjob.checkserver", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCheckRedirectCostFreeUrlJob, "nextcloud.sync.networkjob.checkredirectcostfreeurl", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropfindJob, "nextcloud.sync.networkjob.propfind", QtInfoMsg) Q_LOGGING_CATEGORY(lcAvatarJob, "nextcloud.sync.networkjob.avatar", QtInfoMsg) Q_LOGGING_CATEGORY(lcMkColJob, "nextcloud.sync.networkjob.mkcol", QtInfoMsg) @@ -554,6 +555,38 @@ bool CheckServerJob::finished() /*********************************************************************************************/ +CheckRedirectCostFreeUrlJob::CheckRedirectCostFreeUrlJob(AccountPtr account, QObject *parent) + : AbstractNetworkJob(account, QLatin1String(statusphpC), parent) +{ + setIgnoreCredentialFailure(true); +} + +void CheckRedirectCostFreeUrlJob::start() +{ + setFollowRedirects(false); + const auto url = account()->url(); + sendRequest("GET", Utility::concatUrlPath(url, QStringLiteral("/index.php/204"))); + AbstractNetworkJob::start(); +} + +void CheckRedirectCostFreeUrlJob::onTimedOut() +{ + qCWarning(lcCheckRedirectCostFreeUrlJob) << "TIMEOUT"; + if (reply() && reply()->isRunning()) { + emit timeout(reply()->url()); + } else if (!reply()) { + qCWarning(lcCheckRedirectCostFreeUrlJob) << "Timeout even there was no reply?"; + } + deleteLater(); +} + +bool CheckRedirectCostFreeUrlJob::finished() +{ + emit jobFinished(reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + return true; +} +/*********************************************************************************************/ + PropfindJob::PropfindJob(AccountPtr account, const QString &path, QObject *parent) : AbstractNetworkJob(account, path, parent) { diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index 478852d7c9e2..916ae43f583f 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -364,6 +364,34 @@ private slots: int _permanentRedirects = 0; }; +/** + * @brief The CheckRedirectCostFreeUrlJob class + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT CheckRedirectCostFreeUrlJob : public AbstractNetworkJob +{ + Q_OBJECT +public: + explicit CheckRedirectCostFreeUrlJob(AccountPtr account, QObject *parent = nullptr); + void start() override; + +signals: + /** + * a check is finished + * \a statusCode cost-free URL GET HTTP response code + */ + void jobFinished(int statusCode); + /** A timeout occurred. + * + * \a url The specific url where the timeout happened. + */ + void timeout(const QUrl &url); + +private: + bool finished() override; + void onTimedOut() override; +}; + /** * @brief The RequestEtagJob class