จะดาวน์โหลดไฟล์ PDF ในลิงค์ HTML ได้อย่างไร?


124

ฉันกำลังให้ลิงค์ของไฟล์ pdf บนหน้าเว็บของฉันเพื่อดาวน์โหลดเช่นด้านล่าง

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

ปัญหาคือเมื่อผู้ใช้คลิกที่ลิงค์นี้แล้ว

  • หากผู้ใช้ติดตั้ง Adobe Acrobat โปรแกรมจะเปิดไฟล์ในหน้าต่างเบราว์เซอร์เดียวกันใน Adobe Reader
  • หากไม่ได้ติดตั้ง Adobe Acrobat โปรแกรมจะแสดงขึ้นสำหรับผู้ใช้เพื่อดาวน์โหลดไฟล์

แต่ฉันต้องการให้ผู้ใช้ดาวน์โหลดป๊อปอัปเสมอไม่ว่าจะติดตั้ง "Adobe acrobat" หรือไม่ก็ตาม

โปรดบอกฉันว่าฉันจะทำสิ่งนี้ได้อย่างไร?

คำตอบ:


116

แทนที่จะลิงก์ไปยังไฟล์. PDF ให้ทำสิ่งที่ชอบแทน

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

ซึ่งแสดงผลส่วนหัวที่กำหนดเองเปิด PDF (ไบนารีปลอดภัย) และพิมพ์ข้อมูลไปยังเบราว์เซอร์ของผู้ใช้จากนั้นพวกเขาสามารถเลือกบันทึก PDF ได้แม้จะมีการตั้งค่าเบราว์เซอร์ก็ตาม pdf_server.php ควรมีลักษณะดังนี้:

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

ป.ล. : และเรียกใช้การตรวจสอบความถูกต้องของตัวแปร "ไฟล์" อย่างชัดเจนเพื่อป้องกันไม่ให้บุคคลอื่นขโมยไฟล์ของคุณเช่นไม่ยอมรับนามสกุลไฟล์ปฏิเสธเครื่องหมายทับเพิ่ม. pdf ในค่า


2
ฉันกำลังประสบปัญหาอีกอย่างคือไฟล์ของฉันอยู่ที่ /products/brochure/myfile.pdf ฉันให้ตัวแปร $ file เป็น $ file_path = $ _SERVER ['DOCUMENT_ROOT'] '/ products / โบรชัวร์ /' $ ไฟล์ แต่ดาวน์โหลดไฟล์เป็น "% 2Fvar% 2Fwww% 2Fweb15% 2Fweb% 2Fproducts% 2Fbrochure% 2myfile.pdf"
Prashant

3
@TravisO "Content-type: application/force-download"ไม่อยู่ในรายการที่นี่: iana.org/assignments/media-types/applicationมันเป็นส่วนหัวที่หลอกลวงโดยสิ้นเชิง โปรดอย่าสร้างส่วนหัวแล้วส่ง คุณช่วยปรับปรุงคำตอบของคุณ ขอบคุณ
Nicholas Wilson

4
โปรดใช้ความระมัดระวังเมื่อใช้รหัสนี้แบบคำต่อคำ นี้จะแนะนำช่องโหว่ LFI ร้ายแรงขณะที่คุณกำลังผ่าน GET fopenตัวแปรโดยตรงใน
Joost

4
รหัสนี้น่าจะเป็นอันตรายในอีกทางหนึ่ง หากคุณส่งลิงก์ HTTP ไปยัง fopen ฉันคิดว่ามันจะไปดึงไฟล์จากเซิร์ฟเวอร์อื่น ผู้โจมตีสามารถใช้เพื่อพยายามสแกนเครือข่ายภายในของคุณเพื่อหาไฟล์ PDF ที่เปิดเผย
Rory McCune

2
ไม่ต้องพูดถึงว่าการข้าม "การตรวจสอบความสมบูรณ์" ที่คุณคิดว่าจะทำกับพารามิเตอร์ "file" นั้นง่ายเพียงใด การโจมตีเส้นทางการแทรกเส้นทาง / ไดเรกทอรีมีโอกาสมาก
AviD

210

นี่เป็นปัญหาที่พบบ่อย แต่มีเพียงไม่กี่คนที่รู้ว่ามีโซลูชัน HTML 5 ง่ายๆ:

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

