Implementing autofill for onlyfans login page with QWebEngine based app - c++

I'm trying to implement autofill functionality in a Qt Webengine based app.
My approach is based off Viper browser's implementation: running script on page's loadFinished signal. The script run just looks up all the fields that can be populated and fills them as simple as elem.value = 'value'.
It works with simple example like e.g. my wifi router config page (which uses very simple logic for forms). But it doesn't work with e.g. https://onlyfans.com/ login page.
When I run my script I get JS errors like:
"js: Uncaught TypeError: Cannot set property 'value' of null".
The minimal example demonstrating the issue:
#include <QApplication>
#include <QObject>
#include <QTimer>
#include <QWebEngineView>
#include <QWebEnginePage>
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
const char* URL_ONLYFANS = "https://onlyfans.com/";
const char* URL_ROUTER = "http://192.168.0.1/";
const char* SCRIPT_ROUTER(R"(
document.querySelector('input[type=text]').value = 'admin';
document.querySelector('input[type=password]').value = 'mypass';
)");
const char* SCRIPT_ONLYFANS(R"(
document.querySelector('input[name=email]').value = "random#gmail.com";
document.querySelector('input[name=password]').value = "randompass";
)");
class Observer : public QObject
{
Q_OBJECT
public:
explicit Observer(QWebEnginePage *page): _page(page)
{}
public slots:
void onPageLoad(bool ok)
{
qDebug() << "Page loaded" << ok;
// Running script right away pretty much guarantees JS errors
// No JS errors if delay is >= 1500 ms:
QTimer::singleShot(1500, this, &Observer::runScript);
qDebug() << "Scheduled running script";
}
void runScript()
{
_page->runJavaScript(SCRIPT_ONLYFANS);
qDebug() << "Ran script";
}
private:
QWebEnginePage *_page{nullptr};
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWebEngineView view;
QWebEngineProfile profile;
QWebEnginePage page(&profile);
view.setPage(&page);
view.setUrl(QUrl(URL_ONLYFANS));
view.resize(1024, 750);
Observer observer(view.page());
QObject::connect(view.page(), &QWebEnginePage::loadFinished,
&observer, &Observer::onPageLoad);
/*
{
QWebEngineScript script;
script.setName("autofill");
script.setSourceCode(SCRIPT_ONLYFANS);
// Also tried `DocumentCreation` and `Deferred`, didn't work for Onlyfans
script.setInjectionPoint(QWebEngineScript::DocumentReady);
script.setRunsOnSubFrames(true);
script.setWorldId(QWebEngineScript::ApplicationWorld);
view.page()->scripts().insert(script);
}
*/
view.show();
return app.exec();
}
#include "main.moc"
As can be seen I tried to insert my scripts in two ways: on page's loadFinished signal (both immediately and with a time delay) and by inserting my script into page's scripts collection. I tried all available insertion points: DocumentCreation, DocumentReady and Deferred.
In all cases I get JS error shown above. Except for the case when I delay script execution by about 1500 ms, which is obviously a guess and is not robust.
I do understand that the elements I'm trying to set are probably not yet created, so I'd like some advice on how to properly time the script execution.

Related

Can not get QT Bluetooth module to work with Virtual Box Ubuntu

I have created a small program using QT creator 4.14.0 (QT 5.15.2), using c++ and QML. I am creating, building and running the program on Ubuntu using Virtual box on a windows 10 system. I have installed bluez and other bluetooth packages on the virtual Ubuntu system as per instructions I've found online, and I have bluetooth working in the Ubuntu operating system, I can scan for devices using the Bluetooth quick-tray item and it finds what it's supposed to find, the bluetooth adapter works.
However, when I run my program I get the the following under Application output "qt.bluetooth: Dummy backend running. Qt Bluetooth module is non-functional.", and the bluetooth scan never starts (no further output). From what I can gather by research it seems like QT thinks that the system can not support Bluetooth, and uses a "dummy stub" in lieu of the actual working code. I have researched what I can about how to resolve this, but I can not find anything I can apply to my own code/setup. I would be appreciative of any help I can get.
Here's the relevant code:
// this is bluetooth_scanner.h
#ifndef BLUETOOTH_SCANNER_H
#define BLUETOOTH_SCANNER_H
#include <QObject>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothDeviceInfo>
#include <QDir>
#include <QTextStream>
class bluetooth_scanner : public QObject
{
Q_OBJECT
public:
bluetooth_scanner();
void startDeviceDiscovery();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
};
#endif // BLUETOOTH_SCANNER_H
// this is bluetooth_scanner.cpp
#include "bluetooth_scanner.h"
// constructor
bluetooth_scanner::bluetooth_scanner()
{
}
void bluetooth_scanner::startDeviceDiscovery()
{
// Create a discovery agent and connect to its signals
QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(); // was 'this' inside parantheses, got build error, removed == no build error
QObject::connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
// Start a discovery
discoveryAgent->start();
}
// In your local slot, read information about the found devices
void bluetooth_scanner::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
// init QT printing
QTextStream out(stdout);
out << "Found new device:" << device.name() << '(' << device.address().toString() << ')' << endl;
}
// this is main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <bluetooth_scanner.h>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
// test bluetooth C++ scan function
bluetooth_scanner *bt_scan = new bluetooth_scanner();
bt_scan->startDeviceDiscovery();
return app.exec();
}

Can't get OAuth 2.0 code in Qt app, but is seems to work in browser

I'm trying to configure communication with Google OAuth 2.0 in my Qt project. I was using this tutorial, although it seems to be a bit outdated. I configured everything in Google APIs site, used those data in Credentials/OAuth 2.0 Client IDs page:
Header file:
#ifndef GOOGLEAUTH_H
#define GOOGLEAUTH_H
#include <QObject>
#include <QOAuth2AuthorizationCodeFlow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QUrlQuery>
#include <QOAuthHttpServerReplyHandler>
#include <QDesktopServices>
class GoogleAuth : public QObject
{
Q_OBJECT
public:
explicit GoogleAuth(QObject *parent = nullptr);
Q_INVOKABLE void click();
private:
QOAuth2AuthorizationCodeFlow google;
};
#endif // GOOGLEAUTH_H
Source file:
#include "GoogleAuthenticator.h"
GoogleAuth::GoogleAuth(QObject *parent) : QObject(parent)
{
google.setScope("email");
connect(&google, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, [=](QUrl url) {
QUrlQuery query(url);
query.addQueryItem("prompt", "consent");
query.addQueryItem("access_type", "offline");
query.addQueryItem("nonce", "123456");
url.setQuery(query);
QDesktopServices::openUrl(url);
});
google.setAuthorizationUrl(QUrl("https://accounts.google.com/o/oauth2/auth"));
google.setAccessTokenUrl(QUrl("https://oauth2.googleapis.com/token"));
google.setClientIdentifier("<client_id>");
google.setClientIdentifierSharedKey("<client_secret>");
auto replyHandler = new QOAuthHttpServerReplyHandler(5476, this);
google.setReplyHandler(replyHandler);
connect(&google, &QOAuth2AuthorizationCodeFlow::granted, [=]() {
qDebug() << "Access Granted!";
});
}
void GoogleAuth::click()
{
google.grant();
}
When I run click() method browser opens, I can log into Google account and then it redirects me to the page with following message:
Callback received. Feel free to close this page.
I can even see the code I need in the URL (at least I think it is that code?).
The problem is I don't get proper callback in Qt app. When above page with callback message is loaded, I just get this output in Qt Creator:
qt.networkauth.oauth2: Unexpected call
qt.networkauth.replyhandler: Error transferring https://oauth2.googleapis.com/token - server replied: Bad Request
Outside the app it seems to be working, I checked it on this page.
How to solve that propblem? Can I get more detailed info about that bad request?
I have tested the example and strangely it does not work for the "email" scope, after analyzing the http request I found that the problem is the encoding of the "code" received and that it is used to obtain the token. So my solution is to correct that parameter and that can be done override the requestAccessToken() method or use setModifyParametersFunction(), in this case use the latter:
google.setModifyParametersFunction([](QAbstractOAuth::Stage stage,
QVariantMap* parameters)
{
if(stage == QAbstractOAuth::Stage::RequestingAccessToken){
QByteArray code = parameters->value("code").toByteArray();
(*parameters)["code"] = QUrl::fromPercentEncoding(code);
}
});
The official tutorial is quite outdated, and it contains several problems like the one you're facing. Specifically, you need to URL-decode the login code that Google sends, but you need to intercept the parameter from inside Qt, which requires installing a parameter handler:
googleSSO.setModifyParametersFunction([](QAbstractOAuth::Stage loginStage, QVariantMap* parameters) {
// Percent-decode the "code" parameter so Google can match it
if (QAbstractOAuth::Stage::RequestingAccessToken == loginStage) {
QByteArray code = parameters->value("code").toByteArray();
(*parameters)["code"] = QUrl::fromPercentEncoding(code);
}
});
We wrote about how to authenticate a Qt app with Google SSO, and all the issues that we faced, in our blog.
Answer of Pablo is outdated too. Working solution for now is:
void GoogleCloudAuth::modifyParametersFunction(QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters)
{
qDebug() << "modifyParametersFunction stage=" << static_cast<int>(stage);
if (stage == QAbstractOAuth::Stage::RequestingAuthorization)
{
// The only way to get refresh_token from Google Cloud
parameters->insert("access_type", "offline");
parameters->insert("prompt", "consent");
}
else if (stage == QAbstractOAuth::Stage::RequestingAccessToken)
{
// Percent-decode the "code" parameter so Google can match it
QByteArray code = parameters->value("code").toByteArray();
parameters->replace("code", QUrl::fromPercentEncoding(code));
}
}

