-
Star
(150)
You must be signed in to star a gist -
Fork
(50)
You must be signed in to fork a gist
-
-
Save hakre/1552239 to your computer and use it in GitHub Desktop.
<?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 — 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 ); |
@jmeile The Cookie method is much more straightforward. However, from what I understand is not very secure and easily hackable. People can correct me if I am wrong.
Thanks for your alternative method. I am concerned about speed. I noticed that my galleries are loading slower. Maybe it's just in my mind, but worth me checking into it. I would love to find a solution that is stable.
Thanks again.
@jmeile: Yes, this info is dated. Please see a PHP application's documentation that also has dedicated documentation (no idea if Wordpress fails the docs here, have not checked) https://www.dokuwiki.org/config:xsendfile . It falls into the domain of server configuration, therefore I would not expand on it. But I'm happy if you or others share in comments here.
@jmeile: Yes, it depends and the rule of thumb: Insecure. I would consider a specific cookie method also as server configuration and would not extensively comment on it myself here.
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' ]:'');
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;
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.
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.
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?
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
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();
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
Why the conditional for IIS at L36 ?
@dagoss IIRC the code originates from WordPress, so this is from upstream. Have you taken a look there?
I just read a bit that StackExchange post where the original author of this Gist posted. He wrote:
So, I found that there is a module that implements "X-Sendfile", which does what we need here: instead of handling the media files through php, they will be forwarded to Apache, which is more efficient.
The only problem: although that module works with Apache 2.4, it seems to be deprecated (or at least that's what people says). I found that this might be because it is not a standard module, it was designed initially for 2.2, and it hasn't been updated since long time ago; however, it seems that the last version, from March 2012, solved issues, which made it working for Apache 2.4; I installed that version and got it working.
Anyway, for the interested, here is how this is done:
This will give you access to the command: "apxs", which is used to compile and install the mod_xsendfile module. If you are using Windows or any other distro, then you will have to research how to install this by yourself. There are some Visual Studio and binary files here: https://github.com/nmaier/mod_xsendfile
Now get the module file: "mod_xsendfile.c" from here: https://github.com/nmaier/mod_xsendfile
The documentation can be read here: http://htmlpreview.github.io/?https://github.com/nmaier/mod_xsendfile/blob/master/docs/Readme.html
Then install it by running:
Please note that if "/var/www/html/your_wp_root" is a symlink, then you need to include the real path because php won't use the symlink.
The only difference on those lines and my previous code is that there you are enabling mod_xsendfile for dl-file.php.
So, the only two differences are:
to instruct apache to handle the media file
which is no longer necessary.
I guess this will make serving the files faster. You may try both and see if there is a difference.
And finally, I found that Apache 2.4 included the directive: "EnableSendfile"; however, I don't know how to use it. Perhaps you even don't need that old mod_xsendfile module.
Best regards
Josef