1

I'm trying to write a simple parental control app for my university project, but I'm a newbie in browser addons. I want to use Chrome addon to send hosts viewed by the user in real-time to Qt app, which will analyze the user behavior. The problem is, that sometimes chrome's sending a correct host, another time it's sending trash with an empty string or enormously long message, which my Qt app filter. But these 'wrong' messages are sent in an endless loop, and to make it working again, I have to restart extension or chrome or even whole PC.

Chrome addon manifest:

{
    "name": "AM Chrome addon",
    "version": "0.7",
    "description": "Get your activity supervised!",
    "background": {
        "scripts": [
            "background.js"
        ],
        "persistent": false
    },
    "permissions": [
        "tabs",
        "nativeMessaging",
        "background"
    ],
    "manifest_version": 2
}

Addon background.js file:

var current = undefined;
var port = null;
tryConnectagain();

function tryConnectagain() {
    port = chrome.runtime.connectNative('<Native host app-name>');
    port.onDisconnect.addListener(onDisconnect);
}

function onDisconnect() {
    port = null;
        console.log("last error: ", chrome.runtime.lastError.message);
        setTimeout(tryConnectagain, 1000);
}

function sendMessageToNativeApp(message) {
    if (port != null) port.postMessage({ message: message });
}

function newUrl(u) {
    if (u != undefined && !u.includes(current) && !u.includes("chrome-extension://") && u.includes('.')) {
        var u = new URL(u);
        var domain = u.hostname.replace("www.", "");
        if (domain != current) {
            current = domain;
            sendMessageToNativeApp(current);
            console.log(current);
        }
    }
    else if (current != "NotURL") {
        current = "NotURL";
        sendMessageToNativeApp(current);
        console.log(current);
    }
}

// Here I'm trying to intercept all URL change situations

chrome.tabs.onActivated.addListener(function (activeInfo) {
    chrome.tabs.get(activeInfo.tabId, function (tab) {
        if (tab.active && tab.highlighted) newUrl(tab.url);
    });
});

chrome.tabs.onAttached.addListener(function (tabId, attachInfo) {
    chrome.tabs.get(tabId, function (tab) {
        if (tab.active && tab.highlighted) newUrl(tab.url);
    });
});

chrome.tabs.onReplaced.addListener(function (addedTabId, removedTabId) {
    chrome.tabs.get(addedTabId, function (tab) {
        if (tab.active && tab.highlighted) newUrl(tab.url);
    });
});

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    if (changeInfo.url && tab.active && tab.highlighted) newUrl(changeInfo.url);
});

chrome.windows.onFocusChanged.addListener(function (windowId) {
    if (windowId > -1) {
        var getInfo = { populate: true, windowTypes: ['normal'] };
        chrome.windows.getLastFocused(getInfo, function (window) {
            for (var t = 0; t < window.tabs.length; t++) {
                if (window.tabs[t].active && window.tabs[t].highlighted) {
                    newUrl(window.tabs[t].url);
                    break;
                }
            }
        })
    }
});

Native host app manifest:

{
  "name": "<Native host app-name>",
  "description": "Hostname Identifier",
  "path": "<Hostname app Path>",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://<extension-ID>/"
  ]
}

And a fragment of c++ qt code that receive data from addon: addonmessagereceiver.h:

#ifndef ADDONMESSAGERECEIVER_H
#define ADDONMESSAGERECEIVER_H

#include <qthread.h>
#include <QJsonDocument>
#include <QJsonObject>

#include <iostream>
#include <string>

class AddonMessageReceiver : public QThread
{
    Q_OBJECT
public:
    void run();
signals:
    void UpdateMessage(const QString &);
};

#endif // ADDONMESSAGERECEIVER_H

addonmessagereceiver.cpp:

#include "addonmessagereceiver.h"
#include <qdebug.h>
using namespace std;

void AddonMessageReceiver::run()
{
    do{
        char nextMessageLen[4];
        cin.read(nextMessageLen, 4);
        unsigned long int messageLength = *reinterpret_cast<unsigned long int *>(nextMessageLen);
        qDebug() << messageLength << static_cast<int>(nextMessageLen[0]) << static_cast<int>(nextMessageLen[1]) << static_cast<int>(nextMessageLen[2]) << static_cast<int>(nextMessageLen[3]);
        if(messageLength<1024 && messageLength>1)
        {
            char *incomingMessage = new char[messageLength+1];
            memset(incomingMessage,'\0',messageLength+1);
            cin.read(incomingMessage, messageLength);
            QString message = QString::fromLatin1(incomingMessage);
            delete[] incomingMessage;
            qDebug() << messageLength << message;
            if(message.length()>5)
            {
                QJsonDocument json = QJsonDocument::fromJson(message.toLatin1());
                QJsonObject obj = json.object();
                QString host = obj.value("message").toString();
                emit UpdateMessage(host);
            }
        }
        QThread::msleep(100);
    }while(true);
}

Example of qDebug wrong nextMessageLen in loop:

And an example of good input that turns into wrong in a loop:

