¿Cómo hacer que un archivo PDF se pueda descargar en un enlace HTML?

124

Estoy dando el enlace de un archivo pdf en mi página web para descargar, como a continuación

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

El problema es cuando el usuario hace clic en este enlace y luego

  • Si el usuario ha instalado Adobe Acrobat, abre el archivo en la misma ventana del navegador en Adobe Reader.
  • Si Adobe Acrobat no está instalado, aparecerá una ventana emergente al usuario para descargar el archivo.

Pero quiero que siempre aparezca una ventana emergente para que el usuario la descargue, independientemente de que "Adobe acrobat" esté instalado o no.

Por favor dime cómo puedo hacer esto?

Prasante
fuente

Respuestas:

116

En lugar de vincular al archivo .PDF, haga algo como

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

que genera un encabezado personalizado, abre el PDF (binario seguro) e imprime los datos en el navegador del usuario, luego pueden elegir guardar el PDF a pesar de la configuración de su navegador. El pdf_server.php debería verse así:

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

PD: y, obviamente, ejecute algunas comprobaciones de sanidad en la variable "archivo" para evitar que las personas roben sus archivos, como no aceptar extensiones de archivo, negar barras, agregar .pdf al valor

TravisO
fuente
2
Estoy enfrentando otro problema con esto, que mi archivo está ubicado en /products/brochure/myfile.pdf. Estoy dando $ file variable como $ file_path = $ _SERVER ['DOCUMENT_ROOT']. '/ Products / brochure /'. $ archivo; pero está descargando el archivo como "% 2Fvar% 2Fwww% 2Fweb15% 2Fweb% 2Fproducts% 2Fbrochure% 2myfile.pdf"
Prashant
3
@TravisO "Content-type: application/force-download"no aparece en ninguna parte aquí: iana.org/assignments/media-types/application Es un encabezado completamente falso. Por favor, no invente encabezados y envíelos. ¿Podrías actualizar tu respuesta? Gracias.
Nicholas Wilson el
44
Sin embargo, tenga cuidado al usar este código literalmente. Esto introduce una vulnerabilidad grave de LFI, ya que estás pasando variables GET directamente fopen.
Joost
44
Este código es probablemente peligroso de otra manera. Si pasa enlaces HTTP a fopen, creo que recuperará el archivo de otro servidor. Un atacante podría usarlo para intentar escanear su red interna en busca de archivos PDF expuestos.
Rory McCune
2
Sin mencionar lo fácil que sería pasar por alto cualquier "verificación de cordura" que creas que harás con el parámetro "archivo". Los ataques de inyección de ruta / recorrido de directorio son extremadamente probables.
AviD
209

Este es un problema común, pero pocas personas saben que hay una solución HTML 5 simple:

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

¿Dónde newfilenameestá el nombre de archivo sugerido para que el usuario guarde el archivo? O, por defecto, el nombre del archivo en el lado del servidor si lo deja vacío, así:

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

Compatibilidad: probé esto en Firefox 21 y Iron, ambos funcionaron bien. Es posible que no funcione en navegadores HTML5 incompatibles u obsoletos. El único navegador que probé que no forzó la descarga es IE ...

Verifique la compatibilidad aquí: http://caniuse.com/#feat=download

T_D
fuente
9
Esta es una solución simple pero desafortunadamente no es muy compatible, especialmente. no es compatible con IE caniuse.com/#feat=download
benebun
2
Sí, lo sé bien. Es por eso que tengo la nota al margen sobre compatibilidad. Y de acuerdo con su fuente, tanto IE como Safari no admiten este enfoque, o al menos todavía no :) De todos modos, si desea que todos los navegadores fuercen la descarga, sugiero que revise algunas de las otras respuestas en su lugar ...
T_D
3
¡funciona como un encanto con la versión 39.0.2171.65 de Chrome (64 bits)!
Edelans
La solución es fácil pero desafortunadamente no es compatible con IE y Safari.
valkirilov
dice que no tiene permiso de acceso
Hackbal Teamz
50

No recorra cada línea de archivo. Utilice readfile en su lugar, es más rápido. Esto está fuera del sitio 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;
}

Asegúrese de desinfectar su variable get ya que alguien podría descargar algunos archivos php ...

Alex V
fuente
44
La función readfile es de hecho más rápida. Yo personalmente recomiendo usar esta respuesta en lugar de la respuesta aceptada
Jose Garrido
24

En lugar de usar un script PHP, para leer y vaciar el archivo, es mejor reescribir el encabezado usando .htaccess. Esto mantendrá una URL "agradable" (en myfile.pdflugar de download.php?myfile).

<FilesMatch "\.pdf$">
ForceType applicaton/octet-stream
Header set Content-Disposition attachment
</FilesMatch>
Rob W
fuente
¿No haría esto que TODOS tus archivos PDF sean forzados a descargar?
TecBrat
1
@TecBrat Sí, pero eso fue lo que preguntó el OP. Si desea limitar solo unos pocos PDF, edite la ^\.pdf$expresión regular.
Rob W
1
@TecBrat o coloque el archivo .htaccess solo en la subcarpeta donde se necesita este comportamiento.
habacuc
14

Encontré una manera de hacerlo con HTML y JavaScript / jQuery que se degrada con gracia. Probado en IE7-10, Safari, Chrome y FF:

HTML para el enlace de descarga:

<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 (el código JavaScript puro sería más detallado) que simula hacer clic en el enlace después de un pequeño retraso:

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

Para hacerlo más robusto, puede agregar detección de características HTML5 y, si no está allí, usar window.open()para abrir una nueva ventana con el archivo.

Alex W
fuente
5

Esta es la clave:

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

El tipo de contenido application / x-pdf-document o application / pdf se envía al enviar un archivo PDF. Adobe Reader generalmente establece el controlador para este tipo MIME, por lo que el navegador pasará el documento a Adobe Reader cuando se reciba cualquiera de los tipos MIME PDF.

Def repentina
fuente
3

Sé que llego muy tarde para responder esto, pero encontré un truco para hacerlo en javascript.

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

Prueba esto:

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

El código dentro de pdf_server_with_path.php es:

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
fuente
0

Aquí hay un enfoque diferente. Prefiero en lugar de confiar en el soporte del navegador, o abordar esto en la capa de aplicación, para usar la lógica del servidor web.

Si está usando Apache y puede colocar un archivo .htaccess en el directorio correspondiente, puede usar el código a continuación. Por supuesto, también puede poner esto en httpd.conf, si tiene acceso a eso.

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

La directiva FilesMatch es solo una expresión regular, por lo que se puede configurar de forma tan granular como desee o puede agregar otras extensiones.

La línea de encabezado hace lo mismo que la primera línea en los scripts PHP anteriores. Si necesita establecer también las líneas de tipo de contenido, puede hacerlo de la misma manera, pero no lo he considerado necesario.

Evan Donovan
fuente
0

Resolví el mío usando la url completa del archivo PDF (en lugar de simplemente poner el nombre o la ubicación del archivo en href): a href = "domain. Com / pdf / filename.pdf"

Mark Allena
fuente
0

Si necesita limitar la velocidad de descarga, use este código.

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

?>

Para más información haga clic aquí.

Mehran Hooshangi
fuente
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>   
usuario11021789
fuente
1
Debe explicar lo que ha proporcionado en su código. Incluso en forma abreviada si no es posible en detalles o no se requiere en detalles.
Suraj Kumar
-1

En una aplicación Ruby on Rails (especialmente con algo como la gema Prawn y el complemento Prawnto Rails), puede lograr esto un poco más simplemente que un script completo (como el ejemplo anterior de PHP).

En tu controlador:

def index

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

La parte render: layout => false le dice al navegador que abra el "¿Desea descargar este archivo?" preguntar en lugar de intentar renderizar el PDF. Entonces podrá vincular el archivo normalmente: http://mysite.com/myawesomepdf.pdf

por cierto
fuente
¿Dónde escribir este código? qué controlador, soy nuevo en PHP, explique.
Prashant
@Prashant Esto no es PHP, es Ruby on Rails.
eloyesp
@Prashant: si necesita un ejemplo de PHP, mire los que están más arriba en el hilo, pero lea los comentarios sobre cómo podrían ser inseguros.
Evan Donovan