Skip to content

Commit 60fd9ac

Browse files
committed
Add support for structured MIME suffix formats and subtype fallbacks in Request::getFormat().
1 parent 585e9df commit 60fd9ac

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead
8+
* Add support for structured MIME suffix
89

910
7.3
1011
---

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,7 @@ public static function getMimeTypes(string $format): array
12251225
/**
12261226
* Gets the format associated with the mime type.
12271227
*/
1228-
public function getFormat(?string $mimeType): ?string
1228+
public function getFormat(?string $mimeType, bool $subtypeFallback = false): ?string
12291229
{
12301230
$canonicalMimeType = null;
12311231
if ($mimeType && false !== $pos = strpos($mimeType, ';')) {
@@ -1245,6 +1245,27 @@ public function getFormat(?string $mimeType): ?string
12451245
}
12461246
}
12471247

1248+
if (!$canonicalMimeType) {
1249+
$canonicalMimeType = $mimeType;
1250+
}
1251+
1252+
if (str_starts_with($canonicalMimeType, 'application/') && str_contains($canonicalMimeType, '+')) {
1253+
$suffix = substr(strrchr($canonicalMimeType, '+'), 1);
1254+
if (isset(static::getStructuredSuffixFormats()[$suffix])) {
1255+
return static::getStructuredSuffixFormats()[$suffix];
1256+
}
1257+
}
1258+
1259+
if ($subtypeFallback && str_contains($canonicalMimeType, '/')) {
1260+
[, $subtype] = explode('/', $canonicalMimeType, 2);
1261+
if (str_starts_with($subtype, 'x-')) {
1262+
$subtype = substr($subtype, 2);
1263+
}
1264+
if (!str_contains($subtype, '+')) {
1265+
return $subtype;
1266+
}
1267+
}
1268+
12481269
return null;
12491270
}
12501271

@@ -1917,6 +1938,41 @@ protected static function initializeFormats(): void
19171938
'atom' => ['application/atom+xml'],
19181939
'rss' => ['application/rss+xml'],
19191940
'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'],
1941+
'soap' => ['application/soap+xml'],
1942+
'problem' => ['application/problem+json'],
1943+
'hal' => ['application/hal+json', 'application/hal+xml'],
1944+
'jsonapi' => ['application/vnd.api+json'],
1945+
'yaml' => ['text/yaml', 'application/x-yaml'],
1946+
'wbxml' => ['application/vnd.wap.wbxml'],
1947+
'pdf' => ['application/pdf'],
1948+
'csv' => ['text/csv'],
1949+
];
1950+
}
1951+
1952+
/**
1953+
* Structured MIME suffix fallback formats
1954+
*
1955+
* This mapping is used when no exact MIME match is found in $formats.
1956+
* It enables handling of types like application/soap+xml → 'xml'.
1957+
*
1958+
* @see https://datatracker.ietf.org/doc/html/rfc6839
1959+
* @see https://datatracker.ietf.org/doc/html/rfc7303
1960+
*
1961+
* @return list<string>
1962+
*/
1963+
private static function getStructuredSuffixFormats(): array
1964+
{
1965+
return [
1966+
'json' => 'json',
1967+
'xml' => 'xml',
1968+
'xhtml' => 'html',
1969+
'cbor' => 'cbor',
1970+
'zip' => 'zip',
1971+
'ber' => 'asn1',
1972+
'der' => 'asn1',
1973+
'tlv' => 'tlv',
1974+
'wbxml' => 'xml',
1975+
'yaml' => 'yaml',
19201976
];
19211977
}
19221978

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,38 @@ public static function getFormatToMimeTypeMapProvider()
531531
['xml', ['text/xml', 'application/xml', 'application/x-xml']],
532532
['rdf', ['application/rdf+xml']],
533533
['atom', ['application/atom+xml']],
534+
['rss', ['application/rss+xml']],
535+
['soap', ['application/soap+xml']],
534536
['form', ['application/x-www-form-urlencoded', 'multipart/form-data']],
537+
['html', ['application/xhtml+xml']],
538+
['problem', ['application/problem+json']],
539+
['hal', ['application/hal+json', 'application/hal+xml']],
540+
['jsonapi', ['application/vnd.api+json']],
541+
['yaml', ['application/x-yaml', 'text/yaml']],
542+
['wbxml', ['application/vnd.wap.wbxml']],
543+
];
544+
}
545+
546+
/**
547+
* @dataProvider getFormatWithSubtypeFallbackProvider
548+
*/
549+
public function testGetFormatFromMimeTypeWithSubtypeFallback($expectedFormat, $mimeTypes)
550+
{
551+
$request = new Request();
552+
foreach ($mimeTypes as $mime) {
553+
$this->assertEquals($expectedFormat, $request->getFormat($mime, true));
554+
}
555+
}
556+
557+
public static function getFormatWithSubtypeFallbackProvider()
558+
{
559+
return [
560+
['cbor', ['application/example+cbor']],
561+
['asn1', ['application/ber-stream+ber', 'application/secure-data+der']],
562+
['zip', ['application/foobar+zip']],
563+
['tlv', ['application/device-config+tlv']],
564+
['pdf', ['application/pdf']],
565+
['csv', ['text/csv']],
535566
];
536567
}
537568

0 commit comments

Comments
 (0)