phpxamppmimejquery-file-uploadkcfinder

PHP PNG images are being uploaded with mime "image/jpeg" on KCFINDER 3.12


At this point, I'm very confused for something an image called this-thing.png (created as PNG24 out of photoshop CS5), gets uploaded to kcfinder as image/jpeg.

enter image description here

Analyzed with TriID, shows the image is completely 100% PNG

C:\TrID>trid C:\users\michael\downloads\this-thing.png

TrID/32 - File Identifier v2.20 - (C) 2003-15 By M.Pontello
Definitions found:  3790
Analyzing...

Collecting data from file: C:\users\michael\downloads\this-thing.png
100.0% (.PNG) Portable Network Graphics (16000/1)

But when the variable data within $_FILES[(key($_FILES)]['tmp_name'], reaches kcfinder\core\class\browser.php function moveUploadFile (as the argument $file) ... the mime type from the tmp_name comes up as JPEG and not as PNG.

Modified function, for testing purposes, included code from getimagesize() not returning false

protected function moveUploadFile($file, $dir) {

    $message = $this->checkUploadedFile($file);

    if ($message !== true) {
        if (isset($file['tmp_name']))
            @unlink($file['tmp_name']);
        return "{$file['name']}: $message";
    }

    $filename = $this->normalizeFilename($file['name']);
    $target = "$dir/" . file::getInexistantFilename($filename, $dir);

    echo "<h3>PHP_FILES foreach</h3>";

    foreach ($_FILES['upload']['name'] as $key => $value){    
        echo "<pre>";
        print_r(getimagesize($_FILES['upload']['tmp_name'][$key]));
        echo "</pre>";
    }

    echo "<h3>TEMP FILE</h3>";
    echo "</strong>file variable</strong>";
    echo "<pre>";
    print_r($file);
    echo "</pre>";

    echo "</strong>file.tmp_name</strong>";
    echo "<pre>";
    print_r($file['tmp_name']);
    echo "</pre>";

    $tmp_name_imagesize = getimagesize($file['tmp_name']);

    echo "<pre>";
    print_r($tmp_name_imagesize);
    echo "</pre>";

    if (imagetypes() & IMG_PNG) { echo "PNG Supported"; } else { echo "PNG not supported."; }

    // mkaatman - move tmp file to /tmp/ to check its MD5SUM result
    $temporary_file_path = $file['tmp_name'] . ".uploaded";
    move_uploaded_file($file['tmp_name'], $temporary_file_path);

    die();

}

Result, during the upload of the file this-thing.png, shows that the media type is in fact JPG and not PNG (this is the part, I can't seem to wrap my head around).

enter image description here
(source: iforce.co.nz)

Apparently this file php92C2.tmp.uploaded is the uploaded file, to the /tmp/ directory.

enter image description here

I have added .png to the end of the file, using Windows Rename, for the purposes of file analysis.

C:\TrID>trid C:\users\michael\downloads\php92C2.tmp.uploaded.png

TrID/32 - File Identifier v2.20 - (C) 2003-15 By M.Pontello
Definitions found:  3790
Analyzing...

Collecting data from file: C:\users\michael\downloads\php92C2.tmp.uploaded.png
 50.0% (.JPG) JFIF JPEG Bitmap (4003/3)
 37.4% (.JPG) JPEG Bitmap (3000/1)
 12.4% (.MP3) MP3 audio (1000/1)

But if the image is tested directly through PHP (copy+paste into a directory)

<?php

$image_file = "this-thing.png";
$image_file_details = getimagesize($image_file);

echo "<pre>";
print_r($image_file_details);
echo "</pre>";

?>

The result, reads that the image is in-fact PNG.

Array
(
    [0] => 800
    [1] => 300
    [2] => 3
    [3] => width="800" height="300"
    [bits] => 8
    [mime] => image/png
)

The form used within kcfinder/cache/base.js function function _.initUploadButton = function() is your basic, upload form.

<div id="upload" style="top:5px;width:77px;height:31px">
    <form enctype="multipart/form-data" method="post" target="uploadResponse" action="browse.php?type=image&lng=en&opener=ckeditor&act=upload"><input type="file" name="upload[]" onchange="_.uploadFile(this.form)" style="height:31px" multiple="multiple" /><input type="hidden" name="dir" value="" /></form>
</div>

Finally some information from KCFINDER.Config from php

CONFIG.imageDriversPriority

imagick gmagick gd

CONFIG.deniedExts

exe com msi bat php phps phtml php3 php4 cgi pl htaccess htm html

CONFIG.types

Array
(
    [image] => 7z aiff asf avi bmp csv doc fla flv gif gz gzip jpeg jpg mid mov mp3 mp4 mpc mpeg mpg ods odt pdf png ppt pxd qt ram rar rm rmi rmvb rtf sdc sitd swf sxc sxw tar tgz tif tiff txt vsd wav wma wmv xls xml zip
    [images] => 7z aiff asf avi bmp csv doc fla flv gif gz gzip jpeg jpg mid mov mp3 mp4 mpc mpeg mpg ods odt pdf png ppt pxd qt ram rar rm rmi rmvb rtf sdc sitd swf sxc sxw tar tgz tif tiff txt vsd wav wma wmv xls xml zip
    [files] => 7z aiff asf avi bmp csv doc fla flv gif gz gzip jpeg jpg mid mov mp3 mp4 mpc mpeg mpg ods odt pdf png ppt pxd qt ram rar rm rmi rmvb rtf sdc sitd swf sxc sxw tar tgz tif tiff txt vsd wav wma wmv xls xml zip
    [uploads] => 7z aiff asf avi bmp csv doc fla flv gif gz gzip jpeg jpg mid mov mp3 mp4 mpc mpeg mpg ods odt pdf png ppt pxd qt ram rar rm rmi rmvb rtf sdc sitd swf sxc sxw tar tgz tif tiff txt vsd wav wma wmv xls xml zip
    [mimages] => *mime image/gif image/png image/jpeg
)

Based on all of this information, I can't seem to figure out why on earth an image being uploaded as PNG, comes back as JPEG.

EDIT: I have tested, kcfinder using image created from mspaint (this is where it gets confusing)

The tested PNG image.

enter image description here

The result (based on the above code).

enter image description here

C:\TrID>trid C:\users\michael\Pictures\breaking-bad.png

TrID/32 - File Identifier v2.20 - (C) 2003-15 By M.Pontello
Definitions found:  3790
Analyzing...

Collecting data from file: C:\users\michael\Pictures\breaking-bad.png
100.0% (.PNG) Portable Network Graphics (16000/1)

C:\TrID>

EDIT: PNG Support (reply for markman)

if (imagetypes() & IMG_PNG) {
    echo "PNG Supported";
} else {
    echo "PNG not supported.";
}

EDIT: I've found where the image is being converted from PNG to JPG

if checkUploadedFile is commented out during moveUploadFile the file this-thing.png comes out as expected PNG....

protected function checkUploadedFile(array $aFile=null) {
    $config = &$this->config;
    $file = ($aFile === null) ? $this->file : $aFile;

    if (!is_array($file) || !isset($file['name']))
        return $this->label("Unknown error");

    if (is_array($file['name'])) {
        foreach ($file['name'] as $i => $name) {
            $return = $this->checkUploadedFile(array(
                'name' => $name,
                'tmp_name' => $file['tmp_name'][$i],
                'error' => $file['error'][$i]
            ));
            if ($return !== true)
                return "$name: $return";
        }
        return true;
    }

    $extension = file::getExtension($file['name']);
    $typePatt = strtolower(text::clearWhitespaces($this->types[$this->type]));

    // CHECK FOR UPLOAD ERRORS
    if ($file['error'])
        return
            ($file['error'] == UPLOAD_ERR_INI_SIZE) ?
                $this->label("The uploaded file exceeds {size} bytes.",
                    array('size' => ini_get('upload_max_filesize'))) : (
            ($file['error'] == UPLOAD_ERR_FORM_SIZE) ?
                $this->label("The uploaded file exceeds {size} bytes.",
                    array('size' => $_GET['MAX_FILE_SIZE'])) : (
            ($file['error'] == UPLOAD_ERR_PARTIAL) ?
                $this->label("The uploaded file was only partially uploaded.") : (
            ($file['error'] == UPLOAD_ERR_NO_FILE) ?
                $this->label("No file was uploaded.") : (
            ($file['error'] == UPLOAD_ERR_NO_TMP_DIR) ?
                $this->label("Missing a temporary folder.") : (
            ($file['error'] == UPLOAD_ERR_CANT_WRITE) ?
                $this->label("Failed to write file.") :
                $this->label("Unknown error.")
        )))));

    // HIDDEN FILENAMES CHECK
    elseif (substr($file['name'], 0, 1) == ".")
        return $this->label("File name shouldn't begins with '.'");

    // EXTENSION CHECK
    elseif (
        (substr($file['name'], -1) == ".") ||
        !$this->validateExtension($extension, $this->type)
    )
        return $this->label("Denied file extension.");

    // SPECIAL DIRECTORY TYPES CHECK (e.g. *img)
    elseif (preg_match('/^\*([^ ]+)(.*)?$/s', $typePatt, $patt)) {
        list($typePatt, $type, $params) = $patt;
        $class = __NAMESPACE__ . "\\type_$type";
        if (class_exists($class)) {
            $type = new $class();
            $cfg = $config;
            $cfg['filename'] = $file['name'];
            if (strlen($params))
                $cfg['params'] = trim($params);
            $response = $type->checkFile($file['tmp_name'], $cfg);
            if ($response !== true)
                return $this->label($response);
        } else
            return $this->label("Non-existing directory type.");
    }

    // IMAGE RESIZE
    $img = image::factory($this->imageDriver, $file['tmp_name']);
    if (!$img->initError && !$this->imageResize($img, $file['tmp_name']))
        return $this->label("The image is too big and/or cannot be resized.");

    return true;
}

Output is PNG Supported.


Solution

  • This was pretty confusing issue, I was having. But I have managed to fix it.

    Steps involved are as follows

    Open the file kcfinder/core/class/uploader.php find the function imageResize. Then modify the this.output code.

    Before:

        // WRITE TO FILE
        return $img->output("jpeg", array(
            'file' => $file,
            'quality' => $this->config['jpegQuality']
        ));
    

    After:

        // Check the EXTENSION OF THIS FILE
        if($file && is_string($file) && file_exists($file)) {
            $file_imgsize = @getimagesize($file);
            // Get the EXPECTED EXTENSION from this MIME
            if($file_imgsize && !empty($file_imgsize)) {
                $fileMimeInteger = $file_imgsize[2];
                $outputFileExtension = @image_type_to_extension($fileMimeInteger);
                $outputFileExtension = str_replace('.', '', $outputFileExtension);
            }
        }
    
        // Force Jpeg
        if(!$outputFileExtension) {
            $outputFileExtension = "jpeg";
        }
    
        // WRITE TO FILE
        return $img->output($outputFileExtension, array(
            'file' => $file,
            'quality' => $this->config['jpegQuality']
        ));
    

    Function:

    protected function imageResize($image, $file=null) {
    
            if (!($image instanceof image)) {
                $img = image::factory($this->imageDriver, $image);
                if ($img->initError) return false;
                $file = $image;
            } elseif ($file === null) {
                 return false;
            }
            else {
                $img = $image;
            }
    
            $orientation = 1;
            if (function_exists("exif_read_data")) {
                $orientation = @exif_read_data($file);
                $orientation = isset($orientation['Orientation']) ? $orientation['Orientation'] : 1;
            }
    
            // IMAGE WILL NOT BE RESIZED WHEN NO WATERMARK AND SIZE IS ACCEPTABLE
            if ((
                    !isset($this->config['watermark']['file']) ||
                    (!strlen(trim($this->config['watermark']['file'])))
                ) && (
                    (
                        !$this->config['maxImageWidth'] &&
                        !$this->config['maxImageHeight']
                    ) || (
                        ($img->width <= $this->config['maxImageWidth']) &&
                        ($img->height <= $this->config['maxImageHeight'])
                    )
                ) &&
                ($orientation == 1)
            )
                return true;
    
            // PROPORTIONAL RESIZE
            if ((!$this->config['maxImageWidth'] || !$this->config['maxImageHeight'])) {
    
                if ($this->config['maxImageWidth'] &&
                    ($this->config['maxImageWidth'] < $img->width)
                ) {
                    $width = $this->config['maxImageWidth'];
                    $height = $img->getPropHeight($width);
    
                } elseif (
                    $this->config['maxImageHeight'] &&
                    ($this->config['maxImageHeight'] < $img->height)
                ) {
                    $height = $this->config['maxImageHeight'];
                    $width = $img->getPropWidth($height);
                }
    
                if (isset($width) && isset($height) && !$img->resize($width, $height))
                    return false;
    
            // RESIZE TO FIT
            } elseif (
                $this->config['maxImageWidth'] && $this->config['maxImageHeight'] &&
                !$img->resizeFit($this->config['maxImageWidth'], $this->config['maxImageHeight'])
            )
                return false;
    
            // AUTO FLIP AND ROTATE FROM EXIF
            if ((($orientation == 2) && !$img->flipHorizontal()) ||
                (($orientation == 3) && !$img->rotate(180)) ||
                (($orientation == 4) && !$img->flipVertical()) ||
                (($orientation == 5) && (!$img->flipVertical() || !$img->rotate(90))) ||
                (($orientation == 6) && !$img->rotate(90)) ||
                (($orientation == 7) && (!$img->flipHorizontal() || !$img->rotate(90))) ||
                (($orientation == 8) && !$img->rotate(270))
            )
                return false;
            if (($orientation >= 2) && ($orientation <= 8) && ($this->imageDriver == "imagick"))
                try {
                    $img->image->setImageProperty('exif:Orientation', "1");
                } catch (\Exception $e) {}
    
            // WATERMARK
            if (isset($this->config['watermark']['file']) &&
                is_file($this->config['watermark']['file'])
            ) {
                $left = isset($this->config['watermark']['left'])
                    ? $this->config['watermark']['left'] : false;
                $top = isset($this->config['watermark']['top'])
                    ? $this->config['watermark']['top'] : false;
                $img->watermark($this->config['watermark']['file'], $left, $top);
            }
    
            // Check the EXTENSION OF THIS FILE
            if($file && is_string($file) && file_exists($file)) {
                $file_imgsize = @getimagesize($file);
                // Get the EXPECTED EXTENSION from this MIME
                if($file_imgsize && !empty($file_imgsize)) {
                    $fileMimeInteger = $file_imgsize[2];
                    $outputFileExtension = @image_type_to_extension($fileMimeInteger);
                    $outputFileExtension = str_replace('.', '', $outputFileExtension);
                }
            }
    
            // Force Jpeg
            if(!$outputFileExtension) {
                $outputFileExtension = "jpeg";
            }
    
            // WRITE TO FILE
            return $img->output($outputFileExtension, array(
                'file' => $file,
                'quality' => $this->config['jpegQuality']
            ));
    
        }
    

    Next, the file class_image_gd.php in path kcfinder/lib/class_image_gd.php look for the function output_png make changes to how the quality is worked out (this fixes a PHP related error, with "Compression must be between 0-9").

    protected function output_png(array $options=array()) {
        $file = isset($options['file']) ? $options['file'] : null;
        $quality = isset($options['quality']) ? $options['quality'] : null;
        $filters = isset($options['filters']) ? $options['filters'] : null;
    
        if (($file === null) && !headers_sent()) {
            header("Content-Type: image/png");
        }
    
        // Compression must be between 0-9 - 2/02/2016
        if($quality && is_numeric($quality)) {
            $quality = $quality < 100 ? round(($quality / 100) * 10) : null; 
        } else {
            $quality = null;
        }
    
        return imagepng($this->image, $file, $quality, $filters);
    }
    

    Result:

    enter image description here
    (source: iforce.co.nz)