We use cookies to improve your experience. No personal information is gathered and we don't serve ads. Cookies Policy.

ExpressionEngine Logo ExpressionEngine
Features Pricing Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University
Log In or Sign Up
Log In Sign Up
ExpressionEngine Logo
Features Pro new Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University Blog
  • Home
  • Forums

Tutorial: Using internal files with the file uploader

How Do I?

vw000's avatar
vw000
482 posts
4 years ago
vw000's avatar vw000

Someone else asked how to put an internal folder for file uploading with Expression Engine such as:

/home/yourserver/file-folder

Instead of a public one such as:

/home/yourserver/public_html/file-folder

The obvious answer was that this would not work because the user would not have access to that file on your website since it’s not public.

Why would you want this?

For internal files that should not be public. Example, customer files, you don’t want them to be able to link or be indexed by search engines. It’s common for many systems to use an internal folder for such files that is not available through your web server in a public way.

Tonight I had a bit of time to play around, so I decided to come up with a simple solution. This way you can configure an internal folder for your files uploads and then restrict it to only logged in members. This will not require any changes to your existing template or file links.

  1. Create a template file that we are going to use to download or view the files. You are going to link to this later.

For example, let’s create a template called ‘view-file”. Make sure to enable PHP on the template and select the parsing option as Input

  1. Restrict the template on your EE settings to the users or roles you want to be able to access. This way only authenticated users will be able to download the files.

  2. Create your internal file uploader, example:” /home/yourserver/file-folder

This should be above your public web path.

  1. Now go to your File upload directory on EE configuration and change the Upload directory field to the previous created template file “view-file” or the name you used.

Assuming this template is in a template group like ‘customers’ available on example.com/customers/view-file

Your new setting should the following:

{base_url}customers/view-file?f=/

Note: I’m not using the extension here because it looks better for URL’s but if your server does not support this you should use:

{base_url}customers/view-file.php?f=/

Note the trailing slash is added automatically by the EE control panel, but my code will try to fix this. Don’t bother with it.

Next set your upload path to your internal file directory such as:

/home/yourserver/file-folder

Save your settings.

  1. Now paste the following PHP code in your previously created view-file:
<?php
# Get internal files for Expression Engine for logged users only

// Get from URL
$FileDownload = $_GET["f"];

// We remove the EE trailing slash /
$eeFile = substr($FileDownload, 1);

// Get the file extension
$ext = substr(strrchr($eeFile, '.'), 1);

// Check if file is allowed. Add allowed extensions here
$allowed = array('gif', 'png', 'jpg');
if (!in_array($ext, $allowed)) {
    echo 'ERROR: Forbidden file!';
    goto end;
}

// Internal file path on server. Replace with your existing upload folder from EE
$file = "/home/yourserver/ee-file-upload-folder/$eeFile";

// Check if file exists or fail otherwise
if (file_exists($file)) {
    // Continue
} else {
    echo "ERROR: File not found!";
    goto end;
}

// Stream to browser
if (file_exists($file)) {
    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);
}
?>
{exp:http_header content_type="application/octet-stream"}
<?php
// EE does not like PHP exit codes
end:
?>

Change the line in the code to your EE upload folder (leave the $eeFile variable for the file name)

$file = "/home/yourserver/ee-file-upload-folder/$eeFile";

Done!

Your files are now private and only logged users can download them.

If you check the code, it removes the trailing slash from EE, it actually works fine without that but just in case some browsers don’t support it, we remove it to make sure the file passed has the proper name.

Since we are passing anything here as a GET, I added a security check that will check only for the valid extensions. We don’t want someone trying to tamper with the data they can pass to your installation. You can add more extensions by editing the $allowed field.

Improvements that could be added:

The code will send the file as a download to the browser. This can probably be changed in case you want to display the content in the browser (like images) but this will probably fail if you then try to link to another type that should be downloaded. Ideally the code could, should detect which files can be downloaded or output to the browser, but it would require further coding.

For some reason when the file does not exist, or you call the URL directly Expression Engine will output a session with the result. Not very clean, not sure why, but I created this in one hour and did not have time to investigate yet. Probably something related to the headers output.

You could also run the code using EE variables conditionals if you like that check if a user is logged in, for a specific user only or members.

One problem is that while Expression Engine has in the database the owner of the file that uploaded it, there are actually no tags to check this on a template. The issue is that one user can potentially download files from others (assuming they know the file name). A possible quick fix would be renaming the file on upload directly from your EE template to random characters. Will not solve the problem if someone knows the link but at least it can avoid someone from trying to guess popular file names.

For this reason this does not protect the files from other logged-in users. It just keeps them private, so they cannot be viewed without having to log in or linked on public sites.

       
vw000's avatar
vw000
482 posts
4 years ago
vw000's avatar vw000

I just noticed the code works fine outside EE but inside the template it seems to corrupt the file download. It probably has to do that EE does not respect the PHP headers. Had a similar problem with a JSON output in the past.

There is a plugin for this here: https://docs.expressionengine.com/latest/add-ons/http-header.html

I will try to see how to fix this, not sure if this is the issue, but files seems corrupt after download. Code works fine outside of Expression Engine the same files uploaded by EE.

       
vw000's avatar
vw000
482 posts
4 years ago
vw000's avatar vw000

UPDATE FIX

Fixed the corrupt download issue. It’s related to the PHP exit codes in EE which adds an extra EE tag (session?) to the file binary data, and also the headers are sent as HTML instead of a stream. Fixed both issues, and file downloads should now be fine.

I have updated the code above.

But you need to install the Header module add-on in your EE website to make it work:

https://docs.expressionengine.com/latest/add-ons/http-header.html

Future Improvements:

EE does not like the die or exit codes in PHP. It will add extra garbage to the output. If you remove them, then your messages will not be displayed since the header will not be HTML.

       
vw000's avatar
vw000
482 posts
4 years ago
vw000's avatar vw000

UPDATE FIX

Fixed the errors codes. Now they display correctly without using PHP exit or die codes that stops EE execution. Since this was still reading the EE header tag it was not working before as it tried to download the page instead of displaying the errors.

The solution was to set PHP as Input in the parsing stage.

So make sure when you enable PHP on the file template to select Input instead of Output. Code is now updated with the fix.

       

Reply

Sign In To Reply

ExpressionEngine Home Features Pro Contact Version Support
Learn Docs University Forums
Resources Support Add-Ons Partners Blog
Privacy Terms Trademark Use License

Packet Tide owns and develops ExpressionEngine. © Packet Tide, All Rights Reserved.