[FEATURE] Add a download method to t3lib_http_Request
authorPhilipp Gampe <forge.typo3.org@philippgampe.info>
Tue, 6 Dec 2011 13:03:18 +0000 (15:03 +0200)
committerTolleiv Nietsch <info@tolleiv.de>
Wed, 15 Feb 2012 19:12:11 +0000 (20:12 +0100)
To ease work for developers, add a download method to
t3lib_http_Request. This disables the body, attaches a download
observer and sends the request.

Change-Id: I4123efbd48cb50b82510f9b9e08cfbd92d6090bc
Resolves: #28495
Releases: 4.7
Reviewed-on: http://review.typo3.org/3729
Reviewed-by: Stefan Neufeind
Reviewed-by: Oliver Klee
Tested-by: Wouter Wolters
Reviewed-by: Tolleiv Nietsch
Tested-by: Tolleiv Nietsch
t3lib/core_autoload.php
t3lib/http/class.t3lib_http_request.php
t3lib/http/observer/class.t3lib_http_observer_download.php [new file with mode: 0644]

index 4f491ec..d08506b 100644 (file)
@@ -112,6 +112,7 @@ $t3libClasses = array(
        't3lib_frontendedit' => PATH_t3lib . 'class.t3lib_frontendedit.php',
        't3lib_fullsearch' => PATH_t3lib . 'class.t3lib_fullsearch.php',
        't3lib_http_request' => PATH_t3lib . 'http/class.t3lib_http_request.php',
+       't3lib_http_observer_download' => PATH_t3lib . 'http/observer/class.t3lib_http_observer_download.php',
        't3lib_iconworks' => PATH_t3lib . 'class.t3lib_iconworks.php',
        't3lib_install' => PATH_t3lib . 'class.t3lib_install.php',
        't3lib_install_sql' => PATH_t3lib . 'class.t3lib_install_sql.php',
index 736ee7a..5e8e60f 100644 (file)
@@ -100,5 +100,38 @@ class t3lib_http_Request extends HTTP_Request2 {
 
        }
 
+       /**
+        * Downloads chunk by chunk to file instead of saving the whole response into memory.
+        * $response->getBody() will be empty.
+        * An existing file will be overridden.
+        *
+        * @param string $directory The absolute path to the directory in which the file is saved.
+        * @param string $filename The filename - if not set, it is determined automatically.
+        * @return HTTP_Request2_Response The response with empty body.
+        */
+       public function download($directory, $filename = '') {
+               $isAttached = FALSE;
+
+                       // Do not store the body in memory
+               $this->setConfig('store_body', FALSE);
+
+                       // Check if we already attached an instance of download. If so, just reuse it.
+               foreach ($this->observers as $observer) {
+                       if ($observer instanceof t3lib_http_observer_Download) {
+                               /** @var t3lib_http_observer_Download $attached */
+                               $observer->setDirectory($directory);
+                               $observer->setFilename($filename);
+                               $isAttached = TRUE;
+                       }
+               }
+
+               if (!$isAttached) {
+                       /** @var t3lib_http_observer_Download $observer */
+                       $observer = t3lib_div::makeInstance('t3lib_http_observer_Download', $directory, $filename);
+                       $this->attach($observer);
+               }
+
+               return $this->send();
+       }
 }
