#443286: Workaround bugs in PHP and Windows by the magic of Stream Wrappers.

From: Damien Tournoud <damien@commerceguys.com>


---
 stream_wrappers.inc         |    6 +++
 stream_wrappers.windows.inc |   93 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 99 insertions(+), 0 deletions(-)
 create mode 100644 stream_wrappers.windows.inc

diff --git includes/stream_wrappers.inc includes/stream_wrappers.inc
index 82f44d7..33808ef 100644
--- includes/stream_wrappers.inc
+++ includes/stream_wrappers.inc
@@ -808,3 +808,9 @@ class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper {
     return url('system/temporary/' . $path, array('absolute' => TRUE));
   }
 }
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+  // On Windows, make sure that the file manipulation primitives are behaving
+  // in a decent way by working bugs in the PHP and Windows stack.
+  require_once(DRUPAL_ROOT . '/includes/stream_wrappers.windows.inc');
+}
diff --git includes/stream_wrappers.windows.inc includes/stream_wrappers.windows.inc
new file mode 100644
index 0000000..69581b2
--- /dev/null
+++ includes/stream_wrappers.windows.inc
@@ -0,0 +1,93 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Compatibility implementation of the file system interface for Windows.
+ */
+
+/**
+ * Compatibility implementation of the 'file' stream wrapper for Windows.
+ */
+class DrupalWindowsFileStreamWrapper extends DrupalLocalStreamWrapper {
+  protected function getLocalPath($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    if (($pos = strpos($uri, '://')) !== FALSE) {
+      $target = substr($pos + 3);
+      // Remove erroneous leading or trailing, forward-slashes and backslashes.
+      return trim($target, '\/');
+    }
+    else {
+      return $uri;
+    }
+  }
+
+  public function stream_open($uri, $mode, $options, &$opened_path) {
+    $scope = new StreamWrapperFileScope();
+    return parent::stream_open($uri, $mode, $options, &$opened_path);
+  }
+
+  public function unlink($uri) {
+    $scope = new StreamWrapperFileScope();
+    // On Windows, the "read-only" flag will prevent a file from being deleted.
+    // Force the file to be writable first.
+    chmod($this->getLocalPath($uri), 0600);
+    return parent::unlink($uri);
+  }
+
+  public function rename($from_uri, $to_uri) {
+    $scope = new StreamWrapperFileScope();
+    // On Windows, the "read-only" flag will prevent a file from being renamed.
+    // Force the file to be writable first.
+    chmod($this->getLocalPath($from_uri), 0600);
+    return parent::rename($from_uri, $to_uri);
+  }
+
+  public function dirname($uri = NULL) {
+    $scope = new StreamWrapperFileScope();
+    return parent::dirname($uri);
+  }
+
+  public function mkdir($uri, $mode, $options) {
+    $scope = new StreamWrapperFileScope();
+    return parent::mkdir($uri, $mode, $options);
+  }
+
+  public function rmdir($uri, $options) {
+    $scope = new StreamWrapperFileScope();
+    // On Windows, the "read-only" flag will prevent a directory from being
+    // renamed. Force the file to be writable first.
+    chmod($this->getLocalPath($uri), 0700);
+    return parent::rmdir($uri, $options);
+  }
+
+  public function url_stat($uri, $flags) {
+    $scope = new StreamWrapperFileScope();
+    return parent::url_stat($uri, $flags);
+  }
+
+  public function dir_opendir($uri, $options) {
+    $scope = new StreamWrapperFileScope();
+    return parent::dir_opendir($uri, $options);
+  }
+}
+
+/**
+ * Scope object: revert the 'file' stream wrapper to its default implementation.
+ */
+class StreamWrapperFileScope {
+  function __construct() {
+    stream_wrapper_restore('file');
+  }
+
+  function __destruct() {
+    stream_wrapper_unregister('file');
+    stream_wrapper_register('file', 'DrupalWindowsFileStreamWrapper');
+  }
+}
+
+stream_wrapper_unregister('file');
+stream_wrapper_register('file', 'DrupalWindowsFileStreamWrapper');
