Advanced Htaccess Demo/Example using Cookies, Headers, Rewrites

FREE THOUGHT · FREE SOFTWARE · FREE WORLD

Advanced Htaccess Demo/Example using Cookies, Headers, Rewrites

Advanced Htaccess NSA

Welcome to the first generation of the new .htaccess tutorials/articles. Basically these are articles detailing my BEST (almost) successful .htaccess experiments, meaning this is the only place on the net you’ll find this technique. It’s home-grown .htaccess, and its some kush, good kush. Instead of just publishing my cryptic results and code in days past, whoah.. a working demo and the NSA building ;)

Using the Mod_Rewrite Variables Cheatsheet makes this example, and all advanced .htaccess code easier to understand. Well, advanced for me at least. I know alot of AskApache visitors are some of the top gurus in many IT fields, but mostly web developers or website owners like me.. This demo is pretty self-explanatory.. Try it out to see how simple of a task this .htaccess trick performs. And make sure you read the whole article as this htaccess technique can be used to do a heck of a lot more than this simple demo.

Set PDF Viewing Mode

Inline

Download

Save As

Please make a selection, defaults to "Save As" mode.



View PDF using selected mode »

Whoa pretty sweet huh? Bet you’ve never seen that before! 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 about this example IMHO is that it uses multiple advanced .htaccess ideas in order for it to work, most htaccess code on the net is very singular. This code uses mod_headers to set the Content-Disposition header for forcing a download and uses mod_rewrite to:

  1. Send different Content-Type headers
  2. Check the value of a cookie
  3. Set environment variables for use later by mod_headers header directive

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.

Htaccess Demo File

For the demo I created the folder /storage/pdf/ and this is the .htaccess file at /storage/pdf/.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
#
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

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

Javascript used by Demo

The best place for javascript is quirksmode, here is a definitive article on setting, reading, parsing, etc.. 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

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}/storage/pdf/$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}/storage/pdf/$1.pdf [L,NC,QSA]

More Info

The following is more information about the Content-Dispositon header and related headers, and will make you an expert at this if you read it all.. (no thanks)

Interesting Reading

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

Intense Reading


«
»

Skip to Comments

Add Your Opinion

Reader Comments

  1. 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

  2. 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.

  3. mdgreenfield ~

    Yup, I’m having problems with “download” mode in FF3. But I figured out what I needed. Thanks.

  4. Justin Hartman ~

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

  5. 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.

  6. 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.


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

HTML | DCMI | GRDDL | XOXO | XDMP | XFN | DOM | XML | XHTML 1.1 Strict | CSS 2.1 | W3C | TLDP | WAI | DISA | ICSI | GIAC | SANS RR | GHOST | DEFCON | NIST | DHS CYBER | NIST | .:: Phrack Magazine ::.

↑ TOPExcept 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. HTTPD based on NCSA HTTPd

Site Map | Contact Webmaster | Email AskApache | Glossary | License and Disclaimer | Terms of Service