Jak sprawić, by plik PDF był dostępny do pobrania w linku HTML?

124

Podaję link do pliku pdf na mojej stronie do pobrania, jak poniżej

<a href="myfile.pdf">Download Brochure</a>

Problem występuje wtedy, gdy użytkownik kliknie ten link

  • Jeśli użytkownik zainstalował program Adobe Acrobat, otwiera plik w tym samym oknie przeglądarki w programie Adobe Reader.
  • Jeśli program Adobe Acrobat nie jest zainstalowany, pojawi się wyskakujące okienko dla użytkownika w celu pobrania pliku.

Ale chcę, aby użytkownik zawsze wyświetlał wyskakujące okienko do pobrania, niezależnie od tego, czy program „Adobe Acrobat” jest zainstalowany, czy nie.

Powiedz mi, jak mam to zrobić?

Prashant
źródło

Odpowiedzi:

116

Zamiast tworzyć link do pliku .PDF, zrób coś w rodzaju

<a href="pdf_server.php?file=pdffilename">Download my eBook</a>

który wyświetla niestandardowy nagłówek, otwiera plik PDF (sejf binarny) i drukuje dane do przeglądarki użytkownika, a następnie może wybrać zapisanie pliku PDF pomimo ustawień przeglądarki. Plik pdf_server.php powinien wyglądać następująco:

header("Content-Type: application/octet-stream");

$file = $_GET["file"] .".pdf";
header("Content-Disposition: attachment; filename=" . urlencode($file));   
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");            
header("Content-Length: " . filesize($file));
flush(); // this doesn't really matter.
$fp = fopen($file, "r");
while (!feof($fp))
{
    echo fread($fp, 65536);
    flush(); // this is essential for large downloads
} 
fclose($fp); 

PS: i oczywiście przeprowadź kontrolę poprawności zmiennej "plik", aby zapobiec kradzieży twoich plików, np. Nie akceptuj rozszerzeń plików, odmawiaj ukośników, dodaj .pdf do wartości

TravisO
źródło
2
Mam inny problem z tym, że mój plik znajduje się w /products/brochure/myfile.pdf Podaję zmienną $ file jako $ file_path = $ _SERVER ['DOCUMENT_ROOT']. '/ Products / brochure /'. $ plik; ale pobiera plik jako „% 2Fvar% 2Fwww% 2Fweb15% 2Fweb% 2Fproducts% 2Fbrochure% 2myfile.pdf”
Prashant,
3
@TravisO "Content-type: application/force-download"nie jest nigdzie tutaj wymienione: iana.org/ assignments / media-types / application To całkowicie fałszywy nagłówek. Nie twórz nagłówków i nie wysyłaj ich. Czy mógłbyś zaktualizować swoją odpowiedź. Dzięki.
Nicholas Wilson
4
Uważaj jednak, używając tego kodu dosłownie. Wprowadza to poważną lukę w zabezpieczeniach LFI, ponieważ przekazujesz zmienne GET bezpośrednio do fopen.
Joost
4
Ten kod jest prawdopodobnie niebezpieczny z innego powodu. Jeśli przekażesz linki HTTP do fopen, myślę, że pobierze plik z innego serwera. Może zostać wykorzystane przez osobę atakującą do próby przeskanowania sieci wewnętrznej w poszukiwaniu ujawnionych plików PDF.
Rory McCune
2
Nie wspominając już o tym, jak łatwo byłoby ominąć wszelkie „testy poprawności”, które Twoim zdaniem wykonasz dla parametru „plik”. Ataki polegające na wstrzyknięciu ścieżki / przejściu przez katalog są bardzo prawdopodobne.
AviD
209

To częsty problem, ale niewiele osób wie, że istnieje proste rozwiązanie HTML 5:

<a href="./directory/yourfile.pdf" download="newfilename">Download the pdf</a>

Gdzie newfilenamejest sugerowana nazwa pliku dla użytkownika do zapisania pliku. Lub domyślnie zostanie wybrana nazwa pliku na serwerze, jeśli pozostawisz ją pustą, na przykład:

<a href="./directory/yourfile.pdf" download>Download the pdf</a>

Kompatybilność: przetestowałem to na Firefox 21 i Iron, oba działały dobrze. Może nie działać w przeglądarkach niezgodnych z HTML5 lub nieaktualnych. Jedyną przeglądarką, którą przetestowałem, która nie wymuszała pobierania, jest IE ...

Sprawdź zgodność tutaj: http://caniuse.com/#feat=download

T_D
źródło
9
Jest to proste rozwiązanie, ale niestety niezbyt szeroko obsługiwane, zwł. brak wsparcia przez IE caniuse.com/#feat=download
benebun
2
Tak, wiem dobrze. Dlatego mam dodatkową uwagę na temat kompatybilności. I według twojego źródła zarówno IE, jak i Safari nie obsługują tego podejścia, a przynajmniej jeszcze nie :) W każdym razie, jeśli chcesz, aby wszystkie przeglądarki wymuszały pobieranie, sugeruję sprawdzenie niektórych innych odpowiedzi ...
T_D
3
działa jak urok dzięki Chrome w wersji 39.0.2171.65 (64-bitowej)!
edelans
Rozwiązanie jest łatwe, ale niestety nie jest obsługiwane w IE i Safari.
valkirilov
mówi, że nie ma pozwolenia na dostęp
Hackbal Teamz
50

