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;
// 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();
}
return tr("Service unavailable");
case MaintenanceMode:
return tr("Maintenance mode");
+ case RedirectDetected:
+ return tr("Redirect detected");
case NetworkError:
return tr("Network error");
case ConfigurationError:
_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";
_timeSinceMaintenanceOver.invalidate();
setState(MaintenanceMode);
break;
+ case ConnectionValidator::StatusRedirect:
+ _timeSinceMaintenanceOver.invalidate();
+ setState(RedirectDetected);
+ break;
case ConnectionValidator::Timeout:
setState(NetworkError);
updateRetryCount();
/// 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,
// 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);
}
}
}
_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);
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
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
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);
// 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)
{
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();
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)
/*********************************************************************************************/
+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)
{
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