ในกรณีที่newfilenameเป็นชื่อไฟล์ที่แนะนำสำหรับผู้ใช้เพื่อบันทึกไฟล์ หรือจะเป็นค่าเริ่มต้นของชื่อไฟล์บนเซิร์ฟเวอร์หากคุณปล่อยว่างไว้เช่นนี้:

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

ความเข้ากันได้: ฉันทดสอบสิ่งนี้บน Firefox 21 และ Iron ซึ่งทั้งคู่ทำงานได้ดี อาจใช้ไม่ได้กับเบราว์เซอร์ที่เข้ากันไม่ได้กับ HTML5 หรือเบราว์เซอร์ที่ล้าสมัย เบราว์เซอร์เดียวที่ฉันทดสอบที่ไม่บังคับให้ดาวน์โหลดคือ IE ...

ตรวจสอบความเข้ากันได้ที่นี่: http://caniuse.com/#feat=download


9
นี่เป็นวิธีง่ายๆ แต่น่าเสียดายที่ไม่ได้รับการสนับสนุนอย่างกว้างขวางโดยเฉพาะ no support by IE caniuse.com/#feat=download
benbun

2
ใช่ฉันรู้ถูก นั่นเป็นเหตุผลที่ฉันมีข้อสังเกตเกี่ยวกับความเข้ากันได้ และตามแหล่งที่มาของคุณทั้ง IE และ Safari ไม่สนับสนุนแนวทางนี้หรืออย่างน้อยก็ยังไม่ :) อย่างไรก็ตามหากคุณต้องการให้เบราว์เซอร์ทั้งหมดบังคับดาวน์โหลดฉันขอแนะนำให้ตรวจสอบคำตอบอื่น ๆ แทน ...
T_D

3
ใช้งานได้ดีอย่างมีเสน่ห์ด้วย chrome Version 39.0.2171.65 (64-bit)!
edelans

วิธีแก้ปัญหานั้นง่าย แต่น่าเสียดายที่ไม่รองรับใน IE และ Safari
valkirilov

บอกว่าไม่อนุญาตให้เข้าถึง
Hackbal Teamz

50

อย่าวนซ้ำไฟล์ทุกบรรทัด ใช้readfileแทนซึ่งเร็วกว่า นี่คือจากไซต์ 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;
}

ตรวจสอบให้แน่ใจว่าได้ทำความสะอาดตัวแปร get ของคุณเนื่องจากมีคนดาวน์โหลดไฟล์ php ได้ ...


4
readfileฟังก์ชั่นเป็นจริงได้เร็วขึ้น ฉันแนะนำให้ใช้คำตอบนี้แทนคำตอบที่ยอมรับเป็นการส่วนตัว
Jose Garrido

24

แทนที่จะใช้สคริปต์ PHP ในการอ่านและล้างไฟล์การเขียนส่วนหัวใหม่โดยใช้จะเรียบร้อย.htaccessกว่า สิ่งนี้จะคงไว้ซึ่ง URL ที่ "ดี" ( myfile.pdfแทนที่จะเป็นdownload.php?myfile)

<FilesMatch "\.pdf$">
ForceType applicaton/octet-stream
Header set Content-Disposition attachment
</FilesMatch>

สิ่งนี้จะไม่ทำให้ไฟล์ PDF ทั้งหมดของคุณบังคับให้ดาวน์โหลดหรือไม่?
TecBrat

1
@TecBrat ใช่ แต่นั่นคือสิ่งที่ OP ถาม หากคุณต้องการ จำกัด เฉพาะ PDF เพียงไม่กี่ไฟล์เท่านั้นให้แก้ไข^\.pdf$นิพจน์ทั่วไป
Rob W

1
@TecBrat หรือใส่ไฟล์. htaccess เฉพาะในโฟลเดอร์ย่อยที่จำเป็นต้องใช้พฤติกรรมนี้
habakuk

14

ฉันพบวิธีที่จะทำได้ด้วย HTML แบบเก่าและ JavaScript / jQuery ที่ลดระดับลงอย่างสวยงาม ทดสอบใน IE7-10, Safari, Chrome และ FF:

HTML สำหรับลิงค์ดาวน์โหลด:

<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 (รหัส JavaScript บริสุทธิ์จะมีรายละเอียดมากกว่า) ที่จำลองการคลิกลิงก์หลังจากล่าช้าเล็กน้อย:

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

