Tackling Internal Server Errors When Using Jade-PHP

July 13, 2015   |   Reading time: ~9 mins

jade-post-header

I’m currently taking a break from the glitz and glamour of astrophysics and instead am working on various development projects I promised I’d do for people. My main experience with web development in the past couple years has been using the excellent combo of Node.js + Express + Jade + Stylus. This combo is great because it makes your code elegant and easy to read without any compromise by the user. Since the websites I have to work on exist in the PHP world, I wanted to try and bring some of that elegance across by using a Jade library to handle templating and view compilation. I found this library by kylekatarnls, (forked from here, for full disclosure). I’ve been using this package for weeks for local development and it’s never missed a single beat. It just sat in place and diligently did its job. That was until the site went live.

Late last week I did the arduous task of taking a site from a local development state to a production state. I figured it wouldn’t be too bad as I’d already set up the required configuration, it was just a matter of changing a flag. So I changed the flag over and the site that was working perfectly locally suddenly ground to a halt on every single page load with a 500 Internal Server Error. This is more or less the worst kind of error you can come across because unless you have access to the logs from the web server you have no real idea what caused them. So I went about trying to fix this problem.

What It Was Not

Generally there are a couple common causes of internal server errors. These are:

  • Invalid .htaccess File – .htaccess files are really useful for doing anything from changing the access rights of a directory to reconfiguring PHP, but when this file has an error in it, things go bad very quickly. It’s for this reason that if your web server is throwing internal server errors you should first start by checking these files.
  • Running Out of Memory – Some hosts can tend to be a bit stingy with the amount of memory your PHP scripts can consume. I’ve seen some absurdly low numbers, such as 2 megabytes, so if you’re having issues and you think you could be on a bad host or running a large script, this would be a good thing to check. The maximum allowed memory can be found in the memory_limit directive in php.ini, or by calling phpinfo().

The big problem was that I could pretty much count both of these out straight away. Prior to the new website going live, I had a place holder site there and the .htacess file for that was byte-for-byte identical to the one in the new site. I was also definitely not running out of memory as the memory limit was set at a rather generous 128 megabytes and the page loads peaked out at around 8 megabytes. Something fishy was going on.

What Was Actually the Problem

After several days of bashing my head against a brick wall with the problem I finally managed to pin down its cause. Everything was absolutely fine until the page was rendered by the Jade library. When Jade kicked in everything went pear shaped. It was either fix this or do some major rewriting and swap it out. I decided to dig a little deeper and here is what I found.

The code for the package itself is actually rather elegant, and they use some really clever tricks to actually get it to play nice with PHP. To get a Jade template to render you use this code:

$jade = new \Jade\Jade();
echo $jade->render("static/views/view.jade", $vars));

The render function then goes and fetches either the Jade file, compiles it and outputs it, or fetches a cached, pre-compiled Jade file and outputs it. The mechanism behind outputting it is actually rather clever. The compiled jade is loaded into a string and a prepended with a protocol identifier, ‘jade.stream://data;’. They prior to this have registered a StreamWrapper for that type of URL, which allows PHP to understand how to read from and write to this stream. This allows PHP to essentially treat this string of text as a stream from either a file or a URL. Now comes the clever part. To get the PHP in this template to be properly executed with the required scope variables they extract the variables from the array fed in through $vars and include the file stream as if it were a URL. This allows PHP to directly interpret the code in the template.

There is however a little known PHP directive called allow_url_include. This tells PHP whether it can include code from a URL and interpret it. On this particular host, this value was set to off. This meant that as soon as the Jade library tried to include the code from the compiled template and run the code, PHP interpreted it as a URL include and shut the script off straight away; resulting in an internal server error. I had finally pinned down the problem.

How to Fix It

I’ve managed to find a way around this error. I should first point out that please, please, please, if you are using this plugin sanitise user input before putting it anywhere near Jade. This because both the method employed by the original code and my fix are potentially susceptible to code injection attacks. I won’t tell you how to do it here because honestly you’ll want a different technique for each different use, just make sure that you do it. With that public service announcement out of the way, here are the two different ways you can tackle this issue.

The Hacky Way

There’s a really simple way around this issue that involves substituting two lines of code in a single file, Jade.php. Just as a note, the pieces of code I’m quoting are valid for version 1.1 of this package. If you have a different version you may have to hunt around for the offending code. So if we look around line 100 of src/Jade/Jade.php we can see the offending line.

    public function render($input, array $vars = Array())
    {
        $file = $this->options['cache'] ? $this->cache($input) : $this->stream($input);

        extract($vars);
        ob_start();
        try
        {
            include $file;
        }
        catch(\Exception $e)
        {
            ob_end_clean();
            throw $e;
        }
        return ob_get_clean();
    }

We can resolve the issue by replacing it with:

eval(' ?>' . $file . '<?php ');

This uses the PHP eval function to evaluate the PHP code within the streamed template. It also uses the local scope of the calling code to reference variables, so the existing code is compatible with it. There is still one more small fix. Using the include required a protocol identifier appended to front of the text, so that PHP knew which StreamWrapper it was to use when reading the stream. If we want to eval the code we need to remove this or it will show up on the page. We can do this by simply modifying line 124 of the same file, which reads:

return $this->options['stream'].'://data;'.($compiled ? $input : $this->compile($input));

This should be modified to remove the stream identifier from the front, leaving us with:

return ($compiled ? $input : $this->compile($input));

This should remedy your problems.

The Slightly More Elegant Way

There are some problems with the previous approach, and this is the reason I wouldn’t recommend it for the contentious developer. If, like me, you’re using composer to managed your PHP packages, all you need to do is update your packages and all the fixes could be erased. If you don’t remember the issue, this could make you just repeat the search all over again. Secondly, it could be a right pain in the arse to commit to source control, as chances are you probably get your source control to ignore the vendor directory.

In this situation it’s useful to take advantage of the object orientated nature of PHP. We can subclass the Jade class that is causing the problem and overload the offending functions, replacing them with the alternative code. This basically means that instead of making an instance of \Jade\Jade() when we want to render Jade code, we instead make an instance of our subclass. The code for this should look something like…

<?php

require('vendor/autoload.php');

use Jade\Jade;
use Jade\Parser;
use Jade\Lexer;
use Jade\Compiler;

class SMJade extends Jade {
	    /**
     * @param $input
     * @param array $vars
     * @return mixed|string
     */
    
    public function render($input, array $vars = array())
    {
        $file = $this->options['cache'] ? $this->cache($input) : $this->stream($input);

        extract($vars);
        ob_start();
        try
        {
			// We Put this here to get around the allow_url_include directive
			eval(' ?>'.$file.'<?php ');
        }
        catch(\Exception $e)
        {
            ob_end_clean();
            throw $e;
        }
        return ob_get_clean();
    }

    /**
     * Create a stream wrapper to allow
     * the possibility to add $scope variables
     * @param $input
     * @return string
     */
    public function stream($input, $compiled = false)
    {
        if (false === static::$isWrapperRegistered)
        {
            static::$isWrapperRegistered = true;
            stream_wrapper_register($this->options['stream'], 'Jade\Stream\Template');
        }

		// We also change the stream to get around this as well
        return ($compiled ? $input : $this->compile($input));
    }
}

?>

This is the slightly more elegant fix to the issue. But please don’t forget to very carefully sanitise all user input, because the code in the modified state and the original state is very susceptible to code injection. I hope this might have saved you some time beating your head against a wall. Thanks for reading.

Tags:

You Might Also Like...

Leave a Reply

Your email address will not be published. Required fields are marked *

Comments

Sorry, there are currently no comments for this artcle. Why not start a conversation?