FREE THOUGHT · FREE SOFTWARE · FREE WORLD

Home » Htaccess »  3 Ways to Serve PDF Files using Htaccess Cookies, Headers, Rewrites

3 Ways to Serve PDF Files using Htaccess Cookies, Headers, Rewrites

by Charles Torvalds 8 comments

[hide]

FYI, using the Mod_Rewrite Variables Cheatsheet makes this example, and all advanced .htaccess code easier to understand. This demo lets you set a cookie with 1 of 3 values, then you just request the pdf file with a normal link click and get 1 of 3 different responses. This is accomplished with a nice bit of .htaccess code.

As I explain the htaccess code that achieves this, keep in mind this is merely one simple application for this code. It's much more advanced than your basic htaccess trick, notice how this htaccess acts like a php script, very unusual.. I really wanted to share this trick after I created it for one of my clients because this is the tip of the iceberg. Another use would be to display an alternate style sheet depending on a users theme preference. The coolest thing is that it uses multiple advanced .htaccess ideas. This code uses mod_headers to set the Content-Disposition header for forcing a download and uses mod_rewrite to: Send different Content-Type headers, Check the value of a cookie, Set environment variables for use later by mod_headers header directive

Set PDF Viewing Mode - Make a selection, then click the view pdf button.

Inline Download Save As View PDF using selected mode »

What's Going On

There are 3 different ways for a server to send a pdf file in response to a request for one. This causes 3 different ways to open/view the pdf file in the clients browser.

  1. The browser display's a "Save File As" dialog, allowing you to save the file or open.
  2. The browser opens the pdf file "Inline", opening the pdf file in the browser like a web page.
  3. The browser "Downloads" the pdf file automatically as an "Attachment" and then causes an external pdf reader program like adobe reader to open the file.

Some people prefer to have the option of saving the file to view later, some prefer opening it with an external program, and some just like the pdf file to load right in the browser... The point is that by using .htaccess, we can let them choose any of the 3 methods and save their preference for all further pdf files requested from our site by that user.

How It Works

When you click on one of the 3 demo buttons above, "Inline", "Save As", or "Download", a cookie named askapache_pdf is saved in your browser using the javascript below, with the value being set to which button you clicked. Then when you request the pdf file the .htaccess code below uses mod_rewrite to read the value of the askapache_pdf cookie, and depending on which was your preference it will send alternate HTTP Headers that control how your browser handles the file.

Unique HTTP Headers Returned

When it comes down to it, the following information is the 3 modes. Notice each one is different, because these headers are the only thing controlling how your browser handles the file.

Save As Mode (askapache_pdf=s)

Content-Disposition: attachment
Content-Type: application/pdf

Inline Mode (askapache_pdf=i)

Content-Type: application/pdf

Download Mode (askapache_pdf=a)

Content-Type: application/octet-stream

Htaccess Demo File

For the demo I created the folder / and this is the .htaccess file at /.htaccess

The default Content-Type for .pdf files. This will make .pdf files default Content-Type header have the value 'application/pdf' - but the default can be overridden by using RewriteRule with the [T='different/type']

AddType application/pdf .pdf

Turn on the rewrite engine if its already on you dont need this

RewriteEngine On

Skip RewriteRules if not .pdf request, like autoindexing. The next [2] RewriteRule directives are specific for .pdf files so if the filename requested does not end in .pdf then the [S=2] instructs the next 2 RewriteRule directives to be completely skipped.

RewriteRule !.*.pdf$ - [S=2]

The first RewriteCond checks to see if the askapache_pdf cookie is NOT set. The second RewriteCond checks to see if the askapche_pdf cookie has the value of s, which is the value corresponding to someone clicking the "Save As" button.

The [NC,OR] flag means that if the cookie askapache_pdf does not exist, OR (next cond) if the askapache_pdf cookie does exist and is set to 's' then process the RewriteRule. If neither cond is true the rewriterule is skipped.

If one of the RewriteCond is true, then the RewriteRule is processed. The RewriteRule applies to any/all requests (.*) but doesn't rewrite anything (-) This RewriteRule sets an Apache environment variable ASKAPACHE_PDFS to have the value of 1 if either rewritecond is true. The variable can be checked by any directives following the rewriterule in the whole htaccess file. The ASKAPACHE_PDFS ends in S because if this variable exists then it means the users preference is 'Save As'

