views:

619

answers:

3

We need to create a file management interface that integrates with the clients current website built on cakephp.

The file manager must only let the users see the files that they have permissions too.

The user will be able to upload files(of any size) and have other users download those files (if permissions allow).

This is no problem for many file management systems that we can purchase but none that I can find will integrate with their current log in system. The client only wants the users to log in once to access their user CP and their files. So as far as I can see our only option is to build a file management interface ourselves.

What are some of the options that we have to allow uploads using PHP? I understand that we need to increase the php upload limit, but is there a ceiling that php/apache will allow?

The files will likely cap out at approximately 150MB if that is relevant, however there may be situations where the file sizes will be larger.

Also what are the do's and don'ts of file upload/control on a Linux server?

I suppose I have no real 'specific' questions but would like some advise on where to start and some of the typical pitfalls we will run into.

Thanks

+4  A: 

Hi,

File management is quite easy, actually. Here are some suggestions that may point you in the right direction.

First of all, if this is a load balanced web server situation, you'll need to step up the complexity a little in order to put the files in one common place. If that's the case, ping me and I'll be happy to send you our super-light file server/client we use for that same situation.

There are a few variables you will want to affect in order to allow larger uploads. I recommend using apache directives to limit these changes to a particular file:

<Directory /home/deploy/project/uploader>
    php_value max_upload_size "200M"
    php_value post_max_size "200M"
    php_value max_input_time "1800"

    # this one depends on how much processing you are doing to the file
    php_value memory_limit "32M" 
</Directory>

Architecture:

Create a database table that stores some information about each file.

