cult3

How to open files by file type in PHP

Jul 11, 2012

Table of contents:

  1. Example, please

PHP is a very approachable first language to get to grips with and it’s perfect for building intelligent websites or web applications. PHP is great, but you won’t get very far without other languages like CSS or Javascript. At some point you will probably need to interact with different file types. Opening different types of files is easy, but it is not exactly obvious at first.

Recently I had a question from a young programmer who is just getting to grips with PHP. The question was…

“I need to be able to allow users to open files that sit on my server using PHP! How do I do it?”

At first I was going to just point him in the direction of Stack Overflow, but instead I decided this would make for a nice little tutorial.

So here’s how I approached it.

Example, please

For this tutorial I’m going to give you what I would use to handle opening file types if I were using PHP to handle URL routing.

So for example, each URL request would go through a process to pull the correct files to display on the screen. But if the request was for a file, rather than a page, we would want to display the contents of the file, rather than display it as a page.

So https://mysite.com/about would display the about page, whereas https://mysite.com/style.css would display the css file like on any other website.

This would be useful if you were rolling your own PHP MVC framework from scratch, but the majority of what I’m going to show you will fit into just about any situation where you might want this functionality.

I hope that makes sense.

Anyway…let’s begin!

So the first thing I will do is to define the Root and a Directory Separator. You should really do this in a config file that is abstracted somewhere in your system, but for this example we’ll just define it at the top of the page.

define("DS", DIRECTORY_SEPARATOR);
define("ROOT", dirname(__FILE__));

When you open a file on a web server, you need to point to the location of the file including the root path to open it. The function dirname(__FILE__) pulls the root path directory, so you don’t have to actually write it. This is useful because at some point the path might change. Using a functional representation stops your code from breaking.

Now in the rest of our code we can just use the defined words ROOT and DS whenever we need them. You’ll soon see why this is important.

The next thing I want to do is to grab the request URL and save it into a variable that I can use in my script.

$url = $_SERVER["REQUEST_URI"];

Imagine our URL request is https://mysite.com/thispage/style.css. The /thispage/style.css would be saved into the $url variable.

As I mentioned in the introduction, for this situation I only want to run the code if the request is to a file, rather than a page. To ensure we only run the code if the request is for a file, we just use a simple if statement.

if (pathinfo($url, PATHINFO_EXTENSION)) {
}

This checks to see if the URL requests ends in something like .css or .js. If it does we continue with our script, otherwise we can just skip this whole chunk of code because it is not relevant.

The function pathinfo is a PHP function that allows you to inspect a path and pull information from it. In this example we are checking to see if there is an extension. If there is an extension, the if statement would evaluate as true and we could then proceed with the rest of the script.

So now we have a chunk of code that grabs the URL and inspects it to see if the user is trying to view a file. If they are trying to view a file we can continue to run some code, otherwise we just skip it.

The next thing to do is to build a list of file types that a user is allowed to view. By building a way to view the contents of files, we are potentially exposing ourselves to risk.

To control what file types can be viewed, we create an array to list the extension that are allowed, like this.

$extensions = ["css", "txt", "js", "pdf"];

So as you can see, we only want to allow our users to view css, txt, js or pdf files.

Next we save the extension from the current request into a variable.

$ext = pathinfo($url, PATHINFO_EXTENSION);

As you can see, this is the same function and argument that we used before in the if statement. This time however, we save the extension so we can use it later on.

Next we need to check to see if the current request is one of the file types we allow. To do this we use another if statement. This will prevent people from trying to view the contents of a PHP file, for example.

If someone is trying to view a file type that is not allowed, we just 404 it so to not acknowledge that the file even exists. We don’t want to throw an error that says a user can’t open this type of file because it exposes unnecessary information about our system.

if (in_array($ext, $extensions)) {
} else {
    // Else 404 it
    header("location: /404");
}

The in_array function simply checks to see if what we are looking for is in the array we have specified.

Next we need to check to see if the file that the user has requested exists. For example, they might try to view style1.css when our css file is named style.css. We don’t want to show the user a nasty error. To handle this, use another if statement, like this.

if (file_exists(ROOT . DS . $url)) {
} else {
    // Else 404 it
    header("location: /404");
}

As you can see, we use the PHP function file_exists to determine if the file does in fact exist. If it does we carry on, otherwise we 404 it. Again, if the file exists this if statement will evaluate to true. Notice how we need to run a path to the directory of the file. To do this we use the key words ROOT and DS that we defined earlier.

Next we need to use the appropriate method for opening the specific file type.

To do this we use the old switch-a-roo depending on the file extension. A PHP switch evaluates the argument and runs a chunk of code depending on what you give it. A Switch statement is really just a neater and simpler way of writing many if…else statements. At any point when you can make your code simpler and neater, you should take advantage of it.

switch ($ext) {
}

In this example, we are giving the switch the $ext to evaluate. In other words, run a specific chunk of code depending on what extension we are dealing with.

For CSS files, we open them like this.

case "css";
    $contents = file(ROOT . DS . $url);
    header('Content-type: text/css');
    echo implode(", $contents);
break;

First we get the contents of the file and save it into a variable. Notice again we use the ROOT and DS defined terms to point to the correct directory on the server. Next we need to set the correct header information. For each different type of file we need to use a different header so the contents of the file displays correctly. And finally we echo the contents using the implode function.

For Javascript and Text files the switch is nearly exactly the same apart from the header information.

For Javascript files, we do this.

case "js";
    $contents = file(ROOT . DS . $url);
    header("Content-type: text/javascript");
    echo implode(", $contents);
break;

For text files, we do this.

case "txt";
    $contents = file(ROOT . DS . $url);
    header("Content-type: text/plain");
    echo implode(", $contents);
break;

And for PDF we use the slightly different.

case "pdf";
    $file = ROOT . DS . $url;
    $filename = pathinfo($url, PATHINFO_FILENAME);
    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);
break;

Notice we use the pathinfo function again to pull just the filename.

And there you have it! Now you can easily open whatever file is requested in the correct format and with the correct headers.

Here is the full code.

define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__));

$url = $_SERVER['REQUEST_URI'];

// Check to see if this is a file request
if (pathinfo($url, PATHINFO_EXTENSION)) {
    // Allowed file extensions
    $extensions = array("css", "txt", "js", "pdf");

    // Save extension
    $ext = pathinfo($url, PATHINFO_EXTENSION);

    // Only display if in the allowed extension list
    if (in_array($ext, $extensions)) {
        // Check if file exists
        if (file_exists(ROOT . DS . $url)) {
            // Choose the correct header for the file type
            switch ($ext) {
                case "css";
                    $contents = file(ROOT . DS . $url);
                    header("Content-type: text/css");
                    echo implode(", $contents);
                break;

                case "js";
                    $contents = file(ROOT . DS . $url);
                    header("Content-type: text/javascript");
                    echo implode(", $contents);
                break;

                case "txt";
                    $contents = file(ROOT . DS . $url);
                    header("Content-type: text/plain");
                    echo implode("", $contents);
                break;

                case "pdf";
                    // File
                    $file = ROOT . DS . $url;
                    // Filename
                    $filename = pathinfo($url, PATHINFO_FILENAME);
                    // Open the pdf
                    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);
                break;
            }
        } else {
            // Else 404 it
            header('location: /404');
        }
    } else {
        // Else 404 it
        header('location: /404');
    }
}

I hope this tutorial has helped you. It should be easy enough to change to fit your requirements, but if you have any questions, fell free to leave them in the comments.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.