FileUtils.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. /*
  3. * $Id: FileUtils.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. include_once 'phing/system/lang/Character.php';
  22. include_once 'phing/util/StringHelper.php';
  23. include_once 'phing/system/io/BufferedReader.php';
  24. include_once 'phing/system/io/BufferedWriter.php';
  25. include_once 'phing/filters/util/ChainReaderHelper.php';
  26. include_once 'phing/system/io/PhingFile.php';
  27. /**
  28. * File utility class.
  29. * - handles os independent stuff etc
  30. * - mapper stuff
  31. * - filter stuff
  32. *
  33. * @package phing.util
  34. * @version $Revision: 905 $
  35. */
  36. class FileUtils {
  37. /**
  38. * Returns a new Reader with filterchains applied. If filterchains are empty,
  39. * simply returns passed reader.
  40. *
  41. * @param Reader $in Reader to modify (if appropriate).
  42. * @param array &$filterChains filter chains to apply.
  43. * @param Project $project
  44. * @return Reader Assembled Reader (w/ filter chains).
  45. */
  46. public static function getChainedReader(Reader $in, &$filterChains, Project $project) {
  47. if (!empty($filterChains)) {
  48. $crh = new ChainReaderHelper();
  49. $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
  50. $crh->setPrimaryReader($in);
  51. $crh->setFilterChains($filterChains);
  52. $crh->setProject($project);
  53. $rdr = $crh->getAssembledReader();
  54. return $rdr;
  55. } else {
  56. return $in;
  57. }
  58. }
  59. /**
  60. * Copies a file using filter chains.
  61. *
  62. * @param PhingFile $sourceFile
  63. * @param PhingFile $destFile
  64. * @param boolean $overwrite
  65. * @param boolean $preserveLastModified
  66. * @param array $filterChains
  67. * @param Project $project
  68. * @param integer $mode
  69. * @return void
  70. */
  71. function copyFile(PhingFile $sourceFile, PhingFile $destFile, $overwrite = false, $preserveLastModified = true, &$filterChains = null, Project $project, $mode = 0755) {
  72. if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
  73. if ($destFile->exists() && $destFile->isFile()) {
  74. $destFile->delete();
  75. }
  76. // ensure that parent dir of dest file exists!
  77. $parent = $destFile->getParentFile();
  78. if ($parent !== null && !$parent->exists()) {
  79. $parent->mkdirs($mode);
  80. }
  81. if ((is_array($filterChains)) && (!empty($filterChains))) {
  82. $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
  83. $out = new BufferedWriter(new FileWriter($destFile));
  84. // New read() methods returns a big buffer.
  85. while(-1 !== ($buffer = $in->read())) { // -1 indicates EOF
  86. $out->write($buffer);
  87. }
  88. if ( $in !== null )
  89. $in->close();
  90. if ( $out !== null )
  91. $out->close();
  92. $destFile->setMode($sourceFile->getMode());
  93. } else {
  94. // simple copy (no filtering)
  95. $sourceFile->copyTo($destFile);
  96. }
  97. if ($preserveLastModified) {
  98. $destFile->setLastModified($sourceFile->lastModified());
  99. }
  100. }
  101. }
  102. /**
  103. * Interpret the filename as a file relative to the given file -
  104. * unless the filename already represents an absolute filename.
  105. *
  106. * @param $file the "reference" file for relative paths. This
  107. * instance must be an absolute file and must not contain
  108. * ./ or ../ sequences (same for \ instead of /).
  109. * @param $filename a file name
  110. *
  111. * @return PhingFile A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
  112. * and uses the correct separator for the current platform.
  113. */
  114. function resolveFile($file, $filename) {
  115. // remove this and use the static class constant File::seperator
  116. // as soon as ZE2 is ready
  117. $fs = FileSystem::getFileSystem();
  118. $filename = str_replace('/', $fs->getSeparator(), str_replace('\\', $fs->getSeparator(), $filename));
  119. // deal with absolute files
  120. if (StringHelper::startsWith($fs->getSeparator(), $filename) ||
  121. (strlen($filename) >= 2 && Character::isLetter($filename{0}) && $filename{1} === ':')) {
  122. return new PhingFile($this->normalize($filename));
  123. }
  124. if (strlen($filename) >= 2 && Character::isLetter($filename{0}) && $filename{1} === ':') {
  125. return new PhingFile($this->normalize($filename));
  126. }
  127. $helpFile = new PhingFile($file->getAbsolutePath());
  128. $tok = strtok($filename, $fs->getSeparator());
  129. while ($tok !== false) {
  130. $part = $tok;
  131. if ($part === '..') {
  132. $parentFile = $helpFile->getParent();
  133. if ($parentFile === null) {
  134. $msg = "The file or path you specified ($filename) is invalid relative to ".$file->getPath();
  135. throw new IOException($msg);
  136. }
  137. $helpFile = new PhingFile($parentFile);
  138. } else if ($part === '.') {
  139. // Do nothing here
  140. } else {
  141. $helpFile = new PhingFile($helpFile, $part);
  142. }
  143. $tok = strtok($fs->getSeparator());
  144. }
  145. return new PhingFile($helpFile->getAbsolutePath());
  146. }
  147. /**
  148. * Normalize the given absolute path.
  149. *
  150. * This includes:
  151. * - Uppercase the drive letter if there is one.
  152. * - Remove redundant slashes after the drive spec.
  153. * - resolve all ./, .\, ../ and ..\ sequences.
  154. * - DOS style paths that start with a drive letter will have
  155. * \ as the separator.
  156. * @param string $path Path to normalize.
  157. * @return string
  158. */
  159. function normalize($path) {
  160. $path = (string) $path;
  161. $orig = $path;
  162. $path = str_replace('/', DIRECTORY_SEPARATOR, str_replace('\\', DIRECTORY_SEPARATOR, $path));
  163. // make sure we are dealing with an absolute path
  164. if (!StringHelper::startsWith(DIRECTORY_SEPARATOR, $path)
  165. && !(strlen($path) >= 2 && Character::isLetter($path{0}) && $path{1} === ':')) {
  166. throw new IOException("$path is not an absolute path");
  167. }
  168. $dosWithDrive = false;
  169. $root = null;
  170. // Eliminate consecutive slashes after the drive spec
  171. if (strlen($path) >= 2 && Character::isLetter($path{0}) && $path{1} === ':') {
  172. $dosWithDrive = true;
  173. $ca = str_replace('/', '\\', $path);
  174. $ca = StringHelper::toCharArray($ca);
  175. $path = strtoupper($ca[0]).':';
  176. for ($i=2, $_i=count($ca); $i < $_i; $i++) {
  177. if (($ca[$i] !== '\\') ||
  178. ($ca[$i] === '\\' && $ca[$i - 1] !== '\\')
  179. ) {
  180. $path .= $ca[$i];
  181. }
  182. }
  183. $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
  184. if (strlen($path) == 2) {
  185. $root = $path;
  186. $path = "";
  187. } else {
  188. $root = substr($path, 0, 3);
  189. $path = substr($path, 3);
  190. }
  191. } else {
  192. if (strlen($path) == 1) {
  193. $root = DIRECTORY_SEPARATOR;
  194. $path = "";
  195. } else if ($path{1} == DIRECTORY_SEPARATOR) {
  196. // UNC drive
  197. $root = DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR;
  198. $path = substr($path, 2);
  199. }
  200. else {
  201. $root = DIRECTORY_SEPARATOR;
  202. $path = substr($path, 1);
  203. }
  204. }
  205. $s = array();
  206. array_push($s, $root);
  207. $tok = strtok($path, DIRECTORY_SEPARATOR);
  208. while ($tok !== false) {
  209. $thisToken = $tok;
  210. if ("." === $thisToken) {
  211. $tok = strtok(DIRECTORY_SEPARATOR);
  212. continue;
  213. } elseif (".." === $thisToken) {
  214. if (count($s) < 2) {
  215. // using '..' in path that is too short
  216. throw new IOException("Cannot resolve path: $orig");
  217. } else {
  218. array_pop($s);
  219. }
  220. } else { // plain component
  221. array_push($s, $thisToken);
  222. }
  223. $tok = strtok(DIRECTORY_SEPARATOR);
  224. }
  225. $sb = "";
  226. for ($i=0,$_i=count($s); $i < $_i; $i++) {
  227. if ($i > 1) {
  228. // not before the filesystem root and not after it, since root
  229. // already contains one
  230. $sb .= DIRECTORY_SEPARATOR;
  231. }
  232. $sb .= (string) $s[$i];
  233. }
  234. $path = (string) $sb;
  235. if ($dosWithDrive === true) {
  236. $path = str_replace('/', '\\', $path);
  237. }
  238. return $path;
  239. }
  240. /**
  241. * @return boolean Whether contents of two files is the same.
  242. */
  243. public function contentEquals(PhingFile $file1, PhingFile $file2) {
  244. if (!($file1->exists() || $file2->exists())) {
  245. return false;
  246. }
  247. if (!($file1->canRead() || $file2->canRead())) {
  248. return false;
  249. }
  250. $c1 = file_get_contents($file1->getAbsolutePath());
  251. $c2 = file_get_contents($file2->getAbsolutePath());
  252. return trim($c1) == trim($c2);
  253. }
  254. }