Merge "worthwhile tests in testTitleObjectStringConversion"
[lhc/web/wiklou.git] / includes / media / PNGMetadataExtractor.php
index a8a576a..55f087a 100644 (file)
@@ -1,10 +1,26 @@
 <?php
 /**
  * PNG frame counter and metadata extractor.
+ *
  * Slightly derived from GIFMetadataExtractor.php
  * Deliberately not using MWExceptions to avoid external dependencies, encouraging
  * redistribution.
  *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
  * @file
  * @ingroup Media
  */
@@ -66,7 +82,7 @@ class PNGMetadataExtractor {
                        throw new Exception( __METHOD__ . ": File $filename does not exist" );
                }
 
-               $fh = fopen( $filename, 'r' );
+               $fh = fopen( $filename, 'rb' );
 
                if ( !$fh ) {
                        throw new Exception( __METHOD__ . ": Unable to open file $filename" );
@@ -81,20 +97,24 @@ class PNGMetadataExtractor {
                // Read chunks
                while ( !feof( $fh ) ) {
                        $buf = fread( $fh, 4 );
-                       if ( !$buf ) {
+                       if ( !$buf || strlen( $buf ) < 4 ) {
                                throw new Exception( __METHOD__ . ": Read error" );
                        }
                        $chunk_size = unpack( "N", $buf );
                        $chunk_size = $chunk_size[1];
 
+                       if ( $chunk_size < 0 ) {
+                               throw new Exception( __METHOD__ . ": Chunk size too big for unpack" );
+                       }
+
                        $chunk_type = fread( $fh, 4 );
-                       if ( !$chunk_type ) {
+                       if ( !$chunk_type || strlen( $chunk_type ) < 4 ) {
                                throw new Exception( __METHOD__ . ": Read error" );
                        }
 
                        if ( $chunk_type == "IHDR" ) {
                                $buf = self::read( $fh, $chunk_size );
-                               if ( !$buf ) {
+                               if ( !$buf || strlen( $buf ) < $chunk_size ) {
                                        throw new Exception( __METHOD__ . ": Read error" );
                                }
                                $bitDepth = ord( substr( $buf, 8, 1 ) );
@@ -104,7 +124,7 @@ class PNGMetadataExtractor {
                                        case 0:
                                                $colorType = 'greyscale';
                                                break;
-                                       case 2: 
+                                       case 2:
                                                $colorType = 'truecolour';
                                                break;
                                        case 3:
@@ -122,7 +142,7 @@ class PNGMetadataExtractor {
                                }
                        } elseif ( $chunk_type == "acTL" ) {
                                $buf = fread( $fh, $chunk_size );
-                               if( !$buf ) {
+                               if( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
                                        throw new Exception( __METHOD__ . ": Read error" );
                                }
 
@@ -131,10 +151,13 @@ class PNGMetadataExtractor {
                                $loopCount = $actl['plays'];
                        } elseif ( $chunk_type == "fcTL" ) {
                                $buf = self::read( $fh, $chunk_size );
-                               if ( !$buf ) {
+                               if ( !$buf || strlen( $buf ) < $chunk_size ) {
                                        throw new Exception( __METHOD__ . ": Read error" );
                                }
                                $buf = substr( $buf, 20 );
+                               if ( strlen( $buf ) < 4 ) {
+                                       throw new Exception( __METHOD__ . ": Read error" );
+                               }
 
                                $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
                                if ( $fctldur['delay_den'] == 0 ) {
@@ -203,6 +226,11 @@ class PNGMetadataExtractor {
                        } elseif ( $chunk_type == 'tEXt' ) {
                                $buf = self::read( $fh, $chunk_size );
 
+                               // In case there is no \x00 which will make explode fail.
+                               if ( strpos( $buf, "\x00" ) === false ) {
+                                       throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
+                               }
+
                                list( $keyword, $content ) = explode( "\x00", $buf, 2 );
                                if ( $keyword === '' || $content === '' ) {
                                        throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
@@ -231,6 +259,11 @@ class PNGMetadataExtractor {
                                if ( function_exists( 'gzuncompress' ) ) {
                                        $buf = self::read( $fh, $chunk_size );
 
+                                       // In case there is no \x00 which will make explode fail.
+                                       if ( strpos( $buf, "\x00" ) === false ) {
+                                               throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
+                                       }
+
                                        list( $keyword, $postKeyword ) = explode( "\x00", $buf, 2 );
                                        if ( $keyword === '' || $postKeyword === '' ) {
                                                throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
@@ -284,7 +317,7 @@ class PNGMetadataExtractor {
                                        throw new Exception( __METHOD__ . ": tIME wrong size" );
                                }
                                $buf = self::read( $fh, $chunk_size );
-                               if ( !$buf ) {
+                               if ( !$buf || strlen( $buf ) < $chunk_size ) {
                                        throw new Exception( __METHOD__ . ": Read error" );
                                }
 
@@ -307,20 +340,24 @@ class PNGMetadataExtractor {
                                }
 
                                $buf = self::read( $fh, $chunk_size );
-                               if ( !$buf ) {
+                               if ( !$buf || strlen( $buf ) < $chunk_size ) {
                                        throw new Exception( __METHOD__ . ": Read error" );
                                }
 
                                $dim = unpack( "Nwidth/Nheight/Cunit", $buf );
                                if ( $dim['unit'] == 1 ) {
-                                       // unit is meters
-                                       // (as opposed to 0 = undefined )
-                                       $text['XResolution'] = $dim['width']
-                                               . '/100';
-                                       $text['YResolution'] = $dim['height']
-                                               . '/100';
-                                       $text['ResolutionUnit'] = 3;
-                                       // 3 = dots per cm (from Exif).
+                                       // Need to check for negative because php
+                                       // doesn't deal with super-large unsigned 32-bit ints well
+                                       if ( $dim['width'] > 0 && $dim['height'] > 0 ) {
+                                               // unit is meters
+                                               // (as opposed to 0 = undefined )
+                                               $text['XResolution'] = $dim['width']
+                                                       . '/100';
+                                               $text['YResolution'] = $dim['height']
+                                                       . '/100';
+                                               $text['ResolutionUnit'] = 3;
+                                               // 3 = dots per cm (from Exif).
+                                       }
                                }
 
                        } elseif ( $chunk_type == "IEND" ) {
@@ -343,7 +380,7 @@ class PNGMetadataExtractor {
                                        continue;
                                }
 
-                               // fixme: currently timezones are ignored.
+                               // @todo FIXME: Currently timezones are ignored.
                                // possibly should be wfTimestamp's
                                // responsibility. (at least for numeric TZ)
                                $formatted = wfTimestamp( TS_EXIF, $value );