From 0e48a0f54775657e34025580d9b57ef1d2b43149 Mon Sep 17 00:00:00 2001
From: boombatower <jimmy@boombatower.com>
Date: Fri, 18 Oct 2013 06:56:51 +0000
Subject: Issue #2114885 by boombatower: Modify drupal_move_uploaded_file() to
 check is_uploaded_file(), copy(), and unlink() the file to properly support
 stream wrappers.

---
 core/includes/file.inc | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/core/includes/file.inc b/core/includes/file.inc
index 6065dd4..90f6b4f 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1046,20 +1046,34 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) {
  * @ingroup php_wrappers
  */
 function drupal_move_uploaded_file($filename, $uri) {
-  $result = @move_uploaded_file($filename, $uri);
-  // PHP's move_uploaded_file() does not properly support streams if safe_mode
-  // or open_basedir are enabled so if the move failed, try finding a real path
-  // and retry the move operation.
-  if (!$result) {
+  if (ini_get('php.safe_mode') || ini_get('php.open_basedir')) {
+    // PHP's move_uploaded_file() does not properly support streams if safe_mode
+    // or open_basedir are enabled so if the move failed, try finding a real path
+    // and retry the move operation.
     if ($realpath = drupal_realpath($uri)) {
-      $result = move_uploaded_file($filename, $realpath);
-    }
-    else {
-      $result = move_uploaded_file($filename, $uri);
+      $uri = $realpath;
     }
   }
 
-  return $result;
+  // move_uploaded_file() does not support moving a file between two different
+  // stream wrappers. Other file handling functions like rename() have the same
+  // problem, but copy() works since it explicitly sends the contents to the
+  // new source. As such move_uploaded_file() is replaced by is_uploaded_file(),
+  // copy(), and unlink() the old file. Doing so also works around the
+  // open_basedir limitations of move_uploaded_file() while maintaining the
+  // functionality since copy is used internally.
+  //
+  // This also supports upload proxying during which the file may be remotely
+  // located and referenced by a stream wrapper. In that case $filename may be
+  // something like remote://... and $uri public://... which may end up on the
+  // same remote filesystem, but PHP does not make the distinction. Of course,
+  // this also means the file can be moved between two different file systems.
+  if (is_uploaded_file($filename)) {
+    // Attempt rename() which is less expensive if the origin and destination
+    // use the same stream wrapper, otherwise perform copy() and unlink().
+    return @rename($filename, $uri) || (copy($filename, $uri) && unlink($filename));
+  }
+  return FALSE;
 }
 
 /**
-- 
1.8.1.2

