Foros del Web » Programando para Internet » PHP »

Sistemas de descargas

Estas en el tema de Sistemas de descargas en el foro de PHP en Foros del Web. Hola, Estoy montando un sistema de autentificacion web en PHP como el descrito en www.desarrolloweb.com y me gustaria que los usuarios de mi portal puedan ...
  #1 (permalink)  
Antiguo 09/02/2004, 18:41
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Sistemas de descargas

Hola,
Estoy montando un sistema de autentificacion web en PHP como el descrito en www.desarrolloweb.com y me gustaria que los usuarios de mi portal puedan descargar ficheros, los cuales no quiero que sean accesibles mediante URL, de tal forma que el fichero a descargar no este disponible a no ser que se tenga abierta una sesión. He visto esto en http://www.desarrolloweb.com/descarg...?descarga=1469 y me ha parecido un sistema muy bueno.

He visto un post en este foro en el que sugería almacenar el fichero en formato binario en base de datos, pero en mi caso el tamaños de los archivos es muy dispar, tengo archivos de 10 K o de 100 Mb, por tanto su almacenamiento en base de datos no es muy optimo que se diga.

Alguna idea????
  #2 (permalink)  
Antiguo 09/02/2004, 19:45
Avatar de jpinedo
Colaborador
 
Fecha de Ingreso: septiembre-2003
Ubicación: Lima, Perú
Mensajes: 3.120
Antigüedad: 21 años, 2 meses
Puntos: 41
El hacho de hacer un script que administre las descargas... ya es por sí mismo una forma de ocultar la ruta al archivo que se está descargando... claro que si alguien sabe la ruta y el nombre del archivo, podrá descargarlo... pero nadie tiene por qué saberlo ... a menos que los archivos que quieres proteger de la descarga sean muy importantes... en cuyo caso sería mejor que los pusieras fuera de la raíz.

Sobre cómo hacer un pequeño sitema de descargas hay varios posts aquí en el foro donde se ha explicado... (utiliza el buscador)...

Saludos
  #3 (permalink)  
Antiguo 09/02/2004, 20:55
O_O
 
Fecha de Ingreso: enero-2002
Ubicación: Santiago - Chile
Mensajes: 34.417
Antigüedad: 22 años, 10 meses
Puntos: 129
jcorba .. intenta preguntar las cosas en un mismo tema .. y .. a ser posible como es tu caso en un tema nuevo ..

Por mi parte te respondí en este mensaje a esta misma pregunta:
http://www.forosdelweb.com/s/msg179555.html


Un saludo,
__________________
Por motivos personales ya no puedo estar con Uds. Fue grato haber compartido todos estos años. Igualmente los seguiré leyendo.
  #4 (permalink)  
Antiguo 10/02/2004, 05:07
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

Debido a una pequeña confusion mia publique el mismo post en dos hilos distintos de este foro. Como bien ha dicho Cluster esto es un caso nuevo con lo cual quiero poner aqui su respuesta (a la que el hace referencia en su post) la cual me parece que es muy interesante. Gracias Cluster

La técnica pasa por dos puntos principales:
1) Que PHP sea el que acceda al archivo para enviarlo al navegador

2) Sistema de autentificación si lo requieres..

Del punto 1 .. necesitas leer el archivo sea cual sea su ubicación física: una base de datos en un campo tipo "binario" .. o el sistema de archivos del servidor.

