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.



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.


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"); ?>

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");

@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');
?>

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;

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)

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();
}

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();


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.


¿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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.