Como crear un enlace de descarga temporal con expiración en PHP

Los enlaces de descarga temporales son la mar de útiles para vender bienes digitales en un sitio web. Te proporciona una manera segura de compartir un enlace de descarga a productos digitales. El usuario puedes descargar el fichero una sola vez, y una vez descargado el enlace expira. Este tipo de enlaces son ideales para proporcionar productos digitales (códigos, música, vídeo...) a una sola persona y que se autodestruya una vez lo haya descargado. Algo así como hacía el Inspector Gadget con los mensajes.

En el ejemplo que podrás ver más abajo, generamos un enlace único con el que el usuario podrá descargar el fichero del servidor. Este enlace permitirá al usuario descargar el fichero una única vez. Ojo, el enlace contará a su vez con una fecha de expiración. Si el usuario no ha descargado el fichero en un límite de tiempo, este se eliminará.

Este es el proceso que llevaremos a cabo para implementar nuestro enlace de descarga temporal con expiración en PHP.

  • Generamos un enlace de descarga con una key única
  • Creamos un directorio protegido para almacenar las keys
  • Creamos un fichero y dentro escribimos una key única
  • En la solicitud de descarga, validamos tanto la key como el tiempo de expiración
  • Forzamos al navegador a descargar el fichero

Configuración (config.php)

Definimos las variables de configuración en este fichero:

  • $files: Un array de ficheros con un único ID. (Puedes especificar un nombre diferente al archivo que se quiere descargar. Ayuda a proteger el archivo original. También puedes especificar la ruta del archivo local o remota.)
  • BASE_URL: Define la URL de la aplicación
  • DOWNLOAD_PATH: Define la ruta del script de descarga
  • TOKEN_DIR: Define el directorio de keys
  • OAUTH_PASSWORD: Define el password para generar el enlace de descarga
  • EXPIRATION_TIME: Define el tiempo hasta que expire el fichero
<?php
// Array of the files with an unique ID
$files = array(
    'UID12345' => array(
        'content_type' => 'application/zip', 
        'suggested_name' => 'codex-file.zip', 
        'file_path' => 'files/test.zip',
        'type' => 'local_file'
    ),
    'UID67890' => array(
        'content_type' => 'audio/mpeg', 
        'suggested_name' => 'music-codex.mp3', 
        'file_path' => 'https://www.dropbox.com/XXXXXXX/song.mp3?dl=1',
        'type' => 'remote_file'
    ),
);

// Base URL of the application
define('BASE_URL','http://'. $_SERVER['HTTP_HOST'].'/');

// Path of the download.php file
define('DOWNLOAD_PATH', BASE_URL.'download.php');

// Path of the token directory to store keys
define('TOKEN_DIR', 'tokens');

// Authentication password to generate download links
define('OAUTH_PASSWORD','NOPROG');

// Expiration time of the link (examples: +1 year, +1 month, +5 days, +10 hours)
define('EXPIRATION_TIME', '+5 minutes');

index.php

En este fichero, se mostrará un enlace para navegar hasta el archivo de creación del enlace de descarga. La contraseña debe especificarse en la cadena de consulta del enlace.

<a href="generate.php?NOPROG">Generate download link</a>

Crear un enlace de descarga temporal (generate.php)

Este fichero crea un enlace de descarga temporal y lista los enlaces del sitio web. La cadena de la query debe contener la contraseña y debe coincidir con lo que hemos puesto en el fichero config.php. Si la contraseña no coincide, se mostrará un error 404.

  • Obtenemos la contraseña de la cadena de la query
  • Validamos la contraseña
  • Codificamos el ID del fichero con base64_encode() en PHP
  • Generamos una nueva key utilizando uniqid() en PHP
  • Generamos un enlace de descarga con el ID del fichero y la key
  • Creamos un directorio protegido para almacenar las keys
  • Escribimos la key en un fichero y la ponemos en el directorio de keys
  • Listamos todos los enlaces de descargas en el sitio web.
<?php
// Include the configuration file
require_once 'config.php';
    
// Grab the password from the query string
$oauthPass = trim($_SERVER['QUERY_STRING']);

