forms

I love WordPress. I like Gravity Forms. The only trouble with Gravity forms is if you add a file upload field to your form it is stored within your /wp-content/uploads directory and as such is publicly accessible.

Now the link that Gravity Forms generates is fairly long (mywebsite.com/wp-content/uploads/gravity_forms/reallylongstringofnumbers10239812039802193/year/month/filename) however that just isn’t secure enough for me. The worst part os they don’t seem to care.

After a week or two of searching I have found a way of securing it! The following method secures the folder in question so that only Logged in users can access them. It is also probably possible to modify this method to account for different user roles etc, but for now a little security is better than none!

Ok so we need to do 2 things.

  1. Tell our website that all files in a certain directory need to be passed to a special file for processing before downloading.
  2. Use our special file to check if the user is logged in!
First things first. Create a new file in the ROOT of your WordPress install called dl-file.php and paste in the following:
<?php
require_once('wp-load.php');
is_user_logged_in() || auth_redirect();
$upload_dir = wp_upload_dir();
//Set your path below I am using /gravity_forms/
$basedir = $upload_dir[ 'basedir' ] . '/gravity_forms/';

$file = rtrim( $basedir, '/' ) . '/' . str_replace( '..', '', isset( $_GET[ 'file' ] ) ? $_GET[ 'file' ] : '' );
if ( ! $basedir || ! is_file( $file ) ) {
 status_header( 404 );
 die( '404 &#8212; File not found.' );
}

$mime = wp_check_filetype( $file );
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
 $mime[ 'type' ] = mime_content_type( $file );

if( $mime[ 'type' ] )
 $mimetype = $mime[ 'type' ];
else
 $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );

header( 'Content-Type: ' . $mimetype ); // always send this
if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
 header( 'Content-Length: ' . filesize( $file ) );

$last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
$etag = '"' . md5( $last_modified ) . '"';
header( "Last-Modified: $last_modified GMT" );
header( 'ETag: ' . $etag );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );

// Support for Conditional GET
$client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;

if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
 $_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;

$client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
// If string is empty, return 0. If not, attempt to parse into a timestamp
$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;

// Make a timestamp for our most recent modification...
$modified_timestamp = strtotime($last_modified);

if ( ( $client_last_modified && $client_etag )
 ? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
 : ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
 ) {
 status_header( 304 );
 exit;
}

// If we made it this far, just serve the file
readfile( $file );

Looks kinda scary doesn’t it! Don’t worry about it! The only thing you need to pay attention to is line 8. This is where you set which folder you are protecting, for this I am using /gravity_forms/ the path is relative to your uploads directory.

Second in your .htaccess file you need to add the following:


#Gravity Forms Upload Protection
RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ dl-file.php?file=$1 [QSA,L]

The RewriteRule path needs to match the path you set in your dl-file.php so here you can see gravity_forms is mentioned again.

So what happens.

When a file is requested from within the Gravity Forms folder the request is passed via the .htaccess file to our dl-file.php. This file then checks to see if the user is logged in. If not logged in they are forwarded to the login page, after logging in the file is downloaded. If they don’t login. No file. Simple!

There are probably loads of improvements that can be made to this but it is a great start.

The credit goes entirely to http://wordpress.stackexchange.com/a/37743/12438 I simply took the code and made it work for me! Let me know what improvements you make to it!

 Tagged with: , , ,

