diff options
-rw-r--r-- | includes/file.inc | 495 | ||||
-rw-r--r-- | modules/blogapi/blogapi.module | 4 | ||||
-rw-r--r-- | modules/color/color.module | 4 | ||||
-rw-r--r-- | modules/system/system.admin.inc | 8 | ||||
-rw-r--r-- | modules/system/system.install | 8 | ||||
-rw-r--r-- | modules/upload/upload.module | 3 | ||||
-rw-r--r-- | modules/upload/upload.test | 4 | ||||
-rw-r--r-- | modules/user/user.module | 6 |
8 files changed, 302 insertions, 230 deletions
diff --git a/includes/file.inc b/includes/file.inc index abf5f69e4..2f17d6224 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -10,6 +10,20 @@ * @defgroup file File interface * @{ * Common file handling functions. + * + * Fields on the file object: + * - fid - File ID + * - uid - The {users}.uid of the user who is associated with the file. + * - filename - Name of the file with no path components. This may differ + * from the basename of the filepath if the file is renamed to avoid + * overwriting an existing file. + * - filepath - Path of the file relative to Drupal root. + * - filemime - The file's MIME type. + * - filesize - The size of the file in bytes. + * - status - A bitmapped field indicating the status of the file the least + * sigifigant bit indicates temporary (1) or permanent (0). Temporary files + * older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run. + * - timestamp - UNIX timestamp for the date the file was added to the database. */ /** @@ -29,17 +43,17 @@ define('FILE_DOWNLOADS_PUBLIC', 1); define('FILE_DOWNLOADS_PRIVATE', 2); /** - * Flag used by file_create_directory() -- create directory if not present. + * Flag used by file_check_directory() -- create directory if not present. */ define('FILE_CREATE_DIRECTORY', 1); /** - * Flag used by file_create_directory() -- file permissions may be changed. + * Flag used by file_check_directory() -- file permissions may be changed. */ define('FILE_MODIFY_PERMISSIONS', 2); /** - * Flag for dealing with existing files: Append number until filename is unique. + * Flag for dealing with existing files: Appends number until name is unique. */ define('FILE_EXISTS_RENAME', 0); @@ -77,7 +91,8 @@ define('FILE_STATUS_PERMANENT', 1); * @return A string containing a URL that can be used to download the file. */ function file_create_url($path) { - // Strip file_directory_path from $path. We only include relative paths in urls. + // Strip file_directory_path from $path. We only include relative paths in + // URLs. if (strpos($path, file_directory_path() . '/') === 0) { $path = trim(substr($path, strlen(file_directory_path())), '\\/'); } @@ -93,28 +108,30 @@ function file_create_url($path) { * Make sure the destination is a complete path and resides in the file system * directory, if it is not prepend the file system directory. * - * @param $dest A string containing the path to verify. If this value is + * @param $destination A string containing the path to verify. If this value is * omitted, Drupal's 'files' directory will be used. * @return A string containing the path to file, with file system directory * appended if necessary, or FALSE if the path is invalid (i.e. outside the * configured 'files' or temp directories). */ -function file_create_path($dest = 0) { +function file_create_path($destination = NULL) { $file_path = file_directory_path(); - if (!$dest) { + if (is_null($destination)) { return $file_path; } - // file_check_location() checks whether the destination is inside the Drupal files directory. - if (file_check_location($dest, $file_path)) { - return $dest; + // file_check_location() checks whether the destination is inside the Drupal + // files directory. + if (file_check_location($destination, $file_path)) { + return $destination; } - // check if the destination is instead inside the Drupal temporary files directory. - else if (file_check_location($dest, file_directory_temp())) { - return $dest; + // Check if the destination is instead inside the Drupal temporary files + // directory. + else if (file_check_location($destination, file_directory_temp())) { + return $destination; } // Not found, try again with prefixed directory path. - else if (file_check_location($file_path . '/' . $dest, $file_path)) { - return $file_path . '/' . $dest; + else if (file_check_location($file_path . '/' . $destination, $file_path)) { + return $file_path . '/' . $destination; } // File not found. return FALSE; @@ -133,7 +150,7 @@ function file_create_path($dest = 0) { * work, a form error will be set preventing them from saving the settings. * @return FALSE when directory not found, or TRUE when directory exists. */ -function file_check_directory(&$directory, $mode = 0, $form_item = NULL) { +function file_check_directory(&$directory, $mode = FALSE, $form_item = NULL) { $directory = rtrim($directory, '/\\'); // Check if directory exists. @@ -152,9 +169,13 @@ function file_check_directory(&$directory, $mode = 0, $form_item = NULL) { // Check to see if the directory is writable. if (!is_writable($directory)) { - if (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775)) { - form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory))); - watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR); + // If not able to modify permissions, or if able to, but chmod + // fails, return false. + if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775))) { + if ($form_item) { + form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory))); + watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR); + } return FALSE; } } @@ -176,7 +197,7 @@ function file_check_directory(&$directory, $mode = 0, $form_item = NULL) { } /** - * Checks path to see if it is a directory, or a dir/file. + * Checks path to see if it is a directory, or a directory/file. * * @param $path A string containing a file path. This will be set to the * directory's path. @@ -200,9 +221,11 @@ function file_check_path(&$path) { } /** - * 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. + * Check if a file is really located inside $directory. + * + * This should be used to make sure a file specified is really located within + * the directory to prevent exploits. Note that the file or path being checked + * does not actually need to exist yet. * * @code * // Returns FALSE: @@ -211,7 +234,8 @@ function file_check_path(&$path) { * * @param $source A string set to the file to check. * @param $directory A string where the file should be located. - * @return FALSE for invalid path or the real path of the source. + * @return FALSE if the path does not exist in the directory; + * otherwise, the real path of the source. */ function file_check_location($source, $directory = '') { $check = realpath($source); @@ -219,7 +243,7 @@ function file_check_location($source, $directory = '') { $source = $check; } else { - // This file does not yet exist + // This file does not yet exist. $source = realpath(dirname($source)) . '/' . basename($source); } $directory = realpath($directory); @@ -230,88 +254,75 @@ function file_check_location($source, $directory = '') { } /** - * Copies 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 either the call will error out, replace the - * file or rename the file based on the $replace parameter. + * Copy a file to a new location. * - * @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. - * If this value is omitted, Drupal's 'files' directory will be used. - * @param $replace Replace behavior when the destination file already exists. - * - FILE_EXISTS_REPLACE - Replace the existing file - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique + * This is a powerful function that in many ways performs like an advanced + * version of copy(). + * - Checks if $source and $destination are valid and readable/writable. + * - Checks that $source is not equal to $destination; if they are an error + * is reported. + * - If file already exists in $destination either the call will error out, + * replace the file or rename the file based on the $replace parameter. + * + * @param $source + * A string specifying the file location of the original file. + * @param $destination + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * @return True for success, FALSE for failure. + * @return + * The path to the new file, or FALSE in the event of an error. */ -function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { - $dest = file_create_path($dest); +function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + $source = realpath($source); + if (!file_exists($source)) { + drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); + return FALSE; + } - $directory = $dest; + $destination = file_create_path($destination); + $directory = $destination; $basename = file_check_path($directory); // Make sure we at least have a valid directory. if ($basename === FALSE) { - $source = is_object($source) ? $source->filepath : $source; - drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error'); - watchdog('file system', 'The selected file %file could not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => $source, '%directory' => $dest), WATCHDOG_ERROR); + drupal_set_message(t('The specified file %file could not be copied, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $destination)), 'error'); return FALSE; } - // Process a file upload object. - if (is_object($source)) { - $file = $source; - $source = $file->filepath; - if (!$basename) { - $basename = $file->filename; - } - } - - $source = realpath($source); - if (!file_exists($source)) { - drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); - return FALSE; - } - - // If the destination file is not specified then use the filename of the source file. + // If the destination file is not specified then use the filename of the + // source file. $basename = $basename ? $basename : basename($source); - $dest = $directory . '/' . $basename; - - // Make sure source and destination filenames are not the same, makes no sense - // to copy it if they are. In fact copying the file will most likely result in - // a 0 byte file. Which is bad. Real bad. - if ($source != realpath($dest)) { - if (!$dest = file_destination($dest, $replace)) { - drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error'); - return FALSE; - } - - if (!@copy($source, $dest)) { - drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error'); - return FALSE; - } + $destination = file_destination($directory . '/' . $basename, $replace); - // Give everyone read access so that FTP'd users or - // non-webserver users can see/read these files, - // and give group write permissions so group members - // can alter files uploaded by the webserver. - @chmod($dest, 0664); + if ($destination === FALSE) { + drupal_set_message(t('The specified file %file could not be copied because a file by that name already exists in the destination.', array('%file' => $source)), 'error'); + return FALSE; } - - if (isset($file) && is_object($file)) { - $file->filename = $basename; - $file->filepath = $dest; - $source = $file; + // Make sure source and destination filenames are not the same, makes no + // sense to copy it if they are. In fact copying the file will most likely + // result in a 0 byte file. Which is bad. Real bad. + if ($source == realpath($destination)) { + drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error'); + return FALSE; } - else { - $source = $dest; + if (!@copy($source, $destination)) { + drupal_set_message(t('The specified file %file could not be copied.', array('%file' => $source)), 'error'); + return FALSE; } - return TRUE; // Everything went ok. + // Give everyone read access so that FTP'd users or + // non-webserver users can see/read these files, + // and give group write permissions so group members + // can alter files uploaded by the webserver. + @chmod($destination, 0664); + + return $destination; } /** @@ -320,9 +331,9 @@ function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { * * @param $destination A string specifying the desired path. * @param $replace Replace behavior when the destination file already exists. - * - FILE_EXISTS_REPLACE - Replace the existing file + * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique + * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * @return The destination file path or FALSE if the file already exists and * FILE_EXISTS_ERROR was specified. @@ -330,6 +341,10 @@ function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { function file_destination($destination, $replace) { if (file_exists($destination)) { switch ($replace) { + case FILE_EXISTS_REPLACE: + // Do nothing here, we want to overwrite the existing file. + break; + case FILE_EXISTS_RENAME: $basename = basename($destination); $directory = dirname($destination); @@ -337,7 +352,7 @@ function file_destination($destination, $replace) { break; case FILE_EXISTS_ERROR: - drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error'); + drupal_set_message(t('The specified file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error'); return FALSE; } } @@ -345,35 +360,28 @@ function file_destination($destination, $replace) { } /** - * Moves a file to a new location. - * - Checks if $source and $dest are valid and readable/writable. - * - Performs a file move if $source is not equal to $dest. - * - If file already exists in $dest either the call will error out, replace the - * file or rename the file based on the $replace parameter. + * Move a file to a new location. * - * @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. - * If this value is omitted, Drupal's 'files' directory will be used. - * @param $replace Replace behavior when the destination file already exists. - * - FILE_EXISTS_REPLACE - Replace the existing file - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique + * @param $source + * A string specifying the file location of the original file. + * @param $destination + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * @return TRUE for success, FALSE for failure. + * @return + * The filepath of the moved file, or FALSE in the event of an error. */ -function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { - $path_original = is_object($source) ? $source->filepath : $source; - - if (file_copy($source, $dest, $replace)) { - $path_current = is_object($source) ? $source->filepath : $source; - - if ($path_original == $path_current || file_delete($path_original)) { - return TRUE; - } - drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error'); +function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + $filepath = file_copy($source, $destination, $replace); + if ($filepath == FALSE || file_delete($source) == FALSE) { + return FALSE; } - return FALSE; + return $filepath; } /** @@ -438,59 +446,81 @@ function file_unmunge_filename($filename) { * @return */ function file_create_filename($basename, $directory) { - $dest = $directory . '/' . $basename; + $destination = $directory . '/' . $basename; - if (file_exists($dest)) { + if (file_exists($destination)) { // Destination file already exists, generate an alternative. - if ($pos = strrpos($basename, '.')) { + $pos = strrpos($basename, '.'); + if ($pos !== FALSE) { $name = substr($basename, 0, $pos); $ext = substr($basename, $pos); } else { $name = $basename; + $ext = ''; } $counter = 0; do { - $dest = $directory . '/' . $name . '_' . $counter++ . $ext; - } while (file_exists($dest)); + $destination = $directory . '/' . $name . '_' . $counter++ . $ext; + } while (file_exists($destination)); } - return $dest; + return $destination; } /** * Delete a file. * - * @param $path A string containing a file path. - * @return TRUE for success, FALSE for failure. + * @param $path + * A string containing a file path. + * @return + * TRUE for success or path does not exist, or FALSE in the event of an + * error. */ function file_delete($path) { + if (is_dir($path)) { + watchdog('file', t('%path is a directory and cannot be removed using file_delete().', array('%path' => $path)), WATCHDOG_ERROR); + return FALSE; + } if (is_file($path)) { return unlink($path); } + // Return TRUE for non-existant file, but log that nothing was actually + // deleted, as the current state is the indended result. + if (!file_exists($path)) { + watchdog('file', t('The file %path was not deleted, because it does not exist.', array('%path' => $path)), WATCHDOG_NOTICE); + return TRUE; + } + // Catch all for everything else: sockets, symbolic links, etc. + return FALSE; } /** * Determine total disk space used by a single user or the whole filesystem. * * @param $uid - * An optional user id. A NULL value returns the total space used - * by all files. + * Optional. A user id, specifying NULL returns the total space used by all + * non-temporary files. + * @param $status + * Optional. File Status to return. Combine with a bitwise OR(|) to return + * multiple statuses. The default status is FILE_STATUS_PERMANENT. + * @return + * An integer containing the number of bytes used. */ -function file_space_used($uid = NULL) { - if (isset($uid)) { - return (int) db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid)); +function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { + if (!is_null($uid)) { + return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d AND status & %d', array($uid, $status))); } - return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}')); + return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE status & %d', array($status))); } /** * Saves a file upload to a new location. The source file is validated as a * proper upload and handled as such. * - * The file will be added to the files table as a temporary file. Temporary files - * are periodically cleaned. To make the file permanent file call + * The file will be added to the files table as a temporary file. Temporary + * files are periodically cleaned. To make the file permanent file call * file_set_status() to change its status. * * @param $source @@ -502,7 +532,7 @@ function file_space_used($uid = NULL) { * functions should return an array of error messages, an empty array * indicates that the file passed validation. The functions will be called in * the order specified. - * @param $dest + * @param $destination * A string containing the directory $source should be copied to. If this is * not provided or is not writable, the temporary directory will be used. * @param $replace @@ -510,25 +540,27 @@ function file_space_used($uid = NULL) { * destination directory should overwritten. A false value will generate a * new, unique filename in the destination directory. * @return - * An object containing the file information, or FALSE in the event of an error. + * An object containing the file information, or FALSE in the event of an + * error. */ -function file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME) { +function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) { global $user; static $upload_cache; - // Add in our check of the the file name length. - $validators['file_validate_name_length'] = array(); - // Return cached objects without processing since the file will have // already been processed and the paths in _FILES will be invalid. if (isset($upload_cache[$source])) { return $upload_cache[$source]; } + // Add in our check of the the file name length. + $validators['file_validate_name_length'] = array(); + + // If a file was uploaded, process it. if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source])) { - // Check for file upload errors and return FALSE if a - // lower level system error occurred. + // Check for file upload errors and return FALSE if a lower level system + // error occurred. switch ($_FILES['files']['error'][$source]) { // @see http://php.net/manual/en/features.file-upload.errors.php case UPLOAD_ERR_OK: @@ -560,9 +592,12 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac // Begin building file object. $file = new stdClass(); + $file->uid = $user->uid; + $file->status = FILE_STATUS_TEMPORARY; $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions); $file->filepath = $_FILES['files']['tmp_name'][$source]; $file->filemime = file_get_mimetype($file->filename); + $file->filesize = $_FILES['files']['size'][$source]; // Rename potentially executable files, to help prevent exploits. if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { @@ -573,26 +608,21 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac // If the destination is not provided, or is not writable, then use the // temporary directory. - if (empty($dest) || file_check_path($dest) === FALSE) { - $dest = file_directory_temp(); + if (empty($destination) || file_check_path($destination) === FALSE) { + $destination = file_directory_temp(); } $file->source = $source; - $file->destination = file_destination(file_create_path($dest . '/' . $file->filename), $replace); - $file->filesize = $_FILES['files']['size'][$source]; + $file->destination = file_destination(file_create_path($destination . '/' . $file->filename), $replace); - // Call the validation functions. - $errors = array(); - foreach ($validators as $function => $args) { - array_unshift($args, $file); - $errors = array_merge($errors, call_user_func_array($function, $args)); - } + // Call the validation functions specified by this function's caller. + $errors = file_validate($file, $validators); - // Check for validation errors. + // Check for errors. if (!empty($errors)) { - $message = t('The selected file %name could not be uploaded.', array('%name' => $file->filename)); + $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename)); if (count($errors) > 1) { - $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>'; + $message .= theme('item_list', $errors); } else { $message .= ' ' . array_pop($errors); @@ -601,8 +631,9 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac return FALSE; } - // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory. - // This overcomes open_basedir restrictions for future file operations. + // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary + // directory. This overcomes open_basedir restrictions for future file + // operations. $file->filepath = $file->destination; if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) { form_set_error($source, t('File upload error. Could not move uploaded file.')); @@ -611,7 +642,6 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac } // If we made it this far it's safe to record this file in the database. - $file->uid = $user->uid; $file->status = FILE_STATUS_TEMPORARY; $file->timestamp = $_SERVER['REQUEST_TIME']; drupal_write_record('files', $file); @@ -619,10 +649,38 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac // Add file to the cache. $upload_cache[$source] = $file; return $file; + } return FALSE; } + +/** + * Check that a file meets the criteria specified by the validators. + * + * @param $file + * A Drupal file object. + * @param $validators + * An optional, associative array of callback functions used to validate the + * file. The keys are function names and the values arrays of callback + * parameters which will be passed in after the user and file objects. The + * functions should return an array of error messages, an empty array + * indicates that the file passed validation. The functions will be called in + * the order specified. + * @return + * An array contaning validation error messages. + */ +function file_validate(&$file, $validators = array()) { + // Call the validation functions specified by this function's caller. + $errors = array(); + foreach ($validators as $function => $args) { + array_unshift($args, $file); + $errors = array_merge($errors, call_user_func_array($function, $args)); + } + + return $errors; +} + /** * Check for files with names longer than we can store in the database. * @@ -634,34 +692,34 @@ function file_save_upload($source, $validators = array(), $dest = FALSE, $replac function file_validate_name_length($file) { $errors = array(); + if (empty($file->filename)) { + $errors[] = t("The file's name is empty. Please give a name to the file."); + } if (strlen($file->filename) > 255) { - $errors[] = t('Its name exceeds the 255 characters limit. Please rename the file and try again.'); + $errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again."); } return $errors; } /** - * Check that the filename ends with an allowed extension. This check is not - * enforced for the user #1. + * Check that the filename ends with an allowed extension. * * @param $file * A Drupal file object. * @param $extensions * A string with a space separated * @return - * An array. If the file extension is not allowed, it will contain an error message. + * An array. If the file extension is not allowed, it will contain an error + * message. */ function file_validate_extensions($file, $extensions) { global $user; $errors = array(); - // Bypass validation for uid = 1. - if ($user->uid != 1) { - $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i'; - if (!preg_match($regex, $file->filename)) { - $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)); - } + $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i'; + if (!preg_match($regex, $file->filename)) { + $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)); } return $errors; } @@ -676,10 +734,11 @@ function file_validate_extensions($file, $extensions) { * An integer specifying the maximum file size in bytes. Zero indicates that * no limit should be enforced. * @param $$user_limit - * An integer specifying the maximum number of bytes the user is allowed. Zero - * indicates that no limit should be enforced. + * An integer specifying the maximum number of bytes the user is allowed. + * Zero indicates that no limit should be enforced. * @return - * An array. If the file size exceeds limits, it will contain an error message. + * An array. If the file size exceeds limits, it will contain an error + * message. */ function file_validate_size($file, $file_limit = 0, $user_limit = 0) { global $user; @@ -724,15 +783,16 @@ function file_validate_is_image(&$file) { * maximum and minimum dimensions. Non-image files will be ignored. * * @param $file - * A Drupal file object. This function may resize the file affecting its size. + * A Drupal file object. This function may resize the file affecting its + * size. * @param $maximum_dimensions * An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If * an image toolkit is installed the image will be resized down to these * dimensions. A value of 0 indicates no restriction on size, so resizing * will be attempted. * @param $minimum_dimensions - * An optional string in the form WIDTHxHEIGHT. This will check that the image - * meets a minimum size. A value of 0 indicates no restriction. + * An optional string in the form WIDTHxHEIGHT. This will check that the + * image meets a minimum size. A value of 0 indicates no restriction. * @return * An array. If the file is an image and did not meet the requirements, it * will contain an error message. @@ -776,43 +836,50 @@ function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimu /** * Save a string to the specified destination. * - * @param $data A string containing the contents of the file. - * @param $dest A string containing the destination location. - * @param $replace Replace behavior when the destination file already exists. - * - FILE_EXISTS_REPLACE - Replace the existing file - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique + * @param $data + * A string containing the contents of the file. + * @param $destination + * A string containing the destination location. If no value is provided + * then a randomly name will be generated and the file saved in Drupal's + * files directory. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE - Replace the existing file. + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is + * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return A string containing the resulting filename or FALSE on error + * @return + * A string with the path of the resulting file, or FALSE on error. */ -function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) { - $temp = file_directory_temp(); - // On Windows, tempnam() requires an absolute path, so we use realpath(). - $file = tempnam(realpath($temp), 'file'); - if (!$fp = fopen($file, 'wb')) { +function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + // Write the data to a temporary file. + $temp_name = tempnam(file_directory_temp(), 'file'); + if (file_put_contents($temp_name, $data) === FALSE) { drupal_set_message(t('The file could not be created.'), 'error'); return FALSE; } - fwrite($fp, $data); - fclose($fp); - if (!file_move($file, $dest, $replace)) { - return FALSE; - } - - return $file; + // Move the file to its final destination. + return file_move($temp_name, $destination, $replace); } /** * Set the status of a file. * - * @param file A Drupal file object - * @param status A status value to set the file to. - * @return FALSE on failure, TRUE on success and $file->status will contain the - * status. + * @param $file + * A Drupal file object. + * @param $status + * A status value to set the file to. + * - FILE_STATUS_TEMPORARY - A temporary file that Drupal's garbage + * collection will remove. + * - FILE_STATUS_PERMANENT - A permanent file that Drupal's garbage + * collection will not remove. + * @return + * File object if the change is successful, or FALSE in the event of an + * error. */ -function file_set_status(&$file, $status) { - if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) { +function file_set_status($file, $status = FILE_STATUS_PERMANENT) { + if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', array($status, $file->fid))) { $file->status = $status; return TRUE; } @@ -820,11 +887,13 @@ function file_set_status(&$file, $status) { } /** - * Transfer file using http to client. Pipes a file through Drupal to the + * 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. + * @param $source + * String specifying the file path to transfer. + * @param $headers + * An array of HTTP headers to send along with file. */ function file_transfer($source, $headers) { ob_end_clean(); @@ -853,6 +922,8 @@ function file_transfer($source, $headers) { } /** + * Menu handler private file transfers. + * * Call modules that implement hook_file_download() to find out if a file is * accessible and what headers it should be transferred with. If a module * returns -1 drupal_access_denied() will be returned. If one or more modules @@ -870,6 +941,7 @@ function file_download() { } if (file_exists(file_create_path($filepath))) { + // Let other modules provide headers and controls access to the file. $headers = module_invoke_all('file_download', $filepath); if (in_array(-1, $headers)) { return drupal_access_denied(); @@ -907,7 +979,8 @@ function file_download() { * @param $min_depth * Minimum depth of directories to return files from. * @param $depth - * Current depth of recursion. This parameter is only used internally and should not be passed. + * Current depth of recursion. This parameter is only used internally and + * should not be passed. * * @return * An associative array (keyed on the provided key) of objects with @@ -926,7 +999,8 @@ function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $ca $files = array_merge(file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files); } elseif ($depth >= $min_depth && ereg($mask, $file)) { - // Always use this match over anything already set in $files with the same $$key. + // Always use this match over anything already set in $files with the + // same $$key. $filename = "$dir/$file"; $basename = basename($file); $name = substr($basename, 0, strrpos($basename, '.')); @@ -965,13 +1039,11 @@ function file_directory_temp() { // Operating system specific dirs. if (substr(PHP_OS, 0, 3) == 'WIN') { - $directories[] = 'c:\\windows\\temp'; - $directories[] = 'c:\\winnt\\temp'; - $path_delimiter = '\\'; + $directories[] = 'c:/windows/temp'; + $directories[] = 'c:/winnt/temp'; } else { $directories[] = '/tmp'; - $path_delimiter = '/'; } foreach ($directories as $directory) { @@ -980,8 +1052,8 @@ function file_directory_temp() { } } - // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp'; - $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter . 'tmp'; + // if a directory has been found, use it, otherwise default to 'files/tmp' + $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . '/tmp'; variable_set('file_directory_temp', $temporary_directory); } @@ -1001,7 +1073,8 @@ function file_directory_path() { * Determine the maximum file upload size by querying the PHP settings. * * @return - * A file size limit in bytes based on the PHP upload_max_filesize and post_max_size + * A file size limit in bytes based on the PHP upload_max_filesize and + * post_max_size */ function file_upload_max_size() { static $max_size = -1; diff --git a/modules/blogapi/blogapi.module b/modules/blogapi/blogapi.module index e14c1e689..70938c0f1 100644 --- a/modules/blogapi/blogapi.module +++ b/modules/blogapi/blogapi.module @@ -385,12 +385,12 @@ function blogapi_metaweblog_new_media_object($blogid, $username, $password, $fil return blogapi_error(t('No file sent.')); } - if (!$file = file_save_data($data, $name)) { + if (!$filepath = file_save_data($data, $name)) { return blogapi_error(t('Error storing file.')); } // Return the successful result. - return array('url' => file_create_url($file), 'struct'); + return array('url' => file_create_url($filepath), 'struct'); } /** * Blogging API callback. Returns a list of the taxonomy terms that can be diff --git a/modules/color/color.module b/modules/color/color.module index 0f1d41fe3..c7dbc35c5 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -435,8 +435,8 @@ function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) { * Save the rewritten stylesheet to disk. */ function _color_save_stylesheet($file, $style, &$paths) { - file_save_data($style, $file, FILE_EXISTS_REPLACE); - $paths['files'][] = $file; + $filepath = file_save_data($style, $file, FILE_EXISTS_REPLACE); + $paths['files'][] = $filepath; // Set standard file permissions for webserver-generated files. @chmod($file, 0664); diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index c3f7e05b2..1cde4be2b 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -338,9 +338,9 @@ function system_theme_settings(&$form_state, $key = '') { // The image was saved using file_save_upload() and was added to the // files table as a temporary file. We'll make a copy and let the garbage // collector delete the original upload. - if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) { + if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) { $_POST['default_logo'] = 0; - $_POST['logo_path'] = $file->filepath; + $_POST['logo_path'] = $filepath; $_POST['toggle_logo'] = 1; } } @@ -353,9 +353,9 @@ function system_theme_settings(&$form_state, $key = '') { // The image was saved using file_save_upload() and was added to the // files table as a temporary file. We'll make a copy and let the garbage // collector delete the original upload. - if (file_copy($file, $filename)) { + if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) { $_POST['default_favicon'] = 0; - $_POST['favicon_path'] = $file->filepath; + $_POST['favicon_path'] = $filepath; $_POST['toggle_favicon'] = 1; } } diff --git a/modules/system/system.install b/modules/system/system.install index e133d55ec..af2ceab33 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -629,7 +629,7 @@ function system_schema() { 'description' => t('Stores information for uploaded files.'), 'fields' => array( 'fid' => array( - 'description' => t('Primary Key: Unique files ID.'), + 'description' => t('File ID.'), 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, @@ -642,7 +642,7 @@ function system_schema() { 'default' => 0, ), 'filename' => array( - 'description' => t('Name of the file.'), + 'description' => t('Name of the file with no path components. This may differ from the basename of the filepath if the file is renamed to avoid overwriting an existing file.'), 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, @@ -656,7 +656,7 @@ function system_schema() { 'default' => '', ), 'filemime' => array( - 'description' => t('The file MIME type.'), + 'description' => t("The file's MIME type."), 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, @@ -670,7 +670,7 @@ function system_schema() { 'default' => 0, ), 'status' => array( - 'description' => t('A flag indicating whether file is temporary (1) or permanent (0).'), + 'description' => t('A bitmapped field indicating the status of the file the least sigifigant bit indicates temporary (1) or permanent (0). Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.'), 'type' => 'int', 'not null' => TRUE, 'default' => 0, diff --git a/modules/upload/upload.module b/modules/upload/upload.module index bb71adb00..747bacb85 100644 --- a/modules/upload/upload.module +++ b/modules/upload/upload.module @@ -420,13 +420,12 @@ function upload_save(&$node) { // Create a new revision, or associate a new file needed. if (!empty($node->old_vid) || isset($_SESSION['upload_files'][$fid])) { db_query("INSERT INTO {upload} (fid, nid, vid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $file->fid, $node->nid, $node->vid, $file->list, $file->description, $file->weight); - file_set_status($file, FILE_STATUS_PERMANENT); } // Update existing revision. else { db_query("UPDATE {upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->weight, $file->fid, $node->vid); - file_set_status($file, FILE_STATUS_PERMANENT); } + file_set_status($file, FILE_STATUS_PERMANENT); } // Empty the session storage after save. We use this variable to track files // that haven't been related to the node yet. diff --git a/modules/upload/upload.test b/modules/upload/upload.test index 51f9cc81e..842883b54 100644 --- a/modules/upload/upload.test +++ b/modules/upload/upload.test @@ -106,7 +106,7 @@ class UploadTestCase extends DrupalWebTestCase { // Attempt to upload .txt file when .test is only extension allowed. $this->uploadFile($node, $files[0], FALSE); - $this->assertRaw(t('The selected file %name could not be uploaded. Only files with the following extensions are allowed: %files-allowed.', array('%name' => basename($files[0]), '%files-allowed' => $settings['upload_extensions'])), 'File '. $files[0] . ' was not allowed to be uploaded'); + $this->assertRaw(t('The specified file %name could not be uploaded. Only files with the following extensions are allowed: %files-allowed.', array('%name' => basename($files[0]), '%files-allowed' => $settings['upload_extensions'])), 'File '. $files[0] . ' was not allowed to be uploaded'); // Attempt to upload .test file when .test is only extension allowed. $this->uploadFile($node, $files[1]); @@ -143,7 +143,7 @@ class UploadTestCase extends DrupalWebTestCase { $filename = basename($file); $filesize = format_size($info['size']); $maxsize = format_size(parse_size(($settings['upload_uploadsize'] * 1024) . 'KB')); // Won't parse decimals. - $this->assertRaw(t('The selected file %name could not be uploaded. The file is %filesize exceeding the maximum file size of %maxsize.', array('%name' => $filename, '%filesize' => $filesize, '%maxsize' => $maxsize)), t('File upload was blocked since it was larger than maxsize.')); + $this->assertRaw(t('The specified file %name could not be uploaded. The file is %filesize exceeding the maximum file size of %maxsize.', array('%name' => $filename, '%filesize' => $filesize, '%maxsize' => $maxsize)), t('File upload was blocked since it was larger than maxsize.')); } function setUploadSettings($settings, $rid = NULL) { diff --git a/modules/user/user.module b/modules/user/user.module index 40c5d9588..50cd6695d 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -414,9 +414,9 @@ function user_validate_picture(&$form, &$form_state) { // files table as a temporary file. We'll make a copy and let the garbage // collector delete the original upload. $info = image_get_info($file->filepath); - $destination = variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension']; - if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) { - $form_state['values']['picture'] = $file->filepath; + $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension']); + if ($filepath = file_copy($file->filepath, $destination, FILE_EXISTS_REPLACE)) { + $form_state['values']['picture'] = $filepath; } else { form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures')))); |