Inicio > PHP > Curso PHP. Capítulo 10: Subida y descarga de ficheros.

Curso PHP. Capítulo 10: Subida y descarga de ficheros.

Ahora que ya sabemos manipular archivos vamos a ver un ejemplo sencillo de aplicación que permita subir y descargar ficheros.

Subida de ficheros

Los formularios pueden contener un campo para subir ficheros al servidor. Antes de ver la lógica del sistema de subida de ficheros que vamos a implementar, vamos a explicar la variable super-global $_FILES de PHP y los atributos que hay que modificar en el formulario para permitir la subida de ficheros.

<form method="post" action="subir.php" enctype="multipart/form-data">
   <input type="file" name="archivo" id="archivo" />
   <input type="submit" value="Subir fichero" />
</form>

Este formulario de ejemplo muy sencillo lo hemos diseñado con dos campos, un campo de tipo file para seleccionar el archivo que se subirá al servidor, y un botón de tipo submit que envía el formulario por el método post al script subir.php.  Cabe destacar el uso del atributo enctype=»multipart/form-data» que añadirá la cabecera en la petición HTTP para indicarle al servidor que el cuerpo de la petición incluye un archivo.

Cuando envíamos una petición HTTP con archivos al servidor, PHP detecta que estamos subiendo un archivo y lo almacena en un directorio temporal establecido en la directiva upload_tmp_dir del archivo de configuración de PHP. El tamaño del archivo no debe sobrepasar lo establecido en la directiva upload_max_filesize.

Podemos acceder al fichero subido a través del array super-global $_FILES[‘name del campo file’] que nos ofrece un conjunto de propiedades a las que podemos acceder:

$_FILES[‘archivo’][‘tmp_name’]: El valor almacenado en esta clave devuelve el directorio temporal en el que se ha almacenado el archivo en el servidor.

$_FILES[‘archivo’][‘name’]: El nombre del archivo en el sistema del usuario.

$_FILES[‘archivo’][‘size’]: El tamaño en bytes del archivo subido.

$_FILES[‘archivo’][‘type’]: El tipo MIME del archivo, por ejemplo text/plain o image/gif.

$_FILES[‘archivo’][‘error’]: Código de error si se ha producido algún error.

Comprobando que se ha subido el fichero:

Podemos utilizar la función is_uploaded_file() que toma como parámetro el nombre temporal del fichero subido al servidor, no el nombre del fichero del usuario, para ver si se ha subido satisfactoriamente al servidor. Esta función devuelve TRUE si el archivo está en el directorio temporal, y FALSE en caso contrario.

<?php
if (is_uploaded_file($_FILES['archivo']['nombre_tmp']))
{
   echo 'El archivo se ha subido con éxito';
}
?>
Comprobando que el fichero no sobrepasa el tamaño permitido:

Una simple comparación entre el tamaño que queramos permitir subir y $_FILES[‘archivo’][‘size’] bastará para asegurarnos que no se sobrepasa el tamaño permitido.

<?php

if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';
   }

}

?>
Comprobando que el fichero tiene una extensión permitida:

Para comprobar si el fichero tiene una extensión permitida basta con comprobar el tipo MIME del fichero con las extensiones que permitimos.

<?php
if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';

        if( $_FILES['archivo']['type']=='image/jpeg')
        {
             echo 'La extensión JPEG está permitida';
        }
   }
}
?>
Mover el fichero del directorio temporal a nuestro directorio de subidas:

Si no movemos el archivo del directorio temporal a un directorio donde tengamos almacenados los ficheros subidos por los usuarios, el fichero se perderá en el limbo de PHP y no podremos recuperarlo. Para ello contamos con la función move_uploaded_file() que toma dos parámetros, la ruta del fichero en el directorio temporal y la nueva ruta donde queramos almacenarlo.

<?php

if ( is_uploaded_file($_FILES['archivo']['tmp_name']) )
{
   echo 'El archivo se ha subido con éxito';

   if( $_FILES['archivo']['size']<(512*1024) )
   {
       echo 'El archivo no sobrepasa el tamaño máximo: 512KB';

        if( $_FILES['archivo']['type']=='image/jpeg')
        {
             echo 'La extensión JPEG está permitida';

             $rand = rand(1000,999999);
             $origen = $_FILES['archivo']['tmp_name'];
             $destino = 'uploads/'.$rand.$_FILES['archivo']['name'];
             move_uploaded_file($origen, $destino);

        }
   }
}

?>

El número aleatorio rand entre 1000 y 999999 lo utilizamos para prevenir la sobreescritura de ficheros con el mismo nombre.  De esta forma ya hemos implementado un sistema de subida de archivos JPEG de tamaño menor a 512KB. Ahora bien, este sistema no es del todo seguro para evitar la subida de archivos maliciosos diseñados para atacar nuestra aplicación, como por ejemplo, una shell en php. Queda pendiente realizar una entrada para evitar esta vulnerabilidad de las aplicaciones web. Hasta ahora contais con dos artículos dedicados a las principales vulnerabilidades: evitar XSS y evitar RFI y FLI.

