Cómo forzar la descarga de archivos con PHP

122

Quiero solicitar que se descargue un archivo cuando el usuario visite una página web con PHP. Creo que tiene algo que ver file_get_contents, pero no estoy seguro de cómo ejecutarlo.

$url = "http://example.com/go.exe";

Después de descargar un archivo con header(location)él no se redirige a otra página. Simplemente se detiene.

Juan
fuente
posible duplicado de forzar la descarga de la página en php
hakre
posible duplicado de Forcing para descargar un archivo usando PHP
usuario

Respuestas:

232

Lea los documentos sobre el archivo read de la función PHP incorporada

$file_url = 'http://www.myremoteserver.com/file.exe';
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary"); 
header("Content-disposition: attachment; filename=\"" . basename($file_url) . "\""); 
readfile($file_url); 

También asegúrese de agregar el tipo de contenido adecuado según su aplicación de archivo / zip, aplicación / pdf, etc., pero solo si no desea activar el cuadro de diálogo Guardar como.

Pit Digger
fuente
66
¿Por qué se requiere eso?
juan
Quiero decir, si no es un archivo .exe que solo. De lo contrario, esto debería funcionar bien.
Pit Digger
77
No se olvide de enjuagar ;-) ob_clean (); enjuagar(); / * antes * / readfile ($ file_url);
Ash
Entonces, si tiene un archivo grande de 10 GB, ¿PHP intenta cargar todo ese archivo?
GDY
Se debe llamar a exit () al final para evitar posibles problemas (hablando por experiencia :-)
ykay dice Reinstate Monica
42
<?php
$file = "http://example.com/go.exe"; 

header("Content-Description: File Transfer"); 
header("Content-Type: application/octet-stream"); 
header("Content-Disposition: attachment; filename=\"". basename($file) ."\""); 

readfile ($file);
exit(); 
?>

O, cuando el archivo no se puede abrir con el navegador, puede usar el Locationencabezado:

<?php header("Location: http://example.com/go.exe"); ?>
Marek Sebera
fuente
1
el nombre de archivo en su encabezado no debe ser $file(que contiene la parte http) sino un nombre de archivo válido.
Fabio
1
No hay ningún tipo de medio de aplicación / descarga forzada; use application / octet-stream en su lugar.
Gumbo
funciona muy bien! Pero agrega 'al nombre de archivo almacenado. Utilice: filename = ". Basename ($ archivo));
harry4516
@ harry4516 modificado de acuerdo con su hallazgo
Marek Sebera
No funciona querido en caso de imagen png para mí. Gracias.
Kamlesh
40
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"file.exe\""); 
echo readfile($url);

es correcto

o mejor para el tipo de archivos exe

header("Location: $url");
génesis
fuente
@Fabio: va a añadir más detials
Génesis
Eso fue súper fácil. Funcionó. ¿Para qué sirve file-Get_contents? Sólo curioso. Gracias.
John
descargarlo a su servidor
genesis
10
Simplemente elimine 'echo' antes de 'readfile ()' ya que el valor de retorno se especifica como "Devuelve el número de bytes leídos del archivo. Si se produce un error, se devuelve FALSE ya menos que la función se llame como @readfile (), se imprime un mensaje de error ". Entonces terminará con el contenido del archivo + número entero al final del contenido.
Mladen B.
22

Primero muestre su archivo y establezca su valor en url.

index.php

<a href="download.php?download='.$row['file'].'" title="Download File">

download.php

<?php
/*db connectors*/
include('dbconfig.php');

