Esempio n. 1
0
 /**
  * Extracts or lists the archive depending on $this->listmode.
  *
  * @param tgz_extractor_handler $handler Optional handler
  * @param file_progress $progress Optional progress reporting
  * @throws moodle_exception If there is any error processing the archive
  */
 protected function extract_or_list(tgz_extractor_handler $handler = null, file_progress $progress = null)
 {
     // Open archive.
     if ($this->storedfile) {
         $gz = $this->storedfile->get_content_file_handle(stored_file::FILE_HANDLE_GZOPEN);
         // Estimate number of read-buffers (64KB) in file. Guess that the
         // uncompressed size is 2x compressed size. Add one just to ensure
         // it's non-zero.
         $estimatedbuffers = $this->storedfile->get_filesize() * 2 / self::READ_BLOCK_SIZE + 1;
     } else {
         $gz = gzopen($this->ospath, 'rb');
         $estimatedbuffers = filesize($this->ospath) * 2 / self::READ_BLOCK_SIZE + 1;
     }
     if (!$gz) {
         throw new moodle_exception('errorprocessingarchive', '', '', null, 'Failed to open gzip file');
     }
     // Calculate how much progress to report per buffer read.
     $progressperbuffer = (int) (tgz_packer::PROGRESS_MAX / $estimatedbuffers);
     // Process archive in 512-byte blocks (but reading 64KB at a time).
     $buffer = '';
     $bufferpos = 0;
     $bufferlength = 0;
     $this->numfiles = -1;
     $read = 0;
     $done = 0;
     $beforeprogress = -1;
     while (true) {
         if ($bufferpos == $bufferlength) {
             $buffer = gzread($gz, self::READ_BLOCK_SIZE);
             $bufferpos = 0;
             $bufferlength = strlen($buffer);
             if ($bufferlength == 0) {
                 // EOF.
                 break;
             }
             // Report progress if enabled.
             if ($progress) {
                 if ($this->numfiles === -1) {
                     // If we don't know the number of files, do an estimate based
                     // on number of buffers read.
                     $done += $progressperbuffer;
                     if ($done >= tgz_packer::PROGRESS_MAX) {
                         $done = tgz_packer::PROGRESS_MAX - 1;
                     }
                     $progress->progress($done, tgz_packer::PROGRESS_MAX);
                 } else {
                     // Once we know the number of files, use this.
                     if ($beforeprogress === -1) {
                         $beforeprogress = $done;
                     }
                     // Calculate progress as whatever progress we reported
                     // before we knew how many files there were (might be 0)
                     // plus a proportion of the number of files out of the
                     // remaining progress value.
                     $done = $beforeprogress + (int) ($this->donefiles / $this->numfiles * (tgz_packer::PROGRESS_MAX - $beforeprogress));
                 }
                 $progress->progress($done, tgz_packer::PROGRESS_MAX);
             }
         }
         $block = substr($buffer, $bufferpos, tgz_packer::TAR_BLOCK_SIZE);
         if ($this->currentfile) {
             $this->process_file_block($block, $handler);
         } else {
             $this->process_header($block, $handler);
         }
         // When listing, if we read an index file, we abort archive processing.
         if ($this->mode === self::MODE_LIST_COMPLETE) {
             break;
         }
         $bufferpos += tgz_packer::TAR_BLOCK_SIZE;
         $read++;
     }
     // Close archive and finish.
     gzclose($gz);
 }