Descarga de ficheros

Para descargar ficheros normalmente ponemos la ruta al fichero en el enlace y el navegador ya se encarga de realizar la petición HTTP oportuna al servidor. Pero, ¿que ocurre cuando el archivo que se desea bajar se ha generado dinámicamente y aún no está guardando en ningún directorio dentro del servidor o solo queremos que se puedan descargar archivos a través de un script de PHP?  Pues que tendremos que diseñar algún método que permita forzar la descarga de archivos.

La solución es enviar la petición HTTP de respuesta mediante código PHP enviandouna serie de cabeceras HTTP mediante la función header() de PHP que solo funciona si no se ha enviado nada al flujo de salida.

Las cabeceras de la respuesta HTTP que vamos a enviar son:

– Content-Type: application/force-download

– Content-Disposition: attachment;  filename=nombre_del_fichero

– Content-Transfer-Enconding: binary

– Content-Length: tamaño_del_fichero

Accederemos al script de descarga de la siguiente manera:

<a href="descarga.php?archivo=imagen5.jpeg" >Descargar imagen</a>
<?php
//Si la variable archivo que pasamos por URL no esta 
//establecida acabamos la ejecucion del script.
if (!isset($_GET['archivo']) || empty($_GET['archvo'])) {
   exit();
}

//Utilizamos basename por seguridad, devuelve el 
//nombre del archivo eliminando cualquier ruta. 
$archivo = basename($_GET['archivo']);

$ruta = 'imagenes/'.$archivo;

if (is_file($ruta))
{
   header('Content-Type: application/force-download');
   header('Content-Disposition: attachment; filename='.$archivo);
   header('Content-Transfer-Encoding: binary');
   header('Content-Length: '.filesize($ruta));

   readfile($ruta);
}
else
   exit();
?>

Verificamos que el archivo existe, enviamos las cuatro cabeceras, y utilizamos la funcion readfile() que devuelve el contenido del archivo por el flujo de salida. Podemos mejorar mucho ese script añadiendo el content-type adecuado, y haciendo más comprobaciones de seguridad.

RESUMEN

Ya hemos visto como implementar dos sitemas sencillos de subida y descargas de fichero y como utilizar la variable super-global $_FILES[] para acceder a los datos de los ficheros subidos al servidor. Queda pendiente realizar un artículo sobre las vulnerabilidades en las subidas de archivos. Me documentaré sobre esta cuestión y más adelante intentaré redactar un artículo dedicado a este tema.

En el próximo capítulo veremos como persistir datos entre diferentes peticiones HTTP del mismo usuario.

Hasta la próxima.

