Como forçar o download de arquivos com PHP

122

Quero exigir que um arquivo seja baixado quando o usuário visitar uma página da web com PHP. Eu acho que tem algo a ver com file_get_contents, mas não sei como executá-lo.

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

Depois de baixar um arquivo, header(location)ele não está redirecionando para outra página. Apenas para.

John
fonte
possível duplicata forçando página de download em php
hakre
possível duplicação do Forcing para baixar um arquivo usando PHP
usuário

Respostas:

232

Leia os documentos sobre o readfile da função PHP embutida

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

Além disso, certifique-se de adicionar o tipo de conteúdo adequado com base no seu aplicativo de arquivo / zip, aplicativo / pdf etc. - mas apenas se você não desejar acionar a caixa de diálogo Salvar como.

Pit Digger
fonte
6
Por que isso é necessário?
31411 John
Quero dizer, se é outro arquivo .exe que apenas .Else isso deve funcionar bem.
Pit Digger
7
Não se esqueça de liberar ;-) ob_clean (); rubor(); / * antes * / readfile ($ file_url);
Ash
Então, se você tem um arquivo grande de 10 GB, o php tenta carregar esse arquivo inteiro?
GDY
exit () deve ser chamado no final para evitar problemas em potencial (falando por experiência :-)
ykay diz 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(); 
?>

Ou, quando o arquivo não pode ser aberto com o navegador, você pode apenas usar o Locationcabeçalho:

<?php header("Location: http://example.com/go.exe"); ?>
Marek Sebera
fonte
1
O nome do arquivo no cabeçalho não deve ser $file(que contém a parte http), mas um nome de arquivo válido.
Fabio
1
Não há tipo de mídia de aplicativo / download forçado ; use application / octet-stream .
Gumbo
funciona muito bem! Mas adiciona 'ao nome do arquivo armazenado. Por favor use: filename = ". Basename ($ file));
harry4516
@ harry4516 modificado de acordo com a sua descoberta
Marek Sebera
Não está funcionando caro em caso de imagem png para mim. Obrigado.
Kamlesh 7/03
40
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"file.exe\""); 
echo readfile($url);

está correto

ou melhor para exe tipo de arquivos

header("Location: $url");
gênese
fonte
@Fabio: Adicionará mais detials
genesis
Isso foi super fácil. Funcionou. O que é o arquivo-Get_contents então? Apenas curioso. Obrigado.
31411 John
fazer o download para o seu servidor
genesis
10
Apenas remova 'echo' antes de 'readfile ()', pois o valor de retorno é especificado como "Retorna o número de bytes lidos do arquivo. Se ocorrer um erro, FALSE será retornado e, a menos que a função tenha sido chamada como @readfile (), uma mensagem de erro é impressa. " Então, você terminará com o conteúdo do arquivo + número inteiro no final do conteúdo.
precisa
22

Exiba seu arquivo primeiro e defina seu valor em 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
fonte
2
Onde você conseguiu isso? Parece poderosa
Axel A. García
@ Jundell-agbo interessante e para um arquivo CRT?
Fralbo 11/10
Isso também está funcionando para um arquivo muito grande. Ótima solução. Mas `$ chunksize = 1 * (1024 * 1024);` é lento. Tentei com vários valores e notei que `$ chunksize = 8 * (1024 * 1024);` está usando toda a largura de banda disponível.
Linga
16

Caso você precise baixar um arquivo com um tamanho maior que o limite de memória permitido ( memory_limitconfiguração ini), o que causaria o PHP Fatal error: Allowed memory size of 5242880 bytes exhaustederro, você pode fazer o seguinte:

// 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
fonte
Eu apenas tentei isso e matou meu servidor. Escreveu um grande número de erros nos meus logs.
Daniel Williams
Seria útil saber sobre o que eram os logs.
Parziphal
Sim, desculpe, o arquivo de log ficou tão grande que eu precisei apenas destruí-lo, para não ver.
Daniel Williams
Eu estava tendo que configurar algo assim em um servidor ao qual eu tinha acesso muito limitado. Meu script de download continuava redirecionando para a página inicial e eu não conseguia descobrir o porquê. Agora eu sei que o problema foi um erro de memória e esse código o resolveu.
Gavin
Se você acabou de usar ob_flush(), pode haver o erro: ob_flush (): falha ao liberar o buffer. Nenhum buffer para liberar . envolva-o com if (ob_get_level() > 0) {ob_flush();}(Referência stackoverflow.com/a/9182133/128761 )
vee
11

Uma modificação da resposta aceita acima, que também detecta o tipo MIME em tempo de execução:

$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
fonte
4

O código a seguir é uma maneira correta de implementar um serviço de download em php, conforme explicado no tutorial a seguir

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
fonte
1
Você tem duas variáveis ​​neste código: file_namee filePath. Não é uma prática de codificação muito boa. Especialmente sem explicação. O link para o tutorial externo não é útil.
Khom Nazid
2

tente isto:

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

Você também pode transmitir o download por streaming, o que consumirá significativamente menos recursos. exemplo:

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

No exemplo acima, estou baixando um test.zip (que na verdade era o zip do android studio na minha máquina local). php: // output é um fluxo somente de gravação (geralmente usado por eco ou impressão). depois disso, você só precisa definir os cabeçalhos necessários e chamar stream_copy_to_stream (origem, destino). O método stream_copy_to_stream () atua como um canal que pega a entrada do fluxo de origem (fluxo de leitura) e o direciona para o fluxo de destino (fluxo de gravação) e também evita o problema de memória permitida esgotada, para que você possa baixar arquivos maiores que o seu PHP memory_limit.

Saud Qureshi
fonte
O que você quer dizer com download é transmitido?
Parsa Yazdani
1
@ParsaYazdani Significa que os dados baixados serão agrupados. Por favor, veja o conceito de streaming para mais detalhes. Nota: este não é o único a transmitir (chunk) o download do arquivo em PHP. Mas certamente é um dos métodos mais fáceis.
Saud Qureshi
0

As respostas acima de mim funcionam. Mas, gostaria de contribuir com um método sobre como executá-lo usando GET

na sua página html / php

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

e download.phpcontém

$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;

isso deve funcionar em qualquer tipo de arquivo. isso não é testado usando POST, mas pode funcionar.

Mo Chan
fonte