Bug #69223

dumpFileContents does not work when ['FE']['compressionLevel'] > 0

Added by Torben Hansen about 4 years ago. Updated 5 months ago.

Status:
New
Priority:
Should have
Assignee:
-
Category:
File Abstraction Layer (FAL)
Start date:
2015-08-21
Due date:
% Done:

0%

TYPO3 Version:
7
PHP Version:
Tags:
Complexity:
Is Regression:
No
Sprint Focus:

Description

When ['FE']['compressionLevel'] > 0, dumpFileContents does not output any file, but throws the following exception:

Core: Exception handler (WEB): Uncaught TYPO3 Exception: #1: PHP Warning: Cannot modify header information - headers already sent in /path/to/typo3_src/6.2/typo3_src-6.2.14/typo3/sysext/frontend/Classes/Utility/CompressionUtility.php

How to reproduce:

1. Set ['FE']['compressionLevel'] = 5
2. Dump a file from FAL like shown below

$storage = $this->resourceFactory->getDefaultStorage();
$file = $storage->getFile('test.jpg');
$storage->dumpFileContents($file, TRUE, 'test-filename.jpg');

History

#1 Updated by Frans Saris about 4 years ago

  • Status changed from New to Needs Feedback

Also when you put a exit; right after the dump?

We use this in https://github.com/beechit/fal_securedownload/blob/master/Classes/Hooks/FileDumpHook.php and can remember any issues. But maybe that is due the fact it used in the eId context.

Can you determine where the first header is send?

#2 Updated by Torben Hansen about 4 years ago

Adding exit; does not solve the problem. I believe, that the first header gets send by ResourceStorage->dumpFileContents(), because if I comment out the header sent by the compression utility, then the download gets triggered. Here the next problem appears, which is a following problem of the compression.

When compression is enabled, the ResourceStorage->dumpFileContents() actually sends the correct header information for the file, but the compression messes this up. I'll dump an .ics file and ResourceStorage->dumpFileContents() sets the mime type correctly to "text/calendar". When compression is enabled, this mime type sent in the headers does'nt match the mime type of the sent file, so the browser refuses to download the file.

Resource interpreted as Document but transferred with MIME type text/calendar

IMO dumpFileContents() should ignore FE compression and just dump the file as it is.

#3 Updated by Frans Saris about 4 years ago

If I read the code correct the compression is disabled in $storage->dumpFileContents. Further are the headers send by the dumpFileContents the last headers send. Before the file is dumped the output buffer is cleared and after that no compression is set. See ResourceStorage::dumpFileContents().

So what is already outputted before you call $storage->dumpFileContents?

#4 Updated by Torben Hansen about 4 years ago

My code is really simple.

I have an ExtBase controller, where I have the following action (simplyfied).

public function downloadAction() {
    $storage = $this->resourceFactory->getDefaultStorage();
    $file = $storage->getFile('test.jpg');
    $storage->dumpFileContents($file, TRUE, 'test-filename.jpg');
    return FALSE;
}

This all works well, when FE compression is disabled. When I enable FE compression, the exception is thrown. I get the following headers as a response from the server

HTTP/1.1 200 OK
Date: Fri, 21 Aug 2015 11:17:39 GMT
Server: Apache/2.2.29 (Unix) mod_ssl/2.2.29 OpenSSL/1.0.2a DAV/2 PHP/5.6.9
X-Powered-By: PHP/5.6.9
Expires: 0
Last-Modified: Fri, 21 Aug 2015 11:17:39 GMT
Cache-Control: ''
Pragma: no-cache
Content-Disposition: attachment; filename="event_1.ics" 
Content-Length: 281
Content-Encoding: gzip
Vary: Accept-Encoding
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/calendar;charset=UTF-8

For me, everything seems fine except the Content-Encoding: gzip header. The Content-Length header is ok and outputs the content length for the uncompressed file. So I wonder where this Content-Encoding: gzip header comes from.

Does the ExtBase action send the Content-Encoding: gzip header and if yes, can it be avoided?

#5 Updated by Torben Hansen almost 4 years ago

Any idea how to solve this issue? It seems dumpFileContents for the frontend context only works correct, when FE compression is disabled. Any ideas for a workaround?

#6 Updated by Mathias Schreiber almost 4 years ago

  • Target version deleted (next-patchlevel)

#7 Updated by Alexander Opitz over 3 years ago

  • Status changed from Needs Feedback to New
  • Target version set to next-patchlevel

#8 Updated by Sven Teuber about 2 years ago

  • TYPO3 Version changed from 6.2 to 7

Issue is still present in TYPO3 7.6.19 LTS (bump)

The Content-Encoding: gzip header Torben mentioned, which is the source of the problem, seems to be set by PHP when zlib.output_compression_level is set in \TYPO3\CMS\Frontend\Http\RequestHandler::initializeOutputCompression. I guess it should be removed before dumping a file with dumpFileContents().

#9 Updated by Susanne Moog 7 months ago

  • Status changed from New to Needs Feedback

Can you check this behaviour with 9? Request/Repsonse handling got lots of changes, it might be fixed.

#10 Updated by Torben Hansen 7 months ago

Yes I verified this with TYPO3 9.5 and the problem is still valid, since the method dumpFileContents is still the same.

But anyway, dumpFileContents is deprecated since V9, so I tried to migrate to streamFile instead. Migration (especially using the functionality in an Extbase extension) is not really easy/possible(?), since the successor for dumpFileContents works different. dumpFileContents streams the file directly to the client using output buffering, but streamFile returns a PSR-7 response object.

As far as I know, Extbase has its on request/response handling, so you can't just return the PSR-7 response object in an Extbase action to stream the file to the client. Also I did not find something to emit the PSR7 response in Extbase and a search on Slack resulted in one single person asking for help about this topic, but with no response.

In TYPO3 backend, application::sendResponse is used to emit a PSR-7 response object. I implemented a similar method in my Extbase controller to force output of the content of the PSR-7 response, but honesty this feels dirty (see code below).

    public function downloadAction()
    {
        $storage = $this->resourceFactory->getDefaultStorage();
        $file = $storage->getFile('test.jpg');
        $response = $storage->streamFile($file, true, 'test-filename.jpg');
        $this->sendResponse($response);
        exit();
    }

    protected function sendResponse($response)
    {
        if (!headers_sent()) {
            if (http_response_code() === 200) {
                header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
            }

            foreach ($response->getHeaders() as $name => $values) {
                header($name . ': ' . implode(', ', $values));
            }
        }
        $body = $response->getBody();
        echo $body->__toString();
    }

Is there a recommended way to emit PSR-7 response objects in Extbase? Or is there maybe something I am missing?

#11 Updated by Riccardo De Contardi 7 months ago

  • Status changed from Needs Feedback to New

#12 Updated by Benni Mack 5 months ago

  • Target version changed from next-patchlevel to Candidate for patchlevel

Also available in: Atom PDF