I use wordpress for a private site where users upload files. I use the "Private WordPress" to prevent access in to the site if the user is not logged in.

I would like to do the same to the files uploaded in the uploads folder.

So if a user its not logged in they wont be able to access to : https://xxxxxxx.com/wp-content/uploads/2011/12/xxxxxxx.pdf if they try to access but they are not logged then they should be redirected to login page for example.

I found a plugin called private files but last time updated was in 2009 and it dont seems to work on my wordpress.

Anyone know any method? Hotlinking method will be enough to protect this?

I also found this method :

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^.*uploads/private/.*
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in.*$ [NC]
RewriteRule . /index.php [R,L]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

But then any user that replicate the cookie could pass this right? Regards

  • 1
    Any reason why you can't use a different upload directory, like one outside of the site root? – onetrickpony Dec 22 '11 at 17:11
  • Not really but i already got tons of files attached to posts in that directory, i dont mind on moving all around if i can find a proper solution – chifliiiii Dec 22 '11 at 17:37
up vote 73 down vote accepted
+50

Only checking if the cookie exists, is not much of a strict protection.

To get a stronger protection, you can pass or "proxy" all requests to the uploaded folder (exemplary uploads in the following example) through a php script:

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

All requests to uploaded files (which includes images in posts) would go to dl-file.php which then can do verify if the user is logged in or not.

If the user is not logged in, your sites login-form will be shown. After the user logged in, she will get redirected back to the file and can download it now.

Exemplary dl-file.php.

Something similar can be found in \wp-includes\ms-files.php in your wordpress installation, but that one is for multisite and w/o the login check and redirects.

Depending on how much traffic you have, it could be wise to better integrate this with your server, e.g. X-Accel-Redirect or X-Sendfile headers.

  • 1
    how do you adjust dl-file.php if i want to store files into a subdirectory such as wp-content/uploads/secure ? – user11958 Jan 11 '12 at 22:54
  • This is the only really secure solution. Anything else you can find on the web, like checking referer header, checking cookies, disallow directory listing, is a half measure since you can easily spoof HTTP requests headers to get around it. – Luke Sep 20 '13 at 8:58
  • 1
    What's wrong on my side? it gives me 404 Error and when I update .htaccess file in WP Dashboard it reverted back my changes, too bad please help me. – Ryan S Feb 17 '14 at 10:29
  • 1
    @Ryan S: You're editing the wrong file. Also please don't cry for help, instead contact professional support. Thanks. – hakre Feb 17 '14 at 14:27
  • 1
    I get a 404 Not Found The resource requested could not be found on this server! – Pete Jan 19 '17 at 15:25

You can also write a plugin using the init hook and the get-value $_GET[ 'file' ];. If the user has this get-value, jump in a function to check the rights for access on the files: For example with a checkbox in a Meta Box.

add_action( 'init', 'fb_init' );
function fb_init() {
    // this in a function for init-hook
    if ( '' != $_GET[ 'file' ] ) {
        fb_get_file( $_GET[ 'file' ] );
    }
}

the function get_file()

