Skip to content

Commit e0365cc

Browse files
committed
feature symfony#61267 [HttpFoundation] Add support for structured MIME suffix (Spomky)
This PR was merged into the 7.4 branch. Discussion ---------- [HttpFoundation] Add support for structured MIME suffix | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix symfony#61213 | License | MIT Extended MIME type handling in `Request::getFormat()` to support structured suffixes like `application/soap+xml`. Introduced a private method to define fallback formats based on RFC specifications. Updated the test suite accordingly. It adds supports for common mime types: * `soap`: application/soap+xml (instead of `xml`) * `problem`: application/problem+json * `hal`: application/hal+json, application/hal+xml * `jsonapi`: application/vnd.api+json * `yaml`: text/yaml, application/x-yaml * `wbxml`: application/vnd.wap.wbxml * `pdf`: application/pdf * `csv`: text/csv Commits ------- f2ba0b3 [HttpFoundation] Add new MIME type mappings to `getMimeTypes()``
2 parents 6726234 + f2ba0b3 commit e0365cc

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

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

77
* Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead
88
* Add support for the `QUERY` HTTP method
9+
* Add support for structured MIME suffix
910

1011
7.3
1112
---

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1226,8 +1226,20 @@ public static function getMimeTypes(string $format): array
12261226

12271227
/**
12281228
* Gets the format associated with the mime type.
1229+
*
1230+
* Resolution order:
1231+
* 1) Exact match on the full MIME type (e.g. "application/json").
1232+
* 2) Match on the canonical MIME type (i.e. before the first ";" parameter).
1233+
* 3) If the type is "application/*+suffix", use the structured syntax suffix
1234+
* mapping (e.g. "application/foo+json" → "json"), when available.
1235+
* 4) If $subtypeFallback is true and no match was found:
1236+
* - return the MIME subtype (without "x-" prefix), provided it does not
1237+
* contain a "+" (e.g. "application/x-yaml" → "yaml", "text/csv" → "csv").
1238+
*
1239+
* @param string|null $mimeType The mime type to check
1240+
* @param bool $subtypeFallback Whether to fall back to the subtype if no exact match is found
12291241
*/
1230-
public function getFormat(?string $mimeType): ?string
1242+
public function getFormat(?string $mimeType, bool $subtypeFallback = false): ?string
12311243
{
12321244
$canonicalMimeType = null;
12331245
if ($mimeType && false !== $pos = strpos($mimeType, ';')) {
@@ -1247,6 +1259,27 @@ public function getFormat(?string $mimeType): ?string
12471259
}
12481260
}
12491261

1262+
if (!$canonicalMimeType) {
1263+
$canonicalMimeType = $mimeType;
1264+
}
1265+
1266+
if (str_starts_with($canonicalMimeType, 'application/') && str_contains($canonicalMimeType, '+')) {
1267+
$suffix = substr(strrchr($canonicalMimeType, '+'), 1);
1268+
if (isset(static::getStructuredSuffixFormats()[$suffix])) {
1269+
return static::getStructuredSuffixFormats()[$suffix];
1270+
}
1271+
}
1272+
1273+
if ($subtypeFallback && str_contains($canonicalMimeType, '/')) {
1274+
[, $subtype] = explode('/', $canonicalMimeType, 2);
1275+
if (str_starts_with($subtype, 'x-')) {
1276+
$subtype = substr($subtype, 2);
1277+
}
1278+
if (!str_contains($subtype, '+')) {
1279+
return $subtype;
1280+
}
1281+
}
1282+
12501283
return null;
12511284
}
12521285

@@ -1919,6 +1952,42 @@ protected static function initializeFormats(): void
19191952
'atom' => ['application/atom+xml'],
19201953
'rss' => ['application/rss+xml'],
19211954
'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'],
1955+
'soap' => ['application/soap+xml'],
1956+
'problem' => ['application/problem+json'],
1957+
'hal' => ['application/hal+json', 'application/hal+xml'],
1958+
'jsonapi' => ['application/vnd.api+json'],
1959+
'yaml' => ['text/yaml', 'application/x-yaml'],
1960+
'wbxml' => ['application/vnd.wap.wbxml'],
1961+
'pdf' => ['application/pdf'],
1962+
'csv' => ['text/csv'],
1963+
];
1964+
}
1965+
1966+
/**
1967+
* Structured MIME suffix fallback formats
1968+
*
1969+
* This mapping is used when no exact MIME match is found in $formats.
1970+
* It enables handling of types like application/soap+xml → 'xml'.
1971+
*
1972+
* @see https://datatracker.ietf.org/doc/html/rfc6839
1973+
* @see https://datatracker.ietf.org/doc/html/rfc7303
1974+
* @see https://www.iana.org/assignments/media-types/media-types.xhtml
1975+
*
1976+
* @return array<string, string>
1977+
*/
1978+
private static function getStructuredSuffixFormats(): array
1979+
{
1980+
return [
1981+
'json' => 'json',
1982+
'xml' => 'xml',
1983+
'xhtml' => 'html',
1984+
'cbor' => 'cbor',
1985+
'zip' => 'zip',
1986+
'ber' => 'asn1',
1987+
'der' => 'asn1',
1988+
'tlv' => 'tlv',
1989+
'wbxml' => 'xml',
1990+
'yaml' => 'yaml',
19221991
];
19231992
}
19241993

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

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

0 commit comments

Comments
 (0)