Jak wymusić pobieranie plików za pomocą PHP

125

Chcę wymagać pobrania pliku, gdy użytkownik odwiedza stronę internetową z PHP. Myślę, że ma to coś wspólnego file_get_contents, ale nie jestem pewien, jak to wykonać.

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

Po pobraniu pliku z header(location)nim nie następuje przekierowanie na inną stronę. Po prostu się zatrzymuje.

Jan
źródło
możliwy duplikat wymuszania pobrania strony w php
hakre
możliwy duplikat Wymuszenie pobrania pliku przy użyciu PHP
użytkownik

Odpowiedzi:

234

Przeczytaj dokumentację o wbudowanej funkcji PHP readfile

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

Upewnij się również, że dodajesz odpowiedni typ zawartości w oparciu o plik application / zip, application / pdf itp. - ale tylko wtedy, gdy nie chcesz uruchamiać okna dialogowego Zapisz jako.

Pit Digger
źródło
6
Dlaczego jest to wymagane?
john
Mam na myśli, że jeśli jest to plik inny niż .exe, to tylko .Else to powinno działać dobrze.
Pit Digger
7
Nie zapomnij o opróżnieniu ;-) ob_clean (); spłukać(); / * przed * / readfile ($ file_url);
Ash
Więc jeśli masz duży plik o rozmiarze 10 GB, php próbuje załadować cały plik?
GDY
exit () powinna zostać wywołana na koniec, aby uniknąć ewentualnych problemów (mówiąc z doświadczenia :-)
ykay mówi dozbrojenie 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(); 
?>

Lub, jeśli pliku nie można otworzyć w przeglądarce, możesz po prostu użyć Locationnagłówka:

<?php header("Location: http://example.com/go.exe"); ?>
Marek Sebera
źródło
1
nazwa pliku w nagłówku nie powinna być $file(która zawiera część http), ale poprawną nazwą pliku.
Fabio
1
Nie ma typu nośnika aplikacji / wymuszonego pobierania ; zamiast tego użyj application / octet-stream .
Gumbo
działa bardzo ładnie! Ale dodaje „do zapisanej nazwy pliku. Użyj: filename = ". Basename ($ file));
harry4516
@ harry4516 zmodyfikowane zgodnie z twoim ustaleniem
Marek Sebera
Nie działa kochanie w przypadku obrazu png dla mnie. Dzięki.
Kamlesh
40
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"file.exe\""); 
echo readfile($url);

jest poprawne

lub lepiej dla plików typu exe

header("Location: $url");
geneza
źródło
@Fabio: Doda więcej detali
genesis
To było super łatwe. Zadziałało. Po co więc file-Get_contents? Po prostu ciekawy. Dzięki.
john
aby pobrać go na swój serwer
genesis
10
Po prostu usuń „echo” przed „readfile ()”, ponieważ wartość zwracana jest określona jako „Zwraca liczbę bajtów odczytanych z pliku. Jeśli wystąpi błąd, zwracane jest FALSE i chyba że funkcja została wywołana jako @readfile (), drukowany jest komunikat o błędzie. ”. Więc skończysz z zawartością pliku + liczbą całkowitą na końcu zawartości.
Mladen B.
22

Wyświetl najpierw swój plik i ustaw jego wartość na adres 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
źródło
2
Skąd to masz? Wydaje się potężne
Axel A. García
@ jundell-agbo interesujące i dla pliku crt?
fralbo
Działa to również w przypadku bardzo dużego pliku. Świetne rozwiązanie. Ale `$ chunksize = 1 * (1024 * 1024);` jest powolny. Próbowałem z wieloma wartościami i zauważyłem, że `$ chunksize = 8 * (1024 * 1024);` wykorzystuje całą dostępną przepustowość.
Linga
16

W przypadku, gdy musisz pobrać plik o rozmiarze większym niż dozwolony limit pamięci ( memory_limitustawienie ini), co spowodowałoby PHP Fatal error: Allowed memory size of 5242880 bytes exhaustedbłąd, możesz to zrobić:

// 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
źródło
Właśnie tego spróbowałem i to zabiło mój serwer. Napisałem ogromną liczbę błędów w moich dziennikach.
Daniel Williams,
Przydałoby się wiedzieć, o czym są dzienniki.
Parziphal
Tak, przepraszam, plik dziennika stał się tak duży, że musiałem go po prostu wysadzić, więc nie mogłem go zobaczyć.
Daniel Williams,
Musiałem ustawić coś takiego na serwerze, do którego miałem bardzo ograniczony dostęp. Mój skrypt pobierania ciągle przekierowywał na stronę główną i nie mogłem zrozumieć, dlaczego. Teraz wiem, że problemem był błąd pamięci i ten kod go rozwiązał.
Gavin,
Jeśli używasz tylko ob_flush()tego, może wystąpić błąd: ob_flush (): nie udało się opróżnić bufora. Brak bufora do opróżnienia . wrap around it with if (ob_get_level() > 0) {ob_flush();}(Reference stackoverflow.com/a/9182133/128761 )
vee
11

Modyfikacja zaakceptowanej odpowiedzi powyżej, która również wykrywa typ MIME w czasie wykonywania:

$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
źródło
4

Poniższy kod jest prawidłowym sposobem implementacji usługi pobierania w php, jak wyjaśniono w poniższym samouczku

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
źródło
1
W tym kodzie masz dwie zmienne: file_namei filePath. Niezbyt dobra praktyka kodowania. Zwłaszcza bez wyjaśnienia. Łączenie z zewnętrznym samouczkiem nie jest pomocne.
Khom Nazid
2

Spróbuj tego:

header('Content-type: audio/mp3'); 
header('Content-disposition: attachment; 
filename=“'.$trackname'”');                             
readfile('folder name /'.$trackname);          
exit();
nqobile
źródło
1

Możesz także przesyłać strumieniowo pobieranie, które zużywa znacznie mniej zasobów. przykład:

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

W powyższym przykładzie pobieram plik test.zip (w rzeczywistości był to plik zip ze studia Android na moim komputerze lokalnym). php: // wyjście jest strumieniem tylko do zapisu (zwykle używane przez echo lub print). po tym wystarczy ustawić wymagane nagłówki i wywołać stream_copy_to_stream (źródło, miejsce docelowe). Metoda stream_copy_to_stream () działa jak potok, który pobiera dane wejściowe ze strumienia źródłowego (strumień odczytu) i kieruje je do strumienia docelowego (strumień zapisu), a także pozwala uniknąć problemu wyczerpania dozwolonej pamięci, dzięki czemu można faktycznie pobrać pliki, które są większe niż twój PHP memory_limit.

Saud Qureshi
źródło
Co masz na myśli, mówiąc, że pobieranie jest przesyłane strumieniowo?
Parsa Yazdani
1
@ParsaYazdani Oznacza to, że pobrane dane zostaną podzielone na fragmenty. Aby uzyskać więcej informacji, zapoznaj się z koncepcją przesyłania strumieniowego. Uwaga: nie jest to jedyne pobieranie strumieniowe (fragmenty) pliku w PHP. Ale z pewnością jest to jedna z najłatwiejszych metod.
Saud Qureshi
0

Odpowiedzi przede mną działają. Ale chciałbym wnieść wkład w metodę, jak wykonać to za pomocą GET

na twojej stronie html / php

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

i download.phpzawiera

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

powinno to działać na wszystkich typach plików. nie jest to testowane przy użyciu POST, ale może działać.

Mo Chan
źródło