// Verify the oauth password
if($oauthPass != OAUTH_PASSWORD){
    // Return 404 error, if not a correct path
    header("HTTP/1.0 404 Not Found");
    exit;
}else{    
    // Create a list of links to display the download files
    $download_links = array();
    
    // If the files exist
    if(is_array($files)){
        foreach($files as $fid => $file){
            // Encode the file ID
            $fid = base64_encode($fid);
            
            // Generate new unique key
            $key = uniqid(time().'-key',TRUE);
            
            // Generate download link
            $download_link = DOWNLOAD_PATH."?fid=$fid&key=".$key; 
            
            // Add download link to the list
            $download_links[] = array(
                'link' => $download_link
            );
            
            // Create a protected directory to store keys
            if(!is_dir(TOKEN_DIR)) {
                mkdir(TOKEN_DIR);
                $file = fopen(TOKEN_DIR.'/.htaccess','w');
                fwrite($file,"Order allow,denynDeny from all");
                fclose($file);
            }
            
            // Write the key to the keys list
            $file = fopen(TOKEN_DIR.'/keys','a');
            fwrite($file, "{$key}n");
            fclose($file);
        }
    }
}    
?>

<!-- List all the download links -->
<?php if(!empty($download_links)){ ?>
    <ul>
    <?php foreach($download_links as $download){ ?>            
        <li><a href="<?php echo $download['link']; ?>"><?php echo  $download['link']; ?></a></li>
    <?php } ?>
    </ul>
<?php }else{ ?>
    <p>Links are not found...</p>
<?php } ?>

Descargar fichero a través de un enlace temporal (download.php)

Este fichero descarga el fichero mediante un enlace de descarga temporal.

  • Obtenemos el ID del fichero de la query de la URL
  • Obtenemos el tiempo de la key y calculamos la expiración
  • Recupera las claves del fichero de keys
  • Recorremos las keys para encontrarla, y si la encontramos, la eliminamos
  • Colocamos las keys restantes en el fichero de keys
  • Si se encuentra una coincidencia y el enlace aún no ha vencido, forzamos al navegador a descargar el fichero.
<?php
// Include the configuration file
require_once 'config.php';

// Get the file ID & key from the URL
$fid = base64_decode(trim($_GET['fid']));
$key = trim($_GET['key']);

// Calculate link expiration time
$currentTime = time();
$keyTime = explode('-',$key);
$expTime = strtotime(EXPIRATION_TIME, $keyTime[0]);

// Retrieve the keys from the tokens file
$keys = file(TOKEN_DIR.'/keys');
$match = false;

// Loop through the keys to find a match
// When the match is found, remove it
foreach($keys as &$one){
    if(rtrim($one)==$key){
        $match = true;
        $one = '';
    }
}

// Put the remaining keys back into the tokens file
file_put_contents(TOKEN_DIR.'/keys',$keys);

// If match found and the link is not expired
if($match !== false && $currentTime <= $expTime){
    // If the file is found in the file's array
    if(!empty($files[$fid])){
        // Get the file data
        $contentType = $files[$fid]['content_type'];
        $fileName = $files[$fid]['suggested_name'];
        $filePath = $files[$fid]['file_path'];
        
        // Force the browser to download the file
        if($files[$fid]['type'] == 'remote_file'){
            $file = fopen($filePath, 'r');
            header("Content-Type:text/plain");
            header("Content-Disposition: attachment; filename="{$fileName}"");
            fpassthru($file);
        }else{
            header("Content-Description: File Transfer");
            header("Content-type: {$contentType}");
            header("Content-Disposition: attachment; filename="{$fileName}"");
            header("Content-Length: " . filesize($filePath));
            header('Pragma: public');
            header("Expires: 0");
            readfile($filePath);
        }
        exit;
    }else{
        $response = 'Download link is not valid.';
    }
}else{
    // If the file has been downloaded already or time expired
    $response = 'Download link is expired.';
}
?>

<html>
<head>
    <title><?php echo $response; ?></title>
</head>
<body>
    <h1><?php echo $response; ?></h1>
</body>
</html>

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.