Resource: PHP Upload Class :: Non-commercial Use

  • Truce
  • Guru
  • Guru
  • Truce
  • Posts: 1477
  • Loc: Washington DC

Post 3+ Months Ago

I wrote the following code over a year ago to handle uploading files. It's intended to make uploading files securely much less of a headache. If you'd like to modify or use it on your personal, non-commercial website feel free to. Any commercial use is prohibited, however. You are not allowed to redistribute, repackage, or otherwise share this script. I take no responsibility for any bugs, errors, or lack of security that may be present in the script.

If you see anything wrong, PM me and I'll try to get in to fix it.

NOTE: I haven't looked over this code in a while. There are probably several things wrong still. As noted in the comments in the head, there were several things I left unfinished.

Code: [ Select ]
<?
 
/**
 * Object for an uploaded file with predefined functions for common tasks.
 *
 * @name UploadedFile
 * @version 0.3
 * @author Corey Ward
 * @copyright Copyright © 2007 Corey Ward. All Rights Reserved.
 * @uses Simplifies handling of uploaded files and reduces security issues related to improper handling of files.
 *
 * @todo Convert all variable names to camelCase
 * @todo Make sure all variable names are clear.
 * @todo Swap order of arguments on comparison operators to CONST == VARIABLE (ex: true == $this->isAllowed())
 * @todo Better support for keeping the original filename. Function to neutralize it (strip bad characters).
 * @todo More robust error handling (throwing real errors)
 * @todo Allow for overwriting of files while using name generator.
 *
 * Some notes behind the script...
 *
 * True errors are suppressed to help deter any hacking or tampering. Wherever feasible, preventative
 * measures should be taken (constants before variables in conditionals, etc).
 *
 * Internal errors (invalid data passed as arguments, bad permissions, etc) are separated from upload errors
 * in order to serve as a layer of protection against malicious use of the knowledge of the error. Instead,
 * when an internal error occurs the upload error will only specify that an internal error has occured. You
 * should log internal errors to a file for later review, or email them to yourself, rather than share them
 * with the world.
 *
 * All errors have been normalized to integers set to constants. This helps prevent accidents like assigning
 * a value instead of performing a conditional, as well as helping separate errors from data being passed around.
 *
 * The script should NOT output any information to the browser. Instead, error messages can be obtained by
 * self::errorMessage and self::internalMessage for upload and internal errors, respectively.
 *
 * This class is hell-bent on ensuring you don't upload a file to somewhere bad, with an invalid name, or
 * overwrite an existing file.
 *
 */
class UploadedFile {
   
/*****      CONFIGURABLE CONSTANTS      *****/
   
    /**
     * Sets the maximum number of times to try to generate a file name without overwriting an old file.
     * If this number is exceeded, the upload will fail with an internal error.
     *
     */
    const MAX_ATTEMPTS = 50;
   
    /**
     * String containing every allowed character in the generated filename.
     *
     */
    const ALLOWED_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
   
 
   
/*****      STANDARD VARIABLES      *****/
 
    /**
     * FILES Array for this file.
     *
     * @var array
     */
    var $fileinfo;
   
    /**
     * Location of the temp file.
     *
     * @var string
     */
    var $location;
   
    /**
     * Extension of the original file.
     *
     * @var string
     */
    var $extension;
   
    /**
     * Mimetype specified by the user's browser.
     *
     * @var string
     */
    var $mimetype;
   
    /**
     * Size of the uploaded file.
     *
     * @var integer
     */
    var $size;
   
    /**
     * Contains the most recent upload error.
     *
     * @var integer
     */
    var $error;
   
    /**
     * Contains the most recent internal error.
     *
     * @var unknown_type
     */
    var $internalError;
   
    /**
     * Array containing all of the allowed mimetypes.
     *
     * @var array
     */
    var $allowedMimetypes = array();
   
    /**
     * Array containing all of the allowed extensions.
     *
     * @var unknown_type
     */
    var $allowedExtensions = array();
   
    /**
     * Actual path that the uploaded file will be moved to.
     *
     * @var string
     */
    var $movePath;
   
    /**
     * Full path to the moved file.
     *
     * @todo Change to "moveFullName"
     * @var string
     */
    var $moveName;
   
    /**
     * Name of the moved file (no path).
     *
     * @todo Change to "moveName"
     * @var string
     */
    var $fileName;
   
   
   
   
/*****      UPLOAD ERROR CONSTANTS      *****/
   
    /**
     * Error code specifying an invalid filetype.
     *
     */
    const INVALID_TYPE      = 5001;
   
    /**
     * Error code specifying inability to move the file (usually due to permissions).
     *
     */
    const UNABLE_TO_MOVE    = 5002;
   
    /**
     * Error code specifying that the file passed was not uploaded via POST through an HTTP request to PHP.
     *
     */
    const NOT_UPLOADED      = 5003;
   
    /**
     * Error code specifying that an internal error has occurred.
     *
     */
    const INTERNAL_ERROR    = 5011;
   
   
   
   
/*****      INTERNAL ERRORS         *****/ 
   
    /**
     * Internal error. The move directory was not properly set before attempting to generate a file name.
     *
     */
    const MOVE_DIR_NOT_SET  = 5004;
   
    /**
     * Internal error. The file info passed is invalid.
     *
     */
    const INVALID_FILE_INFO = 5005;
   
    /**
     * Internal error. Maximum attempts to generate a file name has been reached.
     *
     */
    const MAX_ATTEMPTS_EXCEEDED = 5006;
   
