Maurice Meyer
Maurice Meyer

Reputation: 18106

QtWebEngine intercepting request with custom URL schema serving reply

I am trying to rewrite several HTTP requests with a custom URL scheme:
All requests to http://static.foo.bar should be rewitten to static://... and serving some reply.

Problem:
The interception and redirecting seems working, but whatever my implementation of QWebEngineUrlSchemeHandler is returning (image or html) always replaces the full HTML page.

Expected result:
A sample image /tmp/iphone.jpg served from SchemeHandler is embedded in the HTML page, so the HTML shows the <h1> title and 2 images.

Versions:
Python 3.7.4
PyQt: 5.14.1

Sample code:

import sys
import signal
from PyQt5 import QtCore
from PyQt5.QtCore import QUrl, QObject
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings, QWebEnginePage, QWebEngineProfile
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlSchemeHandler, QWebEngineUrlScheme
from PyQt5.QtWidgets import QApplication, QMainWindow


class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
    # Everything requested from static.foo.bar goes to static://
    # //static.foo.bar/1/2/4.jpeg >> static://1/2/4.jpeg
    def interceptRequest(self, info):
        print("interceptRequest")
        print(info.requestUrl())
        if 'static.foo.bar' in str(info.requestUrl()):
            url = QUrl()
            url.setScheme(MyWebEngineUrlScheme.scheme.decode())
            url.setHost('baz.jpg')
            print('Intercepting and redirecting to: %s' % url)
            info.redirect(url)


class MyWebEnginePage(QWebEnginePage):
    # debugging
    def acceptNavigationRequest(self, url, _type, isMainFrame):
        print("acceptNavigationRequest: %s" % url)
        return QWebEnginePage.acceptNavigationRequest(self, url, _type, isMainFrame)


class SchemeHandler(QWebEngineUrlSchemeHandler):
    def __init__(self, app):
        super().__init__(app)

    def requestStarted(self, request):
        url = request.requestUrl()
        print('SchemeHandler requestStarted: %s' % url)

        # Returns a sample image
        raw_html = open('/tmp/iphone.jpg', 'rb').read()
        buf = QtCore.QBuffer(parent=self)
        request.destroyed.connect(buf.deleteLater)
        buf.open(QtCore.QIODevice.WriteOnly)
        buf.write(raw_html)
        buf.seek(0)
        buf.close()
        request.reply(b"image/jpeg", buf)
        return


class MyWebEngineUrlScheme(QObject):
    # Register scheme
    scheme = b"static"

    def __init__(self, parent=None):
        super().__init__(parent)
        scheme = QWebEngineUrlScheme(MyWebEngineUrlScheme.scheme)
        QWebEngineUrlScheme.registerScheme(scheme)
        self.m_functions = dict()

    def init_handler(self, profile=None):
        if profile is None:
            profile = QWebEngineProfile.defaultProfile()
        handler = profile.urlSchemeHandler(MyWebEngineUrlScheme.scheme)
        if handler is not None:
            profile.removeUrlSchemeHandler(handler)

        self.handler = SchemeHandler(self)
        print("registering %s to %s" % (MyWebEngineUrlScheme.scheme, self.handler))
        profile.installUrlSchemeHandler(MyWebEngineUrlScheme.scheme, self.handler)


schemeApp = MyWebEngineUrlScheme()
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QApplication(sys.argv)
win = QMainWindow()
win.resize(800, 600)

html = """
<html>
<body>
<h1>test</h1>
<hr>
<p>First image</p>
<img src="http://static.foo.bar/baz.jpg" />
<hr>
<p>Second image</p>
<img src="https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-xr-red-select-201809?wid=1200&hei=630&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1551226038669" />
</body>
</html>
"""

browser = QWebEngineView()
interceptor = WebEngineUrlRequestInterceptor()
profile = QWebEngineProfile()
profile.setUrlRequestInterceptor(interceptor)
page = MyWebEnginePage(profile, browser)
schemeApp.init_handler(profile)

browser.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True)
browser.settings().setAttribute(QWebEngineSettings.JavascriptCanOpenWindows, False)
browser.settings().setAttribute(QWebEngineSettings.LinksIncludedInFocusChain, False)
browser.settings().setAttribute(QWebEngineSettings.LocalStorageEnabled, True)
browser.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)

page.setHtml(html)
browser.setPage(page)
browser.show()

win.setCentralWidget(browser)
win.show()

sys.exit(app.exec_())

Upvotes: 3

Views: 2248

Answers (1)

eyllanesc
eyllanesc

Reputation: 243897

If you analyze the urls requested by the page you get:

PyQt5.QtCore.QUrl('data:text/html;charset=UTF-8,%0A%3Chtml%3E%0A%3Cbody%3E%0A%3Ch1%3Etest%3C%2Fh1%3E%0A%3Chr%3E%0A%3Cp%3EFirst image%3C%2Fp%3E%0A%3Cimg src%3D%22http%3A%2F%2Fstatic.foo.bar%2Fbaz.jpg%22 %2F%3E%0A%3Chr%3E%0A%3Cp%3ESecond image%3C%2Fp%3E%0A%3Cimg src%3D%22https%3A%2F%2Fstore.storeimages.cdn-apple.com%2F4668%2Fas-images.apple.com%2Fis%2Fiphone-xr-red-select-201809%3Fwid%3D1200%26hei%3D630%26fmt%3Djpeg%26qlt%3D95%26op_usm%3D0.5%2C0.5%26.v%3D1551226038669%22 %2F%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E%0A')
PyQt5.QtCore.QUrl('http://static.foo.bar/baz.jpg')
PyQt5.QtCore.QUrl('https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-xr-red-select-201809?wid=1200&hei=630&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1551226038669')

Where condition if 'static.foo.bar' in str(info.requestUrl()): is met for the first and second url((I have marked it in bold for a better visualization).

The solution is to improve the filter:

class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
    # Everything requested from static.foo.bar goes to static://
    # //static.foo.bar/1/2/4.jpeg >> static://1/2/4.jpeg
    def interceptRequest(self, info):
        print("interceptRequest")
        print(info.requestUrl())
        if info.requestUrl().host().startswith("static.foo.bar"): # or if info.requestUrl().host() == "static.foo.bar":
            url = QUrl()
            url.setScheme(MyWebEngineUrlScheme.scheme.decode())
            url.setHost(info.requestUrl().path()[1:])  # remove "/"
            print("Intercepting and redirecting to: %s" % url)
            info.redirect(url)

Upvotes: 3

Related Questions