Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Drupal\jsonapi\EventSubscriber;
|
Chris@18
|
4
|
Chris@18
|
5 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@18
|
6 use Drupal\jsonapi\JsonApiSpec;
|
Chris@18
|
7 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@18
|
8 use Symfony\Component\HttpFoundation\Request;
|
Chris@18
|
9 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
Chris@18
|
10 use Drupal\Core\Http\Exception\CacheableBadRequestHttpException;
|
Chris@18
|
11 use Symfony\Component\HttpKernel\KernelEvents;
|
Chris@18
|
12
|
Chris@18
|
13 /**
|
Chris@18
|
14 * Request subscriber that validates a JSON:API request.
|
Chris@18
|
15 *
|
Chris@18
|
16 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
|
Chris@18
|
17 * may change at any time and could break any dependencies on it.
|
Chris@18
|
18 *
|
Chris@18
|
19 * @see https://www.drupal.org/project/jsonapi/issues/3032787
|
Chris@18
|
20 * @see jsonapi.api.php
|
Chris@18
|
21 */
|
Chris@18
|
22 class JsonApiRequestValidator implements EventSubscriberInterface {
|
Chris@18
|
23
|
Chris@18
|
24 /**
|
Chris@18
|
25 * Validates JSON:API requests.
|
Chris@18
|
26 *
|
Chris@18
|
27 * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
Chris@18
|
28 * The event to process.
|
Chris@18
|
29 */
|
Chris@18
|
30 public function onRequest(GetResponseEvent $event) {
|
Chris@18
|
31 $request = $event->getRequest();
|
Chris@18
|
32 if ($request->getRequestFormat() !== 'api_json') {
|
Chris@18
|
33 return;
|
Chris@18
|
34 }
|
Chris@18
|
35
|
Chris@18
|
36 $this->validateQueryParams($request);
|
Chris@18
|
37 }
|
Chris@18
|
38
|
Chris@18
|
39 /**
|
Chris@18
|
40 * Validates custom (implementation-specific) query parameter names.
|
Chris@18
|
41 *
|
Chris@18
|
42 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@18
|
43 * The request for which to validate JSON:API query parameters.
|
Chris@18
|
44 *
|
Chris@18
|
45 * @return \Drupal\jsonapi\ResourceResponse|null
|
Chris@18
|
46 * A JSON:API resource response.
|
Chris@18
|
47 *
|
Chris@18
|
48 * @see http://jsonapi.org/format/#query-parameters
|
Chris@18
|
49 */
|
Chris@18
|
50 protected function validateQueryParams(Request $request) {
|
Chris@18
|
51 $invalid_query_params = [];
|
Chris@18
|
52 foreach (array_keys($request->query->all()) as $query_parameter_name) {
|
Chris@18
|
53 // Ignore reserved (official) query parameters.
|
Chris@18
|
54 if (in_array($query_parameter_name, JsonApiSpec::getReservedQueryParameters())) {
|
Chris@18
|
55 continue;
|
Chris@18
|
56 }
|
Chris@18
|
57
|
Chris@18
|
58 if (!JsonApiSpec::isValidCustomQueryParameter($query_parameter_name)) {
|
Chris@18
|
59 $invalid_query_params[] = $query_parameter_name;
|
Chris@18
|
60 }
|
Chris@18
|
61 }
|
Chris@18
|
62
|
Chris@18
|
63 // Drupal uses the `_format` query parameter for Content-Type negotiation.
|
Chris@18
|
64 // Using it violates the JSON:API spec. Nudge people nicely in the correct
|
Chris@18
|
65 // direction. (This is special cased because using it is pretty common.)
|
Chris@18
|
66 if (in_array('_format', $invalid_query_params, TRUE)) {
|
Chris@18
|
67 $uri_without_query_string = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . $request->getPathInfo();
|
Chris@18
|
68 $exception = new CacheableBadRequestHttpException((new CacheableMetadata())->addCacheContexts(['url.query_args:_format']), 'JSON:API does not need that ugly \'_format\' query string! 🤘 Use the URL provided in \'links\' 🙏');
|
Chris@18
|
69 $exception->setHeaders(['Link' => $uri_without_query_string]);
|
Chris@18
|
70 throw $exception;
|
Chris@18
|
71 }
|
Chris@18
|
72
|
Chris@18
|
73 if (empty($invalid_query_params)) {
|
Chris@18
|
74 return NULL;
|
Chris@18
|
75 }
|
Chris@18
|
76
|
Chris@18
|
77 $message = sprintf('The following query parameters violate the JSON:API spec: \'%s\'.', implode("', '", $invalid_query_params));
|
Chris@18
|
78 $exception = new CacheableBadRequestHttpException((new CacheableMetadata())->addCacheContexts(['url.query_args']), $message);
|
Chris@18
|
79 $exception->setHeaders(['Link' => 'http://jsonapi.org/format/#query-parameters']);
|
Chris@18
|
80 throw $exception;
|
Chris@18
|
81 }
|
Chris@18
|
82
|
Chris@18
|
83 /**
|
Chris@18
|
84 * {@inheritdoc}
|
Chris@18
|
85 */
|
Chris@18
|
86 public static function getSubscribedEvents() {
|
Chris@18
|
87 $events[KernelEvents::REQUEST][] = ['onRequest'];
|
Chris@18
|
88 return $events;
|
Chris@18
|
89 }
|
Chris@18
|
90
|
Chris@18
|
91 }
|