3

So I need a little help with a php script I am writing that loads a PDF in a new tab through a PHP script. I am doing it this way to make sure that the PDF can only be accessed if the user is logged in and has a link to it, otherwise the .htaccess in the parent directory to the PDF blocks access.
I am able to read from the PDF using the code below:

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

The result of this is then passed into an AJAX function shown below which generates a new tab and throws the response onto it:

$.ajax({
    url: '../includes/function.projects.php?view-media=' + item,
        success: function(data) {
            $(window.open().document.body).html(data);
        }
    })

This all works smoothly, it's only when the new tab opens, because I am writing the response straight to the documents HTML, I am getting a screen full of text like this:

%PDF-1.5 %���� 2 0 obj << /Linearized 1 /L 17007 /H [ 687 126 ] /O 6 /E 16732 /N 1 /T 16731 >>

So my question is quite simply this; Has anyone tried managed to do this before? If so, are there any libraries I should be using/headers I'm missing etc

I have already seen this question here but I cannot get the answers people are listing to work so I am out of ideas.

Any help would be greatly appreciated, Thanks in advance!

lewisnewson
  • 410
  • 6
  • 22
  • 3
    This is only a guess, though I have done similar things to secure images. I don't think you can simply pass the data like that through `window.open.document.body` because you're not sending any headers with it. Why not just pop open a window with your `function.projects.php?view-media=' + item` as the url and do a security check at the top of it? – dmgig Jun 11 '18 at 20:31
  • hm... though look at this: https://stackoverflow.com/questions/10516463/request-a-file-with-a-custom-header – dmgig Jun 11 '18 at 20:35
  • Thats a good idea! I already have a function written to check the session so that would be nice and easy to implement. Another way I have just started to look into was to load a pre-built page that passes the url of the PDF through a GET variable and then a JS library takes over to grab it and load the PDF onto the page...I like your idea better though :) – lewisnewson Jun 11 '18 at 20:36
  • Good luck! Check out that other link though - perhaps all you need is to convert it to binary and prepend your string with `data:application/pdf;base64,` and your current plan will work. – dmgig Jun 11 '18 at 20:38
  • If no one answers your question in full, please do come back and put your solution as an answer. I'd like to see what you figure out. – dmgig Jun 11 '18 at 20:39
  • 1
    Yup will do mate, I'll keep tinkering and then as soon as I have something that works smoothly I'll add it as an answer – lewisnewson Jun 11 '18 at 20:40

1 Answers1

3

Right so I have a solution. If someone can come up with a better way of doing this, please let me know but for now this works perfectly! I found a JS library called PDFObject which can be used to make a cross-browser solution for easily embedding PDFs into a html document. With this, I can pass in a PDF link on the server along with a target div for it to embed into (which in my case I have setup inside of a modal so we stay on the same page).

Because we are staying on the same page, I can block access to the entire page using a function I had written previously in the project which redirects the user to the index if they don't have the right access level. Something along the lines of this would work:

function checkSession() {
    if(empty($_SESSION)) {
        header("Location: /?cheeky_monkey");
    }
}

If the user tried to access the PDF directly, then this code in the htaccess blocks them. It only allows access if being targeted through the application:

SetEnvIf Referer "localhost" is_local_referer
<FilesMatch "\.pdf$">
    Require env is_local_referer
</FilesMatch>

With that out the way, all that needs to happen is to throw a URL at the script tag to load when a PDF is clicked on, accomplished through this sequence of code:

  1. First we need to grab the URL for the PDF (you might be able to skip this step)

To begin with, this javascript is called whenever someone clicks to view a PDF (this call is simply nested as an onclick attribute):

function viewMedia(projectID, type, item) {
    $.ajax({
        url: '../includes/function.projects.php?cid=' + projectID + '&type=' + type + '&view-media=' + item,
        success: function(data) {
            $('.pdf-script').html('<script>PDFObject.embed("' + data + '", "#the-pdf");</script>');
            $('#viewMediaModal').modal('toggle');
        }
    })
}

This is the code that is ran on the php page.

public function viewMedia($item) {
    $mediaItems = unserialize($this->media);
    $file = '/path/to/pdf/' . $mediaItems[$item]['file'];
    echo $file;
}

An example of the returned data from this function:

/path/to/pdf/example-pdf.pdf
  1. Using this to load the PDF

Once we have the correct URL for the PDF and the AJAX returns as successful, the data received back can be used to re-generate the javascript, this time with the new PDF link:

success: function(data) {
    $('.pdf-script').html('<script>PDFObject.embed("' + data + '", "#the-pdf");</script>');
    $('#viewMediaModal').modal('toggle');
}

This is what the structure of that modal body looks like:

<div class="modal-body">
    <div id="the-pdf"></div>
    <div class="pdf-script" style="display: none;">
        <script>PDFObject.embed('', '#the-pdf');</script>
    </div>
</div>
  1. The result

The result of the above code sequence toggles a modal that look like the below:

enter image description here

And if the user tries to access the file directly:

enter image description here

lewisnewson
  • 410
  • 6
  • 22