    /**
     * Internal error. The filename specified as a destination file already exists.
     *
     */
    const FILE_ALREADY_EXISTS = 5007;
   
   
   
/*****      MISCELLANEOUS CONSTANTS     *****/
    /**
     * Keep the file's extension the same when generating a dynamic name. This is set
     *
     */
    const KEEP_EXTENSION    = true;
 
   
   
   
   
   
    /**
     * Constructor
     *
     * @param array $fileinfo
     * @return UploadedFile
     */
    function __construct($fileinfo) {
       
        // verify that the file info passed is valid
       
        if (isset($fileinfo['tmp_name']) .
            isset($fileinfo['name']) .
            isset($fileinfo['type']) .
            isset($fileinfo['size']) .
            isset($fileinfo['error'])) {
               
                $this->fileinfo     = $fileinfo;
                $this->location     = $fileinfo['tmp_name'];
                $this->size         = $fileinfo['size'];
                $this->error        = $fileinfo['error'];
               
                // parse out the extension
                $this->extension = strtolower(substr(strrchr($fileinfo['name'], '.'), 1));
               
                // get the mimetype and force to lower case
                $this->mimetype = strtolower($fileinfo['type']);
               
        } else {
            $this->internalError = self::INVALID_FILE_INFO;
        }   
       
        if (UPLOAD_ERR_OK == $this->error) {
            if (!is_uploaded_file($this->location)) {
                $this->error = self::NOT_UPLOADED;
            }
        }
       
        if (!isset($this->error)) {
            $this->error = self::INTERNAL_ERROR;
            $this->internalError = self::INVALID_FILE_INFO;
        }
    }
   
    /**
     * Limits error reporting to fatal errors only.
     *
     * @return boolean
     */
    function disableErrorReporting() {
        error_reporting(E_ERROR);
        return true;
    }
   
    /**
     * Add one or more mimetypes to the list of allowed mimetypes.
     *
     * You may specify a single mimetype passed as a string, or pass multiple as an
     * array (indexed or associative, the key will be trashed).
     *
     * This accepts a PCRE compatible regex or a regular string. If you use a regex, please
     * note that the case-insensitive flag is on (for non-regex purposes) and the matching of
     * beginning (^) and end ($) is forced.
     *
     * @param array $mimetypes
     */
    function addAllowedMimeType($mimetypes) {
        if (!is_array($mimetypes))
            $mimetypes = array($mimetypes);
           
        foreach($mimetypes as $mimetype) {
            if (!array_search($mimetype, $this->allowedMimetypes)) {
                $this->allowedMimetypes[] = strtolower($mimetype);
            }
        }
    }
   
    /**
     * Add one or more extensions to the list of allowed extensions.
     *
     * @param array $extensions
     */
    function addAllowedExtension($extensions) {
        if (!is_array($extensions))
            $extensions = array($extensions);
           
        foreach($extensions as $ext) {
            if (!array_search($ext, $this->allowedExtensions)) {
                $this->allowedExtensions[] = strtolower($ext);
            }
        }
    }
   
    /**
     * Checks the mimetype, extension, errors, and checks to ensure it was uploaded (again).
     *
     * @return boolean
     */
    function isAllowed() {
        if (UPLOAD_ERR_OK == $this->error) {
            $mimetypeMatch = $extMatch = false;
           
            foreach ($this->allowedMimetypes as $mimetype) {
                if (preg_match("|^$mimetype$|i", $this->mimetype)) {
                    $mimetypeMatch = true;
                }
            }
           
            foreach ($this->allowedExtensions as $ext) {
                if ($this->extension == $ext) {
                    $extMatch = true;
                }
            }
           
            if (true === $mimetypeMatch && true === $extMatch) {
                if (is_uploaded_file($this->location)) {
                    return true;
                } else {
                    $this->error = self::NOT_UPLOADED;
                    return false;
                }
            } else {
                $this->error = self::INVALID_TYPE;
                return false;
            }
        } else {
            return false;
        }
    }
   
    /**
     * Set the destination directory for the uploaded file.
     *
     * @param string $path
     * @return boolean
     */
    function SetMovePath($path) {
        if (UPLOAD_ERR_OK == $this->error) {
            if (is_writable($path)) {
                if (substr($path, -1, 1) != "/") $path .= "/";
                $this->movePath = $path;
                return true;
            } else {
                $this->error = self::INTERNAL_ERROR;
                $this->internalError = self::UNABLE_TO_MOVE;
                return false;
            }
        } else {
            return false;
        }
    }
       
    /**
     * Generate a random filename that does not conflict with other files in UploadedFile::movePath
     *
     * @param string $prefix - An optional prefix to the file (pass '' for none).
     * @param integer $length - Length of the random portion of the file name.
     * @return boolean
     */
    function GenerateName($prefix, $length, $extension=self::KEEP_EXTENSION) {
        if (UPLOAD_ERR_OK == $this->error) {
            if ($this->movePath) {
                $attempts = 0;
               
                do {
                   
                    $filename = $prefix;
                    for ($i=0; $i<$length; ++$i) {
                        // use the mersenne twister for better randomization
                        $position = mt_rand(0, strlen(self::ALLOWED_CHARACTERS));
                        // parse out a random character from the allowed characters and append it to our filename
                        $filename .= substr(self::ALLOWED_CHARACTERS, $position, 1);
                    }
                   
                    if (self::KEEP_EXTENSION === $extension) {
                        $fullName = $this->movePath . $filename . "." . $this->extension;
                    } else {
                        $fullName = $this->movePath . $filename . "." . $extension;
                    }
                   
                    // check to ensure we aren't overwriting a past file
                    $exists = file_exists($fullName);
                    ++$attempts;
                   
                } while (true === $exists && $attempts < self::MAX_ATTEMPTS);
               
                if ($attempts > self::MAX_ATTEMPTS) {
                    $this->error = self::INTERNAL_ERROR;
                    $this->internalError = self::MAX_ATTEMPTS_EXCEEDED;
                }
               
                $this->moveName = $fullName;
                $this->fileName = $filename . "." . $this->extension;
                return true;
            } else {
                $this->internalError = self::MOVE_DIR_NOT_SET;
                return false;
            }
        } else {
            return false;
        }
    }
   
