skip to Main Content

Short Question

I want to pass GDFONTPATH environment variable from Apache vhost.conf to PHP. How can I do this?

I am running: Debian 12, Apache 2.4, PHP 8.2

Long question

I am using GD image functions (e.g. imagettftext) in PHP and want to use specific fonts that may not be available system wide. This can be done by setting the environment variable GDFONTPATH and the PHP function putenv is good for this purpose.

However, putenv is also a security concern, and as such on my production servers, I have it listed on disable_functions in my php.ini and as a result, I can’t set this environment var within my PHP script. Instead, setting it using SetEnv in my Apache vhost .conf file seems like a good idea (this is a cut-down example):

<Directory "/srv/www/example.com/public">
  AllowOverride None
  Require all granted
  SetEnv GDFONTPATH "/srv/www/example.com/private/fonts"
</Directory>

The snag is, this doesn’t work as [I] expected! It does set GDFONTPATH, but not locally, so I get the following results:

getenv('GDFONTPATH');        # Returns '/srv/www/example.com/private/fonts'
getenv('GDFONTPATH', true);  # Returns ''

The libgd functions ignore this; they only look at the local environment variable that I only seem to be able to set using the putenv function. But enabling putenv on my production servers is not practical for security reasons, hence my really wanting to use SetEnv in my Apache config!

FWIW: I’ve coded a "pragmatic" work-around that will use PHP’s getenv to read the path from Apache’s SetEnv, and then pass a fully-qualified font-filename to gdlib. This works, but is less than ideal! I’ll likely post this code for future reference, but I really would like a solution where I can set the environment from Apache. This post might offer a potential solution, but it looks like it would apply to all vhosts, so again, not ideal.

I’m a bit sketchy on the difference between environment variables and "local" environment variables.

References:
libgd source regarding GDFONTPATH

2

Answers


  1. Chosen as BEST ANSWER

    If there is no way to pass GDFONTPATH via Apache config, then this is the workaround solution I'm using.

    NOTE: This is based on the example code here.

    <?php
    // Functions very loosely based on Pythons os.path
    // https://docs.python.org/3/library/os.path.html
    function pathJoin(string $path, string ...$paths) {
        foreach ($paths as $segment) {
            if ($segment[0] === DIRECTORY_SEPARATOR) {
                $path = $segment;
            }
            else {
                if ($path[-1] !== DIRECTORY_SEPARATOR) {
                    $path .= DIRECTORY_SEPARATOR;
                }
                $path .= $segment;
            }
        }
        return $path;
    }
    
    function pathSplit(string $path) {
        $pos = strrpos($path, DIRECTORY_SEPARATOR);
        if ($pos === false) {
            return ['', $path];
        }
        return [substr($path, 0, $pos + 1), substr($path, $pos + 1)];
    }
    
    function pathBasename(string $path) {
        return pathSplit($path)[1];
    }
    
    
    // Set the content-type
    header('Content-Type: image/png');
    
    // Create the image
    $im = imagecreatetruecolor(400, 30);
    
    // Create some colors
    $white = imagecolorallocate($im, 255, 255, 255);
    $grey = imagecolorallocate($im, 128, 128, 128);
    $black = imagecolorallocate($im, 0, 0, 0);
    imagefilledrectangle($im, 0, 0, 399, 29, $white);
    
    // The text to draw
    $text = 'Testing...';
    $font = 'arial;msttcorefonts/Arial;dejavu/DejaVuSans';
    // ^ this is a font-stack! So list of fonts to _try_
    
    // Use Apache SetEnv directive to set GDFONTPATH. Since libgd will
    // only use the "local" GDFONTPATH, this won't work directly, and
    // the following code is required to build an absolute file-path:
    if (getenv('GDFONTPATH', true) === false) {
        $gdFontPath = getenv('GDFONTPATH');
        if ($gdFontPath) {
            $delim = ';';
            $tok = strtok($font, $delim);
            while ($tok !== false) {
                $fontFile = pathJoin($gdFontPath, $tok);
                if (strrpos(pathBasename($fontFile), '.') === false) {
                    $fontFile .= '.ttf';
                }
                if (file_exists($fontFile)) {
                    $font = $fontFile;
                    break;
                }
                $tok = strtok($delim);
            }
        }
    }
    
    
    // Add some shadow to the text
    imagettftext($im, 20, 0, 11, 21, $grey, $font, $text);
    
    // Add the text
    imagettftext($im, 20, 0, 10, 20, $black, $font, $text);
    
    // Using imagepng() results in clearer text compared with imagejpeg()
    imagepng($im);
    imagedestroy($im);
    
    

    Differences from the php.net example code:

    1. I've added a few file path helper functions at the start.
    2. I'm using a "font-stack" rather than a single font like the original. The original had $font = 'arial.ttf'; but that didn't work on Debian and I suspect only really works on Windows. However, if the example used a font-stack, it could've listed multiple fonts/relative-paths, and that's what I'm doing in my example.
    3. I've added a chunk of code to check the GDFONTPATH environment variable (as supplied via Apache's SetEnv) and then use that to test each directory against each font listed in the stack. If/when it finds a match, it'll replace $font with an absolute file-path. Otherwise, it'll leave $font as it was.

    This pretty much does what I want, but if SetEnv worked as I expected, this additional code wouldn't be required!

    NOTE: I only test for the .ttf file extension whereas the libgd code tries .ttf, .pfa, .pfb, and .dfont extensions.


  2. SetEnv GDFONTPATH "…"

    As you’ve already found out, SetEnv GDFONTPATH "..." sets Apache HTTPD internal variables, accessible through PHPs’ SAPI via getenv() but not getenv(…, true) for system environment variables. Simply spoken, PHP getenv()s’ $local = true variables aren’t Apache internal environment variables.

    GDFONTPATH

    The GDFONTPATH system environment variable is in use by gdlib as the font-search-path parameter, that is similar to PATH, but for font files, not executable files.

    This makes sense, e.g. to get access to fonts configured with the system, this is similar as to execute binaries on that system: Specifying the basename only suffices.

    However, when your intend is to use a font file that is not configured on that system, again similar as with binaries, you need to provide the absolute pathname of the font that resolves to the directory entry of type file for that font.

    $font_dirname = '/srv/www/example.com/private/fonts';
    $font_basename = 'example.ttf';
    
    $font_filename = $font_dirname . '/' . $font_basename;
    
    set_error_handler(fn (int $type, string $message, string $file = null, int $line = null)
        => throw new ErrorException($message, 0, $type, $file, $line),
        E_ALL,
    );
    
    imagettftext($image, $size, $angle, $x, $y, $color, $font_filename, $text);
    
    restore_error_handler();
    

    Tested against:

    • PHP 8.2
    • GD library 2.3.3
    • GDFONTPATH unset

    GDFONTPATH References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search