Quellcode durchsuchen

Merge pull request #5894 from nextcloud/backport/5848/stable-3.9

[stable-3.9] Added new state and new job to check if /index.php/204 is being redirected
Matthieu Gallien vor 2 Jahren
Ursprung
Commit
c953c557f9

+ 3 - 0
src/gui/accountsettings.cpp

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

+ 12 - 5
src/gui/accountstate.cpp

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

+ 4 - 0
src/gui/accountstate.h

@@ -64,6 +64,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,

+ 22 - 2
src/gui/connectionvalidator.cpp

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

+ 5 - 0
src/gui/connectionvalidator.h

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

+ 1 - 1
src/libsync/abstractnetworkjob.cpp

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

+ 1 - 1
src/libsync/abstractnetworkjob.h

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

+ 37 - 0
src/libsync/networkjobs.cpp

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

+ 28 - 0
src/libsync/networkjobs.h

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