    /**
     * Set a name for the uploaded file manually. To choose an extension, simply set it as the second argument.
     * Otherwise, pass only the first argument and the original extension will be kept.
     *
     * @param string $name
     * @param string $extension
     * @return boolean
     */
    function SetName($name, $extension=self::KEEP_EXTENSION) {
        if (UPLOAD_ERR_OK == $this->error) {
            if ($this->movePath) {
                if (self::KEEP_EXTENSION === $extension) {
                    $filename = $name . "." . $this->extension;
                } else {
                    $filename = $name . "." . $extension;
                }
               
                $fullName = $this->movePath . $filename;
               
                if (!file_exists($fullName)) {
                    $this->moveName = $fullName;
                    $this->fileName = $filename;
                    return true;
                } else {
                    $this->internalError = self::FILE_ALREADY_EXISTS;
                    return false;
                }
            } else {
                $this->internalError = self::MOVE_DIR_NOT_SET;
                return false;
            }
        } else {
            return false;
        }
    }
   
    /**
     * Strip unfriendly characters from the set filename.
     *
     * @return null
     */
    function CleanName() {
        $count = strlen($this->fileinfo['name']);
        for ($i=0; $i<$count; ++$i) {
            $nameChar[] = $this->fileinfo['name'][$i];
        }
       
        foreach ($nameChar as $character) {
            if (false !== stripos(self::ALLOWED_CHARACTERS, $character)) {
                $clean .= $character;
            }
        }
       
        $this->name = $clean;
    }
   