เพื่อให้มีประสิทธิภาพมากขึ้นคุณสามารถเพิ่มการตรวจจับคุณลักษณะ HTML5 และหากไม่มีwindow.open()ให้ใช้เพื่อเปิดหน้าต่างใหม่พร้อมไฟล์


5

นี่คือกุญแจสำคัญ:

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

แอปพลิเคชันประเภทเนื้อหา / เอกสาร x-pdf หรือแอปพลิเคชัน / pdf จะถูกส่งในขณะที่ส่งไฟล์ PDF Adobe Reader มักจะตั้งค่าตัวจัดการสำหรับประเภท MIME นี้ดังนั้นเบราว์เซอร์จะส่งเอกสารไปยัง Adobe Reader เมื่อได้รับ PDF MIME ประเภทใดก็ได้


3

ฉันรู้ว่าฉันตอบช้ามาก แต่ฉันพบว่ามีการแฮ็กเพื่อทำสิ่งนี้ในจาวาสคริปต์

function downloadFile(src){
    var link=document.createElement('a');
    document.body.appendChild(link);
    link.href= src;
    link.download = '';
    link.click();
}

0

ลองสิ่งนี้:

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

รหัสภายใน pdf_server_with_path.php คือ:

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

0

นี่คือแนวทางที่แตกต่าง ฉันชอบมากกว่าที่จะพึ่งพาการสนับสนุนเบราว์เซอร์หรือจัดการสิ่งนี้ที่เลเยอร์แอปพลิเคชันเพื่อใช้ตรรกะของเว็บเซิร์ฟเวอร์

หากคุณใช้ Apache และสามารถใส่ไฟล์. htaccess ในไดเร็กทอรีที่เกี่ยวข้องคุณสามารถใช้โค้ดด้านล่างนี้ แน่นอนคุณสามารถใส่สิ่งนี้ใน httpd.conf ได้เช่นกันหากคุณสามารถเข้าถึงได้

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

คำสั่ง FilesMatch เป็นเพียง regex ดังนั้นจึงสามารถตั้งค่าได้อย่างละเอียดตามที่คุณต้องการหรือคุณสามารถเพิ่มในส่วนขยายอื่น ๆ

บรรทัดส่วนหัวจะเหมือนกับบรรทัดแรกในสคริปต์ PHP ด้านบน หากคุณต้องการตั้งค่าบรรทัดประเภทเนื้อหาด้วยคุณสามารถทำได้ในลักษณะเดียวกัน แต่ฉันไม่พบว่าจำเป็น



0

หากคุณต้องการ จำกัด อัตราการดาวน์โหลดให้ใช้รหัสนี้ !!

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

?>

สำหรับข้อมูลเพิ่มเติมคลิกที่นี่


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>   

1
คุณควรอธิบายสิ่งที่คุณระบุไว้ในโค้ดของคุณ แม้ในรูปแบบสั้น ๆ หากไม่สามารถทำได้ในรายละเอียดหรือไม่ต้องการในรายละเอียด
Suraj Kumar

-1

ในแอปพลิเคชัน Ruby on Rails (โดยเฉพาะอย่างยิ่งกับอัญมณีกุ้งและปลั๊กอิน Prawnto Rails) คุณสามารถทำสิ่งนี้ให้สำเร็จได้มากกว่าสคริปต์แบบเต็ม (เช่นตัวอย่าง PHP ก่อนหน้านี้)

ในตัวควบคุมของคุณ:

def index

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

ส่วน render: layout => false บอกให้เบราว์เซอร์เปิดข้อความ "คุณต้องการดาวน์โหลดไฟล์นี้หรือไม่" พร้อมท์แทนที่จะพยายามแสดง PDF จากนั้นคุณจะสามารถเชื่อมโยงไปยังไฟล์ได้ตามปกติ: http://mysite.com/myawesomepdf.pdf


เขียนโค้ดนี้ที่ไหน? ตัวควบคุมใดฉันยังใหม่กับ PHP โปรดอธิบาย
Prashant

@Prashant นี่ไม่ใช่ PHP แต่เป็น Ruby on Rails
eloyesp

@Prashant: หากคุณต้องการตัวอย่าง PHP ให้ดูที่หัวข้อเพิ่มเติมในเธรด แต่โปรดอ่านความคิดเห็นเกี่ยวกับวิธีที่อาจไม่ปลอดภัย
Evan Donovan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.