SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Drivers/Archive/Zip.php

Copyright (C) 2017 Karmabunny Pty Ltd.

This file is a part of SproutCMS.

SproutCMS is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

For more information, visit <http://getsproutcms.com>.

This class was originally from Kohana 2.3.4
Copyright 2007-2008 Kohana Team
  1. <?php
  2. /**
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  *
  13.  * This class was originally from Kohana 2.3.4
  14.  * Copyright 2007-2008 Kohana Team
  15.  */
  16. namespace Sprout\Helpers\Drivers\Archive;
  17.  
  18. use Exception;
  19.  
  20. use Sprout\Helpers\Drivers\ArchiveDriver;
  21.  
  22.  
  23. /**
  24.  * Archive library zip driver.
  25.  */
  26. class Zip implements ArchiveDriver
  27. {
  28. /**
  29.   * @var resource A resource handle to the destination zip file
  30.   */
  31. protected $file;
  32.  
  33. // Compiled directory structure
  34. protected $dirs = [];
  35.  
  36. // Offset location
  37. protected $offset = 0;
  38.  
  39. public function create($paths, $filename = FALSE)
  40. {
  41. if (empty($filename)) throw new Exception('filename is required now, sorry');
  42.  
  43. // Ensure filename has zip extension
  44. if (substr($filename, -4) != '.zip') {
  45. $filename .= '.zip';
  46. }
  47.  
  48. // Sort the paths to make sure that directories come before files
  49. sort($paths);
  50.  
  51. // Create the file in binary write mode
  52. $this->file = fopen($filename, 'wb');
  53. if (!$this->file) throw new Exception('Failed to create zip file');
  54.  
  55. // Lock the file
  56. flock($this->file, LOCK_EX);
  57.  
  58. foreach ($paths as $set)
  59. {
  60. // Add each path individually
  61. $this->addData($set[0], $set[1], isset($set[2]) ? $set[2] : NULL);
  62. }
  63.  
  64. // Directory data
  65. $dirs = implode('', $this->dirs);
  66.  
  67. $zipfile =
  68. $dirs. // Directory data
  69. "\x50\x4b\x05\x06\x00\x00\x00\x00". // Directory EOF
  70. pack('v', count($this->dirs)). // Total number of entries "on disk"
  71. pack('v', count($this->dirs)). // Total number of entries in file
  72. pack('V', strlen($dirs)). // Size of directories
  73. pack('V', $this->offset). // Offset to directories
  74. "\x00\x00"; // Zip comment length
  75.  
  76. if (fwrite($this->file, $zipfile) === false) {
  77. throw new Exception('Failed to write directory info');
  78. }
  79.  
  80. // Unlock the file
  81. flock($this->file, LOCK_UN);
  82.  
  83. // Close the file
  84. fclose($this->file);
  85.  
  86. return true;
  87. }
  88.  
  89. private function dateUnix2dos($timestamp)
  90. {
  91. $timestamp = getdate($timestamp);
  92.  
  93. if ($timestamp['year'] < 1980)
  94. {
  95. return (1 << 21 | 1 << 16);
  96. }
  97.  
  98. $timestamp['year'] -= 1980;
  99.  
  100. // What voodoo is this? I have no idea... Geert can explain it though,
  101. // and that's good enough for me.
  102. return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 |
  103. $timestamp['mday'] << 16 | $timestamp['hours'] << 11 |
  104. $timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1);
  105. }
  106.  
  107. public function addData($file, $name, $contents = NULL)
  108. {
  109. // Determine the file type: 16 = dir, 32 = file
  110. $type = (substr($file, -1) === '/') ? 16 : 32;
  111.  
  112. // Fetch the timestamp, using the current time if manually setting the contents
  113. $timestamp = $this->dateUnix2dos(($contents === NULL) ? filemtime($file) : time());
  114.  
  115. // Read the file or use the defined contents
  116. $data = ($contents === NULL) ? file_get_contents($file) : $contents;
  117.  
  118. // Gzip the data, use substr to fix a CRC bug
  119. $zdata = substr(gzcompress($data), 2, -4);
  120.  
  121. $file_data = "\x50\x4b\x03\x04". // Zip header
  122. "\x14\x00". // Version required for extraction
  123. "\x00\x00". // General bit flag
  124. "\x08\x00". // Compression method
  125. pack('V', $timestamp). // Last mod time and date
  126. pack('V', crc32($data)). // CRC32
  127. pack('V', strlen($zdata)). // Compressed filesize
  128. pack('V', strlen($data)). // Uncompressed filesize
  129. pack('v', strlen($name)). // Length of file name
  130. pack('v', 0). // Extra field length
  131. $name. // File name
  132. $zdata; // Compressed data
  133.  
  134. // Write the zip file
  135. if (fwrite($this->file, $file_data) === false) {
  136. throw new Exception('Failed to write file ' . $name);
  137. }
  138.  
  139. $this->dirs[] =
  140. "\x50\x4b\x01\x02". // Zip header
  141. "\x00\x00". // Version made by
  142. "\x14\x00". // Version required for extraction
  143. "\x00\x00". // General bit flag
  144. "\x08\x00". // Compression method
  145. pack('V', $timestamp). // Last mod time and date
  146. pack('V', crc32($data)). // CRC32
  147. pack('V', strlen($zdata)).// Compressed filesize
  148. pack('V', strlen($data)). // Uncompressed filesize
  149. pack('v', strlen($name)). // Length of file name
  150. pack('v', 0). // Extra field length
  151. // End "local file header"
  152. // Start "data descriptor"
  153. pack('v', 0). // CRC32
  154. pack('v', 0). // Compressed filesize
  155. pack('v', 0). // Uncompressed filesize
  156. pack('V', $type). // File attribute type
  157. pack('V', $this->offset). // Directory offset
  158. $name; // File name
  159.  
  160. // Set the new offset
  161. $this->offset += strlen($file_data);
  162. }
  163.  
  164. } // End Archive_Zip_Driver Class
  165.