Notice that if the user requested the pdf file without selecting a preference i.e. no cookie exists, then the ASKAPACHE_PDFS variable is still set. This just lets us pick the default preference for them, in this example the default is 'Save As'

RewriteCond %{HTTP_COOKIE} !^.*askapache_pdf.*$ [NC,OR]
RewriteCond %{HTTP_COOKIE} ^.*askapache_pdf=s.*$ [NC]
RewriteRule .* - [E=ASKAPACHE_PDFS:1]

The RewriteCond checks the askapache_pdf cookie for the value 'a' which 'a' represents 'Download'

If the cookies value is 'a' then the RewriteRule overrides the default Content-Type from 'application/pdf' set with AddType earlier, to 'application/octet-stream', which is a special content-type that tells the browser that the file cannot be loaded by the browser 'Inline', but must be saved which will be opened by an external viewer depending on browser configuration and plugins.

RewriteCond %{HTTP_COOKIE} ^.*askapache_pdf=a.*$
RewriteRule .* - [T=application/octet-stream]

This is superfly. If the cookie/users-preference was 'Save As' (s) then the RewriteRule above the last one set the environment variable ASKAPACHE_PDFS to have the value 1. The Header directive here is ONLY processed in that variable ASKAPACHE_PDFS exists. That is what the end 'env=ASKAPACHE_PDFS' does, it is the condition that must be met or the Header directive is skipped. If the ASKAPACHE_PDFS environment variable set by RewriteRule does exist then the header directive adds the header 'Content-Disposition: attachment' to the normal Response Headers. The 'Content-Disposition: attachment' header instructs your browser to present you with the 'Save As' dialog box allowing you to choose whether you want to save or open.

Header set Content-Disposition "attachment" env=ASKAPACHE_PDFS

Javascript used by Demo

The best place for javascript is quirksmode, here is a definitive article on setting, reading, parsing, etc.. COOKIES.

Note, I now prefer using jQuery over my AAJS javascript library. Also, the whole using cookies aspect is just to highlight some advanced htaccess, you can accomplish this much easier without javascript or cookies.

if(!gi('pdfr'))return;
var pdfr=gi('pdfr');
var cval=getCookie('askapache_pdf');
 
if(cval=='i'){pdfr.innerHTML='Currently set to "Inline".';}
else if(cval=='a'){pdfr.innerHTML='Currently set to "Download" mode.';}
else if(cval=='s'){pdfr.innerHTML='Currently set to "Save As" mode.';}
 
addMyEvent(gi('pdfi'),"mousedown",function(){
  setCookie("askapache_pdf", "i", "", "/", "www.askapache.com"); gi('pdfr').innerHTML = 'Changed mode to "Inline".'; return false; });
addMyEvent(gi('pdfa'),"mousedown",function(){
  setCookie("askapache_pdf", "a", "", "/", "www.askapache.com"); gi('pdfr').innerHTML = 'Changed mode to "Download".'; return false; });
addMyEvent(gi('pdfs'),"mousedown",function(){
  setCookie("askapache_pdf", "s", "", "/", "www.askapache.com"); gi('pdfr').innerHTML = 'Changed mode to "Save As".'; return false; });

Alternative Method - No Cookies + PHP

This is what I came up with first for my client, and then while programming the php I noticed.. Hey! I think I can do the same thing using .htaccess, which would save me on cpu/memory/potential security/etc.. but this works great too. Though you will need to hack the code to get it working probably..

Note that the .htaccess rewrite code I used here used FILENAME-i.pdf or FILENAME-s.pdf to pass the preference to the pdf-dl.php script, it also worked for FILENAME.pdf?i=i

pdf-dl.php

<?php
if (
  !isset($_GET['file'])
  || ($f=$_GET['file'])===false
  || ($fp=@fopen($f,"rb"))===false
  || ($fi=pathinfo($f))===false
  || ($fi['fsize']=filesize($f))===false
  || strtolower($fi["extension"])!='pdf'
) die('Failed');
 
ob_start();
header('Accept-Ranges: bytes');
header("Content-Length: {$fi['fsize']}");
header('Content-Type: application/pdf');
if(!isset($_GET['i'])) header("Content-Disposition: attachment; filename="{$fi['basename']}"");
 
