File operations

19 February 2018 | 66 views | Tags: Documentation

File operations such as creating, moving or deleting content-associated files and directories are handled by the TfishFileHandler class. For security reasons the file handler methods do not give a free rein. Sensitive operations are generally restricted to the uploads directory (AKA "data_file" path) and the class won't let you work outside of that. appendFile() is the exception, it does not have a path restriction. Therefore it must be used carefully.

Directory restrictions are tested internally by the private _dataFilePath() method. This takes the relative file path supplied as a parameter in public methods, prepends the data_file path to it and then checks that the realpath() matches expectations. If a directory traversal is detected, or the path proves to lie outside the uploads directory, then the operation will be cancelled and an error thrown.

In other cases where specific file operations are required other methods exist in other classes (for example TfishCache::flushCache()), but again these are generally locked to specific directories. If you have need for additional file manipulation methods you will need to write them, but I encourage you to lock them down to the minimum scope required.

Uploading a file

Call uploadFile( string $filename, string $fieldname ) providing the name of a file that has been uploaded via a form to the $_FILES directory, and the content object property it should be associated with (either 'image' or 'media'), which is the subdirectory that it will be moved to. Returns the filename on success and false on failure.

/** This snippet is from insert() in TfishContentObject class. */

// Process image and media files before inserting the object, as related fields must be set.
$property_whitelist = $obj->getPropertyWhitelist();
        
if (array_key_exists('image', $property_whitelist) && !empty($_FILES['image']['name'])) {
    $filename = TfishFilter::trimString($_FILES['image']['name']);
    $clean_filename = TfishFileHandler::uploadFile($filename, 'image');
            
    if ($clean_filename) {
        $key_values['image'] = $clean_filename;
    }
}

// Media file.
if (array_key_exists('media', $property_whitelist) && !empty($_FILES['media']['name'])) {
    $filename = TfishFilter::trimString($_FILES['media']['name']);
    $clean_filename = TfishFileHandler::uploadFile($filename, 'media');
            
    if ($clean_filename) {
        $key_values['media'] = $clean_filename;
        $mimetype_whitelist = TfishFileHandler::getPermittedUploadMimetypes();
        $extension = pathinfo($clean_filename, PATHINFO_EXTENSION);
        $key_values['format'] = $mimetype_whitelist[$extension];
        $key_values['file_size'] = $_FILES['media']['size'];
    }
}

Appending to a file

Call appendFile( string $path, string $contents). Do not set the $path using untrusted data sources such as user input. For example:

$site_salt_constant = 'if (!defined("TFISH_SITE_SALT")) define("TFISH_SITE_SALT", "'
                . $site_salt . '");';
$result = TfishFileHandler::appendFile(TFISH_CONFIGURATION_PATH, $site_salt_constant);

Downloading a file

Media attachments (file downloads associated with content objects) are served by enclosure.php, which basically streams the file as a forced download. Call sendDownload(int $id, string $filename = ''), passing the ID of the content object the file is associated with and the filename, if you would like to rename it.

As the streaming is accomplished via headers you cannot output anything ahead of it, otherwise you will get a "headers already sent" error.

// Send a media file associated with a particular content object.
$clean_id = isset($_GET['id']) ? (int) $_GET['id'] : 0;

if ($clean_id) {
    TfishContentHandler::updateCounter($clean_id);
    TfishFileHandler::sendDownload($clean_id);
}

exit;

Deleting a file

Call deleteFile( string $path ) to remove an individual file in the uploads directory. The $path should be specified relative to the uploads directory.

// Delete a file from the uploads/image directory.
if ($filename) {
    return TfishFileHandler::deleteFile('image/' . $filename);
}

// Delete a file from the uploads/media directory.
if ($filename) {
    return TfishFileHandler::deleteFile('media/' . $filename);
}

Clearing a directory

Call clearDirectory( string $path ) to delete the contents of a specific directory within the uploads directory. The path should be specified relative to the uploads directory, and will not function outside of it. This method is not recursive, so you need to call it on each subdirectory you want to clear.

$clean_path = TfishFilter::trimString($path);

if (!empty($clean_path)) {
    $result = TfishFileHandler::_clearDirectory($clean_path);
    
    if (!$result) {
        trigger_error(TFISH_ERROR_FAILED_TO_DELETE_DIRECTORY, E_USER_NOTICE);
        return false;
    }
            
    return true;
}

Deleting a directory

Call deleteDirectory( string $path ) to remove a directory within the uploads directory. It will not operate outside of uploads. This method is recursive and will destroy subdirectories.

// Delete a subdirectory of /uploads.
$clean_path = TfishFilter::trimString($path);
        
if ($clean_path) {

    $result = TfishFileHandler::deleteDirectory($clean_path);
    
    if (!$result) {
        trigger_error(TFISH_ERROR_FAILED_TO_DELETE_DIRECTORY, E_USER_NOTICE);
        return false;
    }
            
    return true;
}

File type restrictions on uploads

The types of files that can be uploaded via the image and media properties are restricted (whitelisted) by allowedImageMimetypes() and getPermittedUploadMimetypes(), respectively, which also set the mimetype when streaming a media attachment. Currently the list covers common document formats, image, audio and video formats favoured by the web, plus archives.

You can easily add additional mimetypes by extending the whitelist below (and a similar list found in the Javascript validation of the data_entry.html and data_edit.html forms, see trust_path/libraries/tuskfish/form). For dangerous types (executables and code), it is best that they be packed in an archive, rather than be added to the whitelist.

Note that Tuskfish will not force you to upload a media file. You can publish audio or video content, for example, without including an audio or video media file - you may have a valid reason for doing that, such as pre-publishing a story that you will update with the file later.

Tuskfish will display a warning if you attach the wrong type of media file (eg. a video file to an audio object) or change the content type to something that is incompatible with an existing media file. The file will not be deleted, in case you made a mistake. However, it will not be displayed on the public side of your site - you will need to change the content type to match the media type, or vice versa.

public static function getPermittedUploadMimetypes()
{
    return array(
        "doc" => "application/msword", // Documents.
        "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "pdf" => "application/pdf",
        "ppt" => "application/vnd.ms-powerpoint",
        "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
        "odt" => "application/vnd.oasis.opendocument.text",
        "ods" => " application/vnd.oasis.opendocument.spreadsheet",
        "odp" => "application/vnd.oasis.opendocument.presentation",
        "xls" => "application/vnd.ms-excel",
        "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "gif" => "image/gif", // Images.
        "jpg" => "image/jpeg",
        "png" => "image/png",
        "mp3" => "audio/mpeg", // Audio.
        "oga" => "audio/ogg",
        "ogg" => "audio/ogg",
        "wav" => "audio/x-wav",
        "mp4" => "video/mp4", // Video.
        "ogv" => "video/ogg",
        "webm" => "video/webm",
        "zip" => "application/zip", // Archives.
        "gz" => "application/x-gzip",
        "tar" => "application/x-tar"
    );
}

Copyright, all rights reserved.

Related

Tuskfish CMS Developer Guide

This guide will give you an overview of the architecture of Tuskfish CMS, how to write code to perform common operations and how to extend the system to suit yourself. The guide accompanies the Tuskfish API documentation. Keep a copy handy as you read this guide. It is best to review links to the API where provided, as not every detail will be discussed in the text. This is the first version of the guide, so it is still a work in progress.