Una ve leido necesitas enviarlo al cliente (navegador) .. vas a enviar esos datos que has leido, debes decirle al navegador que tipo de datos le estas enviando para que los interprete como corresponda .. ya sea que los abra (asocie a su lector caso de un .pdf .. un .doc .. o una .jpg, .gif ...) o bien los "fuerce" para descargar (independiente del formato MIME que sea el archivo que has leido y sus datos ..

Este control sobre el navegador lo haces con las cabeceras HTTP adecuadas tipo "content-type" y afines. Estas cabeceras HTTP las define el protocolo HTTP (no es própio de PHP). Con PHP "lanzas" esas cabeceras con la función header() ...

Existe un pequeño "problema" .. Dichas cabeceras HTTP, si bien son standards y lo define ese protocolo HTTP, algunos navegadores no las respetan o bien no las interpretan de la misma forma: ejemplo .. a uno le dices "tal cabecera = descargar" y lo entiende como "mostrar" .. Así que no te extrañe si un mismo código que puedas probar que use X cabeceras te funciona o se comporta de una manera en X navegador y en Y navegador (incluso versión de mismo fabricante) se comporta diferente. Ese es un problema que se suele solventar identificando el navegador que está conectado al script .. y a partir de ahí enviarle las cabeceras HTTP más adecuadas a ese determinado script (todo para mantener una compatibilidad "aparente").

Ejemplos de todo esto tienes en las FAQ's (una bastante completa con "opción resume" para que tu navegador o cliente de descargas pueda retomar la descarga si se perdió la conexión (esto es una propiedad más del protocolo HTTP ..)).

Sobre el tema de "donde" almacenar tus datos .. Si bien almacenarlos en una Base de datos en formato binario (en un campo de ese tipo en la BD) .. Para archivos grandes o en generar un total de bytes de esos archivos (peso) grande .. se puede hacer "pesado" para manejarlo por el motor de BD que uses. Mysql por ejemplo no es el mejor motor de BD para ese fin aunque su implementación es sencilla. Existen otros motores de BD que se manejan mejor con gran cantidad de datos como los que vas a generar si almacenas tus archivos en tu BD en formato binario.

La otra alternativa es sólo guardar en tu BD una referencia hacia donde está el archivo en tu sistema de archivos (ruta/nombre). Ese "dato" para la Base de datos es sólo una cadena de texto corriente (y tal vez de no más de 256 caracteres) así que .. es muchoooo más manejable para trabajar con esa BD y hacer consultas en general .. En el momento que se requiera ese archivo se "linkeará" al archivo que está ya en el sistema de archivos del servidor en esa ruta que guardas en ese campo de tu tabla (BD).

Un saludo,
  #5 (permalink)  
Antiguo 10/02/2004, 06:06
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

He estado mirando un poco de información por el foro y en el manual de php (en los comentarios).

Creo que lo que voy a hacer es poner los ficheros que quiero descargar en una ruta dentro del sistema de archivos de mi servidor la cual no sea publicamente accesible.
Luego creare una base de datos con los campos "id" y "path_fichero" (pondre el path del archivo y no el propio archivo en un campo binario ya que el peso del archivo es muy grande). De esta forma los link de descargas de la pagina serán del tipo
http://elsitio.com/descargas.php?id=45.
La pagina descargas.php deberá acceder a la base de datos para obtener la ruta del fichero dentro del sistema de archivos del servidor. Llegado a este punto lo que falte es empezar a servir el fichero al navegador.

He visto dos formas de hacer esto y me gustaría saber cual pensais que es mejor y xq o si es lo mismo (a priori a mi me parece que si..pero por si acaso..):
Tengo que decir que el tamaño de mis archivos de descargas pueden tener a llegar unos 100 Mb con lo cual no me gustaria que se tenga que hacer una copia
temporal en el servidor o que el fichero se tenga que cargar en memoria hasta que lo pueda servir
1ª.- $fichero=fopen($nombrefichero,'rb'); fpassthru($fichero);
2ª.- readfile ("path/nombrefichero");
(por lo que he leido con fopen obtengo un puntero y con fpassthru voy recorriendolo con lo cual creo que utilizando esta forma no se cargara mucho al servidor...que me podeis decir de esto??)

Por otro lado he estado viendo las distintas cabeceras que se deben mandar segun el navegador que este visitando el script.

En este foro he visto una posible opcion:

function descarga_fichero ($nombreFichero) {
// ob_clean ();
header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($nombreFichero));
header("Content-Disposition: attachment; filename=\"$nombreFichero\"");
$fichero = fopen($nombreFichero, 'rb');
if (!$fichero) {die("unable to open $fichero");}
fpassthru($fichero);
header ("Connection: close");
die ();
}

Y otra opción que he visto tb bastante interesante en php.net es:

header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Disposition: attachment; filename=".basename($filename).";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));

@readfile("$filename"); // pongo la arrabo delante para omitir mensaje de error en caso de fallo
exit();

Pues bueno señores, esta es toda la información que he recogido y me gustaría unas cuantas criticas sobre lo aquó comentado.

Un saludo a tod@s
Jose Corbacho
  #6 (permalink)  
Antiguo 10/02/2004, 06:30
O_O
 
Fecha de Ingreso: enero-2002
Ubicación: Santiago - Chile
Mensajes: 34.417
Antigüedad: 22 años, 10 meses
Puntos: 129
Pues .. como comentaba en el mensaje que citastes mio .. Las cabeceras HTTP son muy variadas y lo peor es que no todo navegador reconoce las mismas cabeceras para la misma taréa (forzar una descarga de los datos que recibe)..

En las FAQ's de este foro tienes otro ejemplo que usa la opción "resume" (para retomar descargas que se perdió la conexión) bastante interesante.

Sobre que función usar para "leer" el archivo .. tendrías que probarlo .. pero "creo" (no sé fisicamente como funciona dichas funciones) que para este fin te vendría mejor usar readfile() directo ya que esta función "lee -> entrega al buffer de salida) .. mientras que fopen() y compañia leen hacia una variable la cual ya ocupará memoria (y podrías tener porblemas con el la memoria que se reserva para la ejecución de cada script .. que suele estar en 8 Megabytes por defeco en el php.ini -> memory_limit) y despues tienes que volcar esa variable hacia el buffer de salida usando echo $variable; por ejemplo ...

