+
+ /* -------------------------------------------------------------- */
+
+ /**
+ * Split a file into a base name and all dot-delimited 'extensions'
+ * on the end. Some web server configurations will fall back to
+ * earlier pseudo-'extensions' to determine type and execute
+ * scripts, so the blacklist needs to check them all.
+ *
+ * @return array
+ */
+ function splitExtensions( $filename ) {
+ $bits = explode( '.', $filename );
+ $basename = array_shift( $bits );
+ return array( $basename, $bits );
+ }
+
+ /**
+ * Perform case-insensitive match against a list of file extensions.
+ * Returns true if the extension is in the list.
+ *
+ * @param string $ext
+ * @param array $list
+ * @return bool
+ */
+ function checkFileExtension( $ext, $list ) {
+ return in_array( strtolower( $ext ), $list );
+ }
+
+ /**
+ * Perform case-insensitive match against a list of file extensions.
+ * Returns true if any of the extensions are in the list.
+ *
+ * @param array $ext
+ * @param array $list
+ * @return bool
+ */
+ function checkFileExtensionList( $ext, $list ) {
+ foreach( $ext as $e ) {
+ if( in_array( strtolower( $e ), $list ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns false if the file is of a known type but can't be recognized,
+ * indicating a corrupt file.
+ * Returns true otherwise; unknown file types are not checked if given
+ * with an unrecognized extension.
+ *
+ * @param string $tmpfile Pathname to the temporary upload file
+ * @param string $extension The filename extension that the file is to be served with
+ * @return bool
+ */
+ function verify( $tmpfile, $extension ) {
+ if( $this->triggersIEbug( $tmpfile ) ||
+ $this->triggersSafariBug( $tmpfile ) ) {
+ return false;
+ }
+
+ $fname = 'SpecialUpload::verify';
+ $mergeExtensions = array(
+ 'jpg' => 'jpeg',
+ 'tif' => 'tiff' );
+ $extensionTypes = array(
+ # See http://www.php.net/getimagesize
+ 1 => 'gif',
+ 2 => 'jpeg',
+ 3 => 'png',
+ 4 => 'swf',
+ 5 => 'psd',
+ 6 => 'bmp',
+ 7 => 'tiff',
+ 8 => 'tiff',
+ 9 => 'jpc',
+ 10 => 'jp2',
+ 11 => 'jpx',
+ 12 => 'jb2',
+ 13 => 'swc',
+ 14 => 'iff',
+ 15 => 'wbmp',
+ 16 => 'xbm' );
+
+ $extension = strtolower( $extension );
+ if( isset( $mergeExtensions[$extension] ) ) {
+ $extension = $mergeExtensions[$extension];
+ }
+ wfDebug( "$fname: Testing file '$tmpfile' with given extension '$extension'\n" );
+
+ if( !in_array( $extension, $extensionTypes ) ) {
+ # Not a recognized image type. We don't know how to verify these.
+ # They're allowed by policy or they wouldn't get this far, so we'll
+ # let them slide for now.
+ wfDebug( "$fname: Unknown extension; passing.\n" );
+ return true;
+ }
+
+ wfSuppressWarnings();
+ $data = getimagesize( $tmpfile );
+ wfRestoreWarnings();
+ if( false === $data ) {
+ # Didn't recognize the image type.
+ # Either the image is corrupt or someone's slipping us some
+ # bogus data such as HTML+JavaScript trying to take advantage
+ # of an Internet Explorer security flaw.
+ wfDebug( "$fname: getimagesize() doesn't recognize the file; rejecting.\n" );
+ return false;
+ }
+
+ $imageType = $data[2];
+ if( !isset( $extensionTypes[$imageType] ) ) {
+ # Now we're kind of confused. Perhaps new image types added
+ # to PHP's support that we don't know about.
+ # We'll let these slide for now.
+ wfDebug( "$fname: getimagesize() knows the file, but we don't recognize the type; passing.\n" );
+ return true;
+ }
+
+ $ext = strtolower( $extension );
+ if( $extension != $extensionTypes[$imageType] ) {
+ # The given filename extension doesn't match the
+ # file type. Probably just a mistake, but it's a stupid
+ # one and we shouldn't let it pass. KILL THEM!
+ wfDebug( "$fname: file extension does not match recognized type; rejecting.\n" );
+ return false;
+ }
+
+ wfDebug( "$fname: all clear; passing.\n" );
+ return true;
+ }
+
+ /**
+ * Internet Explorer for Windows performs some really stupid file type
+ * autodetection which can cause it to interpret valid image files as HTML
+ * and potentially execute JavaScript, creating a cross-site scripting
+ * attack vectors.
+ *
+ * Returns true if IE is likely to mistake the given file for HTML.
+ *
+ * @param string $filename
+ * @return bool
+ */
+ function triggersIEbug( $filename ) {
+ $file = fopen( $filename, 'rb' );
+ $chunk = strtolower( fread( $file, 256 ) );
+ fclose( $file );
+
+ $tags = array(
+ '<body',
+ '<head',
+ '<html',
+ '<img',
+ '<pre',
+ '<script',
+ '<table',
+ '<title' );
+ foreach( $tags as $tag ) {
+ if( false !== strpos( $chunk, $tag ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Apple's Safari browser performs some unsafe file type autodetection
+ * which can cause legitimate files to be interpreted as HTML if the
+ * web server is not correctly configured to send the right content-type
+ * (or if you're really uploading plain text and octet streams!)
+ *
+ * Returns true if Safari would mistake the given file for HTML
+ * when served with a generic content-type.
+ *
+ * @param string $filename
+ * @return bool
+ */
+ function triggersSafariBug( $filename ) {
+ $file = fopen( $filename, 'rb' );
+ $chunk = strtolower( fread( $file, 1024 ) );
+ fclose( $file );
+
+ $tags = array(
+ '<html',
+ '<script',
+ '<title' );
+ foreach( $tags as $tag ) {
+ if( false !== strpos( $chunk, $tag ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+