Added new state and new job to check if /index.php/204 is being redirected.

Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
alex-z 2023-06-28 19:18:39 +02:00
parent 6c12a221ff
commit 71f1d1aad4
9 changed files with 113 additions and 9 deletions

View file

@ -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;

View file

@ -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();
}
@ -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:
@ -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";
@ -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();

View file

@ -65,6 +65,10 @@ public:
/// 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,

View file

@ -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 >= 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

View file

@ -90,6 +90,7 @@ public:
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 @@ signals:
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);

View file

@ -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)

View file

@ -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();

View file

@ -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,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)
{

View file

@ -364,6 +364,34 @@ private:
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