1
votes

I make some https requests directly from my qml views, for instance for image sources. As I have a self signed certificate server side, I need to tell qt to ignore some ssl errors (I control both the server and the client applications, so this shouldn't really be a problem).

I've made a QQmlNetworkAccessManagerFactory to create NAMs, where I connect to the sslErrors signal.

UltraQmlAccessManagerFactory.h:

#ifndef FACKFACKTORy_H
#define FACKFACKTORy_H


#include <QQmlNetworkAccessManagerFactory>
#include <QObject>
#include <QNetworkReply>
#include <QList>
#include <QSslError>
#include <QNetworkAccessManager>
#include <QDebug>
#include <QSslCertificate>

class UltraQmlNetworkAccessManagerFactory : public QObject,
                                            public QQmlNetworkAccessManagerFactory {
  Q_OBJECT
private:
  QNetworkAccessManager* nam;
  QList<QSslError> expectedSslErrors;
public:
  explicit UltraQmlNetworkAccessManagerFactory();
  ~UltraQmlNetworkAccessManagerFactory();
  virtual QNetworkAccessManager* create(QObject* parent);

public slots:
  void onIgnoreSslErrors(QNetworkReply* reply, QList<QSslError> errors);
};

#endif

UltraQmlNetworkAccessManagerFactory.cpp:

#include "UltraQmlNetworkAccessManagerFactory.h"

UltraQmlNetworkAccessManagerFactory::UltraQmlNetworkAccessManagerFactory() {

}

UltraQmlNetworkAccessManagerFactory::~UltraQmlNetworkAccessManagerFactory() {
  delete nam;
}

QNetworkAccessManager* UltraQmlNetworkAccessManagerFactory::create(QObject* parent) {
  QNetworkAccessManager* nam = new QNetworkAccessManager(parent);
  QObject::connect(nam, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),
                   this, SLOT(onIgnoreSslErrors(QNetworkReply*,QList<QSslError>))
                   );
  return nam;
}


void UltraQmlNetworkAccessManagerFactory::onIgnoreSslErrors(QNetworkReply *reply, QList<QSslError> errors) {
  for (int i = 0; i < errors.size(); i++) {
    qDebug() << "e: " << errors.at(i) << endl;

  }
  reply->ignoreSslErrors(errors);
}

There is also some glue in main.cpp that sets this factory to be used, I doubt that part is a source of errors as the qDebug prints are visible in the output.

As can be seen in the .cpp file in the function/slot onIgnoreSslErrors, I try to ignore every error (as a test) that I receive, but in the output I do not get the expected results.

Output

e:  "The certificate is self-signed, and untrusted" 

qrc:/qml/file/ImageView.qml:16:5: QML Image: SSL handshake failed

I have successfully made QNetworkRequests from C++ directly with a QSslConfiguration, specifying TLSV1_0 and a certificate. As I have a suspicion that the handshake fails because one side expects SSL and the other TLS I have also tried to set the QSslConfiguration on the QNetworkRequest object throgh reply->request(); This, however, changes nothing.

1
Try set the default SSL configuration, by using QSslConfiguration::setDefaultConfiguration(). - Meefte

1 Answers

1
votes

(This is a very old one but as I stumbled over this recently and haven't found the answer, I think it's still worth answering)

You don't show the place where you actually setup the factory object but it is extremely likely that it's the factory object does not belong to the same (actually, any) thread used when its create() method is called. Here's an excerpt from the Qt documentation on the class:

the developer should be careful if the signals of the object to be returned from create() are connected to the slots of an object that may be created in a different thread

It further mentions authenticationRequired() signal but sslErrors() acts in the same way: both signals, among a few others, need either a direct or a blocking queued connection so that by the time of returning to the place of emitting the signal the network reply object has already been configured by the slot.

What happens in your case is (very likely) the following (TL;DR: your slot is called asynchronously by a queued connection because it lives in a different thread, while sslErrors() requires a synchronous change to the running network reply object; despite the order of log lines, the request fails first and ignoreSslErrors() is called later):

  1. The factory object is created, and the QML engine configured, in the main thread.
  2. QML engine spawns a few threads to perform backend stuff, notably network requests for URLs (I'm making the assumption here that your ImageView.qml has an Image component). To perform the network request, these threads call UltraQmlNetworkAccessManagerFactory::create().
  3. create() produces a NAM object and sets up a connection on it. The parent here is either the QQmlEngine object or (specifically for image requests) the backend thread object, as you can see e.g. here. Therefore, this NAM object belongs to the backend thread.
  4. connect() uses Qt::AutoConnection type by default, which, since the threads of the factory and of the NAM object are different, corresponds to Qt::QueuedConnection. As a sidenote, the threads are checked at the time of signal invocation.
  5. Eventually a QNetworkAccessManager::sslErrors() signal is emitted. Since this is a queued connection, the only thing that immediately happens is placing an invocation of onIgnoreSslErrors() on the event queue for the main thread.
  6. If you're extremely lucky, you may have a context switch to the main thread right after that - but there's literally nothing to ensure that so it's much more likely that control returns to the site where QNetworkAccessManager::sslErrors() was emitted. And since ignoreSslErrors() wasn't called, the request fails the handshake. The backend thread posts relevant data from the (failed) QNetworkReply object back to the main thread - see the postReply call in the middle of the method (or it may do it a bit later - that doesn't matter anymore).
  7. Once the context switches to the main thread ignoreSslErrors() is executed - alas, it's already too late, as the network reply has very likely finished with failure already; but that first log line comes out now.
  8. The main thread goes on through the event loop and finds the QQuickPixmapReply::Event object with the failure data. After some unroll of the calls and signals the failed image ends up in QQuickImageBase::requestFinished() that prints the second log line for you .

As for the fix, it's tempting to just specify Qt::BlockingQueuedConnection as the fifth parameter to connect(). Unfortunately, this will deadlock if a request is ever made from QQmlEngine that runs in the main thread - and uses a NAM instance created by this factory to, e.g., request QML components over the network. Best I could come out with so far is an equivalent of

connect(nam, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),
        this, SLOT(onIgnoreSslErrors(QNetworkReply*,QList<QSslError>)),
        currentThread() == this->thread() ? Qt::DirectConnection
                                          : Qt::BlockingQueuedConnection
        );