Skip to content

Instantly share code, notes, and snippets.

@hakre
Created January 2, 2012 21:41
Show Gist options
  • Save hakre/1552239 to your computer and use it in GitHub Desktop.
Save hakre/1552239 to your computer and use it in GitHub Desktop.
Wordpress login to download uploaded files
<?php
/*
* dl-file.php
*
* Protect uploaded files with login.
*
* @link http://wordpress.stackexchange.com/questions/37144/protect-wordpress-uploads-if-user-is-not-logged-in
*
* @author hakre <http://hakre.wordpress.com/>
* @license GPL-3.0+
* @registry SPDX
*/
require_once('wp-load.php');
is_user_logged_in() || auth_redirect();
list($basedir) = array_values(array_intersect_key(wp_upload_dir(), array('basedir' => 1)))+array(NULL);
$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 );
@Dalias96
Copy link

Thanks for this solution, its great!

I had to make changes in favor of supporting custom folder in wp-content/uploads dir

So, if you want to use subfolder, your .htaccess should look like this:

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

where subfolder stands for your custom folder.

Next, you should add variable on the very begining of dl-file:

$subfolder = 'subfolder/';

and then change line 20 (from original file, if you add var before, line number will change) to:

$file = rtrim($basedir,'/').'/'.$subfolder.''.str_replace('..', '', isset($_GET[ 'file' ])?$_GET[ 'file' ]:'');

@dabock
Copy link

dabock commented Sep 28, 2021

Thanks for this solutions and all the contributions.
The (adapted) code still works.

If you have a ngnix + apacher server, you may need to do a ngnix redirect (instead of the .htaccess redirect).

For me, the following code made it work (I only restrict access to the folder /uploads/subfolder)

rewrite ^/wp-content/uploads/subfolder/(.*)$ /dl-file.php?file=$1 permanent;

@JPOak
Copy link

JPOak commented Sep 29, 2021

This is a great gist, which I have on follow. I am actually surprised that Direct Access Protection isn't discussed more in the Wordpress community. A lot of people mistake the many "Members Only" plugins providing this, but they do not. There is a third party service, but it is pretty expensive for smaller private sites.

I implemented this solution and it did work, but it slowed down certain aspects of the site. For example galleries loaded much slower and some images failed to be served. I turned off lazy load that did improve some things, but still not great.

@anthony-curtis
Copy link

anthony-curtis commented Sep 30, 2022

There were a few items needed to make this work on a shared hosting environment:

Make sure RewriteEngine On was at top of the .htaccess file, or somewhere before your htaccess changes like so:

RewriteEngine On

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

Disable any special CDN in your site tools or cPanel, and purge server cache between changes for sanity's sake.

Otherwise, the original htaccess and dl-file.php worked perfectly.

@Tsjippy
Copy link

Tsjippy commented Nov 3, 2022

I have no clue why this doesn't work:
.htaccess:

# BEGIN THIS DL-FILE.PHP ADDITION
RewriteEngine  on
RewriteRule ^wp-content/uploads/private/(.*)$ dl-file.php?file=$1 [QSA,L]
# END THIS DL-FILE.PHP ADDITION

Then the normal dl-file.php but with this on top:

<?php
 file_put_contents(__DIR__.'/dl-file.log', $_GET['file']."\n", FILE_APPEND);

This somehow only works for .jpe files. I tried a .jpg, .jpeg, .doc file and they are not triggered by the RewriteRule. Why?

@joelseneque
Copy link

I can't get this to work with my Wordpress site.
The redirect to Login works fine but when displaying the image it shows a blank file

@meelad2
Copy link

meelad2 commented Jun 20, 2023

I can't get this to work with my Wordpress site. The redirect to Login works fine but when displaying the image it shows a blank file

The problem was an unwanted blank line at the first of file.
Add these codes before the last line(67):

ob_clean();
flush();

Source: https://stackoverflow.com/a/8041597

@davidstaab
Copy link

I used this thread and a couple of other sources to implement a "media access control" solution for WordPress. My code draws on what I learned from this gist, so I want to share what I made. I'm a novice PHP developer and would love feedback and suggestions from this community! wp-mac, a Media Access Control solution for WordPress sites

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment