/scripts/xinha/plugins/PSServer/backend.php
PHP | 830 lines | 428 code | 119 blank | 283 comment | 105 complexity | e8208addc4b1608cec5f3f6a3e877fbc MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, AGPL-1.0
- <?php
- /**
- * File Operations API
- *
- * This file contains the new backend File Operations API used for Xinha File
- * Storage. It will serve as the documentation for others wishing to implement
- * the backend in their own language, as well as the PHP implementation. The
- * return data will come via the HTTP status in the case of an error, or JSON
- * data when call has succeeded.
- *
- * Some examples of the URLS associated with this API:
- * ** File Operations **
- * ?file&rename&filename=''newname=''
- * ?file©&filename=''
- * ?file&delete&filename=''
- *
- * ** Directory Operations **
- * ?directory&listing
- * ?directory&create&dirname=''
- * ?directory&delete&dirname=''
- * ?directory&rename&dirname=''newname=''
- *
- * ** Image Operations **
- * ?image&filename=''&[scale|rotate|convert]
- *
- * ** Upload **
- * ?upload&filedata=[binary|text]&filename=''&replace=[true|false]
- *
- * @author Douglas Mayle <douglas@openplans.org>
- * @version 1.0
- * @package PersistentStorage
- *
- */
- /**
- * Config file
- */
- require_once('config.inc.php');
- // Strip slashes if MQGPC is on
- set_magic_quotes_runtime(0);
- if(get_magic_quotes_gpc())
- {
- $to_clean = array(&$_GET, &$_POST, &$_REQUEST, &$_COOKIE);
- while(count($to_clean))
- {
- $cleaning = $to_clean[array_pop($junk = array_keys($to_clean))];
- unset($to_clean[array_pop($junk = array_keys($to_clean))]);
- foreach(array_keys($cleaning) as $k)
- {
- if(is_array($cleaning[$k]))
- {
- $to_clean[] = $cleaning[$k];
- }
- else
- {
- $cleaning[$k] = stripslashes($cleaning[$k]);
- }
- }
- }
- }
- // Set the return headers for a JSON response.
- header('Cache-Control: no-cache, must-revalidate');
- header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
- //header('Content-type: application/json');
- /**#@+
- * Constants
- *
- * Since this is being used as part of a web interface, we'll set some rather
- * conservative limits to keep from overloading the user or the backend.
- */
- /**
- * This is the maximum folder depth to present to the user
- */
- define('MAX_DEPTH', 10);
- /**
- * This is the maximum number of file entries per folder to show to the user,
- */
- define('MAX_FILES_PER_FOLDER', 50);
- /**
- * This array contains the default HTTP Response messages
- *
- */
- $HTTP_ERRORS = array(
- 'HTTP_SUCCESS_OK' => array('code' => 200, 'message' => 'OK'),
- 'HTTP_SUCCESS_CREATED' => array('code' => 201, 'message' => 'Created'),
- 'HTTP_SUCCESS_ACCEPTED' => array('code' => 202, 'message' => 'Accepted'),
- 'HTTP_SUCCESS_NON_AUTHORITATIVE' => array('code' => 203, 'message' => 'Non-Authoritative Information'),
- 'HTTP_SUCCESS_NO_CONTENT' => array('code' => 204, 'message' => 'No Content'),
- 'HTTP_SUCCESS_RESET_CONTENT' => array('code' => 205, 'message' => 'Reset Content'),
- 'HTTP_SUCCESS_PARTIAL_CONTENT' => array('code' => 206, 'message' => 'Partial Content'),
- 'HTTP_REDIRECTION_MULTIPLE_CHOICES' => array('code' => 300, 'message' => 'Multiple Choices'),
- 'HTTP_REDIRECTION_PERMANENT' => array('code' => 301, 'message' => 'Moved Permanently'),
- 'HTTP_REDIRECTION_FOUND' => array('code' => 302, 'message' => 'Found'),
- 'HTTP_REDIRECTION_SEE_OTHER' => array('code' => 303, 'message' => 'See Other'),
- 'HTTP_REDIRECTION_NOT_MODIFIED' => array('code' => 304, 'message' => 'Not Modified'),
- 'HTTP_REDIRECTION_USE_PROXY' => array('code' => 305, 'message' => 'Use Proxy'),
- 'HTTP_REDIRECTION_UNUSED' => array('code' => 306, 'message' => '(Unused)'),
- 'HTTP_REDIRECTION_TEMPORARY' => array('code' => 307, 'message' => 'Temporary Redirect'),
- 'HTTP_CLIENT_BAD_REQUEST' => array('code' => 400, 'message' => 'Bad Request'),
- 'HTTP_CLIENT_UNAUTHORIZED' => array('code' => 401, 'message' => 'Unauthorized'),
- 'HTTP_CLIENT_PAYMENT_REQUIRED' => array('code' => 402, 'message' => 'Payment Required'),
- 'HTTP_CLIENT_FORBIDDEN' => array('code' => 403, 'message' => 'Forbidden'),
- 'HTTP_CLIENT_NOT_FOUND' => array('code' => 404, 'message' => 'Not Found'),
- 'HTTP_CLIENT_METHOD_NOT_ALLOWED' => array('code' => 405, 'message' => 'Method Not Allowed'),
- 'HTTP_CLIENT_NOT_ACCEPTABLE' => array('code' => 406, 'message' => 'Not Acceptable'),
- 'HTTP_CLIENT_PROXY_AUTH_REQUIRED' => array('code' => 407, 'message' => 'Proxy Authentication Required'),
- 'HTTP_CLIENT_REQUEST_TIMEOUT' => array('code' => 408, 'message' => 'Request Timeout'),
- 'HTTP_CLIENT_CONFLICT' => array('code' => 409, 'message' => 'Conflict'),
- 'HTTP_CLIENT_GONE' => array('code' => 410, 'message' => 'Gone'),
- 'HTTP_CLIENT_LENGTH_REQUIRED' => array('code' => 411, 'message' => 'Length Required'),
- 'HTTP_CLIENT_PRECONDITION_FAILED' => array('code' => 412, 'message' => 'Precondition Failed'),
- 'HTTP_CLIENT_REQUEST_TOO_LARGE' => array('code' => 413, 'message' => 'Request Entity Too Large'),
- 'HTTP_CLIENT_REQUEST_URI_TOO_LARGE' => array('code' => 414, 'message' => 'Request-URI Too Long'),
- 'HTTP_CLIENT_UNSUPPORTED_MEDIA_TYPE' => array('code' => 415, 'message' => 'Unsupported Media Type'),
- 'HTTP_CLIENT_REQUESTED_RANGE_NOT_POSSIBLE' => array('code' => 416, 'message' => 'Requested Range Not Satisfiable'),
- 'HTTP_CLIENT_EXPECTATION_FAILED' => array('code' => 417, 'message' => 'Expectation Failed'),
- 'HTTP_SERVER_INTERNAL' => array('code' => 500, 'message' => 'Internal Server Error'),
- 'HTTP_SERVER_NOT_IMPLEMENTED' => array('code' => 501, 'message' => 'Not Implemented'),
- 'HTTP_SERVER_BAD_GATEWAY' => array('code' => 502, 'message' => 'Bad Gateway'),
- 'HTTP_SERVER_SERVICE_UNAVAILABLE' => array('code' => 503, 'message' => 'Service Unavailable'),
- 'HTTP_SERVER_GATEWAY_TIMEOUT' => array('code' => 504, 'message' => 'Gateway Timeout'),
- 'HTTP_SERVER_UNSUPPORTED_VERSION' => array('code' => 505, 'message' => 'HTTP Version not supported')
- );
- /**
- * This is a regular expression used to detect reserved or dangerous filenames.
- * Most NTFS special filenames begin with a dollar sign ('$'), and most Unix
- * special filenames begin with a period (.), so we'll keep them out of this
- * list and just prevent those two characters in the first position. The rest
- * of the special filenames are included below.
- */
- define('RESERVED_FILE_NAMES', 'pagefile\.sys|a\.out|core');
- /**
- * This is a regular expression used to detect invalid file names. It's more
- * strict than necessary, to be valid multi-platform, but not posix-strict
- * because we want to allow unicode filenames. We do, however, allow path
- * seperators in the filename because the file could exist in a subdirectory.
- */
- define('INVALID_FILE_NAME','^[.$]|^(' . RESERVED_FILE_NAMES . ')$|[?%*:|"<>]');
- /**#@-*/
- function main($arguments) {
- $config = get_config(true);
- // Trigger authentication if it's configured.
- if ($config['capabilities']['user_storage'] && empty($_SERVER['PHP_AUTH_USER'])) {
- header('WWW-Authenticate: Basic realm="Xinha Persistent Storage"');
- header('HTTP/1.0 401 Unauthorized');
- echo "You must login in order to use Persistent Storage";
- exit;
- }
- if (!input_valid($arguments, $config['capabilities'])) {
- http_error_exit();
- }
- if (!method_valid($arguments)) {
- http_error_exit('HTTP_CLIENT_METHOD_NOT_ALLOWED');
- }
- if (!dispatch($arguments)) {
- http_error_exit();
- }
- exit();
- }
- main($_REQUEST + $_FILES);
- // ************************************************************
- // ************************************************************
- // Helper Functions
- // ************************************************************
- // ************************************************************
- /**
- * Take the call and properly dispatch it to the methods below. This method
- * assumes valid input.
- */
- function dispatch($arguments) {
- if (array_key_exists('file', $arguments)) {
- if (array_key_exists('rename', $arguments)) {
- if (!file_directory_rename($arguments['filename'], $arguments['newname'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- return true;
- }
- if (array_key_exists('copy', $arguments)) {
- if (!$newentry = file_copy($arguments['filename'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- echo json_encode($newentry);
- return true;
- }
- if (array_key_exists('delete', $arguments)) {
- if (!file_delete($arguments['filename'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- return true;
- }
- }
- if (array_key_exists('directory', $arguments)) {
- if (array_key_exists('listing', $arguments)) {
- echo json_encode(directory_listing());
- return true;
- }
- if (array_key_exists('create', $arguments)) {
- if (!directory_create($arguments['dirname'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- return true;
- }
- if (array_key_exists('delete', $arguments)) {
- if (!directory_delete($arguments['dirname'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- return true;
- }
- if (array_key_exists('rename', $arguments)) {
- if (!file_directory_rename($arguments['dirname'], $arguments['newname'], working_directory())) {
- http_error_exit('HTTP_CLIENT_FORBIDDEN');
- }
- return true;
- }
- }
- if (array_key_exists('image', $arguments)) {
- }
- if (array_key_exists('upload', $arguments)) {
- store_uploaded_file($arguments['filename'], $arguments['filedata'], working_directory());
- return true;
- }
- return false;
- }
- /**
- * Validation of the HTTP Method. For operations that make changes we require
- * POST. To err on the side of safety, we'll only allow GET for known safe
- * operations. This way, if the API is extended, and the method is not
- * updated, we will not accidentally expose non-idempotent methods to GET.
- * This method can only correctly validate the operation if the input is
- * already known to be valid.
- *
- * @param array $arguments The arguments array received by the page.
- * @return boolean Whether or not the HTTP method is correct for the given input.
- */
- function method_valid($arguments) {
- // We assume that the only
- $method = $_SERVER['REQUEST_METHOD'];
- if ($method == 'GET') {
- if (array_key_exists('directory', $arguments) && array_key_exists('listing', $arguments)) {
- return true;
- }
- return false;
- }
- if ($method == 'POST') {
- return true;
- }
- return false;
- }
- /**
- * Validation of the user input. We'll verify what we receive from the user,
- * and send an error in the case of malformed input.
- *
- * Some examples of the URLS associated with this API:
- * ** File Operations **
- * ?file&delete&filename=''
- * ?file©&filename=''
- * ?file&rename&filename=''newname=''
- *
- * ** Directory Operations **
- * ?directory&listing
- * ?directory&create&dirname=''
- * ?directory&delete&dirname=''
- * ?directory&rename&dirname=''newname=''
- *
- * ** Image Operations **
- * ?image&filename=''&[scale|rotate|convert]
- *
- * ** Upload **
- * ?upload&filedata=[binary|text]&filename=''&replace=[true|false]
- *
- * @param array $arguments The arguments array received by the page.
- * @param array $capabilities The capabilities config array used to limit operations.
- * @return boolean Whether or not the input received is valid.
- */
- function input_valid($arguments, $capabilities) {
- // This is going to be really ugly code because it's basically a DFA for
- // parsing arguments. To make things a little clearer, I'll put a
- // pseudo-BNF for each block to show the decision structure.
- //
- // file[empty] filename[valid] (delete[empty] | copy[empty] | (rename[empty] newname[valid]))
- if ($capabilities['file_operations'] &&
- array_key_exists('file', $arguments) &&
- empty($arguments['file']) &&
- array_key_exists('filename', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['filename'])) {
- if (array_key_exists('delete', $arguments) &&
- empty($arguments['delete']) &&
- 3 == count($arguments)) {
- return true;
- }
- if (array_key_exists('copy', $arguments) &&
- empty($arguments['copy']) &&
- 3 == count($arguments)) {
- return true;
- }
- if (array_key_exists('rename', $arguments) &&
- empty($arguments['rename']) &&
- 4 == count($arguments)) {
- if (array_key_exists('newname', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['newname'])) {
- return true;
- }
- }
- return false;
- } elseif (array_key_exists('file', $arguments)) {
- // This isn't necessary because we'll fall through to false, but I'd
- // rather return earlier than later.
- return false;
- }
- // directory[empty] (listing[empty] | (dirname[valid] (create[empty] | delete[empty] | (rename[empty] newname[valid]))))
- if ($capabilities['directory_operations'] &&
- array_key_exists('directory', $arguments) &&
- empty($arguments['directory'])) {
- if (array_key_exists('listing', $arguments) &&
- empty($arguments['listing']) &&
- 2 == count($arguments)) {
- return true;
- }
- if (array_key_exists('dirname', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['dirname'])) {
- if (array_key_exists('create', $arguments) &&
- empty($arguments['create']) &&
- 3 == count($arguments)) {
- return true;
- }
- if (array_key_exists('delete', $arguments) &&
- empty($arguments['delete']) &&
- 3 == count($arguments)) {
- return true;
- }
- if (array_key_exists('rename', $arguments) &&
- empty($arguments['rename']) &&
- 4 == count($arguments)) {
- if (array_key_exists('newname', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['newname'])) {
- return true;
- }
- }
- }
- return false;
- } elseif (array_key_exists('directory', $arguments)) {
- // This isn't necessary because we'll fall through to false, but I'd
- // rather return earlier than later.
- return false;
- }
- // image[empty] filename[valid] ((scale[empty] dimensions[valid]) | (rotate[empty] angle[valid]) | (convert[empty] imagetype[valid]))
- if ($capabilities['image_operations'] &&
- array_key_exists('image', $arguments) &&
- empty($arguments['image']) &&
- array_key_exists('filename', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['filename']) &&
- 4 == count($arguments)) {
- if (array_key_exists('scale', $arguments) &&
- empty($arguments['scale']) &&
- !ereg(INVALID_FILE_NAME, $arguments['dimensions'])) {
- // TODO: FIX REGEX
- http_error_exit();
- return true;
- }
- if (array_key_exists('rotate', $arguments) &&
- empty($arguments['rotate']) &&
- !ereg(INVALID_FILE_NAME, $arguments['angle'])) {
- // TODO: FIX REGEX
- http_error_exit();
- return true;
- }
- if (array_key_exists('convert', $arguments) &&
- empty($arguments['convert']) &&
- !ereg(INVALID_FILE_NAME, $arguments['imagetype'])) {
- // TODO: FIX REGEX
- http_error_exit();
- return true;
- }
- return false;
- } elseif (array_key_exists('image', $arguments)) {
- // This isn't necessary because we'll fall through to false, but I'd
- // rather return earlier than later.
- return false;
- }
- // upload[empty] filedata[binary|text] replace[true|false] filename[valid]?
- if ($capabilities['upload_operations'] &&
- array_key_exists('upload', $arguments) &&
- empty($arguments['upload']) &&
- array_key_exists('filedata', $arguments) &&
- !empty($arguments['filedata']) &&
- array_key_exists('replace', $arguments) &&
- ereg('true|false', $arguments['replace'])) {
- if (4 == count($arguments) &&
- array_key_exists('filename', $arguments) &&
- !ereg(INVALID_FILE_NAME, $arguments['filename'])) {
- return true;
- }
- if (3 == count($arguments)) {
- return true;
- }
- return false;
- } elseif (array_key_exists('upload', $arguments)) {
- // This isn't necessary because we'll fall through to false, but I'd
- // rather return earlier than later.
- return false;
- }
- return false;
- }
- /**
- * HTTP level error handling.
- * @param integer $code The HTTP error code to return to the client. This defaults to 400.
- * @param string $message Error message to send to the client. This defaults to the standard HTTP error messages.
- */
- function http_error_exit($error = 'HTTP_CLIENT_BAD_REQUEST', $message='') {
- global $HTTP_ERRORS;
- $message = !empty($message) ? $message : "HTTP/1.0 {$HTTP_ERRORS[$error]['code']} {$HTTP_ERRORS[$error]['message']}";
- header($message);
- exit($message);
- }
- /**
- * Process the config and return the absolute directory we should be working with,
- * @return string contains the path of the directory all file operations are limited to.
- */
- function working_directory() {
- $config = get_config(true);
- return realpath(getcwd() . DIRECTORY_SEPARATOR . $config['storage_dir'] . DIRECTORY_SEPARATOR);
- }
- /**
- * Check to see if the supplied filename is inside
- */
- function directory_contains($container_directory, $checkfile) {
- // Get the canonical directory and canonical filename. We add a directory
- // seperator to prevent the user from sidestepping into a sibling directory
- // that starts with the same prefix. (e.g. from /home/john to
- // /home/johnson)
- $container_directory = realpath($container_directory) + DIRECTORY_SEPARATOR;
- $checkfile = realpath($checkfile);
- // Now that we have the canonical versions, we can do a string comparison
- // to see if checkfile is inside of container_directory.
- if (strlen($checkfile) <= strlen($container_directory)) {
- // We don't consider the directory to be inside of itself. This
- // prevents users from trying to perform operations on the container
- // directory itself.
- return false;
- }
- // PHP equivalent of string.startswith()
- return substr($checkfile, 0, strlen($container_directory)) == $container_directory;
- }
- /**#@+
- * Directory Operations
- * {@internal *****************************************************************
- * **************************************************************************}}
- */
- /**
- * Return a directory listing as a PHP array.
- * @param string $directory The directory to return a listing of.
- * @param integer $depth The private argument used to limit recursion depth.
- * @return array representing the directory structure.
- */
- function directory_listing($directory='', $depth=1) {
- // We return an empty array if the directory is empty
- $result = array('$type'=>'folder');
- // We won't recurse below MAX_DEPTH.
- if ($depth > MAX_DEPTH) {
- return $result;
- }
- $path = empty($directory) ? working_directory() : $directory;
- // We'll open the directory to check each of the entries
- if ($dir = opendir($path)) {
- // We'll keep track of how many file we process.
- $count = 0;
- // For each entry in the file
- while (($file = readdir($dir)) !== false) {
- // Limit the number of files we process in this folder
- $count += 1;
- if ($count > MAX_FILES_PER_FOLDER) {
- return $result;
- }
- // Ignore hidden files (this includes special files '.' and '..')
- if (strlen($file) && ($file[0] == '.')) {
- continue;
- }
- $filepath = $path . DIRECTORY_SEPARATOR . $file;
- if (filetype($filepath) == 'dir') {
- // We'll recurse and add those results
- $result[$file] = directory_listing($filepath, $depth + 1);
- } else {
- // We'll check to see if we can read any image information from
- // the file. If so, we know it's an image, and we can return
- // it's metadata.
- $imageinfo = @getimagesize($filepath);
- if ($imageinfo) {
- $result[$file] = array('$type'=>'image','metadata'=>array(
- 'width'=>$imageinfo[0],
- 'height'=>$imageinfo[1],
- 'mimetype'=>$imageinfo['mime']
- ));
- } elseif ($extension = strrpos($file, '.')) {
- $extension = substr($file, $extension);
- if (($extension == '.htm') || ($extension == '.html')) {
- $result[$file] = array('$type'=>'html');
- } else {
- $result[$file] = array('$type'=>'text');
- }
- } else {
- $result[$file] = array('$type'=>'document');
- }
- }
- }
-
- closedir($dir);
- }
- return $result;
- }
- /**
- * Create a directory, limiting operations to the chroot directory.
- * @param string $dirname The path to the directory, relative to $chroot.
- * @param string $chroot Only directories inside this directory or its subdirectories can be affected.
- * @return boolean Returns TRUE if successful, and FALSE otherwise.
- */
- function directory_create($dirname, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // We have to take the dirname of the parent directory first, since
- // realpath just returns false if the directory doesn't already exist on
- // the filesystem.
- $createparent = realpath(dirname($chroot . DIRECTORY_SEPARATOR . $dirname));
- $createsub = basename($chroot . DIRECTORY_SEPARATOR . $dirname);
- // The bailout rules for directories that don't exist are complicated
- // because of having to work around realpath. If the parent directory is
- // the same as the chroot, it won't be contained. For this case, we'll
- // check to see if the chroot and the parent are the same and allow it only
- // if the sub portion of dirname is not-empty.
- if (!directory_contains($chroot, $createparent) &&
- !(($chroot == $createparent) && !empty($createsub))) {
- return false;
- }
- return @mkdir($createparent . DIRECTORY_SEPARATOR . $createsub);
- }
- /**
- * Delete a directory, limiting operations to the chroot directory.
- * @param string $dirname The path to the directory, relative to $chroot.
- * @param string $chroot Only directories inside this directory or its subdirectories can be affected.
- * @return boolean Returns TRUE if successful, and FALSE otherwise.
- */
- function directory_delete($dirname, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // $dirname is relative to $chroot.
- $dirname = realpath($chroot . DIRECTORY_SEPARATOR . $dirname);
- // Limit directory operations to the supplied directory.
- if (!directory_contains($chroot, $dirname)) {
- return false;
- }
- return @rmdir($dirname);
- }
- /**#@-*/
- /**#@+
- * File Operations
- * {@internal *****************************************************************
- * **************************************************************************}}
- */
- /**
- * Rename a file or directory, limiting operations to the chroot directory.
- * @param string $filename The path to the file or directory, relative to $chroot.
- * @param string $renameto The path to the renamed file or directory, relative to $chroot.
- * @param string $chroot Only files and directories inside this directory or its subdirectories can be affected.
- * @return boolean Returns TRUE if successful, and FALSE otherwise.
- */
- function file_directory_rename($filename, $renameto, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // $filename is relative to $chroot.
- $filename = realpath($chroot . DIRECTORY_SEPARATOR . $filename);
- // We have to take the dirname of the renamed file or directory first,
- // since realpath just returns false if the file or direcotry doesn't
- // already exist on the filesystem.
- $renameparent = realpath(dirname($chroot . DIRECTORY_SEPARATOR . $renameto));
- $renamefile = basename($chroot . DIRECTORY_SEPARATOR . $renameto);
- // Limit file operations to the supplied directory.
- if (!directory_contains($chroot, $filename)) {
- return false;
- }
- // The bailout rules for the renamed file or directory are more complicated
- // because of having to work around realpath. If the renamed parent
- // directory is the same as the chroot, it won't be contained. For this
- // case, we'll check to see if they're the same and allow it only if the
- // file portion of renameto is not-empty.
- if (!directory_contains($chroot, $renameparent) &&
- !(($chroot == $renameparent) && !empty($renamefile))) {
- return false;
- }
- return @rename($filename, $renameparent . DIRECTORY_SEPARATOR . $renamefile);
- }
- /**
- * Copy a file, limiting operations to the chroot directory.
- * @param string $filename The path to the file, relative to $chroot.
- * @param string $chroot Only files inside this directory or its subdirectories can be affected.
- * @return boolean Returns TRUE if successful, and FALSE otherwise.
- */
- function file_copy($filename, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // $filename is relative to $chroot.
- $filename = realpath($chroot . DIRECTORY_SEPARATOR . $filename);
- // Limit file operations to the supplied directory.
- if (!directory_contains($chroot, $filename)) {
- return false;
- }
- // The PHP copy function blindly copies over existing files. We don't wish
- // this to happen, so we have to perform the copy a bit differently. If we
- // do a check to make sure the file exists, there's always the chance of a
- // race condition where someone else creates the file in between the check
- // and the copy. The only safe way to ensure we don't overwrite an
- // existing file is to call fopen in create-only mode (mode 'x'). If it
- // succeeds, the file did not exist before, and we've successfully created
- // it, meaning we own the file. After that, we can safely copy over our
- // own file.
- for ($count=1; $count<MAX_FILES_PER_FOLDER; ++$count) {
- if (strpos(basename($filename), '.')) {
- $extpos = strrpos($filename, '.');
- $copyname = substr($filename, 0, $extpos) . '_' . $count . substr($filename, $extpos);
- } else {
- // There's no extension, we we'll just add our copy count.
- $copyname = $filename . '_' . $count;
- }
- if ($file = @fopen($copyname, 'x')) {
- // We've successfully created a file, so it's ours. We'll close
- // our handle.
- if (!@fclose($file)) {
- // There was some problem with our file handle.
- return false;
- }
- // Now we copy over the file we created.
- if (!@copy($filename, $copyname)) {
- // The copy failed, even though we own the file, so we'll clean
- // up by removing the file and report failure.
- file_delete($filename, $chroot);
- return false;
- }
- return array(basename($copyname)=>array('$type'=>'image'));
- }
- }
- return false;
- }
- /**
- * Delete a file, limiting operations to the chroot directory.
- * @param string $filename The path to the file, relative to $chroot.
- * @param string $chroot Only files inside this directory or its subdirectories can be affected.
- * @return boolean Returns TRUE if successful, and FALSE otherwise.
- */
- function file_delete($filename, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // $filename is relative to $chroot.
- $filename = realpath($chroot . DIRECTORY_SEPARATOR . $filename);
- // Limit file operations to the supplied directory.
- if (!directory_contains($chroot, $filename)) {
- return false;
- }
- return @unlink($filename);
- }
- /**#@-*/
- /**#@+
- * Upload Operations
- * {@internal *****************************************************************
- * **************************************************************************}}
- */
- function store_uploaded_file($filename, $filedata, $chroot) {
- // If chroot is empty, then we will not perform the operation.
- if (empty($chroot)) {
- return false;
- }
- // If the filename is empty, it was possibly supplied as part of the
- // upload.
- $filename = empty($filename) ? $filedata['name'] : $filename;
- // We have to take the dirname of the parent directory first, since
- // realpath just returns false if the directory doesn't already exist on
- // the filesystem.
- $uploadparent = realpath(dirname($chroot . DIRECTORY_SEPARATOR . $filename));
- $uploadfile = basename($chroot . DIRECTORY_SEPARATOR . $filename);
- // The bailout rules for directories that don't exist are complicated
- // because of having to work around realpath. If the parent directory is
- // the same as the chroot, it won't be contained. For this case, we'll
- // check to see if the chroot and the parent are the same and allow it only
- // if the sub portion of dirname is not-empty.
- if (!directory_contains($chroot, $uploadparent) &&
- !(($chroot == $uploadparent) && !empty($uploadfile))) {
- return false;
- }
-
- $target_path = $uploadparent . DIRECTORY_SEPARATOR . $uploadfile;
- if (is_array($filedata)) {
- // We've received the file as an upload, so it's been saved to a temp
- // directory. We'll move it to where it belongs.
-
- if(move_uploaded_file($filedata['tmp_name'], $target_path)) {
- return true;
- }
- } elseif ($file = @fopen($target_path, 'w')) {
- // We've received the file as data. We'll create/open the file and
- // save the data.
- @fwrite($file, $filedata);
- @fclose($file);
- return true;
- }
-
- return false;
- }
- /**#@-*/
- ?>