Nulik Nulik - 13 days ago 6
C++ Question

How to make QNetworkReply to return custom data?

I am going to use QNetworkAccessManager to make requests to HTTP server by my mobile app to the server. The question is, how do you link custom data to each request ? I tried to subclass QNetworkReply but I found out that I have to implement virtual methods

close()
and
isSequential()
but I don't know what those should return so I am afraid I am going to break network request functionality.

For example, when my app does the log in procedure, it has to store the email address of the account:

class MyApp : public QObject
{
Q_OBJECT

private:
QNetworkRequest request;
QNetworkReply *reply;
QNetworkAccessManager *manager;

...

}

void MyApp::do_log_in(QString email, QString password) {
QString s;

someobject.email=email; // <-- I have to store email address before sending request to server, but where do I store it?
s.append("http://myapp.com/do-auth.php?email=");
s.append(QUrl::toPercentEncoding(email));
s.append("&password=");
s.append(QUrl::toPercentEncoding(password));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(login_finished(QNetworkReply*)));
request.setUrl(QUrl(s));
manager->get(request);

}

void MyApp::login_finished(QNetworkReply *rep) {
DepservReply *reply;
QString email;
....
email= ...... // <-- I need to get the email address from QNetworkReply object somehow
///my code here handling server reply
....
}


So, how do I implement storage and retrieval of email in my case, what classes should I subclass and what methods should I re-implement ?

Answer

You can leverage the dynamic property system available in each QObject and hold the data in the reply:

// https://github.com/KubaO/stackoverflown/tree/master/questions/network-reply-tracking-40707025
#include <QtNetwork>

class MyCtl : public QObject
{
    Q_OBJECT
    QNetworkAccessManager manager{this};
    // ...
    void reply_finished(QNetworkReply *reply);
public:
    MyCtl(QObject *parent = nullptr);
    void do_log_in(const QString &email, const QString &password);
};

static const char kAuthGetSalt[] = "req_auth-get-salt";
static const char kDoAuth[] = "req_do-auth";
static const char kEmail[] = "req_email";
static const char kPassword[] = "req_password";

static const auto authGetSaltUrl = QStringLiteral("https://myapp.com/auth-get-salt.php?email=%1");
static const auto doAuthUrl = QStringLiteral("https://myapp.com/do-auth.php?email=%1&passwordHash=%2");

MyCtl::MyCtl(QObject *parent) : QObject{parent}
{
    connect(&manager, &QNetworkAccessManager::finished, this, &MyCtl::reply_finished);
}

void MyCtl::do_log_in(const QString &email, const QString &password) {
    auto url = authGetSaltUrl.arg(email);
    auto reply = manager.get(QNetworkRequest{url});
    reply->setProperty(kAuthGetSalt, true);
    reply->setProperty(kEmail, email);
    reply->setProperty(kPassword, password);
}

void MyCtl::reply_finished(QNetworkReply *reply) {
    if (!reply->property(kAuthGetSalt).isNull()) {
        reply->deleteLater(); // let's not leak the reply
        if (reply->error() == QNetworkReply::NoError) {
            auto salt = reply->readAll();
            auto email = reply->property(kEmail).toString();
            auto password = reply->property(kPassword).toString();
            Q_ASSERT(!password.isEmpty() && !email.isEmpty());
            QCryptographicHash hasher{QCryptographicHash::Sha1};
            hasher.addData(salt); // the server must hash the same way
            hasher.addData("----");
            hasher.addData(password.toUtf8());
            auto hash = hasher.result().toBase64(QByteArray::Base64UrlEncoding);
            auto url = doAuthUrl.arg(email).arg(QString::fromLatin1(hash));

            auto reply = manager.get(QNetworkRequest{url});
            reply->setProperty(kDoAuth, true);
            reply->setProperty(kEmail, email);
        }
    }
    else if (!reply->property(kDoAuth).isNull()) {
        if (reply->error() == QNetworkReply::NoError) {
            auto email = reply->property(kEmail).toString();
            // ...
        }
    }
}

Use a constant for a property name to avoid typos by letting the compiler check that you're using a valid identifier.

The example above rectifies the following critical safety issues in your code:

  1. Sending security credentials over a clear connection: use https://, not http://.

  2. Sending a password in cleartext: instead, send a salted hash of it. Your server should generate a random salt for each account when the accounts are created. Existing accounts can be left unsalted, but they should acquire a salt as soon as the user changes the password.

Also note that a QString to QUrl conversion will automatically percent-encode the string, so doing it explicitly is unnecessary.

Comments