Capture server response with QWebEngineView - qt

I'm trying to create a Dialog in Qt which loads a URL (which I do not want to expose to the end-user, hence a Dialog). Once the user has entered their credentials on the page, the server returns a redirect URL which I want to capture. How can I do this?
QtWebkit made this easy to do as QWebView had a QNetworkAccessManager object. But with QtWebEngine, the QWebEngineView class does not have this capability. The former also allowed HTTP headers to be set for any requests by using the QNetworkRequest class and then load requests with these specific requests in QWebView. How do I do this with QWebEngineView?

Since Qt 5.6 the proposed solution for what you are trying to achieve with QWebEngineView is QWebEngineUrlRequestInterceptor:
Implementing the QWebEngineUrlRequestInterceptor interface and installing the interceptor on the profile enables intercepting, blocking, and modifying URL requests before they reach the networking stack of Chromium.
It is an abstract class which means you need to subclass it to get what you want:
#include <QWebEngineUrlRequestInterceptor>
#include <QDebug>
class RequestInterceptor : public QWebEngineUrlRequestInterceptor
{
public:
explicit RequestInterceptor(QObject * parent = Q_NULLPTR) : QWebEngineUrlRequestInterceptor(parent) {}
virtual void interceptRequest(QWebEngineUrlRequestInfo & info) Q_DECL_OVERRIDE;
};
void RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo & info)
{
// Intercepting the requested URL
QUrl url = info.requestUrl();
qDebug() << "Request URL: " << url;
// Optionally redirect the request URL but it only works for requests
// without payload data such as GET ones
info.redirect(QUrl("https://www.google.com"));
// Set HTTP header
QByteArray httpHeaderName = "SomeHeaderName";
QByteArray httpHeaderValue = "SomeHeaderValue";
info.setHttpHeader(httpHeaderName, httpHeaderValue);
}
Then you need to register the pointer to this interceptor in QWebEngineProfile for a particular QWebEnginePage, like this:
QWebEngineView * view = new QWebEngineView;
RequestInterceptor * interceptor = new RequestInterceptor(view);
QWebEngineProfile * profile = new QWebEngineProfile(view);
profile->setRequestInterceptor(interceptor);
QWebEnginePage * page = new QWebEnginePage(profile, view);
view->setPage(page);

Related

Catching and responding to the Connman 'RequestInput' method call with QtDBus