CREATE TABLE `File` (
  `File_MNID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `Owner_Field` enum('User.User_ID', 'Resource.Resource_ID') NOT NULL,
  `Owner_Key` int(10) unsigned NOT NULL,
  `ContentType` varchar(64) NOT NULL,
  `Size` int(10) NOT NULL,
  `Hash` varchar(40) NOT NULL,
  `Name` varchar(128) NOT NULL,
  PRIMARY KEY (`File_MNID`),
  KEY `Owner` (`Owner_Field`,`Owner_Key`)
) ENGINE=InnoDB

What is Owner_Field and Owner_Key? A simple way to say what "entity" owns the file. In this specific case there were multiple types of files being uploaded. In your case, a simple User_ID field may be adequate.

The purpose of storing the owner is so that you can restrict who can download and delete the file. That will be crucial for protecting the downloads.

Here is a sample class that can be used to accept the file uploads from the browser. You'll need to modify it to suit, of course.

There are a few things to note about the following code. Since this is used with an Application Server and a File Server, there are a few things to "replace".

  1. Any occurrences of App::CallAPI(...) will need to be replaced with a query or set of queries that do the "same thing".
  2. Any occurrences of App::$FS->... will need to be replaced with the correct file handling functions in PHP such as move_uploaded_file, readfile, etc...

Here it is. Keep in mind that there are functions here which allow you to see files owned by a given user, delete files, and so on and so forth. More explanation at the bottom...

<?php

class FileClient
{
    public static $DENY     = '/\.ade$|\.adp$|\.asp$|\.bas$|\.bat$|\.chm$|\.cmd$|\.com$|\.cpl$|\.crt$|\.exe$|\.hlp$|\.hta$|\.inf$|\.ins$|\.isp$|\.its$| \.js$|\.jse$|\.lnk$|\.mda$|\.mdb$|\.mde$|\.mdt,\. mdw$|\.mdz$|\.msc$|\.msi$|\.msp$|\.mst$|\.pcd$|\.pif$|\.reg$|\.scr$|\.sct$|\.shs$|\.tmp$|\.url$|\.vb$|\.vbe$|\.vbs$|vsmacros$|\.vss$|\.vst$|\.vsw$|\.ws$|\.wsc$|\.wsf$|\.wsh$/i';

    public static $MAX_SIZE = 5000000;

    public static function SelectList($Owner_Field, $Owner_Key)
    {
        $tmp = App::CallAPI
        (
            'File.List',
            array
            (
                'Owner_Field' => $Owner_Field,
                'Owner_Key' => $Owner_Key,
            )
        );

        return $tmp['Result'];
    }

    public static function HandleUpload($Owner_Field, $Owner_Key, $FieldName)
    {
        $aError = array();

        if(! isset($_FILES[$FieldName]))
            return false;
        elseif(! is_array($_FILES[$FieldName]))
            return false;
        elseif(! $_FILES[$FieldName]['tmp_name'])
            return false;
        elseif($_FILES[$FieldName]['error'])
            return array('An unknown upload error has occured.');

        $sPath = $_FILES[$FieldName]['tmp_name'];
        $sHash = sha1_file($sPath);
        $sType = $_FILES[$FieldName]['type'];
        $nSize = (int) $_FILES[$FieldName]['size'];
        $sName = $_FILES[$FieldName]['name'];

        if(preg_match(self::$DENY, $sName))
        {
            $aError[] = "File type not allowed for security reasons.  If this file must be attached, please add it to a .zip file first...";
        }

        if($nSize > self::$MAX_SIZE)
        {
            $aError[] = 'File too large at $nSize bytes.';
        }

        // Any errors? Bail out.
        if($aError)
        {
            return $aError;
        }


        $File = App::CallAPI
        (
            'File.Insert',
            array
            (
                'Owner_Field'        => $Owner_Field,
                'Owner_Key'            => $Owner_Key,
                'ContentType'        => $sType,
                'Size'                => $nSize,
                'Hash'                => $sHash,
                'Name'                => $sName,
            )
        );

        App::InitFS();
        App::$FS->PutFile("File_" . $File['File_MNID'], $sPath);

        return $File['File_MNID'];

    }

    public static function Serve($Owner_Field, $Owner_Key, $File_MNID)
    {
        //Also returns the name, content-type, and ledger_MNID
        $File    = App::CallAPI
        (
            'File.Select',
            array
            (
                'Owner_Field'     => $Owner_Field,
                'Owner_Key'     => $Owner_Key,
                'File_MNID'     => $File_MNID
            )
        );

        $Name     = 'File_' . $File['File_MNID'] ;

        //Content Header for that given file
        header('Content-disposition: attachment; filename="' . $File['Name'] . '"');
        header("Content-type:'" . $File['ContentType'] . "'");

        App::InitFS();
        #TODO
        echo App::$FS->GetString($Name);

    }

    public static function Delete($Owner_Field, $Owner_Key, $File_MNID)
    {

        $tmp = App::CallAPI
        (
            'File.Delete',
            array
            (
                'Owner_Field' => $Owner_Field,
                'Owner_Key' => $Owner_Key,
                'File_MNID' => $File_MNID,
            )
        );

        App::InitFS();
        App::$FS->DelFile("File_" . $File_MNID);
    }

    public static function DeleteAll($Owner_Field, $Owner_Key)
    {
        foreach(self::SelectList($Owner_Field, $Owner_Key) as $aRow)
        {
            self::Delete($Owner_Field, $Owner_Key, $aRow['File_MNID']);
        }
    }

}

Notes:

Please keep in mind that this class DOES NOT implement security. It assumes that the caller has an authenticated Owner_Field and Owner_Key before calling FileClient::Serve(...) etc...

It's a bit late, so if some of this doesn't make sense, just leave a comment. Have a great evening, and I hope this helps some.

PS. The user interface can be simple tables and file upload fields, etc.. Or you could be fancy and use a flash uploader...

gahooa
Wow thanks a lot, this was a lot more than I was expecting to get! It will take me a few to go through it but I believe this answers all of my questions :)
A: 

If the existing platform ist built on cake I think it could be beneficial to look into cakephp file handling. I don't know this part of cake but I have used Zend_Form in combination with Zend_File and Zend_File_Transfer which supports me with a lot of very helpfull stuff like multi-upload, validators (Count, Crc32, ExcludeExtension, ExcludeMimeType, Exists, Extension, FilesSize, ImageSize, IsCompressed, IsImage, Hash, Md5, MimeType, NotExists, Sha1, Size, Upload, WordCount)...

tharkun
You can drop Zend Framework into your app/vendors folder within the default CakePHP directory structure and use App::import('Vendor', ...) to load in any of the Zend classes you need to use.
deizel
+1  A: 

I recommend having a look at the following community-contributed CakePHP code examples:

deizel
AHh very cool thanks, I couldn't find anything like this before.