ManifestTask.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <?php
  2. /**
  3. * $Id: ManifestTask.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. require_once "phing/Task.php";
  22. require_once 'phing/system/io/PhingFile.php';
  23. /**
  24. * ManifestTask
  25. *
  26. * Generates a simple Manifest file with optional checksums.
  27. *
  28. *
  29. * Manifest schema:
  30. * ...
  31. * path/to/file CHECKSUM [CHECKSUM2] [CHECKSUM3]
  32. * path/to/secondfile CHECKSUM [CHECKSUM2] [CHECKSUM3]
  33. * ...
  34. *
  35. * Example usage:
  36. * <manifest checksum="crc32" file="${dir_build}/Manifest">
  37. * <fileset refid="files_build" />
  38. * </manifest>
  39. *
  40. * <manifest checksum="md5,adler32,sha256" file="${dir_build}/Manifest">
  41. * <fileset refid="files_build" />
  42. * </manifest>
  43. *
  44. *
  45. *
  46. * @author David Persson <davidpersson at qeweurope dot org>
  47. * @package phing.tasks.ext
  48. * @version $Id: ManifestTask.php 905 2010-10-05 16:28:03Z mrook $
  49. * @since 2.3.1
  50. */
  51. class ManifestTask extends Task
  52. {
  53. var $taskname = 'manifest';
  54. /**
  55. * Action
  56. *
  57. * "w" for reading in files from fileSet
  58. * and writing manifest
  59. *
  60. * or
  61. *
  62. * "r" for reading in files from fileSet
  63. * and checking against manifest
  64. *
  65. * @var string "r" or "w"
  66. */
  67. private $action = 'w';
  68. /**
  69. * The target file passed in the buildfile.
  70. */
  71. private $destFile = null;
  72. /**
  73. * Holds filesets
  74. *
  75. * @var array An Array of objects
  76. */
  77. private $filesets = array();
  78. /**
  79. * Enable/Disable checksuming or/and select algorithm
  80. * true defaults to md5
  81. * false disables checksuming
  82. * string "md5,sha256,..." enables generation of multiple checksums
  83. * string "sha256" generates sha256 checksum only
  84. *
  85. * @var mixed
  86. */
  87. private $checksum = false;
  88. /**
  89. * A string used in hashing method
  90. *
  91. * @var string
  92. */
  93. private $salt = '';
  94. /**
  95. * Holds some data collected during runtime
  96. *
  97. * @var array
  98. */
  99. private $meta = array('totalFileCount' => 0,'totalFileSize' => 0);
  100. /**
  101. * The setter for the attribute "file"
  102. * This is where the manifest will be written to/read from
  103. *
  104. * @param string Path to readable file
  105. * @return void
  106. */
  107. public function setFile(PhingFile $file)
  108. {
  109. $this->file = $file;
  110. }
  111. /**
  112. * The setter for the attribute "checksum"
  113. *
  114. * @param mixed $mixed
  115. * @return void
  116. */
  117. public function setChecksum($mixed)
  118. {
  119. if(is_string($mixed)) {
  120. $data = array(strtolower($mixed));
  121. if(strpos($data[0],',')) {
  122. $data = explode(',',$mixed);
  123. }
  124. $this->checksum = $data;
  125. } elseif($mixed === true) {
  126. $this->checksum = array('md5');
  127. }
  128. }
  129. /**
  130. * The setter for the optional attribute "salt"
  131. *
  132. * @param string $string
  133. * @return void
  134. */
  135. public function setSalt($string)
  136. {
  137. $this->salt = $string;
  138. }
  139. /**
  140. * Nested creator, creates a FileSet for this task
  141. *
  142. * @access public
  143. * @return object The created fileset object
  144. */
  145. public function createFileSet()
  146. {
  147. $num = array_push($this->filesets, new FileSet());
  148. return $this->filesets[$num-1];
  149. }
  150. /**
  151. * The init method: Do init steps.
  152. */
  153. public function init()
  154. {
  155. // nothing to do here
  156. }
  157. /**
  158. * Delegate the work
  159. */
  160. public function main()
  161. {
  162. $this->validateAttributes();
  163. if($this->action == 'w') {
  164. $this->write();
  165. } elseif($this->action == 'r') {
  166. $this->read();
  167. }
  168. }
  169. /**
  170. * Creates Manifest file
  171. * Writes to $this->file
  172. *
  173. * @throws BuildException
  174. */
  175. private function write()
  176. {
  177. $project = $this->getProject();
  178. if(!touch($this->file->getPath())) {
  179. throw new BuildException("Unable to write to ".$this->file->getPath().".");
  180. }
  181. $this->log("Writing to " . $this->file->__toString(), Project::MSG_INFO);
  182. if(is_array($this->checksum)) {
  183. $this->log("Using " . implode(', ',$this->checksum)." for checksuming.", Project::MSG_INFO);
  184. }
  185. foreach($this->filesets as $fs) {
  186. $dir = $fs->getDir($this->project)->getPath();
  187. $ds = $fs->getDirectoryScanner($project);
  188. $fromDir = $fs->getDir($project);
  189. $srcFiles = $ds->getIncludedFiles();
  190. $srcDirs = $ds->getIncludedDirectories();
  191. foreach($ds->getIncludedFiles() as $file_path) {
  192. $line = $file_path;
  193. if($this->checksum) {
  194. foreach($this->checksum as $algo) {
  195. if(!$hash = $this->hashFile($dir.'/'.$file_path,$algo)) {
  196. throw new BuildException("Hashing $dir/$file_path with $algo failed!");
  197. }
  198. $line .= "\t".$hash;
  199. }
  200. }
  201. $line .= "\n";
  202. $manifest[] = $line;
  203. $this->log("Adding file ".$file_path,Project::MSG_VERBOSE);
  204. $this->meta['totalFileCount'] ++;
  205. $this->meta['totalFileSize'] += filesize($dir.'/'.$file_path);
  206. }
  207. }
  208. file_put_contents($this->file,$manifest);
  209. $this->log("Done. Total files: ".$this->meta['totalFileCount'].". Total file size: ".$this->meta['totalFileSize']." bytes.", Project::MSG_INFO);
  210. }
  211. /**
  212. * @todo implement
  213. */
  214. private function read()
  215. {
  216. throw new BuildException("Checking against manifest not yet supported.");
  217. }
  218. /**
  219. * Wrapper method for hash generation
  220. * Automatically selects extension
  221. * Falls back to built-in functions
  222. *
  223. * @link http://www.php.net/mhash
  224. * @link http://www.php.net/hash
  225. *
  226. * @param string $msg The string that should be hashed
  227. * @param string $algo Algorithm
  228. * @return mixed String on success, false if $algo is not available
  229. */
  230. private function hash($msg,$algo)
  231. {
  232. if(extension_loaded('hash')) {
  233. $algo = strtolower($algo);
  234. if(in_array($algo,hash_algos())) {
  235. return hash($algo,$this->salt.$msg);
  236. }
  237. }
  238. if(extension_loaded('mhash')) {
  239. $algo = strtoupper($algo);
  240. if(defined('MHASH_'.$algo)) {
  241. return mhash('MHASH_'.$algo,$this->salt.$msg);
  242. }
  243. }
  244. switch(strtolower($algo)) {
  245. case 'md5':
  246. return md5($this->salt.$msg);
  247. case 'crc32':
  248. return abs(crc32($this->salt.$msg));
  249. }
  250. return false;
  251. }
  252. /**
  253. * Hash a files contents
  254. * plus it's size an modification time
  255. *
  256. * @param string $file
  257. * @param string $algo
  258. * @return mixed String on success, false if $algo is not available
  259. */
  260. private function hashFile($file,$algo)
  261. {
  262. if(!file_exists($file)) {
  263. return false;
  264. }
  265. $msg = file_get_contents($file).filesize($file).filemtime($file);
  266. return $this->hash($msg,$algo);
  267. }
  268. /**
  269. * Validates attributes coming in from XML
  270. *
  271. * @access private
  272. * @return void
  273. * @throws BuildException
  274. */
  275. protected function validateAttributes()
  276. {
  277. if($this->action != 'r' && $this->action != 'w') {
  278. throw new BuildException("'action' attribute has non valid value. Use 'r' or 'w'");
  279. }
  280. if(empty($this->salt)) {
  281. $this->log("No salt provided. Specify one with the 'salt' attribute.", Project::MSG_WARN);
  282. }
  283. if (is_null($this->file) && count($this->filesets) === 0) {
  284. throw new BuildException("Specify at least sources and destination - a file or a fileset.");
  285. }
  286. if (!is_null($this->file) && $this->file->exists() && $this->file->isDirectory()) {
  287. throw new BuildException("Destination file cannot be a directory.");
  288. }
  289. }
  290. }