skip to Main Content

On a godaddy hosted website using CPanel, I have a small PHP script that shows each line in a text file that’s on the server. Each line contains a private href link to a PDF that only the logged-in user can see. The links points to various PDFs in the same folder on the server. The code works fine and I can click on the link and see each PDF.

The problem is that each PDF can also be seen by using a direct URL query (i.e. website/folder/pdfname.pdf). As these are private PDFs, I don’t want them public. I’ve tried changing CPanel permissions on the folder to “owner” – but that seems to prevent the PHP script from opening the PDFs also.

Is there a way to allow a PHP script access to PDFs in a folder – but prevent direct URL references?

NOTE: I’m not particularly adept at PHP or CPanel – sorry.

Code…

$fname = "PDF-" . $user_name.".txt";
$fnum = fopen($fname,"r");
echo "<tr>";
While (($str = fgets($fnum)) !==  false) {
    $arr = explode("|",$str);   
    for ($x  = 0 ;  $x < count($arr);  $x++) {
        echo "<td>$arr[$x]</td>";
    }
    echo "</tr>";   
}
echo "</tr>";
fclose($fnum);

File contents…

Xyz Company|21 Jan 2018|<a href="http://website.com"> website link</a>
Xyz Company|21 Jan 2018|<a href="http://website.com"> website link</a>
Xyz Company|21 Jan 2018|<a href="http://website.com"> website link</a>
Xyz Company|21 Jan 2018|<a href="http://website.com"> website link</a>*

2

Answers


  1. Asside from removing the files from the root, if you are running apache, you can change your .htaccess (I’m sure windows-based system have a web.config equivalent) to forbid access to certain files directly. If you add this snippet to that file, it will deny files with .pdf extension:

    <FilesMatch ".(pdf)$">
    Order Allow,Deny
    Deny from all
    </FilesMatch>
    

    From there, inside your app, you can create some sort of system for curating your PDF links, so if you store the real path in a database and use the id as the link similar to:

    http://www.example.com/?file=1
    

    or if you just do a simple scan:

    <?php
    # The folder that the PDFs are in
    $dir = __DIR__.'/website/folder/';
    # Loop over a scan of the directory (you can also use glob() here)
    foreach(scandir($dir) as $file):
        # If file, create a link
        if(is_file($dir.$file)): ?>
    <a href="?action=download&file=<?php echo $file ?>"><?php echo $file ?></a>
    <?php
        endif;
    endforeach;
    

    Then, if the user tries to download using the link, you check they are first logged in and if they are, download the file by doing a script like so BEFORE you output anything else to the browser (including spaces):

    <?php
    session_start();
    # First check that the user is logged in
    if(empty($_SESSION['username']))
        die('You must be logged in to download this document.');
    # Not sure which directory you are currently in, so I will assume root
    # I would do basename() here incase the user tries to add in something like:
    # ../index.php and tries to download files they are not supposed to
    $file = __DIR__.'/website/folder/'.basename($_GET['file']);
    if(!is_file($file))
        die('File does not exist.');
    # Double check that the file is a pdf
    elseif(strtolower(pathinfo($file, PATHINFO_EXTENSION)) != 'pdf')
        die('File appears to be invalid.');
    # Start download headers
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="'.basename($file).'"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    readfile($file);
    exit;
    
    Login or Signup to reply.
  2. One simpler and basic example (and derivative of previous answer) is to use two separate PHP files, where one is evaluating a set cookie (set to expire soon) in the browser upon link click (set via JS or PHP or other). If the cookie was read correctly, the first PHP page imports a second page that utilizes the PHP header() redirect containing your original file name forcibly downloaded with another name. Using the Content Disposition header field.

    In action this works like this

    1: Original page with download links – we set the cookie to work for 2 minutes

    <a onclick="setCookie(1, 1, 2, 60)" href="php-secure-files-delivery-page.php">Download My Final PDF name.pdf</a>
    
    <script type="text/javascript">
    
        // set a cookie with your own time limits.
        function setCookie(days, hours, minutes, seconds) { // Create cookie
            var expires;
            var date = new Date();
            date.setTime(date.getTime()+(days*hours*minutes*seconds*1000));
            expires = "; expires="+date.toGMTString();
            document.cookie = "my_cookie_name"+"="+"my_cookie_value"+expires+"; path=/";
        }
    
    </script>
    

    On the link page we include a hyperlink with the evaluating PHP page. Here we use JavaScript to set a cookie using the custom function setCookie(days, hours, minutes, seconds), that will receive your wishes for expiry. Just note that 1 is the minimum number. Not 0.

    2: Download page – evaluating cookie and presenting texts, or simply downloading the file

    (php-secure-files-delivery-page.php)

    <?php
    
    // if the cookie is set correctly, load the file downloader page.
    
    if (isset($_COOKIE['my_cookie_name'] && $_COOKIE['my_cookie_name'] === 'my_cookie_value')) {
    
        require_once 'file-downloader.php'; // the file will force the download upon import.
    
    } else {
        
        die('The link expired, go to your downloads section and click on the link again.');
    }
    
    ?>
    

    Here we evaluate the cookie, present either the correct info or die(). Using require_once we get the PHP page into the current one.

    3: Imported file includer PHP page

    (file-downloader.php)

    <?php
    
    // We'll be outputting a PDF
    header('Content-Type: application/pdf');
    
    // It will be downloaded as your-downloaded.pdf
    header('Content-Disposition: attachment; filename="your-downloaded.pdf"');
    
    // The PDF source is in your own specified long name
    readfile('original-with-really-weird-original-name.pdf');
    
    ?>
    

    Results

    • User always go to the same page, being presented with the appropriate information.
    • You can name your original files on your server anything you want, like "my_really_difficult_and_long_file_name.pdf", while the user sees only the nice pretty file name when the file is downloaded.
    • for more files, use an extra input in the cookie function to take the file name too, and some if statements in the php downloader page, that looks for separate end PHP pages to require_once.
    • If you go to the browsers "Downloads" section to try to get the url of the downloaded file, you see the initiating PHP page, the second page, that leaves you empty with a die() if no correct cookie was set. That cookie is only set when you want it to. On your pages. You can of course do this in JavaScript too, but that will expose the cookie, still, for most unauthorized sharing, that takes care of it.

    Lastly, easy security for your folder (without Apache/Nginx/.htaccess stuff)

    Using .htaccess files on local folders or directives on your server is the best and most secure way. But that´s not transferable to your other applications on other systems. Instead use a index.php and a default.php page on your PDF file´s parent folder, where they are located, including this header redirect to wear off unwanted visits:

    <?php
        header("Location: http://yoursite.com/some-other-page/"); /* Redirect browser here */
    ?>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search