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.
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'), ?>
app/vendors
folder. You should now have a file named magic.db
in your app/vendors
folder. 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(); }
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>
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; } } ?>
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:
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.
3.147.66.128