PageRenderTime 25ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/_h5ai/private/php/ext/class-archive.php

https://gitlab.com/billyprice1/h5ai
PHP | 183 lines | 149 code | 32 blank | 2 comment | 26 complexity | ed594892f53213aacb0119ac145844f3 MD5 | raw file
  1. <?php
  2. class Archive {
  3. const NULL_BYTE = "\0";
  4. private static $SEGMENT_SIZE = 16777216; // 1024 * 1024 * 16 = 16MiB
  5. private static $TAR_PASSTHRU_CMD = 'cd [ROOTDIR] && tar --no-recursion -c -- [DIRS] [FILES]';
  6. private static $ZIP_PASSTHRU_CMD = 'cd [ROOTDIR] && zip - -- [FILES]';
  7. private $context;
  8. private $base_path;
  9. private $dirs;
  10. private $files;
  11. public function __construct($context) {
  12. $this->context = $context;
  13. }
  14. public function output($type, $base_href, $hrefs) {
  15. $this->base_path = $this->context->to_path($base_href);
  16. if (!$this->context->is_managed_path($this->base_path)) {
  17. return false;
  18. }
  19. $this->dirs = [];
  20. $this->files = [];
  21. $this->add_hrefs($hrefs);
  22. if (count($this->dirs) === 0 && count($this->files) === 0) {
  23. if ($type === 'php-tar') {
  24. $this->add_dir($this->base_path, '/');
  25. } else {
  26. $this->add_dir($this->base_path, '.');
  27. }
  28. }
  29. if ($type === 'php-tar') {
  30. return $this->php_tar($this->dirs, $this->files);
  31. } else if ($type === 'shell-tar') {
  32. return $this->shell_cmd(Archive::$TAR_PASSTHRU_CMD);
  33. } else if ($type === 'shell-zip') {
  34. return $this->shell_cmd(Archive::$ZIP_PASSTHRU_CMD);
  35. }
  36. return false;
  37. }
  38. private function shell_cmd($cmd) {
  39. $cmd = str_replace('[ROOTDIR]', escapeshellarg($this->base_path), $cmd);
  40. $cmd = str_replace('[DIRS]', count($this->dirs) ? implode(' ', array_map('escapeshellarg', $this->dirs)) : '', $cmd);
  41. $cmd = str_replace('[FILES]', count($this->files) ? implode(' ', array_map('escapeshellarg', $this->files)) : '', $cmd);
  42. try {
  43. Util::passthru_cmd($cmd);
  44. } catch (Exeption $err) {
  45. return false;
  46. }
  47. return true;
  48. }
  49. private function php_tar($dirs, $files) {
  50. $filesizes = [];
  51. $total_size = 512 * count($dirs);
  52. foreach (array_keys($files) as $real_file) {
  53. $size = filesize($real_file);
  54. $filesizes[$real_file] = $size;
  55. $total_size += 512 + $size;
  56. if ($size % 512 != 0) {
  57. $total_size += 512 - ($size % 512);
  58. }
  59. }
  60. header('Content-Length: ' . $total_size);
  61. foreach ($dirs as $real_dir => $archived_dir) {
  62. echo $this->php_tar_header($archived_dir, 0, @filemtime($real_dir . DIRECTORY_SEPARATOR . "."), 5);
  63. }
  64. foreach ($files as $real_file => $archived_file) {
  65. $size = $filesizes[$real_file];
  66. echo $this->php_tar_header($archived_file, $size, @filemtime($real_file), 0);
  67. $this->print_file($real_file);
  68. if ($size % 512 != 0) {
  69. echo str_repeat(Archive::NULL_BYTE, 512 - ($size % 512));
  70. }
  71. }
  72. return true;
  73. }
  74. private function php_tar_header($filename, $size, $mtime, $type) {
  75. $name = substr(basename($filename), -99);
  76. $prefix = substr(Util::normalize_path(dirname($filename)), -154);
  77. if ($prefix === '.') {
  78. $prefix = '';
  79. }
  80. $header =
  81. str_pad($name, 100, Archive::NULL_BYTE) // filename [100]
  82. . '0000755' . Archive::NULL_BYTE // file mode [8]
  83. . '0000000' . Archive::NULL_BYTE // uid [8]
  84. . '0000000' . Archive::NULL_BYTE // gid [8]
  85. . str_pad(decoct($size), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file size [12]
  86. . str_pad(decoct($mtime), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file modification time [12]
  87. . ' ' // checksum [8]
  88. . str_pad($type, 1) // file type [1]
  89. . str_repeat(Archive::NULL_BYTE, 100) // linkname [100]
  90. . 'ustar' . Archive::NULL_BYTE // magic [6]
  91. . '00' // version [2]
  92. . str_repeat(Archive::NULL_BYTE, 80) // uname, gname, defmajor, devminor [32 + 32 + 8 + 8]
  93. . str_pad($prefix, 155, Archive::NULL_BYTE) // filename [155]
  94. . str_repeat(Archive::NULL_BYTE, 12); // fill [12]
  95. assert(strlen($header) === 512);
  96. // checksum
  97. $checksum = array_sum(array_map('ord', str_split($header)));
  98. $checksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . Archive::NULL_BYTE . ' ';
  99. $header = substr_replace($header, $checksum, 148, 8);
  100. return $header;
  101. }
  102. private function print_file($file) {
  103. // Send file content in segments to not hit PHP's memory limit (default: 128M)
  104. if ($fd = fopen($file, 'rb')) {
  105. while (!feof($fd)) {
  106. print fread($fd, Archive::$SEGMENT_SIZE);
  107. @ob_flush();
  108. @flush();
  109. }
  110. fclose($fd);
  111. }
  112. }
  113. private function add_hrefs($hrefs) {
  114. foreach ($hrefs as $href) {
  115. if (trim($href) === '') {
  116. continue;
  117. }
  118. $d = Util::normalize_path(dirname($href), true);
  119. $n = basename($href);
  120. if ($this->context->is_managed_href($d) && !$this->context->is_hidden($n)) {
  121. $real_file = $this->context->to_path($href);
  122. $archived_file = preg_replace('!^' . preg_quote(Util::normalize_path($this->base_path, true)) . '!', '', $real_file);
  123. if (is_dir($real_file)) {
  124. $this->add_dir($real_file, $archived_file);
  125. } else {
  126. $this->add_file($real_file, $archived_file);
  127. }
  128. }
  129. }
  130. }
  131. private function add_file($real_file, $archived_file) {
  132. if (is_readable($real_file)) {
  133. $this->files[$real_file] = $archived_file;
  134. }
  135. }
  136. private function add_dir($real_dir, $archived_dir) {
  137. if ($this->context->is_managed_path($real_dir)) {
  138. $this->dirs[$real_dir] = $archived_dir;
  139. $files = $this->context->read_dir($real_dir);
  140. foreach ($files as $file) {
  141. $real_file = $real_dir . '/' . $file;
  142. $archived_file = $archived_dir . '/' . $file;
  143. if (is_dir($real_file)) {
  144. $this->add_dir($real_file, $archived_file);
  145. } else {
  146. $this->add_file($real_file, $archived_file);
  147. }
  148. }
  149. }
  150. }
  151. }