Nie przechodź przez każdą linię pliku. Zamiast tego użyj readfile , jest szybszy. To jest poza witryną php: http://php.net/manual/en/function.readfile.php

$file = $_GET["file"];
if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header("Content-Type: application/force-download");
    header('Content-Disposition: attachment; filename=' . urlencode(basename($file)));
    // header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    ob_clean();
    flush();
    readfile($file);
    exit;
}

Upewnij się, że wyczyściłeś swoją zmienną get, ponieważ ktoś mógłby pobrać niektóre pliki php ...

Alex V
źródło
4
Funkcja readfile jest rzeczywiście szybsza. Osobiście zalecam użycie tej odpowiedzi zamiast zaakceptowanej
Jose Garrido
24

Zamiast używać skryptu PHP do odczytu i opróżnienia pliku, lepiej jest przepisać nagłówek za pomocą .htaccess. Pozwoli to zachować „ładny” adres URL ( myfile.pdfzamiast download.php?myfile).

<FilesMatch "\.pdf$">
ForceType applicaton/octet-stream
Header set Content-Disposition attachment
</FilesMatch>
Rob W
źródło
Czy nie spowodowałoby to wymuszenia pobierania WSZYSTKICH plików PDF?
TecBrat
1
@TecBrat Tak, ale o to prosił OP. Jeśli chcesz ograniczyć tylko do kilku plików PDF, edytuj ^\.pdf$wyrażenie regularne.
Rob W
1
@TecBrat lub umieść plik .htaccess tylko w podfolderze, w którym to zachowanie jest potrzebne.
habakuk
14

Znalazłem sposób, aby to zrobić za pomocą zwykłego starego HTML i JavaScript / jQuery, które z wdziękiem degradują się. Testowane w IE7-10, Safari, Chrome i FF:

Link HTML do pobrania:

<p>Thanks for downloading! If your download doesn't start shortly, 
<a id="downloadLink" href="...yourpdf.pdf" target="_blank" 
type="application/octet-stream" download="yourpdf.pdf">click here</a>.</p>

jQuery (czysty kod JavaScript byłby bardziej rozwlekły), który symuluje kliknięcie linku po niewielkim opóźnieniu:

var delay = 3000;
window.setTimeout(function(){$('#downloadLink')[0].click();},delay);

Aby uczynić to bardziej niezawodnym, możesz dodać wykrywanie funkcji HTML5, a jeśli go tam nie ma, użyj, window.open()aby otworzyć nowe okno z plikiem.

Alex W.
źródło
5

To jest klucz:

header("Content-Type: application/octet-stream");

Aplikacja typu content / x-pdf-document lub application / pdf jest wysyłana podczas wysyłania pliku PDF. Adobe Reader zwykle ustawia program obsługi dla tego typu MIME, więc przeglądarka przekaże dokument do programu Adobe Reader po odebraniu dowolnego typu PDF MIME.

Sudden Def
źródło
3

Wiem, że jestem bardzo spóźniony, aby odpowiedzieć na to pytanie, ale znalazłem sposób, aby to zrobić w javascript.

function downloadFile(src){
    var link=document.createElement('a');
    document.body.appendChild(link);
    link.href= src;
    link.download = '';
    link.click();
}
Shivek Parmar
źródło
0

Spróbuj tego:

<a href="pdf_server_with_path.php?file=pdffilename&path=http://myurl.com/mypath/">Download my eBook</a>

Kod wewnątrz pdf_server_with_path.php to:

header("Content-Type: application/octet-stream");

$file = $_GET["file"] .".pdf";
$path = $_GET["path"];
$fullfile = $path.$file;

header("Content-Disposition: attachment; filename=" . Urlencode($file));   
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");            
header("Content-Length: " . Filesize($fullfile));
flush(); // this doesn't really matter.
$fp = fopen($fullfile, "r");
while (!feof($fp))
{
    echo fread($fp, 65536);
    flush(); // this is essential for large downloads
} 
fclose($fp);
Saill
źródło
0

Oto inne podejście. Wolę raczej niż polegać na obsłudze przeglądarki lub zajmować się tym w warstwie aplikacji, używać logiki serwera WWW.

Jeśli używasz Apache i możesz umieścić plik .htaccess w odpowiednim katalogu, możesz użyć poniższego kodu. Oczywiście możesz również umieścić to w httpd.conf, jeśli masz do tego dostęp.

<FilesMatch "\.(?i:pdf)$">
  Header set Content-Disposition attachment
</FilesMatch>

