? 147310-56.patch
? 197266-2.patch
? 197266.file_validate_size.patch
? drupal_asolute_paths_0.patch
? file_create_url-10.patch
? file_create_url-2.patch
? file_create_url-3.patch
? file_create_url-5.patch
? file_create_url-6.patch
? file_create_url-7.patch
? file_create_url-8.patch
? file_create_url-9.patch
? magic_quotes_runtime-1.patch
? xml.php
? sites/chsc-drupal.dev.peytz.dk
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.799
diff -u -9 -p -r1.799 common.inc
--- includes/common.inc	20 Sep 2008 20:22:23 -0000	1.799
+++ includes/common.inc	24 Sep 2008 20:39:32 -0000
@@ -1358,32 +1358,26 @@ function url($path = NULL, $options = ar
     if ($options['query']) {
       $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;
 
   // The special path '<front>' links to the default front page.
@@ -1397,19 +1391,19 @@ function url($path = NULL, $options = ar
   if (function_exists('custom_url_rewrite_outbound')) {
     // 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'];
     }
   }
   else {
@@ -2361,31 +2355,32 @@ function drupal_json($var = NULL) {
  * Wrapper around urlencode() which avoids Apache quirks.
  *
  * 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 occurences of the
+ *   characters  % / & # +  which are interpreted as delimiters by PHP. 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'),
+    // Decoded:              %        /      &        #        +        //
+    return str_replace(array('%25',   '%2F', '%26',   '%23',   '%2B',   '//'),
+                       array('%2525', '/',   '%2526', '%2523', '%252B', '/%252F'),
                        rawurlencode($text));
   }
   else {
     return str_replace('%2F', '/', rawurlencode($text));
   }
 }
 
 /**
  * Returns a string of highly randomized bytes (over the full 8-bit range).
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.135
diff -u -9 -p -r1.135 file.inc
--- includes/file.inc	20 Sep 2008 03:49:23 -0000	1.135
+++ includes/file.inc	24 Sep 2008 20:39:32 -0000
@@ -92,19 +92,26 @@ 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.
  *
@@ -440,19 +447,24 @@ 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) {
-  $destination = $directory . '/' . $basename;
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    // These characters are not allowed in Windows filenames
+    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
+  }
+
+  $destination = strtr($directory . '/' . $basename, '\\', '/');
 
   if (file_exists($destination)) {
     // Destination file already exists, generate an alternative.
     $pos = strrpos($basename, '.');
     if ($pos !== FALSE) {
       $name = substr($basename, 0, $pos);
       $ext = substr($basename, $pos);
     }
     else {
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.5
diff -u -9 -p -r1.5 file.test
--- modules/simpletest/tests/file.test	20 Sep 2008 07:35:53 -0000	1.5
+++ modules/simpletest/tests/file.test	24 Sep 2008 20:39:32 -0000
@@ -727,9 +727,104 @@ class FileCopyTest extends FileTestCase 
     // Copy the file into same directory with renaming works.
     $new_filepath = file_copy($this->file->filepath, dirname($this->file->filepath), FILE_EXISTS_RENAME);
     $this->assertTrue($new_filepath, t('Copying into same directory works.'));
     $this->assertNotEqual($new_filepath, $this->file->filepath, t('Copied file has a new name.'));
     $this->assertTrue(file_exists($this->file->filepath), t('Original file exists after copying onto itself.'));
     $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
   }
 }
 
+/**
+ * This will test file_create_url() by making round-trips through the web server.
+ */
+class FileCreateUrlTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('URL generation'),
+      'description' => t('Tests URL generation'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * 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',
+      '0',
+      ' -._~!$',
+      '\'"()*@[]',
+      '?&+%#',
+      ',;=:',
+      '¤£^§½|`´',
+      '%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.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.1
diff -u -9 -p -r1.1 file_test.module
--- modules/simpletest/tests/file_test.module	20 Sep 2008 07:35:53 -0000	1.1
+++ modules/simpletest/tests/file_test.module	24 Sep 2008 20:39:32 -0000
@@ -44,9 +44,23 @@ function _file_test_form_submit(&$form, 
   $file = file_save_upload('file_test_upload', array('file_validate_is_image' => array()));
   if ($file) {
     $form_state['values']['file_test_upload'] = $file;
     drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->filepath)));
   }
   else {
     drupal_set_message(t('Epic upload FAIL!'), 'error');
   }
 }
+
+/**
+ * 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', FALSE);
+  if (file_create_path($filepath) === $path) {
+    // Return non-empty array to tell file_download() that download is permitted
+    return array(
+      'X-Foo: Bar',
+    );
+  }
+}
