You can try the simple medium where PHP reads the entire file and dispatches it to the user. However, this technique has a high cost because it consumes memory, and the larger the file, the greater the consumption.
We can consider large files from 1mb. And this is proportional to the volume of accesses. In a very busy site for example, 10kb is also heavy. Anyway.
One feasible option is to use in conjunction with the module X-Sendfile.
If you can install X-Sendfile on the server, it is a better option regarding the use of functions such as file_get_contents()
, readfile()
and fopen()
.
The logic in using X-Sendfile is that a "route deviation" is made during PHP processing where the web server itself (apache, Nginx, etc.) will dispatch this file to the user, without using PHP to read the file.
In the image above, the traditional process using only PHP.
① User requests
② Webserver sends data to PHP
③ PHP processes (does authentication and anything else)
Then read from it.
④ PHP writes the contents of the file to the webserver
⑤ The webserver dispatches to the user
See the same situation with X-Sendfile
① User requests
② Webserver sends data to PHP
③ PHP processes (does authentication and anything else)
Then send a header to the webserver (PHP work ends here).
④ webserver understands the header and invokes the X-Sendfile module that returns the path of the file to be dispatched.
Note: X-Sendfile does not read the contents of the file. Just let the webserver know that the content to be dispatched is in the path specified in the header provided by PHP.
Practical example
It’s not a complete example of an authenticated download as the question asks, but it’s a useful example of how to use X-Sendfile.
Create a folder on your localhost or wherever you prefer:
/xsendfile
Inside that folder create a folder imgx
and an archive index.html
with the following content:
ok, imagem<br>
<img src="img/1.jpg">
Remember that we previously created the folder imgx
but in HTML we are calling img/
that doesn’t exist.
The structure is like this
/xsendfile
/index.html
/imgx/1.jpg
The archive 1.jpg
is any image for testing. It must be inside the folder imgx
. This folder will be accessible by X-Sendfile.
Create a file .htaccess
with the following content:
XSendFile on
RewriteEngine On
RewriteRule ^/?img/(.*)$ /xsendfile/img.php?file=$1 [L,R=301]
Save it in the folder /xsendfile
Now the structure is like this:
/xsendfile
/.htaccess
/index.html
/imgx/1.jpg
Let’s create the router. In htaccess is img.php
.
That is the code:
<?php
date_default_timezone_set('Asia/Tokyo');
ini_set('error_reporting', E_ALL & ~E_STRICT & ~E_DEPRECATED); // & ~E_NOTICE
ini_set('log_errors', true);
ini_set('html_errors', false);
ini_set('display_errors', true);
/*
The images must be on the same directory or into subfolders.
Not allowed to load images from up folders.
Must turn it on into htaccess file:
XSendFile on
*/
/*
true: send file headers
false: do not send the file headers
*/
define('FILE_HEADERS_ENABLED', false);
if (isset($_GET['file'])) {
$file = $_GET['file'];
}
// File's absolute path
/*
Lembra da pasta "imgx"? É aqui que setamos o caminho real.
Lembre-se que o nome da pasta deve ser dificil de ser advinhada caso esteja num diretório público. Mais para frente explico porque nesse exemplo colocamos numa pasta pública.
*/
$path = __DIR__.DIRECTORY_SEPARATOR.'imgx'.DIRECTORY_SEPARATOR.$file;
// Checking if path exists.
// Se o arquivo não existir, carregará uma imagem padrão.
if (!file_exists($path)) {
// Path not found. Will load default missing image.
$path = __DIR__.DIRECTORY_SEPARATOR.'imgx'.DIRECTORY_SEPARATOR.'no_image.gif';
}
/*
Aqui ou em qualquer outra parte que for conveniente para o seu caso,
poderia chamar rotinas de autenticação, por exemplo.
*/
$autentica_algo = true;
if ($autentica_algo === false) {
// aqui não autenticou, então interrompe, envia um arquivo falso ou sei lá.. Cada um inventa a firula que quiser.
}
/*
Os headers abaixo pode apagar se quiser. Não faz diferença. Mas dependendo do caso pode ser útil.
Normalmente utilizo para testes.
*/
header("Cache-Control: no-cache, must-revalidate"); //HTTP 1.1
header("Pragma: no-cache"); //HTTP 1.0
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
if (FILE_HEADERS_ENABLED) {
$info = getimagesize($path);
header('Content-type: '.$info['mime']);
header('Content-length: '.filesize($path));
header('Content-Disposition: inline; filename="'.basename($file).'"');
}
// Sent the x-sendfile header
// Esse header invoca o módulo X-Sendfile.
header('X-Sendfile: '.$path);
/*
Aqui pode usar um exit se preferir que o script não continue. Mas normalmente já interrompe no header acima.
Por via das dúvidas, interrompa explicitamente.
*/
exit;
With all this we have the structure:
/xsendfile
/.htaccess
/index.html
/img.php
/imgx/1.jpg
Run and see what happens: http://localhost/xsendfile/
The image in the folder will be displayed imgx
even though there is no place img/1.jpg
.
Try removing or disabling . htaccess and see what happens. Returns a not found in the image as there is no routing and therefore will not load the image invoked by X-Sendfile.
You can also try directly in the browser http://localhost/xsendfile/img/1.jpg
because htaccess takes care of the redirect and the img.php
will invoke the X-Sendfile.
How to install X-Sendfile?
To use X-Sendfile you usually need administrative access to the server (dedicated, vps). Usually in shared hosting there is no module installed and hardly the support of the provider would make the installation. Therefore this solution is usually more accessible to those who have administrative access to the server environment.
*Considering Apache as webserver
For Windows: https://www.apachelounge.com/download/ or https://tn123.org/mod_xsendfile/
For Linux: https://tn123.org/mod_xsendfile/
Additional explanation of the structure of the example
To better understand by following the example above:
/xsendfile
/.htaccess
/index.html
/img.php
/imgx/1.jpg
To further protect the files in the folder imgx/
, we could put it out of public folder.
c:/ww/private/imgx/1.jpg
c:/ww/public/xsendfile
/.htaccess
/index.html
/img.php
This would be ideal, but X-Sendfile does not allow directory indentation for security reasons. It is possible to apply this way by making some implementations but, to avoid complicating the example, I demonstrated in a simpler way.
The module is open source and anyone can recompile and remove such restrictions. But only do so if you know what you are doing.
Remarks on the use of X-Sendfile
It usually conflicts with URL rewriting rules.
If you have rewrite rules on an already working system, prefer to use the X-Sendfile separate from that system. Unless you’re a Mazoquist and want to do tricks to make it compatible.
Cannot upload to X-Sendfile header, files above the directory from which it is invoked. That is, it is not allowed to define a path with indentation of directories.
The module is developed by a third party without support from a company that guarantees continuity. This is perhaps the weakest point because the day the developer fails to maintain, all who use it will be without updates and support or will have to learn to read the code and give continuity to the project. Anyway, it depends on the Velopers community.
Are you using any framework? I just went through it and was using Windows. The basic logic is to remove from public (of course) and "stream" the file in an authenticated route.
– Rafael Mena Barreto
@Rafaelmenabarreto good idea. If you want to add this as a response. And yes, I use Laravel. If you have any way to add an answer to someone who uses "pure PHP", you’ll be even better
– Wallace Maxters
OK I’m creating an answer
– Rafael Mena Barreto
Duplicate of the previous one? ;) http://answall.com/questions/128049/70
– Bacco
Seems so.
– Wallace Maxters