Skip to content

Commit

Permalink
Added new state and new job to check if /index.php/204 is being redir…
Browse files Browse the repository at this point in the history
…ected.

Signed-off-by: alex-z <[email protected]>
  • Loading branch information
allexzander committed Jun 28, 2023
1 parent 0a65df1 commit 0cc78b6
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 3 deletions.
12 changes: 11 additions & 1 deletion src/gui/accountstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/gui/accountstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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 != 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
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
33 changes: 33 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,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)
{
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(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 0cc78b6

Please sign in to comment.