PageRenderTime 49ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/3.0/modules/downloadalbum/controllers/downloadalbum.php

https://github.com/wrlee/gallery3-contrib
PHP | 300 lines | 181 code | 49 blank | 70 comment | 22 complexity | 3410ff68bbf652d8d784a05681b309be MD5 | raw file
  1. <?php defined("SYSPATH") or die("No direct script access.");
  2. /**
  3. * Gallery - a web based photo album viewer and editor
  4. * Copyright (C) 2000-2011 Bharat Mediratta
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or (at
  9. * your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. class downloadalbum_Controller extends Controller {
  21. /**
  22. * Generate a ZIP on-the-fly.
  23. */
  24. public function zip($container_type, $id) {
  25. switch($container_type) {
  26. case "album":
  27. $container = ORM::factory("item", $id);
  28. if (!$container->is_album()) {
  29. throw new Kohana_Exception('container is not an album: '.$container->relative_path());
  30. }
  31. $zipname = (empty($container->name))
  32. ? 'Gallery.zip' // @todo purified_version_of($container->title).'.zip'
  33. : $container->name.'.zip';
  34. break;
  35. case "tag":
  36. // @todo: if the module is not installed, it crash
  37. $container = ORM::factory("tag", $id);
  38. if (is_null($container->name)) {
  39. throw new Kohana_Exception('container is not a tag: '.$id);
  40. }
  41. $zipname = $container->name.'.zip';
  42. break;
  43. default:
  44. throw new Kohana_Exception('unhandled container type: '.$container_type);
  45. }
  46. $files = $this->getFilesList($container);
  47. // Calculate ZIP size (look behind for details)
  48. $zipsize = 22;
  49. foreach($files as $f_name => $f_path) {
  50. $zipsize += 76 + 2*strlen($f_name) + filesize($f_path);
  51. }
  52. // Send headers
  53. $this->prepareOutput();
  54. $this->sendHeaders($zipname, $zipsize);
  55. // Generate and send ZIP file
  56. // http://www.pkware.com/documents/casestudies/APPNOTE.TXT (v6.3.2)
  57. $lfh_offset = 0;
  58. $cds = '';
  59. $cds_offset = 0;
  60. foreach($files as $f_name => $f_path) {
  61. $f_namelen = strlen($f_name);
  62. $f_size = filesize($f_path);
  63. $f_mtime = $this->unix2dostime(filemtime($f_path));
  64. $f_crc32 = $this->fixBug45028(hexdec(hash_file('crc32b', $f_path, false)));
  65. // Local file header
  66. echo pack('VvvvVVVVvva' . $f_namelen,
  67. 0x04034b50, // local file header signature (4 bytes)
  68. 0x0a, // version needed to extract (2 bytes) => 1.0
  69. 0x0800, // general purpose bit flag (2 bytes) => UTF-8
  70. 0x00, // compression method (2 bytes) => store
  71. $f_mtime, // last mod file time and date (4 bytes)
  72. $f_crc32, // crc-32 (4 bytes)
  73. $f_size, // compressed size (4 bytes)
  74. $f_size, // uncompressed size (4 bytes)
  75. $f_namelen, // file name length (2 bytes)
  76. 0, // extra field length (2 bytes)
  77. $f_name // file name (variable size)
  78. // extra field (variable size) => n/a
  79. );
  80. // File data
  81. readfile($f_path);
  82. // Data descriptor (n/a)
  83. // Central directory structure: File header
  84. $cds .= pack('VvvvvVVVVvvvvvVVa' . $f_namelen,
  85. 0x02014b50, // central file header signature (4 bytes)
  86. 0x031e, // version made by (2 bytes) => v3 / Unix
  87. 0x0a, // version needed to extract (2 bytes) => 1.0
  88. 0x0800, // general purpose bit flag (2 bytes) => UTF-8
  89. 0x00, // compression method (2 bytes) => store
  90. $f_mtime, // last mod file time and date (4 bytes)
  91. $f_crc32, // crc-32 (4 bytes)
  92. $f_size, // compressed size (4 bytes)
  93. $f_size, // uncompressed size (4 bytes)
  94. $f_namelen, // file name length (2 bytes)
  95. 0, // extra field length (2 bytes)
  96. 0, // file comment length (2 bytes)
  97. 0, // disk number start (2 bytes)
  98. 0, // internal file attributes (2 bytes)
  99. 0x81b40000, // external file attributes (4 bytes) => chmod 664
  100. $lfh_offset, // relative offset of local header (4 bytes)
  101. $f_name // file name (variable size)
  102. // extra field (variable size) => n/a
  103. // file comment (variable size) => n/a
  104. );
  105. // Update local file header/central directory structure offset
  106. $cds_offset = $lfh_offset += 30 + $f_namelen + $f_size;
  107. }
  108. // Archive decryption header (n/a)
  109. // Archive extra data record (n/a)
  110. // Central directory structure: Digital signature (n/a)
  111. echo $cds; // send Central directory structure
  112. // Zip64 end of central directory record (n/a)
  113. // Zip64 end of central directory locator (n/a)
  114. // End of central directory record
  115. $numfile = count($files);
  116. $cds_len = strlen($cds);
  117. echo pack('VvvvvVVv',
  118. 0x06054b50, // end of central dir signature (4 bytes)
  119. 0, // number of this disk (2 bytes)
  120. 0, // number of the disk with the start of
  121. // the central directory (2 bytes)
  122. $numfile, // total number of entries in the
  123. // central directory on this disk (2 bytes)
  124. $numfile, // total number of entries in the
  125. // central directory (2 bytes)
  126. $cds_len, // size of the central directory (4 bytes)
  127. $cds_offset, // offset of start of central directory
  128. // with respect to the
  129. // starting disk number (4 bytes)
  130. 0 // .ZIP file comment length (2 bytes)
  131. // .ZIP file comment (variable size)
  132. );
  133. }
  134. /**
  135. * Return the files that must be included in the archive.
  136. */
  137. private function getFilesList($container) {
  138. $files = array();
  139. if( $container instanceof Item_Model && $container->is_album() ) {
  140. $container_realpath = realpath($container->file_path().'/../');
  141. $items = $container->viewable()
  142. ->descendants(null, null, array(array("type", "<>", "album")));
  143. foreach($items as $i) {
  144. if (!access::can('view_full', $i)) {
  145. continue;
  146. }
  147. $i_realpath = realpath($i->file_path());
  148. if (!is_readable($i_realpath)) {
  149. continue;
  150. }
  151. $i_relative_path = str_replace($container_realpath.DIRECTORY_SEPARATOR, '', $i_realpath);
  152. $i_relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $i_relative_path);
  153. $files[$i_relative_path] = $i_realpath;
  154. }
  155. } else if( $container instanceof Tag_Model ) {
  156. $items = $container->items();
  157. foreach($items as $i) {
  158. if (!access::can('view_full', $i)) {
  159. continue;
  160. }
  161. if( $i->is_album() ) {
  162. foreach($this->getFilesList($i) as $f_name => $f_path) {
  163. $files[$container->name.'/'.$f_name] = $f_path;
  164. }
  165. } else {
  166. $i_realpath = realpath($i->file_path());
  167. if (!is_readable($i_realpath)) {
  168. continue;
  169. }
  170. $i_relative_path = $container->name.'/'.$i->name;
  171. $files[$i_relative_path] = $i_realpath;
  172. }
  173. }
  174. }
  175. if (count($files) === 0) {
  176. throw new Kohana_Exception('no zippable files in ['.$container->name.']');
  177. }
  178. return $files;
  179. }
  180. /**
  181. * See system/helpers/download.php
  182. */
  183. private function prepareOutput() {
  184. // Close output buffers
  185. Kohana::close_buffers(FALSE);
  186. // Clear any output
  187. Event::add('system.display', create_function('', 'Kohana::$output = "";'));
  188. }
  189. /**
  190. * See system/helpers/download.php
  191. */
  192. private function sendHeaders($filename, $filesize = null) {
  193. if (!is_null($filesize)) {
  194. header('Content-Length: '.$filesize);
  195. }
  196. // Retrieve MIME type by extension
  197. $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1)));
  198. $mime = empty($mime) ? 'application/octet-stream' : $mime[0];
  199. header("Content-Type: $mime");
  200. header('Content-Transfer-Encoding: binary');
  201. // Send headers necessary to invoke a "Save As" dialog
  202. header('Content-Disposition: attachment; filename="'.$filename.'"');
  203. // Prevent caching
  204. header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
  205. $pragma = 'no-cache';
  206. $cachecontrol = 'no-cache, max-age=0';
  207. // request::user_agent('browser') seems bugged
  208. if (request::user_agent('browser') === 'Internet Explorer'
  209. || stripos(request::user_agent(), 'msie') !== false
  210. || stripos(request::user_agent(), 'internet explorer') !== false)
  211. {
  212. if (request::protocol() === 'https') {
  213. // See http://support.microsoft.com/kb/323308/en-us
  214. $pragma = 'cache';
  215. $cachecontrol = 'private';
  216. } else if (request::user_agent('version') <= '6.0') {
  217. $pragma = '';
  218. $cachecontrol = 'must-revalidate, post-check=0, pre-check=0';
  219. }
  220. }
  221. header('Pragma: '.$pragma);
  222. header('Cache-Control: '.$cachecontrol);
  223. }
  224. /**
  225. * @return integer DOS date and time
  226. * @param integer _timestamp Unix timestamp
  227. * @desc returns DOS date and time of the timestamp
  228. */
  229. private function unix2dostime($timestamp)
  230. {
  231. $timebit = getdate($timestamp);
  232. if ($timebit['year'] < 1980) {
  233. return (1 << 21 | 1 << 16);
  234. }
  235. $timebit['year'] -= 1980;
  236. return ($timebit['year'] << 25 | $timebit['mon'] << 21 |
  237. $timebit['mday'] << 16 | $timebit['hours'] << 11 |
  238. $timebit['minutes'] << 5 | $timebit['seconds'] >> 1);
  239. }
  240. /**
  241. * See http://bugs.php.net/bug.php?id=45028
  242. */
  243. private function fixBug45028($hash) {
  244. $output = $hash;
  245. if( version_compare(PHP_VERSION, '5.2.7', '<') ) {
  246. $str = str_pad(dechex($hash), 8, '0', STR_PAD_LEFT);
  247. $output = hexdec($str{6}.$str{7}.$str{4}.$str{5}.$str{2}.$str{3}.$str{0}.$str{1});
  248. }
  249. return $output;
  250. }
  251. }