Esempio n. 2
0
 /**
  * Perform archiving file from file path.
  *
  * @param zip_archive $ziparch zip archive instance
  * @param string $archivepath file path to archive
  * @param string $file path name of the file
  * @param file_progress $progress Progress indicator callback or null if not required
  * @return bool success
  */
 private function archive_pathname($ziparch, $archivepath, $file, file_progress $progress = null)
 {
     // Record progress each time this function is called.
     if ($progress) {
         $progress->progress();
     }
     if (!file_exists($file)) {
         return false;
     }
     if (is_file($file)) {
         if (!is_readable($file)) {
             return false;
         }
         return $ziparch->add_file_from_pathname($archivepath, $file);
     }
     if (is_dir($file)) {
         if ($archivepath !== '') {
             $ziparch->add_directory($archivepath);
         }
         $files = new DirectoryIterator($file);
         foreach ($files as $file) {
             if ($file->isDot()) {
                 continue;
             }
             $newpath = $archivepath . '/' . $file->getFilename();
             $this->archive_pathname($ziparch, $newpath, $file->getPathname(), $progress);
         }
         unset($files);
         // Release file handles.
         return true;
     }
 }
 /**
  * Writes a single tar file to the archive, including its header record and
  * then the file contents.
  *
  * @param resource $gz Gzip file
  * @param string $archivepath Full path of file within archive
  * @param string|resource $file Full path of file on disk or file handle or null if none
  * @param int $size Size or 0 for directories
  * @param int|string $mtime Time or ? if unknown
  * @param string $content Actual content of file to write (null if using $filepath)
  * @param file_progress $progress Progress indicator or null if none
  * @param int $done Value for progress indicator
  * @return bool True if OK
  * @throws coding_exception If names aren't valid
  */
 protected function write_tar_entry($gz, $archivepath, $file, $size, $mtime, $content = null, file_progress $progress = null, $done = 0)
 {
     // Header based on documentation of POSIX ustar format from:
     // http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current .
     // For directories, ensure name ends in a slash.
     $directory = false;
     if ($size === 0 && is_null($file)) {
         $directory = true;
         if (!preg_match('~/$~', $archivepath)) {
             $archivepath .= '/';
         }
         $mode = '755';
     } else {
         $mode = '644';
     }
     // Split archivepath into name and prefix.
     $name = $archivepath;
     $prefix = '';
     while (strlen($name) > 100) {
         $slash = strpos($name, '/');
         if ($slash === false) {
             throw new coding_exception('Name cannot fit length restrictions (> 100 characters): ' . $archivepath);
         }
         if ($prefix !== '') {
             $prefix .= '/';
         }
         $prefix .= substr($name, 0, $slash);
         $name = substr($name, $slash + 1);
         if (strlen($prefix) > 155) {
             throw new coding_exception('Name cannot fit length restrictions (path too long): ' . $archivepath);
         }
     }
     // Checksum performance is a bit slow because of having to call 'ord'
     // lots of times (it takes about 1/3 the time of the actual gzwrite
     // call). To improve performance of checksum calculation, we will
     // store all the non-zero, non-fixed bytes that need adding to the
     // checksum, and checksum only those bytes.
     $forchecksum = $name;
     // struct header_posix_ustar {
     //    char name[100];
     $header = str_pad($name, 100, "");
     //    char mode[8];
     //    char uid[8];
     //    char gid[8];
     $header .= '0000' . $mode . "00000000000000";
     $forchecksum .= $mode;
     //    char size[12];
     $octalsize = decoct($size);
     if (strlen($octalsize) > 11) {
         throw new coding_exception('File too large for .tar file: ' . $archivepath . ' (' . $size . ' bytes)');
     }
     $paddedsize = str_pad($octalsize, 11, '0', STR_PAD_LEFT);
     $forchecksum .= $paddedsize;
     $header .= $paddedsize . "";
     //    char mtime[12];
     if ($mtime === '?') {
         // Use a default timestamp rather than zero; GNU tar outputs
         // warnings about zeroes here.
         $mtime = self::DEFAULT_TIMESTAMP;
     }
     $octaltime = decoct($mtime);
     $paddedtime = str_pad($octaltime, 11, '0', STR_PAD_LEFT);
     $forchecksum .= $paddedtime;
     $header .= $paddedtime . "";
     //    char checksum[8];
     // Checksum needs to be completed later.
     $header .= '        ';
     //    char typeflag[1];
     $typeflag = $directory ? '5' : '0';
     $forchecksum .= $typeflag;
     $header .= $typeflag;
     //    char linkname[100];
     $header .= str_pad('', 100, "");
     //    char magic[6];
     //    char version[2];
     $header .= "ustar00";
     //    char uname[32];
     //    char gname[32];
     //    char devmajor[8];
     //    char devminor[8];
     $header .= str_pad('', 80, "");
     //    char prefix[155];
     //    char pad[12];
     $header .= str_pad($prefix, 167, "");
     $forchecksum .= $prefix;
     // };
     // We have now calculated the header, but without the checksum. To work
     // out the checksum, sum all the bytes that aren't fixed or zero, and add
     // to a standard value that contains all the fixed bytes.
     // The fixed non-zero bytes are:
     //
     // '000000000000000000        ustar00'
     // mode (except 3 digits), uid, gid, checksum space, magic number, version
     //
     // To calculate the number, call the calculate_checksum function on the
     // above string. The result is 1775.
     $checksum = 1775 + self::calculate_checksum($forchecksum);
     $octalchecksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . " ";
     // Slot it into place in the header.
     $header = substr($header, 0, 148) . $octalchecksum . substr($header, 156);
     if (strlen($header) != self::TAR_BLOCK_SIZE) {
         throw new coding_exception('Header block wrong size!!!!!');
     }
     // Awesome, now write out the header.
     gzwrite($gz, $header);
     // Special pre-handler for OS filename.
     if (is_string($file)) {
         $file = fopen($file, 'rb');
         if (!$file) {
             return false;
         }
     }
     if ($content !== null) {
         // Write in-memory content if any.
         if (strlen($content) !== $size) {
             throw new coding_exception('Mismatch between provided sizes: ' . $archivepath);
         }
         gzwrite($gz, $content);
     } else {
         if ($file !== null) {
             // Write file content if any, using a 64KB buffer.
             $written = 0;
             $chunks = 0;
             while (true) {
                 $data = fread($file, 65536);
                 if ($data === false || strlen($data) == 0) {
                     break;
                 }
                 $written += gzwrite($gz, $data);
                 // After every megabyte of large files, update the progress
                 // tracker (so there are no long gaps without progress).
                 $chunks++;
                 if ($chunks == 16) {
                     $chunks = 0;
                     if ($progress) {
                         // This call always has the same values, but that gives
                         // the tracker a chance to indicate indeterminate
                         // progress and output something to avoid timeouts.
                         $progress->progress($done, self::PROGRESS_MAX);
                     }
                 }
             }
             fclose($file);
             if ($written !== $size) {
                 throw new coding_exception('Mismatch between provided sizes: ' . $archivepath . ' (was ' . $written . ', expected ' . $size . ')');
             }
         } else {
             if ($size != 0) {
                 throw new coding_exception('Missing data file handle for non-empty file');
             }
         }
     }
     // Pad out final 512-byte block in file, if applicable.
     $leftover = self::TAR_BLOCK_SIZE - $size % self::TAR_BLOCK_SIZE;
     if ($leftover == 512) {
         $leftover = 0;
     } else {
         gzwrite($gz, str_pad('', $leftover, ""));
     }
     return true;
 }