| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | /**
 | 
        
           |  |  | 18 |  * Implementation of .tar.gz packer.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * A limited subset of the .tar format is supported. This packer can open files
 | 
        
           |  |  | 21 |  * that it wrote, but may not be able to open files from other sources,
 | 
        
           |  |  | 22 |  * especially if they use extensions. There are restrictions on file
 | 
        
           |  |  | 23 |  * length and character set of filenames.
 | 
        
           |  |  | 24 |  *
 | 
        
           |  |  | 25 |  * We generate POSIX-compliant ustar files. As a result, the following
 | 
        
           |  |  | 26 |  * restrictions apply to archive paths:
 | 
        
           |  |  | 27 |  *
 | 
        
           |  |  | 28 |  * - Filename may not be more than 100 characters.
 | 
        
           |  |  | 29 |  * - Total of path + filename may not be more than 256 characters.
 | 
        
           |  |  | 30 |  * - For path more than 155 characters it may or may not work.
 | 
        
           |  |  | 31 |  * - May not contain non-ASCII characters.
 | 
        
           |  |  | 32 |  *
 | 
        
           |  |  | 33 |  * @package core_files
 | 
        
           |  |  | 34 |  * @copyright 2013 The Open University
 | 
        
           |  |  | 35 |  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 36 |  */
 | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 39 |   | 
        
           |  |  | 40 | require_once("$CFG->libdir/filestorage/file_packer.php");
 | 
        
           |  |  | 41 | require_once("$CFG->libdir/filestorage/tgz_extractor.php");
 | 
        
           |  |  | 42 |   | 
        
           |  |  | 43 | /**
 | 
        
           |  |  | 44 |  * Utility class - handles all packing/unpacking of .tar.gz files.
 | 
        
           |  |  | 45 |  *
 | 
        
           |  |  | 46 |  * @package core_files
 | 
        
           |  |  | 47 |  * @category files
 | 
        
           |  |  | 48 |  * @copyright 2013 The Open University
 | 
        
           |  |  | 49 |  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 50 |  */
 | 
        
           |  |  | 51 | class tgz_packer extends file_packer {
 | 
        
           |  |  | 52 |     /**
 | 
        
           |  |  | 53 |      * @var int Default timestamp used where unknown (Jan 1st 2013 00:00)
 | 
        
           |  |  | 54 |      */
 | 
        
           |  |  | 55 |     const DEFAULT_TIMESTAMP = 1356998400;
 | 
        
           |  |  | 56 |   | 
        
           |  |  | 57 |     /**
 | 
        
           |  |  | 58 |      * @var string Name of special archive index file added by Moodle.
 | 
        
           |  |  | 59 |      */
 | 
        
           |  |  | 60 |     const ARCHIVE_INDEX_FILE = '.ARCHIVE_INDEX';
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     /**
 | 
        
           |  |  | 63 |      * @var string Required text at start of archive index file before file count.
 | 
        
           |  |  | 64 |      */
 | 
        
           |  |  | 65 |     const ARCHIVE_INDEX_COUNT_PREFIX = 'Moodle archive file index. Count: ';
 | 
        
           |  |  | 66 |   | 
        
           |  |  | 67 |     /**
 | 
        
           |  |  | 68 |      * @var bool If true, includes .ARCHIVE_INDEX file in root of tar file.
 | 
        
           |  |  | 69 |      */
 | 
        
           |  |  | 70 |     protected $includeindex = true;
 | 
        
           |  |  | 71 |   | 
        
           |  |  | 72 |     /**
 | 
        
           |  |  | 73 |      * @var int Max value for total progress.
 | 
        
           |  |  | 74 |      */
 | 
        
           |  |  | 75 |     const PROGRESS_MAX = 1000000;
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 |     /**
 | 
        
           |  |  | 78 |      * @var int Tar files have a fixed block size of 512 bytes.
 | 
        
           |  |  | 79 |      */
 | 
        
           |  |  | 80 |     const TAR_BLOCK_SIZE = 512;
 | 
        
           |  |  | 81 |   | 
        
           |  |  | 82 |     /**
 | 
        
           |  |  | 83 |      * Archive files and store the result in file storage.
 | 
        
           |  |  | 84 |      *
 | 
        
           |  |  | 85 |      * Any existing file at that location will be overwritten.
 | 
        
           |  |  | 86 |      *
 | 
        
           |  |  | 87 |      * @param array $files array from archive path => pathname or stored_file
 | 
        
           |  |  | 88 |      * @param int $contextid context ID
 | 
        
           |  |  | 89 |      * @param string $component component
 | 
        
           |  |  | 90 |      * @param string $filearea file area
 | 
        
           |  |  | 91 |      * @param int $itemid item ID
 | 
        
           |  |  | 92 |      * @param string $filepath file path
 | 
        
           |  |  | 93 |      * @param string $filename file name
 | 
        
           |  |  | 94 |      * @param int $userid user ID
 | 
        
           |  |  | 95 |      * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
 | 
        
           |  |  | 96 |      * @param file_progress $progress Progress indicator callback or null if not required
 | 
        
           |  |  | 97 |      * @return stored_file|bool false if error stored_file instance if ok
 | 
        
           |  |  | 98 |      * @throws file_exception If file operations fail
 | 
        
           |  |  | 99 |      * @throws coding_exception If any archive paths do not meet the restrictions
 | 
        
           |  |  | 100 |      */
 | 
        
           |  |  | 101 |     public function archive_to_storage(array $files, $contextid,
 | 
        
           |  |  | 102 |             $component, $filearea, $itemid, $filepath, $filename,
 | 
        
           | 1441 | ariadna | 103 |             $userid = null, $ignoreinvalidfiles = true, ?file_progress $progress = null) {
 | 
        
           | 1 | efrain | 104 |         global $CFG;
 | 
        
           |  |  | 105 |   | 
        
           |  |  | 106 |         // Set up a temporary location for the file.
 | 
        
           |  |  | 107 |         $tempfolder = $CFG->tempdir . '/core_files';
 | 
        
           |  |  | 108 |         check_dir_exists($tempfolder);
 | 
        
           |  |  | 109 |         $tempfile = tempnam($tempfolder, '.tgz');
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |         // Archive to the given path.
 | 
        
           |  |  | 112 |         if ($result = $this->archive_to_pathname($files, $tempfile, $ignoreinvalidfiles, $progress)) {
 | 
        
           |  |  | 113 |             // If there is an existing file, delete it.
 | 
        
           |  |  | 114 |             $fs = get_file_storage();
 | 
        
           |  |  | 115 |             if ($existing = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
 | 
        
           |  |  | 116 |                 $existing->delete();
 | 
        
           |  |  | 117 |             }
 | 
        
           |  |  | 118 |             $filerecord = array('contextid' => $contextid, 'component' => $component,
 | 
        
           |  |  | 119 |                     'filearea' => $filearea, 'itemid' => $itemid, 'filepath' => $filepath,
 | 
        
           |  |  | 120 |                     'filename' => $filename, 'userid' => $userid, 'mimetype' => 'application/x-tgz');
 | 
        
           |  |  | 121 |             self::delete_existing_file_record($fs, $filerecord);
 | 
        
           |  |  | 122 |             $result = $fs->create_file_from_pathname($filerecord, $tempfile);
 | 
        
           |  |  | 123 |         }
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 |         // Delete the temporary file (if created) and return.
 | 
        
           |  |  | 126 |         @unlink($tempfile);
 | 
        
           |  |  | 127 |         return $result;
 | 
        
           |  |  | 128 |     }
 | 
        
           |  |  | 129 |   | 
        
           |  |  | 130 |     /**
 | 
        
           |  |  | 131 |      * Wrapper function useful for deleting an existing file (if present) just
 | 
        
           |  |  | 132 |      * before creating a new one.
 | 
        
           |  |  | 133 |      *
 | 
        
           |  |  | 134 |      * @param file_storage $fs File storage
 | 
        
           |  |  | 135 |      * @param array $filerecord File record in same format used to create file
 | 
        
           |  |  | 136 |      */
 | 
        
           |  |  | 137 |     public static function delete_existing_file_record(file_storage $fs, array $filerecord) {
 | 
        
           |  |  | 138 |         if ($existing = $fs->get_file($filerecord['contextid'], $filerecord['component'],
 | 
        
           |  |  | 139 |                 $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'],
 | 
        
           |  |  | 140 |                 $filerecord['filename'])) {
 | 
        
           |  |  | 141 |             $existing->delete();
 | 
        
           |  |  | 142 |         }
 | 
        
           |  |  | 143 |     }
 | 
        
           |  |  | 144 |   | 
        
           |  |  | 145 |     /**
 | 
        
           |  |  | 146 |      * By default, the .tar file includes a .ARCHIVE_INDEX file as its first
 | 
        
           |  |  | 147 |      * entry. This makes list_files much faster and allows for better progress
 | 
        
           |  |  | 148 |      * reporting.
 | 
        
           |  |  | 149 |      *
 | 
        
           |  |  | 150 |      * If you need to disable the inclusion of this file, use this function
 | 
        
           |  |  | 151 |      * before calling one of the archive_xx functions.
 | 
        
           |  |  | 152 |      *
 | 
        
           |  |  | 153 |      * @param bool $includeindex If true, includes index
 | 
        
           |  |  | 154 |      */
 | 
        
           |  |  | 155 |     public function set_include_index($includeindex) {
 | 
        
           |  |  | 156 |         $this->includeindex = $includeindex;
 | 
        
           |  |  | 157 |     }
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |     /**
 | 
        
           |  |  | 160 |      * Archive files and store the result in an OS file.
 | 
        
           |  |  | 161 |      *
 | 
        
           |  |  | 162 |      * @param array $files array from archive path => pathname or stored_file
 | 
        
           |  |  | 163 |      * @param string $archivefile path to target zip file
 | 
        
           |  |  | 164 |      * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
 | 
        
           |  |  | 165 |      * @param file_progress $progress Progress indicator callback or null if not required
 | 
        
           |  |  | 166 |      * @return bool true if file created, false if not
 | 
        
           |  |  | 167 |      * @throws coding_exception If any archive paths do not meet the restrictions
 | 
        
           |  |  | 168 |      */
 | 
        
           |  |  | 169 |     public function archive_to_pathname(array $files, $archivefile,
 | 
        
           | 1441 | ariadna | 170 |             $ignoreinvalidfiles=true, ?file_progress $progress = null) {
 | 
        
           | 1 | efrain | 171 |         // Open .gz file.
 | 
        
           |  |  | 172 |         if (!($gz = gzopen($archivefile, 'wb'))) {
 | 
        
           |  |  | 173 |             return false;
 | 
        
           |  |  | 174 |         }
 | 
        
           |  |  | 175 |         try {
 | 
        
           |  |  | 176 |             // Because we update how we calculate progress after we already
 | 
        
           |  |  | 177 |             // analyse the directory list, we can't just use a number of files
 | 
        
           |  |  | 178 |             // as progress. Instead, progress always goes to PROGRESS_MAX
 | 
        
           |  |  | 179 |             // and we do estimates as a proportion of that. To begin with,
 | 
        
           |  |  | 180 |             // assume that counting files will be 10% of the work, so allocate
 | 
        
           |  |  | 181 |             // one-tenth of PROGRESS_MAX to the total of all files.
 | 
        
           |  |  | 182 |             if ($files) {
 | 
        
           |  |  | 183 |                 $progressperfile = (int)(self::PROGRESS_MAX / (count($files) * 10));
 | 
        
           |  |  | 184 |             } else {
 | 
        
           |  |  | 185 |                 // If there are no files, avoid divide by zero.
 | 
        
           |  |  | 186 |                 $progressperfile = 1;
 | 
        
           |  |  | 187 |             }
 | 
        
           |  |  | 188 |             $done = 0;
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |             // Expand the provided files into a complete list of single files.
 | 
        
           |  |  | 191 |             $expandedfiles = array();
 | 
        
           |  |  | 192 |             foreach ($files as $archivepath => $file) {
 | 
        
           |  |  | 193 |                 // Update progress if required.
 | 
        
           |  |  | 194 |                 if ($progress) {
 | 
        
           |  |  | 195 |                     $progress->progress($done, self::PROGRESS_MAX);
 | 
        
           |  |  | 196 |                 }
 | 
        
           |  |  | 197 |                 $done += $progressperfile;
 | 
        
           |  |  | 198 |   | 
        
           |  |  | 199 |                 if (is_null($file)) {
 | 
        
           |  |  | 200 |                     // Empty directory record. Ensure it ends in a /.
 | 
        
           |  |  | 201 |                     if (!preg_match('~/$~', $archivepath)) {
 | 
        
           |  |  | 202 |                         $archivepath .= '/';
 | 
        
           |  |  | 203 |                     }
 | 
        
           |  |  | 204 |                     $expandedfiles[$archivepath] = null;
 | 
        
           |  |  | 205 |                 } else if (is_string($file)) {
 | 
        
           |  |  | 206 |                     // File specified as path on disk.
 | 
        
           |  |  | 207 |                     if (!$this->list_files_path($expandedfiles, $archivepath, $file,
 | 
        
           |  |  | 208 |                             $progress, $done)) {
 | 
        
           |  |  | 209 |                         gzclose($gz);
 | 
        
           |  |  | 210 |                         unlink($archivefile);
 | 
        
           |  |  | 211 |                         return false;
 | 
        
           |  |  | 212 |                     }
 | 
        
           |  |  | 213 |                 } else if (is_array($file)) {
 | 
        
           |  |  | 214 |                     // File specified as raw content in array.
 | 
        
           |  |  | 215 |                     $expandedfiles[$archivepath] = $file;
 | 
        
           |  |  | 216 |                 } else {
 | 
        
           |  |  | 217 |                     // File specified as stored_file object.
 | 
        
           |  |  | 218 |                     $this->list_files_stored($expandedfiles, $archivepath, $file);
 | 
        
           |  |  | 219 |                 }
 | 
        
           |  |  | 220 |             }
 | 
        
           |  |  | 221 |   | 
        
           |  |  | 222 |             // Store the list of files as a special file that is first in the
 | 
        
           |  |  | 223 |             // archive. This contains enough information to implement list_files
 | 
        
           |  |  | 224 |             // if required later.
 | 
        
           |  |  | 225 |             $list = self::ARCHIVE_INDEX_COUNT_PREFIX . count($expandedfiles) . "\n";
 | 
        
           |  |  | 226 |             $sizes = array();
 | 
        
           |  |  | 227 |             $mtimes = array();
 | 
        
           |  |  | 228 |             foreach ($expandedfiles as $archivepath => $file) {
 | 
        
           |  |  | 229 |                 // Check archivepath doesn't contain any non-ASCII characters.
 | 
        
           |  |  | 230 |                 if (!preg_match('~^[\x00-\xff]*$~', $archivepath)) {
 | 
        
           |  |  | 231 |                     throw new coding_exception(
 | 
        
           |  |  | 232 |                             'Non-ASCII paths not supported: ' . $archivepath);
 | 
        
           |  |  | 233 |                 }
 | 
        
           |  |  | 234 |   | 
        
           |  |  | 235 |                 // Build up the details.
 | 
        
           |  |  | 236 |                 $type = 'f';
 | 
        
           |  |  | 237 |                 $mtime = '?';
 | 
        
           |  |  | 238 |                 if (is_null($file)) {
 | 
        
           |  |  | 239 |                     $type = 'd';
 | 
        
           |  |  | 240 |                     $size = 0;
 | 
        
           |  |  | 241 |                 } else if (is_string($file)) {
 | 
        
           |  |  | 242 |                     $stat = stat($file);
 | 
        
           |  |  | 243 |                     $mtime = (int)$stat['mtime'];
 | 
        
           |  |  | 244 |                     $size = (int)$stat['size'];
 | 
        
           |  |  | 245 |                 } else if (is_array($file)) {
 | 
        
           |  |  | 246 |                     $size = (int)strlen(reset($file));
 | 
        
           |  |  | 247 |                 } else {
 | 
        
           |  |  | 248 |                     $mtime = (int)$file->get_timemodified();
 | 
        
           |  |  | 249 |                     $size = (int)$file->get_filesize();
 | 
        
           |  |  | 250 |                 }
 | 
        
           |  |  | 251 |                 $sizes[$archivepath] = $size;
 | 
        
           |  |  | 252 |                 $mtimes[$archivepath] = $mtime;
 | 
        
           |  |  | 253 |   | 
        
           |  |  | 254 |                 // Write a line in the index.
 | 
        
           |  |  | 255 |                 $list .= "$archivepath\t$type\t$size\t$mtime\n";
 | 
        
           |  |  | 256 |             }
 | 
        
           |  |  | 257 |   | 
        
           |  |  | 258 |             // The index file is optional; only write into archive if needed.
 | 
        
           |  |  | 259 |             if ($this->includeindex) {
 | 
        
           |  |  | 260 |                 // Put the index file into the archive.
 | 
        
           |  |  | 261 |                 $this->write_tar_entry($gz, self::ARCHIVE_INDEX_FILE, null, strlen($list), '?', $list);
 | 
        
           |  |  | 262 |             }
 | 
        
           |  |  | 263 |   | 
        
           |  |  | 264 |             // Update progress ready for main stage.
 | 
        
           |  |  | 265 |             $done = (int)(self::PROGRESS_MAX / 10);
 | 
        
           |  |  | 266 |             if ($progress) {
 | 
        
           |  |  | 267 |                 $progress->progress($done, self::PROGRESS_MAX);
 | 
        
           |  |  | 268 |             }
 | 
        
           |  |  | 269 |             if ($expandedfiles) {
 | 
        
           |  |  | 270 |                 // The remaining 9/10ths of progress represents these files.
 | 
        
           |  |  | 271 |                 $progressperfile = (int)((9 * self::PROGRESS_MAX) / (10 * count($expandedfiles)));
 | 
        
           |  |  | 272 |             } else {
 | 
        
           |  |  | 273 |                 $progressperfile = 1;
 | 
        
           |  |  | 274 |             }
 | 
        
           |  |  | 275 |   | 
        
           |  |  | 276 |             // Actually write entries for each file/directory.
 | 
        
           |  |  | 277 |             foreach ($expandedfiles as $archivepath => $file) {
 | 
        
           |  |  | 278 |                 if (is_null($file)) {
 | 
        
           |  |  | 279 |                     // Null entry indicates a directory.
 | 
        
           |  |  | 280 |                     $this->write_tar_entry($gz, $archivepath, null,
 | 
        
           |  |  | 281 |                             $sizes[$archivepath], $mtimes[$archivepath]);
 | 
        
           |  |  | 282 |                 } else if (is_string($file)) {
 | 
        
           |  |  | 283 |                     // String indicates an OS file.
 | 
        
           |  |  | 284 |                     $this->write_tar_entry($gz, $archivepath, $file,
 | 
        
           |  |  | 285 |                             $sizes[$archivepath], $mtimes[$archivepath], null, $progress, $done);
 | 
        
           |  |  | 286 |                 } else if (is_array($file)) {
 | 
        
           |  |  | 287 |                     // Array indicates in-memory data.
 | 
        
           |  |  | 288 |                     $data = reset($file);
 | 
        
           |  |  | 289 |                     $this->write_tar_entry($gz, $archivepath, null,
 | 
        
           |  |  | 290 |                             $sizes[$archivepath], $mtimes[$archivepath], $data, $progress, $done);
 | 
        
           |  |  | 291 |                 } else {
 | 
        
           |  |  | 292 |                     // Stored_file object.
 | 
        
           |  |  | 293 |                     $this->write_tar_entry($gz, $archivepath, $file->get_content_file_handle(),
 | 
        
           |  |  | 294 |                             $sizes[$archivepath], $mtimes[$archivepath], null, $progress, $done);
 | 
        
           |  |  | 295 |                 }
 | 
        
           |  |  | 296 |                 $done += $progressperfile;
 | 
        
           |  |  | 297 |                 if ($progress) {
 | 
        
           |  |  | 298 |                     $progress->progress($done, self::PROGRESS_MAX);
 | 
        
           |  |  | 299 |                 }
 | 
        
           |  |  | 300 |             }
 | 
        
           |  |  | 301 |   | 
        
           |  |  | 302 |             // Finish tar file with two empty 512-byte records.
 | 
        
           |  |  | 303 |             gzwrite($gz, str_pad('', 2 * self::TAR_BLOCK_SIZE, "\x00"));
 | 
        
           |  |  | 304 |             gzclose($gz);
 | 
        
           |  |  | 305 |             return true;
 | 
        
           |  |  | 306 |         } catch (Exception $e) {
 | 
        
           |  |  | 307 |             // If there is an exception, delete the in-progress file.
 | 
        
           |  |  | 308 |             gzclose($gz);
 | 
        
           |  |  | 309 |             unlink($archivefile);
 | 
        
           |  |  | 310 |             throw $e;
 | 
        
           |  |  | 311 |         }
 | 
        
           |  |  | 312 |     }
 | 
        
           |  |  | 313 |   | 
        
           |  |  | 314 |     /**
 | 
        
           |  |  | 315 |      * Writes a single tar file to the archive, including its header record and
 | 
        
           |  |  | 316 |      * then the file contents.
 | 
        
           |  |  | 317 |      *
 | 
        
           |  |  | 318 |      * @param resource $gz Gzip file
 | 
        
           |  |  | 319 |      * @param string $archivepath Full path of file within archive
 | 
        
           |  |  | 320 |      * @param string|resource $file Full path of file on disk or file handle or null if none
 | 
        
           |  |  | 321 |      * @param int $size Size or 0 for directories
 | 
        
           |  |  | 322 |      * @param int|string $mtime Time or ? if unknown
 | 
        
           |  |  | 323 |      * @param string $content Actual content of file to write (null if using $filepath)
 | 
        
           |  |  | 324 |      * @param file_progress $progress Progress indicator or null if none
 | 
        
           |  |  | 325 |      * @param int $done Value for progress indicator
 | 
        
           |  |  | 326 |      * @return bool True if OK
 | 
        
           |  |  | 327 |      * @throws coding_exception If names aren't valid
 | 
        
           |  |  | 328 |      */
 | 
        
           |  |  | 329 |     protected function write_tar_entry($gz, $archivepath, $file, $size, $mtime, $content = null,
 | 
        
           | 1441 | ariadna | 330 |             ?file_progress $progress = null, $done = 0) {
 | 
        
           | 1 | efrain | 331 |         // Header based on documentation of POSIX ustar format from:
 | 
        
           |  |  | 332 |         // http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current .
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 |         // For directories, ensure name ends in a slash.
 | 
        
           |  |  | 335 |         $directory = false;
 | 
        
           |  |  | 336 |         if ($size === 0 && is_null($file)) {
 | 
        
           |  |  | 337 |             $directory = true;
 | 
        
           |  |  | 338 |             if (!preg_match('~/$~', $archivepath)) {
 | 
        
           |  |  | 339 |                 $archivepath .= '/';
 | 
        
           |  |  | 340 |             }
 | 
        
           |  |  | 341 |             $mode = '755';
 | 
        
           |  |  | 342 |         } else {
 | 
        
           |  |  | 343 |             $mode = '644';
 | 
        
           |  |  | 344 |         }
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 |         // Split archivepath into name and prefix.
 | 
        
           |  |  | 347 |         $name = $archivepath;
 | 
        
           |  |  | 348 |         $prefix = '';
 | 
        
           |  |  | 349 |         while (strlen($name) > 100) {
 | 
        
           |  |  | 350 |             $slash = strpos($name, '/');
 | 
        
           |  |  | 351 |             if ($slash === false) {
 | 
        
           |  |  | 352 |                 throw new coding_exception(
 | 
        
           |  |  | 353 |                         'Name cannot fit length restrictions (> 100 characters): ' . $archivepath);
 | 
        
           |  |  | 354 |             }
 | 
        
           |  |  | 355 |   | 
        
           |  |  | 356 |             if ($prefix !== '') {
 | 
        
           |  |  | 357 |                 $prefix .= '/';
 | 
        
           |  |  | 358 |             }
 | 
        
           |  |  | 359 |             $prefix .= substr($name, 0, $slash);
 | 
        
           |  |  | 360 |             $name = substr($name, $slash + 1);
 | 
        
           |  |  | 361 |             if (strlen($prefix) > 155) {
 | 
        
           |  |  | 362 |                 throw new coding_exception(
 | 
        
           |  |  | 363 |                         'Name cannot fit length restrictions (path too long): ' . $archivepath);
 | 
        
           |  |  | 364 |             }
 | 
        
           |  |  | 365 |         }
 | 
        
           |  |  | 366 |   | 
        
           |  |  | 367 |         // Checksum performance is a bit slow because of having to call 'ord'
 | 
        
           |  |  | 368 |         // lots of times (it takes about 1/3 the time of the actual gzwrite
 | 
        
           |  |  | 369 |         // call). To improve performance of checksum calculation, we will
 | 
        
           |  |  | 370 |         // store all the non-zero, non-fixed bytes that need adding to the
 | 
        
           |  |  | 371 |         // checksum, and checksum only those bytes.
 | 
        
           |  |  | 372 |         $forchecksum = $name;
 | 
        
           |  |  | 373 |   | 
        
           |  |  | 374 |         // struct header_posix_ustar {
 | 
        
           |  |  | 375 |         //    char name[100];
 | 
        
           |  |  | 376 |         $header = str_pad($name, 100, "\x00");
 | 
        
           |  |  | 377 |   | 
        
           |  |  | 378 |         //    char mode[8];
 | 
        
           |  |  | 379 |         //    char uid[8];
 | 
        
           |  |  | 380 |         //    char gid[8];
 | 
        
           |  |  | 381 |         $header .= '0000' . $mode . "\x000000000\x000000000\x00";
 | 
        
           |  |  | 382 |         $forchecksum .= $mode;
 | 
        
           |  |  | 383 |   | 
        
           |  |  | 384 |         //    char size[12];
 | 
        
           |  |  | 385 |         $octalsize = decoct($size);
 | 
        
           |  |  | 386 |         if (strlen($octalsize) > 11) {
 | 
        
           |  |  | 387 |             throw new coding_exception(
 | 
        
           |  |  | 388 |                     'File too large for .tar file: ' . $archivepath . ' (' . $size . ' bytes)');
 | 
        
           |  |  | 389 |         }
 | 
        
           |  |  | 390 |         $paddedsize = str_pad($octalsize, 11, '0', STR_PAD_LEFT);
 | 
        
           |  |  | 391 |         $forchecksum .= $paddedsize;
 | 
        
           |  |  | 392 |         $header .= $paddedsize . "\x00";
 | 
        
           |  |  | 393 |   | 
        
           |  |  | 394 |         //    char mtime[12];
 | 
        
           |  |  | 395 |         if ($mtime === '?') {
 | 
        
           |  |  | 396 |             // Use a default timestamp rather than zero; GNU tar outputs
 | 
        
           |  |  | 397 |             // warnings about zeroes here.
 | 
        
           |  |  | 398 |             $mtime = self::DEFAULT_TIMESTAMP;
 | 
        
           |  |  | 399 |         }
 | 
        
           |  |  | 400 |         $octaltime = decoct($mtime);
 | 
        
           |  |  | 401 |         $paddedtime = str_pad($octaltime, 11, '0', STR_PAD_LEFT);
 | 
        
           |  |  | 402 |         $forchecksum .= $paddedtime;
 | 
        
           |  |  | 403 |         $header .= $paddedtime . "\x00";
 | 
        
           |  |  | 404 |   | 
        
           |  |  | 405 |         //    char checksum[8];
 | 
        
           |  |  | 406 |         // Checksum needs to be completed later.
 | 
        
           |  |  | 407 |         $header .= '        ';
 | 
        
           |  |  | 408 |   | 
        
           |  |  | 409 |         //    char typeflag[1];
 | 
        
           |  |  | 410 |         $typeflag = $directory ? '5' : '0';
 | 
        
           |  |  | 411 |         $forchecksum .= $typeflag;
 | 
        
           |  |  | 412 |         $header .= $typeflag;
 | 
        
           |  |  | 413 |   | 
        
           |  |  | 414 |         //    char linkname[100];
 | 
        
           |  |  | 415 |         $header .= str_pad('', 100, "\x00");
 | 
        
           |  |  | 416 |   | 
        
           |  |  | 417 |         //    char magic[6];
 | 
        
           |  |  | 418 |         //    char version[2];
 | 
        
           |  |  | 419 |         $header .= "ustar\x0000";
 | 
        
           |  |  | 420 |   | 
        
           |  |  | 421 |         //    char uname[32];
 | 
        
           |  |  | 422 |         //    char gname[32];
 | 
        
           |  |  | 423 |         //    char devmajor[8];
 | 
        
           |  |  | 424 |         //    char devminor[8];
 | 
        
           |  |  | 425 |         $header .= str_pad('', 80, "\x00");
 | 
        
           |  |  | 426 |   | 
        
           |  |  | 427 |         //    char prefix[155];
 | 
        
           |  |  | 428 |         //    char pad[12];
 | 
        
           |  |  | 429 |         $header .= str_pad($prefix, 167, "\x00");
 | 
        
           |  |  | 430 |         $forchecksum .= $prefix;
 | 
        
           |  |  | 431 |   | 
        
           |  |  | 432 |         // };
 | 
        
           |  |  | 433 |   | 
        
           |  |  | 434 |         // We have now calculated the header, but without the checksum. To work
 | 
        
           |  |  | 435 |         // out the checksum, sum all the bytes that aren't fixed or zero, and add
 | 
        
           |  |  | 436 |         // to a standard value that contains all the fixed bytes.
 | 
        
           |  |  | 437 |   | 
        
           |  |  | 438 |         // The fixed non-zero bytes are:
 | 
        
           |  |  | 439 |         //
 | 
        
           |  |  | 440 |         // '000000000000000000        ustar00'
 | 
        
           |  |  | 441 |         // mode (except 3 digits), uid, gid, checksum space, magic number, version
 | 
        
           |  |  | 442 |         //
 | 
        
           |  |  | 443 |         // To calculate the number, call the calculate_checksum function on the
 | 
        
           |  |  | 444 |         // above string. The result is 1775.
 | 
        
           |  |  | 445 |         $checksum = 1775 + self::calculate_checksum($forchecksum);
 | 
        
           |  |  | 446 |   | 
        
           |  |  | 447 |         $octalchecksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . "\x00 ";
 | 
        
           |  |  | 448 |   | 
        
           |  |  | 449 |         // Slot it into place in the header.
 | 
        
           |  |  | 450 |         $header = substr($header, 0, 148) . $octalchecksum . substr($header, 156);
 | 
        
           |  |  | 451 |   | 
        
           |  |  | 452 |         if (strlen($header) != self::TAR_BLOCK_SIZE) {
 | 
        
           |  |  | 453 |             throw new coding_exception('Header block wrong size!!!!!');
 | 
        
           |  |  | 454 |         }
 | 
        
           |  |  | 455 |   | 
        
           |  |  | 456 |         // Awesome, now write out the header.
 | 
        
           |  |  | 457 |         gzwrite($gz, $header);
 | 
        
           |  |  | 458 |   | 
        
           |  |  | 459 |         // Special pre-handler for OS filename.
 | 
        
           |  |  | 460 |         if (is_string($file)) {
 | 
        
           |  |  | 461 |             $file = fopen($file, 'rb');
 | 
        
           |  |  | 462 |             if (!$file) {
 | 
        
           |  |  | 463 |                 return false;
 | 
        
           |  |  | 464 |             }
 | 
        
           |  |  | 465 |         }
 | 
        
           |  |  | 466 |   | 
        
           |  |  | 467 |         if ($content !== null) {
 | 
        
           |  |  | 468 |             // Write in-memory content if any.
 | 
        
           |  |  | 469 |             if (strlen($content) !== $size) {
 | 
        
           |  |  | 470 |                 throw new coding_exception('Mismatch between provided sizes: ' . $archivepath);
 | 
        
           |  |  | 471 |             }
 | 
        
           |  |  | 472 |             gzwrite($gz, $content);
 | 
        
           |  |  | 473 |         } else if ($file !== null) {
 | 
        
           |  |  | 474 |             // Write file content if any, using a 64KB buffer.
 | 
        
           |  |  | 475 |             $written = 0;
 | 
        
           |  |  | 476 |             $chunks = 0;
 | 
        
           |  |  | 477 |             while (true) {
 | 
        
           |  |  | 478 |                 $data = fread($file, 65536);
 | 
        
           |  |  | 479 |                 if ($data === false || strlen($data) == 0) {
 | 
        
           |  |  | 480 |                     break;
 | 
        
           |  |  | 481 |                 }
 | 
        
           |  |  | 482 |                 $written += gzwrite($gz, $data);
 | 
        
           |  |  | 483 |   | 
        
           |  |  | 484 |                 // After every megabyte of large files, update the progress
 | 
        
           |  |  | 485 |                 // tracker (so there are no long gaps without progress).
 | 
        
           |  |  | 486 |                 $chunks++;
 | 
        
           |  |  | 487 |                 if ($chunks == 16) {
 | 
        
           |  |  | 488 |                     $chunks = 0;
 | 
        
           |  |  | 489 |                     if ($progress) {
 | 
        
           |  |  | 490 |                         // This call always has the same values, but that gives
 | 
        
           |  |  | 491 |                         // the tracker a chance to indicate indeterminate
 | 
        
           |  |  | 492 |                         // progress and output something to avoid timeouts.
 | 
        
           |  |  | 493 |                         $progress->progress($done, self::PROGRESS_MAX);
 | 
        
           |  |  | 494 |                     }
 | 
        
           |  |  | 495 |                 }
 | 
        
           |  |  | 496 |             }
 | 
        
           |  |  | 497 |             fclose($file);
 | 
        
           |  |  | 498 |   | 
        
           |  |  | 499 |             if ($written !== $size) {
 | 
        
           |  |  | 500 |                 throw new coding_exception('Mismatch between provided sizes: ' . $archivepath .
 | 
        
           |  |  | 501 |                         ' (was ' . $written . ', expected ' . $size . ')');
 | 
        
           |  |  | 502 |             }
 | 
        
           |  |  | 503 |         } else if ($size != 0) {
 | 
        
           |  |  | 504 |             throw new coding_exception('Missing data file handle for non-empty file');
 | 
        
           |  |  | 505 |         }
 | 
        
           |  |  | 506 |   | 
        
           |  |  | 507 |         // Pad out final 512-byte block in file, if applicable.
 | 
        
           |  |  | 508 |         $leftover = self::TAR_BLOCK_SIZE - ($size % self::TAR_BLOCK_SIZE);
 | 
        
           |  |  | 509 |         if ($leftover == 512) {
 | 
        
           |  |  | 510 |             $leftover = 0;
 | 
        
           |  |  | 511 |         } else {
 | 
        
           |  |  | 512 |             gzwrite($gz, str_pad('', $leftover, "\x00"));
 | 
        
           |  |  | 513 |         }
 | 
        
           |  |  | 514 |   | 
        
           |  |  | 515 |         return true;
 | 
        
           |  |  | 516 |     }
 | 
        
           |  |  | 517 |   | 
        
           |  |  | 518 |     /**
 | 
        
           |  |  | 519 |      * Calculates a checksum by summing all characters of the binary string
 | 
        
           |  |  | 520 |      * (treating them as unsigned numbers).
 | 
        
           |  |  | 521 |      *
 | 
        
           |  |  | 522 |      * @param string $str Input string
 | 
        
           |  |  | 523 |      * @return int Checksum
 | 
        
           |  |  | 524 |      */
 | 
        
           |  |  | 525 |     protected static function calculate_checksum($str) {
 | 
        
           |  |  | 526 |         $checksum = 0;
 | 
        
           |  |  | 527 |         $checklength = strlen($str);
 | 
        
           |  |  | 528 |         for ($i = 0; $i < $checklength; $i++) {
 | 
        
           |  |  | 529 |             $checksum += ord($str[$i]);
 | 
        
           |  |  | 530 |         }
 | 
        
           |  |  | 531 |         return $checksum;
 | 
        
           |  |  | 532 |     }
 | 
        
           |  |  | 533 |   | 
        
           |  |  | 534 |     /**
 | 
        
           |  |  | 535 |      * Based on an OS path, adds either that path (if it's a file) or
 | 
        
           |  |  | 536 |      * all its children (if it's a directory) into the list of files to
 | 
        
           |  |  | 537 |      * archive.
 | 
        
           |  |  | 538 |      *
 | 
        
           |  |  | 539 |      * If a progress indicator is supplied and if this corresponds to a
 | 
        
           |  |  | 540 |      * directory, then it will be repeatedly called with the same values. This
 | 
        
           |  |  | 541 |      * allows the progress handler to respond in some way to avoid timeouts
 | 
        
           |  |  | 542 |      * if required.
 | 
        
           |  |  | 543 |      *
 | 
        
           |  |  | 544 |      * @param array $expandedfiles List of all files to archive (output)
 | 
        
           |  |  | 545 |      * @param string $archivepath Current path within archive
 | 
        
           |  |  | 546 |      * @param string $path OS path on disk
 | 
        
           |  |  | 547 |      * @param file_progress|null $progress Progress indicator or null if none
 | 
        
           |  |  | 548 |      * @param int $done Value for progress indicator
 | 
        
           |  |  | 549 |      * @return bool True if successful
 | 
        
           |  |  | 550 |      */
 | 
        
           |  |  | 551 |     protected function list_files_path(array &$expandedfiles, $archivepath, $path,
 | 
        
           |  |  | 552 |             ?file_progress $progress , $done) {
 | 
        
           |  |  | 553 |         if (is_dir($path)) {
 | 
        
           |  |  | 554 |             // Unless we're using this directory as archive root, add a
 | 
        
           |  |  | 555 |             // directory entry.
 | 
        
           |  |  | 556 |             if ($archivepath != '') {
 | 
        
           |  |  | 557 |                 // Add directory-creation record.
 | 
        
           |  |  | 558 |                 $expandedfiles[$archivepath . '/'] = null;
 | 
        
           |  |  | 559 |             }
 | 
        
           |  |  | 560 |   | 
        
           |  |  | 561 |             // Loop through directory contents and recurse.
 | 
        
           |  |  | 562 |             if (!$handle = opendir($path)) {
 | 
        
           |  |  | 563 |                 return false;
 | 
        
           |  |  | 564 |             }
 | 
        
           |  |  | 565 |             while (false !== ($entry = readdir($handle))) {
 | 
        
           |  |  | 566 |                 if ($entry === '.' || $entry === '..') {
 | 
        
           |  |  | 567 |                     continue;
 | 
        
           |  |  | 568 |                 }
 | 
        
           |  |  | 569 |                 $result = $this->list_files_path($expandedfiles,
 | 
        
           |  |  | 570 |                         $archivepath . '/' . $entry, $path . '/' . $entry,
 | 
        
           |  |  | 571 |                         $progress, $done);
 | 
        
           |  |  | 572 |                 if (!$result) {
 | 
        
           |  |  | 573 |                     return false;
 | 
        
           |  |  | 574 |                 }
 | 
        
           |  |  | 575 |                 if ($progress) {
 | 
        
           |  |  | 576 |                     $progress->progress($done, self::PROGRESS_MAX);
 | 
        
           |  |  | 577 |                 }
 | 
        
           |  |  | 578 |             }
 | 
        
           |  |  | 579 |             closedir($handle);
 | 
        
           |  |  | 580 |         } else {
 | 
        
           |  |  | 581 |             // Just add it to list.
 | 
        
           |  |  | 582 |             $expandedfiles[$archivepath] = $path;
 | 
        
           |  |  | 583 |         }
 | 
        
           |  |  | 584 |         return true;
 | 
        
           |  |  | 585 |     }
 | 
        
           |  |  | 586 |   | 
        
           |  |  | 587 |     /**
 | 
        
           |  |  | 588 |      * Based on a stored_file objects, adds either that file (if it's a file) or
 | 
        
           |  |  | 589 |      * all its children (if it's a directory) into the list of files to
 | 
        
           |  |  | 590 |      * archive.
 | 
        
           |  |  | 591 |      *
 | 
        
           |  |  | 592 |      * If a progress indicator is supplied and if this corresponds to a
 | 
        
           |  |  | 593 |      * directory, then it will be repeatedly called with the same values. This
 | 
        
           |  |  | 594 |      * allows the progress handler to respond in some way to avoid timeouts
 | 
        
           |  |  | 595 |      * if required.
 | 
        
           |  |  | 596 |      *
 | 
        
           |  |  | 597 |      * @param array $expandedfiles List of all files to archive (output)
 | 
        
           |  |  | 598 |      * @param string $archivepath Current path within archive
 | 
        
           |  |  | 599 |      * @param stored_file $file File object
 | 
        
           |  |  | 600 |      */
 | 
        
           |  |  | 601 |     protected function list_files_stored(array &$expandedfiles, $archivepath, stored_file $file) {
 | 
        
           |  |  | 602 |         if ($file->is_directory()) {
 | 
        
           |  |  | 603 |             // Add a directory-creation record.
 | 
        
           |  |  | 604 |             $expandedfiles[$archivepath . '/'] = null;
 | 
        
           |  |  | 605 |   | 
        
           |  |  | 606 |             // Loop through directory contents (this is a recursive collection
 | 
        
           |  |  | 607 |             // of all children not just one directory).
 | 
        
           |  |  | 608 |             $fs = get_file_storage();
 | 
        
           |  |  | 609 |             $baselength = strlen($file->get_filepath());
 | 
        
           |  |  | 610 |             $files = $fs->get_directory_files(
 | 
        
           |  |  | 611 |                     $file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
 | 
        
           |  |  | 612 |                     $file->get_filepath(), true, true);
 | 
        
           |  |  | 613 |             foreach ($files as $childfile) {
 | 
        
           |  |  | 614 |                 // Get full pathname after original part.
 | 
        
           |  |  | 615 |                 $path = $childfile->get_filepath();
 | 
        
           |  |  | 616 |                 $path = substr($path, $baselength);
 | 
        
           |  |  | 617 |                 $path = $archivepath . '/' . $path;
 | 
        
           |  |  | 618 |                 if ($childfile->is_directory()) {
 | 
        
           |  |  | 619 |                     $childfile = null;
 | 
        
           |  |  | 620 |                 } else {
 | 
        
           |  |  | 621 |                     $path .= $childfile->get_filename();
 | 
        
           |  |  | 622 |                 }
 | 
        
           |  |  | 623 |                 $expandedfiles[$path] = $childfile;
 | 
        
           |  |  | 624 |             }
 | 
        
           |  |  | 625 |         } else {
 | 
        
           |  |  | 626 |             // Just add it to list.
 | 
        
           |  |  | 627 |             $expandedfiles[$archivepath] = $file;
 | 
        
           |  |  | 628 |         }
 | 
        
           |  |  | 629 |     }
 | 
        
           |  |  | 630 |   | 
        
           |  |  | 631 |     /**
 | 
        
           |  |  | 632 |      * Extract file to given file path (real OS filesystem), existing files are overwritten.
 | 
        
           |  |  | 633 |      *
 | 
        
           |  |  | 634 |      * @param stored_file|string $archivefile full pathname of zip file or stored_file instance
 | 
        
           |  |  | 635 |      * @param string $pathname target directory
 | 
        
           |  |  | 636 |      * @param array $onlyfiles only extract files present in the array
 | 
        
           |  |  | 637 |      * @param file_progress $progress Progress indicator callback or null if not required
 | 
        
           |  |  | 638 |      * @param bool $returnbool Whether to return a basic true/false indicating error state, or full per-file error
 | 
        
           |  |  | 639 |      * details.
 | 
        
           |  |  | 640 |      * @return array list of processed files (name=>true)
 | 
        
           |  |  | 641 |      * @throws moodle_exception If error
 | 
        
           |  |  | 642 |      */
 | 
        
           |  |  | 643 |     public function extract_to_pathname($archivefile, $pathname,
 | 
        
           | 1441 | ariadna | 644 |             ?array $onlyfiles = null, ?file_progress $progress = null, $returnbool = false) {
 | 
        
           | 1 | efrain | 645 |         $extractor = new tgz_extractor($archivefile);
 | 
        
           |  |  | 646 |         try {
 | 
        
           |  |  | 647 |             $result = $extractor->extract(
 | 
        
           |  |  | 648 |                     new tgz_packer_extract_to_pathname($pathname, $onlyfiles), $progress);
 | 
        
           |  |  | 649 |             if ($returnbool) {
 | 
        
           |  |  | 650 |                 if (!is_array($result)) {
 | 
        
           |  |  | 651 |                     return false;
 | 
        
           |  |  | 652 |                 }
 | 
        
           |  |  | 653 |                 foreach ($result as $status) {
 | 
        
           |  |  | 654 |                     if ($status !== true) {
 | 
        
           |  |  | 655 |                         return false;
 | 
        
           |  |  | 656 |                     }
 | 
        
           |  |  | 657 |                 }
 | 
        
           |  |  | 658 |                 return true;
 | 
        
           |  |  | 659 |             } else {
 | 
        
           |  |  | 660 |                 return $result;
 | 
        
           |  |  | 661 |             }
 | 
        
           |  |  | 662 |         } catch (moodle_exception $e) {
 | 
        
           |  |  | 663 |             if ($returnbool) {
 | 
        
           |  |  | 664 |                 return false;
 | 
        
           |  |  | 665 |             } else {
 | 
        
           |  |  | 666 |                 throw $e;
 | 
        
           |  |  | 667 |             }
 | 
        
           |  |  | 668 |         }
 | 
        
           |  |  | 669 |     }
 | 
        
           |  |  | 670 |   | 
        
           |  |  | 671 |     /**
 | 
        
           |  |  | 672 |      * Extract file to given file path (real OS filesystem), existing files are overwritten.
 | 
        
           |  |  | 673 |      *
 | 
        
           |  |  | 674 |      * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
 | 
        
           |  |  | 675 |      * @param int $contextid context ID
 | 
        
           |  |  | 676 |      * @param string $component component
 | 
        
           |  |  | 677 |      * @param string $filearea file area
 | 
        
           |  |  | 678 |      * @param int $itemid item ID
 | 
        
           |  |  | 679 |      * @param string $pathbase file path
 | 
        
           |  |  | 680 |      * @param int $userid user ID
 | 
        
           |  |  | 681 |      * @param file_progress $progress Progress indicator callback or null if not required
 | 
        
           |  |  | 682 |      * @return array list of processed files (name=>true)
 | 
        
           |  |  | 683 |      * @throws moodle_exception If error
 | 
        
           |  |  | 684 |      */
 | 
        
           |  |  | 685 |     public function extract_to_storage($archivefile, $contextid,
 | 
        
           |  |  | 686 |             $component, $filearea, $itemid, $pathbase, $userid = null,
 | 
        
           | 1441 | ariadna | 687 |             ?file_progress $progress = null) {
 | 
        
           | 1 | efrain | 688 |         $extractor = new tgz_extractor($archivefile);
 | 
        
           |  |  | 689 |         return $extractor->extract(
 | 
        
           |  |  | 690 |                 new tgz_packer_extract_to_storage($contextid, $component,
 | 
        
           |  |  | 691 |                     $filearea, $itemid, $pathbase, $userid), $progress);
 | 
        
           |  |  | 692 |     }
 | 
        
           |  |  | 693 |   | 
        
           |  |  | 694 |     /**
 | 
        
           |  |  | 695 |      * Returns array of info about all files in archive.
 | 
        
           |  |  | 696 |      *
 | 
        
           |  |  | 697 |      * @param string|stored_file $archivefile
 | 
        
           |  |  | 698 |      * @return array of file infos
 | 
        
           |  |  | 699 |      */
 | 
        
           |  |  | 700 |     public function list_files($archivefile) {
 | 
        
           |  |  | 701 |         $extractor = new tgz_extractor($archivefile);
 | 
        
           |  |  | 702 |         return $extractor->list_files();
 | 
        
           |  |  | 703 |     }
 | 
        
           |  |  | 704 |   | 
        
           |  |  | 705 |     /**
 | 
        
           |  |  | 706 |      * Checks whether a file appears to be a .tar.gz file.
 | 
        
           |  |  | 707 |      *
 | 
        
           |  |  | 708 |      * @param string|stored_file $archivefile
 | 
        
           |  |  | 709 |      * @return bool True if file contains the gzip magic number
 | 
        
           |  |  | 710 |      */
 | 
        
           |  |  | 711 |     public static function is_tgz_file($archivefile) {
 | 
        
           |  |  | 712 |         if (is_a($archivefile, 'stored_file')) {
 | 
        
           |  |  | 713 |             $fp = $archivefile->get_content_file_handle();
 | 
        
           |  |  | 714 |         } else {
 | 
        
           |  |  | 715 |             $fp = fopen($archivefile, 'rb');
 | 
        
           |  |  | 716 |         }
 | 
        
           |  |  | 717 |         $firstbytes = fread($fp, 2);
 | 
        
           |  |  | 718 |         fclose($fp);
 | 
        
           |  |  | 719 |         return ($firstbytes[0] == "\x1f" && $firstbytes[1] == "\x8b");
 | 
        
           |  |  | 720 |     }
 | 
        
           |  |  | 721 |   | 
        
           |  |  | 722 |     /**
 | 
        
           |  |  | 723 |      * The zlib extension is required for this packer to work. This is a single
 | 
        
           |  |  | 724 |      * location for the code to check whether the extension is available.
 | 
        
           |  |  | 725 |      *
 | 
        
           |  |  | 726 |      * @deprecated since 2.7 Always true because zlib extension is now required.
 | 
        
           |  |  | 727 |      *
 | 
        
           |  |  | 728 |      * @return bool True if the zlib extension is available OK
 | 
        
           |  |  | 729 |      */
 | 
        
           |  |  | 730 |     public static function has_required_extension() {
 | 
        
           |  |  | 731 |         return extension_loaded('zlib');
 | 
        
           |  |  | 732 |     }
 | 
        
           |  |  | 733 | }
 | 
        
           |  |  | 734 |   | 
        
           |  |  | 735 |   | 
        
           |  |  | 736 | /**
 | 
        
           |  |  | 737 |  * Handles extraction to pathname.
 | 
        
           |  |  | 738 |  */
 | 
        
           |  |  | 739 | class tgz_packer_extract_to_pathname implements tgz_extractor_handler {
 | 
        
           |  |  | 740 |     /**
 | 
        
           |  |  | 741 |      * @var string Target directory for extract.
 | 
        
           |  |  | 742 |      */
 | 
        
           |  |  | 743 |     protected $pathname;
 | 
        
           |  |  | 744 |     /**
 | 
        
           |  |  | 745 |      * @var array Array of files to extract (other files are skipped).
 | 
        
           |  |  | 746 |      */
 | 
        
           |  |  | 747 |     protected $onlyfiles;
 | 
        
           |  |  | 748 |   | 
        
           |  |  | 749 |     /**
 | 
        
           |  |  | 750 |      * Constructor.
 | 
        
           |  |  | 751 |      *
 | 
        
           |  |  | 752 |      * @param string $pathname target directory
 | 
        
           |  |  | 753 |      * @param array $onlyfiles only extract files present in the array
 | 
        
           |  |  | 754 |      */
 | 
        
           | 1441 | ariadna | 755 |     public function __construct($pathname, ?array $onlyfiles = null) {
 | 
        
           | 1 | efrain | 756 |         $this->pathname = $pathname;
 | 
        
           |  |  | 757 |         $this->onlyfiles = $onlyfiles;
 | 
        
           |  |  | 758 |     }
 | 
        
           |  |  | 759 |   | 
        
           |  |  | 760 |     /**
 | 
        
           |  |  | 761 |      * @see tgz_extractor_handler::tgz_start_file()
 | 
        
           |  |  | 762 |      */
 | 
        
           |  |  | 763 |     public function tgz_start_file($archivepath) {
 | 
        
           |  |  | 764 |         // Check file restriction.
 | 
        
           |  |  | 765 |         if ($this->onlyfiles !== null && !in_array($archivepath, $this->onlyfiles)) {
 | 
        
           |  |  | 766 |             return null;
 | 
        
           |  |  | 767 |         }
 | 
        
           |  |  | 768 |         // Ensure directory exists and prepare filename.
 | 
        
           |  |  | 769 |         $fullpath = $this->pathname . '/' . $archivepath;
 | 
        
           |  |  | 770 |         check_dir_exists(dirname($fullpath));
 | 
        
           |  |  | 771 |         return $fullpath;
 | 
        
           |  |  | 772 |     }
 | 
        
           |  |  | 773 |   | 
        
           |  |  | 774 |     /**
 | 
        
           |  |  | 775 |      * @see tgz_extractor_handler::tgz_end_file()
 | 
        
           |  |  | 776 |      */
 | 
        
           |  |  | 777 |     public function tgz_end_file($archivepath, $realpath) {
 | 
        
           |  |  | 778 |         // Do nothing.
 | 
        
           |  |  | 779 |     }
 | 
        
           |  |  | 780 |   | 
        
           |  |  | 781 |     /**
 | 
        
           |  |  | 782 |      * @see tgz_extractor_handler::tgz_directory()
 | 
        
           |  |  | 783 |      */
 | 
        
           |  |  | 784 |     public function tgz_directory($archivepath, $mtime) {
 | 
        
           |  |  | 785 |         // Check file restriction.
 | 
        
           |  |  | 786 |         if ($this->onlyfiles !== null && !in_array($archivepath, $this->onlyfiles)) {
 | 
        
           |  |  | 787 |             return false;
 | 
        
           |  |  | 788 |         }
 | 
        
           |  |  | 789 |         // Ensure directory exists.
 | 
        
           |  |  | 790 |         $fullpath = $this->pathname . '/' . $archivepath;
 | 
        
           |  |  | 791 |         check_dir_exists($fullpath);
 | 
        
           |  |  | 792 |         return true;
 | 
        
           |  |  | 793 |     }
 | 
        
           |  |  | 794 | }
 | 
        
           |  |  | 795 |   | 
        
           |  |  | 796 |   | 
        
           |  |  | 797 | /**
 | 
        
           |  |  | 798 |  * Handles extraction to file storage.
 | 
        
           |  |  | 799 |  */
 | 
        
           |  |  | 800 | class tgz_packer_extract_to_storage implements tgz_extractor_handler {
 | 
        
           |  |  | 801 |     /**
 | 
        
           |  |  | 802 |      * @var string Path to temp file.
 | 
        
           |  |  | 803 |      */
 | 
        
           |  |  | 804 |     protected $tempfile;
 | 
        
           |  |  | 805 |   | 
        
           |  |  | 806 |     /**
 | 
        
           |  |  | 807 |      * @var int Context id for files.
 | 
        
           |  |  | 808 |      */
 | 
        
           |  |  | 809 |     protected $contextid;
 | 
        
           |  |  | 810 |     /**
 | 
        
           |  |  | 811 |      * @var string Component name for files.
 | 
        
           |  |  | 812 |      */
 | 
        
           |  |  | 813 |     protected $component;
 | 
        
           |  |  | 814 |     /**
 | 
        
           |  |  | 815 |      * @var string File area for files.
 | 
        
           |  |  | 816 |      */
 | 
        
           |  |  | 817 |     protected $filearea;
 | 
        
           |  |  | 818 |     /**
 | 
        
           |  |  | 819 |      * @var int Item ID for files.
 | 
        
           |  |  | 820 |      */
 | 
        
           |  |  | 821 |     protected $itemid;
 | 
        
           |  |  | 822 |     /**
 | 
        
           |  |  | 823 |      * @var string Base path for files (subfolders will go inside this).
 | 
        
           |  |  | 824 |      */
 | 
        
           |  |  | 825 |     protected $pathbase;
 | 
        
           |  |  | 826 |     /**
 | 
        
           |  |  | 827 |      * @var int User id for files or null if none.
 | 
        
           |  |  | 828 |      */
 | 
        
           |  |  | 829 |     protected $userid;
 | 
        
           |  |  | 830 |   | 
        
           |  |  | 831 |     /**
 | 
        
           |  |  | 832 |      * Constructor.
 | 
        
           |  |  | 833 |      *
 | 
        
           |  |  | 834 |      * @param int $contextid Context id for files.
 | 
        
           |  |  | 835 |      * @param string $component Component name for files.
 | 
        
           |  |  | 836 |      * @param string $filearea File area for files.
 | 
        
           |  |  | 837 |      * @param int $itemid Item ID for files.
 | 
        
           |  |  | 838 |      * @param string $pathbase Base path for files (subfolders will go inside this).
 | 
        
           |  |  | 839 |      * @param int $userid User id for files or null if none.
 | 
        
           |  |  | 840 |      */
 | 
        
           |  |  | 841 |     public function __construct($contextid, $component, $filearea, $itemid, $pathbase, $userid) {
 | 
        
           |  |  | 842 |         global $CFG;
 | 
        
           |  |  | 843 |   | 
        
           |  |  | 844 |         // Store all data.
 | 
        
           |  |  | 845 |         $this->contextid = $contextid;
 | 
        
           |  |  | 846 |         $this->component = $component;
 | 
        
           |  |  | 847 |         $this->filearea = $filearea;
 | 
        
           |  |  | 848 |         $this->itemid = $itemid;
 | 
        
           |  |  | 849 |         $this->pathbase = $pathbase;
 | 
        
           |  |  | 850 |         $this->userid = $userid;
 | 
        
           |  |  | 851 |   | 
        
           |  |  | 852 |         // Obtain temp filename.
 | 
        
           |  |  | 853 |         $tempfolder = $CFG->tempdir . '/core_files';
 | 
        
           |  |  | 854 |         check_dir_exists($tempfolder);
 | 
        
           |  |  | 855 |         $this->tempfile = tempnam($tempfolder, '.dat');
 | 
        
           |  |  | 856 |     }
 | 
        
           |  |  | 857 |   | 
        
           |  |  | 858 |     /**
 | 
        
           |  |  | 859 |      * @see tgz_extractor_handler::tgz_start_file()
 | 
        
           |  |  | 860 |      */
 | 
        
           |  |  | 861 |     public function tgz_start_file($archivepath) {
 | 
        
           |  |  | 862 |         // All files are stored in the same filename.
 | 
        
           |  |  | 863 |         return $this->tempfile;
 | 
        
           |  |  | 864 |     }
 | 
        
           |  |  | 865 |   | 
        
           |  |  | 866 |     /**
 | 
        
           |  |  | 867 |      * @see tgz_extractor_handler::tgz_end_file()
 | 
        
           |  |  | 868 |      */
 | 
        
           |  |  | 869 |     public function tgz_end_file($archivepath, $realpath) {
 | 
        
           |  |  | 870 |         // Place temp file into storage.
 | 
        
           |  |  | 871 |         $fs = get_file_storage();
 | 
        
           |  |  | 872 |         $filerecord = array('contextid' => $this->contextid, 'component' => $this->component,
 | 
        
           |  |  | 873 |                 'filearea' => $this->filearea, 'itemid' => $this->itemid);
 | 
        
           |  |  | 874 |         $filerecord['filepath'] = $this->pathbase . dirname($archivepath) . '/';
 | 
        
           |  |  | 875 |         $filerecord['filename'] = basename($archivepath);
 | 
        
           |  |  | 876 |         if ($this->userid) {
 | 
        
           |  |  | 877 |             $filerecord['userid'] = $this->userid;
 | 
        
           |  |  | 878 |         }
 | 
        
           |  |  | 879 |         // Delete existing file (if any) and create new one.
 | 
        
           |  |  | 880 |         tgz_packer::delete_existing_file_record($fs, $filerecord);
 | 
        
           |  |  | 881 |         $fs->create_file_from_pathname($filerecord, $this->tempfile);
 | 
        
           |  |  | 882 |         unlink($this->tempfile);
 | 
        
           |  |  | 883 |     }
 | 
        
           |  |  | 884 |   | 
        
           |  |  | 885 |     /**
 | 
        
           |  |  | 886 |      * @see tgz_extractor_handler::tgz_directory()
 | 
        
           |  |  | 887 |      */
 | 
        
           |  |  | 888 |     public function tgz_directory($archivepath, $mtime) {
 | 
        
           |  |  | 889 |         // Standardise path.
 | 
        
           |  |  | 890 |         if (!preg_match('~/$~', $archivepath)) {
 | 
        
           |  |  | 891 |             $archivepath .= '/';
 | 
        
           |  |  | 892 |         }
 | 
        
           |  |  | 893 |         // Create directory if it doesn't already exist.
 | 
        
           |  |  | 894 |         $fs = get_file_storage();
 | 
        
           |  |  | 895 |         if (!$fs->file_exists($this->contextid, $this->component, $this->filearea, $this->itemid,
 | 
        
           |  |  | 896 |                 $this->pathbase . $archivepath, '.')) {
 | 
        
           |  |  | 897 |             $fs->create_directory($this->contextid, $this->component, $this->filearea, $this->itemid,
 | 
        
           |  |  | 898 |                     $this->pathbase . $archivepath);
 | 
        
           |  |  | 899 |         }
 | 
        
           |  |  | 900 |         return true;
 | 
        
           |  |  | 901 |     }
 | 
        
           |  |  | 902 | }
 |