diff --git a/src/Symfony/Component/HttpFoundation/HeaderUtils.php b/src/Symfony/Component/HttpFoundation/HeaderUtils.php index 3ee50b87302e..f9e7bdac740b 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderUtils.php +++ b/src/Symfony/Component/HttpFoundation/HeaderUtils.php @@ -18,6 +18,9 @@ */ class HeaderUtils { + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + /** * This class should not be instantiated. */ @@ -143,6 +146,54 @@ public static function unquote(string $s): string return preg_replace('/\\\\(.)|"/', '$1', $s); } + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = array('filename' => $filenameFallback); + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + private static function groupParts(array $matches, string $separators): array { $separator = $separators[0]; diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index f4ca374fe0a4..1141e8d98712 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -251,51 +251,11 @@ public function clearCookie($name, $path = '/', $domain = null, $secure = false, } /** - * Generates a HTTP Content-Disposition field-value. - * - * @param string $disposition One of "inline" or "attachment" - * @param string $filename A unicode string - * @param string $filenameFallback A string containing only ASCII characters that - * is semantically equivalent to $filename. If the filename is already ASCII, - * it can be omitted, or just copied from $filename - * - * @return string A string suitable for use as a Content-Disposition field-value - * - * @throws \InvalidArgumentException - * - * @see RFC 6266 + * @see HeaderUtils::makeDisposition() */ public function makeDisposition($disposition, $filename, $filenameFallback = '') { - if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { - throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); - } - - if ('' == $filenameFallback) { - $filenameFallback = $filename; - } - - // filenameFallback is not ASCII. - if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { - throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); - } - - // percent characters aren't safe in fallback. - if (false !== strpos($filenameFallback, '%')) { - throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); - } - - // path separators aren't allowed in either. - if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { - throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); - } - - $params = array('filename' => $filenameFallback); - if ($filename !== $filenameFallback) { - $params['filename*'] = "utf-8''".rawurlencode($filename); - } - - return $disposition.'; '.HeaderUtils::toString($params, ';'); + return HeaderUtils::makeDisposition((string) $disposition, (string) $filename, (string) $filenameFallback); } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php index 2f5fdc21c3b2..15efdf9234c1 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php @@ -82,4 +82,53 @@ public function testUnquote() $this->assertEquals('foo "bar"', HeaderUtils::unquote('"foo \"\b\a\r\""')); $this->assertEquals('foo \\ bar', HeaderUtils::unquote('"foo \\\\ bar"')); } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionInvalidDisposition() + { + HeaderUtils::makeDisposition('invalid', 'foo.html'); + } + + /** + * @dataProvider provideMakeDisposition + */ + public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) + { + $this->assertEquals($expected, HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback)); + } + + public function provideMakeDisposition() + { + return array( + array('attachment', 'foo.html', 'foo.html', 'attachment; filename=foo.html'), + array('attachment', 'foo.html', '', 'attachment; filename=foo.html'), + array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'), + array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'), + array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'), + array('attachment', 'föö.html', 'foo.html', 'attachment; filename=foo.html; filename*=utf-8\'\'f%C3%B6%C3%B6.html'), + ); + } + + /** + * @dataProvider provideMakeDispositionFail + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionFail($disposition, $filename) + { + HeaderUtils::makeDisposition($disposition, $filename); + } + + public function provideMakeDispositionFail() + { + return array( + array('attachment', 'foo%20bar.html'), + array('attachment', 'foo/bar.html'), + array('attachment', '/foo.html'), + array('attachment', 'foo\bar.html'), + array('attachment', '\foo.html'), + array('attachment', 'föö.html'), + ); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index 2544e2bad457..f6ddb98ea761 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -250,26 +250,6 @@ public function testGetCookiesWithInvalidArgument() $bag->getCookies('invalid_argument'); } - /** - * @expectedException \InvalidArgumentException - */ - public function testMakeDispositionInvalidDisposition() - { - $headers = new ResponseHeaderBag(); - - $headers->makeDisposition('invalid', 'foo.html'); - } - - /** - * @dataProvider provideMakeDisposition - */ - public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) - { - $headers = new ResponseHeaderBag(); - - $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback)); - } - public function testToStringDoesntMessUpHeaders() { $headers = new ResponseHeaderBag(); @@ -284,41 +264,6 @@ public function testToStringDoesntMessUpHeaders() $this->assertEquals(array('text/html'), $allHeaders['Content-type']); } - public function provideMakeDisposition() - { - return array( - array('attachment', 'foo.html', 'foo.html', 'attachment; filename=foo.html'), - array('attachment', 'foo.html', '', 'attachment; filename=foo.html'), - array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'), - array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'), - array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'), - array('attachment', 'föö.html', 'foo.html', 'attachment; filename=foo.html; filename*=utf-8\'\'f%C3%B6%C3%B6.html'), - ); - } - - /** - * @dataProvider provideMakeDispositionFail - * @expectedException \InvalidArgumentException - */ - public function testMakeDispositionFail($disposition, $filename) - { - $headers = new ResponseHeaderBag(); - - $headers->makeDisposition($disposition, $filename); - } - - public function provideMakeDispositionFail() - { - return array( - array('attachment', 'foo%20bar.html'), - array('attachment', 'foo/bar.html'), - array('attachment', '/foo.html'), - array('attachment', 'foo\bar.html'), - array('attachment', '\foo.html'), - array('attachment', 'föö.html'), - ); - } - public function testDateHeaderAddedOnCreation() { $now = time();