Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.124
diff -u -8 -p -r1.124 file.inc
--- includes/file.inc 23 Apr 2008 18:17:41 -0000 1.124
+++ includes/file.inc 27 Apr 2008 00:59:12 -0000
@@ -28,16 +28,109 @@ define('FILE_EXISTS_ERROR', 2);
*
* If you wish to add custom statuses for use by contrib modules please expand as
* binary flags and consider the first 8 bits reserved. (0,1,2,4,8,16,32,64,128)
*/
define('FILE_STATUS_TEMPORARY', 0);
define('FILE_STATUS_PERMANENT', 1);
/**
+ * Get canonicalized absolute path of a file or directory. Symbolic links are
+ * expanded and "/./", "/../", multiple "//" characters are resolved, and "\"
+ * is converted to "/".
+ *
+ * @code
+ * // Returns "/foo/bar/boo" (assuming the file exists):
+ * file_realpath('/foo//bar/./baz/..\\boo');
+ * @endcode
+ *
+ * @param $path A string containing a path to a file or directory.
+ * @return A string containing the absolute path to the file/directory,
+ * or FALSE if file/directory does not exist.
+ */
+function file_realpath($path) {
+ // Does $path include an explicit protocol/wrapper prefix "foo://" (not a
+ // Windows drive letter "C:/temp")?
+ if (preg_match('@^([a-z0-9.+-]{2,})://(.*)@i', $path, $reg)) {
+ if (strncasecmp($path, 'file://', 7) == 0) {
+ // realpath() doesn't work with "file://" prefix, so strip it
+ $path = realpath($reg[2]);
+ return $path ? 'file://' . $path : $path;
+ } else{
+ // $path uses stream wrapper.
+ // Assume the wrapper uses the usual convention for "/../", "/./" and "//",
+ // and that "/" may be used as path separator. Arguments to this function
+ // may use either "/" or "\" as path separator.
+ // Symbolic links are not supported.
+
+ // Normalize path separator
+ $wrappedPath = preg_replace('@[/\\\\]+@', '/', $reg[2]);
+ // Remove "/./" and "/../"
+ $wrappedPath = _file_remove_dot_segments($wrappedPath);
+ $path = $reg[1] . '://' . $wrappedPath;
+ return file_exists($path) ? $path : false;
+ }
+ } else {
+ return realpath($path);
+ }
+}
+
+/**
+ * Remove "/./" and "/../" from a path as described in RFC 3986, section 5.2.4.
+ * @param $path A string containing a path
+ * @return A string containing a path
+ */
+function _file_remove_dot_segments($path) {
+ $original_path = $path;
+ $output = '';
+ $j = 0;
+ while ($path) {
+ // Step A
+ if (substr($path, 0, 2) == './') {
+ $path = substr($path, 2);
+ } elseif (substr($path, 0, 3) == '../') {
+ $path = substr($path, 3);
+
+ // Step B
+ } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+ $path = '/' . substr($path, 3);
+
+ // Step C
+ } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
+ $path = '/' . substr($path, 4);
+ $i = strrpos($output, '/');
+ $output = $i === false ? '' : substr($output, 0, $i);
+
+ // Step D
+ } elseif ($path == '.' || $path == '..') {
+ $path = '';
+
+ // Step E
+ } else {
+ $i = strpos($path, '/');
+ if ($i === 0) {
+ $i = strpos($path, '/', 1);
+ }
+ if ($i === false) {
+ $i = strlen($path);
+ }
+ $output .= substr($path, 0, $i);
+ $path = substr($path, $i);
+ }
+ if ($j++ > 100) {
+ // Make sure not to be trapped in an infinite loop due to a bug in this
+ // method
+ watchdog('file system', 'Extremely long path %path, or bug in %function()', array('%path' => $original_path, '%function' => __FUNCTION__), WATCHDOG_ERROR);
+ return false;
+ }
+ }
+ return $output;
+}
+
+/**
* Create the download path to a file.
*
* @param $path A string containing the path of the file to generate URL for.
* @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.
if (strpos($path, file_directory_path() . '/') === 0) {
@@ -123,17 +216,17 @@ function file_check_directory(&$director
return FALSE;
}
}
if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
fclose($fp);
- chmod($directory . '/.htaccess', 0664);
+ @chmod($directory . '/.htaccess', 0664);
}
else {
$variables = array('%directory' => $directory, '!htaccess' => '
' . nl2br(check_plain($htaccess_lines)));
form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: !htaccess
", $variables));
watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: !htaccess
", $variables, WATCHDOG_ERROR);
}
}
@@ -174,25 +267,25 @@ function file_check_path(&$path) {
* 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 = '') {
- $check = realpath($source);
+ $check = file_realpath($source);
if ($check) {
$source = $check;
}
else {
// This file does not yet exist
- $source = realpath(dirname($source)) . '/' . basename($source);
+ $source = file_realpath(dirname($source)) . '/' . basename($source);
}
- $directory = realpath($directory);
+ $directory = file_realpath($directory);
if ($directory && strpos($source, $directory) !== 0) {
return 0;
}
return $source;
}
/**
* Copies a file to a new location. This is a powerful function that in many ways
@@ -231,30 +324,30 @@ function file_copy(&$source, $dest = 0,
if (is_object($source)) {
$file = $source;
$source = $file->filepath;
if (!$basename) {
$basename = $file->filename;
}
}
- $source = realpath($source);
+ $source = file_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 0;
}
// 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 ($source != file_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 0;
@@ -520,16 +613,17 @@ function file_save_upload($source, $vali
$extensions = '';
foreach ($user->roles as $rid => $name) {
$extensions .= ' ' . variable_get("upload_extensions_$rid",
variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
}
// Begin building file object.
$file = new stdClass();
+ // basename() strips leading non-us-ascii characters
$file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
$file->filepath = $_FILES['files']['tmp_name'][$source];
$file->filemime = $_FILES['files']['type'][$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')) {
$file->filemime = 'text/plain';
$file->filepath .= '.txt';
@@ -746,19 +840,18 @@ function file_validate_image_resolution(
* @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 0 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');
+ // Create temporary file with unique name (tempnam() does not work with stream wrappers)
+ $file = file_directory_temp() . '/file_' . getmypid() . '-' . microtime(true);
if (!$fp = fopen($file, 'wb')) {
drupal_set_message(t('The file could not be created.'), 'error');
return 0;
}
fwrite($fp, $data);
fclose($fp);
if (!file_move($file, $dest, $replace)) {