28 thoughts on “Secure your Gravity Forms uploads

  • Still usefull after almost 3 years! Thanks for this Gravity-forms solution.

     Reply

    • Thanks I’m glad this is still useful to people! I’m surprised that after all this time it still hasn’t made it’s way into the Gravity Forms core!

       Reply

  • Great solution to a terrible oversight by Gravity Forms. I changed the authentication portion to this, so that it just throws a 404 error instead of redirecting to the login page:

    if(!is_user_logged_in()) {
    status_header( 404 );
    nocache_headers();
    include( get_query_template( '404' ) );
    die();
    }

     Reply

  • Excellent & elegant solution to a real problem. Works like a charm. Thank you so much!

     Reply

  • This is great. I would rather it redirected to the login page (with wp_login_url() set to redirect to the file). I’m still trying to get into the code, let me know if you have a fix for that handy.

     Reply

  • Hi, Is there a way to protect multiple folders?

     Reply

  • Hi – just wanted to say that I got this working now and it’s wonderful! I even used a modified version of your PHP file to password protect some additional directories unrelated to gravity forms. This was hugely useful and I am very grateful.

     Reply

  • Thanks! Working as expected. If you haven’t already sent this on to the Gravity Forms developers, please do. There’s no reason this shouldn’t already be included in the setup options or among their documentation.

     Reply

  • I just tried this and it did not work. I was still able to access uploaded documents even when not logged in. No idea why. Is there any specific place within .htaccess where the 3 lines of text should go?

     Reply

    • No but you do need to ensure that mod_rewrite is enabled and that the path to your uploads is set correctly in both files.

       Reply

      • Not sure I understand… the path in line 6 of dl-file.php? You have it as ‘/gravity_forms/’
        Do I need the full path, ralative to the wordpress root?

         Reply

        • Sorry, I meant relative, not ralative. :-)

           Reply

          • The path is relative to your WordPress uploads folder so in my example wp-content/uploads/gravity_forms.

            You only need to change it if you are using a directory named something different to gravity_forms.

             

          • I appreciate the help, thanks. It still did not work for me, but I have a feeling I have some deeper issues that need to be fixed. If your method works for 90% of users, you have a great idea here!

             

  • This is a great post and has been very useful to me. Thanks. One thing worth pointing out though is that if you use the Really Simple Captcha integration with Gravity Forms then it stores the generated png in the gravity_forms directory, as this script stands it will not be displayed on the front end.

    I modified it slightly so there is a check up front for the filetype to see if it’s a .png and then just serving the file if it is. ie;

    require_once('wp-load.php');
    
    $upload_dir = wp_upload_dir();
    //Set your path below I am using /gravity_forms/
    $basedir = $upload_dir[ 'basedir' ] . '/gravity_forms/';
    
    $file = rtrim( $basedir, '/' ) . '/' . str_replace( '..', '', isset( $_GET[ 'file' ] ) ? $_GET[ 'file' ] : '' );
    $mime = wp_check_filetype( $file );
    
    if ($mime[ 'ext' ] == 'png') {
    
    	// Its the captcha , we just serve the file with a basic header
    	header('Content-type: image/jpeg');
    	readfile($file);
    	die();
    }
    
    is_user_logged_in() || auth_redirect();

    Hope this helps, cheers.

     Reply

    • Thanks for your comment, I’ve also fixed your typo as you mentioned in your other comment.

       Reply

  • Thanks for putting this together! Saved me lots of time searching the web! Noticed that the copy on github is giving an error because it is missing:

    require_once('wp-load.php');

    I also modified it to only allow access to the gravity_forms folder if the user is an admin by replacing:

    is_user_logged_in() || auth_redirect();

    to include the user role:

    ( (is_user_logged_in() ) && (current_user_can('administrator')) ) || auth_redirect();

     Reply

    • Thanks for letting me know I’ve updated the Gist.

       Reply

  • I have uploaded the dl-file.php to the root of the wp installation, and made the following alterations (see below) to my .htaccess file – but when I navigate to the uploaded file in the gravity forms directory I don’t get redirected. Instead, my browsers just show the text from the the dl-file.php in a paragraph. I have messed around with this for hours, please tell me I’m overlooking something really simple here? Thanks, Owain

    #Gravity Forms Upload Protection
    RewriteCond %{REQUEST_FILENAME} -s
    RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ dl-file.php?file=$1 [QSA,L]
    
    # Use PHP5 Single php.ini as default
    AddHandler application/x-httpd-php5s .php
    
    # BEGIN WordPress
    
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
    
    
    # END WordPress
    AuthName "topsete1"
    AuthUserFile "/home2/topsete1/.htpasswds/public_html/passwd"

     Reply

    • Nothing I can see there although I did notice that in my code example there was a closing peer tag at the start for some reason (probably from when I changed syntax highlighters). Do you have that in your code if so remove it. And make sure you have a opening php tag at the start of the file as well!

       Reply

      • Please excuse how silly this is going to sound, but what does a closing peer tag look like? Do you mean ‘?>’?

         Reply

      • In the line below, is the closing tag the ‘;’ ?

        Thanks for your help!

         Reply

        • Sorry for the confusion I made my reply from my iPad which auto-corrected everything I said.

          Ok there were 2 issues with my code example.

          1) On line 1 there was a

          </pre>

          tag which shouldn’t have been there.
          2) I didn’t including the opening

          <?php

          tags as I hoped everyone would insert that themselves.

          I’ve fixed the code example above and have also put the code on GitHub so you can simply get the whole thing. Let me know if you get this working!

          GitHub Gist

           Reply

  • Works great for me! Do you think this could made into a Gravity Forms add-on?

     Reply

    • Possibly but as it needs to write to the .htaccess file to make it work I’m not sure how easy it is do do this from a plugin.

       Reply

      • Hey man,

        I’ve used Super Cache in the past and it def. does write to the .htaccess file. I’m way more of a theme author than plugin developer, so I have no idea how it is actually accomplished. But I have seen it happen.

        Also, the Retina @2x plugin writes to the .htaccess file as well.

        And of course, thanks for the code!

         Reply

  • Brilliant solution, I’m trying it out at this very moment. Thanks for sharing it!

     Reply

  • Thanks, I will give this a try.

     Reply

Leave a Reply

Mojowill Avatar

Who the Hell am I?

I'm Will, a full time web developer, geek and musician. I develop using PHP and MySQL and spend most of my time working with WordPress or CakePHP. When I'm not buried in code I'm gaming, cooking or writing and recording music in my studio. I like sci-fi, pancakes and coffee and am totally prepared for the zombie apocalypse...

Stalk me on these other sites...

Why not be super creepy and check me out on all these other sites, I think they call it social media?