Categorías: PHP Etiquetas:
  1. mago
    6 julio, 2012 a las 1:44

    creo q me va servir gracias por el aporte

  2. Carlos
    16 julio, 2012 a las 2:00

    Hola, necesito crear un formulario donde el usuario introduzca su nombre y correo electrónico y al darle al botón enviar permita enviarle un archivo .pdf por correo electrónico alojado en mi web.
    Gracias.

  3. 9 agosto, 2012 a las 19:50

    Hola Francisco. Quiero darte las gracias por la gran ayuda que me brindó tu explicación. La verdad es que tenía días tratando de resolver lo relativo a la descarga de imágenes guardadas en un fichero. Gracias de nuevo y un saludo desde Venezuela.

  4. martin smigliani
    15 agosto, 2012 a las 3:58

    Hola que tal me gustaria saber como introducir un campo que te permita escribir el archivo que uno desea buscar, y despues asi bajarlo. muchas gracias.

  5. 11 septiembre, 2012 a las 13:47

    buenas tardes o dias jeej como sea, quisiera que alguien me pudiera ayudar, lo que pasa es que le codigo de arriba me abre la ventanda de desargar y esta viene con el archivo que deseo descargar pero a la hora de abrir el archivo este viene dañado

  6. Isabel
    25 septiembre, 2012 a las 22:05

    Francisco, tengo una duda, la variable super-global $_FILES, solo se activa cuando se existe un ACTION en el form y se presiona sobre el boton submit, o tambien se activa si uno tiene action definido asi action=»» y en lugar de submit tiene un evento onclik()

  7. Anonimo
    4 noviembre, 2012 a las 13:37

    Antes de nada decir que el codigo aqui representtado tiene el mismo error que el de muchas webs en el tema de descarga y es el siguiente fallo de representación en la lectura del nombre, deben reemplazar:

    header(‘Content-Disposition: attachment; filename=’.$archivo);

    por:

    header(‘Content-Disposition: attachment; filename=»‘.$archivo.'»‘);

    Encuanto a la duda de carlos le recomiendo mandar la URL de descarga del PDF por email, encuanto la de martin, puedes hacer una página que mediante una variable de si o no en $_POST con un if() que lo compruebe muestre un formulario en el else que mande los datos a la misma pagina que los recoge antes del if() y si estos estan vacios va al else mostrando el formulario y si ya a mandado los datos compruebe si existe el fichero con file_exits() de no existir lo indique y si existe saque la descarga del fichero, lo unico es que devera el usuario introducir el nombre y extension exacta de ese fichero, por ejemplo (archivo.txt).

    Encuanto a mi duda:

    ¿Cómo puedo hacer la descarga de un archivo mayor a 1 GB?, cuando la descarga es de 20 KB por ejemplo se baja perfectamente pero cuando el archivo es por ejemplo de 1 GB la descarga se para, es como que el servidor me anula, ya probe con set_time_limit(0); y añadiendo mas información en el head sobre el archivo, insertando file_get_contents(); antes del readfile(); pero nada,,, no hay manera, espero una respuesta :) .

    Un saludo, gracias.

  8. Matias I.
    19 diciembre, 2012 a las 3:38

    A mi se me presenta un problema cuando descargo.. En mi caso tengo que dejar un txt en mi servidor, descargable, que grabé previamente en él. Utilizando el codigo del amigo logre que se pueda descargar, pero cuando lo abro ademas de aparecerme la informacion grabada en el txt, me aparece parte del codigo fuente de la web html. Nose a que se debera… Gracias!

  9. gustavo
    1 marzo, 2013 a las 14:02

    hola , estoy usando Header para forzar la descarga de un archivo del servidor, pero necesito saber si existe alguna forma de obtener la direccion(path)donde el usuario descargará finalmente ese archivo.Gracias de antemano.

  10. Jorge
    16 abril, 2013 a las 10:33

    Hay un fallo en el código, en el apartado de descarga, el primer if que comprueba si existe o esta vacío el parámetro pasado por el enlace, en el empty pone «archvo» en vez de «archivo» , por si a alguien no le funciona el copia y pega ;)

  11. pedro
    18 septiembre, 2013 a las 6:52

    y en que paso se sube el archivo? porque segun vi solo se comprueba en la script (perdonar mi ignorancia)

  12. Aleja Ruiz
    24 octubre, 2013 a las 19:15

    uffffffffffffffffffffffffffff buenisimo amigo, muchas gracias por el aporte!!

  13. 30 diciembre, 2013 a las 3:40

    yo lo descargo en windows per

  14. ISMAEL
    30 diciembre, 2013 a las 3:41

    en windows no me funciona la ruta, por default me los almacena en mis descargas pero lo quiero cambiar y no me funciona

  15. irving
    26 junio, 2014 a las 23:04

    sigo teniendo problemas con este codigo… me realiza la descarga pero no se puede visualizar la imagen, me dice que el archivo esta corrupto y al ver las propiedades del archvo descargado no hay ninguna información de la imgen… AYUDA !!!

  16. Ruth Moreno
    2 julio, 2014 a las 13:26

    Buenos días. Muchas gracias por compartir tus conocimientos en PHP, y de antemano también agradezco la ayuda. Se me presenta el siguiente problema, usando un código upload prácticamente idéntico al que pones como ejemplo, que admite imágenes y pdf de máximo 1 MB. Algunas veces, el archivo carga distorsionado, me explico: si comparo el archivo original con el que subió al servidor, son iguales en tamaño y extensión, pero el archivo en el servidor muestra la imagen distorsionada (muestra franjas blancas o de otros colores que ocultan el contenido, o simplemente la imagen incompleta). Usé comp.exe para comparar dos de estos archivos, y aparece error de desplazamiento, es decir, corrobora que los contenidos no son iguales. Inicialmente, este código de upload estaba dentro de una página que hacía otro montón de cosas, cargaba el archivo y luego enviaba un correo electrónico, así que pensé que podría tratarse de físico agotamiento :-), pero separé el proceso de carga (haciendo el upload primero, usando ajax), y nada, el resultado es el mismo, el archivo sube distorsionado a veces. La conexión del usuario no es buena, pero desde esa misma conexión me envían el mismo archivo adjunto a un correo de Gmail, por ejemplo,y llega bien, así que ese tampoco sería el problema, creo yo. De nuevo, gracias por la ayuda, buen día.

  17. 25 abril, 2016 a las 18:46

    Hola, muy buen tuto pero, si quisiera que mi descarg fuera dentro de la misma pagina?, es decir, a travez de ajax?? eso es posible??

  18. 18 noviembre, 2016 a las 0:30

    Una pregunta por que al descargar otro tipo de archivos que no sean .jpg me los descarga sin extension? osea me descarga una cancion mp3 pero me lo descarga como: «nombre de la cancion» osea sin el .mp3 y con archivos rar me pasa lo mismo.
    en el caso de la musica al darle al boton descargar me pide que lo reproduzca o que lo descarge y si le pongo a que me lo reproduzca si lo descarga como mp3 pero al darle en gusrdar me lo descarga sin extencion, por que?

  19. Diego Meza
    17 marzo, 2017 a las 17:28

    Muchas Gracias! estuve revisando los headers en varios foros y ninguno generaba completamente el resultado, me ha servido un monton!

  1. 13 noviembre, 2020 a las 16:29
  2. 4 May, 2021 a las 3:36
  3. 28 julio, 2021 a las 11:18

Deja un comentario