Yesterday, I found myself in the unenviable position of being unable to get the MIME type of a video file uploaded to my production server. The fileinfo functions on that server will only work with the magic.mime file at /usr/share/file/magic for some reason. The sysadmin claimed he had added the requested lines to this file to detect FLV, WMV, ASF, etc., but a quick perusal of the file using UltraEdit's find function showed that this wasn't the case. What's worse, he changed mime_magic.magicfile to an empty file at /etc/magic, so I can't use mime_content_type now either! Since my application is targeted at non-technical users, it would be unthinkable to expect them to jump through the same hoops I've had to jump through (in vain) in order to get the package working. What to do?

I decided I needed a reliable platform-independent solution to the problem. Enter the MimeValidator class. What this class does is take hints from the browser that uploaded a file - $_FILES['userfile']['type'] and the extension in $_FILES['userfile']['name'] - and confirm whether the file contents match the type that the browser says the file is.

The constructor simply builds the magic SQLite database if it doesn't exist. Since I am only concerned with a handful of video file types that I want to allow here, I elected not to place magic numbers for every possible file type in the table. (Yes, magic.mime could conceivably waste MIPS by checking your file against such irrelevant types as Doom and Adventure game files!)

The validateX() functions look up the MIME type or extension passed by the browser in the mime_type table. If it isn't in the table, then it isn't an allowed type, so we just return false. If it is in the table, then these functions read the appropriate bytes from the file and compare them to the magic numbers in the table. Simple!

Here's the code:

PHP Code:
  class MimeValidator {

   private 
$database;

   
// __construct //////////////////////////////////////////////////////////////

   
public function __construct() {
    try {
     
$this->database = new PDO('sqlite:' INSTALL_PATH 'data/mime_types.db');
     
$this->database->setAttribute(PDO::ATTR_PERSISTENT,true);
     
$this->database->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
     
$query $this->database->query("SELECT COUNT(name) FROM sqlite_master WHERE type='table'");
     
$result $query->fetch(PDO::FETCH_ASSOC);
     
$numTables $result['COUNT(name)'];
    }
    catch (
PDOException $e) {
     die(
'PDOException in MimeValidator::__construct: ' $e->getMessage());
    }
    if (
$numTables == 0) {
     try {
      
$this->database->query("CREATE TABLE `mime_type` (`id` INTEGER PRIMARY KEY AUTOINCREMENT,
                                                        `extension` TEXT NOT NULL DEFAULT '',
                                                        `ianna_type` TEXT NOT NULL DEFAULT '',
                                                        `search_pos` INTEGER NOT NULL DEFAULT 0,
                                                        `search_str` TEXT NOT NULL DEFAULT '')"
                            
);
     }
     catch (
PDOException $e) {
      die(
'PDOException in MimeValidator::__construct: ' $e->getMessage());
     }
    }
    try {
     
$query $this->database->query("SELECT COUNT(*) FROM mime_type");
     
$result $query->fetch(PDO::FETCH_ASSOC);
     
$numItems $result['COUNT(*)'];
    }
    catch (
PDOException $e) {
     die(
'PDOException in MimeValidator::__construct: ' $e->getMessage());
    }
    if (
$numItems == 0) {
     try {
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('flv','video/x-flv',0,'FLV')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('asf','video/x-ms-asf',0,'0&uf')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('wmv','video/x-ms-wmv',0,'0&uf')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('avi','video/x-msvideo',8,'AVILIST')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('rm','application/vnd.rn-realmedia',0,'.RMF')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('rv','video/vnd.rn-realvideo',0,'.RMF')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('mov','video/quicktime',4,'moov')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('swf','application/x-shockwave-flash',0,'FWS')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('swf','application/x-shockwave-flash',0,'CWS')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('mpeg','video/mpeg',0,'  ')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('mpeg','video/mpeg',0,'  ')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('mpg','video/mpeg',0,'  ')");
      
$this->database->query("INSERT INTO mime_type (extension,ianna_type,search_pos,search_str) VALUES ('mpg','video/mpeg',0,'  ')");
     }
     catch (
PDOException $e) {
      die(
'PDOException in MimeValidator::__construct: ' $e->getMessage());
     }
    }
   } 
// end function __construct

   // validateExtension ////////////////////////////////////////////////////////

   
public function validateExtension($filename,$ext "") {
       if (empty(
$ext)) $ext end(explode(".",end(explode("/",$filename))));
       try {
        
$query $this->database->prepare("SELECT COUNT(*) FROM mime_type WHERE extension=?");
        
$query->bindParam(1,strtolower($ext));
        
$query->execute();
        
$result $query->fetch(PDO::FETCH_ASSOC);
        
$count $result['COUNT(*)'];
       }
       catch(
PDOException $e) {
        die(
'PDOException in MimeValidator::validateExtension: ' $e->getMessage());
       }
       if (
$count == 0) return false;
       try {
        
$query $this->database->prepare("SELECT * FROM mime_type WHERE extension=?");
        
$query->bindParam(1,strtolower($ext));
        
$query->execute();
        
$result $query->fetchAll(PDO::FETCH_ASSOC);
       }
       catch(
PDOException $e) {
        die(
'PDOException in MimeValidator::validateExtension: ' $e->getMessage());
       }
       if ((
$fp = @fopen($filename,"r")) === false) return false;
       foreach (
$result as $row) {
        
fseek($fp,$row['search_pos'],SEEK_SET);
        
$haystack "";
        while (!
feof($fp) && (strlen($haystack) < strlen($row['search_str']))) $haystack .= fgetc($fp);
        
fclose($fp);
        if (
$haystack == $row['search_str']) return true;
    }
    return 
false;
   } 
// end function validateExtension

   // validateType ////////////////////////////////////////////////////////////

   
public function validateType($filename,$mimeType) {
    try {
        
$query $this->database->prepare("SELECT COUNT(*) FROM mime_type WHERE ianna_type=?");
        
$query->bindParam(1,strtolower($mimeType));
        
$query->execute();
        
$result $query->fetch(PDO::FETCH_ASSOC);
        
$count $result['COUNT(*)'];
       }
       catch(
PDOException $e) {
        die(
'PDOException in MimeValidator::validateType: ' $e->getMessage());
       }
       if (
$count == 0) return false;
       try {
        
$query $this->database->prepare("SELECT * FROM mime_type WHERE ianna_type=?");
        
$query->bindParam(1,strtolower($mimeType));
        
$query->execute();
        
$result $query->fetchAll(PDO::FETCH_ASSOC);
       }
       catch(
PDOException $e) {
        die(
'PDOException in MimeValidator::validateType: ' $e->getMessage());
       }
       if ((
$fp = @fopen($filename,"r")) === false) return false;
       foreach (
$result as $row) {
        
fseek($fp,$row['search_pos'],SEEK_SET);
        
$haystack "";
        while (!
feof($fp) && (strlen($haystack) < strlen($row['search_str']))) $haystack .= fgetc($fp);
        
fclose($fp);
        if (
$haystack == $row['search_str']) return true;
    }
    return 
false;
   } 
// end function validateType

  
// end class MimeValidator