Skip to content

Commit

Permalink
feature #28807 [HttpFoundation] Make ResponseHeaderBag::makeDispositi…
Browse files Browse the repository at this point in the history
…on static (fabpot)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[HttpFoundation] Make ResponseHeaderBag::makeDisposition static

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes-ish
| BC breaks?    | no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes
| Fixed tickets | #27851
| License       | MIT
| Doc PR        | n/a

<!--
Write a short README entry for your feature/bugfix here (replace this comment block.)
This will help people understand your PR and can be used as a start of the Doc PR.
Additionally:
 - Bug fixes must be submitted against the lowest branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against the master branch.
-->

Commits
-------

d29b410 [HttpFoundation] made ResponseHeaderBag::makeDisposition static
  • Loading branch information
fabpot committed Oct 15, 2018
2 parents 2b34f2b + d29b410 commit 76f80e9
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 97 deletions.
51 changes: 51 additions & 0 deletions src/Symfony/Component/HttpFoundation/HeaderUtils.php
Expand Up @@ -18,6 +18,9 @@
*/
class HeaderUtils
{
const DISPOSITION_ATTACHMENT = 'attachment';
const DISPOSITION_INLINE = 'inline';

/**
* This class should not be instantiated.
*/
Expand Down Expand Up @@ -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];
Expand Down
44 changes: 2 additions & 42 deletions src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php
Expand Up @@ -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);
}

/**
Expand Down
49 changes: 49 additions & 0 deletions src/Symfony/Component/HttpFoundation/Tests/HeaderUtilsTest.php
Expand Up @@ -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'),
);
}
}
Expand Up @@ -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();
Expand All @@ -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();
Expand Down

0 comments on commit 76f80e9

Please sign in to comment.