/*function to set your files*/
function output_file($file, $name, $mime_type='')
{
    if(!is_readable($file)) die('File not found or inaccessible!');
    $size = filesize($file);
    $name = rawurldecode($name);
    $known_mime_types=array(
        "htm" => "text/html",
        "exe" => "application/octet-stream",
        "zip" => "application/zip",
        "doc" => "application/msword",
        "jpg" => "image/jpg",
        "php" => "text/plain",
        "xls" => "application/vnd.ms-excel",
        "ppt" => "application/vnd.ms-powerpoint",
        "gif" => "image/gif",
        "pdf" => "application/pdf",
        "txt" => "text/plain",
        "html"=> "text/html",
        "png" => "image/png",
        "jpeg"=> "image/jpg"
    );

    if($mime_type==''){
        $file_extension = strtolower(substr(strrchr($file,"."),1));
        if(array_key_exists($file_extension, $known_mime_types)){
            $mime_type=$known_mime_types[$file_extension];
        } else {
            $mime_type="application/force-download";
        };
    };
    @ob_end_clean();
    if(ini_get('zlib.output_compression'))
    ini_set('zlib.output_compression', 'Off');
    header('Content-Type: ' . $mime_type);
    header('Content-Disposition: attachment; filename="'.$name.'"');
    header("Content-Transfer-Encoding: binary");
    header('Accept-Ranges: bytes');

    if(isset($_SERVER['HTTP_RANGE']))
    {
        list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
        list($range) = explode(",",$range,2);
        list($range, $range_end) = explode("-", $range);
        $range=intval($range);
        if(!$range_end) {
            $range_end=$size-1;
        } else {
            $range_end=intval($range_end);
        }

        $new_length = $range_end-$range+1;
        header("HTTP/1.1 206 Partial Content");
        header("Content-Length: $new_length");
        header("Content-Range: bytes $range-$range_end/$size");
    } else {
        $new_length=$size;
        header("Content-Length: ".$size);
    }

    $chunksize = 1*(1024*1024);
    $bytes_send = 0;
    if ($file = fopen($file, 'r'))
    {
        if(isset($_SERVER['HTTP_RANGE']))
        fseek($file, $range);

        while(!feof($file) &&
            (!connection_aborted()) &&
            ($bytes_send<$new_length)
        )
        {
            $buffer = fread($file, $chunksize);
            echo($buffer);
            flush();
            $bytes_send += strlen($buffer);
        }
        fclose($file);
    } else
        die('Error - can not open file.');
    die();
}
set_time_limit(0);

/*set your folder*/
$file_path='uploads/'."your file";

/*output must be folder/yourfile*/

output_file($file_path, ''."your file".'', $row['type']);

/*back to index.php while downloading*/
header('Location:index.php');
?>
jundell agbo
fuente
2
¿De dónde sacaste esto? Parece poderoso
Axel A. García
@ jundell-agbo interesante y para un archivo crt?
fralbo
Esto también funciona para un archivo muy grande. Gran solución Pero `$ chunksize = 1 * (1024 * 1024);` es lento. Intenté con varios valores y noté que `$ chunksize = 8 * (1024 * 1024);` está usando todo el ancho de banda disponible.
Linga
16

En caso de que tenga que descargar un archivo con un tamaño mayor que el límite de memoria permitido ( memory_limitconfiguración ini), lo que causaría el PHP Fatal error: Allowed memory size of 5242880 bytes exhaustederror, puede hacer esto:

// File to download.
$file = '/path/to/file';

// Maximum size of chunks (in bytes).
$maxRead = 1 * 1024 * 1024; // 1MB

// Give a nice name to your download.
$fileName = 'download_file.txt';

// Open a file in read mode.
$fh = fopen($file, 'r');

// These headers will force download on browser,
// and set the custom file name for the download, respectively.
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');

// Run this until we have read the whole file.
// feof (eof means "end of file") returns `true` when the handler
// has reached the end of file.
while (!feof($fh)) {
    // Read and output the next chunk.
    echo fread($fh, $maxRead);

    // Flush the output buffer to free memory.
    ob_flush();
}

