478 lines
15 KiB
PHP
478 lines
15 KiB
PHP
|
<?php
|
||
|
/*
|
||
|
* $Id: Win32FileSystem.php 905 2010-10-05 16:28:03Z mrook $
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* This software consists of voluntary contributions made by many individuals
|
||
|
* and is licensed under the LGPL. For more information please see
|
||
|
* <http://phing.info>.
|
||
|
*/
|
||
|
|
||
|
include_once 'phing/system/io/FileSystem.php';
|
||
|
|
||
|
/**
|
||
|
* @package phing.system.io
|
||
|
*/
|
||
|
class Win32FileSystem extends FileSystem {
|
||
|
|
||
|
protected $slash;
|
||
|
protected $altSlash;
|
||
|
protected $semicolon;
|
||
|
|
||
|
private static $driveDirCache = array();
|
||
|
|
||
|
function __construct() {
|
||
|
$this->slash = self::getSeparator();
|
||
|
$this->semicolon = self::getPathSeparator();
|
||
|
$this->altSlash = ($this->slash === '\\') ? '/' : '\\';
|
||
|
}
|
||
|
|
||
|
function isSlash($c) {
|
||
|
return ($c == '\\') || ($c == '/');
|
||
|
}
|
||
|
|
||
|
function isLetter($c) {
|
||
|
return ((ord($c) >= ord('a')) && (ord($c) <= ord('z')))
|
||
|
|| ((ord($c) >= ord('A')) && (ord($c) <= ord('Z')));
|
||
|
}
|
||
|
|
||
|
function slashify($p) {
|
||
|
if ((strlen($p) > 0) && ($p{0} != $this->slash)) {
|
||
|
return $this->slash.$p;
|
||
|
}
|
||
|
else {
|
||
|
return $p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -- Normalization and construction -- */
|
||
|
|
||
|
function getSeparator() {
|
||
|
// the ascii value of is the \
|
||
|
return chr(92);
|
||
|
}
|
||
|
|
||
|
function getPathSeparator() {
|
||
|
return ';';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A normal Win32 pathname contains no duplicate slashes, except possibly
|
||
|
* for a UNC prefix, and does not end with a slash. It may be the empty
|
||
|
* string. Normalized Win32 pathnames have the convenient property that
|
||
|
* the length of the prefix almost uniquely identifies the type of the path
|
||
|
* and whether it is absolute or relative:
|
||
|
*
|
||
|
* 0 relative to both drive and directory
|
||
|
* 1 drive-relative (begins with '\\')
|
||
|
* 2 absolute UNC (if first char is '\\'), else directory-relative (has form "z:foo")
|
||
|
* 3 absolute local pathname (begins with "z:\\")
|
||
|
*/
|
||
|
function normalizePrefix($strPath, $len, &$sb) {
|
||
|
$src = 0;
|
||
|
while (($src < $len) && $this->isSlash($strPath{$src})) {
|
||
|
$src++;
|
||
|
}
|
||
|
$c = "";
|
||
|
if (($len - $src >= 2)
|
||
|
&& $this->isLetter($c = $strPath{$src})
|
||
|
&& $strPath{$src + 1} === ':') {
|
||
|
/* Remove leading slashes if followed by drive specifier.
|
||
|
* This hack is necessary to support file URLs containing drive
|
||
|
* specifiers (e.g., "file://c:/path"). As a side effect,
|
||
|
* "/c:/path" can be used as an alternative to "c:/path". */
|
||
|
$sb .= $c;
|
||
|
$sb .= ':';
|
||
|
$src += 2;
|
||
|
}
|
||
|
else {
|
||
|
$src = 0;
|
||
|
if (($len >= 2)
|
||
|
&& $this->isSlash($strPath{0})
|
||
|
&& $this->isSlash($strPath{1})) {
|
||
|
/* UNC pathname: Retain first slash; leave src pointed at
|
||
|
* second slash so that further slashes will be collapsed
|
||
|
* into the second slash. The result will be a pathname
|
||
|
* beginning with "\\\\" followed (most likely) by a host
|
||
|
* name. */
|
||
|
$src = 1;
|
||
|
$sb.=$this->slash;
|
||
|
}
|
||
|
}
|
||
|
return $src;
|
||
|
}
|
||
|
|
||
|
/** Normalize the given pathname, whose length is len, starting at the given
|
||
|
offset; everything before this offset is already normal. */
|
||
|
protected function normalizer($strPath, $len, $offset) {
|
||
|
if ($len == 0) {
|
||
|
return $strPath;
|
||
|
}
|
||
|
if ($offset < 3) {
|
||
|
$offset = 0; //Avoid fencepost cases with UNC pathnames
|
||
|
}
|
||
|
$src = 0;
|
||
|
$slash = $this->slash;
|
||
|
$sb = "";
|
||
|
|
||
|
if ($offset == 0) {
|
||
|
// Complete normalization, including prefix
|
||
|
$src = $this->normalizePrefix($strPath, $len, $sb);
|
||
|
} else {
|
||
|
// Partial normalization
|
||
|
$src = $offset;
|
||
|
$sb .= substr($strPath, 0, $offset);
|
||
|
}
|
||
|
|
||
|
// Remove redundant slashes from the remainder of the path, forcing all
|
||
|
// slashes into the preferred slash
|
||
|
while ($src < $len) {
|
||
|
$c = $strPath{$src++};
|
||
|
if ($this->isSlash($c)) {
|
||
|
while (($src < $len) && $this->isSlash($strPath{$src})) {
|
||
|
$src++;
|
||
|
}
|
||
|
if ($src === $len) {
|
||
|
/* Check for trailing separator */
|
||
|
$sn = (int) strlen($sb);
|
||
|
if (($sn == 2) && ($sb{1} === ':')) {
|
||
|
// "z:\\"
|
||
|
$sb .= $slash;
|
||
|
break;
|
||
|
}
|
||
|
if ($sn === 0) {
|
||
|
// "\\"
|
||
|
$sb .= $slash;
|
||
|
break;
|
||
|
}
|
||
|
if (($sn === 1) && ($this->isSlash($sb{0}))) {
|
||
|
/* "\\\\" is not collapsed to "\\" because "\\\\" marks
|
||
|
the beginning of a UNC pathname. Even though it is
|
||
|
not, by itself, a valid UNC pathname, we leave it as
|
||
|
is in order to be consistent with the win32 APIs,
|
||
|
which treat this case as an invalid UNC pathname
|
||
|
rather than as an alias for the root directory of
|
||
|
the current drive. */
|
||
|
$sb .= $slash;
|
||
|
break;
|
||
|
}
|
||
|
// Path does not denote a root directory, so do not append
|
||
|
// trailing slash
|
||
|
break;
|
||
|
} else {
|
||
|
$sb .= $slash;
|
||
|
}
|
||
|
} else {
|
||
|
$sb.=$c;
|
||
|
}
|
||
|
}
|
||
|
$rv = (string) $sb;
|
||
|
return $rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check that the given pathname is normal. If not, invoke the real
|
||
|
* normalizer on the part of the pathname that requires normalization.
|
||
|
* This way we iterate through the whole pathname string only once.
|
||
|
* @param string $strPath
|
||
|
* @return string
|
||
|
*/
|
||
|
function normalize($strPath) {
|
||
|
$n = strlen($strPath);
|
||
|
$slash = $this->slash;
|
||
|
$altSlash = $this->altSlash;
|
||
|
$prev = 0;
|
||
|
for ($i = 0; $i < $n; $i++) {
|
||
|
$c = $strPath{$i};
|
||
|
if ($c === $altSlash) {
|
||
|
return $this->normalizer($strPath, $n, ($prev === $slash) ? $i - 1 : $i);
|
||
|
}
|
||
|
if (($c === $slash) && ($prev === $slash) && ($i > 1)) {
|
||
|
return $this->normalizer($strPath, $n, $i - 1);
|
||
|
}
|
||
|
if (($c === ':') && ($i > 1)) {
|
||
|
return $this->normalizer($strPath, $n, 0);
|
||
|
}
|
||
|
$prev = $c;
|
||
|
}
|
||
|
if ($prev === $slash) {
|
||
|
return $this->normalizer($strPath, $n, $n - 1);
|
||
|
}
|
||
|
return $strPath;
|
||
|
}
|
||
|
|
||
|
function prefixLength($strPath) {
|
||
|
$path = (string) $strPath;
|
||
|
$slash = (string) $this->slash;
|
||
|
$n = (int) strlen($path);
|
||
|
if ($n === 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
$c0 = $path{0};
|
||
|
$c1 = ($n > 1) ? $path{1} :
|
||
|
0;
|
||
|
if ($c0 === $slash) {
|
||
|
if ($c1 === $slash) {
|
||
|
return 2; // absolute UNC pathname "\\\\foo"
|
||
|
}
|
||
|
return 1; // drive-relative "\\foo"
|
||
|
}
|
||
|
|
||
|
if ($this->isLetter($c0) && ($c1 === ':')) {
|
||
|
if (($n > 2) && ($path{2}) === $slash) {
|
||
|
return 3; // Absolute local pathname "z:\\foo" */
|
||
|
}
|
||
|
return 2; // Directory-relative "z:foo"
|
||
|
}
|
||
|
return 0; // Completely relative
|
||
|
}
|
||
|
|
||
|
function resolve($parent, $child) {
|
||
|
$parent = (string) $parent;
|
||
|
$child = (string) $child;
|
||
|
$slash = (string) $this->slash;
|
||
|
|
||
|
$pn = (int) strlen($parent);
|
||
|
if ($pn === 0) {
|
||
|
return $child;
|
||
|
}
|
||
|
$cn = (int) strlen($child);
|
||
|
if ($cn === 0) {
|
||
|
return $parent;
|
||
|
}
|
||
|
|
||
|
$c = $child;
|
||
|
if (($cn > 1) && ($c{0} === $slash)) {
|
||
|
if ($c{1} === $slash) {
|
||
|
// drop prefix when child is a UNC pathname
|
||
|
$c = substr($c, 2);
|
||
|
}
|
||
|
else {
|
||
|
//Drop prefix when child is drive-relative */
|
||
|
$c = substr($c, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$p = $parent;
|
||
|
if ($p{$pn - 1} === $slash) {
|
||
|
$p = substr($p, 0, $pn - 1);
|
||
|
}
|
||
|
return $p.$this->slashify($c);
|
||
|
}
|
||
|
|
||
|
function getDefaultParent() {
|
||
|
return (string) ("".$this->slash);
|
||
|
}
|
||
|
|
||
|
function fromURIPath($strPath) {
|
||
|
$p = (string) $strPath;
|
||
|
if ((strlen($p) > 2) && ($p{2} === ':')) {
|
||
|
|
||
|
// "/c:/foo" --> "c:/foo"
|
||
|
$p = substr($p,1);
|
||
|
|
||
|
// "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
|
||
|
if ((strlen($p) > 3) && StringHelper::endsWith('/', $p)) {
|
||
|
$p = substr($p, 0, strlen($p) - 1);
|
||
|
}
|
||
|
} elseif ((strlen($p) > 1) && StringHelper::endsWith('/', $p)) {
|
||
|
// "/foo/" --> "/foo"
|
||
|
$p = substr($p, 0, strlen($p) - 1);
|
||
|
}
|
||
|
return (string) $p;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* -- Path operations -- */
|
||
|
|
||
|
function isAbsolute(PhingFile $f) {
|
||
|
$pl = (int) $f->getPrefixLength();
|
||
|
$p = (string) $f->getPath();
|
||
|
return ((($pl === 2) && ($p{0} === $this->slash)) || ($pl === 3) || ($pl === 1 && $p{0} === $this->slash));
|
||
|
}
|
||
|
|
||
|
/** private */
|
||
|
function _driveIndex($d) {
|
||
|
$d = (string) $d{0};
|
||
|
if ((ord($d) >= ord('a')) && (ord($d) <= ord('z'))) {
|
||
|
return ord($d) - ord('a');
|
||
|
}
|
||
|
if ((ord($d) >= ord('A')) && (ord($d) <= ord('Z'))) {
|
||
|
return ord($d) - ord('A');
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/** private */
|
||
|
function _getDriveDirectory($drive) {
|
||
|
$drive = (string) $drive{0};
|
||
|
$i = (int) $this->_driveIndex($drive);
|
||
|
if ($i < 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$s = (isset(self::$driveDirCache[$i]) ? self::$driveDirCache[$i] : null);
|
||
|
|
||
|
if ($s !== null) {
|
||
|
return $s;
|
||
|
}
|
||
|
|
||
|
$s = $this->_getDriveDirectory($i + 1);
|
||
|
self::$driveDirCache[$i] = $s;
|
||
|
return $s;
|
||
|
}
|
||
|
|
||
|
function _getUserPath() {
|
||
|
//For both compatibility and security, we must look this up every time
|
||
|
return (string) $this->normalize(Phing::getProperty("user.dir"));
|
||
|
}
|
||
|
|
||
|
function _getDrive($path) {
|
||
|
$path = (string) $path;
|
||
|
$pl = $this->prefixLength($path);
|
||
|
return ($pl === 3) ? substr($path, 0, 2) : null;
|
||
|
}
|
||
|
|
||
|
function resolveFile(PhingFile $f) {
|
||
|
$path = $f->getPath();
|
||
|
$pl = (int) $f->getPrefixLength();
|
||
|
|
||
|
if (($pl === 2) && ($path{0} === $this->slash)) {
|
||
|
return $path; // UNC
|
||
|
}
|
||
|
|
||
|
if ($pl === 3) {
|
||
|
return $path; // Absolute local
|
||
|
}
|
||
|
|
||
|
if ($pl === 0) {
|
||
|
return (string) ($this->_getUserPath().$this->slashify($path)); //Completely relative
|
||
|
}
|
||
|
|
||
|
if ($pl === 1) { // Drive-relative
|
||
|
$up = (string) $this->_getUserPath();
|
||
|
$ud = (string) $this->_getDrive($up);
|
||
|
if ($ud !== null) {
|
||
|
return (string) $ud.$path;
|
||
|
}
|
||
|
return (string) $up.$path; //User dir is a UNC path
|
||
|
}
|
||
|
|
||
|
if ($pl === 2) { // Directory-relative
|
||
|
$up = (string) $this->_getUserPath();
|
||
|
$ud = (string) $this->_getDrive($up);
|
||
|
if (($ud !== null) && StringHelper::startsWith($ud, $path)) {
|
||
|
return (string) ($up . $this->slashify(substr($path,2)));
|
||
|
}
|
||
|
$drive = (string) $path{0};
|
||
|
$dir = (string) $this->_getDriveDirectory($drive);
|
||
|
|
||
|
$np = (string) "";
|
||
|
if ($dir !== null) {
|
||
|
/* When resolving a directory-relative path that refers to a
|
||
|
drive other than the current drive, insist that the caller
|
||
|
have read permission on the result */
|
||
|
$p = (string) $drive . (':'.$dir.$this->slashify(substr($path,2)));
|
||
|
|
||
|
if (!$this->checkAccess($p, false)) {
|
||
|
// FIXME
|
||
|
// throw security error
|
||
|
die("Can't resolve path $p");
|
||
|
}
|
||
|
return $p;
|
||
|
}
|
||
|
return (string) $drive.':'.$this->slashify(substr($path,2)); //fake it
|
||
|
}
|
||
|
|
||
|
throw new Exception("Unresolvable path: " . $path);
|
||
|
}
|
||
|
|
||
|
/* -- most of the following is mapped to the functions mapped th php natives in FileSystem */
|
||
|
|
||
|
/* -- Attribute accessors -- */
|
||
|
|
||
|
function setReadOnly($f) {
|
||
|
// dunno how to do this on win
|
||
|
throw new Exception("WIN32FileSystem doesn't support read-only yet.");
|
||
|
}
|
||
|
|
||
|
/* -- Filesystem interface -- */
|
||
|
|
||
|
protected function _access($path) {
|
||
|
if (!$this->checkAccess($path, false)) {
|
||
|
throw new Exception("Can't resolve path $p");
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function _nativeListRoots() {
|
||
|
// FIXME
|
||
|
}
|
||
|
|
||
|
function listRoots() {
|
||
|
$ds = _nativeListRoots();
|
||
|
$n = 0;
|
||
|
for ($i = 0; $i < 26; $i++) {
|
||
|
if ((($ds >> $i) & 1) !== 0) {
|
||
|
if (!$this->access((string)( chr(ord('A') + $i) . ':' . $this->slash))) {
|
||
|
$ds &= ~(1 << $i);
|
||
|
} else {
|
||
|
$n++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$fs = array();
|
||
|
$j = (int) 0;
|
||
|
$slash = (string) $this->slash;
|
||
|
for ($i = 0; $i < 26; $i++) {
|
||
|
if ((($ds >> $i) & 1) !== 0) {
|
||
|
$fs[$j++] = new PhingFile(chr(ord('A') + $i) . ':' . $this->slash);
|
||
|
}
|
||
|
}
|
||
|
return $fs;
|
||
|
}
|
||
|
|
||
|
/* -- Basic infrastructure -- */
|
||
|
|
||
|
/** compares file paths lexicographically */
|
||
|
function compare(PhingFile $f1, PhingFile $f2) {
|
||
|
$f1Path = $f1->getPath();
|
||
|
$f2Path = $f2->getPath();
|
||
|
return (boolean) strcasecmp((string) $f1Path, (string) $f2Path);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* returns the contents of a directory in an array
|
||
|
*/
|
||
|
function lister($f) {
|
||
|
$dir = @opendir($f->getAbsolutePath());
|
||
|
if (!$dir) {
|
||
|
throw new Exception("Can't open directory " . $f->__toString());
|
||
|
}
|
||
|
$vv = array();
|
||
|
while (($file = @readdir($dir)) !== false) {
|
||
|
if ($file == "." || $file == "..") {
|
||
|
continue;
|
||
|
}
|
||
|
$vv[] = (string) $file;
|
||
|
}
|
||
|
@closedir($dir);
|
||
|
return $vv;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|