Detect correct type on request when there are multiple media types mapped to the same content type
In the case where multiple types are registered to lithium\net\http\Media with the same content type, even when distinguished by conditions, an instance of lithium\action\Request will fail to determine the correct type to use to decode the request body. For example, if we register another type which uses 'application/json' as a content type like this:
use lithium\net\http\Media;
Media::type('json_base64', ['application/json'], [
'encode' => function ($data) {
return base64_encode(json_encode($data));
},
'decode' => function ($data) {
return json_decode(base64_decode($data), true);
},
'cast' => true,
'conditions' => [
'http:content_transfer_encoding' => 'base64'
]
]);
A call to Media::type('application/json') will return an array like:
[
'json_base64',
'json'
]
This case is not handled by lithium\net\http\Message::type(), which causes lithium\action\Request::$_type to be assigned as the content type of 'application/json'.
Futhermore, when Request::body() is called to decode the request body, the Media class fails to find a handler for 'application/json' (it expects a short name like 'json').
To fix this, lithium\net\http\Message::type() must be extended by lithium\action\Request. In the case that $type is a string and equals the result of the parent's call, we can loop over each type returned by Media::type() and attempt to match the request to it like this:
public function type($type = null) {
if (!$type && !empty($this->params['type'])) {
$type = $this->params['type'];
}
$_type = parent::type($type);
if (is_string($type) && $_type === $type) {
$media = $this->_classes['media'];
$content = $media::type($type);
if (is_array($content) && !isset($content['content'])) {
foreach ($content as $short_type) {
$conf = $media::type($short_type);
$conf['name'] = $short_type;
if ($media::match($this, $conf)) {
$_type = $short_type;
break;
}
}
}
}
return ($this->_type = $_type);
}
This will correctly determine a single short name to use for the type of the request data, and it can now correctly decode the request body.
@mariuswilms I don't recall, is this on the critical path for the request/response loop? If so, it should probably be benchmarked for performance impact.
This issue also exists in this situation when attempting to find the correct type on \lithium\net\http\Response given only the Content-Type header. I only implemented this fix on the Request however, since it depended on Media::match, which in turn expects an instance of \lithium\action\Request as an argument.
Ideally, this would be a part of \lithium\net\http\Message::type(), but it would require that we have the functionality of \lithium\action\Request on the Message class (e.g. is() and get()).