1
0
This repository has been archived on 2021-04-26. You can view files and clone it, but cannot push or open issues or pull requests.
gallery3-contrib/3.0/modules/downloadalbum/controllers/downloadalbum.php
2013-01-21 01:24:24 -05:00

301 lines
10 KiB
PHP

<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2013 Bharat Mediratta
*
* This program 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class downloadalbum_Controller extends Controller {
/**
* Generate a ZIP on-the-fly.
*/
public function zip($container_type, $id) {
switch($container_type) {
case "album":
$container = ORM::factory("item", $id);
if (!$container->is_album()) {
throw new Kohana_Exception('container is not an album: '.$container->relative_path());
}
$zipname = (empty($container->name))
? 'Gallery.zip' // @todo purified_version_of($container->title).'.zip'
: $container->name.'.zip';
break;
case "tag":
// @todo: if the module is not installed, it crash
$container = ORM::factory("tag", $id);
if (is_null($container->name)) {
throw new Kohana_Exception('container is not a tag: '.$id);
}
$zipname = $container->name.'.zip';
break;
default:
throw new Kohana_Exception('unhandled container type: '.$container_type);
}
$files = $this->getFilesList($container);
// Calculate ZIP size (look behind for details)
$zipsize = 22;
foreach($files as $f_name => $f_path) {
$zipsize += 76 + 2*strlen($f_name) + filesize($f_path);
}
// Send headers
$this->prepareOutput();
$this->sendHeaders($zipname, $zipsize);
// Generate and send ZIP file
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT (v6.3.2)
$lfh_offset = 0;
$cds = '';
$cds_offset = 0;
foreach($files as $f_name => $f_path) {
$f_namelen = strlen($f_name);
$f_size = filesize($f_path);
$f_mtime = $this->unix2dostime(filemtime($f_path));
$f_crc32 = $this->fixBug45028(hexdec(hash_file('crc32b', $f_path, false)));
// Local file header
echo pack('VvvvVVVVvva' . $f_namelen,
0x04034b50, // local file header signature (4 bytes)
0x0a, // version needed to extract (2 bytes) => 1.0
0x0800, // general purpose bit flag (2 bytes) => UTF-8
0x00, // compression method (2 bytes) => store
$f_mtime, // last mod file time and date (4 bytes)
$f_crc32, // crc-32 (4 bytes)
$f_size, // compressed size (4 bytes)
$f_size, // uncompressed size (4 bytes)
$f_namelen, // file name length (2 bytes)
0, // extra field length (2 bytes)
$f_name // file name (variable size)
// extra field (variable size) => n/a
);
// File data
readfile($f_path);
// Data descriptor (n/a)
// Central directory structure: File header
$cds .= pack('VvvvvVVVVvvvvvVVa' . $f_namelen,
0x02014b50, // central file header signature (4 bytes)
0x031e, // version made by (2 bytes) => v3 / Unix
0x0a, // version needed to extract (2 bytes) => 1.0
0x0800, // general purpose bit flag (2 bytes) => UTF-8
0x00, // compression method (2 bytes) => store
$f_mtime, // last mod file time and date (4 bytes)
$f_crc32, // crc-32 (4 bytes)
$f_size, // compressed size (4 bytes)
$f_size, // uncompressed size (4 bytes)
$f_namelen, // file name length (2 bytes)
0, // extra field length (2 bytes)
0, // file comment length (2 bytes)
0, // disk number start (2 bytes)
0, // internal file attributes (2 bytes)
0x81b40000, // external file attributes (4 bytes) => chmod 664
$lfh_offset, // relative offset of local header (4 bytes)
$f_name // file name (variable size)
// extra field (variable size) => n/a
// file comment (variable size) => n/a
);
// Update local file header/central directory structure offset
$cds_offset = $lfh_offset += 30 + $f_namelen + $f_size;
}
// Archive decryption header (n/a)
// Archive extra data record (n/a)
// Central directory structure: Digital signature (n/a)
echo $cds; // send Central directory structure
// Zip64 end of central directory record (n/a)
// Zip64 end of central directory locator (n/a)
// End of central directory record
$numfile = count($files);
$cds_len = strlen($cds);
echo pack('VvvvvVVv',
0x06054b50, // end of central dir signature (4 bytes)
0, // number of this disk (2 bytes)
0, // number of the disk with the start of
// the central directory (2 bytes)
$numfile, // total number of entries in the
// central directory on this disk (2 bytes)
$numfile, // total number of entries in the
// central directory (2 bytes)
$cds_len, // size of the central directory (4 bytes)
$cds_offset, // offset of start of central directory
// with respect to the
// starting disk number (4 bytes)
0 // .ZIP file comment length (2 bytes)
// .ZIP file comment (variable size)
);
}
/**
* Return the files that must be included in the archive.
*/
private function getFilesList($container) {
$files = array();
if( $container instanceof Item_Model && $container->is_album() ) {
$container_realpath = realpath($container->file_path().'/../');
$items = $container->viewable()
->descendants(null, null, array(array("type", "<>", "album")));
foreach($items as $i) {
if (!access::can('view_full', $i)) {
continue;
}
$i_realpath = realpath($i->file_path());
if (!is_readable($i_realpath)) {
continue;
}
$i_relative_path = str_replace($container_realpath.DIRECTORY_SEPARATOR, '', $i_realpath);
$i_relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $i_relative_path);
$files[$i_relative_path] = $i_realpath;
}
} else if( $container instanceof Tag_Model ) {
$items = $container->items();
foreach($items as $i) {
if (!access::can('view_full', $i)) {
continue;
}
if( $i->is_album() ) {
foreach($this->getFilesList($i) as $f_name => $f_path) {
$files[$container->name.'/'.$f_name] = $f_path;
}
} else {
$i_realpath = realpath($i->file_path());
if (!is_readable($i_realpath)) {
continue;
}
$i_relative_path = $container->name.'/'.$i->name;
$files[$i_relative_path] = $i_realpath;
}
}
}
if (count($files) === 0) {
throw new Kohana_Exception('no zippable files in ['.$container->name.']');
}
return $files;
}
/**
* See system/helpers/download.php
*/
private function prepareOutput() {
// Close output buffers
Kohana::close_buffers(FALSE);
// Clear any output
Event::add('system.display', create_function('', 'Kohana::$output = "";'));
}
/**
* See system/helpers/download.php
*/
private function sendHeaders($filename, $filesize = null) {
if (!is_null($filesize)) {
header('Content-Length: '.$filesize);
}
// Retrieve MIME type by extension
$mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1)));
$mime = empty($mime) ? 'application/octet-stream' : $mime[0];
header("Content-Type: $mime");
header('Content-Transfer-Encoding: binary');
// Send headers necessary to invoke a "Save As" dialog
header('Content-Disposition: attachment; filename="'.$filename.'"');
// Prevent caching
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
$pragma = 'no-cache';
$cachecontrol = 'no-cache, max-age=0';
// request::user_agent('browser') seems bugged
if (request::user_agent('browser') === 'Internet Explorer'
|| stripos(request::user_agent(), 'msie') !== false
|| stripos(request::user_agent(), 'internet explorer') !== false)
{
if (request::protocol() === 'https') {
// See http://support.microsoft.com/kb/323308/en-us
$pragma = 'cache';
$cachecontrol = 'private';
} else if (request::user_agent('version') <= '6.0') {
$pragma = '';
$cachecontrol = 'must-revalidate, post-check=0, pre-check=0';
}
}
header('Pragma: '.$pragma);
header('Cache-Control: '.$cachecontrol);
}
/**
* @return integer DOS date and time
* @param integer _timestamp Unix timestamp
* @desc returns DOS date and time of the timestamp
*/
private function unix2dostime($timestamp)
{
$timebit = getdate($timestamp);
if ($timebit['year'] < 1980) {
return (1 << 21 | 1 << 16);
}
$timebit['year'] -= 1980;
return ($timebit['year'] << 25 | $timebit['mon'] << 21 |
$timebit['mday'] << 16 | $timebit['hours'] << 11 |
$timebit['minutes'] << 5 | $timebit['seconds'] >> 1);
}
/**
* See http://bugs.php.net/bug.php?id=45028
*/
private function fixBug45028($hash) {
$output = $hash;
if( version_compare(PHP_VERSION, '5.2.7', '<') ) {
$str = str_pad(dechex($hash), 8, '0', STR_PAD_LEFT);
$output = hexdec($str{6}.$str{7}.$str{4}.$str{5}.$str{2}.$str{3}.$str{0}.$str{1});
}
return $output;
}
}