1
I own a wordpress-based Learning application, and I have several video lessons in the directory public_html/wp-content/uploads/...
I would like to prevent direct access to these videos, since it is necessary to purchase the course to be able to attend them.
In studying the question I came across the following propositions of solution to this problem:
Solution 1: Block requests coming from the WEB, using a file .htaccess
, to my video files (.mp4) and serve them to the client with PHP, through the function readfile()
. This way I could check if the user can access the file before serving it. This solution is functional, but I did not like it, because I believe that the server’s resource consumption will be very high, since PHP will be reading/processing the files.
In this case I would add a file .htaccess
in wp-content/uploads/
with the following Directives:
Rewriterule \.mp4$ dl-file.php [L]
And would use a script (dl-file.php
) analogous to this to serve the video file :
/*
* 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 );
In this case the video files would not be outside of public_html
, since I would still need to assess the consequences of this, for my Wordpress application, and still the impacts under some utilities I use, based on the internal API of the WP-CLI.
Solution 2: Using an apache module called mod_xsendfile
to serve the file, after checking, with PHP, whether or not the user can get access to that video. mod_xsendfile
is an unofficial module, from a third party, and the latest update dates from 2012. Because of these factors, added to the fact that the project does not have an active community, I chose not to use this solution.
Solution 3: Prevent direct access through a file .htacess
performing header checking %{HTTP_REFERER}
. This is not a reliable way, since I must allow, for usability reasons, access when %{HTTP_REFERER}
is also empty, that is, just a customer modify %{HTTP_REFERER}
to have access to video lessons (.mp4) private. If the value of %{HTTP_REFERER}
is reliable, 100% of the time, this would be a good solution, because the delivery of the video file would be conditioned to my application, which decides whether or not the user should access the class.
Solution 4: Using a plugin called Prevent Direct Access To deliver the file to the user the plugin uses a script very similar to the one displayed on solution 1, which also made it unviable, plus the cost of the license is high. But in general it is a good solution when the files to be made available are not very large.
Solution 5: Finally I imagined a possible solution to my problem, but I still lack the necessary knowledge to verify its applicability, follows:
Whenever a user performs a direct request to a video file I perform a flag check through a file .htaccess
. As for this flag I’m still not sure what it might be, a parameter get
, or an apache environment variable, or a header.
If this flag has a given value I send the file, or if it has another value, other than necessary for the file to be sent, it is not sent to the user. Finally if this flag does not exist, at the time of checking, I rewrite the URL with a directive RewriteRule
which will point to a PHP script.
This script checks whether or not the user can access the video file (checks if it is logged in and if it has purchased the course, basically) and then arrow the respective flag, and make a new request to the video file, which will cause the .htaccess
previously run again, but now the flag exists as it was set in the script. Thus the file .htaccess
will find the flag set, and depending on the value decides whether or not to deliver the requested file to the user. After sending the reply the flag is then destroyed.
As I mentioned, I cannot say if this is possible, and what structures to use. It is a supposed solution, which I need to check if it is possible. If not how I could do to prevent direct access to videos, remembering to consider the solutions I have already found and discarded previously.
put . htaccess com: Deny from all
– Lucas Antonio
But in this case how would I serve the user the videos? Would I have to read them with PHP?
– Allan Dantas
About 2, although it is not something very up-to-date (after all, it’s quite simple, it doesn’t have anything to update), it has no problem using X-Sendfile in Apache currently. What was the difficulty found? Inclusive, has available ready for many distros. Ex: https://centos.pkgs.org/7/epel-x86_64/mod_xsendfile-0.12-10.el7.x86_64.rpm.html. - The only way I see for you is to actually allow access after login, either by X-Sendfile or even by a PHP serving file saved outside the site root (which is the normal and simple to do in such cases).
– Bacco
Thanks for the time @Bacco. I checked solution 1 in part, and it proved to be functional, even though, theoretically, it appears to be very costly to my eyes. Why do you consider it ineffective ? Regarding the use of
X-SendFile
, after researches, I came across some opinions contrary to its use ( this for example) and although, theoretically the ideal solution for me this made me back a little. Have you used this module? As for "a PHP serving file recorded outside the site root" in this case I would have to process the file, which I wish to avoid.– Allan Dantas
Use X-Sendfile constantly to relieve PHP’s work, but remember that if you don’t want to use it, you have the
readfile();
PHP itself, which can get files out of the root without problem (which is partially its 1). I only prefer the module precisely to not have PHP processing the stream for no reason if I can leave it to Apache. About the link, it is an opinion, of a person, that may be valid, but has to relativize a bit.– Bacco
About 1, I talked about htaccess, not readfile() - in the sense that it is better to put the files out of the root, instead of having to create locks. I don’t understand what you mean by "having to process the files" in that context.
– Bacco
@Bacco, I get it. I’ll turn to the mod_xsendfile study, based on your settings. I’ll run some tests to verify its applicability in my case. If you can formalize your statements as an answer to the main question I can classify it later and use it in the question’s Edit. Thanks for your help.
– Allan Dantas
@Allandantas if you prefer to keep the readfile, as long as the server has resources slack, I see no problem, but the fundamental thing is with sendfile or readfile, is the file is outside the root instead of just blocked
– Bacco
@Bacco, Wordpress keeps your media inside a directory on
public_html
, would have to analyze the implications of this change. The solution I have in mind is to useFilesMatch
to identify when a request is being made to a file.mp4
and then transfer the responsibility of responding to a PHP script, which would send the file withX-Sendfile
. What you think about ?– Allan Dantas
I keep thinking the same, that it’s better to take it from the root
– Bacco
Let’s go continue this discussion in chat.
– Allan Dantas