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.



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.


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

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

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

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;

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)

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

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


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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.