diff options
author | Dries Buytaert <dries@buytaert.net> | 2003-12-26 23:03:21 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2003-12-26 23:03:21 +0000 |
commit | 15f289a8f0ab4cc2e1253731ca5af5987b3af0d6 (patch) | |
tree | e064d4466941c7044df068e1f07cf831ca23909f /includes | |
parent | c18984562a596fc1c9d823e5e83d5bd2d4fb761c (diff) | |
download | brdo-15f289a8f0ab4cc2e1253731ca5af5987b3af0d6.tar.gz brdo-15f289a8f0ab4cc2e1253731ca5af5987b3af0d6.tar.bz2 |
- Added file handling. Work by Kjartan.
Diffstat (limited to 'includes')
-rw-r--r-- | includes/common.inc | 1 | ||||
-rw-r--r-- | includes/file.inc | 357 |
2 files changed, 358 insertions, 0 deletions
diff --git a/includes/common.inc b/includes/common.inc index e3bbbf841..fd92d3664 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -935,6 +935,7 @@ include_once "includes/pager.inc"; include_once "includes/menu.inc"; include_once "includes/xmlrpc.inc"; include_once "includes/tablesort.inc"; +include_once "includes/file.inc"; // set error handler: set_error_handler("error_handler"); diff --git a/includes/file.inc b/includes/file.inc new file mode 100644 index 000000000..2e8cd7b44 --- /dev/null +++ b/includes/file.inc @@ -0,0 +1,357 @@ +<?php +/** @file + * $Id$ + * + * Common file handling functions + * + * @par Example: + * @code + * function module_validate($node) { + * if (!$node->file = file_save_upload('file')) { + * $error['file'] = t('File required.'); + * } + * } + * + * function module_insert($node) { + * if ($node->file) { + * file_save($node->file); + * } + * } + * @endcode + * + */ + +define('FILE_DOWNLOADS_PUBLIC', 1); +define('FILE_DOWNLOADS_PRIVATE', 2); +define('FILE_SEPARATOR', PHP_OS == 'WINNT' ? '\\' : '/'); + +/** + * Create the download path to a file. + */ +function file_create_url($path) { + switch (variable_get('file_downloads', FILE_DOWNLOADS_PRIVATE)) { + case FILE_DOWNLOADS_PUBLIC: + case FILE_DOWNLOADS_PRIVATE: + global $base_url; + if (strpos($path, variable_get('file_directory_path', 'files')) !== false) { + $path = trim(substr($path, strlen(variable_get('file_directory_path', 'files'))), '\\/'); + } + return $base_url .'/'. variable_get('file_directory_path', 'files') .'/'. str_replace('\\', '/', $path); + case FILE_DOWNLOADS_PRIVATE: + return url('system/files', 'file='. $path); + } +} + +/** + * Make sure the destination is a complete path, if it is not prepend the + * file system directory. + */ +function file_create_path($dest = 0) { + if (!$dest) { + return variable_get('file_directory_path', 'files'); + } + + $regex = (PHP_OS == 'WINNT' ? '.?:\\\\' : '/'); + if (!file_check_location($dest, variable_get('file_directory_path', 'files')) && !preg_match("|^$regex|", $dest)) { + return variable_get('file_directory_path', 'files') . FILE_SEPARATOR . trim($dest, '\\/'); + } + else { + return $dest; + } +} + +/** + * Check that directory exists and is writable. + * + * @param $directory Path to extract and verify directory for. + * @return False when directory not found, or true when directory exists. + */ +function file_check_directory(&$directory) { + $directory = rtrim($directory, '/\\'); + return is_dir($directory) && is_writable($directory); +} + +/** + * Checks path to see if it is a directory, or a dir/file. + * + * @param $path + */ +function file_check_path(&$path) { + // Check if path is a directory. + if (file_check_directory($path)) { + return ''; + } + + // Check if path is a possible dir/file. + $filename = basename($path); + $path = dirname($path); + if (file_check_directory($path)) { + return $filename; + } + + return false; +} + +/** + * Check if $source is a valid file upload. + * + * @param $source + */ +function file_check_upload($source) { + if ($_FILES["edit"]["name"][$source] && is_uploaded_file($_FILES["edit"]["tmp_name"][$source])) { + $file->name = trim(basename($_FILES["edit"]["name"][$source]), '.'); + $file->type = $_FILES["edit"]["type"][$source]; + $file->path = $_FILES["edit"]["tmp_name"][$source]; + $file->error = $_FILES["edit"]["error"][$source]; + $file->size = $_FILES["edit"]["size"][$source]; + return $file; + } +} + +/** + * Check if a file is really located inside $directory. Should be used to make + * sure a file specified is really located within the directory to prevent + * exploits. + * + * @code + * // Returns false: + * file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files'); + * @endcode + * + * @param $source A string set to the file to check. + * @param $directory A string where the file should be located. + * @return 0 for invalid path or the real path of the source. + */ +function file_check_location($source, $directory = 0) { + $source = realpath($source); + if ($directory && strpos($source, $directory) !== 0) { + return 0; + } + return $source; +} + +/** + * Saves a file to a new location. This is a powerful function that in many ways + * performs like an advanced version of copy(). + * - Checks if $source and $dest are valid and readable/writable. + * - Performs a file copy if $source is not equal to $dest. + * - If file already exists in $dest it will append a number to the end of the + * filename, but before the file extension. It will increment the number until + * it finds a filename that is not already in use. + * + * @param $source A string specifying the file location of the original file. + * This parameter will contain the resulting destination filename in case of + * success. + * @param $dest A string containing the directory $source should be copied to. + * @param $replace A boolean that when true will overwrite any existing files, + * but when false append a _X to the filename. + * @return True for success, false for failure. + */ +function file_copy(&$source, $dest = 0, $replace = 0) { + $dest = file_create_path($dest); + + $directory = $dest; + $basename = file_check_path($directory); + + // Make sure we at least have a valid directory. + if ($basename === false) { + drupal_set_message(t('file copy failed: no directory configured, or it could not be accessed.'), 'error'); + return 0; + } + + // Process a file upload object. + if (is_object($source)) { + $file = $source; + $source = $file->path; + if (!$basename) { + $basename = $file->name; + } + } + + $source = realpath($source); + if (!file_exists($source)) { + drupal_set_message(t('file copy failed: source file does not exist.'), 'error'); + return 0; + } + + // If destination file is not specified then use filename of source file. + $basename = $basename ? $basename : basename($source); + $dest = $directory . FILE_SEPARATOR . $basename; + + if (file_exists($dest) && !$replace) { + // Destination file already exists and we can't replace is so we try and + // and find a new filename. + list($name, $ext) = explode('.', $basename, 2); + $ext = $ext ? ".$ext" : ''; + $counter = 0; + do { + $dest = $directory . FILE_SEPARATOR . $name .'_'. $counter++ . $ext; + $counter++; + } while (file_exists($dest)); + } + + if (!copy($source, $dest)) { + drupal_set_message(t('file copy failed.'), 'error'); + return 0; + } + + if (is_object($file)) { + $file->name = $basename; + $file->path = $dest; + $source = $file; + } + else { + $source = $dest; + } + return 1; // Everything went ok. +} + +function file_move(&$source, $dest = 0, $replace = 0) { + $path = is_object($source) ? $source->path : $source; + if (file_copy($source, $dest, $replace)) { + if (unlink($path)) { + return 1; + } + drupal_set_message(t('removing original file failed.'), 'error'); + } + return 0; +} + +function file_delete($source) { + unlink(file_create_path($source)); +} + +/** + * Saves a file upload to a new location. The source file is validated as a + * proper upload and handled as such. + * + * @param $source A string specifying the name of the upload field to save. + * This parameter will contain the resulting destination filename in case of + * success. + * @param $dest A string containing the directory $source should be copied to, + * will use the temporary directory in case no other value is set. + * @param $dest A boolean, set to true if the destination should be replaced + * when in use, but when false append a _X to the filename. + * @return An object containing file info or 0 in case of error. + */ +function file_save_upload($source, $dest = 0, $replace = 0) { + // Make sure $source exists in $_FILES. + if ($file = file_check_upload($source)) { + if ($dest === 0) { + $dest = variable_get('file_directory_temp', ini_get('upload_tmp_dir')); + $temporary = 1; + if (file_exists($_SESSION['file_uploads'][$source]->path)) { + // If this file was uploaded by this user before replace the temporary copy. + $replace = 1; + } + } + + if (!valid_input_data($file)) { + watchdog('error', t('Possible exploit abuse: invalid data.')); + drupal_set_message(t("file upload failed: invalid data."), 'error'); + return 0; + } + + // Check for file upload errors. + switch ($file->error) { + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + drupal_set_message(t("file upload failed: incomplete upload."), 'error'); + return 0; + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + drupal_set_message(t("file upload failed: file size too big."), 'error'); + return 0; + } + + unset($_SESSION['file_uploads'][$source]); + if (file_move($file, $dest, $replace)) { + if ($temporary) { + $_SESSION['file_uploads'][$source] = $file; + } + return $file; + } + return 0; + } + else { + // In case if previews return previous file object. + if (file_exists($_SESSION['file_uploads'][$source]->path)) { + return $_SESSION['file_uploads'][$source]; + } + } + return 0; +} + +/** + * Transfer file using http to client. Pipes a file through Drupal to the + * client. + * + * @param $source File to transfer. + * @param $headers An array of http headers to send along with file. + */ +function file_transfer($source, $headers) { + ob_end_clean(); + + foreach ($headers as $header) { + header($header); + } + + $source = file_create_path($source); + + // Transfer file in 1024 byte chunks to save memory usage. + $fd = fopen($source, 'rb'); + while (!feof($fd)) { + print fgets($fd); + } + fclose($fd); + exit(); +} + +/** + * Call modules to find out if a file is accessible for a given user. + */ +function file_download() { + $file = $_GET['file']; + if (file_exists(file_create_path($file))) { + $list = module_list(); + foreach ($list as $module) { + $headers = module_invoke($module, 'file_download', $file); + if ($headers === -1) { + print theme('page', message_access()); + } + elseif (is_array($headers)) { + file_transfer($file, $headers); + } + } + } + //drupal_not_found(); +} + +/** + * Finds all files that match a given mask in a given directory. Searches + * recursively. + */ +function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0) { + $files = array(); + if (is_dir($dir) && $handle = opendir($dir)) { + while ($file = readdir($handle)) { + if (!in_array($file, $nomask)) { + if (is_dir("$dir/$file")) { + $files = array_merge($files, file_scan_directory("$dir/$file", $mask, $nomask, $callback)); + } + elseif (ereg($mask, $file)) { + $name = basename($file); + $files["$dir/$file"]->filename = "$dir/$file"; + $files["$dir/$file"]->name = substr($name, 0, strrpos($name, '.')); + if ($callback) { + $callback("$dir/$file"); + } + } + } + } + closedir($handle); + } + return $files; +} + +?> |