From c739f098358b535c58f7ce69a414705afcf35ed2 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 7 Sep 2016 09:52:07 -0700 Subject: [PATCH 1/2] add monolithic version, and php server starter --- monolithic/Upload.php | 1391 +++++++++++++++++++++++ monolithic/index.php | 53 + monolithic/startPhpServer.sh | 1 + monolithic/uploadsdir/57d044f9d991b.jpg | Bin 0 -> 29758 bytes 4 files changed, 1445 insertions(+) create mode 100644 monolithic/Upload.php create mode 100644 monolithic/index.php create mode 100755 monolithic/startPhpServer.sh create mode 100644 monolithic/uploadsdir/57d044f9d991b.jpg diff --git a/monolithic/Upload.php b/monolithic/Upload.php new file mode 100644 index 0000000..8ce26f5 --- /dev/null +++ b/monolithic/Upload.php @@ -0,0 +1,1391 @@ + + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * Autoloader + * + * This class provides a default PSR-0 autoloader if not using Composer. + * + * @author Josh Lockhart + * @since 1.0.0 + * @package Upload + */ +class Autoloader +{ + /** + * The project's base directory + * @var string + */ + static protected $base; + + /** + * Register autoloader + */ + static public function register() + { + self::$base = dirname(__FILE__) . '/../'; + spl_autoload_register(array(new self, 'autoload')); + } + + /** + * Autoload classname + * @param string $className The class to load + */ + static public function autoload($className) + { + $className = ltrim($className, '\\'); + $fileName = ''; + $namespace = ''; + if ($lastNsPos = strripos($className, '\\')) { + $namespace = substr($className, 0, $lastNsPos); + $className = substr($className, $lastNsPos + 1); + $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; + } + $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; + + require self::$base . $fileName; + } +} +?>fileInfo = $fileInfo; + + parent::__construct($message); + } + + /** + * Get related file + * + * @return \Upload\FileInfoInterface + */ + public function getFileInfo() + { + return $this->fileInfo; + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * FileInfo Interface + * + * @author Josh Lockhart + * @since 2.0.0 + * @package Upload + */ +interface FileInfoInterface +{ + public function getPathname(); + + public function getName(); + + public function setName($name); + + public function getExtension(); + + public function setExtension($extension); + + public function getNameWithExtension(); + + public function getMimetype(); + + public function getSize(); + + public function getMd5(); + + public function getDimensions(); + + public function isUploadedFile(); +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * File Information + * + * @author Josh Lockhart + * @since 2.0.0 + * @package Upload + */ +class FileInfo extends \SplFileInfo implements \Upload\FileInfoInterface +{ + /** + * Factory method that returns new instance of \FileInfoInterface + * @var callable + */ + protected static $factory; + + /** + * File name (without extension) + * @var string + */ + protected $name; + + /** + * File extension (without dot prefix) + * @var string + */ + protected $extension; + + /** + * File mimetype + * @var string + */ + protected $mimetype; + + /** + * Constructor + * + * @param string $filePathname Absolute path to uploaded file on disk + * @param string $newName Desired file name (with extension) of uploaded file + */ + public function __construct($filePathname, $newName = null) + { + $desiredName = is_null($newName) ? $filePathname : $newName; + $this->setName(pathinfo($desiredName, PATHINFO_FILENAME)); + $this->setExtension(pathinfo($desiredName, PATHINFO_EXTENSION)); + + parent::__construct($filePathname); + } + + /** + * Get file name (without extension) + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set file name (without extension) + * + * It also makes sure file name is safe + * + * @param string $name + * @return \Upload\FileInfo Self + */ + public function setName($name) + { + $name = preg_replace("/([^\w\s\d\-_~,;:\[\]\(\).]|[\.]{2,})/", "", $name); + $name = basename($name); + $this->name = $name; + + return $this; + } + + /** + * Get file extension (without dot prefix) + * + * @return string + */ + public function getExtension() + { + return $this->extension; + } + + /** + * Set file extension (without dot prefix) + * + * @param string $extension + * @return \Upload\FileInfo Self + */ + public function setExtension($extension) + { + $this->extension = strtolower($extension); + + return $this; + } + + /** + * Get file name with extension + * + * @return string + */ + public function getNameWithExtension() + { + return $this->extension === '' ? $this->name : sprintf('%s.%s', $this->name, $this->extension); + } + + /** + * Get mimetype + * + * @return string + */ + public function getMimetype() + { + if (isset($this->mimetype) === false) { + $finfo = new \finfo(FILEINFO_MIME); + $mimetype = $finfo->file($this->getPathname()); + $mimetypeParts = preg_split('/\s*[;,]\s*/', $mimetype); + $this->mimetype = strtolower($mimetypeParts[0]); + unset($finfo); + } + + return $this->mimetype; + } + + /** + * Get md5 + * + * @return string + */ + public function getMd5() + { + return md5_file($this->getPathname()); + } + + /** + * Get a specified hash + * + * @return string + */ + public function getHash($algorithm = 'md5') + { + return hash_file($algorithm, $this->getPathname()); + } + + /** + * Get image dimensions + * + * @return array formatted array of dimensions + */ + public function getDimensions() + { + list($width, $height) = getimagesize($this->getPathname()); + + return array( + 'width' => $width, + 'height' => $height + ); + } + + /** + * Is this file uploaded with a POST request? + * + * This is a separate method so that it can be stubbed in unit tests to avoid + * the hard dependency on the `is_uploaded_file` function. + * + * @return bool + */ + public function isUploadedFile() + { + return is_uploaded_file($this->getPathname()); + } + + public static function setFactory($callable) + { + if (is_object($callable) === false || method_exists($callable, '__invoke') === false) { + throw new \InvalidArgumentException('Callback is not a Closure or invokable object.'); + } + + static::$factory = $callable; + } + + public static function createFromFactory($tmpName, $name = null) { + if (isset(static::$factory) === true) { + $result = call_user_func_array(static::$factory, array($tmpName, $name)); + if ($result instanceof \Upload\FileInfoInterface === false) { + throw new \RuntimeException('FileInfo factory must return instance of \Upload\FileInfoInterface.'); + } + + return $result; + } + + return new static($tmpName, $name); + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * File + * + * This class provides the implementation for an uploaded file. It exposes + * common attributes for the uploaded file (e.g. name, extension, media type) + * and allows you to attach validations to the file that must pass for the + * upload to succeed. + * + * @author Josh Lockhart + * @since 1.0.0 + * @package Upload + */ +class File implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** + * Upload error code messages + * @var array + */ + protected static $errorCodeMessages = array( + 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', + 3 => 'The uploaded file was only partially uploaded', + 4 => 'No file was uploaded', + 6 => 'Missing a temporary folder', + 7 => 'Failed to write file to disk', + 8 => 'A PHP extension stopped the file upload' + ); + + /** + * Storage delegate + * @var \Upload\StorageInterface + */ + protected $storage; + + /** + * File information + * @var array[\Upload\FileInfoInterface] + */ + protected $objects = array(); + + /** + * Validations + * @var array[\Upload\ValidationInterface] + */ + protected $validations = array(); + + /** + * Validation errors + * @var array[String] + */ + protected $errors = array(); + + /** + * Before validation callback + * @var callable + */ + protected $beforeValidationCallback; + + /** + * After validation callback + * @var callable + */ + protected $afterValidationCallback; + + /** + * Before upload callback + * @var callable + */ + protected $beforeUploadCallback; + + /** + * After upload callback + * @var callable + */ + protected $afterUploadCallback; + + /** + * Constructor + * + * @param string $key The $_FILES[] key + * @param \Upload\StorageInterface $storage The upload delegate instance + * @throws \RuntimeException If file uploads are disabled in the php.ini file + * @throws \InvalidArgumentException If $_FILES[] does not contain key + */ + public function __construct($key, \Upload\StorageInterface $storage) + { + // Check if file uploads are allowed + if (ini_get('file_uploads') == false) { + throw new \RuntimeException('File uploads are disabled in your PHP.ini file'); + } + + // Check if key exists + if (isset($_FILES[$key]) === false) { + throw new \InvalidArgumentException("Cannot find uploaded file(s) identified by key: $key"); + } + + // Collect file info + if (is_array($_FILES[$key]['tmp_name']) === true) { + foreach ($_FILES[$key]['tmp_name'] as $index => $tmpName) { + if ($_FILES[$key]['error'][$index] !== UPLOAD_ERR_OK) { + $this->errors[] = sprintf( + '%s: %s', + $_FILES[$key]['name'][$index], + static::$errorCodeMessages[$_FILES[$key]['error'][$index]] + ); + continue; + } + + $this->objects[] = \Upload\FileInfo::createFromFactory( + $_FILES[$key]['tmp_name'][$index], + $_FILES[$key]['name'][$index] + ); + } + } else { + if ($_FILES[$key]['error'] !== UPLOAD_ERR_OK) { + $this->errors[] = sprintf( + '%s: %s', + $_FILES[$key]['name'], + static::$errorCodeMessages[$_FILES[$key]['error']] + ); + } + + $this->objects[] = \Upload\FileInfo::createFromFactory( + $_FILES[$key]['tmp_name'], + $_FILES[$key]['name'] + ); + } + + $this->storage = $storage; + } + + /******************************************************************************** + * Callbacks + *******************************************************************************/ + + /** + * Set `beforeValidation` callable + * + * @param callable $callable Should accept one `\Upload\FileInfoInterface` argument + * @return \Upload\File Self + * @throws \InvalidArgumentException If argument is not a Closure or invokable object + */ + public function beforeValidate($callable) + { + if (is_object($callable) === false || method_exists($callable, '__invoke') === false) { + throw new \InvalidArgumentException('Callback is not a Closure or invokable object.'); + } + $this->beforeValidation = $callable; + + return $this; + } + + /** + * Set `afterValidation` callable + * + * @param callable $callable Should accept one `\Upload\FileInfoInterface` argument + * @return \Upload\File Self + * @throws \InvalidArgumentException If argument is not a Closure or invokable object + */ + public function afterValidate($callable) + { + if (is_object($callable) === false || method_exists($callable, '__invoke') === false) { + throw new \InvalidArgumentException('Callback is not a Closure or invokable object.'); + } + $this->afterValidation = $callable; + + return $this; + } + + /** + * Set `beforeUpload` callable + * + * @param callable $callable Should accept one `\Upload\FileInfoInterface` argument + * @return \Upload\File Self + * @throws \InvalidArgumentException If argument is not a Closure or invokable object + */ + public function beforeUpload($callable) + { + if (is_object($callable) === false || method_exists($callable, '__invoke') === false) { + throw new \InvalidArgumentException('Callback is not a Closure or invokable object.'); + } + $this->beforeUpload = $callable; + + return $this; + } + + /** + * Set `afterUpload` callable + * + * @param callable $callable Should accept one `\Upload\FileInfoInterface` argument + * @return \Upload\File Self + * @throws \InvalidArgumentException If argument is not a Closure or invokable object + */ + public function afterUpload($callable) + { + if (is_object($callable) === false || method_exists($callable, '__invoke') === false) { + throw new \InvalidArgumentException('Callback is not a Closure or invokable object.'); + } + $this->afterUpload = $callable; + + return $this; + } + + /** + * Apply callable + * + * @param string $callbackName + * @param \Upload\FileInfoInterface $file + * @return \Upload\File Self + */ + protected function applyCallback($callbackName, \Upload\FileInfoInterface $file) + { + if (in_array($callbackName, array('beforeValidation', 'afterValidation', 'beforeUpload', 'afterUpload')) === true) { + if (isset($this->$callbackName) === true) { + call_user_func_array($this->$callbackName, array($file)); + } + } + } + + /******************************************************************************** + * Validation and Error Handling + *******************************************************************************/ + + /** + * Add file validations + * + * @param array[\Upload\ValidationInterface] $validations + * @return \Upload\File Self + */ + public function addValidations(array $validations) + { + foreach ($validations as $validation) { + $this->addValidation($validation); + } + + return $this; + } + + /** + * Add file validation + * + * @param \Upload\ValidationInterface $validation + * @return \Upload\File Self + */ + public function addValidation(\Upload\ValidationInterface $validation) + { + $this->validations[] = $validation; + + return $this; + } + + /** + * Get file validations + * + * @return array[\Upload\ValidationInterface] + */ + public function getValidations() + { + return $this->validations; + } + + /** + * Is this collection valid and without errors? + * + * @return bool + */ + public function isValid() + { + foreach ($this->objects as $fileInfo) { + // Before validation callback + $this->applyCallback('beforeValidation', $fileInfo); + + // Check is uploaded file + if ($fileInfo->isUploadedFile() === false) { + $this->errors[] = sprintf( + '%s: %s', + $fileInfo->getNameWithExtension(), + 'Is not an uploaded file' + ); + continue; + } + + // Apply user validations + foreach ($this->validations as $validation) { + try { + $validation->validate($fileInfo); + } catch (\Upload\Exception $e) { + $this->errors[] = sprintf( + '%s: %s', + $fileInfo->getNameWithExtension(), + $e->getMessage() + ); + } + } + + // After validation callback + $this->applyCallback('afterValidation', $fileInfo); + } + + return empty($this->errors); + } + + /** + * Get file validation errors + * + * @return array[String] + */ + public function getErrors() + { + return $this->errors; + } + + /******************************************************************************** + * Helper Methods + *******************************************************************************/ + + public function __call($name, $arguments) + { + $count = count($this->objects); + $result = null; + + if ($count) { + if ($count > 1) { + $result = array(); + foreach ($this->objects as $object) { + $result[] = call_user_func_array(array($object, $name), $arguments); + } + } else { + $result = call_user_func_array(array($this->objects[0], $name), $arguments); + } + } + + return $result; + } + + /******************************************************************************** + * Upload + *******************************************************************************/ + + /** + * Upload file (delegated to storage object) + * + * @return bool + * @throws \Upload\Exception If validation fails + * @throws \Upload\Exception If upload fails + */ + public function upload() + { + if ($this->isValid() === false) { + throw new \Upload\Exception('File validation failed'); + } + + foreach ($this->objects as $fileInfo) { + $this->applyCallback('beforeUpload', $fileInfo); + $this->storage->upload($fileInfo); + $this->applyCallback('afterUpload', $fileInfo); + } + + return true; + } + + /******************************************************************************** + * Array Access Interface + *******************************************************************************/ + + public function offsetExists($offset) + { + return isset($this->objects[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->objects[$offset]) ? $this->objects[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->objects[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->objects[$offset]); + } + + /******************************************************************************** + * Iterator Aggregate Interface + *******************************************************************************/ + + public function getIterator() + { + return new \ArrayIterator($this->objects); + } + + /******************************************************************************** + * Countable Interface + *******************************************************************************/ + + public function count() + { + return count($this->objects); + } + + /******************************************************************************** + * Helpers + *******************************************************************************/ + + /** + * Convert human readable file size (e.g. "10K" or "3M") into bytes + * + * @param string $input + * @return int + */ + public static function humanReadableToBytes($input) + { + $number = (int)$input; + $units = array( + 'b' => 1, + 'k' => 1024, + 'm' => 1048576, + 'g' => 1073741824 + ); + $unit = strtolower(substr($input, -1)); + if (isset($units[$unit])) { + $number = $number * $units[$unit]; + } + + return $number; + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * Storage Interface + * + * @author Josh Lockhart + * @since 2.0.0 + * @package Upload + */ +interface StorageInterface +{ + /** + * Upload file + * + * This method is responsible for uploading an `\Upload\FileInfoInterface` instance + * to its intended destination. If upload fails, an exception should be thrown. + * + * @param \Upload\FileInfoInterface $fileInfo + * @throws \Exception If upload fails + */ + public function upload(\Upload\FileInfoInterface $fileInfo); +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * Validation Interface + * + * @author Josh Lockhart + * @since 2.0.0 + * @package Upload + */ +interface ValidationInterface +{ + /** + * Validate file + * + * This method is responsible for validating an `\Upload\FileInfoInterface` instance. + * If validation fails, an exception should be thrown. + * + * @param \Upload\FileInfoInterface $fileInfo + * @throws \Exception If validation fails + */ + public function validate(\Upload\FileInfoInterface $fileInfo); +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload\Storage; + +/** + * FileSystem Storage + * + * This class uploads files to a designated directory on the filesystem. + * + * @author Josh Lockhart + * @since 1.0.0 + * @package Upload + */ +class FileSystem implements \Upload\StorageInterface +{ + /** + * Path to upload destination directory (with trailing slash) + * @var string + */ + protected $directory; + + /** + * Overwrite existing files? + * @var bool + */ + protected $overwrite; + + /** + * Constructor + * + * @param string $directory Relative or absolute path to upload directory + * @param bool $overwrite Should this overwrite existing files? + * @throws \InvalidArgumentException If directory does not exist + * @throws \InvalidArgumentException If directory is not writable + */ + public function __construct($directory, $overwrite = false) + { + if (!is_dir($directory)) { + throw new \InvalidArgumentException('Directory does not exist'); + } + if (!is_writable($directory)) { + throw new \InvalidArgumentException('Directory is not writable'); + } + $this->directory = rtrim($directory, '/') . DIRECTORY_SEPARATOR; + $this->overwrite = (bool)$overwrite; + } + + /** + * Upload + * + * @param \Upload\FileInfoInterface $file The file object to upload + * @throws \Upload\Exception If overwrite is false and file already exists + * @throws \Upload\Exception If error moving file to destination + */ + public function upload(\Upload\FileInfoInterface $fileInfo) + { + $destinationFile = $this->directory . $fileInfo->getNameWithExtension(); + if ($this->overwrite === false && file_exists($destinationFile) === true) { + throw new \Upload\Exception('File already exists', $fileInfo); + } + + if ($this->moveUploadedFile($fileInfo->getPathname(), $destinationFile) === false) { + throw new \Upload\Exception('File could not be moved to final destination.', $fileInfo); + } + } + + /** + * Move uploaded file + * + * This method allows us to stub this method in unit tests to avoid + * hard dependency on the `move_uploaded_file` function. + * + * @param string $source The source file + * @param string $destination The destination file + * @return bool + */ + protected function moveUploadedFile($source, $destination) + { + return move_uploaded_file($source, $destination); + } +} +?>width = $expectedWidth; + $this->height = $expectedHeight; + } + + /** + * @inheritdoc + */ + public function validate(FileInfoInterface $info) + { + $dimensions = $info->getDimensions(); + $filename = $info->getNameWithExtension(); + if (!$dimensions) { + throw new Exception(sprintf('%s: Could not detect image size.', $filename)); + } + if ($dimensions['width'] != $this->width) { + throw new Exception( + sprintf( + '%s: Image width(%dpx) does not match required width(%dpx)', + $filename, + $dimensions['width'], + $this->width + ) + ); + } + if ($dimensions['height'] != $this->height) { + throw new Exception( + sprintf( + '%s: Image height(%dpx) does not match required height(%dpx)', + $filename, + $dimensions['height'], + $this->height + ) + ); + } + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload\Validation; + +/** + * Validate File Extension + * + * This class validates an uploads file extension. It takes file extension with out dot + * or array of extensions. For example: 'png' or array('jpg', 'png', 'gif'). + * + * WARNING! Validation only by file extension not very secure. + * + * @author Alex Kucherenko + * @package Upload + */ +class Extension implements \Upload\ValidationInterface +{ + /** + * Array of acceptable file extensions without leading dots + * @var array + */ + protected $allowedExtensions; + + /** + * Constructor + * + * @param string|array $allowedExtensions Allowed file extensions + * @example new \Upload\Validation\Extension(array('png','jpg','gif')) + * @example new \Upload\Validation\Extension('png') + */ + public function __construct($allowedExtensions) + { + if (is_string($allowedExtensions) === true) { + $allowedExtensions = array($allowedExtensions); + } + + $this->allowedExtensions = array_map('strtolower', $allowedExtensions); + } + + /** + * Validate + * + * @param \Upload\FileInfoInterface $fileInfo + * @throws \RuntimeException If validation fails + */ + public function validate(\Upload\FileInfoInterface $fileInfo) + { + $fileExtension = strtolower($fileInfo->getExtension()); + + if (in_array($fileExtension, $this->allowedExtensions) === false) { + throw new \Upload\Exception(sprintf('Invalid file extension. Must be one of: %s', implode(', ', $this->allowedExtensions)), $fileInfo); + } + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload\Validation; + +/** + * Validate Upload Media Type + * + * This class validates an upload's media type (e.g. "image/png"). + * + * @author Josh Lockhart + * @since 1.0.0 + * @package Upload + */ +class Mimetype implements \Upload\ValidationInterface +{ + /** + * Valid media types + * @var array + */ + protected $mimetypes; + + /** + * Constructor + * + * @param string|array $mimetypes + */ + public function __construct($mimetypes) + { + if (is_string($mimetypes) === true) { + $mimetypes = array($mimetypes); + } + $this->mimetypes = $mimetypes; + } + + /** + * Validate + * + * @param \Upload\FileInfoInterface $fileInfo + * @throws \RuntimeException If validation fails + */ + public function validate(\Upload\FileInfoInterface $fileInfo) + { + if (in_array($fileInfo->getMimetype(), $this->mimetypes) === false) { + throw new \Upload\Exception(sprintf('Invalid mimetype. Must be one of: %s', implode(', ', $this->mimetypes)), $fileInfo); + } + } +} +?> + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 2.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload\Validation; + +/** + * Validate Upload File Size + * + * This class validates an uploads file size using maximum and (optionally) + * minimum file size bounds (inclusive). Specify acceptable file sizes + * as an integer (in bytes) or as a human-readable string (e.g. "5MB"). + * + * @author Josh Lockhart + * @since 1.0.0 + * @package Upload + */ +class Size implements \Upload\ValidationInterface +{ + /** + * Minimum acceptable file size (bytes) + * @var int + */ + protected $minSize; + + /** + * Maximum acceptable file size (bytes) + * @var int + */ + protected $maxSize; + + /** + * Constructor + * + * @param int $maxSize Maximum acceptable file size in bytes (inclusive) + * @param int $minSize Minimum acceptable file size in bytes (inclusive) + */ + public function __construct($maxSize, $minSize = 0) + { + if (is_string($maxSize)) { + $maxSize = \Upload\File::humanReadableToBytes($maxSize); + } + $this->maxSize = $maxSize; + + if (is_string($minSize)) { + $minSize = \Upload\File::humanReadableToBytes($minSize); + } + $this->minSize = $minSize; + } + + /** + * Validate + * + * @param \Upload\FileInfoInterface $fileInfo + * @throws \RuntimeException If validation fails + */ + public function validate(\Upload\FileInfoInterface $fileInfo) + { + $fileSize = $fileInfo->getSize(); + + if ($fileSize < $this->minSize) { + throw new \Upload\Exception(sprintf('File size is too small. Must be greater than or equal to: %s', $this->minSize), $fileInfo); + } + + if ($fileSize > $this->maxSize) { + throw new \Upload\Exception(sprintf('File size is too large. Must be less than: %s', $this->maxSize), $fileInfo); + } + } +} +?> \ No newline at end of file diff --git a/monolithic/index.php b/monolithic/index.php new file mode 100644 index 0000000..bad700a --- /dev/null +++ b/monolithic/index.php @@ -0,0 +1,53 @@ +setName($new_filename); + + // Validate file upload + // MimeType List => http://www.iana.org/assignments/media-types/media-types.xhtml + $file->addValidations(array( + // Ensure file mime type is either "png", "jpg" or "gif" + new \Upload\Validation\Mimetype(array('image/png', 'image/jpeg', 'image/gif')), + + // Ensure file is no larger than 5M (use "B", "K", M", or "G") + new \Upload\Validation\Size('5M') + )); + + // Access data about the file that has been uploaded + $data = array( + 'name' => $file->getNameWithExtension(), + 'extension' => $file->getExtension(), + 'mime' => $file->getMimetype(), + 'size' => $file->getSize(), + 'md5' => $file->getMd5(), + 'dimensions' => $file->getDimensions() + ); + + // Try to upload file + try { + // Success! + $file->upload(); + } catch (\Exception $e) { + // Fail! + $errors = $file->getErrors(); + } +} + +?> + +
+ + +
\ No newline at end of file diff --git a/monolithic/startPhpServer.sh b/monolithic/startPhpServer.sh new file mode 100755 index 0000000..de7e8c9 --- /dev/null +++ b/monolithic/startPhpServer.sh @@ -0,0 +1 @@ +php -S localhost:12345 \ No newline at end of file diff --git a/monolithic/uploadsdir/57d044f9d991b.jpg b/monolithic/uploadsdir/57d044f9d991b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10f62f3bb56e55aea96349f8518e2ddd0ff0c401 GIT binary patch literal 29758 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 Date: Wed, 7 Sep 2016 09:57:22 -0700 Subject: [PATCH 2/2] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 73eddc6..96c6cfa 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@ This component simplifies file validation and uploading. +## Quickstart + +``` +git clone https://github.com/richard512/Upload.git +cd Upload/monolithic +sleep 3; xdg-open http://localhost:12345 & +./startPhpServer.sh +``` + ## Usage Assume a file is uploaded with this HTML form: