From 63788345a8446550cfcb1c95223920d19b1bdbd5 Mon Sep 17 00:00:00 2001
From: Konstantin Dmitriev
Date: Fri, 14 Aug 2015 21:58:13 +0600
Subject: [PATCH] Allow to download directories as zip files
The feature adds a button to download all files in current directory as zip archive.
For this function to work it is required to have 'zip' utility available on the server.
To enable this function set the 'zip_dirs' option to 'true' in configuration file (it is disabled by default).
By default the zip is created 'on-the-fly' and streamed to client, without any temporary file. This behaviour can be changed by setting 'zip_stream' configuration option to false.
It is also possible to choose compression level for zip files - see 'zip_compression_level' configuration option. The default value is zero for perfomance reasons.
The 'zip_disable' option allows to disable zip downloading for particular set of directories. It is works much the same as 'hidden_files' option.
TODO: When zip is created the script tries to exclude files with respect to 'hidden files' option. But it doesn't do it very well - for example, if 'hidden_files' have 'resources/*' entry, then it will exclude the contents of 'resources' directory on any subdirectory, not just at the root level.
---
index.php | 8 +++
resources/DirectoryLister.php | 86 ++++++++++++++++++++++++++++
resources/default.config.php | 13 ++++-
resources/themes/bootstrap/index.php | 5 ++
4 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/index.php b/index.php
index 7ef14e9..a954d92 100644
--- a/index.php
+++ b/index.php
@@ -20,6 +20,12 @@
die($data);
}
+
+ if (isset($_GET['zip'])) {
+ $dirArray = $lister->zipDirectory($_GET['zip']);
+ } else {
+
+
// Initialize the directory array
if (isset($_GET['dir'])) {
@@ -42,3 +48,5 @@
} else {
die('ERROR: Failed to initialize theme');
}
+
+ }
diff --git a/resources/DirectoryLister.php b/resources/DirectoryLister.php
index 9667e68..2200f2b 100644
--- a/resources/DirectoryLister.php
+++ b/resources/DirectoryLister.php
@@ -61,6 +61,81 @@ class DirectoryLister {
$this->_themeName = $this->_config['theme_name'];
}
+
+ /**
+ * If it is allowed to zip whole directories
+ *
+ * @param string $directory Relative path of directory to list
+ * @return true or false
+ * @access public
+ */
+ public function isZipEnabled() {
+ foreach ($this->_config['zip_disable'] as $disabledPath) {
+ if (fnmatch($disabledPath, $this->_directory)) {
+ return false;
+ }
+ }
+ return $this->_config['zip_dirs'];
+ }
+
+ /**
+ * Creates zipfile of directory
+ *
+ * @param string $directory Relative path of directory to list
+ * @access public
+ */
+ public function zipDirectory($directory) {
+ if ($this->_config['zip_dirs'])
+ {
+ // Cleanup directory path
+ $directory = $this->setDirectoryPath($directory);
+
+ if ($directory != '.' && $this->_isHidden($directory)){
+ echo "Access denied.";
+ }
+
+ $filename_no_ext = basename("$directory");
+ if ( $directory == '.' )
+ {
+ $filename_no_ext = "Home";
+ }
+
+ // We deliver a zip file
+ header("Content-Type: archive/zip");
+ // Filename for the browser to save the zip file
+ header("Content-Disposition: attachment; filename=\"$filename_no_ext".".zip\"");
+ //change directory so the zip file doesnt have a tree structure in it.
+ chdir($directory);
+
+ // TODO: Probably we have to parse exclude list more carefully
+ $exclude_list = implode(" ", array_merge($this->_config['hidden_files'],array('index.php')));
+ $exclude_list = str_replace("*", "\*", $exclude_list);
+
+ if ($this->_config['zip_stream'])
+ {
+ // zip the stuff (dir and all in there) into the streamed zip file
+ $stream = popen( "/usr/bin/zip -".$this->_config['zip_compression_level']." -r -q - * -x ".$exclude_list, "r" );
+ if( $stream )
+ {
+ fpassthru( $stream );
+ fclose( $stream );
+ }
+ } else {
+ // zip the stuff (dir and all in there) into the tmp_zip file
+ exec('zip -'.$this->_config['zip_compression_level'].' -r '.$tmp_zip.' * -x '.$exclude_list);
+ // get a tmp name for the .zip
+ $tmp_zip = tempnam ("tmp", "tempzip") . ".zip";
+ // calc the length of the zip. it is needed for the progress bar of the browser
+ $filesize = filesize($tmp_zip);
+ header("Content-Length: $filesize");
+ // deliver the zip file
+ $fp = fopen("$tmp_zip","r");
+ echo fpassthru($fp);
+ // clean up the tmp zip file
+ unlink($tmp_zip);
+ }
+ }
+ }
/**
@@ -341,6 +416,17 @@ class DirectoryLister {
return $this->_directory;
}
+
+ /**
+ * Get directory path variable
+ *
+ * @return string Sanitizd path to directory
+ * @access public
+ */
+ public function getDirectoryPath() {
+ return $this->_directory;
+
+ }
/**
diff --git a/resources/default.config.php b/resources/default.config.php
index 7ceb32e..b6240cf 100644
--- a/resources/default.config.php
+++ b/resources/default.config.php
@@ -34,6 +34,17 @@ return array(
// Custom sort order
'reverse_sort' => array(
// 'path/to/folder'
- )
+ ),
+
+ // Allow to download directories as zip files
+ 'zip_dirs' => false,
+ // Stream zip file content directly to the client, without any temporary file
+ 'zip_stream' => true,
+ 'zip_compression_level' => 0,
+ // Disable zip downloads for particular directories
+ 'zip_disable' => array(
+ '.' // - disable for root directory by default
+ // 'path/to/folder'
+ ),
);
diff --git a/resources/themes/bootstrap/index.php b/resources/themes/bootstrap/index.php
index 5285569..d7df61e 100644
--- a/resources/themes/bootstrap/index.php
+++ b/resources/themes/bootstrap/index.php
@@ -47,6 +47,11 @@
+ isZipEnabled()): ?>
+
+