Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.128
diff -u -8 -p -r1.128 file.inc
--- includes/file.inc	14 Aug 2008 12:10:47 -0000	1.128
+++ includes/file.inc	25 Aug 2008 20:58:30 -0000
@@ -78,17 +78,24 @@ define('FILE_STATUS_PERMANENT', 1);
  */
 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) {
     $path = trim(substr($path, strlen(file_directory_path())), '\\/');
   }
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
-      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
+      if (substr(PHP_OS, 0, 3) == 'WIN') {
+        // PHP on Windows assumes that filenames are encoded in Windows-1252, but
+        // Drupal passes UTF-8-encoded filenames to filesystem functions, so
+        // non-US-ASCII characters end up mangled in the filesystem. We thus need
+        // to do the same mangling to the URLs in order for them to work.
+        $path = utf8_encode($path);
+      }
+      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace(array('%2F', '%5C'), '/', rawurlencode($path));
     case FILE_DOWNLOADS_PRIVATE:
       return url('system/files/' . $path, array('absolute' => TRUE));
   }
 }
 
 /**
  * Make sure the destination is a complete path and resides in the file system
  * directory, if it is not prepend the file system directory.
@@ -433,26 +440,32 @@ function file_unmunge_filename($filename
  * Create a full file path from a directory and filename. If a file with the
  * specified name already exists, an alternative will be used.
  *
  * @param $basename string filename
  * @param $directory string directory
  * @return
  */
 function file_create_filename($basename, $directory) {
-  $dest = $directory . '/' . $basename;
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    // These characters are not allowed in Windows filenames
+    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
+  }
+
+  $dest = strtr($directory . '/' . $basename, '\\', '/');
 
   if (file_exists($dest)) {
     // Destination file already exists, generate an alternative.
     if ($pos = strrpos($basename, '.')) {
       $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));
   }
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.788
diff -u -8 -p -r1.788 common.inc
--- includes/common.inc	21 Aug 2008 19:36:36 -0000	1.788
+++ includes/common.inc	25 Aug 2008 20:58:30 -0000
@@ -1325,30 +1325,24 @@ function url($path = NULL, $options = ar
       $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query'];
     }
     // Reassemble.
     return $path . $options['fragment'];
   }
 
   global $base_url;
   static $script;
-  static $clean_url;
 
   if (!isset($script)) {
     // On some web servers, such as IIS, we can't omit "index.php". So, we
     // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
     // Apache.
     $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : '';
   }
 
-  // Cache the clean_url variable to improve performance.
-  if (!isset($clean_url)) {
-    $clean_url = (bool)variable_get('clean_url', '0');
-  }
-
   if (!isset($options['base_url'])) {
     // The base_url might be rewritten from the language rewrite in domain mode.
     $options['base_url'] = $base_url;
   }
 
   // Preserve the original path before aliasing.
   $original_path = $path;
 
@@ -1364,17 +1358,17 @@ function url($path = NULL, $options = ar
     // Modules may alter outbound links by reference.
     custom_url_rewrite_outbound($path, $options, $original_path);
   }
 
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
   $path = drupal_urlencode($prefix . $path);
 
