*/ class CorsService { /** * @var CorsOptions */ protected $options; /** * @param CorsOptions $options */ public function __construct(CorsOptions $options) { $this->options = $options; } /** * Check if the HTTP request is a CORS request by checking if the Origin header is present * * @param HttpRequest $request * @return bool */ public function isCorsRequest(HttpRequest $request) { return $request->getHeaders()->has('Origin'); } /** * Check if the CORS request is a preflight request * * @param HttpRequest $request * @return bool */ public function isPreflightRequest(HttpRequest $request) { return $this->isCorsRequest($request) && strtoupper($request->getMethod()) === 'OPTIONS' && $request->getHeaders()->has('Access-Control-Request-Method'); } /** * Create a preflight response by adding the corresponding headers * * @param HttpRequest $request * @return HttpResponse */ public function createPreflightCorsResponse(HttpRequest $request) { $response = new HttpResponse(); $response->setStatusCode(200); $headers = $response->getHeaders(); $headers->addHeaderLine('Access-Control-Allow-Origin', $this->getAllowedOriginValue($request)); $headers->addHeaderLine('Access-Control-Allow-Methods', implode(', ', $this->options->getAllowedMethods())); $headers->addHeaderLine('Access-Control-Allow-Headers', implode(', ', $this->options->getAllowedHeaders())); $headers->addHeaderLine('Access-Control-Max-Age', $this->options->getMaxAge()); $headers->addHeaderLine('Content-Length', 0); if ($this->options->getAllowedCredentials()) { $headers->addHeaderLine('Access-Control-Allow-Credentials', 'true'); } return $response; } /** * Populate a simple CORS response * * @param HttpRequest $request * @param HttpResponse $response * @return HttpResponse * @throws DisallowedOriginException If the origin is not allowed */ public function populateCorsResponse(HttpRequest $request, HttpResponse $response) { $origin = $this->getAllowedOriginValue($request); // If $origin is "null", then it means than the origin is not allowed. As this is // a simple request, it is useless to continue the processing as it will be refused // by the browser anyway, so we throw an exception if ($origin === 'null') { throw new DisallowedOriginException( sprintf( 'The origin "%s" is not authorized', $request->getHeader('Origin')->getFieldValue() ) ); } $headers = $response->getHeaders(); $headers->addHeaderLine('Access-Control-Allow-Origin', $origin); $headers->addHeaderLine('Access-Control-Expose-Headers', implode(', ', $this->options->getExposedHeaders())); // If the origin is not "*", we should add the "Origin" value to the "Vary" header // See more: http://www.w3.org/TR/cors/#resource-implementation if ($origin !== '*') { if ($headers->has('Vary')) { $varyHeader = $headers->get('Vary'); $varyValue = $varyHeader->getFieldValue() . ', Origin'; $headers->removeHeader($varyHeader); $headers->addHeaderLine('Vary', $varyValue); } else { $headers->addHeaderLine('Vary', 'Origin'); } } return $response; } /** * Get a single value for the "Access-Control-Allow-Origin" header * * According to the spec, it is not valid to set multiple origins separated by commas. Only accepted * value are wildcard ("*"), an exact domain or a null string. * * @link http://www.w3.org/TR/cors/#access-control-allow-origin-response-header * @param HttpRequest $request * @return string */ protected function getAllowedOriginValue(HttpRequest $request) { $allowedOrigins = $this->options->getAllowedOrigins(); if (in_array('*', $allowedOrigins)) { return '*'; } if (in_array($request->getHeader('Origin')->getFieldValue(), $allowedOrigins)) { return $request->getHeader('Origin')->getFieldValue(); } return 'null'; } }