Dyrektywa FilesMatch jest po prostu wyrażeniem regularnym, więc można ją ustawić tak szczegółowo, jak chcesz, lub dodać inne rozszerzenia.

Linia nagłówka robi to samo, co pierwsza linia w powyższych skryptach PHP. Jeśli potrzebujesz ustawić również linie Content-Type, możesz to zrobić w ten sam sposób, ale nie uznałem tego za konieczne.

Evan Donovan
źródło
0

Rozwiązałem mój, używając całego adresu URL pliku PDF (zamiast umieszczać nazwę pliku lub lokalizację w href): a href = "domena. Com / pdf / nazwa pliku.pdf"

Mark Allena
źródło
0

jeśli chcesz ograniczyć prędkość pobierania, użyj tego kodu !!

<?php
$local_file = 'file.zip';
$download_file = 'name.zip';

// set the download rate limit (=> 20,5 kb/s)
$download_rate = 20.5;
if(file_exists($local_file) && is_file($local_file))
{
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($local_file));
header('Content-Disposition: filename='.$download_file);

flush();
$file = fopen($local_file, "r");
while(!feof($file))
{
    // send the current file part to the browser
    print fread($file, round($download_rate * 1024));
    // flush the content to the browser
    flush();
    // sleep one second
    sleep(1);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}

?>

Aby uzyskać więcej informacji, kliknij tutaj

Mehran Hooshangi
źródło
0
<!DOCTYPE html>  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
    <title>File Uploader</title>  
    <script src="../Script/angular1.3.8.js"></script>  
    <script src="../Script/angular-route.js"></script>  
    <script src="../UserScript/MyApp.js"></script>  
    <script src="../UserScript/FileUploder.js"></script>  
    <>  
        .percent {  
            position: absolute;  
            width: 300px;  
            height: 14px;  
            z-index: 1;  
            text-align: center;  
            font-size: 0.8em;  
            color: white;  
        }  

        .progress-bar {  
            width: 300px;  
            height: 14px;  
            border-radius: 10px;  
            border: 1px solid #CCC;  
            background-image: -webkit-gradient(linear, left top, left bottom, from(#6666cc), to(#4b4b95));  
            border-image: initial;  
        }  

        .uploaded {  
            padding: 0;  
            height: 14px;  
            border-radius: 10px;  
            background-image: -webkit-gradient(linear, left top, left bottom, from(#66cc00), to(#4b9500));  
            border-image: initial;  
        }  
    </>  
</head>  
<body ng-app="MyApp" ng-controller="FileUploder">  
    <div>  
        <table ="width:100%;border:solid;">  
            <tr>  
                <td>Select File</td>  
                <td>  
                    <input type="file" ng-model-instant id="fileToUpload" onchange="angular.element(this).scope().setFiles(this)" />  
                </td>  
            </tr>  
            <tr>  
                <td>File Size</td>  
                <td>  
                    <div ng-repeat="file in files.slice(0)">  
                        <span ng-switch="file.size > 1024*1024">  
                            <span ng-switch-when="true">{{file.size / 1024 / 1024 | number:2}} MB</span>  
                            <span ng-switch-default>{{file.size / 1024 | number:2}} kB</span>  
                        </span>  
                    </div>  
                </td>  
            </tr>             
            <tr>  
                <td>  
                    File Attach Status  
                </td>  
                <td>{{AttachStatus}}</td>  
            </tr>  
            <tr>  
                <td>  
                    <input type="button" value="Upload" ng-click="fnUpload();" />  
                </td>  
                <td>  
                    <input type="button" value="DownLoad" ng-click="fnDownLoad();" />  
                </td>  
            </tr>  
        </table>  
    </div>  
</body>  
</html>   
user11021789
źródło
1
Powinieneś wyjaśnić, co podałeś w swoim kodzie. Nawet w krótkiej formie, jeśli nie jest to możliwe w szczegółach lub nie jest wymagane w szczegółach.
Suraj Kumar
-1

W aplikacji Ruby on Rails (zwłaszcza w przypadku czegoś takiego jak klejnot Prawn i wtyczka Prawnto Rails) możesz to zrobić w nieco prostszy sposób niż pełny skrypt (jak w poprzednim przykładzie PHP).

W kontrolerze:

def index

 respond_to do |format|
   format.html # Your HTML view
   format.pdf { render :layout => false }
 end
end

Część render: layout => false mówi przeglądarce, aby otworzyła okno „Czy chcesz pobrać ten plik?” zamiast próbować renderować plik PDF. Wtedy możesz normalnie połączyć się z plikiem: http://mysite.com/myawesomepdf.pdf

przy okazji
źródło
Gdzie napisać ten kod? który kontroler, jestem nowy w PHP, proszę wyjaśnić.
Prashant
@Prashant To nie jest PHP, to Ruby on Rails.
eloyesp
@Prashant: Jeśli potrzebujesz przykładu PHP, spójrz na te w dalszej części wątku, ale przeczytaj komentarze o tym, jak mogą być niepewne.
Evan Donovan