Un saludo,
__________________
Por motivos personales ya no puedo estar con Uds. Fue grato haber compartido todos estos años. Igualmente los seguiré leyendo.
  #7 (permalink)  
Antiguo 10/02/2004, 07:58
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

En la paginas 334/335 del libro "Beginning PHP 4" pone:

If all you want to do is read and print the entire file to the web browser, yo can use the fpassthru() function. This takes a single argument, a FILE HANDLE (obtenido mediante la funcion fopen() ) which it uses to read the remaining data from a file (that is, from the current position until end-of-file). It then write results to standard output.


We can also print the contents of a file without even having to call fopen(), by using readfile(). This function takes a filename as its single argument, READS THE WHOLE FILE, and then writes it to standard output, returning the number of bytes read (or False upon error).


Por lo que yo entiendo readfile lee todo el fichero y luego lo manda (demasiada carga para el servidor), pero fpassthru() tb pone que no pasa la lectura a la salida estandar hasta que no lee todo el fichero.
O yo no me aclaro pero me parece que las dos cosas son igual de malas para leer para descargar ficheros de gran peso.

Alguna interpretación a esta parrafada???

Un saludo
Jose Corbacho
  #8 (permalink)  
Antiguo 10/02/2004, 08:57
O_O
 
Fecha de Ingreso: enero-2002
Ubicación: Santiago - Chile
Mensajes: 34.417
Antigüedad: 22 años, 10 meses
Puntos: 129
Pues según tu libro .. el fpassthru() lee y envia a la vez al buffer de salida el archivo .. y readfile() lo lee completo y lo entrega a la salida. (justo al reves de lo que pensaba) ...

Si lo dice "el libro" .. supongo que habrán leido mejor que yo la documentación de PHP y lo habrán probado .. así que habrá que creerles xD ¬¬. Yo por mi parte tomo nota para otra vez.

Un saludo,
__________________
Por motivos personales ya no puedo estar con Uds. Fue grato haber compartido todos estos años. Igualmente los seguiré leyendo.
  #9 (permalink)  
Antiguo 10/02/2004, 16:22
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

Respecto al tipo de cabeceras a enviar para descargar el archivo de las dos opciones que he puesto en el otro post me gustaría saber si con la segunda opción quedan todas las posibilidades de los distintos navegadores cubiertas.

En otro post he visto esto:
To force a download on MSIE for Windows, you don't have to use "attachment", but you need it for all other browsers. The correct code for that is then:

<?php
$filename=""; // the name the file will have on client computer
$file_to_download=""; // the name the file has on the server (or an FTP or HTTP request)
$user_agent = strtolower ($_SERVER["HTTP_USER_AGENT"]);
header( "Content-type: application/force-download" );
if ((is_integer (strpos($user_agent, "msie"))) && (is_integer (strpos($user_agent, "win")))) {
header( "Content-Disposition: filename=".$filename);
} else {
header( "Content-Disposition: attachment; filename="$filename);
}
header( "Content-Description: File Transfert");
@readfile($file_to_download);
?>

This code work properly on Windows (MSIE, Netscape, Mosilla, Opera) and Mac (MSIE, Netscape).

For the Mac, there is a little problem: if the file is recognized by the OS, it will open it and not force save to disk. To prevent this, ask your clients to use "save link to disk" and not click on it.


Alguien ha jugado ya alguna vez con esto y tiene una solución final o me puede remitir a algún sitio que conozca (da igual si está en ingles)????

Un saludo
Jose Corbacho
  #10 (permalink)  
Antiguo 10/02/2004, 16:30
O_O
 
Fecha de Ingreso: enero-2002
Ubicación: Santiago - Chile
Mensajes: 34.417
Antigüedad: 22 años, 10 meses
Puntos: 129
Hace tiempo usé google personalmente buscando info sobre lo mismo y aplicado a PHP .. eso es lo que encontré.

Es más "urgando" en el código fuente de phpMyadmin que dispone de su gestor de descargas própios para el código que genera (los "dump" que puedes hacer de tus BD) .. usa un sistema similar con esas mismas cabeceras donde detecta el navegador y le envia las que según parece son las cabeceras más idoneas ...

Si encuentras algo méjor (y testeado sería lo ideal) nos avisas.

Un saludo,
__________________
Por motivos personales ya no puedo estar con Uds. Fue grato haber compartido todos estos años. Igualmente los seguiré leyendo.
  #11 (permalink)  
Antiguo 01/03/2004, 17:52
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

Al final he implementado una mezcla de las cabeceras que he puesto en los post anteriores, haciendo una pequeña distinción según el navegador (copy and paste de los que hace myphpadmin cuando hace el volcado de un fichero).

