Drupal 7 file core (public and private StreamWrapper-s) are not supported junction points (symlinks) of Windows NTFS file system.
For example: standard is_dir() function return "true" of NTFS junction points (symlinks), but is_dir() passed throw Drupal7 file core return "false" for same folder.

P.S.: NTFS junction points (symlinks) are very useful in sates on Windows Server.

Comments

W32’s picture

Steps to reproduce error:

1. We will use Windows 7 (or any Windows Server). Create junction folder in drupal public or private files folder. Junction folder can be created by junction utility (http://technet.microsoft.com/en-us/sysinternals/bb896768) or by mklink command in cmd.exe (Windows 7 only).
Example:
junction "C:/Net/Web/root/sites/default/files/priv/images/DWall" "D:/PictersHugeLibrary"

where "C:/Net/Web/root/sites/default/files/priv" - private files Drupal folder, "C:/Net/Web/root" - Drupal installation folder, "D:/PictersHugeLibrary" - mounted volume with pictures library, "C:/Net/Web/root/sites/default/files/priv/images/DWall" - junction (symlink) folder which reflects content of "D:/PictersHugeLibrary".

2. Create php block with code:

echo var_export( is_dir("C:/Net/Web/root/sites/default/files/priv/images/DWall") );
echo var_export( is_dir("private://images/DWall") );

3. First echo return "true" (because junction is real folder in terms of Windows NTFS system). But second echo returns "false", may error exists in classes DrupalLocalStreamWrapper, DrupalStreamWrapperInterface, DrupalPublicStreamWrapper, DrupalPrivateStreamWrapper (see stream_wrappers.inc).

W32’s picture

This Bug in method DrupalLocalStreamWrapper::getLocalPath().

W32’s picture

Assigned: Unassigned » W32
Status: Active » Needs work

Looking at method DrupalLocalStreamWrapper::getLocalPath().

  /**
   * Return the local filesystem path.
   *
   * @param $uri
   *   Optional URI, supplied when doing a move or rename.
   */
  protected function getLocalPath($uri = NULL) {
    if (!isset($uri)) {
      $uri = $this->uri;
    }
    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
    $realpath = realpath($path);
    if (!$realpath) {
      // This file does not yet exist.
      $realpath = realpath(dirname($path)) . '/' . basename($path);
    }
    $directory = realpath($this->getDirectoryPath());
    if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {       // <== strpos() is error approach
      return FALSE;
    }
    return $realpath;
  }

As we can see, path to file/folder after concatenations with base folder is passed throw realpath() method. It's ok while $path is real file or folder, but if $path is symlink (junction point) than method realpath() returns not fixed $path, but REAL PATH on which symlink is refers! So, strpos() method, that checks base folder overruning validation, returns FALSE. But this FALSE is mistaken, because $realpath is really stands in drupal folder (by means of his symlink).

W32’s picture

This is a fixed method DrupalLocalStreamWrapper::getLocalPath() ( file stream_wrappers.inc ).

  /**
   * Return the local filesystem path.
   *
   * @param $uri
   *   Optional URI, supplied when doing a move or rename.
   */
  protected function getLocalPath($uri = NULL) {
    if (!isset($uri)) {
      $uri = $this->uri;
    }
    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
    $realpath = realpath($path);
    if (!$realpath) {
      // This file does not yet exist.
      $realpath = realpath(dirname($path)) . '/' . basename($path);
    }
    $directory = realpath($this->getDirectoryPath());
    if (!$realpath || !$directory) {       // <== whithout strpos() 
      return FALSE;
    }
    return $realpath;
  }

Does anybody can apply this patch to current drupal7 code ?

bfroehle’s picture

Status: Needs work » Active

The strpos call is a security check to prevent allowing paths like private://../../../etc/passwd, I believe.

There isn't a patch here, so the status is "active." See the Drupal docs on creating patches if you need help.

bfroehle’s picture

Status: Active » Closed (duplicate)

This behavior is consistent in UNIX based environments as well. For example, if I set the private files path to /Applications/MAMP/private, then run

$ cd /Applications/MAMP/private
$ mkdir folder
$ ln -s /tmp symlink

and then execute

print is_dir("/Applications/MAMP/private/folder");
print is_dir("private://folder");
print is_dir("/Applications/MAMP/private/symlink");
print is_dir("private://symlink");

The results are true, true, true, false.

I'm marking this issue as a duplicate of #1008402: Allow the use of symlinks within the files directory., which is an older issue on the same topic.

nzcodarnoc’s picture

Can we reopen this? As #1008402 is targeting D8, and the discussion here is around D7.