Check if the file is a PNG image file by reading its signature

Here is a quick function to check if a file is a PNG file by reading the file's signature. As this page on Wikipedia states, "A PNG file starts with an 8-byte signature. The hexadecimal byte values are 89 50 4E 47 0D 0A 1A 0A; the decimal values are 137 80 78 71 13 10 26 10".

Knowing that we can compare the first 8 bytes of the file itself to this list. And here is how:

  1. <?php
  2.  
  3. /**
  4.  * Check if a file is a PNG file. Does not depend on the file's extension
  5.  *
  6.  * @param string $filename Full file path
  7.  * @return boolean|null
  8.  */
  9. function isPngFile($filename)
  10. {
  11.     // check if the file exists
  12.     if (!file_exists($filename)) {
  13.         return null;
  14.     }
  15.    
  16.     // define the array of first 8 png bytes
  17.     $png_header = array(137, 80, 78, 71, 13, 10, 26, 10);
  18.  
  19.     // open file for reading
  20.     $f = fopen($filename, 'r');
  21.  
  22.     // loop through first 8 bytes of the file
  23.     for ($i = 0; $i < 8; $i++) {
  24.         // convert current character to its ascii value
  25.         $byte = ord(fread($f, 1));
  26.  
  27.         // return false if it doesn't match png's header
  28.         if ($byte !== $png_header[$i]) {
  29.             fclose($f);
  30.             return false;
  31.         }
  32.     }
  33.     fclose($f);
  34.  
  35.     return true;
  36. }

As you can see all this function requires is a path of the file you want to check.

First the function checks if the file exists at all. If not, it returns null.

Then we define what the first 8 bytes of the file should be ($png_header), open the file for reading and loop through those first 8 bytes. If we encounter one byte that is different we close the file and return false.

If all bytes are equal, we close the file as well but return true.

However, accessing the file 8 times in a row just to read 8 bytes is not the most optimal solution. Here is a revised version of the function:

  1. <?php
  2.  
  3. function isPngFile($filename)
  4. {
  5.     // check if the file exists
  6.     if (!file_exists($filename)) {
  7.         return null;
  8.     }
  9.  
  10.     // define the array of first 8 png bytes
  11.     $png_header = array(137, 80, 78, 71, 13, 10, 26, 10);
  12.     // or: array(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A);
  13.  
  14.     // open file for reading
  15.     $f = fopen($filename, 'r');
  16.  
  17.     // read first 8 bytes from the file and close the resource
  18.     $header = fread($f, 8);
  19.     fclose($f);
  20.  
  21.     // convert the string to an array
  22.     $chars = preg_split('//', $header, -1, PREG_SPLIT_NO_EMPTY);
  23.  
  24.     // convert each charater to its ascii value
  25.     $chars = array_map('ord', $chars);
  26.  
  27.     // return true if there are no differences or false otherwise
  28.     return (count(array_diff($png_header, $chars)) === 0);
  29. }

It gets you exactly the same result as the first one but in a bit more "correct" way.

Comments
1
If you want to check other file formats,
here is a list of various file signatures, you could use for the 2nd function:

File Signatures
Sebastian Lasse, August 21st 2011, 12:12
2
Just look for the length of the signature (not always 8) ...
e.g.

$header = fread($f, count(myCustomFormat_header) );
Sebastian Lasse, August 21st 2011, 12:22
3
Awesome tips, Sebastian! Especially the list of file headers. Will definitely be useful in the future.
Andris, August 22nd 2011, 22:23
4
Very nice!

Here's the C# version of this for an uploaded file:


         private bool isPngFile(HttpPostedFileBase file)
        {
            int[] pngHeader = new int[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

            for (int i = 0; i < 8; i++) {
                int eachByte = file.InputStream.ReadByte();
 
                if (eachByte != pngHeader[i]) return false;
            }
        
            return true;
        }
Internet Marketing, June 1st 2013, 4:57
5
Nice piece of bloatware, how about something a little more efficient like:


function isPhpFile( $path ) {
  if ($f = fopen ($path, 'rb')) {
    $header = fread ($f, 8);
    fclose ($f);
    return strncmp ($header, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8)==0 && strlen ($header)==8;
  }
  // If no 'return' statement, function returns NULL
}
Kent, August 9th 2013, 21:22
6
Most of my snippets are "blown up" for clarity. This website was initially intended as a help for new developers and eventually I just started to add stuff that I couldn't find elsewhere. So I started posting stuff I couldn't find elsewhere explained in the way that newbies could understand too.

Otherwise, of course, every method can be reduced to a few lines.
Andris, August 12th 2013, 20:38
Name
Email (required)
will not be published
Website
Recaptcha
you will only be required to fill it in once in this session

You can use [code][/code] tags in your comments