Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable-3.9] Added new state and new job to check if /index.php/204 is being redirected #5894

Merged
merged 1 commit into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,9 @@ void AccountSettings::slotAccountStateChanged()
case AccountState::MaintenanceMode:
showConnectionLabel(tr("Server %1 is currently in maintenance mode.").arg(server));
break;
case AccountState::RedirectDetected:
showConnectionLabel(tr("Server %1 is currently being redirected, or your connection is behind a captive portal.").arg(server));
break;
case AccountState::SignedOut:
showConnectionLabel(tr("Signed out from %1.").arg(serverWithUser));
break;
Expand Down
17 changes: 12 additions & 5 deletions src/gui/accountstate.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/gui/accountstate.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/gui/accountstate.cpp

File src/gui/accountstate.cpp (lines 350): Code does not conform to Custom style guidelines.
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -118,10 +118,10 @@
// If we stop being voluntarily signed-out, try to connect and
// auth right now!
checkConnectivity();
} else if (_state == ServiceUnavailable) {
// Check if we are actually down for maintenance.
} else if (_state == ServiceUnavailable || _state == RedirectDetected) {
// Check if we are actually down for maintenance/in a redirect state (captive portal?).
// To do this we must clear the connection validator that just
// produced the 503. It's finished anyway and will delete itself.
// produced the 503/302. It's finished anyway and will delete itself.
_connectionValidator.clear();
checkConnectivity();
}
Expand Down Expand Up @@ -150,6 +150,8 @@
return tr("Service unavailable");
case MaintenanceMode:
return tr("Maintenance mode");
case RedirectDetected:
return tr("Redirect detected");
case NetworkError:
return tr("Network error");
case ConfigurationError:
Expand Down Expand Up @@ -342,10 +344,11 @@

_lastConnectionValidatorStatus = status;

// Come online gradually from 503 or maintenance mode
// Come online gradually from 503, captive portal(redirection) 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";
Expand Down Expand Up @@ -411,6 +414,10 @@
_timeSinceMaintenanceOver.invalidate();
setState(MaintenanceMode);
break;
case ConnectionValidator::StatusRedirect:
_timeSinceMaintenanceOver.invalidate();
setState(RedirectDetected);
break;
case ConnectionValidator::Timeout:
setState(NetworkError);
updateRetryCount();
Expand Down
4 changes: 4 additions & 0 deletions src/gui/accountstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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
RedirectDetected,

/// Similar to ServiceUnavailable, but we know the server is down
/// for maintenance
MaintenanceMode,
Expand Down
24 changes: 22 additions & 2 deletions src/gui/connectionvalidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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);
Expand All @@ -96,6 +107,15 @@ void ConnectionValidator::slotCheckServerAndAuth()
checkJob->start();
}

void ConnectionValidator::slotCheckRedirectCostFreeUrlFinished(int statusCode)
{
if (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
Expand Down
5 changes: 5 additions & 0 deletions src/gui/connectionvalidator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/abstractnetworkjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Q_LOGGING_CATEGORY(lcNetworkJob, "nextcloud.sync.networkjob", QtInfoMsg)
// If not set, it is overwritten by the Application constructor with the value from the config
int AbstractNetworkJob::httpTimeout = qEnvironmentVariableIntValue("OWNCLOUD_TIMEOUT");

AbstractNetworkJob::AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent)
AbstractNetworkJob::AbstractNetworkJob(const AccountPtr &account, const QString &path, QObject *parent)
: QObject(parent)
, _account(account)
, _reply(nullptr)
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/abstractnetworkjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class OWNCLOUDSYNC_EXPORT AbstractNetworkJob : public QObject
{
Q_OBJECT
public:
explicit AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent = nullptr);
explicit AbstractNetworkJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
~AbstractNetworkJob() override;

virtual void start();
Expand Down
37 changes: 37 additions & 0 deletions src/libsync/networkjobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -554,6 +555,42 @@ bool CheckServerJob::finished()

/*********************************************************************************************/

CheckRedirectCostFreeUrlJob::CheckRedirectCostFreeUrlJob(const AccountPtr &account, QObject *parent)
: AbstractNetworkJob(account, QLatin1String(statusphpC), parent)
{
setIgnoreCredentialFailure(true);
}

void CheckRedirectCostFreeUrlJob::start()
{
setFollowRedirects(false);
sendRequest("GET", Utility::concatUrlPath(account()->url(), QStringLiteral("/index.php/204")));
AbstractNetworkJob::start();
}

void CheckRedirectCostFreeUrlJob::onTimedOut()
{
qCDebug(lcCheckRedirectCostFreeUrlJob) << "TIMEOUT";
if (reply() && reply()->isRunning()) {
emit timeout(reply()->url());
} else if (!reply()) {
qCDebug(lcCheckRedirectCostFreeUrlJob) << "Timeout without a reply?";
}
AbstractNetworkJob::onTimedOut();
}

bool CheckRedirectCostFreeUrlJob::finished()
{
const auto statusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (statusCode >= 301 && statusCode <= 307) {
const auto redirectionTarget = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
qCDebug(lcCheckRedirectCostFreeUrlJob) << "Redirecting cost-free URL" << reply()->url() << " to" << redirectionTarget;
}
emit jobFinished(statusCode);
return true;
}
/*********************************************************************************************/

PropfindJob::PropfindJob(AccountPtr account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
Expand Down
28 changes: 28 additions & 0 deletions src/libsync/networkjobs.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/libsync/networkjobs.h

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/networkjobs.h

File src/libsync/networkjobs.h (lines 379, 380, 381, 393): Code does not conform to Custom style guidelines.
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
*
Expand Down Expand Up @@ -364,6 +364,34 @@
int _permanentRedirects = 0;
};

/**
* @brief The CheckRedirectCostFreeUrlJob class
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT CheckRedirectCostFreeUrlJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit CheckRedirectCostFreeUrlJob(const 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
Expand Down