Detecting file types with MagicDb

When handling file uploads, it is often important to determine the type of file being uploaded. While some files may be easily recognizable based on their contents, others may prove to be hard to identify.

MagicDb is a file database that consists of specifications for several file formats. This recipe shows us how to use this database, through CakePHP's MagicDb class, to properly identify files uploaded by our users.

The license for the MagicDb database file allows its use only on open source or freely available software. If you wish to identify files on commercial applications, you will have to find a different approach.

Getting ready

As we will be working on files uploaded by our users, we need to build a form to upload files. We will store these uploads in a table, so create this table with the following SQL statement:

CREATE TABLE `uploads`(
`id` INT UNSIGNED AUTO_INCREMENT NOT NULL,
`file` VARCHAR(255) NOT NULL,
`mime` VARCHAR(255) default NULL,
`description` TEXT default NULL,
PRIMARY KEY(`id`)
);

Create a file named uploads_controller.php and place it in your app/controllers folder, with the following contents:

class UploadsController extends AppController {
public function add() {
if (!empty($this->data)) {
$this->Upload->create();
if ($this->Upload->save($this->data)) {
$this->Session->setFlash('File succesfully uploaded'),
$this->redirect(array('action'=>'view', $this->Upload->id));
} else {
$this->Session->setFlash('Please correct the errors marked below'),
}
}
}
}

Create a folder named uploads in your app/views folder. Create the view for the add() method in a file named add.ctp and place it in your app/views/uploads folder, with the following contents:

<?php
echo $this->Form->create('Upload', array('type'=>'file'));
echo $this->Form->inputs(array(
'file' => array('type'=>'file')
));
echo $this->Form->end('Upload'),
?>

How to do it...

  1. Download the latest MagicDb database file from http://www.magicdb.org/magic.db and place it in your app/vendors folder. You should now have a file named magic.db in your app/vendors folder.
  2. Edit your app/controllers/uploads_controller.php file and add the following methods right below the add() method:
    public function view($id) {
    $upload = $this->Upload->find('first', array(
    'conditions' => array('Upload.id' => $id)
    ));
    if (empty($upload)) {
    $this->cakeError('error404'),
    }
    $this->set(compact('upload'));
    }
    public function download($id) {
    $upload = $this->Upload->find('first', array(
    'conditions' => array('Upload.id' => $id)
    ));
    if (empty($upload)) {
    $this->cakeError('error404'),
    }
    $path = TMP . $upload['Upload']['file'];
    header('Content-type: '.$upload['Upload']['mime']);
    readfile($path);
    $this->_stop();
    }
    
  3. Create the view for the view() method in a file named view.ctp and place it in your app/views/uploads folder with the following contents:
    <h2><?php echo $upload['Upload']['file']; ?></h2>
    <p>
    <strong>File</strong>: <?php echo $upload['Upload']['file']; ?><br />
    <strong>MIME Type</strong>: <?php echo $upload['Upload']['mime']; ?><br />
    <strong>Description</strong>: <?php echo $upload['Upload']['description']; ?>
    </p>
    <br />
    <p>
    <?php if (strpos($upload['Upload']['mime'], 'image/') === 0) { ?>
    <?php echo $this->Html->image(array('action'=>'download', $upload['Upload']['id']), array('height'=>200)); ?>
    <?php } else { ?>
    <?php echo $this->Html->link('Download', array('action'=>'download', $upload['Upload']['id'])); ?>
    <?php } ?>
    </p>
    
  4. Create the model in a file named upload.php and place it in your app/models folder with the following contents:
    <?php
    class Upload extends AppModel {
    protected $magicDb;
    protected function getMagicDb() {
    if (!isset($this->magicDb)) {
    App::import('Core', 'MagicDb'),
    $magicDb = new MagicDb();
    if (!$magicDb->read(APP . 'vendors' . DS . 'magic.db')) {
    return null;
    }
    $this->magicDb = $magicDb;
    }
    return $this->magicDb;
    }
    }
    ?>
    
  5. While still editing your app/models/upload.php file, add the following method to the Upload class:
    public function beforeValidate($options = array()) {
    $result = parent::beforeValidate($options);
    $data = $this->data[$this->alias];
    if (!empty($data['file'])) {
    if (
    empty($data['file']) ||
    !is_array($data['file']) ||
    empty($data['file']['tmp_name']) ||
    !is_uploaded_file($data['file']['tmp_name'])
    ) {
    $this->invalidate('file', 'No file uploaded'),
    return false;
    }
    $magicDb = $this->getMagicDb();
    if (!isset($magicDb)) {
    $this->invalidate('file', 'Can't get instance of MagicDb'),
    return false;
    }
    $path = TMP . $data['file']['name'];
    if (!move_uploaded_file($data['file']['tmp_name'], $path)) {
    $this->invalidate('file', 'Could not move uploaded file'),
    return false;
    }
    $data['file'] = basename($path);
    unset($data['mime']);
    $analysis = $magicDb->analyze($path);
    if (!empty($analysis)) {
    $analysis = $analysis[0];
    if (preg_match('/^[.+?;ext=[^;]+;mime=([^;]+);.*?](.*)$/i', $analysis[3], $match)) {
    $data['mime'] = $match[1];
    if (empty($data['description'])) {
    $data['description'] = $match[2];
    }
    }
    }
    if (empty($data['mime'])) {
    $this->invalidate('Can't recognize file '.$data['file']);
    return false;
    }
    $this->data[$this->alias] = $data;
    } else {
    $this->invalidate('file', 'This field is required'),
    return false;
    }
    return $result;
    }
    

If you now browse to http://localhost/uploads/add, you will see a form where you can select a file, and then click the button Upload. Doing so with a GIF image will produce a result similar to what shown in the following screenshot:

How to do it...

How it works...

The recipe starts by downloading the MagicDb file and placing it into the app/vendors directory. This file is a text file; containing blocks of identifier file signatures, and for each of these file signature definitions, their respective mime type, and description.

Next, we create the view() and download() controller actions. Both of them are very similar, except that the download() action uses the field mime to set the Content-type header, thus properly informing the client browser the type of data being sent.

The download() action simply sends the contents of the file by using PHP's readfile() function, then calling the _stop() method (available to all CakePHP classes that descend from Object) to stop execution. The view() action, on the other hand, requires a view, which prints out the Upload record information, showing an image if the file is indeed an image, or showing a link to download the file, in any other case.

The Upload model defines two methods: beforeValidate(), and getMagicDb(). The second method creates an instance of the MagicDb class provided by CakePHP, populating it with the contents from the magic.db file that was saved in the app/vendors directory.

The validation callback beforeValidate() starts by making sure that a proper file was uploaded. If so, it moves the uploaded file to the application's temporary directory, and then uses the analyze() method of the MagicDb class to obtain the file information.

This method will return an empty array if the file was not identified, or a set of file identifications that match the file. These file identifications are themselves arrays, containing information that is defined in the magic.db file. The fourth element out of this array contains the information we are looking for: a string that includes the file extension, the mime type, and the file type description.

We extract this information, and we set it so it is saved together with the filename. If the file was not identified, we invalidate the file field.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.66.128