Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.222
diff -u -p -r1.222 file.inc
--- includes/file.inc	16 Aug 2010 21:06:53 -0000	1.222
+++ includes/file.inc	17 Aug 2010 14:53:58 -0000
@@ -943,6 +943,10 @@ function file_create_filename($basename,
   // Strip control characters (ASCII value < 32). Though these are allowed in
   // some filesystems, not many applications handle them well.
   $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    // These characters are not allowed in Windows filenames
+    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
+  }
 
   // A URI or path may already have a trailing slash or look like "public://".
   if (substr($directory, -1) == '/') {
@@ -1042,7 +1046,7 @@ function file_unmanaged_delete($path) {
     return FALSE;
   }
   if (is_file($path)) {
-    return unlink($path);
+    return drupal_unlink($path);
   }
   // Return TRUE for non-existent file, but log that nothing was actually
   // deleted, as the current state is the intended result.
@@ -1090,7 +1094,8 @@ function file_unmanaged_delete_recursive
       file_unmanaged_delete_recursive($entry_path);
     }
     $dir->close();
-    return rmdir($path);
+
+    return drupal_rmdir($path);
   }
   return file_unmanaged_delete($path);
 }
@@ -1893,6 +1898,37 @@ function drupal_chmod($uri, $mode = NULL
 }
 
 /**
+ * Deletes a file.
+ *
+ * PHP's unlink() is broken on Windows, as it can fail to remove a file
+ * when it has a read-only flag set.
+ *
+ * @param $uri
+ *   A URI or pathname.
+ * @param $context
+ *   Refer to http://php.net/manual/en/ref.stream.php
+ *
+ * @return
+ *   Boolean TRUE on success, or FALSE on failure.
+ *
+ * @see unlink()
+ * @ingroup php_wrappers
+ */
+function drupal_unlink($uri, $context = NULL) {
+  $scheme = file_uri_scheme($uri);
+  if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme))
+    && (substr(PHP_OS, 0, 3) == 'WIN')) {
+    chmod($uri, 0600);
+  }
+  if ($context) {
+    return unlink($uri, $context);
+  }
+  else {
+    return unlink($uri);
+  }
+}
+
+/**
  * Returns the absolute path of a file or directory
  *
  * PHP's realpath() does not properly support streams, so this function
@@ -1988,7 +2024,6 @@ function drupal_dirname($uri) {
  * @ingroup php_wrappers
  */
 function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
-
   if (is_null($mode)) {
     $mode = variable_get('file_chmod_directory', 0775);
   }
