1

Things to note, I have tested this on two different servers, Debian 9 and Ubuntu 14.04 and the same error persists. Right now I am using Ubuntu 14.04 with PHP 5, I have installed composer, I have installed both wkhtmltopdf and phpwkhtmltopdf correctly. How do I know this? Well wkhtmltopdf/image works via CLI, phpwkhtmltopdf also works via PHP however when I attempt to send the image to the client as an inline display or download the image corrupts. For example;

  1. Visit desired url, for us its test.php
  2. Phpwkhtmltopdf will send commands that hook up with the CLI version wkhtmltopdf
  3. Once the page loads it will visit google.com, save a screenshot on the disk /var/www/html/tmp/page.jpg and that image opens/displays fine, however when I attempt to use $image->send('page.jpg'); the sent image is corrupt/wont open.

I have made two changes to the system, I have disabled mod_deflate within apache2 and I have also increased the max_filesize options within apache2's php.ini config file.

Dependencies

Live Example

http://155.254.35.63/test.php // Generate the image
http://155.254.35.63/tmp/page.png // The image file generated

test.php

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

?>

<?php

$loader = require __DIR__ . '/vendor/autoload.php';

?>

<?php

use mikehaertl\wkhtmlto\Image;

$image = new \mikehaertl\wkhtmlto\Image('https://www.google.co.uk/search?q=what+is+the+time&oq=what+is+the+time&aqs=chrome.0.69i59j69i60l3j0l2.2977j0j4&sourceid=chrome&ie=UTF-8');
$image->setOptions(array(
'binary' => '/usr/local/bin/wkhtmltoimage',
'type' => 'png'
));

$image->saveAs('/var/www/html/tmp/page.png');

header('Content-Type: image/png');
echo file_get_contents('/var/www/html/tmp/page.jpg');

?>

File.php (Lines 72 to 83)

<?php
namespace mikehaertl\tmp;

/**
 * File
 *
 * A convenience class for temporary files.
 *
 * @author Michael Härtl <haertl.mike@gmail.com>
 * @version 1.1.0
 * @license http://www.opensource.org/licenses/MIT
 */
class File
{
    /**
     * @var bool whether to delete the tmp file when it's no longer referenced or when the request ends.
     * Default is `true`.
     */
    public $delete = true;

    /**
     * @var string the name of this file
     */
    protected $_fileName;

    /**
     * Constructor
     *
     * @param string $content the tmp file content
     * @param string|null $suffix the optional suffix for the tmp file
     * @param string|null $prefix the optional prefix for the tmp file. If null 'php_tmpfile_' is used.
     * @param string|null $directory directory where the file should be created. Autodetected if not provided.
     */
    public function __construct($content, $suffix = null, $prefix = null, $directory = null)
    {
        if ($directory===null) {
            $directory = self::getTempDir();
        }

        if ($prefix===null) {
            $prefix = 'php_tmpfile_';
        }

        $this->_fileName = tempnam($directory,$prefix);
        if ($suffix!==null) {
            $newName = $this->_fileName.$suffix;
            rename($this->_fileName, $newName);
            $this->_fileName = $newName;
        }
        file_put_contents($this->_fileName, $content);
    }

    /**
     * Delete tmp file on shutdown if `$delete` is `true`
     */
    public function __destruct()
    {
        if ($this->delete) {
            unlink($this->_fileName);
        }
    }

    /**
     * Send tmp file to client, either inline or as download
     *
     * @param string|null $filename the filename to send. If empty, the file is streamed inline.
     * @param string $contentType the Content-Type header
     * @param bool $inline whether to force inline display of the file, even if filename is present.
     */
    public function send($filename = null, $contentType, $inline = false)
    {
        header('Pragma: public');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Content-Type: image/png');
        header('Content-Transfer-Encoding: binary');

        if ($filename!==null || $inline) {
            $disposition = $inline ? 'inline' : 'attachment';
            header("Content-Disposition: $disposition; filename=\"$filename\"");
        }

        readfile($this->_fileName);
    }

    /**
     * @param string $name the name to save the file as
     * @return bool whether the file could be saved
     */
    public function saveAs($name)
    {
        return copy($this->_fileName, $name);
    }

    /**
     * @return string the full file name
     */
    public function getFileName()
    {
        return $this->_fileName;
    }