    /**
     * Move the uploaded file to UploadedFile::moveName
     *
     * @todo Add in a check for movePath as well as moveName
     * @todo Add in an error for a moveName not being set
     *
     * @return boolean
     */
    function Move() {
        if (UPLOAD_ERR_OK == $this->error) {
            if ($this->isAllowed()) {
                if ($this->moveName) {
                    $result = move_uploaded_file($this->location, $this->moveName);
                   
                    if ($result) {
                        return true;
                    } else {
                        $this->error = self::INTERNAL_ERROR;
                        $this->internalError = self::UNABLE_TO_MOVE;
                        return false;
                    }
                } else {
                    $this->error = self::INTERNAL_ERROR;
                    $this->internalError = self::UNABLE_TO_MOVE;
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
   
    /**
     * Alias of self::internalMessage
     *
     * @return string
     */
    function internalErrorMessage() {
        return $this->internalMessage();
    }
   
    /**
     * Gets the message associated with the internal error (non-upload associated).
     *
     * @return string
     */
    function internalMessage() {
        switch($this->internalError) {
            case self::MOVE_DIR_NOT_SET:
                $message = "The move directory was not properly set before generating a file name.";
                break;
               
            case self::UNABLE_TO_MOVE:
                $message = "We were unable to move the file to the proper destination due to permissions. Please try again later.";
                break;
               
            case self::MAX_ATTEMPTS_EXCEEDED:
                $message = "The number of attempts to generate a randomized file name has exceeded the threshold set by MAX_ATTEMPTS (" . self::MAX_ATTEMPTS . ")";
                break;
               
            case self::FILE_ALREADY_EXISTS:
                $message = "A file with the specified filename already exists in \"$this->movePath\".";
                break;
               
            default:
                $message = "No errors found.";
                break;
        }
       
        return $message;
    }
   
    /**
     * Returns an error message for the current error code.
     *
     * @return string
     */
    function errorMessage() {
        switch ($this->error) {
            case UPLOAD_ERR_OK:
                $message = "Upload OK!";
                break;
               
            case UPLOAD_ERR_INI_SIZE:
                $message = "The file you uploaded is larger than the maximum file size allowed by this installation of PHP.";
                break;
               
            case UPLOAD_ERR_FORM_SIZE:
                $message = "The file you uploaded is larger than the maximum file size allowed by this form.";
                break;
               
            case UPLOAD_ERR_PARTIAL:
                $message = "The file did not upload completely. Please try again.";
                break;
               
            case UPLOAD_ERR_NO_FILE:
                $message = "You did not select a file to upload.";
                break;
               
            case UPLOAD_ERR_NO_TMP_DIR:
                $message = "There was no temporary directory found to upload your file to. Please try again later.";
                break;
               
            case UPLOAD_ERR_CANT_WRITE:
                $message = "There is a problem with the permissions on this server. Please try again later.";
                break;
               
            case UPLOAD_ERR_EXTENSION:
                $message = "Your file upload was stopped by an extension of PHP.";
                break;
               
            case self::NOT_UPLOADED:
                $message = "This file was not uploaded via HTTP POST. For security reasons it cannot be used.";
                break;
               
            case self::INVALID_TYPE:
                $message = "The file you uploaded is not in the list of allowed file types.";
                break;
               
            case self::INTERNAL_ERROR:
                $message = "An internal error has occured.";
                break;
               
            default:
                $message = false;
                break;
        }
       
        return $message;
    }
}
 
/**
 * Example 1:
 *
 * Demonstrates a very basic use of the script.
 *
 *
 
$file = new UploadedFile($_FILES['myfile']);
 
$file->addAllowedExtension('.txt');
$file->addAllowedMimeType('text/plain');
 
if ($file->isAllowed()) {
    $file->SetMovePath('/home/public/files');
    $file->GenerateName('', 5);
    $file->Move();
}
 
if (UPLOAD_ERR_OK != $file->error) {
    echo $file->errorMessage;
}
 
if ($file->internalError) {
    mail('you@domain.com', 'Upload Error', $file->internalMessage());
}
 
*/
 
 
?>
  1. <?
  2.  
  3. /**
  4.  * Object for an uploaded file with predefined functions for common tasks.
  5.  *
  6.  * @name UploadedFile
  7.  * @version 0.3
  8.  * @author Corey Ward
  9.  * @copyright Copyright © 2007 Corey Ward. All Rights Reserved.
  10.  * @uses Simplifies handling of uploaded files and reduces security issues related to improper handling of files.
  11.  *
  12.  * @todo Convert all variable names to camelCase
  13.  * @todo Make sure all variable names are clear.
  14.  * @todo Swap order of arguments on comparison operators to CONST == VARIABLE (ex: true == $this->isAllowed())
  15.  * @todo Better support for keeping the original filename. Function to neutralize it (strip bad characters).
  16.  * @todo More robust error handling (throwing real errors)
  17.  * @todo Allow for overwriting of files while using name generator.
  18.  *
  19.  * Some notes behind the script...
  20.  *
  21.  * True errors are suppressed to help deter any hacking or tampering. Wherever feasible, preventative
  22.  * measures should be taken (constants before variables in conditionals, etc).
  23.  *
  24.  * Internal errors (invalid data passed as arguments, bad permissions, etc) are separated from upload errors
  25.  * in order to serve as a layer of protection against malicious use of the knowledge of the error. Instead,
  26.  * when an internal error occurs the upload error will only specify that an internal error has occured. You
  27.  * should log internal errors to a file for later review, or email them to yourself, rather than share them
  28.  * with the world.
  29.  *
  30.  * All errors have been normalized to integers set to constants. This helps prevent accidents like assigning
  31.  * a value instead of performing a conditional, as well as helping separate errors from data being passed around.
  32.  *
  33.  * The script should NOT output any information to the browser. Instead, error messages can be obtained by
  34.  * self::errorMessage and self::internalMessage for upload and internal errors, respectively.
  35.  *
  36.  * This class is hell-bent on ensuring you don't upload a file to somewhere bad, with an invalid name, or
  37.  * overwrite an existing file.
  38.  *
  39.  */
  40. class UploadedFile {
  41.    
  42. /*****      CONFIGURABLE CONSTANTS      *****/
  43.    
  44.     /**
  45.      * Sets the maximum number of times to try to generate a file name without overwriting an old file.
  46.      * If this number is exceeded, the upload will fail with an internal error.
  47.      *
  48.      */
  49.     const MAX_ATTEMPTS = 50;
  50.    
  51.     /**
  52.      * String containing every allowed character in the generated filename.
  53.      *
  54.      */
  55.     const ALLOWED_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
  56.    
  57.  
  58.    
  59. /*****      STANDARD VARIABLES      *****/
  60.  
  61.     /**
  62.      * FILES Array for this file.
  63.      *
  64.      * @var array
  65.      */
  66.     var $fileinfo;
  67.    
  68.     /**
  69.      * Location of the temp file.
  70.      *
  71.      * @var string
  72.      */
  73.     var $location;
  74.    
  75.     /**
  76.      * Extension of the original file.
  77.      *
  78.      * @var string
  79.      */
  80.     var $extension;
  81.    
  82.     /**
  83.      * Mimetype specified by the user's browser.
  84.      *
  85.      * @var string
  86.      */
  87.     var $mimetype;
  88.    
  89.     /**
  90.      * Size of the uploaded file.
  91.      *
  92.      * @var integer
  93.      */
  94.     var $size;
  95.    
  96.     /**
  97.      * Contains the most recent upload error.
  98.      *
  99.      * @var integer
  100.      */
  101.     var $error;
  102.    
  103.     /**
  104.      * Contains the most recent internal error.
  105.      *
  106.      * @var unknown_type
  107.      */
  108.     var $internalError;
  109.    
  110.     /**
  111.      * Array containing all of the allowed mimetypes.
  112.      *
  113.      * @var array
  114.      */
  115.     var $allowedMimetypes = array();
  116.    
  117.     /**
  118.      * Array containing all of the allowed extensions.
  119.      *
  120.      * @var unknown_type
  121.      */
  122.     var $allowedExtensions = array();
  123.    
  124.     /**
  125.      * Actual path that the uploaded file will be moved to.
  126.      *
  127.      * @var string
  128.      */
  129.     var $movePath;
  130.    
  131.     /**
  132.      * Full path to the moved file.
  133.      *
  134.      * @todo Change to "moveFullName"
  135.      * @var string
  136.      */
  137.     var $moveName;
  138.    
  139.     /**
  140.      * Name of the moved file (no path).
  141.      *
  142.      * @todo Change to "moveName"
  143.      * @var string
  144.      */
  145.     var $fileName;
  146.    
  147.    
  148.    
  149.    
  150. /*****      UPLOAD ERROR CONSTANTS      *****/
  151.    
  152.     /**
  153.      * Error code specifying an invalid filetype.
  154.      *
  155.      */
  156.     const INVALID_TYPE      = 5001;
  157.    
  158.     /**
  159.      * Error code specifying inability to move the file (usually due to permissions).
  160.      *
  161.      */
  162.     const UNABLE_TO_MOVE    = 5002;
  163.    
  164.     /**
  165.      * Error code specifying that the file passed was not uploaded via POST through an HTTP request to PHP.
  166.      *
  167.      */
  168.     const NOT_UPLOADED      = 5003;
  169.    
  170.     /**
  171.      * Error code specifying that an internal error has occurred.
  172.      *
  173.      */
  174.     const INTERNAL_ERROR    = 5011;
  175.    
  176.    
  177.    
  178.    
  179. /*****      INTERNAL ERRORS         *****/ 
  180.    
  181.     /**
  182.      * Internal error. The move directory was not properly set before attempting to generate a file name.
  183.      *
  184.      */
  185.     const MOVE_DIR_NOT_SET  = 5004;
  186.    
  187.     /**
  188.      * Internal error. The file info passed is invalid.
  189.      *
  190.      */
  191.     const INVALID_FILE_INFO = 5005;
  192.    
  193.     /**
  194.      * Internal error. Maximum attempts to generate a file name has been reached.
  195.      *
  196.      */
  197.     const MAX_ATTEMPTS_EXCEEDED = 5006;
  198.    
  199.     /**
  200.      * Internal error. The filename specified as a destination file already exists.
  201.      *
  202.      */
  203.     const FILE_ALREADY_EXISTS = 5007;
  204.    
  205.    
  206.    
  207. /*****      MISCELLANEOUS CONSTANTS     *****/
  208.     /**
  209.      * Keep the file's extension the same when generating a dynamic name. This is set
  210.      *
  211.      */
  212.     const KEEP_EXTENSION    = true;
  213.  
  214.    
  215.    
  216.    
  217.    
  218.    
  219.     /**
  220.      * Constructor
  221.      *
  222.      * @param array $fileinfo
  223.      * @return UploadedFile
  224.      */
  225.     function __construct($fileinfo) {
  226.        
  227.         // verify that the file info passed is valid
  228.        
  229.         if (isset($fileinfo['tmp_name']) .
  230.             isset($fileinfo['name']) .
  231.             isset($fileinfo['type']) .
  232.             isset($fileinfo['size']) .
  233.             isset($fileinfo['error'])) {
  234.                
  235.                 $this->fileinfo     = $fileinfo;
  236.                 $this->location     = $fileinfo['tmp_name'];
  237.                 $this->size         = $fileinfo['size'];
  238.                 $this->error        = $fileinfo['error'];
  239.                
  240.                 // parse out the extension
  241.                 $this->extension = strtolower(substr(strrchr($fileinfo['name'], '.'), 1));
  242.                
  243.                 // get the mimetype and force to lower case
  244.                 $this->mimetype = strtolower($fileinfo['type']);
  245.                
  246.         } else {
  247.             $this->internalError = self::INVALID_FILE_INFO;
  248.         }   
  249.        
  250.         if (UPLOAD_ERR_OK == $this->error) {
  251.             if (!is_uploaded_file($this->location)) {
  252.                 $this->error = self::NOT_UPLOADED;
  253.             }
  254.         }
  255.        
  256.         if (!isset($this->error)) {
  257.             $this->error = self::INTERNAL_ERROR;
  258.             $this->internalError = self::INVALID_FILE_INFO;
  259.         }
  260.     }
  261.    
  262.     /**
  263.      * Limits error reporting to fatal errors only.
  264.      *
  265.      * @return boolean
  266.      */
  267.     function disableErrorReporting() {
  268.         error_reporting(E_ERROR);
  269.         return true;
  270.     }
  271.    
  272.     /**
  273.      * Add one or more mimetypes to the list of allowed mimetypes.
  274.      *
  275.      * You may specify a single mimetype passed as a string, or pass multiple as an
  276.      * array (indexed or associative, the key will be trashed).
  277.      *
  278.      * This accepts a PCRE compatible regex or a regular string. If you use a regex, please
  279.      * note that the case-insensitive flag is on (for non-regex purposes) and the matching of
  280.      * beginning (^) and end ($) is forced.
  281.      *
  282.      * @param array $mimetypes
  283.      */
  284.     function addAllowedMimeType($mimetypes) {
  285.         if (!is_array($mimetypes))
  286.             $mimetypes = array($mimetypes);
  287.            
  288.         foreach($mimetypes as $mimetype) {
  289.             if (!array_search($mimetype, $this->allowedMimetypes)) {
  290.                 $this->allowedMimetypes[] = strtolower($mimetype);
  291.             }
  292.         }
  293.     }
  294.    
  295.     /**
  296.      * Add one or more extensions to the list of allowed extensions.
  297.      *
  298.      * @param array $extensions
  299.      */
  300.     function addAllowedExtension($extensions) {
  301.         if (!is_array($extensions))
  302.             $extensions = array($extensions);
  303.            
  304.         foreach($extensions as $ext) {
  305.             if (!array_search($ext, $this->allowedExtensions)) {
  306.                 $this->allowedExtensions[] = strtolower($ext);
  307.             }
  308.         }
  309.     }
  310.    
  311.     /**
  312.      * Checks the mimetype, extension, errors, and checks to ensure it was uploaded (again).
  313.      *
  314.      * @return boolean
  315.      */
  316.     function isAllowed() {
  317.         if (UPLOAD_ERR_OK == $this->error) {
  318.             $mimetypeMatch = $extMatch = false;
  319.            
  320.             foreach ($this->allowedMimetypes as $mimetype) {
  321.                 if (preg_match("|^$mimetype$|i", $this->mimetype)) {
  322.                     $mimetypeMatch = true;
  323.                 }
  324.             }
  325.            
  326.             foreach ($this->allowedExtensions as $ext) {
  327.                 if ($this->extension == $ext) {
  328.                     $extMatch = true;
  329.                 }
  330.             }
  331.            
  332.             if (true === $mimetypeMatch && true === $extMatch) {
  333.                 if (is_uploaded_file($this->location)) {
  334.                     return true;
  335.                 } else {
  336.                     $this->error = self::NOT_UPLOADED;
  337.                     return false;
  338.                 }
  339.             } else {
  340.                 $this->error = self::INVALID_TYPE;
  341.                 return false;
  342.             }
  343.         } else {
  344.             return false;
  345.         }
  346.     }
  347.    
  348.     /**
  349.      * Set the destination directory for the uploaded file.
  350.      *
  351.      * @param string $path
  352.      * @return boolean
  353.      */
  354.     function SetMovePath($path) {
  355.         if (UPLOAD_ERR_OK == $this->error) {
  356.             if (is_writable($path)) {
  357.                 if (substr($path, -1, 1) != "/") $path .= "/";
  358.                 $this->movePath = $path;
  359.                 return true;
  360.             } else {
  361.                 $this->error = self::INTERNAL_ERROR;
  362.                 $this->internalError = self::UNABLE_TO_MOVE;
  363.                 return false;
  364.             }
  365.         } else {
  366.             return false;
  367.         }
  368.     }
  369.        
  370.     /**
  371.      * Generate a random filename that does not conflict with other files in UploadedFile::movePath
  372.      *
  373.      * @param string $prefix - An optional prefix to the file (pass '' for none).
  374.      * @param integer $length - Length of the random portion of the file name.
  375.      * @return boolean
  376.      */
  377.     function GenerateName($prefix, $length, $extension=self::KEEP_EXTENSION) {
  378.         if (UPLOAD_ERR_OK == $this->error) {
  379.             if ($this->movePath) {
  380.                 $attempts = 0;
  381.                
  382.                 do {
  383.                    
  384.                     $filename = $prefix;
  385.                     for ($i=0; $i<$length; ++$i) {
  386.                         // use the mersenne twister for better randomization
  387.                         $position = mt_rand(0, strlen(self::ALLOWED_CHARACTERS));
  388.                         // parse out a random character from the allowed characters and append it to our filename
  389.                         $filename .= substr(self::ALLOWED_CHARACTERS, $position, 1);
  390.                     }
  391.                    
  392.                     if (self::KEEP_EXTENSION === $extension) {
  393.                         $fullName = $this->movePath . $filename . "." . $this->extension;
  394.                     } else {
  395.                         $fullName = $this->movePath . $filename . "." . $extension;
  396.                     }
  397.                    
  398.                     // check to ensure we aren't overwriting a past file
  399.                     $exists = file_exists($fullName);
  400.                     ++$attempts;
  401.                    
  402.                 } while (true === $exists && $attempts < self::MAX_ATTEMPTS);
  403.                
  404.                 if ($attempts > self::MAX_ATTEMPTS) {
  405.                     $this->error = self::INTERNAL_ERROR;
  406.                     $this->internalError = self::MAX_ATTEMPTS_EXCEEDED;
  407.                 }
  408.                
  409.                 $this->moveName = $fullName;
  410.                 $this->fileName = $filename . "." . $this->extension;
  411.                 return true;
  412.             } else {
  413.                 $this->internalError = self::MOVE_DIR_NOT_SET;
  414.                 return false;
  415.             }
  416.         } else {
  417.             return false;
  418.         }
  419.     }
  420.    
  421.     /**
  422.      * Set a name for the uploaded file manually. To choose an extension, simply set it as the second argument.
  423.      * Otherwise, pass only the first argument and the original extension will be kept.
  424.      *
  425.      * @param string $name
  426.      * @param string $extension
  427.      * @return boolean
  428.      */
  429.     function SetName($name, $extension=self::KEEP_EXTENSION) {
  430.         if (UPLOAD_ERR_OK == $this->error) {
  431.             if ($this->movePath) {
  432.                 if (self::KEEP_EXTENSION === $extension) {
  433.                     $filename = $name . "." . $this->extension;
  434.                 } else {
  435.                     $filename = $name . "." . $extension;
  436.                 }
  437.                
  438.                 $fullName = $this->movePath . $filename;
  439.                
  440.                 if (!file_exists($fullName)) {
  441.                     $this->moveName = $fullName;
  442.                     $this->fileName = $filename;
  443.                     return true;
  444.                 } else {
  445.                     $this->internalError = self::FILE_ALREADY_EXISTS;
  446.                     return false;
  447.                 }
  448.             } else {
  449.                 $this->internalError = self::MOVE_DIR_NOT_SET;
  450.                 return false;
  451.             }
  452.         } else {
  453.             return false;
  454.         }
  455.     }
  456.    
  457.     /**
  458.      * Strip unfriendly characters from the set filename.
  459.      *
  460.      * @return null
  461.      */
  462.     function CleanName() {
  463.         $count = strlen($this->fileinfo['name']);
  464.         for ($i=0; $i<$count; ++$i) {
  465.             $nameChar[] = $this->fileinfo['name'][$i];
  466.         }
  467.        
  468.         foreach ($nameChar as $character) {
  469.             if (false !== stripos(self::ALLOWED_CHARACTERS, $character)) {
  470.                 $clean .= $character;
  471.             }
  472.         }
  473.        
  474.         $this->name = $clean;
  475.     }
  476.    
  477.     /**
  478.      * Move the uploaded file to UploadedFile::moveName
  479.      *
  480.      * @todo Add in a check for movePath as well as moveName
  481.      * @todo Add in an error for a moveName not being set
  482.      *
  483.      * @return boolean
  484.      */
  485.     function Move() {
  486.         if (UPLOAD_ERR_OK == $this->error) {
  487.             if ($this->isAllowed()) {
  488.                 if ($this->moveName) {
  489.                     $result = move_uploaded_file($this->location, $this->moveName);
  490.                    
  491.                     if ($result) {
  492.                         return true;
  493.                     } else {
  494.                         $this->error = self::INTERNAL_ERROR;
  495.                         $this->internalError = self::UNABLE_TO_MOVE;
  496.                         return false;
  497.                     }
  498.                 } else {
  499.                     $this->error = self::INTERNAL_ERROR;
  500.                     $this->internalError = self::UNABLE_TO_MOVE;
  501.                     return false;
  502.                 }
  503.             } else {
  504.                 return false;
  505.             }
  506.         } else {
  507.             return false;
  508.         }
  509.     }
  510.    
  511.     /**
  512.      * Alias of self::internalMessage
  513.      *
  514.      * @return string
  515.      */
  516.     function internalErrorMessage() {
  517.         return $this->internalMessage();
  518.     }
  519.    
  520.     /**
  521.      * Gets the message associated with the internal error (non-upload associated).
  522.      *
  523.      * @return string
  524.      */
  525.     function internalMessage() {
  526.         switch($this->internalError) {
  527.             case self::MOVE_DIR_NOT_SET:
  528.                 $message = "The move directory was not properly set before generating a file name.";
  529.                 break;
  530.                
  531.             case self::UNABLE_TO_MOVE:
  532.                 $message = "We were unable to move the file to the proper destination due to permissions. Please try again later.";
  533.                 break;
  534.                
  535.             case self::MAX_ATTEMPTS_EXCEEDED:
  536.                 $message = "The number of attempts to generate a randomized file name has exceeded the threshold set by MAX_ATTEMPTS (" . self::MAX_ATTEMPTS . ")";
  537.                 break;
  538.                
  539.             case self::FILE_ALREADY_EXISTS:
  540.                 $message = "A file with the specified filename already exists in \"$this->movePath\".";
  541.                 break;
  542.                
  543.             default:
  544.                 $message = "No errors found.";
  545.                 break;
  546.         }
  547.        
  548.         return $message;
  549.     }
  550.    
  551.     /**
  552.      * Returns an error message for the current error code.
  553.      *
  554.      * @return string
  555.      */
  556.     function errorMessage() {
  557.         switch ($this->error) {
  558.             case UPLOAD_ERR_OK:
  559.                 $message = "Upload OK!";
  560.                 break;
  561.                
  562.             case UPLOAD_ERR_INI_SIZE:
  563.                 $message = "The file you uploaded is larger than the maximum file size allowed by this installation of PHP.";
  564.                 break;
  565.                
  566.             case UPLOAD_ERR_FORM_SIZE:
  567.                 $message = "The file you uploaded is larger than the maximum file size allowed by this form.";
  568.                 break;
  569.                
  570.             case UPLOAD_ERR_PARTIAL:
  571.                 $message = "The file did not upload completely. Please try again.";
  572.                 break;
  573.                
  574.             case UPLOAD_ERR_NO_FILE:
  575.                 $message = "You did not select a file to upload.";
  576.                 break;
  577.                
  578.             case UPLOAD_ERR_NO_TMP_DIR:
  579.                 $message = "There was no temporary directory found to upload your file to. Please try again later.";
  580.                 break;
  581.                
  582.             case UPLOAD_ERR_CANT_WRITE:
  583.                 $message = "There is a problem with the permissions on this server. Please try again later.";
  584.                 break;
  585.                
  586.             case UPLOAD_ERR_EXTENSION:
  587.                 $message = "Your file upload was stopped by an extension of PHP.";
  588.                 break;
  589.                
  590.             case self::NOT_UPLOADED:
  591.                 $message = "This file was not uploaded via HTTP POST. For security reasons it cannot be used.";
  592.                 break;
  593.                
  594.             case self::INVALID_TYPE:
  595.                 $message = "The file you uploaded is not in the list of allowed file types.";
  596.                 break;
  597.                
  598.             case self::INTERNAL_ERROR:
  599.                 $message = "An internal error has occured.";
  600.                 break;
  601.                
  602.             default:
  603.                 $message = false;
  604.                 break;
  605.         }
  606.        
  607.         return $message;
  608.     }
  609. }
  610.  
  611. /**
  612.  * Example 1:
  613.  *
  614.  * Demonstrates a very basic use of the script.
  615.  *
  616.  *
  617.  
  618. $file = new UploadedFile($_FILES['myfile']);
  619.  
  620. $file->addAllowedExtension('.txt');
  621. $file->addAllowedMimeType('text/plain');
  622.  
  623. if ($file->isAllowed()) {
  624.     $file->SetMovePath('/home/public/files');
  625.     $file->GenerateName('', 5);
  626.     $file->Move();
  627. }
  628.  
  629. if (UPLOAD_ERR_OK != $file->error) {
  630.     echo $file->errorMessage;
  631. }
  632.  
  633. if ($file->internalError) {
  634.     mail('you@domain.com', 'Upload Error', $file->internalMessage());
  635. }
  636.  
  637. */
  638.  
  639.  
  640. ?>
  • Anonymous
  • Bot
  • No Avatar
  • Posts: ?
  • Loc: Ozzuland
  • Status: Online

Post 3+ Months Ago

  • righteous_trespasser
  • Scuffle
  • Genius
  • User avatar
  • Posts: 6230
  • Loc: South-Africa

Post 3+ Months Ago

just a question, isn't "move_uploaded_file" easy enough?
  • may
  • Proficient
  • Proficient
  • User avatar
  • Posts: 328
  • Loc: Holland [NL]

Post 3+ Months Ago

lol, could be. The should be is a tough one though :D
  • may
  • Proficient
  • Proficient
  • User avatar
  • Posts: 328
  • Loc: Holland [NL]

Post 3+ Months Ago

I often face myself with the question is my code realy functional or is there a smarter way to reach my goal. When am i trying to create a programatical wonder or is it realy the basic functionality i want to guarantee here.

When i look at this part of the code i wonder the same...
Code: [ Select ]
 
 * Generate a random filename that does not conflict with other files in UploadedFile::movePath
    function GenerateName($prefix, $length, $extension=self::KEEP_EXTENSION)
 
  1.  
  2.  * Generate a random filename that does not conflict with other files in UploadedFile::movePath
  3.     function GenerateName($prefix, $length, $extension=self::KEEP_EXTENSION)
  4.  


i.e. isnt this random enough? and i think this is always succesfull :)

Code: [ Select ]
 
private function CreateUniqueName($ext, $savedir){
 /* Lets do a basic check for the very unique moment the md5 hash does match ^^ usually it shouldnt loop at all.*/
 do{
  $FileTry = $this->CreateRand().$ext;
    if(!file_exists($savedir.$FileTry)){
      $unique = true;
      return $FileTry;
    }
  }while($unique = false;)
}
   
private function CreateRand(){
   /* Pick some randoms */
   $string = md5($_SERVER['remote_addr']).md5(date('HiSuYdm')).Session_id();
   /* Make the string 32 chr long */
   $string = md5($string);
   Return $string;
}
 
  1.  
  2. private function CreateUniqueName($ext, $savedir){
  3.  /* Lets do a basic check for the very unique moment the md5 hash does match ^^ usually it shouldnt loop at all.*/
  4.  do{
  5.   $FileTry = $this->CreateRand().$ext;
  6.     if(!file_exists($savedir.$FileTry)){
  7.       $unique = true;
  8.       return $FileTry;
  9.     }
  10.   }while($unique = false;)
  11. }
  12.    
  13. private function CreateRand(){
  14.    /* Pick some randoms */
  15.    $string = md5($_SERVER['remote_addr']).md5(date('HiSuYdm')).Session_id();
  16.    /* Make the string 32 chr long */
  17.    $string = md5($string);
  18.    Return $string;
  19. }
  20.  
  • righteous_trespasser
  • Scuffle
  • Genius
  • User avatar
  • Posts: 6230
  • Loc: South-Africa

Post 3+ Months Ago

but why even write a function for that why not just make it something like:
Code: [ Select ]
$FileTry = md5(md5($_SERVER['remote_addr']).md5(date('HiSuYdm')).Session_id());

Why would you rather have a function for that if in this case that's most likely only going to be needed in this one place ... ?
  • Truce
  • Guru
  • Guru
  • Truce
  • Posts: 1477
  • Loc: Washington DC

Post 3+ Months Ago

righteous_trespasser wrote:
just a question, isn't "move_uploaded_file" easy enough?


Sure, for your website that your mom and girlfriend visit and nobody else.

Security and being able to handle your logic and the petty stuff separate are what makes this trump writing the code every time you need to upload a file.
  • Truce
  • Guru
  • Guru
  • Truce
  • Posts: 1477
  • Loc: Washington DC

Post 3+ Months Ago

may wrote:
i.e. isnt this random enough? and i think this is always succesfull :)

Code: [ Select ]
 
private function CreateUniqueName($ext, $savedir){
 /* Lets do a basic check for the very unique moment the md5 hash does match ^^ usually it shouldnt loop at all.*/
 do{
  $FileTry = $this->CreateRand().$ext;
    if(!file_exists($savedir.$FileTry)){
      $unique = true;
      return $FileTry;
    }
  }while($unique = false;)
}
   
private function CreateRand(){
   /* Pick some randoms */
   $string = md5($_SERVER['remote_addr']).md5(date('HiSuYdm')).Session_id();
   /* Make the string 32 chr long */
   $string = md5($string);
   Return $string;
}
 
  1.  
  2. private function CreateUniqueName($ext, $savedir){
  3.  /* Lets do a basic check for the very unique moment the md5 hash does match ^^ usually it shouldnt loop at all.*/
  4.  do{
  5.   $FileTry = $this->CreateRand().$ext;
  6.     if(!file_exists($savedir.$FileTry)){
  7.       $unique = true;
  8.       return $FileTry;
  9.     }
  10.   }while($unique = false;)
  11. }
  12.    
  13. private function CreateRand(){
  14.    /* Pick some randoms */
  15.    $string = md5($_SERVER['remote_addr']).md5(date('HiSuYdm')).Session_id();
  16.    /* Make the string 32 chr long */
  17.    $string = md5($string);
  18.    Return $string;
  19. }
  20.  


"Usually it shouldn't loop at all." That depends on your website.

You've got a blatantly obvious error that should you have spent your time learning rather than trying to critique my code, you would have kept from being a problem.

Code: [ Select ]
}while($unique = false;)


Well since PHP is going to have no problem setting $unique to false, this loop is going to keep on going and going until you jump into the server and terminate the process. Had you written it like I do when I have comparison operators and where it's relevant and put the constant on the left and the variable on the right, you would have exited the loop and spared your server. And what's with the semicolon after the comparison? Whoops.

What happens when I want to give files a name that is only 3 characters long with your code? What if I want the file names to only be numbers? What happens if every one of the 999 possible filenames is already taken? Now you're stuck in an infinite loop. What if I run a real website so I use cookies and not sessions?

Now you need to go back to work.

Post Information

  • Total Posts in this topic: 7 posts
  • Users browsing this forum: No registered users and 104 guests
  • You cannot post new topics in this forum
  • You cannot reply to topics in this forum
  • You cannot edit your posts in this forum
  • You cannot delete your posts in this forum
  • You cannot post attachments in this forum
 
cron
 

© 1998-2014. Ozzu® is a registered trademark of Unmelted, LLC.