6

Working on a WordPress support site which has registered only user content, including uploaded PDF and ZIP files.

I'm looking for a way to prevent direct access to those PDFs and ZIP files inside the wp-content/uploads directory without the use of a plugin.

Reading through older questions, this is REALLY close (but comments are closed): https://wordpress.stackexchange.com/a/37743/18608 as it detects direct access to the file, then does some login to save the file requested and check to see if the user is logged in. If not, they get redirected to the WordPress login. If they are logged in, then it loads the file.

This is the original htaccess:

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

However, the script blocks access to all files inside the wp-content/uploads directory, including images - so unless the user is logged in, even blog post images (which don't need protected) are hidden on the front-end.

I'm trying to revise the RewriteRule to check for only PDF or ZIP files, so that the dl-file.php file only gets called if it's a PDF or ZIP being requested.

I've tried the following, but it returns "404-File not found." when accessing a PDF or ZIP when the user is logged in, so even though the file type check appears to be working, the dl-file.php check is failing.

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

How would I revise this so that it only calls the dl-file.php if it's a pdf or zip file being requested, but still pass the correct info to dl-file.php?

Thank you, Jonathon

/*
 * 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 );
4
  • Note that if you put this comment at the top of the answer /** Plugin Name: Zip Plugin **/ it becomes a plugin, but what you're asking looks like it has nothing to do with WordPress, a pure Apache HTAccess question
    – Tom J Nowell
    Commented Sep 29, 2017 at 18:45
  • Thanks - the original post I was referencing contains the dl-file.php with login check / redirect that is WordPress specific - that's what I'm having trouble getting to work. Trying to check for pdf and zip, and still getting the conditional login check to work for the redirect portion. I've added more details for clarity.
    – d38
    Commented Sep 29, 2017 at 19:47
  • I just noticed, you changed dl-file.php to filecheck.php? Presumably this is the same file?
    – MrWhite
    Commented Sep 29, 2017 at 21:54
  • Thank you, it is - I had changed it here, but kept it the same for the example. I missed my reference to it.
    – d38
    Commented Sep 29, 2017 at 22:16

1 Answer 1

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

This actually looks OK, except if you have additional subdirectories within the /uploads directory? An alternative is to include an additional condition on the original rule that only rewrites the request if the request ends in .pdf or .zip. For example:

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

It shouldn't really matter, but make sure this goes before the WordPress front-controller.

6
  • Thank you - this makes sense. I'm testing here, but for some reason it's not picking up on the login condition, so the PDF is loading when logged out?
    – d38
    Commented Sep 29, 2017 at 22:18
  • 1
    Thank you, MrWhite - this was the right direction. I couldn't get REQUEST_URL to work in the first RewriteCond, but changing to RewriteCond %{REQUEST_FILENAME} \.(pdf|zip)$ did the trick.
    – d38
    Commented Sep 29, 2017 at 23:32
  • 1
    Ahhh, sorry that was a typo, it should be REQUEST_URI, not REQUEST_URL! (I've updated my answer.) Glad you got it working!
    – MrWhite
    Commented Sep 29, 2017 at 23:45
  • Hello, Thanks for your trick. I have a question : If i'm not logged, i'm redirected to login page when i put a URL with PDF file on the browser. Is it possible to redirect to 404 page? thanks again.
    – Samuel
    Commented Apr 26, 2019 at 9:01
  • @sampaii You would need to do that in your download/authentication script (in PHP)... if the user is not logged in then respond with a 404 instead of redirecting - which is presumably currently being triggered by your script - if you have followed the example above?
    – MrWhite
    Commented Apr 26, 2019 at 9:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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