I'm building a simple Qt-based application for monitoring and connecting to WiFi networks. I'm interfacing with Connman via its D-Bus APIs, and am able to scan for available networks, turn on/off technologies and register an agent as expected. I'm currently unable to provide the requested passphrase when the Connman RequestInput method is called (when attempting to connect to a protected/secure network), as I'm unsure how to bind the RequestInput method with a function in Qt.
Below is some indicative code which outlines the approach:
//Create a QDBusConnection systemBus() object
QDBusConnection connection = QDBusConnection::systemBus();
//Ensure the systemBus() connection is established
if (!connection.isConnected()) {
qDebug() << "Connection error.";
}
//Create a Connman Manager D-Bus API interface object
QDBusInterface manager("net.connman", "/", "net.connman.Manager", connection);
//Register an agent with the Connman Manager API
manager.call("RegisterAgent", QVariant::fromValue(QDBusObjectPath("/test/agent")));
//Attempt to bind the mySlot() function with the net.connman.Agent RequestInput method
//This does not currently work
connection.connect("",
"/test/agent",
"net.connman.Agent",
"RequestInput",
this,
SLOT(mySlot(QDBusObjectPath, QVariantMap)));
//Create a Connman Service D-Bus API interface object (for a specific WiFi Service)
QDBusInterface service("net.connman",
"/net/connman/service/[WIFI SERVICE]",
"net.connman.Service",
connection);
//Attempt to connect to the secure WiFi network
//Note: this network has not previously been connected, so the RequestInput method is guaranteed to be called
service.call("Connect");
QVariantMap myClass::mySlot(const QDBusObjectPath &path, const QVariantMap &map)
{
//Connman Agent RequestInput() method received
}
As commented above, the attempted binding of the /test/agent path, net.connman.Agent interface and RequestInput method to the mySlot() function does not work; there are no errors reported but the mySlot() function is never called. If I enable debugging with the QDBUS_DEBUG environment variable, I receive the following:
QDBusConnectionPrivate(0xffff74003a00) got message (signal): QDBusMessage(type=MethodCall, service=":1.3", path="/test/agent", interface="net.connman.Agent", member="RequestInput", signature="oa{sv}", contents=([ObjectPath: /net/connman/service/[WIFI SERVICE]], [Argument: a{sv} {"Passphrase" = [Variant: [Argument: a{sv} {"Type" = [Variant(QString): "psk"], "Requirement" = [Variant(QString): "mandatory"]}]]}]) )
The above is exactly what I'd expect; the RequestInput method is being called for the /test/agent path on the net.connman.Agent interface with the oa{sv} signature.
My questions:
How do I 'connect' to the RequestInput method call, such that my mySlot() function can parse the RequestInput method data?
How do I return the required QVariantMap from within mySlot()?
From the debug output it appears that ConnMan is doing a MethodCall, but QDBusConnection::connect() is for handling DBus singals, which is why your slot is not invoked.
You need to register an object implementing the net.connman.Agent interface onto the corresponding path, so that ConnMan can invoke your methods:
class ConnManAgent : public QObject {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "net.connman.Agent")
public:
ConnManAgent(QObject *parent = nullptr);
Q_INVOKABLE QVariantMap RequestInput(const QDBusObjectPath &, const QVariantMap &);
// ... Rest of the net.connman.Agent interface
};
and then register it on the respective path:
connection.registerObject(
QStringLiteral("/test/agent"),
new ConnManAgent(this),
QDBusConnection::ExportAllInvokables);
This exports all methods marked with Q_INVOKABLE to DBus. You might also mark them as Q_SLOTS and use ExportAllSlots, that's mostly up to you.

Renew QQuickImageProvider request

To show some Pixmap in QML from a C++ model, I used a QQuickImageProvider:
class ImageProvider : public QQuickImageProvider
{
public:
ImageProvider(MyModel *model) : QQuickImageProvider(QQuickImageProvider::Pixmap), _model(model) { }
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
Q_UNUSED(requestedSize);
int width = 160;
int height = 120;
QString name = id.left(id.indexOf("*"));
if (size) *size = QSize(width, height);
return _model->findThumbnail(name); // retrieve image in the model
}
private:
MyModel *_model;
};
The model is ready on startup, but the images are populated at run-time, slowly.
When the QML page is loaded it requests to the image provider the pixmaps but they are not ready yet.
After some time they are available in the model, but I don't know how to tell to the QML Image object to renew the request to the provider.
QQuickImageProvider has a well documented asynchronous mode, which you can force by passing QQmlImageProviderBase::ForceAsynchronousImageLoading to the provider constructor.
The way I understand it is that requests will be handled by a dedicated thread and delivered when done. Which means that what you should do is stall the thread until the image data can be provided, ideally busy with fetching said data.
This also would cause images to be loaded in the order they are requested by the application rather than some other order, presumably that of the model items.
The solution is simple, create a new role that indicates that the image is fully loaded.
setData(index, false, IsLoadedRole);
// finished loading
setData(index, true, IsLoadedRole);
*.qml
Image {
source: isLoaded ? "image://MyImageProvider/" : ""}
}

Downloading file over HTTPS using Qt behind authenticating proxy