Resultado: con MSIE 6.0 SP1 me funciona tanto si pulso en el boton abrir como en el de guardar (detectando correctamente el tipo de archivo)
Con MSIE 6.0 el boton guardar funciona bien (detectando correctamente el tipo de archivo), pero si pulso en el boton abrir me sale otra vez el boton abrir y al volver a pulsar en boton abrir lo hace bien (he actualizado este navegador con el sp1 y entonces lo hace bien...curioso)

Con Netscape 4.73 solo aparece la opcion de guardar el archivo.... pero lo hace bien

Pero con Netscape 7.0, además de solo salir la opción de guardar (que digamos eso me da igual) me dice que el archivo es de tipo application/octet-stream y cuando pulso en el boton guardar y voy a seleccionar el directorio donde quiero q me lo guarde, me pone una segunda extensión al fichero .php, de tal forma que si el archivo que estoy sirviendo es pepe.pdf el me pone como nombre del archivo a guardar pepe.pdf.php`. Alguien sabe la cabecera que hay q mandar al Netscape 7.0 para que conserve el nombre del fichero original??

P.:D: Ahora mismo no tengo aquí el codigo que he implementado...pero si hace falta mañana por la mañana lo pongo.

Un saludo
  #12 (permalink)  
Antiguo 02/03/2004, 03:58
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Este es el codigo que he implementado:

function descargaFichero ($fichero)
{ // HEADERS SACADAS DE PHPMYADMIN
$filename=basename($fichero);
$filesize = filesize($fichero);

// 'application/octet-stream' is the registered IANA type but
// MSIE and Opera seems to prefer 'application/octetstream'
$USR_BROWSER_AGENT="";
if (preg_match('@Opera(/| )([0-9].[0-9]{1,2})@', $_SERVER['HTTP_USER_AGENT'])) $USR_BROWSER_AGENT='OPERA';
if (preg_match('@MSIE ([0-9].[0-9]{1,2})@', $_SERVER['HTTP_USER_AGENT'])) $USR_BROWSER_AGENT='IE';
$mime_type = ($USR_BROWSER_AGENT == 'IE' || $USR_BROWSER_AGENT == 'OPERA')
? 'application/octetstream'
: 'application/octet-stream';

header('Content-Type: ' . $mime_type);
header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
// IE need specific headers
if ($USR_BROWSER_AGENT == 'IE')
{
//header('Content-Disposition: inline; filename="' . $filename . '"');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
}
else
{
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Pragma: no-cache');
}
@readfile ($fichero);
exit();
}
  #13 (permalink)  
Antiguo 02/03/2004, 05:27
jcorba
Invitado
 
Mensajes: n/a
Puntos:
Hola,

Finalmente he implementado este código el cual funciona en IE 6.0 SP1, IE 5.0 SP2 ,IE 6.0 (con la salvedad de que en estos dos ultimos si abrimos el fichero y no lo descargamos hay que dar dos veces al boton abrir), Netscape 7.1, Mozilla 1.6 y Netscape 7.0 (con la salvedad que el nombre con el que intenta salvar le añade la extension .php --> esto esta solucionado con la version 7.1),

Todo ha sido probado con navegadores bajo windows 2000/XP.
Es codigo final es:

function descargaFichero ($fichero)
{ // HEADERS SACADAS DE PHPMYADMIN
$filename=basename($fichero);
$filesize = filesize($fichero);

// 'application/octet-stream' is the registered IANA type but
// MSIE and Opera seems to prefer 'application/octetstream'
$USR_BROWSER_AGENT="";
if (preg_match('@Opera(/| )([0-9].[0-9]{1,2})@', $_SERVER['HTTP_USER_AGENT'])) $USR_BROWSER_AGENT='OPERA';
if (preg_match('@MSIE ([0-9].[0-9]{1,2})@', $_SERVER['HTTP_USER_AGENT'])) $USR_BROWSER_AGENT='IE';
$mime_type = ($USR_BROWSER_AGENT == 'IE' || $USR_BROWSER_AGENT == 'OPERA')
? 'application/octetstream'
: 'application/octet-stream';

// Esta funcion esta operativa desde php 4.3.0 y parece ser que tiene buena pinta arreglando el nombre de los
// ficheros y las extensiones
//$mime_type=mime_content_type ($fichero);



header('Content-Type: ' . $mime_type);
// Se informa al navegador del tamaño del fichero y puede mostrar la barra de progreso de descarga
header('Content-Length: ' . filesize($fichero));
header('Content-Transfer-Encoding: binary');
header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
// IE need specific headers
if ($USR_BROWSER_AGENT == 'IE')
{
//header('Content-Disposition: inline; filename="' . $filename . '"');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
}
else
{
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Pragma: no-cache');
}
@readfile ($fichero);
exit();
}
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 14:07.