Forzar la descarga de ficheros desde PHP

Recientemente me han pedido que añada en una de las webs que administro un enlace (de manera artesanal) para poder descargar un documento concreto.

Bien… la mayoría pensaréis que es algo trivial, porque seguramente uso alguno de los múltiples cms que existen en el mercado (y no os equivocáis), que seguro implementa de core o mediante algún sencillo plugin… Y es totalmente cierto. Pero, como os planteaba en la primera línea, me lo han pedido de forma “artesanal”.

En fin, no le demos mas vueltas, porque no tiene mucho sentido.

Sin más preámbulos, os dejo un código escrito en php que hará las delicias de los mas necesitados.

<?php
    // accepted file extensions
    $extensions = array("pdf","mp3","zip","rar");

    // file name to download
    $file = $_GET["file"];

    // file size
    $size = filesize($file);

    // Prevent go throught directories
    if(strpos($file,"/")!==false){
        die("Permission denied to change directory, please, especify only a file name");
    }

    // test the file estension
    $ftmp = explode(".",$file);
    $fExt = strtolower($ftmp[count($ftmp)-1]);
    if(!in_array($fExt,$extensions)){
        die("ERROR: File extension not recognized: $fExt");
    }

    // if it was ok, let's download it
    header("Content-Transfer-Encoding: binary");    
    header("Content-type: application/octet-stream");
    header("Content-Disposition: attachment; filename=$file");
    header("Content-Length: $size");
    $fp=fopen("$file", "r");
    fpassthru($fp);
?>

Expliquemos un poco las líneas mas importantes

  • 3 : definimos un array de extensiones de fichero aceptadas (en este caso, pdf, mp3, zip, rar…)
  • 6 : obtenemos de la request (query string) el nombre del fichero a descargar, mediante el parámetro file
  • 9 : validamos el parámetro, y no permitimos que se especifiquen nombres de fichero que contengan cambios de directorio, para prevenir descargas no deseadas (si encontramos una / terminamos con un error)
  • 14 : extraemos la extensión del fichero solicitado
  • 15 : eliminamos el . de la extensión y lo convertimos a minúsculas, para asemejarlo al array de aceptados
  • 16 : comprobamos que la extensión existe en el array de extensiones aceptadas, y en caso contrario, finalizamos con un mensaje de error adecuado
  • 21 : modificamos la cabecera de respuesta html para indicar que contiene un binario
  • 22 : añadimos a la cabecera html el nombre del fichero a descargar
  • 23 : abrimos el fichero como lectura (aquí es donde podemos comprobar que el fichero debe estar en la ruta actual en la que se encuentra esta misma página php, aunque si deseais cambiar el directorio de almacenamiento de los ficheros, es tan fácil como añadir la ruta delante del $file
  • 24 : se escribe el fichero en la response de la página

Como véis, el procedimiento es muy sencillo:

  • Subid vuestro fichero al directorio desde dónde descargaréis los ficheros
  • Subid vuestro código al directorio desde dónde descargaréis los ficheros (este es el caso sencillo, pero como os he explicado sobre la línea 23, la ruta podría variarse sin problemas)
  • Añadid un link a vuestra imagen o texto de descarga, (supongamos que yo tengo el tinglado montado en /files)  del tipo: /files/download.php?file=nombre_fichero

Y ya lo tenéis.

Fácil, ¿verdad?

Espero haberos sido de ayuda!

Jordi

Share Button

8 comentarios

  1. Buenos dias estoy intentando hacer esto para descargar la sesión de 150mb de la página de un cliente pero solo me descarga la mitad del archivo.

    ¿Que solución hay? Si lo pongo como RAR lo descarga completo, pero el cliente quiere que se descargue en mp3 directamente.

    Un saludo

    • Buenos días, Alejandro.

      Quizá el problema sea por varios motivos. Añade las siguientes líneas a la cabecera:


      $size = filesize($file);
      header("Content-Transfer-Encoding: binary");
      header("Content-Length: $size");

      Con esto lo que haremos será decirle que debe descargar un fichero binario, y por otro lado, añades el tamaño esperado. Quizá esté cortando el flujo porque no sabe cuánto pesa en realidad y evita saturarse (temas de navegador).

      Espero te sirva. Dime qué tal.

      Jordi

  2. Hola Jordi
    Me sirivio mucho, excelente aporte solo una consulta mas:
    Despues de haber hecho la descarga, hay alguna forma de avisarle al usuario con un mensaje por Ej. Ud hizo la descarga del archivo … en el directorio ….

    gracias
    Luis

    • Hola Luís, gracis por tu comentario. Me alegro de que te haya sido útil.

      Respondiendo a tu pregunta, no es posible controlarlo desde la página o código PHP, ya que el proceso de descarga es tarea del navegador, y nosotros ya no tenemos el control una vez comienza la descarga. Al menos no se me ocurre cómo hacerlo ahora mismo. Lo siento!

      Jordi

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.