@@ -2002,6 +2037,37 @@ function drupal_mkdir($uri, $mode = NULL
 }
 
 /**
+ * Remove a directory.
+ *
+ * PHP's rmdir() is broken on Windows, as it can fail to remove a directory
+ * when it has a read-only flag set.
+ *
+ * @param $uri
+ *   A URI or pathname.
+ * @param $context
+ *   Refer to http://php.net/manual/en/ref.stream.php
+ *
+ * @return
+ *   Boolean TRUE on success, or FALSE on failure.
+ *
+ * @see rmdir()
+ * @ingroup php_wrappers
+ */
+function drupal_rmdir($uri, $context = NULL) {
+  $scheme = file_uri_scheme($uri);
+  if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme))
+    && (substr(PHP_OS, 0, 3) == 'WIN')) {
+    chmod($uri, 0700);
+  }
+  if ($context) {
+    return rmdir($uri, $context);
+  }
+  else {
+    return rmdir($uri);
+  }
+}
+
+/**
  * Creates a file with a unique filename in the specified directory.
  *
  * PHP's tempnam() does not return a URI like we want. This function
Index: includes/stream_wrappers.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/stream_wrappers.inc,v
retrieving revision 1.18
diff -u -p -r1.18 stream_wrappers.inc
--- includes/stream_wrappers.inc	10 Jul 2010 01:51:02 -0000	1.18
+++ includes/stream_wrappers.inc	17 Aug 2010 14:53:58 -0000
@@ -544,7 +544,7 @@ abstract class DrupalLocalStreamWrapper 
    */
   public function unlink($uri) {
     $this->uri = $uri;
-    return unlink($this->getLocalPath());
+    return drupal_unlink($this->getLocalPath());
   }
 
   /**
@@ -641,10 +641,10 @@ abstract class DrupalLocalStreamWrapper 
   public function rmdir($uri, $options) {
     $this->uri = $uri;
     if ($options & STREAM_REPORT_ERRORS) {
-      return rmdir($this->getLocalPath());
+      return drupal_rmdir($this->getLocalPath());
     }
     else {
-      return @rmdir($this->getLocalPath());
+      return @drupal_rmdir($this->getLocalPath());
     }
   }
 
Index: includes/filetransfer/ftp.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/filetransfer/ftp.inc,v
retrieving revision 1.12
diff -u -p -r1.12 ftp.inc
--- includes/filetransfer/ftp.inc	11 Jan 2010 16:25:16 -0000	1.12
+++ includes/filetransfer/ftp.inc	17 Aug 2010 14:53:58 -0000
@@ -77,7 +77,7 @@ class FileTransferFTPWrapper extends Fil
         }
       }
       closedir($dh);
-      if (!rmdir($this->connection . $directory)) {
+      if (!drupal_rmdir($this->connection . $directory)) {
         $exception = new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory));
         throw $exception;
       }
@@ -91,7 +91,7 @@ class FileTransferFTPWrapper extends Fil
   }
 
   function removeFileJailed($destination) {
-    if (!@unlink($this->connection . '/' .$destination)) {
+    if (!@drupal_unlink($this->connection . '/' .$destination)) {
       throw new FileTransferException('Cannot remove @destination', NULL, array('@destination' => $destination));
     }
   }
Index: includes/filetransfer/local.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/filetransfer/local.inc,v
retrieving revision 1.3
diff -u -p -r1.3 local.inc
--- includes/filetransfer/local.inc	30 Jan 2010 07:59:24 -0000	1.3
+++ includes/filetransfer/local.inc	17 Aug 2010 14:53:58 -0000
@@ -33,23 +33,23 @@ class FileTransferLocal extends FileTran
     }
     foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
       if ($file->isDir()) {
-        if (@!rmdir($filename)) {
+        if (@!drupal_rmdir($filename)) {
           throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $filename));
         }
       }
       elseif ($file->isFile()) {
-        if (@!unlink($filename)) {
+        if (@!drupal_unlink($filename)) {
           throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $filename));
         }
       }
     }
-    if (@!rmdir($directory)) {
+    if (@!drupal_rmdir($directory)) {
       throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $directory));
     }
   }
 
   protected function removeFileJailed($file) {
-    if (@!unlink($file)) {
+    if (@!drupal_unlink($file)) {
       throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $file));
     }
   }
Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.89
diff -u -p -r1.89 color.module
--- modules/color/color.module	30 Jul 2010 02:47:27 -0000	1.89
+++ modules/color/color.module	17 Aug 2010 14:53:58 -0000
@@ -312,10 +312,10 @@ function color_scheme_form_submit($form,
 
   // Delete old files.
   foreach (variable_get('color_' . $theme . '_files', array()) as $file) {
-    @unlink($file);
+    @drupal_unlink($file);
   }
   if (isset($file) && $file = dirname($file)) {
-    @rmdir($file);
+    @drupal_rmdir($file);
   }
 
   // Don't render the default colorscheme, use the standard theme instead.
Index: modules/locale/locale.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v
retrieving revision 1.62
diff -u -p -r1.62 locale.install
--- modules/locale/locale.install	29 Jul 2010 02:31:40 -0000	1.62
+++ modules/locale/locale.install	17 Aug 2010 14:53:58 -0000
@@ -129,7 +129,7 @@ function locale_uninstall() {
     }
     // Delete the JavaScript translations directory if empty.
     if (!file_scan_directory($locale_js_directory, '/.*/')) {