-?>
+?>
\ No newline at end of file
diff --git a/t3lib/http/observer/class.t3lib_http_observer_download.php b/t3lib/http/observer/class.t3lib_http_observer_download.php
new file mode 100644 (file)
index 0000000..ebd33da
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2011 Philipp Gampe (dev.typo3@philippgampe.info)
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project 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.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  This script 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.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Observer to automatically save a http request chunk by chunk to a file.
+ * If the file already exists, it will be overwritten.
+ * This follows an example in HTTP_Request2 manual.
+ *
+ * @see http://pear.php.net/manual/en/package.http.http-request2.observers.php
+ * @author Philipp Gampe
+ */
+class t3lib_http_observer_Download implements SplObserver {
+
+       /**
+        * @var resource A file pointer resource
+        */
+       protected $filePointer = FALSE;
+
+       /**
+        * @var string The full filename including the leading directory
+        */
+       protected $targetFilePath = '';
+
+       /**
+        * @var string The name of the target directory
+        */
+       protected $targetDirectory = '';
+
+       /**
+        * @var string The name of the target file
+        */
+       protected $targetFilename = '';
+
+       /**
+        * Constructor
+        *
+        * @throws InvalidArgumentException if directory is not found or is not within the PATH_site
+        * or within the lockRootPath
+        * @param string $directory The absolute path to the directory in which the file is saved.
+        * A trailing '/' is removed automatically.
+        * @param string $filename The filename - if not set, it is determined automatically.
+        */
+       public function __construct($directory, $filename = '') {
+               $this->setDirectory($directory);
+               $this->setFilename($filename);
+       }
+
+       /**
+        * Saves current chunk to disk each time a body part is received.
+        * If the filename is empty, tries to determine it from received headers
+        *
+        * @throws t3lib_exception if file can not be opened
+        * @throws UnexpectedValueException if the file name is empty and can not be determined from headers
+        * @param SplSubject|HTTP_Request2 $request
+        * @return void
+        */
+       public function update(SplSubject $request) {
+               $event = $request->getLastEvent();
+
+               switch ($event['name']) {
+                       case 'receivedHeaders':
+                               if ($this->targetFilename === '') {
+                                       $this->determineFilename($request, $event['data']);
+                               }
+                               $this->openFile();
+                               break;
+                       case 'receivedBodyPart':
+                               // Fall through
+                       case 'receivedEncodedBodyPart':
+                               fwrite($this->filePointer, $event['data']);
+                               break;
+                       case 'receivedBody':
+                               $this->closeFile();
+                               break;
+                       default:
+                               // do nothing
+               }
+       }
+
+       /**
+        * Sets the directory and checks whether the directory is available.
+        *
+        * @throws InvalidArgumentException if directory is not found or is not within the PATH_site
+        * or within the lockRootPath
+        * @param string $directory The absolute path to the directory in which the file is saved.
+        * @return void
+        */
+       public function setDirectory($directory) {
+               if (!is_dir($directory)) {
+                       throw new InvalidArgumentException($directory . ' is not a directory', 1312223779);
+               }
+               if (!t3lib_div::isAllowedAbsPath($directory)) {
+                       throw new InvalidArgumentException($directory . ' is not within the PATH_site'
+                                       . ' OR within the lockRootPath', 1328734617);
+               }
+               $this->targetDirectory = $directory = rtrim($directory, DIRECTORY_SEPARATOR);
+       }
+
+       /**
+        * Sets the filename.
+        *
+        * If the file already exists, it will be overridden
+        *
+        * @param string $filename The filename
+        * @return void
+        */
+       public function setFilename($filename = '') {
+               $this->targetFilename = basename($filename);
+       }
+
+       /**
+        * Determines the filename from either the 'content-disposition' header
+        * or from the basename of the current request.
+        *
+        * @param HTTP_Request2 $request
+        * @param HTTP_Request2_Response $response
+        * @return void
+        */
+       protected function determineFilename(HTTP_Request2 $request, HTTP_Request2_Response $response) {
+               $matches = array();
+
+               $disposition = $response->getHeader('content-disposition');
+               if ($disposition !== NULL
+                               && 0 === strpos($disposition, 'attachment')
+                               && 1 === preg_match('/filename="([^"]+)"/', $disposition, $matches)) {
+                       $filename = basename($matches[1]);
+               } else {
+                       $filename = basename($request->getUrl()->getPath());
+               }
+               $this->setFilename($filename);
+       }
+
+       /**
+        * Determines the absolute path to the file by combining the directory and filename.
+        * Afterwards tries to open the file for writing.
+        *
+        * $this->filename must be set before calling this function.
+        *
+        * @throws UnexpectedValueException if $this->filename is not set
+        * @throws t3lib_exception if file can not be opened
+        * @return void
+        */
+       protected function openFile() {
+               if ($this->targetFilename === '') {
+                       throw new UnexpectedValueException('The file name must not be empty', 1321113658);
+               }
+               $this->targetFilePath = $this->targetDirectory . DIRECTORY_SEPARATOR . $this->targetFilename;
+               $this->filePointer = @fopen($this->targetFilePath, 'wb');
+
+               if ($this->filePointer === FALSE) {
+                       throw new t3lib_exception('Cannot open target file ' . $this->targetFilePath, 1320833203);
+               }
+       }
+
+       /**
+        * Closes the file handler and fixes permissions.
+        *
+        * @return void
+        */
+       protected function closeFile() {
+               fclose($this->filePointer);
+               $this->filePointer = FALSE;
+               t3lib_div::fixPermissions($this->targetFilePath);
+       }
+}
+
+if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/http/observer/class.t3lib_http_observer_download.php'])) {
+       include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/http/observer/class.t3lib_http_observer_download.php']);
+}
+
+?>
\ No newline at end of file