tar.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. <?php
  2. class tar {
  3. var $filename;
  4. var $isGzipped;
  5. var $tar_file;
  6. var $files;
  7. var $directories;
  8. var $numFiles;
  9. var $numDirectories;
  10. public function __construct() {
  11. return true;
  12. }
  13. /**
  14. * Computes the unsigned Checksum of a file's header
  15. * to try to ensure valid file
  16. *
  17. * @param string $bytestring
  18. *
  19. * @access private
  20. */
  21. function __computeUnsignedChecksum($bytestring) {
  22. $unsigned_chksum = '';
  23. for ($i = 0; $i < 512; $i++)
  24. @$unsigned_chksum += ord($bytestring[$i]);
  25. for ($i = 0; $i < 8; $i++)
  26. @$unsigned_chksum -= ord($bytestring[148 + $i]);
  27. @$unsigned_chksum += ord(" ") * 8;
  28. return $unsigned_chksum;
  29. }
  30. /**
  31. * Converts a NULL padded string to a non-NULL padded string
  32. *
  33. * @param string $string
  34. *
  35. * @return string
  36. *
  37. * @access private
  38. * */
  39. function __parseNullPaddedString($string) {
  40. $position = strpos($string, chr(0));
  41. return substr($string, 0, $position);
  42. }
  43. /**
  44. * This function parses the current TAR file
  45. *
  46. * @return bool always TRUE
  47. *
  48. * @access private
  49. * */
  50. function __parseTar() {
  51. // Read Files from archive
  52. $tar_length = strlen($this->tar_file);
  53. $main_offset = 0;
  54. $this->numFiles = 0;
  55. while ($main_offset < $tar_length) {
  56. // If we read a block of 512 nulls, we are at the end of the archive
  57. if (substr($this->tar_file, $main_offset, 512) == str_repeat(chr(0), 512))
  58. break;
  59. // Parse file name
  60. $file_name = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset, 100));
  61. // Parse the file mode
  62. $file_mode = substr($this->tar_file, $main_offset + 100, 8);
  63. // Parse the file user ID
  64. $file_uid = octdec(substr($this->tar_file, $main_offset + 108, 8));
  65. // Parse the file group ID
  66. $file_gid = octdec(substr($this->tar_file, $main_offset + 116, 8));
  67. // Parse the file size
  68. $file_size = octdec(substr($this->tar_file, $main_offset + 124, 12));
  69. // Parse the file update time - unix timestamp format
  70. $file_time = octdec(substr($this->tar_file, $main_offset + 136, 12));
  71. // Parse Checksum
  72. $file_chksum = octdec(substr($this->tar_file, $main_offset + 148, 6));
  73. // Parse user name
  74. $file_uname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 265, 32));
  75. // Parse Group name
  76. $file_gname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 297, 32));
  77. // Make sure our file is valid
  78. if ($this->__computeUnsignedChecksum(substr($this->tar_file, $main_offset, 512)) != $file_chksum)
  79. return false;
  80. // Parse File Contents
  81. $file_contents = substr($this->tar_file, $main_offset + 512, $file_size);
  82. /* ### Unused Header Information ###
  83. $activeFile["typeflag"] = substr($this->tar_file,$main_offset + 156,1);
  84. $activeFile["linkname"] = substr($this->tar_file,$main_offset + 157,100);
  85. $activeFile["magic"] = substr($this->tar_file,$main_offset + 257,6);
  86. $activeFile["version"] = substr($this->tar_file,$main_offset + 263,2);
  87. $activeFile["devmajor"] = substr($this->tar_file,$main_offset + 329,8);
  88. $activeFile["devminor"] = substr($this->tar_file,$main_offset + 337,8);
  89. $activeFile["prefix"] = substr($this->tar_file,$main_offset + 345,155);
  90. $activeFile["endheader"] = substr($this->tar_file,$main_offset + 500,12);
  91. */
  92. if ($file_size > 0) {
  93. // Increment number of files
  94. $this->numFiles++;
  95. // Create us a new file in our array
  96. $activeFile = &$this->files[];
  97. // Asign Values
  98. $activeFile["name"] = $file_name;
  99. $activeFile["mode"] = $file_mode;
  100. $activeFile["size"] = $file_size;
  101. $activeFile["time"] = $file_time;
  102. $activeFile["user_id"] = $file_uid;
  103. $activeFile["group_id"] = $file_gid;
  104. $activeFile["user_name"] = $file_uname;
  105. $activeFile["group_name"] = $file_gname;
  106. $activeFile["checksum"] = $file_chksum;
  107. $activeFile["file"] = $file_contents;
  108. } else {
  109. // Increment number of directories
  110. $this->numDirectories++;
  111. // Create a new directory in our array
  112. $activeDir = &$this->directories[];
  113. // Assign values
  114. $activeDir["name"] = $file_name;
  115. $activeDir["mode"] = $file_mode;
  116. $activeDir["time"] = $file_time;
  117. $activeDir["user_id"] = $file_uid;
  118. $activeDir["group_id"] = $file_gid;
  119. $activeDir["user_name"] = $file_uname;
  120. $activeDir["group_name"] = $file_gname;
  121. $activeDir["checksum"] = $file_chksum;
  122. }
  123. // Move our offset the number of blocks we have processed
  124. $main_offset += 512 + (ceil($file_size / 512) * 512);
  125. }
  126. return true;
  127. }
  128. /**
  129. * Read a non gzipped tar file in for processing.
  130. *
  131. * @param string $filename full filename
  132. * @return bool always TRUE
  133. *
  134. * @access private
  135. * */
  136. function __readTar($filename = '') {
  137. // Set the filename to load
  138. if (!$filename)
  139. $filename = $this->filename;
  140. // Read in the TAR file
  141. $fp = fopen($filename, "rb");
  142. $this->tar_file = fread($fp, filesize($filename));
  143. fclose($fp);
  144. if ($this->tar_file[0] == chr(31) && $this->tar_file[1] == chr(139) && $this->tar_file[2] == chr(8)) {
  145. if (!function_exists("gzinflate"))
  146. return false;
  147. $this->isGzipped = true;
  148. $this->tar_file = gzinflate(substr($this->tar_file, 10, -4));
  149. }
  150. // Parse the TAR file
  151. $this->__parseTar();
  152. return true;
  153. }
  154. /**
  155. * Generates a TAR file from the processed data
  156. *
  157. * @return bool always TRUE
  158. *
  159. * @access private
  160. * */
  161. function __generateTAR() {
  162. $this->tar_file = '';
  163. // Generate Records for each directory, if we have directories
  164. if ($this->numDirectories > 0) {
  165. foreach ($this->directories as $key => $information) {
  166. unset($header);
  167. // Generate tar header for this directory
  168. // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
  169. $header = str_pad($information["name"], 100, chr(0));
  170. $header .= str_pad(decoct($information["mode"]), 7, "0", STR_PAD_LEFT) . chr(0);
  171. $header .= str_pad(decoct($information["user_id"]), 7, "0", STR_PAD_LEFT) . chr(0);
  172. $header .= str_pad(decoct($information["group_id"]), 7, "0", STR_PAD_LEFT) . chr(0);
  173. $header .= str_pad(decoct(0), 11, "0", STR_PAD_LEFT) . chr(0);
  174. $header .= str_pad(decoct($information["time"]), 11, "0", STR_PAD_LEFT) . chr(0);
  175. $header .= str_repeat(" ", 8);
  176. $header .= "5";
  177. $header .= str_repeat(chr(0), 100);
  178. $header .= str_pad("ustar", 6, chr(32));
  179. $header .= chr(32) . chr(0);
  180. $header .= str_pad("", 32, chr(0));
  181. $header .= str_pad("", 32, chr(0));
  182. $header .= str_repeat(chr(0), 8);
  183. $header .= str_repeat(chr(0), 8);
  184. $header .= str_repeat(chr(0), 155);
  185. $header .= str_repeat(chr(0), 12);
  186. // Compute header checksum
  187. $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, "0", STR_PAD_LEFT);
  188. for ($i = 0; $i < 6; $i++) {
  189. $header[(148 + $i)] = substr($checksum, $i, 1);
  190. }
  191. $header[154] = chr(0);
  192. $header[155] = chr(32);
  193. // Add new tar formatted data to tar file contents
  194. $this->tar_file .= $header;
  195. }
  196. }
  197. // Generate Records for each file, if we have files (We should...)
  198. if ($this->numFiles > 0) {
  199. foreach ($this->files as $key => $information) {
  200. // Generate the TAR header for this file
  201. // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
  202. $header = str_pad($information['name'], 100, chr(0));
  203. $header .= str_pad(decoct($information['mode']), 7, "0", STR_PAD_LEFT) . chr(0);
  204. $header .= str_pad(decoct($information['user_id']), 7, "0", STR_PAD_LEFT) . chr(0);
  205. $header .= str_pad(decoct($information['group_id']), 7, "0", STR_PAD_LEFT) . chr(0);
  206. $header .= str_pad(decoct($information['size']), 11, "0", STR_PAD_LEFT) . chr(0);
  207. $header .= str_pad(decoct($information['time']), 11, "0", STR_PAD_LEFT) . chr(0);
  208. $header .= str_repeat(" ", 8);
  209. $header .= "0";
  210. $header .= str_repeat(chr(0), 100);
  211. $header .= str_pad("ustar", 6, chr(32));
  212. $header .= chr(32) . chr(0);
  213. $header .= str_pad($information["user_name"], 32, chr(0)); // How do I get a file's user name from PHP?
  214. $header .= str_pad($information["group_name"], 32, chr(0)); // How do I get a file's group name from PHP?
  215. $header .= str_repeat(chr(0), 8);
  216. $header .= str_repeat(chr(0), 8);
  217. $header .= str_repeat(chr(0), 155);
  218. $header .= str_repeat(chr(0), 12);
  219. // Compute header checksum
  220. $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, "0", STR_PAD_LEFT);
  221. for ($i = 0; $i < 6; $i++) {
  222. $header[(148 + $i)] = substr($checksum, $i, 1);
  223. }
  224. $header[154] = chr(0);
  225. $header[155] = chr(32);
  226. // Pad file contents to byte count divisible by 512
  227. $file_contents = str_pad($information["file"], (ceil($information["size"] / 512) * 512), chr(0));
  228. // Add new tar formatted data to tar file contents
  229. $this->tar_file .= $header . $file_contents;
  230. }
  231. }
  232. // Add 512 bytes of NULLs to designate EOF
  233. $this->tar_file .= str_repeat(chr(0), 512);
  234. return true;
  235. }
  236. /**
  237. * Open a TAR file
  238. *
  239. * @param string $filename
  240. * @return bool
  241. * */
  242. function openTAR($filename) {
  243. // Clear any values from previous tar archives
  244. unset($this->filename);
  245. unset($this->isGzipped);
  246. unset($this->tar_file);
  247. unset($this->files);
  248. unset($this->directories);
  249. unset($this->numFiles);
  250. unset($this->numDirectories);
  251. // If the tar file doesn't exist...
  252. if (!file_exists($filename))
  253. return false;
  254. $this->filename = $filename;
  255. // Parse this file
  256. $this->__readTar();
  257. return true;
  258. }
  259. /**
  260. * Appends a tar file to the end of the currently opened tar file.
  261. *
  262. * @param string $filename
  263. * @return bool
  264. * */
  265. function appendTar($filename) {
  266. // If the tar file doesn't exist...
  267. if (!file_exists($filename))
  268. return false;
  269. $this->__readTar($filename);
  270. return true;
  271. }
  272. /**
  273. * Retrieves information about a file in the current tar archive
  274. *
  275. * @param string $filename
  276. * @return string FALSE on fail
  277. * */
  278. function getFile($filename) {
  279. if ($this->numFiles > 0) {
  280. foreach ($this->files as $key => $information) {
  281. if ($information["name"] == $filename)
  282. return $information;
  283. }
  284. }
  285. return false;
  286. }
  287. /**
  288. * Retrieves information about a directory in the current tar archive
  289. *
  290. * @param string $dirname
  291. * @return string FALSE on fail
  292. * */
  293. function getDirectory($dirname) {
  294. if ($this->numDirectories > 0) {
  295. foreach ($this->directories as $key => $information) {
  296. if ($information["name"] == $dirname)
  297. return $information;
  298. }
  299. }
  300. return false;
  301. }
  302. /**
  303. * Check if this tar archive contains a specific file
  304. *
  305. * @param string $filename
  306. * @return bool
  307. * */
  308. function containsFile($filename) {
  309. if ($this->numFiles > 0) {
  310. foreach ($this->files as $key => $information) {
  311. if ($information["name"] == $filename)
  312. return true;
  313. }
  314. }
  315. return false;
  316. }
  317. /**
  318. * Check if this tar archive contains a specific directory
  319. *
  320. * @param string $dirname
  321. * @return bool
  322. * */
  323. function containsDirectory($dirname) {
  324. if ($this->numDirectories > 0) {
  325. foreach ($this->directories as $key => $information) {
  326. if ($information["name"] == $dirname) {
  327. return true;
  328. }
  329. }
  330. }
  331. return false;
  332. }
  333. /**
  334. * Add a directory to this tar archive
  335. *
  336. * @param string $dirname
  337. * @return bool
  338. * */
  339. function addDirectory($dirname, $recurse = false) {
  340. if (!file_exists($dirname)) {
  341. return false;
  342. }
  343. // Get directory information
  344. $file_information = stat($dirname);
  345. // Add directory to processed data
  346. $this->numDirectories++;
  347. $activeDir = &$this->directories[];
  348. $activeDir["name"] = $dirname;
  349. $activeDir["mode"] = @$file_information["mode"];
  350. $activeDir["time"] = @$file_information["time"];
  351. $activeDir["user_id"] = @$file_information["uid"];
  352. $activeDir["group_id"] = @$file_information["gid"];
  353. $activeDir["checksum"] = 0;
  354. if ($recurse) {
  355. $cdir = opendir($dirname);
  356. while ($element = readdir($cdir)) {
  357. if ($element != '.' && $element != '..') {
  358. if (is_dir($dirname . '/' . $element)) {
  359. $this->addDirectory($dirname . '/' . $element, true);
  360. } else {
  361. $this->addFile($dirname . '/' . $element);
  362. }
  363. }
  364. }
  365. closedir($cdir);
  366. }
  367. return true;
  368. }
  369. /**
  370. * Add a file to the tar archive
  371. *
  372. * @param string $filename
  373. * @param boolean $binary Binary file?
  374. * @return bool
  375. * */
  376. function addFile($filename, $binary = false) {
  377. // Make sure the file we are adding exists!
  378. if (!file_exists($filename)) {
  379. return false;
  380. }
  381. // Make sure there are no other files in the archive that have this same filename
  382. if ($this->containsFile($filename)) {
  383. return false;
  384. }
  385. // Get file information
  386. $file_information = stat($filename);
  387. // Read in the file's contents
  388. if (!$binary) {
  389. $fp = fopen($filename, "r");
  390. } else {
  391. $fp = fopen($filename, "rb");
  392. }
  393. if (filesize($filename) !== 0)
  394. $file_contents = fread($fp, filesize($filename));
  395. else
  396. $file_contents = '';
  397. fclose($fp);
  398. // Add file to processed data
  399. $this->numFiles++;
  400. $activeFile = &$this->files[];
  401. $activeFile["name"] = $filename;
  402. $activeFile["mode"] = $file_information["mode"];
  403. $activeFile["user_id"] = $file_information["uid"];
  404. $activeFile["group_id"] = $file_information["gid"];
  405. $activeFile["size"] = $file_information["size"];
  406. $activeFile["time"] = $file_information["mtime"];
  407. $activeFile["checksum"] = isset($checksum) ? $checksum : '';
  408. $activeFile["user_name"] = "";
  409. $activeFile["group_name"] = "";
  410. $activeFile["file"] = trim($file_contents);
  411. return true;
  412. }
  413. /**
  414. * Remove a file from the tar archive
  415. *
  416. * @param string $filename
  417. * @return bool
  418. * */
  419. function removeFile($filename) {
  420. if ($this->numFiles > 0) {
  421. foreach ($this->files as $key => $information) {
  422. if ($information["name"] == $filename) {
  423. $this->numFiles--;
  424. unset($this->files[$key]);
  425. return true;
  426. }
  427. }
  428. }
  429. return false;
  430. }
  431. /**
  432. * Remove a directory from the tar archive
  433. *
  434. * @param string $dirname
  435. * @return bool
  436. * */
  437. function removeDirectory($dirname) {
  438. if ($this->numDirectories > 0) {
  439. foreach ($this->directories as $key => $information) {
  440. if ($information["name"] == $dirname) {
  441. $this->numDirectories--;
  442. unset($this->directories[$key]);
  443. return true;
  444. }
  445. }
  446. }
  447. return false;
  448. }
  449. /**
  450. * Write the currently loaded tar archive to disk
  451. *
  452. * @return bool
  453. * */
  454. function saveTar() {
  455. if (!$this->filename) {
  456. return false;
  457. }
  458. // Write tar to current file using specified gzip compression
  459. $this->toTar($this->filename, $this->isGzipped);
  460. return true;
  461. }
  462. /**
  463. * Saves tar archive to a different file than the current file
  464. *
  465. * @param string $filename
  466. * @param bool $useGzip Use GZ compression?
  467. * @return bool
  468. * */
  469. function toTar($filename, $useGzip) {
  470. if (!$filename) {
  471. return false;
  472. }
  473. // Encode processed files into TAR file format
  474. $this->__generateTar();
  475. // GZ Compress the data if we need to
  476. if ($useGzip) {
  477. // Make sure we have gzip support
  478. if (!function_exists("gzencode")) {
  479. return false;
  480. }
  481. $file = gzencode($this->tar_file);
  482. } else {
  483. $file = $this->tar_file;
  484. }
  485. // Write the TAR file
  486. $fp = fopen($filename, "wb");
  487. fwrite($fp, $file);
  488. fclose($fp);
  489. return true;
  490. }
  491. /**
  492. * Sends tar archive to stdout
  493. *
  494. * @param string $filename
  495. * @param bool $useGzip Use GZ compression?
  496. * @return string
  497. * */
  498. function toTarOutput($filename, $useGzip) {
  499. if (!$filename) {
  500. return false;
  501. }
  502. // Encode processed files into TAR file format
  503. $this->__generateTar();
  504. // GZ Compress the data if we need to
  505. if ($useGzip) {
  506. // Make sure we have gzip support
  507. if (!function_exists("gzencode")) {
  508. return false;
  509. }
  510. $file = gzencode($this->tar_file);
  511. } else {
  512. $file = $this->tar_file;
  513. }
  514. return $file;
  515. }
  516. }
  517. ?>