// Exit to make sure not to output anything else.
exit;
Parziphal
fuente
Acabo de probar esto y mató a mi servidor. Escribí una gran cantidad de errores en mis registros.
Daniel Williams el
Sería útil saber de qué se tratan los registros.
Parziphal
Sí, lo siento, el archivo de registro se hizo tan grande que tuve que destruirlo, así que no pude verlo.
Daniel Williams
Tenía que configurar algo como esto en un servidor al que tenía acceso muy limitado. Mi script de descarga siguió redirigiendo a la página de inicio y no pude entender por qué. Ahora sé que el problema fue un error de memoria y este código lo resolvió.
Gavin
Si solo usa ob_flush(), puede haber el error: ob_flush (): no se pudo vaciar el búfer. No hay tampón para lavar . envolverlo con if (ob_get_level() > 0) {ob_flush();}(Referencia stackoverflow.com/a/9182133/128761 )
vee
11

Una modificación de la respuesta aceptada anteriormente, que también detecta el tipo MIME en tiempo de ejecución:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
header('Content-Type: '.finfo_file($finfo, $path));

$finfo = finfo_open(FILEINFO_MIME_ENCODING);
header('Content-Transfer-Encoding: '.finfo_file($finfo, $path)); 

header('Content-disposition: attachment; filename="'.basename($path).'"'); 
readfile($path); // do the double-download-dance (dirty but worky)
Jure Sah
fuente
4

El siguiente código es una forma correcta de implementar un servicio de descarga en php como se explica en el siguiente tutorial

header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=\"$file_name\"");
set_time_limit(0);
$file = @fopen($filePath, "rb");
while(!feof($file)) {
    print(@fread($file, 1024*8));
    ob_flush();
    flush();
}
Grigor Nazaryan
fuente
1
Tiene dos variables en este código: file_namey filePath. No es muy buena práctica de codificación. Especialmente sin explicación. La vinculación a un tutorial externo no es útil.
Khom Nazid
2

prueba esto:

header('Content-type: audio/mp3'); 
header('Content-disposition: attachment; 
filename=“'.$trackname'”');                             
readfile('folder name /'.$trackname);          
exit();
nqobile
fuente
1

También puede transmitir descargas que consumirán significativamente menos recursos. ejemplo:

$readableStream = fopen('test.zip', 'rb');
$writableStream = fopen('php://output', 'wb');

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="test.zip"');
stream_copy_to_stream($readableStream, $writableStream);
ob_flush();
flush();

En el ejemplo anterior, estoy descargando un test.zip (que en realidad era el zip del estudio de Android en mi máquina local). php: // output es una secuencia de solo escritura (generalmente utilizada por echo o print). después de eso, solo necesita configurar los encabezados necesarios y llamar a stream_copy_to_stream (fuente, destino). El método stream_copy_to_stream () actúa como una tubería que toma la entrada de la secuencia de origen (secuencia de lectura) y la canaliza a la secuencia de destino (secuencia de escritura) y también evita el problema de la memoria permitida agotada para que realmente pueda descargar archivos que son más grandes que tu PHP memory_limit.

Saud Qureshi
fuente
¿Qué quieres decir con que la descarga se transmite?
Parsa Yazdani
1
@ParsaYazdani Significa que los datos descargados se fragmentarán. Consulte el concepto de transmisión para obtener más detalles. Nota: este no es el único para transmitir (fragmento) descargar el archivo en PHP. Pero seguramente es uno de los métodos más fáciles.
Saud Qureshi
0

Las respuestas sobre mí funcionan. Pero, me gustaría contribuir con un método sobre cómo realizarlo usando GET

en su página html / php

$File = 'some/dir/file.jpg';
<a href="<?php echo '../sumdir/download.php?f='.$File; ?>" target="_blank">Download</a>

y download.phpcontiene

$file = $_GET['f']; 

header("Expires: 0");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

$ext = pathinfo($file, PATHINFO_EXTENSION);
$basename = pathinfo($file, PATHINFO_BASENAME);

header("Content-type: application/".$ext);
header('Content-length: '.filesize($file));
header("Content-Disposition: attachment; filename=\"$basename\"");
ob_clean(); 
flush();
readfile($file);
exit;

Esto debería funcionar en cualquier tipo de archivo. esto no se prueba con POST, pero podría funcionar.

Mo Chan
fuente