I am trying to download a file, publicly available over an HTTPS URL, using Qt 4.8. I am behind a corporate authenticating proxy (over which I have no control).
I am using QNetworkAccessManager, as shown below:
bool FileDownloader::download(const QString& fileUrl, const QString& fileName)
{
QString outputFileName = fileName.isEmpty() ? fileNameFromUrl(fileUrl) : fileName;
_file.setFileName(outputFileName);
_file.open(QIODevice::WriteOnly);
QUrl url(fileUrl);
QNetworkReply* pReply = _networkAccessManager.get(QNetworkRequest(url));
pReply->ignoreSslErrors();
connect(pReply, SIGNAL(finished()), _pEventLoop, SLOT(quit()));
connect(pReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(pReply, SIGNAL(sslErrors(const QList<QSslError>&)), this, SLOT(onSslErrors(const QList<QSslError>&)));
connect(pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
_pEventLoop->exec();
return true;
}
As soon as I call QNetworkAccessManager::get(), and receive a QNetworkReply object, I connect to its sslErrors signal as mentioned in the docs. Following the same docs, I'm also calling ignoreSslErrors() in the slot connected to that signal:
void FileDownloader::onSslErrors(const QList<QSslError>& sslErrors)
{
QNetworkReply* pReply = (QNetworkReply*)(sender());
pReply->ignoreSslErrors(sslErrors);
}
However, I never receive an sslErrors signal, I always get an error signal, which I'm handling in the onError slot, and which always says:
SSL handshake failed
I'm calling the download functionality thus:
FileDownloader fileDownloader;
fileDownloader.setProxy("http://username:password#proxyserver:8080");
fileDownloader.download(
"https://679cc07dd5c2e4623d32-c8530501a6bee9a6c1860bbdab5cb6f1.ssl.cf2.rackcdn.com/9807/05377d04583309a88e808df5a1758e4e.png",
"f:\\temp\\test.png"
I do not want to use other libraries like libcurl because I don't want to introduce dependencies.
So how can I download the file over HTTPS using Qt?

Call the default asp.net HttpHandler from a custom handler

I'm adding ASP.NET routing to an older webforms app. I'm using a custom HttpHandler to process everything. In some situations I would like to map a particular path back to an aspx file, so I need to just pass control back to the default HttpHandler for asp.net.
The closest I've gotten is this
public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on
var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);
MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);
// write headers, etc. & send stream to Response
}
It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."
From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler
There is also this thing System.Web.UI.PageHandlerFactory which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.
This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.
Any ideas on how I can do this? Is it possible?
Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.
http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
string newTarget = "default.aspx";
string newQueryString = // whatever you want
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the file name.
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget , "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.
I expect this will only work in IIS7.
I'm you're using Routing in webforms you should be able to just add an ignore route for the specific .aspx files you want. This will then be handled by the default HttpHandler.
http://msdn.microsoft.com/en-us/library/dd505203.aspx
Another option is to invert the logic by handling the cases in which you do NOT want to return the default response and remap the others to your own IHttpHandler. Whenever myCondition is false, the response will be the "default". The switch is implemented as an IHttpModule:
public class SwitchModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += app_PostAuthenticateRequest;
}
void app_PostAuthenticateRequest(object sender, EventArgs e)
{
// Check for whatever condition you like
if (true)
HttpContext.Current.RemapHandler(new CustomHandler());
}
public void Dispose()
}
internal class CustomHandler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hallo");
}
public bool IsReusable { get; }
}

Qt: can i load and manipulate the web page dom with QWebKit without rendering the page?

is there any way to load url and to manipulate the page dom without rendering the page
i like to do it problematically without showing the page it self in the browser
I believe you should be able to load the web page using QNetworkAccessManager and manipulate its content using QTextDocument; below is a small example. Also you can use QWebPage class without showing the page contents. I also included it into the example below:
void MainWindow::on_pushButton_clicked()
{
// load web page
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl("http://www.google.com")));
}
void MainWindow::replyFinished(QNetworkReply* reply)
{
QByteArray content = reply->readAll();
// process network reply using QTextDocument
QTextDocument page;
page.setHtml(content);
for (QTextBlock block = page.begin(); block != page.end(); block = block.next())
{
// do smth here
qDebug() << block.text();
}
// process network reply using QWebPage
QWebPage webPage;
webPage.mainFrame()->setHtml(content);
QWebElement document = webPage.mainFrame()->documentElement();
QWebElementCollection elements = document.findAll("element_name");
foreach (QWebElement element, elements)
{
// do smth here
qDebug() << element.toPlainText();
}
}
hope this helps, regards

Resources