Protected PDFs being served as text/XML via direct URL

I’m experiencing an odd condition that I don’t know how to resolve. I used the files firewall cookbook example to set up a repository of protected posts which contain embedded PDFs that are streamed to the page via PDF.js. The PDFs are firewalled. The combination of the two prevents the PDFs from being downloaded even by logged-in users.

Everything seems to work reasonably well except that if I paste the protected URL to the PDF directly into the browser as a logged-in user, either the web server or Kirby is serving the PDF as text/html, application/xhtml, or application/xml as its mime type, and of course that results in garbled output. Sometimes it results in a memory exception error if the PDF is large.

What might be going on?

First, my understanding was that the firewall cookbook example ought to prevent direct URL access to protected files. My implementation does prevent direct access to the protected files if the user isn’t logged in. Second, it’s not clear to me why direct access is resulting in an unexpected mime stream? Is there a way to prevent this?

If it’s helpful, this is my modified version of a files firewall plugin:

use Kirby\Cms\Response;

Kirby::plugin('my/files-firewall', [
   'components' => [
      'file::url' => function ($kirby, $file) {
         // my special file blueprint for PDFs
         if ($file->template() === 'casefile') {
            // customized URL string
            return $kirby->url() . '/' . $file->parent()->id() . '/protected/' . $file->filename();
			}
         // default URL for other files
         return $file->mediaUrl();
      },
      'file::version' => function ($kirby, $file, array $options = []) {
         static $original;
         // if the file is protected, return the original file
         if ($file->template() === 'protected') {
            return $file;
         }
         // if static $original is null, get the original component
         if ($original === null) {
            $original = $kirby->nativeComponent('file::version');
         }
         // return it with the given options
         return $original($kirby, $file, $options);
      }
   ],
   'routes' => [
      [
         'pattern' => '(:all)/protected/(:any)',
         'action'  => function ($pageId, $filename) {
            if (kirby()->user() &&
            ($page = page($pageId)) && 
            $file = $page->files()->findBy('filename', $filename)) {
               // is this the source of the mime weirdness?
               return $file->read();
            }
            return site()->errorPage();
         },
      ],
   ],
]);

This returns a string, so is not really useful for your PDF download. For PDF files, do a $file->download() like in the recipe.

Got it, thanks. Interestingly, PDF.js doesn’t seem to care either way.