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)) {