[BUGFIX] Huge memory usage when copying files
authorAndreas Wolf <andreas.wolf@typo3.org>
Tue, 22 Sep 2015 07:27:57 +0000 (09:27 +0200)
committerAndreas Wolf <andreas.wolf@typo3.org>
Tue, 22 Sep 2015 07:27:58 +0000 (09:27 +0200)
Files were first read into memory and then dumped to disc, which breaks
for large files. Therefore, we now use the "dump to file" mechanism of
cURL. As Sabre does not expose this directly, we instead use our client
extension.

Classes/Dav/WebDavClient.php
Classes/Driver/WebDavDriver.php

index 4174c82..9583c60 100644 (file)
@@ -17,6 +17,8 @@ namespace TYPO3\FalWebdav\Dav;
 include_once __DIR__ . '/../../Resources/Composer/vendor/autoload.php';
 
 use Sabre\DAV\Client;
+use Sabre\HTTP;
+
 
 /**
  * Helper class to circumvent limitations in SabreDAV's support for cURL's certificate verification options.
@@ -31,6 +33,14 @@ class WebDavClient extends Client {
        protected $verifyCertificates = TRUE;
 
        /**
+        * The file to write the data to.
+        *
+        * @var resource
+        */
+       protected $outputFile = NULL;
+
+
+       /**
         * @param boolean $peerVerification
         */
        public function setCertificateVerification($peerVerification) {
@@ -48,6 +58,24 @@ class WebDavClient extends Client {
                if ($this->verifyCertificates === FALSE) {
                        curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
                }
+               if ($this->outputFile !== NULL) {
+                       // make sure the file is never returned into the default output stream
+                       curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, FALSE);
+                       // don’t return the headers
+                       curl_setopt($curlHandle, CURLOPT_HEADER, FALSE);
+                       curl_setopt($curlHandle, CURLOPT_FILE, $this->outputFile);
+                       curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, TRUE);
+                       $this->outputFile = NULL;
+               } else {
+                       // set back to default as the cURL handle is not always reset; the file is not used anyways, but cURL
+                       // still complains about the handle being gone away if we closed the file that was used in an earlier
+                       // request
+                       curl_setopt($curlHandle, CURLOPT_FILE, fopen('php://stdout','w'));
+
+                       // we must use a different order here; setting RETURNTRANSFER before resetting the file handle will let
+                       // cURL still use the file.
+                       curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, TRUE);
+               }
 
                return parent::curlExec($curlHandle);
        }
@@ -64,6 +92,26 @@ class WebDavClient extends Client {
                return parent::propFind($url, $properties, $depth); // TODO: Change the autogenerated stub
        }
 
+       /**
+        * Reads the given URL’s contents to the given file handle. The handle must be writable, the disk must have enough
+        * free space and
+        *
+        * @param string $url
+        * @param resource $fileHandle
+        * @param array $headers
+        * @return HTTP\ResponseInterface
+        */
+       public function readUrlToHandle($url, $fileHandle, array $headers = []) {
+               if (!is_resource($fileHandle)) {
+                       throw new \InvalidArgumentException('The file handle must be a valid resource');
+               }
+               $request = new HTTP\Request('GET', $url, $headers);
+
+               // The actual option is set in curlExec(), as we don’t have access to the cURL handle here
+               $this->outputFile = $fileHandle;
+
+               return $this->send($request);
+       }
 
 }
 
index 184e673..bbd97f2 100644 (file)
@@ -541,8 +541,12 @@ class WebDavDriver extends AbstractDriver {
                $temporaryPath = GeneralUtility::tempnam('vfs-tempfile-');
                $fileUrl = $this->getResourceUrl($fileIdentifier);
 
-               $result = $this->executeDavRequest('GET', $fileUrl);
-               file_put_contents($temporaryPath, $result['body']);
+               $fileHandle = fopen($temporaryPath, 'w');
+               $fileUrl = $this->encodeUrl($fileUrl);
+               $this->davClient->readUrlToHandle($fileUrl, $fileHandle);
+
+               // the handle is not closed by readUrlToHandle()!
+               fclose($fileHandle);
 
                return $temporaryPath;
        }