BinaryFileResponseTest

class BinaryFileResponseTest extends ResponseTestCase

Methods

public testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE() No description from ResponseTestCase
protected provideResponse() No description
public testConstruction() No description
public testConstructWithNonAsciiFilename() No description
public testSetContent() No description
public testGetContent() No description
public testSetContentDispositionGeneratesSafeFallbackFilename() No description
public testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename() No description
public testRequests($requestRange, $offset, $length, $responseRange) No description
public testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) No description
public provideRanges() No description
public testRangeRequestsWithoutLastModifiedDate() No description
public testFullFileRequests($requestRange) No description
public provideFullFileRanges() No description
public testInvalidRequests($requestRange) No description
public provideInvalidRanges() No description
public testXSendfile($file) No description
public provideXSendfileFiles() No description
public testXAccelMapping($realpath, $mapping, $virtual) No description
public testDeleteFileAfterSend() No description
public testAcceptRangeOnUnsafeMethods() No description
public testAcceptRangeNotOverriden() No description
public getSampleXAccelMappings() No description
public testStream() No description
public static tearDownAfterClass() No description

Details

in ResponseTestCase at line 19

testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE()

public testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE()
at line 340

provideResponse()

protected provideResponse()
at line 22

testConstruction()

public testConstruction()
at line 38

testConstructWithNonAsciiFilename()

public testConstructWithNonAsciiFilename()
at line 52

testSetContent()

public testSetContent()
at line 58

testGetContent()

public testGetContent()
at line 64

testSetContentDispositionGeneratesSafeFallbackFilename()

public testSetContentDispositionGeneratesSafeFallbackFilename()
at line 72

testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()

public testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
at line 86

testRequests()

public testRequests($requestRange, $offset, $length, $responseRange)

Parameters

$requestRange
$offset
$length
$responseRange
at line 118

testRequestsWithoutEtag()

public testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange)

Parameters

$requestRange
$offset
$length
$responseRange
at line 146

provideRanges()

public provideRanges()
at line 157

testRangeRequestsWithoutLastModifiedDate()

public testRangeRequestsWithoutLastModifiedDate()
at line 179

testFullFileRequests()

public testFullFileRequests($requestRange)

Parameters

$requestRange
at line 199

provideFullFileRanges()

public provideFullFileRanges()
at line 214

testInvalidRequests()

public testInvalidRequests($requestRange)

Parameters

$requestRange
at line 230

provideInvalidRanges()

public provideInvalidRanges()
at line 241

testXSendfile()

public testXSendfile($file)

Parameters

$file
at line 256

provideXSendfileFiles()

public provideXSendfileFiles()
at line 267

testXAccelMapping()

public testXAccelMapping($realpath, $mapping, $virtual)

Parameters

$realpath
$mapping
$virtual
at line 286

testDeleteFileAfterSend()

public testDeleteFileAfterSend()
at line 304

testAcceptRangeOnUnsafeMethods()

public testAcceptRangeOnUnsafeMethods()
at line 313

testAcceptRangeNotOverriden()

public testAcceptRangeNotOverriden()
at line 323

getSampleXAccelMappings()

public getSampleXAccelMappings()
at line 331

testStream()

public testStream()
at line 345

tearDownAfterClass()

public static tearDownAfterClass()

Source code

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\HttpFoundation\Tests;

use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\Tests\File\FakeFile;