    /**
     * @return string the path to the temp directory
     */
    public static function getTempDir()
    {
        if (function_exists('sys_get_temp_dir')) {
            return sys_get_temp_dir();
        } elseif ( ($tmp = getenv('TMP')) || ($tmp = getenv('TEMP')) || ($tmp = getenv('TMPDIR')) ) {
            return realpath($tmp);
        } else {
            return '/tmp';
        }
    }

    /**
     * @return string the full file name
     */
    public function __toString()
    {
        return $this->_fileName;
    }
}

php.ini

;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;

; Whether to allow HTTP file uploads.
; http://php.net/file-uploads
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
; http://php.net/upload-tmp-dir
;upload_tmp_dir = /var/www/html/tmp

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 50M

; Maximum number of files that can be uploaded via a single request
max_file_uploads = 20

There are none (0) errors in the apache log file, which is really putting me off what the issue could be. I have attempted to find a resolution with the dev but no look;

https://github.com/mikehaertl/phpwkhtmltopdf/issues/278

Id appreciate the help on this one.

1 Answers1

1

I have examined the page.jpg file your test site generates. The file itself is intact. This means there is nothing wrong with your plumbing.

The file header shows that instead of a standard JPEG file, yours is a JFIF variant. See if you can set the library to generate a PNG file to workaround this issue.

Edit: now that I see the generated file is correct, see if you can just stream the content instead of using $image->send. Send it youself:

header('Content-Type: image/png');
echo file_get_contents('/var/www/html/tmp/page.jpg');
Schien
  • 3,855
  • 1
  • 16
  • 29
  • 1
    Hi, I made an error which I have changed, within file.php this line (header('Content-Type: image/jpg');) is what it actually is not (image/png) regardless I have tried both and its not making a difference. – user9708574 Apr 27 '18 at 06:38
  • 1
    It wouldn't make a difference when the content of the image file is not recognized. You can actually name a jpg file png, and still load it in an image tag. I used this to test the image. You have to change library call itself. So far it's still emitting the JFIF header. – Schien Apr 27 '18 at 06:42
  • 1
    I wish I knew what you meant by this, how would I change the library call? – user9708574 Apr 27 '18 at 06:44
  • 1
    In $image->setoptions, set the type to 'png'. Not sure if the libary supports it. Give it a try. – Schien Apr 27 '18 at 06:45
  • 1
    I tried that before I changed it to jpg mate, same issue. For reference I have changed the content-type to png within File.php and I have changed all occurances of jpg to png inside test.php, you can view the image it generates when you visit /test.php here - http://155.254.35.63/tmp/page.png, as you can see, perfectly fine which also led me to believe its something to do with the headers in File.php. – user9708574 Apr 27 '18 at 06:49
  • Unfortunately not, I have removed $image->send and replaced it with those 2 lines, the core function of the script still works (the image is saved correctly and visible within /var/www/html/tmp/page.png however when its streaming/displaying in-line the same issue occurs (http://155.254.35.63/test.php) – user9708574 Apr 27 '18 at 06:59
  • disable the "header" line and you'll see some error messages – Schien Apr 27 '18 at 07:00
  • my guess is your script is hitting a memory limit – Schien Apr 27 '18 at 07:00
  • I have commented out this header('Content-Type: image/png'); and as you stated, some crazy chars here (http://155.254.35.63/test.php), means nothing to me though. – user9708574 Apr 27 '18 at 07:02
  • i'm doing a binary comparison. one sec – Schien Apr 27 '18 at 07:02
  • Thanks mate, I do appreciate this a lot. Also, I have increased within /etc/php5/apache2/php.ini this line memory_limit to 500m and its visible on the php info page here http://155.254.35.63/black.php. Still no change. – user9708574 Apr 27 '18 at 07:06
  • they are not the same file at all. the culprit might be the tmp folder setup. just use the /tmp folder instead of /var/www/tmp. then use move_uploaded_file to a target directory, and then read from that file. also try a plain PHP just to stream the content of that file. – Schien Apr 27 '18 at 07:07
  • Added this below where it originally saves to the disk (move_uploaded_file('tmp/page.png', 'image/page.png')), its not moving the image file from /tmp to /image. – user9708574 Apr 27 '18 at 07:21
  • I have changed the file.php to this (https://pastebin.com/BqhD547n), and its appearing correctly. Is my code bad practice at all or ok? – user9708574 Apr 27 '18 at 07:39
  • You have sidestepped the problem of resolving the actual file path. It's working, but you won't be able to cache the image, and not all browsers support the inline image data. You end up delivering a huge payload each time. But again it works, so that's more important at this point. – Schien Apr 27 '18 at 15:43