$sent = 0;
while ( !feof($fp) && $sent < $fi['fsize'] && ($buf = fread($fp, 8192)) != '' ){
  echo $buf;
  $sent += strlen($buf);
  flush();  ob_flush();
}
fclose($fp);
exit;
?>

Alternate Method .htaccess

Deny direct request to pdf-dl.php file

RewriteCond %{THE_REQUEST} ^.*pdf-dl.php.*$ [NC]
RewriteRule .* - [F]

Handle PDF files named anything-i.pdf as inline

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ([^/]*)-i.pdf$  /cgi-bin/pdf-dl.php?i=i&file=%{DOCUMENT_ROOT}/$1.pdf [L,NC,QSA,S=1]

Handle PDF files without -i.pdf as attachments

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ([^/]*).pdf$  /cgi-bin/pdf-dl.php?file=%{DOCUMENT_ROOT}/$1.pdf [L,NC,QSA]

More Info

The following is more information about the Content-Dispositon header and related subjects for fast readers.

Interesting Reading

Here is the thread of the original draft proposal for the Content-Disposition header.

Intense Reading


August 20th, 2011

Comments Welcome

  • http://justinhartman.com Justin Hartman

    Great tutorial but this doesn't work on FF3 or Safari on a Mac. In Safari you can view inline (safari supports this mode by default) and the download works as well. However, the Save As doesn't work.

    In FF3 each of the options simply saves the file to your downloads folder.

  • http://www.askapache.com/ AskApache

    Thanks for your comments Justin.. In FF3 (or any version of FF) your personal options of how you want FF to handle the download override these headers. After all, its the browser that receives these headers and determines how to handle the file.

  • http://justinhartman.com Justin Hartman

    Aah that makes sense. So I assume there's no way to force or override this then?

  • http://mdgreenfield.com mdgreenfield

    Yup, I'm having problems with "download" mode in FF3. But I figured out what I needed. Thanks.

  • http://www.askapache.com/ AskApache

    Well you might goto about:config in your location bar, but I know you can change how ff handles this by changing your settings.

  • rejetto

    thank you for this article!

    I was playing with cookies, and found no way to unescape the content of the cookie without using RewriteMap, which is not available in .htaccess files. With

    RewriteMap unescape int:unescape

    i would define my "unescape" function, but i can't because i only have .htaccess, and even if i could, my application would not be as self-contained and neat as it would be relying only an .htaccess

    Do you know of a name of a predefined Rewritemap so that i can unescape in my case?
    thanks

  • Adam

    Same in Chrome. Each mode just saves to the downloads folder.

  • http://URL Silver

    Hi,
    Do you know of anyway to drop a cookie or anything of that sort from a PDF file? Is it even possible?

    Thanks!

My Online Tools

Related Articles
Twitter

  • askapache: Today in 1965 DEC announces PDP-8
  • hubail: RT @askapache: Make sure you unplug your Ethernet when leaving the room, or disable wifi
  • askapache: Make sure you unplug your Ethernet when leaving the room, or disable wifi
  • askapache: My servers, and me, are getting annoyed. Fail2ban works fairly well against all the Chinese brute forcing going on
  • askapache: Can't the Chinese stop ordering their hackers to hack us? Ugh
  • askapache: All I want for my bday is a bottle of American whiskey :)
  • askapache: The first Dino fossil wasn't found until 1822, we sure are young

My Picks
Newest Posts

WordPress Development
Hacking and Hackers

The use of "hacker" to mean "security breaker" is a confusion on the part of the mass media. We hackers refuse to recognize that meaning, and continue using the word to mean someone who loves to program, someone who enjoys playful cleverness, or the combination of the two. See my article, On Hacking.
-- Richard M. Stallman






It's very simple - you read the protocol and write the code. -Bill Joy

Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 3.0 License, just credit with a link.
This site is not supported or endorsed by The Apache Software Foundation (ASF). All software and documentation produced by The ASF is licensed. "Apache" is a trademark of The ASF. NCSA HTTPd.
UNIX ® is a registered Trademark of The Open Group. POSIX ® is a registered Trademark of The IEEE.

Site Map | Contact Webmaster | License and Disclaimer | Terms of Service

↑ TOPMain