Disabling media generation for specific files

Hello,

I developed a product download portal for customers with Kirby 2. The portal hosts private files for each customer, and downloading these files should not be possible via public URL (download is initiated with PHP instead).

The product files reside in the content-folder to allow easy manipulation via panel. I’ve restricted access to these files via .htaccess. The path of each file looks like this:
/content/products/customer/filename.ext

When migrating to Kirby 3, I noticed that after downloading a product file, the file gets copied to the media directory.
media/pages/products/customer/123456789-123456789/filename.ext

Is it possible to disable caching of these files to media directory? Motivations:

  • The product files can be very large. Duplication to media folder unnecessarily fill-ups the web host disk quota
  • Although the files are not in “plain sight”, they are still downloadable via a public URL in media folder. The project has high security requirements, and these kind of things cannot be overlooked

Are you making download links available for restricted files with $file->url()? Files get copied to the media folder only when the URL returned by the url() method is accessed in the browser.

I suggest you to create a custom file method to generate a URL for restricted files instead of using the default $file->url(). Then point those files to a custom route from where you could handle the authorization logic or token validation. Here is a simple example:

<?php // site/config/config.php

return [
  'routes' => [
    [
      'pattern' => 'restricted/(:any)',
      'action'  => function ($hash) {
        // validate request

        if ($file = site()->index()->files()->findBy('hash', $hash)) {
          return Response::download($file->root(), 'new-filename.pdf');
        }

        return site()->errorPage();
      }
    ],
  ],
];
3 Likes

Oh, so it’s the url() method cause caching to media. That solves it then, so I’ll mark tag your response as the solution.

My current product portal initiates file download a bit differently, but to generalize the solution, I developed a file method extension:

Kirby::plugin('company/file-methods', [
    'fileMethods' => [
        'directDownload' => function ()
		{
			echo Response::download($this->root(), $this->filename());
        },
    ]
]);

Once you have a reference to the file object, you can simply call $file->directDownload() to initiate download and bypass media caching.

1 Like

I built a login-protected page following the cookbook recipe Restricting access to your site. I need to be able to download files via links.

As I understand, these files are now not protected outside this page – how exactly do I need to implement this solution if I list links from a simple files section like below? Unfortunately I’m not very familiar with routes and plugins – thanks a lot for any tips!

<?php foreach($page->files() as $file): ?>
    <a href="<?= $file->url(); ?>"><?= $file->filename() ?></a>
<?php endforeach ?>
1 Like

Assuming that you want to restrict access to these files?

As @pedroborges pointed out above, if you call $file->url(), the file is copied to the media folder and thus publicly accessible.

That’s why you have to create a custom file method (e.g. secretUrl()or whatever) that points to a route, e.g the pattern outlined by @pedroborges above.

In your route, you can then also check if the user has access rights (e.g. is a logged in user with a given role), and then return the file.

Additionally, you will have to restrict access to the files in the content folder in your .htaccess.

Here’s my file-method plugin to handle this case, inspired by other answers here. I thought I would share in case anyone else finds it useful.

Save this code as index.php in folder site>plugins>file-methods. Note you will need to create the folder as it will not exist by default.

To use, simply call $file->urlCacheBypass(). This plugin will yield the uncached file url.

<?php

Kirby::plugin('my/file-methods', [
  'fileMethods' => [
    'urlCacheBypass' => function () {
      return site()->url() . '/' . $this->page()->id() . '/' . $this->filename();
    },
  ]
]);