Check that the app runs for the first time

I'm new to qt mobile development and I have a rather dumb question.
How would I check whether a user runs the app for the first time (both Android and iOS)?
EDIT:
The reason I need this check is that I have an intro SwipeView for the first-timers and after it's read once it should always open the main app screen.
I've tried the way #TrebledJ suggested and it seems to work alright, Or is this stupid to do that in main.cpp?
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QSettings>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QSettings settings;
QVariant firstRun = settings.value("first-run");
QQmlApplicationEngine engine;
QUrl startingScreen(QStringLiteral("qrc:/main.qml"));
if(!firstRun.isValid())
settings.setValue("first-run", true);
else
startingScreen.setUrl(QStringLiteral("qrc:/start.qml"));
engine.load(startingScreen);
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Use QSettings to check for a set value.
QSettings settings;
QVariant val = settings.value("first-time");
if (!val.isValid()) {
// ... first run
settings.setValue("first-time", false); // set a value so that the value is valid on the next run
} else {
// ... not first run
}
In QML, there is the Settings QML Type.
import Qt.labs.settings 1.0
Settings {
id: settings
property bool isFirstTime: true
}
Component.onCompleted: {
if (settings.isFirstTime) {
// ... first run
settings.isFirstTime = false;
} else {
// ... not first run
}
}
However, according to documentation:
Note: This type is made available by importing the Qt.labs.settings module. Types in the Qt.labs module are not guaranteed to remain compatible in future versions.
In consideration of the non-guarantee, Felgo/V-Play's API has a Storage QML Type which can also perform the check in QML. (The first example in their documentation implements this.)

How to set OffTheRecord profile for QWebEngineView?

How to setup OffTheRecord profile for QWebEngineView?
I use QT5.10 for Linux.
I am going to use it in embedded environment with read-only filesystem and I need to prevent WebEngine writing files and creating folders in filesystem.
#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QWebEngineProfile>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QWebEngineView view;
auto profile = view.page()->profile();
profile->setHttpCacheType(QWebEngineProfile::MemoryHttpCache);
profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
//profile->setPersistentStoragePath(nullptr);
std::cout << "StoragePath: " << profile->persistentStoragePath().toStdString() << std::endl;
std::cout << "isOffTheRecord: " << profile->isOffTheRecord() << std::endl;
profile->settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); // Since Qt5.7
profile->settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
view.setUrl(QUrl(QStringLiteral("http://localhost/index.html")));
view.resize(1920, 1080);
view.show();
return a.exec();
}
Try this configuration:
First of all, disable any possible cookie. Use setPersistentCookiesPolicy and set it to NoPersistentCookies
If you can write in to a given folder, try to save all temporal files in a secure storage:
auto *profile = QWebEngineProfile::defaultProfile();
profile->setCachePath("yourfolder");
profile->setPersistentStoragePath("yourfolder");
This should give you the control of all the temporal files that are generated by the Web Engine.
If not, taking a look in to Qt repo, you can see that the variable that manage this state is controlled in BrowserContextAdapter, this variable is set up to false, if the storage path is empty while creating the browser context.
So if you create your own QWebEngineProfile with an empty QString as path and use it as default profile:
QWebEngineProfile* profile = new QWebEngineProfile(QString(), parent)
std::cout << "isOffTheRecord: " << profile->isOffTheRecord() << std::endl; // Should return true
This can be done easily if you use it to create any single QWebEnginePage manually using this profile and set it in your QWebEngineView using setPage:
engineview->setPage(new QWebEnginePage(profile, parent));
The documentation for QWebEngineProfile's default constructor states:
Constructs a new off-the-record profile with the parent parent.
An off-the-record profile leaves no record on the local machine, and
has no persistent data or cache. Thus, the HTTP cache can only be in
memory and the cookies can only be non-persistent. Trying to change
these settings will have no effect.
Once you've created a default QWebEngineProfile, pass it to a QWebEnginePage and set that as the page in your QWebEngineView.
Here's a simple example that compiles and runs (tested on Mac OS):
#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QWebEnginePage>
#include <QWebEngineProfile>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWebEngineView view;
QWebEngineProfile profile;
QWebEnginePage page(&profile);
qDebug() << "StoragePath:" << profile.persistentStoragePath();
qDebug() << "isOffTheRecord:" << profile.isOffTheRecord();
view.setPage(&page);
view.setUrl(QUrl(QStringLiteral("http://www.stackoverflow.com/")));
view.show();
return a.exec();
}
When running the above you should see this appear in standard out:
StoragePath: ""
isOffTheRecord: true

QWebView / Qt WebKit won't open some SSL pages; redirects not allowed?

Clean install of Qt SDK 1.1.4 on Windows 7 with Visual C++ 2008 SP1; I'm using Qt Creator. Why does this code not load some web pages?
#include <QtGui/QApplication>
#include <QtWebKit/QWebView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWebView b;
b.load(QUrl("https://gmail.com")); // doesn't work
//b.load(QUrl("https://accounts.google.com")); // works
//b.load(QUrl("https://google.com")); // doesn't work
//b.load(QUrl("https://www.google.com")); // works
b.show();
return a.exec();
}
Why do some of the URLs not work, and others do?
I think the google.com / www.google.com is especially telling; google.com normally redirects to www.google.com. And gmail.com is redirecting to accounts.google.com. Is WebKit not allowing secure pages to redirect? If so, how to fix that?
By the way, Qt SDK 1.1.4 seems to include OpenSSL; I noticed its presence at C:\QtSDK\Desktop\Qt\4.7.4\msvc2008\bin\ssleay32.dll. Also notice that some pages seem to work, just not others.
EDIT: Two more URLs:
b.load(QUrl("https://support.motionview3d.com/help/_media/images/directory.png")); // doesn't work
b.load(QUrl("https://mail.google.com")); // works
Again, both of these work fine in other web browsers.
You are probably getting SSL errors which you can handle in a slot. Although not the best final solution, you can use the slot to ignore all SSL errors. I did this by subclassing QWebView:
qwebview.h:
#ifndef WEBVIEW_H
#define WEBVIEW_H
#include <QWebView>
class WebView : public QWebView
{
Q_OBJECT
public:
WebView(QWidget *parent = 0);
private slots:
void handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors);
};
#endif // WEBVIEW_H
qwebview.cpp:
#include "webview.h"
#include <QNetworkReply>
#include <QtDebug>
#include <QSslError>
WebView::WebView(QWidget *parent) :
QWebView(parent)
{
load(QUrl("https://gmail.com"));
connect(page()->networkAccessManager(),
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )),
this,
SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> & )));
}
void WebView::handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors)
{
qDebug() << "handleSslErrors: ";
foreach (QSslError e, errors)
{
qDebug() << "ssl error: " << e;
}
reply->ignoreSslErrors();
}
main.cpp"
#include <QApplication>
#include "WebView.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
WebView w;
w.show();
return a.exec();
}
Running this should produce debug output like this:
handleSslErrors:
ssl error: "The host name did not match any of the valid hosts for this certificate"
ssl error: "No error"
ssl error: "No error"
...
In your final program, you will of course want to handle SSL errors properly :)
I usually use the "Arnold Spence"s solution but sometimes that wont work.
in that case just change the default Ssl configuration like this
QSslConfiguration sslconf = QSslConfiguration::defaultConfiguration();
QList<QSslCertificate> cert_list = sslconf.caCertificates();
QList<QSslCertificate> cert_new = QSslCertificate::fromData("CaCertificates");
cert_list += cert_new;
sslconf.setCaCertificates(cert_list);
sslconf.setProtocol(QSsl::AnyProtocol);
QSslConfiguration::setDefaultConfiguration(sslconf);
Here we altered the configuration for the entire application .
I recommend you handle the sslErrors signal too ..