diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 06dd307..5d35672 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -11,19 +11,19 @@ - + %igorw_file_serve.base_dir% - + %igorw_file_serve.skip_file_exists% - + %igorw_file_serve.base_dir% - + %igorw_file_serve.skip_file_exists% - + %igorw_file_serve.base_dir% - + %igorw_file_serve.skip_file_exists% diff --git a/Response/AbstractResponseFactory.php b/Response/AbstractResponseFactory.php index 82d3139..972c945 100644 --- a/Response/AbstractResponseFactory.php +++ b/Response/AbstractResponseFactory.php @@ -2,22 +2,22 @@ namespace Igorw\FileServeBundle\Response; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Request; abstract class AbstractResponseFactory { protected $baseDir; protected $skipFileExists; - protected $request; + protected $requestStack; protected $fullFilename; protected $contentType; protected $options; - public function __construct($baseDir, Request $request, $skipFileExists = false) + public function __construct($baseDir, RequestStack $requestStack, $skipFileExists = false) { $this->baseDir = $baseDir; - $this->request = $request; + $this->requestStack = $requestStack; $this->skipFileExists = $skipFileExists; } @@ -68,16 +68,41 @@ protected function resolveDispositionHeader(array $options) } /** - * Algorithm inspired by phpBB3 + * @param string $filename + * + * @return string */ protected function resolveDispositionHeaderFilename($filename) { - $userAgent = $this->request->headers->get('User-Agent'); + if ($this->clientSupportsUtf8Filename()) { + $dispositionHeaderFilename = "filename*=UTF-8''".rawurlencode($filename); + } else { + $dispositionHeaderFilename = "filename=".rawurlencode($filename); + } + + return $dispositionHeaderFilename; + } + + /** + * Algorithm inspired by phpBB3 + * + * @throws \LogicException + * + * @return bool + */ + private function clientSupportsUtf8Filename() + { + $request = $this->requestStack->getCurrentRequest(); + if (is_null($request)) { + throw new \LogicException('Current request is not available'); + } + + $userAgent = $request->headers->get('User-Agent'); if (preg_match('#MSIE|Safari|Konqueror#', $userAgent)) { - return "filename=".rawurlencode($filename); + return false; } - return "filename*=UTF-8''".rawurlencode($filename); + return true; } } diff --git a/Tests/Response/PhpResponseFactoryTest.php b/Tests/Response/PhpResponseFactoryTest.php index 0ddeb9a..a0994bc 100644 --- a/Tests/Response/PhpResponseFactoryTest.php +++ b/Tests/Response/PhpResponseFactoryTest.php @@ -3,6 +3,7 @@ namespace Igorw\FileServeBundle\Response; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; class PhpResponseFactoryTest extends \PHPUnit_Framework_TestCase { @@ -10,9 +11,9 @@ class PhpResponseFactoryTest extends \PHPUnit_Framework_TestCase * @test * @dataProvider provideRequestsAndContentDisposition */ - public function createShouldReturnResponseWithRequestSpecificContentDisposition($disposition, $request) + public function createShouldReturnResponseWithRequestSpecificContentDisposition($disposition, $requestStack) { - $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', $request); + $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', $requestStack); $options = array( 'serve_filename' => 'internet.zip', @@ -26,7 +27,8 @@ public function createShouldReturnResponseWithRequestSpecificContentDisposition( /** @test */ public function createWithRelativePath() { - $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', new Request()); + $requestStack = $this->createRequestStack(); + $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', $requestStack); $response = $factory->create('internet.txt', 'text/plain'); @@ -40,7 +42,8 @@ public function createWithRelativePath() /** @test */ public function createWithAbsolutePath() { - $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', new Request()); + $requestStack = $this->createRequestStack(); + $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', $requestStack); $response = $factory->create(__DIR__.'/../Fixtures/internet.txt', 'text/plain', array( 'absolute_path' => true, @@ -59,7 +62,8 @@ public function createWithAbsolutePath() */ public function createWithNonExistentPathShouldThrowException() { - $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', new Request()); + $requestStack = $this->createRequestStack(); + $factory = new PhpResponseFactory(__DIR__.'/../Fixtures', $requestStack); $response = $factory->create(__DIR__.'/../Fixtures/missing.txt', 'text/plain'); } @@ -67,15 +71,40 @@ public function createWithNonExistentPathShouldThrowException() public function provideRequestsAndContentDisposition() { return array( - array('attachment; filename=internet.zip', $this->createRequestWithUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Ubuntu/12.04 Chromium/20.0.1132.47 Chrome/20.0.1132.47 Safari/536.11')), - array('attachment; filename*=UTF-8\'\'internet.zip', $this->createRequestWithUserAgent('Yeti/1.0 (NHN Corp.; http://help.naver.com/robots/)')), + 'user agent without UTF-8 filename support' => array( + 'attachment; filename=internet.zip', + $this->createRequestStackWithUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Ubuntu/12.04 Chromium/20.0.1132.47 Chrome/20.0.1132.47 Safari/536.11') + ), + 'user agent with UTF-8 filename support' => array( + 'attachment; filename*=UTF-8\'\'internet.zip', + $this->createRequestStackWithUserAgent('Yeti/1.0 (NHN Corp.; http://help.naver.com/robots/)') + ), ); } - private function createRequestWithUserAgent($userAgent) + /** + * @param string $userAgent + * + * @return RequestStack + */ + private function createRequestStackWithUserAgent($userAgent) + { + $requestStack = $this->createRequestStack(); + $requestStack->getCurrentRequest()->headers->set('User-Agent', $userAgent); + + return $requestStack; + } + + /** + * @return RequestStack + */ + private function createRequestStack() { $request = Request::create('/'); - $request->headers->set('User-Agent', $userAgent); - return $request; + + $requestStack = new RequestStack(); + $requestStack->push($request); + + return $requestStack; } } diff --git a/Tests/Response/SendfileResponseFactoryTest.php b/Tests/Response/SendfileResponseFactoryTest.php index 7d259a3..5aceedd 100644 --- a/Tests/Response/SendfileResponseFactoryTest.php +++ b/Tests/Response/SendfileResponseFactoryTest.php @@ -3,15 +3,30 @@ namespace Igorw\FileServeBundle\Response; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; class SendfileResponseFactoryTest extends \PHPUnit_Framework_TestCase { /** @test */ public function createShouldSetXAccelRedirectHeader() { - $factory = new SendfileResponseFactory(__DIR__.'/../Fixtures', new Request()); + $requestStack = $this->createRequestStack(); + $factory = new SendfileResponseFactory(__DIR__.'/../Fixtures', $requestStack); $response = $factory->create('internet.txt', 'application/zip'); $this->assertSame(__DIR__.'/../Fixtures/internet.txt', $response->headers->get('X-Accel-Redirect')); } + + /** + * @return RequestStack + */ + private function createRequestStack() + { + $request = Request::create('/'); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + return $requestStack; + } } diff --git a/Tests/Response/XsendfileResponseFactoryTest.php b/Tests/Response/XsendfileResponseFactoryTest.php index 6324314..2214c31 100644 --- a/Tests/Response/XsendfileResponseFactoryTest.php +++ b/Tests/Response/XsendfileResponseFactoryTest.php @@ -3,15 +3,30 @@ namespace Igorw\FileServeBundle\Response; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; class XsendfileResponseFactoryTest extends \PHPUnit_Framework_TestCase { /** @test */ public function createShouldSetXSendfileHeader() { - $factory = new XsendfileResponseFactory(__DIR__.'/../Fixtures', new Request()); + $requestStack = $this->createRequestStack(); + $factory = new XsendfileResponseFactory(__DIR__.'/../Fixtures', $requestStack); $response = $factory->create('internet.txt', 'application/zip'); $this->assertSame(__DIR__.'/../Fixtures/internet.txt', $response->headers->get('X-Sendfile')); } + + /** + * @return RequestStack + */ + private function createRequestStack() + { + $request = Request::create('/'); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + return $requestStack; + } }