Skip to content

Commit

Permalink
Merge pull request #5894 from nextcloud/backport/5848/stable-3.9
Browse files Browse the repository at this point in the history
[stable-3.9] Added new state and new job to check if /index.php/204 is being redirected
  • Loading branch information
mgallien committed Jul 17, 2023
2 parents f295689 + e4816de commit c953c55
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 9 deletions.
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
Expand Up @@ -118,10 +118,10 @@ void AccountState::setState(State state)
// 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 @@ QString AccountState::stateString(State state)
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 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta

_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 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
_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
Expand Up @@ -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(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

0 comments on commit c953c55

Please sign in to comment.