Can you please tell me what is going on with that extension or chrome, or what I f... up with native app? Thank you for your answer.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
km2442
  • 779
  • 2
  • 11
  • 31
  • It's either a bug in Chrome (try older portable versions) or something else in your app writes into the stream or somehow messes up the stream's contents. – wOxxOm Dec 14 '19 at 16:31
  • wOxxOm, Tried portable chrome 70.0... same behavior, also tried this example https://medium.com/@svanas/chrome-native-messaging-example-5cf8f91cdce6, which works great... So I think it's something with my code... – km2442 Dec 14 '19 at 17:10
  • @km2442 what is your OS? – eyllanesc Dec 14 '19 at 23:50
  • @eyllanesc, Windows 10 1903 with Chrome 79 – km2442 Dec 15 '19 at 11:25
  • Previously I implemented the Native Messaging protocol for Qt but only for linux, now I have added some modifications for windows but I am not sure if it will work, you could try it: https://github.com/eyllanesc/stackoverflow/tree/master/questions/59336957. I also recommend checking that you don't have the errors indicated in https://developer.chrome.com/apps/nativeMessaging#native-messaging-debugging – eyllanesc Dec 15 '19 at 17:31
  • After adding all listeners from my addon, and quick test, your host seems working slightly better than mine. No hangs, or unexpected behaviour. I will do several hours testing and will give you know tomorrow. – km2442 Dec 15 '19 at 18:42
  • @km2442 Have you done the test with just my code without implementing it in your code? If so, did it work for you? Also use `@username` to get your comments notified. – eyllanesc Dec 15 '19 at 22:13
  • @eyllanesc, Sorry for not tagging you. I'm trying your code with all listeners in addon from my code. For now, your host application is working better than mine, without hanging so far, but I will test if it works in all cases and let you know tomorrow. Have a nice day. – km2442 Dec 15 '19 at 22:20

1 Answers1

2

Based on this answer I have built a class that monitors "stdin", get the text and decode it using QFile:

nativemessenger.h

#ifndef NATIVEMESSENGER_H
#define NATIVEMESSENGER_H

#include <QObject>
#include <QFile>

class NativeMessenger : public QObject
{
    Q_OBJECT
public:
    explicit NativeMessenger(QObject *parent = nullptr);
public Q_SLOTS:
    void sendMessage(const QByteArray & message);
Q_SIGNALS:
    void messageChanged(const QByteArray & message);
private Q_SLOTS:
    void readyRead();
private:
    QFile m_qin;
    QFile m_qout;
};

#endif // NATIVEMESSENGER_H

nativemessenger.cpp

#include "nativemessenger.h"

#include <QCoreApplication>
#ifdef Q_OS_WIN
#include <QWinEventNotifier>
#include <windows.h>
#else
#include <QSocketNotifier>
#endif
NativeMessenger::NativeMessenger(QObject *parent) : QObject(parent)
{
#ifdef Q_OS_WIN
    // https://developer.chrome.com/apps/nativeMessaging#native-messaging-debugging
    _setmode(_fileno(stdin), _O_BINARY);
    _setmode(_fileno(stdout), _O_BINARY);
#endif

    m_qin.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered);
    m_qout.open(stdout, QIODevice::WriteOnly);

#ifdef Q_OS_WIN
    QWinEventNotifier *m_notifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE));
    connect(m_notifier, &QWinEventNotifier::activated, this, &NativeMessenger::readyRead);
#else
    QSocketNotifier *m_notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this);
    connect(m_notifier, &QSocketNotifier::activated, this, &NativeMessenger::readyRead);
#endif
}

void NativeMessenger::sendMessage(const QByteArray &message){
    quint32 len = message.length();
    m_qout.write(reinterpret_cast<char *>(&len), sizeof(len));
    m_qout.write(message);
    m_qout.flush();
}

void NativeMessenger::readyRead(){
    m_qin.startTransaction();
    quint32 length = 0;
    qint64 rc = m_qin.read(reinterpret_cast<char *>(&length), sizeof(quint32));
    if (rc == -1) {
        m_qin.rollbackTransaction();
        return;
    }
    QByteArray message = m_qin.read(length);
    if (message.length() != int(length)) {
        m_qin.rollbackTransaction();
        return;
    }
    if (message.isEmpty()) {
        m_qin.rollbackTransaction();
        return;
    }
    m_qin.commitTransaction();
    Q_EMIT messageChanged(message);
}

main.cpp

#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>

#include <QDebug>

#include "nativemessenger.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    NativeMessenger listener;
    QObject::connect(&listener, &NativeMessenger::messageChanged,
                     [&listener]
                     (const QByteArray & message){
        // decode message 
        QJsonParseError error;
        QJsonDocument json = QJsonDocument::fromJson(message, &error);
        if(error.error != QJsonParseError::NoError){
            qDebug() << error.errorString();
        }
        // build response
        QJsonObject object
        {
            {"data", json.object()},
            {"name", QCoreApplication::applicationName()}
        };
        QByteArray response = QJsonDocument(object).toJson(QJsonDocument::Compact);
        // send response
        listener.sendMessage(response);
    });
    return a.exec();
}

A complete example can be found here.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Great example, thank you. However i'm having an issue with QWinEvent::activated continuously firing which results in the rest of the program hanging. Would you be able to test if it's the same in your environment? `connect(m_notifier, &QWinEventNotifier::activated, this, []() { qDebug("QWinEventNotifier::activated fired"); });` https://i.imgur.com/WfDn4dS.gif – Alan Kałuża Aug 02 '20 at 03:35
  • 1
    @AlanKałuża I have not tested it on windows, I asked other users and they pointed out that it worked. Try with `setEnabled()` https://doc.qt.io/qt-5/qwineventnotifier.html#setEnabled – eyllanesc Aug 02 '20 at 03:53
  • 1
    @AlanKałuża You could also use threads as they do in this repo: https://github.com/Skycoder42/QConsole/blob/master/readthread_win.cpp – eyllanesc Aug 02 '20 at 04:01
  • Was good tip: by using setEnabled() it unclogged the loop - but, still it fires non-stop and causes high CPU usage. I wonder if some people just simply don't realize that. I have a feeling the issue isn't your code, but something with API. In the end I still used your code: i moved readyRead() to an infinite blocking loop in it's own worker thread. – Alan Kałuża Aug 02 '20 at 08:14