Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream zip or tar #19318

Merged
merged 9 commits into from
Sep 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty
97 changes: 34 additions & 63 deletions lib/private/files.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*/

use OC\Lock\NoopLockingProvider;
use OC\Streamer;
use OCP\Lock\ILockingProvider;

/**
Expand All @@ -57,20 +58,15 @@ class OC_Files {
/**
* @param string $filename
* @param string $name
* @param bool $zip
*/
private static function sendHeaders($filename, $name, $zip = false) {
private static function sendHeaders($filename, $name) {
OC_Response::setContentDispositionHeader($name, 'attachment');
header('Content-Transfer-Encoding: binary');
OC_Response::disableCaching();
if ($zip) {
header('Content-Type: application/zip');
} else {
$filesize = \OC\Files\Filesystem::filesize($filename);
header('Content-Type: '.\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)));
if ($filesize > -1) {
OC_Response::setContentLengthHeader($filesize);
}
$filesize = \OC\Files\Filesystem::filesize($filename);
header('Content-Type: '.\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)));
if ($filesize > -1) {
OC_Response::setContentLengthHeader($filesize);
}
}

Expand All @@ -79,55 +75,58 @@ private static function sendHeaders($filename, $name, $zip = false) {
*
* @param string $dir
* @param string $files ; separated list of files to download
* @param boolean $only_header ; boolean to only send header of the request
* @param boolean $onlyHeader ; boolean to only send header of the request
*/
public static function get($dir, $files, $only_header = false) {
public static function get($dir, $files, $onlyHeader = false) {
$view = \OC\Files\Filesystem::getView();

if (is_array($files) && count($files) === 1) {
$files = $files[0];
}

if (is_array($files)) {
$get_type = self::ZIP_FILES;
$getType = self::ZIP_FILES;
$basename = basename($dir);
if ($basename) {
$name = $basename . '.zip';
$name = $basename;
} else {
$name = 'download.zip';
$name = 'download';
}

$filename = $dir . '/' . $name;
} else {
$filename = $dir . '/' . $files;
if (\OC\Files\Filesystem::is_dir($dir . '/' . $files)) {
$get_type = self::ZIP_DIR;
$getType = self::ZIP_DIR;
// downloading root ?
if ($files === '') {
$name = 'download.zip';
$name = 'download';
} else {
$name = $files . '.zip';
$name = $files;
}

} else {
$get_type = self::FILE;
$getType = self::FILE;
$name = $files;
}
}

if ($get_type === self::FILE) {
$zip = false;
if ($getType === self::FILE) {
$streamer = false;
} else {
$zip = new ZipStreamer\ZipStreamer();
$streamer = new Streamer();
}
OC_Util::obEnd();

try {
if ($get_type === self::FILE) {
if ($getType === self::FILE) {
$view->lockFile($filename, ILockingProvider::LOCK_SHARED);
}
if ($zip or \OC\Files\Filesystem::isReadable($filename)) {
self::sendHeaders($filename, $name, $zip);

if ($streamer) {
$streamer->sendHeaders($name);
} elseif (\OC\Files\Filesystem::isReadable($filename)) {
self::sendHeaders($filename, $name);
} elseif (!\OC\Files\Filesystem::file_exists($filename)) {
header("HTTP/1.0 404 Not Found");
$tmpl = new OC_Template('', '404', 'guest');
Expand All @@ -137,33 +136,34 @@ public static function get($dir, $files, $only_header = false) {
header("HTTP/1.0 403 Forbidden");
die('403 Forbidden');
}
if ($only_header) {
if ($onlyHeader) {
return;
}
if ($zip) {
if ($streamer) {
$executionTime = intval(ini_get('max_execution_time'));
set_time_limit(0);
if ($get_type === self::ZIP_FILES) {
if ($getType === self::ZIP_FILES) {
foreach ($files as $file) {
$file = $dir . '/' . $file;
if (\OC\Files\Filesystem::is_file($file)) {
$fileSize = \OC\Files\Filesystem::filesize($file);
$fh = \OC\Files\Filesystem::fopen($file, 'r');
$zip->addFileFromStream($fh, basename($file));
$streamer->addFileFromStream($fh, basename($file), $fileSize);
fclose($fh);
} elseif (\OC\Files\Filesystem::is_dir($file)) {
self::zipAddDir($file, $zip);
$streamer->addDirRecursive($file);
}
}
} elseif ($get_type === self::ZIP_DIR) {
} elseif ($getType === self::ZIP_DIR) {
$file = $dir . '/' . $files;
self::zipAddDir($file, $zip);
$streamer->addDirRecursive($file);
}
$zip->finalize();
$streamer->finalize();
set_time_limit($executionTime);
} else {
\OC\Files\Filesystem::readfile($filename);
}
if ($get_type === self::FILE) {
if ($getType === self::FILE) {
$view->unlockFile($filename, ILockingProvider::LOCK_SHARED);
}
} catch (\OCP\Lock\LockedException $ex) {
Expand All @@ -177,35 +177,6 @@ public static function get($dir, $files, $only_header = false) {
}
}

/**
* @param string $dir
* @param ZipStreamer $zip
* @param string $internalDir
*/
public static function zipAddDir($dir, ZipStreamer $zip, $internalDir='') {
$dirname=basename($dir);
$rootDir = $internalDir.$dirname;
if (!empty($rootDir)) {
$zip->addEmptyDir($rootDir);
}
$internalDir.=$dirname.='/';
// prevent absolute dirs
$internalDir = ltrim($internalDir, '/');

$files=\OC\Files\Filesystem::getDirectoryContent($dir);
foreach($files as $file) {
$filename=$file['name'];
$file=$dir.'/'.$filename;
if(\OC\Files\Filesystem::is_file($file)) {
$fh = \OC\Files\Filesystem::fopen($file, 'r');
$zip->addFileFromStream($fh, $internalDir.$filename);
fclose($fh);
}elseif(\OC\Files\Filesystem::is_dir($file)) {
self::zipAddDir($file, $zip, $internalDir);
}
}
}

/**
* set the maximum upload size limit for apache hosts using .htaccess
*
Expand Down
124 changes: 124 additions & 0 deletions lib/private/streamer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php
/**
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OC;

use ownCloud\TarStreamer\TarStreamer;
use ZipStreamer\ZipStreamer;

class Streamer {
// array of regexp. Matching user agents will get tar instead of zip
private $preferTarFor = [ '/macintosh|mac os x/i' ];

// streamer instance
private $streamerInstance;

public function __construct(){
/** @var \OCP\IRequest */
$request = \OC::$server->getRequest();

if ($request->isUserAgent($this->preferTarFor)) {
$this->streamerInstance = new TarStreamer();
} else {
$this->streamerInstance = new ZipStreamer();
}
}

/**
* Send HTTP headers
* @param string $name
*/
public function sendHeaders($name){
$extension = $this->streamerInstance instanceof ZipStreamer ? '.zip' : '.tar';
$fullName = $name . $extension;
// ZipStreamer does not escape name in Content-Disposition atm
if ($this->streamerInstance instanceof ZipStreamer) {
$fullName = rawurlencode($fullName);
}
$this->streamerInstance->sendHeaders($fullName);
}

/**
* Stream directory recursively
* @param string $dir
* @param string $internalDir
*/
public function addDirRecursive($dir, $internalDir='') {
$dirname = basename($dir);
$rootDir = $internalDir . $dirname;
if (!empty($rootDir)) {
$this->streamerInstance->addEmptyDir($rootDir);
}
$internalDir .= $dirname . '/';
// prevent absolute dirs
$internalDir = ltrim($internalDir, '/');

$files= \OC\Files\Filesystem::getDirectoryContent($dir);
foreach($files as $file) {
$filename = $file['name'];
$file = $dir . '/' . $filename;
if(\OC\Files\Filesystem::is_file($file)) {
$filesize = \OC\Files\Filesystem::filesize($file);
$fh = \OC\Files\Filesystem::fopen($file, 'r');
$this->addFileFromStream($fh, $internalDir . $filename, $filesize);
fclose($fh);
}elseif(\OC\Files\Filesystem::is_dir($file)) {
$this->addDirRecursive($file, $internalDir);
}
}
}

/**
* Add a file to the archive at the specified location and file name.
*
* @param string $stream Stream to read data from
* @param string $internalName Filepath and name to be used in the archive.
* @param int $size Filesize
* @return bool $success
*/
public function addFileFromStream($stream, $internalName, $size){
if ($this->streamerInstance instanceof ZipStreamer) {
return $this->streamerInstance->addFileFromStream($stream, $internalName);
} else {
return $this->streamerInstance->addFileFromStream($stream, $internalName, $size);
}
}

/**
* Add an empty directory entry to the archive.
*
* @param string $directoryPath Directory Path and name to be added to the archive.
* @return bool $success
*/
public function addEmptyDir($dirName){
return $this->streamerInstance->addEmptyDir($dirName);
}

/**
* Close the archive.
* A closed archive can no longer have new files added to it. After
* closing, the file is completely written to the output stream.
* @return bool $success
*/
public function finalize(){
return $this->streamerInstance->finalize();
}
}