-      rmdir($locale_js_directory);
+      drupal_rmdir($locale_js_directory);
     }
   }
 
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.76
diff -u -p -r1.76 locale.test
--- modules/locale/locale.test	5 Aug 2010 23:53:38 -0000	1.76
+++ modules/locale/locale.test	17 Aug 2010 14:53:59 -0000
@@ -762,7 +762,7 @@ class LocaleImportFunctionalTest extends
     file_put_contents($name, $contents);
     $options['files[file]'] = $name;
     $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
-    unlink($name);
+    drupal_unlink($name);
   }
 
   /**
@@ -911,7 +911,7 @@ class LocaleExportFunctionalTest extends
       'langcode' => 'fr',
       'files[file]' => $name,
     ), t('Import'));
-    unlink($name);
+    drupal_unlink($name);
 
     // Get the French translations.
     $this->drupalPost('admin/config/regional/translate/export', array(
Index: modules/simpletest/simpletest.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.install,v
retrieving revision 1.35
diff -u -p -r1.35 simpletest.install
--- modules/simpletest/simpletest.install	26 Mar 2010 17:14:45 -0000	1.35
+++ modules/simpletest/simpletest.install	17 Aug 2010 14:53:59 -0000
@@ -25,7 +25,7 @@ function simpletest_uninstall() {
   foreach ($files as $file) {
     file_unmanaged_delete($file->uri);
   }
-  rmdir($path);
+  drupal_rmdir($path);
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.61
diff -u -p -r1.61 file.test
--- modules/simpletest/tests/file.test	5 Aug 2010 23:53:38 -0000	1.61
+++ modules/simpletest/tests/file.test	17 Aug 2010 14:53:59 -0000
@@ -108,7 +108,20 @@ class FileTestCase extends DrupalWebTest
     clearstatcache();
 
     // Mask out all but the last three octets.
-    $actual_mode = fileperms($filepath) & 511;
+    $actual_mode = fileperms($filepath) & 0777;
+
+    // PHP on Windows has limited support for file permissions. Usually each of
+    // "user", "group" and "other" use one octal digit (3 bits) to represent the
+    // read/write/execute bits. On Windows, chmod() ignores the "group" and 
+    // "other" bits, and fileperms() returns the "user" bits in all three
+    // positions. $expected_mode is updated to reflect this.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      // Reset the "group" and "other" bits.
+      $expected_mode = $expected_mode & 0700;
+      // Shift the "user" bits to the "group" and "other" positions also.
+      $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
+    }
+
     if (!isset($message)) {
       $message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
     }
@@ -130,7 +143,20 @@ class FileTestCase extends DrupalWebTest
     clearstatcache();
 
     // Mask out all but the last three octets.
-    $actual_mode = fileperms($directory) & 511;
+    $actual_mode = fileperms($directory) & 0777;
+
+    // PHP on Windows has limited support for file permissions. Usually each of
+    // "user", "group" and "other" use one octal digit (3 bits) to represent the
+    // read/write/execute bits. On Windows, chmod() ignores the "group" and 
+    // "other" bits, and fileperms() returns the "user" bits in all three
+    // positions. $expected_mode is updated to reflect this.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      // Reset the "group" and "other" bits.
+      $expected_mode = $expected_mode & 0700;
+      // Shift the "user" bits to the "group" and "other" positions also.
+      $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
+    }
+
     if (!isset($message)) {
       $message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
     }
@@ -413,7 +439,7 @@ class FileValidatorTest extends DrupalWe
       $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File');
       $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File');
 
-      unlink(drupal_realpath($temp_dir . '/druplicon.png'));
+      drupal_unlink(drupal_realpath($temp_dir . '/druplicon.png'));
     }
     else {
       // TODO: should check that the error is returned if no toolkit is available.
@@ -866,19 +892,26 @@ class FileDirectoryTest extends FileTest
     // Make sure directory actually exists.
     $this->assertTrue(is_dir($directory), t('Directory actually exists.'), 'File');
 
-    // Make directory read only.
-    @chmod($directory, 0444);
-    $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for a non-writeable directory.'), 'File');
+    if (substr(PHP_OS, 0, 3) != 'WIN') {
+      // PHP on Windows doesn't support any kind of useful read-only mode for
+      // directories. When executing a chmod() on a directory, PHP only sets the
+      // read-only flag, which doesn't prevent files to actually be written
+      // in the directory on any recent version of Windows.
+
+      // Make directory read only.
+      @chmod($directory, 0444);
+      $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for a non-writeable directory.'), 'File');
 
-    // Test directory permission modification.
-    $this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
+      // Test directory permission modification.
+      $this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
+    }
 
-    // Test directory permission modification actually set correct permissions.
+    // Test that the directory has the correct permissions.
     $this->assertDirectoryPermissions($directory, variable_get('file_chmod_directory', 0775));
 
     // Remove .htaccess file to then test that it gets re-created.
     $directory = file_directory_path();
-    @unlink($directory . '/.htaccess');
+    @drupal_unlink($directory . '/.htaccess');
     $this->assertFalse(is_file($directory . '/.htaccess'), t('Successfully removed the .htaccess file in the files directory.'), 'File');
     file_ensure_htaccess();
     $this->assertTrue(is_file($directory . '/.htaccess'), t('Successfully re-created the .htaccess file in the files directory.'), 'File');
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.494
diff -u -p -r1.494 system.install
--- modules/system/system.install	31 Jul 2010 12:29:31 -0000	1.494
+++ modules/system/system.install	17 Aug 2010 14:54:00 -0000
@@ -2351,10 +2351,10 @@ function system_update_7046() {
       variable_set('theme_garland_settings', $settings);
       // Remove Garland's color files since they won't match Minnelli's.
       foreach (variable_get('color_garland_files', array()) as $file) {
-        @unlink($file);
+        @drupal_unlink($file);
       }
       if (isset($file) && $file = dirname($file)) {
-        @rmdir($file);
+        @drupal_rmdir($file);
       }
       variable_del('color_garland_palette');
       variable_del('color_garland_stylesheets');
Index: modules/system/system.tar.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.tar.inc,v
retrieving revision 1.8
diff -u -p -r1.8 system.tar.inc
--- modules/system/system.tar.inc	5 Aug 2010 07:52:18 -0000	1.8
+++ modules/system/system.tar.inc	17 Aug 2010 14:54:00 -0000
@@ -210,7 +210,7 @@ class Archive_Tar // extends PEAR
         $this->_close();
         // ----- Look for a local copy to delete
         if ($this->_temp_tarname != '')
-            @unlink($this->_temp_tarname);
+            @drupal_unlink($this->_temp_tarname);
 //        $this->_PEAR();
     }
     // }}}
@@ -777,7 +777,7 @@ class Archive_Tar // extends PEAR
         // ----- Look if a local copy need to be erase
         // Note that it might be interesting to keep the url for a time : ToDo
         if ($this->_temp_tarname != '') {
-            @unlink($this->_temp_tarname);
+            @drupal_unlink($this->_temp_tarname);
             $this->_temp_tarname = '';
         }
 
@@ -793,11 +793,11 @@ class Archive_Tar // extends PEAR
         // ----- Look for a local copy
         if ($this->_temp_tarname != '') {
             // ----- Remove the local copy but not the remote tarname
-            @unlink($this->_temp_tarname);
+            @drupal_unlink($this->_temp_tarname);
             $this->_temp_tarname = '';
         } else {
             // ----- Remove the local tarname file
-            @unlink($this->_tarname);
+            @drupal_unlink($this->_tarname);
         }
         $this->_tarname = '';
 
@@ -1603,7 +1603,7 @@ class Archive_Tar // extends PEAR
             }
           } elseif ($v_header['typeflag'] == "2") {
               if (@file_exists($v_header['filename'])) {
-                  @unlink($v_header['filename']);
+                  @drupal_unlink($v_header['filename']);
               }
               if (!@symlink($v_header['link'], $v_header['filename'])) {
                   $this->_error('Unable to extract symbolic link {'
@@ -1740,7 +1740,7 @@ class Archive_Tar // extends PEAR
                 @bzclose($v_temp_tar);
             }
 
-            if (!@unlink($this->_tarname.".tmp")) {
+            if (!@drupal_unlink($this->_tarname.".tmp")) {
                 $this->_error('Error while deleting temporary file \''
 				              .$this->_tarname.'.tmp\'');
             }
