Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -3609,3 +3609,39 @@ |
3610 | 3610 | function wfRunHooks( $event, $args = array() ) { |
3611 | 3611 | return Hooks::run( $event, $args ); |
3612 | 3612 | } |
| 3613 | + |
| 3614 | +/** |
| 3615 | + * Wrapper around php's unpack. |
| 3616 | + * |
| 3617 | + * @param $format String: The format string (See php's docs) |
| 3618 | + * @param $data: A binary string of binary data |
| 3619 | + * @param $length integer or false: The minimun length of $data. This is to |
| 3620 | + * prevent reading beyond the end of $data. false to disable the check. |
| 3621 | + * |
| 3622 | + * Also be careful when using this function to read unsigned 32 bit integer |
| 3623 | + * because php might make it negative. |
| 3624 | + * |
| 3625 | + * @throws MWException if $data not long enough, or if unpack fails |
| 3626 | + * @return Associative array of the extracted data |
| 3627 | + */ |
| 3628 | +function wfUnpack( $format, $data, $length=false ) { |
| 3629 | + if ( $length !== false ) { |
| 3630 | + $realLen = strlen( $data ); |
| 3631 | + if ( $realLen < $length ) { |
| 3632 | + throw new MWException( "Tried to use wfUnpack on a " |
| 3633 | + . "string of length $realLen, but needed one " |
| 3634 | + . "of at least length $length." |
| 3635 | + ); |
| 3636 | + } |
| 3637 | + } |
| 3638 | + |
| 3639 | + wfSuppressWarnings(); |
| 3640 | + $result = unpack( $format, $data ); |
| 3641 | + wfRestoreWarnings(); |
| 3642 | + |
| 3643 | + if ( $result === false ) { |
| 3644 | + // If it cannot extract the packed data. |
| 3645 | + throw new MWException( "unpack could not unpack binary data" ); |
| 3646 | + } |
| 3647 | + return $result; |
| 3648 | +} |
Index: trunk/phase3/includes/media/GIFMetadataExtractor.php |
— | — | @@ -50,7 +50,7 @@ |
51 | 51 | throw new Exception( "File $filename does not exist" ); |
52 | 52 | } |
53 | 53 | |
54 | | - $fh = fopen( $filename, 'r' ); |
| 54 | + $fh = fopen( $filename, 'rb' ); |
55 | 55 | |
56 | 56 | if ( !$fh ) { |
57 | 57 | throw new Exception( "Unable to open file $filename" ); |
— | — | @@ -95,6 +95,7 @@ |
96 | 96 | self::skipBlock( $fh ); |
97 | 97 | } elseif ( $buf == self::$gif_extension_sep ) { |
98 | 98 | $buf = fread( $fh, 1 ); |
| 99 | + if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" ); |
99 | 100 | $extension_code = unpack( 'C', $buf ); |
100 | 101 | $extension_code = $extension_code[1]; |
101 | 102 | |
— | — | @@ -105,6 +106,7 @@ |
106 | 107 | fread( $fh, 1 ); // Transparency, disposal method, user input |
107 | 108 | |
108 | 109 | $buf = fread( $fh, 2 ); // Delay, in hundredths of seconds. |
| 110 | + if ( strlen( $buf ) < 2 ) throw new Exception( "Ran out of input" ); |
109 | 111 | $delay = unpack( 'v', $buf ); |
110 | 112 | $delay = $delay[1]; |
111 | 113 | $duration += $delay * 0.01; |
— | — | @@ -112,6 +114,7 @@ |
113 | 115 | fread( $fh, 1 ); // Transparent colour index |
114 | 116 | |
115 | 117 | $term = fread( $fh, 1 ); // Should be a terminator |
| 118 | + if ( strlen( $term ) < 1 ) throw new Exception( "Ran out of input" ); |
116 | 119 | $term = unpack( 'C', $term ); |
117 | 120 | $term = $term[1]; |
118 | 121 | if ($term != 0 ) { |
— | — | @@ -150,6 +153,7 @@ |
151 | 154 | // Application extension (Netscape info about the animated gif) |
152 | 155 | // or XMP (or theoretically any other type of extension block) |
153 | 156 | $blockLength = fread( $fh, 1 ); |
| 157 | + if ( strlen( $blockLength ) < 1 ) throw new Exception( "Ran out of input" ); |
154 | 158 | $blockLength = unpack( 'C', $blockLength ); |
155 | 159 | $blockLength = $blockLength[1]; |
156 | 160 | $data = fread( $fh, $blockLength ); |
— | — | @@ -172,6 +176,7 @@ |
173 | 177 | |
174 | 178 | // Unsigned little-endian integer, loop count or zero for "forever" |
175 | 179 | $loopData = fread( $fh, 2 ); |
| 180 | + if ( strlen( $loopData ) < 2 ) throw new Exception( "Ran out of input" ); |
176 | 181 | $loopData = unpack( 'v', $loopData ); |
177 | 182 | $loopCount = $loopData[1]; |
178 | 183 | |
— | — | @@ -209,6 +214,7 @@ |
210 | 215 | } elseif ( $buf == self::$gif_term ) { |
211 | 216 | break; |
212 | 217 | } else { |
| 218 | + if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" ); |
213 | 219 | $byte = unpack( 'C', $buf ); |
214 | 220 | $byte = $byte[1]; |
215 | 221 | throw new Exception( "At position: ".ftell($fh). ", Unknown byte ".$byte ); |
— | — | @@ -242,6 +248,7 @@ |
243 | 249 | * @return int |
244 | 250 | */ |
245 | 251 | static function decodeBPP( $data ) { |
| 252 | + if ( strlen( $data ) < 1 ) throw new Exception( "Ran out of input" ); |
246 | 253 | $buf = unpack( 'C', $data ); |
247 | 254 | $buf = $buf[1]; |
248 | 255 | $bpp = ( $buf & 7 ) + 1; |
— | — | @@ -259,6 +266,7 @@ |
260 | 267 | static function skipBlock( $fh ) { |
261 | 268 | while ( !feof( $fh ) ) { |
262 | 269 | $buf = fread( $fh, 1 ); |
| 270 | + if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" ); |
263 | 271 | $block_len = unpack( 'C', $buf ); |
264 | 272 | $block_len = $block_len[1]; |
265 | 273 | if ($block_len == 0) { |
Index: trunk/phase3/includes/media/BMP.php |
— | — | @@ -42,7 +42,7 @@ |
43 | 43 | * @return array |
44 | 44 | */ |
45 | 45 | function getImageSize( $image, $filename ) { |
46 | | - $f = fopen( $filename, 'r' ); |
| 46 | + $f = fopen( $filename, 'rb' ); |
47 | 47 | if( !$f ) { |
48 | 48 | return false; |
49 | 49 | } |
— | — | @@ -54,8 +54,12 @@ |
55 | 55 | $h = substr( $header, 22, 4); |
56 | 56 | |
57 | 57 | // Convert the unsigned long 32 bits (little endian): |
58 | | - $w = unpack( 'V' , $w ); |
59 | | - $h = unpack( 'V' , $h ); |
| 58 | + try { |
| 59 | + $w = wfUnpack( 'V', $w, 4 ); |
| 60 | + $h = wfUnpack( 'V', $h, 4 ); |
| 61 | + } catch ( MWException $e ) { |
| 62 | + return false; |
| 63 | + } |
60 | 64 | return array( $w[1], $h[1] ); |
61 | 65 | } |
62 | 66 | } |
Index: trunk/phase3/includes/media/PNGMetadataExtractor.php |
— | — | @@ -66,7 +66,7 @@ |
67 | 67 | throw new Exception( __METHOD__ . ": File $filename does not exist" ); |
68 | 68 | } |
69 | 69 | |
70 | | - $fh = fopen( $filename, 'r' ); |
| 70 | + $fh = fopen( $filename, 'rb' ); |
71 | 71 | |
72 | 72 | if ( !$fh ) { |
73 | 73 | throw new Exception( __METHOD__ . ": Unable to open file $filename" ); |
— | — | @@ -81,20 +81,24 @@ |
82 | 82 | // Read chunks |
83 | 83 | while ( !feof( $fh ) ) { |
84 | 84 | $buf = fread( $fh, 4 ); |
85 | | - if ( !$buf ) { |
| 85 | + if ( !$buf || strlen( $buf ) < 4 ) { |
86 | 86 | throw new Exception( __METHOD__ . ": Read error" ); |
87 | 87 | } |
88 | 88 | $chunk_size = unpack( "N", $buf ); |
89 | 89 | $chunk_size = $chunk_size[1]; |
90 | 90 | |
| 91 | + if ( $chunk_size < 0 ) { |
| 92 | + throw new Exception( __METHOD__ . ": Chunk size too big for unpack" ); |
| 93 | + } |
| 94 | + |
91 | 95 | $chunk_type = fread( $fh, 4 ); |
92 | | - if ( !$chunk_type ) { |
| 96 | + if ( !$chunk_type || strlen( $chunk_type ) < 4 ) { |
93 | 97 | throw new Exception( __METHOD__ . ": Read error" ); |
94 | 98 | } |
95 | 99 | |
96 | 100 | if ( $chunk_type == "IHDR" ) { |
97 | 101 | $buf = self::read( $fh, $chunk_size ); |
98 | | - if ( !$buf ) { |
| 102 | + if ( !$buf || strlen( $buf ) < $chunk_size ) { |
99 | 103 | throw new Exception( __METHOD__ . ": Read error" ); |
100 | 104 | } |
101 | 105 | $bitDepth = ord( substr( $buf, 8, 1 ) ); |
— | — | @@ -122,7 +126,7 @@ |
123 | 127 | } |
124 | 128 | } elseif ( $chunk_type == "acTL" ) { |
125 | 129 | $buf = fread( $fh, $chunk_size ); |
126 | | - if( !$buf ) { |
| 130 | + if( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) { |
127 | 131 | throw new Exception( __METHOD__ . ": Read error" ); |
128 | 132 | } |
129 | 133 | |
— | — | @@ -131,10 +135,13 @@ |
132 | 136 | $loopCount = $actl['plays']; |
133 | 137 | } elseif ( $chunk_type == "fcTL" ) { |
134 | 138 | $buf = self::read( $fh, $chunk_size ); |
135 | | - if ( !$buf ) { |
| 139 | + if ( !$buf || strlen( $buf ) < $chunk_size ) { |
136 | 140 | throw new Exception( __METHOD__ . ": Read error" ); |
137 | 141 | } |
138 | 142 | $buf = substr( $buf, 20 ); |
| 143 | + if ( strlen( $buf ) < 4 ) { |
| 144 | + throw new Exception( __METHOD__ . ": Read error" ); |
| 145 | + } |
139 | 146 | |
140 | 147 | $fctldur = unpack( "ndelay_num/ndelay_den", $buf ); |
141 | 148 | if ( $fctldur['delay_den'] == 0 ) { |
— | — | @@ -294,7 +301,7 @@ |
295 | 302 | throw new Exception( __METHOD__ . ": tIME wrong size" ); |
296 | 303 | } |
297 | 304 | $buf = self::read( $fh, $chunk_size ); |
298 | | - if ( !$buf ) { |
| 305 | + if ( !$buf || strlen( $buf ) < $chunk_size ) { |
299 | 306 | throw new Exception( __METHOD__ . ": Read error" ); |
300 | 307 | } |
301 | 308 | |
— | — | @@ -317,20 +324,24 @@ |
318 | 325 | } |
319 | 326 | |
320 | 327 | $buf = self::read( $fh, $chunk_size ); |
321 | | - if ( !$buf ) { |
| 328 | + if ( !$buf || strlen( $buf ) < $chunk_size ) { |
322 | 329 | throw new Exception( __METHOD__ . ": Read error" ); |
323 | 330 | } |
324 | 331 | |
325 | 332 | $dim = unpack( "Nwidth/Nheight/Cunit", $buf ); |
326 | 333 | if ( $dim['unit'] == 1 ) { |
327 | | - // unit is meters |
328 | | - // (as opposed to 0 = undefined ) |
329 | | - $text['XResolution'] = $dim['width'] |
330 | | - . '/100'; |
331 | | - $text['YResolution'] = $dim['height'] |
332 | | - . '/100'; |
333 | | - $text['ResolutionUnit'] = 3; |
334 | | - // 3 = dots per cm (from Exif). |
| 334 | + // Need to check for negative because php |
| 335 | + // doesn't deal with super-large unsigned 32-bit ints well |
| 336 | + if ( $dim['width'] > 0 && $dim['height'] > 0 ) { |
| 337 | + // unit is meters |
| 338 | + // (as opposed to 0 = undefined ) |
| 339 | + $text['XResolution'] = $dim['width'] |
| 340 | + . '/100'; |
| 341 | + $text['YResolution'] = $dim['height'] |
| 342 | + . '/100'; |
| 343 | + $text['ResolutionUnit'] = 3; |
| 344 | + // 3 = dots per cm (from Exif). |
| 345 | + } |
335 | 346 | } |
336 | 347 | |
337 | 348 | } elseif ( $chunk_type == "IEND" ) { |
Index: trunk/phase3/includes/media/JpegMetadataExtractor.php |
— | — | @@ -128,7 +128,7 @@ |
129 | 129 | continue; |
130 | 130 | } else { |
131 | 131 | // segment we don't care about, so skip |
132 | | - $size = unpack( "nint", fread( $fh, 2 ) ); |
| 132 | + $size = wfUnpack( "nint", fread( $fh, 2 ), 2 ); |
133 | 133 | if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" ); |
134 | 134 | fseek( $fh, $size['int'] - 2, SEEK_CUR ); |
135 | 135 | } |
— | — | @@ -144,9 +144,11 @@ |
145 | 145 | * @return data content of segment. |
146 | 146 | */ |
147 | 147 | private static function jpegExtractMarker( &$fh ) { |
148 | | - $size = unpack( "nint", fread( $fh, 2 ) ); |
| 148 | + $size = wfUnpack( "nint", fread( $fh, 2 ), 2 ); |
149 | 149 | if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" ); |
150 | | - return fread( $fh, $size['int'] - 2 ); |
| 150 | + $segment = fread( $fh, $size['int'] - 2 ); |
| 151 | + if ( strlen( $segment ) !== $size['int'] - 2 ) throw new MWException( "Segment shorter than expected" ); |
| 152 | + return $segment; |
151 | 153 | } |
152 | 154 | |
153 | 155 | /** |
— | — | @@ -205,7 +207,12 @@ |
206 | 208 | $offset += $lenName; |
207 | 209 | |
208 | 210 | // now length of data (unsigned long big endian) |
209 | | - $lenData = unpack( 'Nlen', substr( $app13, $offset, 4 ) ); |
| 211 | + $lenData = wfUnpack( 'Nlen', substr( $app13, $offset, 4 ), 4 ); |
| 212 | + // PHP can take issue with very large unsigned ints and make them negative. |
| 213 | + // Which should never ever happen, as this has to be inside a segment |
| 214 | + // which is limited to a 16 bit number. |
| 215 | + if ( $lenData['len'] < 0 ) throw new MWException( "Too big PSIR (" . $lenData['len'] . ')' ); |
| 216 | + |
210 | 217 | $offset += 4; // 4bytes length field; |
211 | 218 | |
212 | 219 | // this should not happen, but check. |