-  if ($clean_url) {
+  if (variable_get('clean_url', '0')) {
     // With Clean URLs.
     if ($options['query']) {
       return $base . $path . '?' . $options['query'] . $options['fragment'];
     }
     else {
       return $base . $path . $options['fragment'];
     }
   }
@@ -2326,29 +2320,28 @@ function drupal_json($var = NULL) {
  *
  * Should be used when placing arbitrary data in an URL. Note that Drupal paths
  * are urlencoded() when passed through url() and do not require urlencoding()
  * of individual components.
  *
  * Notes:
  * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
  *   in Apache where it 404s on any path containing '%2F'.
- * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
- *   URLs are used, which are interpreted as delimiters by PHP. These
+ * - When clean URLs are used, mod_rewrite unescapes %-encoded characters. These
  *   characters are double escaped so PHP will still see the encoded version.
  * - With clean URLs, Apache changes '//' to '/', so every second slash is
  *   double escaped.
  *
  * @param $text
  *   String to encode
  */
 function drupal_urlencode($text) {
   if (variable_get('clean_url', '0')) {
-    return str_replace(array('%2F', '%26', '%23', '//'),
-                       array('/', '%2526', '%2523', '/%252F'),
+    return str_replace(array('%2F', '%', '//'),
+                       array('/', '%25', '/%252F'),
                        rawurlencode($text));
   }
   else {
     return str_replace('%2F', '/', rawurlencode($text));
   }
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: modules/simpletest/tests/file.test
diff -N modules/simpletest/tests/file.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file.test	25 Aug 2008 20:58:30 -0000
@@ -0,0 +1,99 @@
+<?php
+// $Id: file.test,v 1.1 2008/08/08 08:08:08 nobody E4p $
+
+/**
+ * @file
+ * Unit tests for the Drupal File API.
+ */
+
+class FileCreateUrlTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('URL generation'),
+      'description' => t('Verify that URLs generated by file_create_url() point to the right file on the webserver'),
+      'group' => t('File API'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp('file_test');
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PUBLIC.
+   */
+  function testFileCreateUrlPublic() {
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PUBLIC);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs enabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlEnabled() {
+    variable_set('clean_url', '1');
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PRIVATE);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs disabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlDisabled() {
+    variable_set('clean_url', '0');
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PRIVATE);
+  }
+
+  /**
+   * Test file_create_url().
+   * @param $file_downloads
+   *   Download method, either FILE_DOWNLOADS_PUBLIC or FILE_DOWNLOADS_PRIVATE
+   */
+  private function _testFileCreateUrl($file_downloads) {
+    $files = array(
+      'foo.txt',
+      'dir/file',
+      'dir/subdir/file1',
+      'dir\subdir\file2',
+      ' -._~!$',
+      '\'"()*@[]',
+      '?&+%#',
+      ',;=:',
+      '€£^§½|`΄',
+      '%23%25%26%2B%2F%3F',
+      'ζψειόφο',
+    );
+
+    variable_set('file_downloads', $file_downloads);
+
+    foreach ($files as $file) {
+      // Replacement for dirname() and basename() with support for filenames containing non-US-ASCII characters
+      preg_match('@^(.*)(?:^|/|\\\\)+([^/\\\\]+)@', file_directory_path() . '/' . $file, $tmp);
+      $directory = $tmp[1];
+      $basename = $tmp[2];
+
+      file_check_directory($directory, FILE_CREATE_DIRECTORY);
+      $path = file_create_filename($basename, $directory);
+      $this->assertTrue($path, t('Full path for %file is %path', array('%file' => $file, '%path' => $path)));
+
+      $content = $path . ' ' . microtime(true);
+      $ok = file_put_contents($path, $content);
+      $this->assertTrue($ok, t('Saved file %path', array('%path' => $path)));
+
+      if ($file_downloads == FILE_DOWNLOADS_PRIVATE) {
+        // Tell file_test_file_download() in file_test.module that this file may be downloaded
+        variable_set('file_test_file_download', $path);
+      }
+
+      $url = file_create_url($path);
+      $this->assertFalse(preg_match('@/\.{1,2}(/|$)@', $url), t('No /./ or /../ segments in URL'));
+      $this->drupalGet($url);
+
+      if ($this->assertResponse(200) == 'pass') {
+        $this->assertEqual($content, $this->drupalGetContent());
+      }
+    }
+  }
+}
Index: modules/simpletest/tests/file_test.info
===================================================================
RCS file: modules/simpletest/tests/file_test.info
diff -N modules/simpletest/tests/file_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_test.info	25 Aug 2008 20:58:30 -0000
@@ -0,0 +1,8 @@
+; $Id: file_test.info,v 1.1 2008/08/08 08:08:08 nobody Exp $
+name = File API test
+description = Support module for File API tests
+package = Testing
+version = VERSION
+core = 7.x
+files[] = file_test.module
+hidden = TRUE
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: modules/simpletest/tests/file_test.module
diff -N modules/simpletest/tests/file_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_test.module	25 Aug 2008 20:58:30 -0000
@@ -0,0 +1,19 @@
+<?php
+
+// $Id: file_test.module,v 1.1 2008/08/08 08:08:08 nobody Exp $
+
+/**
+ * Implementation of hook_file_download().
+ */
+function file_test_file_download($filepath) {
+  // Check whether $filepath points to the file we just created in FileCreateUrlTestCase::_testFileCreateUrl()
+  $path = variable_get('file_test_file_download', array());
+  if (file_create_path($filepath) == $path) {
+    // Return non-empty array to tell file_download() that download is permitted
+    return array(
+      'X-Foo: Bar',
+    );
+  }
+}
+
+?>
\ No newline at end of file
