feat(api): harden version header parsing
Handle Accept-Version parameters and comma-separated Accept values when extracting API versions. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
812400f303
commit
c21c3409d7
2 changed files with 56 additions and 4 deletions
|
|
@ -188,7 +188,9 @@ class ApiVersion
|
|||
return null;
|
||||
}
|
||||
|
||||
// Strip 'v' prefix if present
|
||||
// Strip 'v' prefix and any optional parameters if present.
|
||||
$header = trim($header);
|
||||
$header = explode(';', $header, 2)[0];
|
||||
$version = ltrim($header, 'vV');
|
||||
|
||||
if (is_numeric($version)) {
|
||||
|
|
@ -207,9 +209,14 @@ class ApiVersion
|
|||
{
|
||||
$accept = $request->header('Accept', '');
|
||||
|
||||
// Match vendor media type: application/vnd.{name}.v{n}+json
|
||||
if (preg_match('#application/vnd\.[^.]+\.v(\d+)\+#', $accept, $matches)) {
|
||||
return (int) $matches[1];
|
||||
foreach (preg_split('/\s*,\s*/', $accept, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $mediaType) {
|
||||
// Strip media-type parameters before matching the vendor suffix.
|
||||
$mediaType = explode(';', trim($mediaType), 2)[0];
|
||||
|
||||
// Match vendor media type: application/vnd.{name}.v{n}+json
|
||||
if (preg_match('#^application/vnd\.[^.]+\.v(\d+)\+#i', $mediaType, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
45
src/php/tests/Feature/ApiVersionParsingTest.php
Normal file
45
src/php/tests/Feature/ApiVersionParsingTest.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Core\Front\Api\Middleware\ApiVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
beforeEach(function () {
|
||||
Config::set('api.versioning.default', 1);
|
||||
Config::set('api.versioning.current', 2);
|
||||
Config::set('api.versioning.supported', [1, 2]);
|
||||
Config::set('api.versioning.deprecated', []);
|
||||
Config::set('api.versioning.sunset', []);
|
||||
Config::set('api.headers.include_version', true);
|
||||
Config::set('api.headers.include_deprecation', true);
|
||||
});
|
||||
|
||||
it('resolves the api version from an accept-version header with parameters', function () {
|
||||
$middleware = new ApiVersion();
|
||||
$request = Request::create('/api/users', 'GET');
|
||||
$request->headers->set('Accept-Version', 'v2; q=1.0');
|
||||
|
||||
$response = $middleware->handle($request, fn () => new Response('OK'));
|
||||
|
||||
expect($response->headers->get('X-API-Version'))->toBe('2');
|
||||
expect($request->attributes->get('api_version'))->toBe(2);
|
||||
expect($request->attributes->get('api_version_string'))->toBe('v2');
|
||||
});
|
||||
|
||||
it('resolves the api version from a vendor accept header inside a list', function () {
|
||||
$middleware = new ApiVersion();
|
||||
$request = Request::create('/api/users', 'GET');
|
||||
$request->headers->set(
|
||||
'Accept',
|
||||
'text/html;q=0.8, application/json, application/vnd.hosthub.v2+json; charset=utf-8'
|
||||
);
|
||||
|
||||
$response = $middleware->handle($request, fn () => new Response('OK'));
|
||||
|
||||
expect($response->headers->get('X-API-Version'))->toBe('2');
|
||||
expect($request->attributes->get('api_version'))->toBe(2);
|
||||
expect($request->attributes->get('api_version_string'))->toBe('v2');
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue