8

I am trying to set up a site so that users only have access to their own images and audio files. To to this I am using variables in the URL such as:

 <img src="/public/get_file.php?type=image&file=pic001.png" />

On the front-end I am using React JS (not sure if that is important to this issue). But on the backend, the PHP script will validate that the user is logged in (just by checking a simple SESSION variable) and look for that file in the user's data directory (based on the user id in their SESSION variable). If it exists it will return it to the user using XSendFile.

The problem I am having is that every time that the user tries to access the files there is a bit of a delay before they load. This is telling me that they probably are not being cached by the browser.

Why are the files not getting cached? Does it have to do with the URL parameter or the use of PHP/XSendFile?

What can I do to allow my files (images/audio) to be cached?

Update

As requested here is my get_file.php:

<?php

        session_start();

        if (empty($_SESSION['auth']['id'])){
                exit;
        }

        require_once("../../api_modules/settings.php");

        $developer_id = $_SESSION['auth']['id'];

        $type = preg_replace('/[^-a-zA-Z0-9_]/', '', $_GET['type']);
        $file = preg_replace('/[^-a-zA-Z0-9_\.]/', '', $_GET['file']);

        $file = preg_replace('/[\.]{2,}/', '', $file);

        $project_id = preg_replace('/[^0-9]/', '', $_GET['project_id']);
        $developer_id = preg_replace('/[^0-9]/', '', $developer_id);

        if ($type == "image")
                $file = FILEPATH ."files/$developer_id/$project_id/images/thumbs/88x88/$file";
        else if ($type == "full_image")
                $file = FILEPATH ."files/$developer_id/$project_id/images/originals/$file";
        else if ($type == "audio"){
                $file = FILEPATH ."files/$developer_id/$project_id/audio/$file";
        }
        else {
                exit;
        }


        header("X-Sendfile: $file");
        header("Content-type: application/octet-stream");
        header('Content-Disposition: attachment; filename="' . basename($file) . '"');
kojow7
  • 10,308
  • 17
  • 80
  • 135
  • can you post the response of the image request from the Chrome dev tools network tab? – jsdeveloper Mar 10 '19 at 22:13
  • @jsdeveloper While I do use the console often, I am not too used to using the network tab (response) section. However, I do see my request, but the response says: "The request has no response data available". – kojow7 Mar 11 '19 at 02:52
  • just for clarification, you want the browser to be able to cache the images? – Vidal Mar 20 '19 at 12:16
  • @kojow7 Which HTTP server are you using? – Tomasz Kajtoch Mar 20 '19 at 12:56
  • @TomaszKajtoch I do not fully understand your question. My question is tagged as Apache. Is there something else you are looking for? – kojow7 Mar 20 '19 at 14:47
  • @Vidal Yes, currently if I go to one page it will load images, if I leave to another page, and then come back to the first page it has to reload the images again. – kojow7 Mar 20 '19 at 14:51
  • Could you post your `get_file.php` code please? – Alex Mar 23 '19 at 01:45

4 Answers4

0

Try to use this kind of header (PHP code) with for example header('Cache-Control: must-revalidate'); for download a private files

$mime_types = array(
        'pdf' => 'application/pdf',
        'txt' => 'text/plain',
        'html' => 'text/html',
        'exe' => 'application/octet-stream',
        'zip' => 'application/zip',
        'doc' => 'application/msword',
        'xls' => 'application/vnd.ms-excel',
        'ppt' => 'application/vnd.ms-powerpoint',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'jpeg' => 'image/jpg',
        'jpg' => 'image/jpg',
        'php' => 'text/plain'
        );

if ($mimeType == '') {
    $file_ext = strtolower(substr(strrchr($file_path.$file_name_gen, '.'), 1));
    if(array_key_exists($file_ext, $mime_types)) $mime_type = $mime_types[$file_ext];
    else $mime_type = 'application/force-download';
    }


ob_start();
header('Content-Disposition: attachment; filename="' . $file_name_gen . '"');
header('Content-Type: '.$mime_type);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file_path.$file_name));
ob_clean();

flush();
readfile($file_path.$file_name);

And use Cache control with a best option for your solution like

Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache 
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached

BTW. $file_name_gen is a genuine file name on the server and $file_name is a file name users see for more file privacy.

uxmal
  • 428
  • 1
  • 7
  • 17
0

For the record, this is mostly due to the fact the default value of session.cache_limiter is nocache.

See http://php.net/manual/en/function.session-cache-limiter.php on how to set a different value that fits your needs.

Capsule
  • 6,118
  • 1
  • 20
  • 27
0

How do you generate the images? Do you have a public local directory or it is loaded by a CDN (remotely)?

Maybe it can help: HOW TO CACHE IMAGES GENERATED BY PHP

dm777
  • 119
  • 8
  • The images are stored in directories on the same server. Each user has their own user directory labeled by their user id. – kojow7 Mar 21 '19 at 14:23
  • Ok, so it's easier to manage cache with local images. You can try the solution on link and see if it'll work. But what happen if someone try to access the images directly? – dm777 Mar 21 '19 at 17:07
  • They cannot access images directly as they are not located in the web accessible folder. – kojow7 Mar 21 '19 at 17:47
0

I think this is a duplicate.

But since there is a bounty lets try to adapt your code together with the suggested solution there.

You need to check that Apache has mod_expires and mod_headers enabled and working properly.

And then before you start real output check if file was modified:

...
$last_modified_time = filemtime($file); 
$etag = md5_file($file);
// always send headers
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT"); 
header("Etag: $etag"); 
// exit if not modified
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time || 
    @trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) { 
    header("HTTP/1.1 304 Not Modified"); 
    exit; 
} else {
    header("X-Sendfile: $file");
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' . basename($file) . '"');
}
Alex
  • 16,739
  • 1
  • 28
  • 51
  • I am not sure I am fully understanding, but does the web browser request the file, but then if it receives the 304 message, it cancels its request and uses its cache instead? – kojow7 Mar 23 '19 at 03:44
  • Yes. That is how browser can reuse cached http responses – Alex Mar 23 '19 at 03:49
  • Likely won't get time to fully test this by tomorrow, but this seems like the best answer to me. – kojow7 Mar 24 '19 at 07:06