Skip to content

Instantly share code, notes, and snippets.

@Nithanim
Created January 6, 2023 14:26
Show Gist options
  • Save Nithanim/0ac77f5e61da1e1064b5b2d8ee293190 to your computer and use it in GitHub Desktop.
Save Nithanim/0ac77f5e61da1e1064b5b2d8ee293190 to your computer and use it in GitHub Desktop.
Display PDF DataUri on Website in IFrame WITH sandbox

At some point you might need to diplay a PDF on a Webpage. And you do not have it as normal URL on your server but as DataUri. It sounds very easy but in reality is not really. I would be if Chome would not be dumb. What we really want is a sandboxed iframe where a PDF is displayed. The sandbox is essential to prevent a malicious script to break out to the parent and steal sensitive data.

Warning This is just a little write-up about my experiences and not a full tutorial! Also, I did this a couple of weeks ago, so something might be missing or inaccurate!

What works and doesn't

In firefox:

  • Pass the PDF DatUri via the src attribute: works
  • Pass the PDF via a BlobUrl (from URL.createObjectURL() of a Blob): works (IIRC)
  • Using the sandbox attribute without any data: partially loads the viewer but does not work (it uses JS)
  • Using sandbox with allow-scripts: works flawlessly.

In chrome:

  • Pass the PDF DataUri via the src attribute: broken; seems to be too long
  • Pass the PDF via a BlobUrl: "Content Blocked"
  • Pass the PDF via BlobUrl with allow-same-origin: Should not be a great idea because since this then counts as from your domain, we have the security problem we wanted to avoid. (Also I think it does not work regardless for som reason.)

Solution

Since the PDF viewer of Firefox is JS and open source, we could use that. However when we host PDF.js on our server, we still have to pass the PDF file to the viewer somehow. The BlobUrl still does not work in Chrome, so we cannot do like viewer.html?file=blob://....

But we can modify the viewer minimally such that it posts a messaage to the parent via Window.parent.postMessage() when the viewer is loaded. In the parent, we can listen to it using

const iframeWindow = document.getElementById('myframe').contentWindow;
window.addEventListener('message', message => {
    if (message.source === iframeWindow) {
        // Check if it is the "ready" event
        // Send PDF as DataUri to the iframe: iframeWindow.postMessage()
    }
});

And then in the iframe, we listen for a DataUri and pass it to the viewer via PDFViewerApplication.open().

PDF.js

It is not very clear how to use it, honestly. You can have it completely barebones but we usually want the complete package you have in firefox.

There is some kind of introduction here but without any helpful information: https://github.com/mozilla/pdf.js/wiki/Setup-pdf.js-in-a-website

So what I did is download the distribution zip. There are two folders in there: "build" and "web". You will need both, so its best to make a folder for it on your server.

Note: If you a building a Single Page Application, put it in the "public" folder or something aequivalent.

The PDF.js dist is rather big so to save some space, remove:

  • web/*.pdf
  • web/debugger.*
  • web/*.map
  • build/*.map
  • (I think pdf.sandbox.js was also not needed)

The "web/viewer.html" is the one you use as "src" for your iframe. But you have to add a little code at the end that registers the message listener for messages from the parent first. And the send off the message that the viewer is ready to the parent.

I did it immediately and it seems to work but it might be better to listen for an internal ready event of PDF.js first. You could find it relatively easy, because you will find it red in the browser console because part of the event dispaatch failes becausee of the cross site prevention.

A quick ordered list of stuff to do:

  1. Parent: Create iframe element with src to the viewer on your server and sandbox="allow-scripts".
  2. Parent: Add the message listener
  3. Magic: Browser loads the viewer url and document in the iframe
  4. Iframe: Registering the listener for messages from the parent
  5. Iframe: Posting a "ready" message to the parent
  6. Parent: Receives the "ready" event
  7. Parent: Sends the PDF as DataUri via a message to the iframe
  8. Iframe: The event listener is called with the PDF as DataUri
  9. Iframe: The PDF DataUri is passed to PDF.js via PDFViewerApplication.open()
  10. ...
  11. Parent: When the iframe is no longer needed, remove the parent listener with removeEventListener.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment