126

I want to show my users PDF files. The reason why I use CGI to show the PDF is I want to track the clicks for the PDF, and cloak the real location of the saved PDF.

I've been searching on the Internet and only found how to show save dialog to the users and creating a PDF, not show the files to the users.

What I wanted for is show the users my PDF files, not creating or download the PDF.

Here is what I got form the official PHP documentation:

<?php
header('Content-type: application/pdf');
readfile('the.pdf');
?>

Also my google-search-result perl code:

open(PDF, "the.pdf") or die "could not open PDF [$!]";
binmode PDF;
my $output = do { local $/; <PDF> };
close (PDF);
 
print "Content-Type: application/pdf\n";
print "Content-Length: " .length($output) . "\n\n";
print $output

if you do it on ruby, please say it to me. But I'm not sure if my server support rails.

Sorry if my code is too far away from the method to show the pdf, since I don't know anything about pdf processing and how to implement this problem.

Lets assume that the users have the Adobe Reader plug-in. So, how to fix my problem?

edit : I want to show plain PDF file. My primary purpose: track my pdf files and use some fancy urls.

edit : Here's my main php code:

<?php
$file='/files/the.pdf';
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="the.pdf"');
@readfile($file);
?>

edit : Now the code is working. But the loading progress bar (on Adobe Reader X plugin) doesn't shows up. Why? Anyone can help me? Here's my main code:

<?php
$file='./files/the.pdf';
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="the.pdf"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
@readfile($file);
?>

edit : All my problems solved. Here's the final code:

<?php
$file = './path/to/the.pdf';
$filename = 'Custom file name for the.pdf'; /* Note: Always use .pdf at the end. */

header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
header('Accept-Ranges: bytes');

@readfile($file);
?>

Thanks! :)

brian d foy
  • 129,424
  • 31
  • 207
  • 592
dimassony
  • 1,287
  • 2
  • 9
  • 6
  • 5
    "show" is possible only if the client has the Acrobat Reader plug-in installed. Otherwise, it will always be served as a download file, there's nothing you can do about that. Obscuring the PDF's path is also impossible, it is trivial to find out. What are trying to do? Maybe there are some alternatives if we know more about your goals – Pekka Jan 13 '11 at 12:00
  • 4
    the Adobe Reader plugin is only required if you're using a browser that doesn't handle PDFs natively. also, if the PDF is sent to the browser via PHP/Perl, it is **very** possible to obscure the real PDF path, or even have it outside the docroot so it's not directly accessible. – Stephen Jan 13 '11 at 12:04
  • 1
    Adobe Reader is only required if **the user** is using a browser that doesn't handle PDFs natively... – GolezTrol Jan 13 '11 at 12:11
  • @Stephen it is of course possible to obscure the real path, but what the OP seems to be wanting is to obscure the URL to prevent theft. – Pekka Jan 13 '11 at 12:14
  • Let's assume that the users have the Adobe Reader plug-in. So, how to fix my problem? – dimassony Jan 13 '11 at 12:41
  • the OP specifically says the reason for using this method is to track clicks. – Stephen Jan 13 '11 at 12:57
  • @Pekka sorry if my question isn't clear to you. What I wanted to do is show the user plain pdf file, assuming that the user have Adobe Reader. The goals: 1) users is able to save my pdf files. 2) I can track my pdf files. 3) I can show alternative pages of the pdf to the user if user doesn't have the plug-in. 4) I can block some clients, and 5) I can use mod_rewrite of the Apache HTTPD to use some fancy urls. – dimassony Jan 13 '11 at 12:59
  • @dimassony fair enough, that makes it clear. @Stephen's suggestion should be the right way to go – Pekka Jan 13 '11 at 13:00
  • @Stephen do you know how to make it work? – dimassony Jan 13 '11 at 13:07
  • Thank you, my IE10 needs header('Accept-Ranges: bytes'); to work! – neobie Jun 21 '13 at 02:53
  • Don't forget to 'exit;' after the readfile. If you don't it can cause the pdf error 'file is damaged and cannot be repaired' – Jennifer Aug 21 '13 at 19:30
  • `Accept-Ranges: bytes` tells Adobe Reader/other PDF plugins that it's okay for them to issue clever `Range: bytes=XXX-YYY` HTTP requests to download pages of the PDF file, instead of the whole thing at once. – Eric L. Oct 16 '13 at 18:57
  • Well the code is working fine with mozilla safari and chrome but here i am with an another question . if i have idm(internet download manager) installed in my pc it always force get file for downloading and dont let browser to open file ... so how can we prevent download manager's from grabbing our link. – Aitazaz Khan Jun 30 '15 at 23:31

6 Answers6

49

I assume you want the PDF to display in the browser, rather than forcing a download. If that is the case, try setting the Content-Disposition header with a value of inline.

Also remember that this will also be affected by browser settings - some browsers may be configured to always download PDF files or open them in a different application (e.g. Adobe Reader)

Stephen
  • 18,597
  • 4
  • 32
  • 33
  • Yes. But when I'm trying your suggestion, I get an error says "File does not begin with '%PDF-'". I use 'Content-Disposition: inline; filename="the.pdf"'. Do I made a mistake? – dimassony Jan 13 '11 at 12:30
  • 5
  • sounds like it's not a valid PDF file? – Stephen Jan 13 '11 at 13:35
  • @Stephen When I open right to the file, it works. But when I use the inline content dispisition, it doesn't work. – dimassony Jan 13 '11 at 13:39
  • I'm using these headers and it's working: header('Content-type: application/pdf'); header("Content-Disposition: inline; filename=\"$name\""); header('Content-Transfer-Encoding: binary'); header('Content-Length: ' . filesize($file)); @readfile($file); – Stephen Jan 13 '11 at 13:42
  • @Stepehen I'm trying again, now with other PDF files on the Internet. And the result still the same. – dimassony Jan 13 '11 at 13:52
  • what browser/pdf viewer are you using? – Stephen Jan 13 '11 at 13:57
  • @Stephen Firefox 3.6.13 and Adobe Reader with the plugin enabled. – dimassony Jan 13 '11 at 13:59
  • can you use firebug and show the headers that are being sent by the server? – Stephen Jan 13 '11 at 14:00
  • @Stephen This is the headers: Content-Type:application/pdf; Content-Disposition:inline; filename="kick.pdf"; Content-Transfer-Encoding:binary; Keep-Alive:timeout=15, max=99; Connection:Keep-Alive; Transfer-Encoding:chunked; – dimassony Jan 13 '11 at 14:03
  • try removing the Transfer-Encoding: chunked - that gives me an error (in Safari/OSX) when I try it locally – Stephen Jan 13 '11 at 14:17
  • @Stephen Still get the error message. I'm trying to remove all other headers except the content-type and content-disposition. And get the error message pop up again. I'm trying to figure out what's the problem. – dimassony Jan 13 '11 at 14:35
  • can you edit your original post and add the exact contents of your .php file? – Stephen Jan 13 '11 at 14:39
  • @Stephen there you got it... Do you think something is missing? – dimassony Jan 13 '11 at 14:54
  • i've just noticed you're putting in '/files/the.pdf' as the path to the file - PHP will try to access a file called "the.pdf" in a directory called "files" in the root of your hard drive. you may want to change that to `'./files/the.pdf'` or `dirname(__FILE__) . '/files/the.pdf'` – Stephen Jan 13 '11 at 15:03
  • @Stephen Well, that's embarrassing. But the code still not working. And I still don't know why. – dimassony Jan 13 '11 at 15:12
  • Save the resulting file and look at it using a hex editor, or even a simple text editor. Any garbage at the beginning of the file? A PDF MUSt start with '%PDF' as the first four characters. If your PHP is throwing out extra characters (errors/warnings, or even a unicode BOM), then you'll get the "not a pdf" error. – Marc B Jan 13 '11 at 15:14
  • @Marc that's explain all my problems. When i check via Fiddler, it shows an extra char on the first line. Now it's working :) thanks @Stephen :) – dimassony Jan 13 '11 at 15:38
  • Now, the PDF working. But the loading bar (on Adobe Reader X) doesn't shows up. Anyone can help with that? – dimassony Jan 13 '11 at 15:41
  • edit: just noticed you are. not make sure you're setting the Content-Length header – Stephen Jan 13 '11 at 16:43
  • @Stephen I've found that I must set 'Accept-Ranges: bytes' header to make it work. Now My problems solved. – dimassony Jan 14 '11 at 14:00
  • 7
    good to hear, and great to know the solution! It'd be good if you could post the final code showing which headers you're sending, for anyone else who comes across this question. – Stephen Jan 14 '11 at 15:15
  • `Accept-Ranges: bytes` tells Adobe Reader it can issue clever `Range` HTTP requests to download pieces of the PDF file, instead of the whole thing at once. – Eric L. Oct 16 '13 at 18:55
19
    $url ="https://yourFile.pdf";
    $content = file_get_contents($url);

    header('Content-Type: application/pdf');
    header('Content-Length: ' . strlen($content));
    header('Content-Disposition: inline; filename="YourFileName.pdf"');
    header('Cache-Control: private, max-age=0, must-revalidate');
    header('Pragma: public');
    ini_set('zlib.output_compression','0');

    die($content);

Tested and works fine. If you want the file to download instead, replace

    Content-Disposition: inline

with

    Content-Disposition: attachment
Kareem
  • 5,068
  • 44
  • 38
  • Why are you switching off compression? ini_set('zlib.output_compression','0'); – Enigma Plus Jun 19 '20 at 14:31
  • Because zlib compression doesn't work with ob_gzhandler() and this is a snippet of code rather than a full script. Ngnix handles compression better than php. You can use zlib if it suits your app and server setup. – Kareem Jun 20 '20 at 03:11
3

You could modify a PDF renderer such as xpdf or evince to render into a graphics image on your server, and then deliver the image to the user. This is how Google's quick view of PDF files works, they render it locally, then deliver images to the user. No downloaded PDF file, and the source is pretty well obscured. :)

sarnold
  • 102,305
  • 22
  • 181
  • 238
  • This is the only really safe way to prevent the theft of the original document. – Pekka Jan 13 '11 at 12:14
  • 1
    I don't mind about original document theft. Otherwise, I wanted it to be able for users to save it. Also, I wanted to block some client headers. And maybe I will implement your suggestion when the user doesn't have Adobe Reader. – dimassony Jan 13 '11 at 12:37
  • 1
    Besides, it would not be theft, because the original is still there. – Svante Jan 13 '11 at 15:00
  • @Svante - I doubt that owners of [intellectual property](https://wikipedia.org/wiki/Intellectual_property) would agree with your logic. – ashleedawg Jun 30 '19 at 10:36
  • 1
    @ashleedawg: I doubt the moniker “intellectual property”. This is just word hacking to make information hoarding seem like not only a legitimate business model, but even as worthwhile for the public to defend against innovation. – Svante Jul 01 '19 at 14:31
2

The safest way to have a PDF display instead of download seems to be embedding it using an object or iframe element. There are also 3rd party solutions like Google's PDF viewer.

See Best Way to Embed PDF in HTML for an overview.

There's also DoPDF, a Java based In-browser PDF viewer. I can't speak to its quality but it looks interesting.

Community
  • 1
  • 1
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
0

You can also use fpdf class available at: http://www.fpdf.org. It gives options for both outputting to a file and displaying on browser.

tapananand
  • 4,392
  • 2
  • 19
  • 35
-1

There is a simple solution using the embed tag:

<span class="fileShow">
  <a href="aa.pdf" onclick="event.stopPropagation();" target="_blank">
    <embed style="width:450px; height:300px; max-width:450px; max-height:300px" src="aa.pdf">
  </a>
</span>