function fb_get_file( $file ) {

    $upload     = wp_upload_dir();
    $the_file   = $file; 
    $file       = $upload[ 'basedir' ] . '/' . $file;
    if ( !is_file( $file ) ) {
        status_header( 404 );
        die( '404 &#8212; File not found.' );
    }
    else {
        $image = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attached_file', 'value' => $the_file ) ) ) );
        if ( 0 < count( $image ) && 0 < $image[0] -> post_parent ) { // attachment found and parent available
            if ( post_password_required( $image[0] -> post_parent ) ) { // password for the post is not available
                wp_die( get_the_password_form() );// show the password form 
            }
            $status = get_post_meta( $image[0] -> post_parent, '_inpsyde_protect_content', true );

            if ( 1 == $status &&  !is_user_logged_in() ) {
                wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                die();
            }
        }
        else {
            // not a normal attachment check for thumbnail
            $filename   = pathinfo( $the_file );
            $images     = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attachment_metadata', 'compare' => 'LIKE', 'value' => $filename[ 'filename' ] . '.' . $filename[ 'extension' ] ) ) ) );
            if ( 0 < count( $images ) ) {
                foreach ( $images as $SINGLEimage ) {
                    $meta = wp_get_attachment_metadata( $SINGLEimage -> ID );
                    if ( 0 < count( $meta[ 'sizes' ] ) ) {
                        $filepath   = pathinfo( $meta[ 'file' ] );
                        if ( $filepath[ 'dirname' ] == $filename[ 'dirname' ] ) {// current path of the thumbnail
                            foreach ( $meta[ 'sizes' ] as $SINGLEsize ) {
                                if ( $filename[ 'filename' ] . '.' . $filename[ 'extension' ] == $SINGLEsize[ 'file' ] ) {
                                    if ( post_password_required( $SINGLEimage -> post_parent ) ) { // password for the post is not available
                                        wp_die( get_the_password_form() );// show the password form 
                                    }
                                    die('dD');
                                    $status = get_post_meta( $SINGLEimage -> post_parent, '_inpsyde_protect_content', true );

                                    if ( 1 == $status &&  !is_user_logged_in() ) {
                                        wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                                        die();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    $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 );
    die();
}

You can also add a custom URL for files via the hook generate_rewrite_rules

add_filter( 'generate_rewrite_rules', 'fb_generate_rewrite_rules' );

function fb_generate_rewrite_rules( $wprewrite ) {
        $upload = wp_upload_dir();
        $path = str_replace( site_url( '/' ), '', $upload[ 'baseurl' ] );
        $wprewrite -> non_wp_rules = array( $path . '/(.*)' => 'index.php?file=$1' );
        return $wprewrite;
}
  • This didn't work on my side anyone know why? I copy exactly. – Ryan S Feb 17 '14 at 10:40
  • Protection working only pdf. other files extension not working like : doc, docx, jpg and etc... – Patel Aug 23 '17 at 10:43

If you would like a plugin-based approach to solving this problem, here is a reasonably good solution that I have (finally) found:

  1. Install the plugin 'Download Monitor', available at:
    https://wordpress.org/plugins/download-monitor/
  2. In the WordPress Dashboard, go to the new 'Downloads' menu item and add a new 'Download', as described on the plugin documentation website here: https://www.download-monitor.com/kb/adding-downloads/. Take note of the 'Download' shortcode provided for you (eg. save to Notepad). Note that the file gets saved in /wp-content/uploads/dlm_uploads/
  3. In the 'Download options' metabox, specify 'Members only' (as documented here https://www.download-monitor.com/kb/download-options/), and click 'Publish'.
  4. On the page that you want the Members only download to appear, add in the shortcode you took note of in step #2, and 'Publish/Update' the page, as documented here: https://www.download-monitor.com/kb/shortcode-download/. You can change the download link template as described here https://www.download-monitor.com/kb/content-templates/, or create your own (eg. to remove the Download 'count')
  5. Browse to your page, you should see a download link (but which does not reveal the URL to the download file). If you browse to the same page in a new browser window (or Incognito window), you should find that the download no longer works.

This means that anyone not logged in cannot either download the file or see the real URL to the file. If in the event that someone unauthorised figures out the URL to the file, the plugin also stops users browsing to the real file URL by blocking access to the /wp-content/uploads/dlm_uploads/ folder.

Bonus: if you a doing this for a site where you need users to be able to login as 'Members' only (but have no WordPress permissions like page editing or being an Admin), install the 'Members' plugin https://wordpress.org/plugins/members/, create a new user role called 'Member', and give it the single capability of 'read', create a new User in WordPress, and make sure to give them a role of 'Member'.

If you want protect the content of pages, the 'Members' plugin provides some options, or there are other plugins out there. If you want to theme the login page for Members to look better than the WordPress default login form, use something like 'Theme My Login': https://wordpress.org/plugins/theme-my-login/

protected by fuxia Aug 2 '12 at 20:36

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.