class BinaryFileResponseTest extends ResponseTestCase
{
    public function testConstruction()
    {
        $file = __DIR__.'/../README.md';
        $response = new BinaryFileResponse($file, 404, array('X-Header' => 'Foo'), true, null, true, true);
        $this->assertEquals(404, $response->getStatusCode());
        $this->assertEquals('Foo', $response->headers->get('X-Header'));
        $this->assertTrue($response->headers->has('ETag'));
        $this->assertTrue($response->headers->has('Last-Modified'));
        $this->assertFalse($response->headers->has('Content-Disposition'));

        $response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE);
        $this->assertEquals(404, $response->getStatusCode());
        $this->assertFalse($response->headers->has('ETag'));
        $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition'));
    }

    public function testConstructWithNonAsciiFilename()
    {
        touch(sys_get_temp_dir().'/fööö.html');

        $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, array(), true, 'attachment');

        @unlink(sys_get_temp_dir().'/fööö.html');

        $this->assertSame('fööö.html', $response->getFile()->getFilename());
    }

    /**
     * @expectedException \LogicException
     */
    public function testSetContent()
    {
        $response = new BinaryFileResponse(__FILE__);
        $response->setContent('foo');
    }

    public function testGetContent()
    {
        $response = new BinaryFileResponse(__FILE__);
        $this->assertFalse($response->getContent());
    }

    public function testSetContentDispositionGeneratesSafeFallbackFilename()
    {
        $response = new BinaryFileResponse(__FILE__);
        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html');

        $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
    }

    public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
    {
        $response = new BinaryFileResponse(__FILE__);

        $iso88591EncodedFilename = utf8_decode('föö.html');
        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename);

        // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one)
        $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
    }

    /**
     * @dataProvider provideRanges
     */
    public function testRequests($requestRange, $offset, $length, $responseRange)
    {
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();

        // do a request to get the ETag
        $request = Request::create('/');
        $response->prepare($request);
        $etag = $response->headers->get('ETag');

        // prepare a request for a range of the testing file
        $request = Request::create('/');
        $request->headers->set('If-Range', $etag);
        $request->headers->set('Range', $requestRange);

        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
        fseek($file, $offset);
        $data = fread($file, $length);
        fclose($file);

        $this->expectOutputString($data);
        $response = clone $response;
        $response->prepare($request);
        $response->sendContent();

        $this->assertEquals(206, $response->getStatusCode());
        $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
        $this->assertSame($length, $response->headers->get('Content-Length'));
    }

    /**
     * @dataProvider provideRanges
     */
    public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange)
    {
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));

        // do a request to get the LastModified
        $request = Request::create('/');
        $response->prepare($request);
        $lastModified = $response->headers->get('Last-Modified');

        // prepare a request for a range of the testing file
        $request = Request::create('/');
        $request->headers->set('If-Range', $lastModified);
        $request->headers->set('Range', $requestRange);

        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
        fseek($file, $offset);
        $data = fread($file, $length);
        fclose($file);

        $this->expectOutputString($data);
        $response = clone $response;
        $response->prepare($request);
        $response->sendContent();

        $this->assertEquals(206, $response->getStatusCode());
        $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
    }

    public function provideRanges()
    {
        return array(
            array('bytes=1-4', 1, 4, 'bytes 1-4/35'),
            array('bytes=-5', 30, 5, 'bytes 30-34/35'),
            array('bytes=30-', 30, 5, 'bytes 30-34/35'),
            array('bytes=30-30', 30, 1, 'bytes 30-30/35'),
            array('bytes=30-34', 30, 5, 'bytes 30-34/35'),
        );
    }

    public function testRangeRequestsWithoutLastModifiedDate()
    {
        // prevent auto last modified
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false);

        // prepare a request for a range of the testing file
        $request = Request::create('/');
        $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT');
        $request->headers->set('Range', 'bytes=1-4');

        $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif'));
        $response = clone $response;
        $response->prepare($request);
        $response->sendContent();

        $this->assertEquals(200, $response->getStatusCode());
        $this->assertNull($response->headers->get('Content-Range'));
    }

    /**
     * @dataProvider provideFullFileRanges
     */
    public function testFullFileRequests($requestRange)
    {
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();

        // prepare a request for a range of the testing file
        $request = Request::create('/');
        $request->headers->set('Range', $requestRange);

        $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
        $data = fread($file, 35);
        fclose($file);

        $this->expectOutputString($data);
        $response = clone $response;
        $response->prepare($request);
        $response->sendContent();

        $this->assertEquals(200, $response->getStatusCode());
    }

    public function provideFullFileRanges()
    {
        return array(
            array('bytes=0-'),
            array('bytes=0-34'),
            array('bytes=-35'),
            // Syntactical invalid range-request should also return the full resource
            array('bytes=20-10'),
            array('bytes=50-40'),
        );
    }

    /**
     * @dataProvider provideInvalidRanges
     */
    public function testInvalidRequests($requestRange)
    {
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();

        // prepare a request for a range of the testing file
        $request = Request::create('/');
        $request->headers->set('Range', $requestRange);

        $response = clone $response;
        $response->prepare($request);
        $response->sendContent();

        $this->assertEquals(416, $response->getStatusCode());
        $this->assertEquals('bytes */35', $response->headers->get('Content-Range'));
    }

    public function provideInvalidRanges()
    {
        return array(
            array('bytes=-40'),
            array('bytes=30-40'),
        );
    }

    /**
     * @dataProvider provideXSendfileFiles
     */
    public function testXSendfile($file)
    {
        $request = Request::create('/');
        $request->headers->set('X-Sendfile-Type', 'X-Sendfile');

        BinaryFileResponse::trustXSendfileTypeHeader();
        $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream'));
        $response->prepare($request);

        $this->expectOutputString('');
        $response->sendContent();

        $this->assertContains('README.md', $response->headers->get('X-Sendfile'));
    }

    public function provideXSendfileFiles()
    {
        return array(
            array(__DIR__.'/../README.md'),
            array('file://'.__DIR__.'/../README.md'),
        );
    }

    /**
     * @dataProvider getSampleXAccelMappings
     */
    public function testXAccelMapping($realpath, $mapping, $virtual)
    {
        $request = Request::create('/');
        $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
        $request->headers->set('X-Accel-Mapping', $mapping);

        $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test');

        BinaryFileResponse::trustXSendfileTypeHeader();
        $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream'));
        $reflection = new \ReflectionObject($response);
        $property = $reflection->getProperty('file');
        $property->setAccessible(true);
        $property->setValue($response, $file);

        $response->prepare($request);
        $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect'));
    }

    public function testDeleteFileAfterSend()
    {
        $request = Request::create('/');

        $path = __DIR__.'/File/Fixtures/to_delete';
        touch($path);
        $realPath = realpath($path);
        $this->assertFileExists($realPath);

        $response = new BinaryFileResponse($realPath, 200, array('Content-Type' => 'application/octet-stream'));
        $response->deleteFileAfterSend(true);

        $response->prepare($request);
        $response->sendContent();

        $this->assertFileNotExists($path);
    }

    public function testAcceptRangeOnUnsafeMethods()
    {
        $request = Request::create('/', 'POST');
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
        $response->prepare($request);

        $this->assertEquals('none', $response->headers->get('Accept-Ranges'));
    }

    public function testAcceptRangeNotOverriden()
    {
        $request = Request::create('/', 'POST');
        $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
        $response->headers->set('Accept-Ranges', 'foo');
        $response->prepare($request);

        $this->assertEquals('foo', $response->headers->get('Accept-Ranges'));
    }

    public function getSampleXAccelMappings()
    {
        return array(
            array('/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'),
            array('/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'),
        );
    }

    public function testStream()
    {
        $request = Request::create('/');
        $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, array('Content-Type' => 'text/plain'));
        $response->prepare($request);

        $this->assertNull($response->headers->get('Content-Length'));
    }

    protected function provideResponse()
    {
        return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream'));
    }

    public static function tearDownAfterClass()
    {
        $path = __DIR__.'/../Fixtures/to_delete';
        if (file_exists($path)) {
            @unlink($path);
        }
    }
}