Loading and displaying remote HTTP images in an extension

Hello everyone.

My extension QR Lite has a feature where the user opens the context menu on an image and choose “Scan QR code in Image”, the extension will load the target image via its URL in a popup window, read the image’s data and make an attempt to decode QR code.

The feature works well for images loaded though HTTPS, but it fails to load HTTP-only images. The browser seems to consider it Mixed Content, and always attempt to upgrade to HTTPS.

You can test it out by installing the extension, then open the following URL, right-click the image and choose “Scan QR Code in Image”.
(It’s not an image of an QR Code but it could still serve our purpose. Hard to find an HTTP-only image on the internet these days.)

http://www.vulnweb.com/acunetix-logo.png

Normally you would see the target image being displayed in the popup window but in this case the image would fail to load. If you inspect the popup it should say in the console:

Mixed Content: Upgrading insecure display request ‘http://www.vulnweb.com/acunetix-logo.png’ to use ‘https’

According to this MDN page, mixed content will either be upgraded or blocked, and there seems to be no other way around.

Another MDN page suggests that extensions can opt out of the insecure request upgrading behavior by using a CSP that does not include upgrade-insecure-requests. But my extension already does that and HTTP requests are still being upgraded.

Is there a way to make HTTP-only images work, or is it a dead end?
I want to support HTTP-only images because I can still think of valid use cases for it.
Any help would be appreciated.

1 Like

I think you should be able to fetch it using fetch.
At least a quick proof of concept suggests it works - execute this in extension page:

document.body.replaceChildren(Object.assign(document.createElement('img'), {src: URL.createObjectURL(await (await fetch('http://www.vulnweb.com/acunetix-logo.png')).blob())}))

But you need host permission for vulnweb domain.

Alternative approach could be to use this Firefox exclusive API:

To gain access to the image element - and then:

3 Likes

fetch() works! Host permission is not a problem because I already need <all_urls> anyway.
I ended up with something that looks like this:

if (url.startsWith("http://")) {
     url = await fetch(url)
        .then((r) => r.blob())
        .then((b) => URL.createObjectURL(b))
}

I did have to add http: to connect-src in the CSP.

I’ve actually explored the alternative approach you suggested before. I guess I could get rid of <all_urls> with that approach, but it comes with its own challenges. Specifically, if I do anything async in the menu handler, the handler lose its status of user action and thus won’t be able to open the popup anymore. I think I’ll stay with the fetch approach for now.

Thank you for your help. I really appreciate it.

2 Likes