199 lines
6.1 KiB
PHP
199 lines
6.1 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace OFFLINE\CORS\Classes;
|
||
|
|
|
||
|
|
|
||
|
|
use Symfony\Component\HttpFoundation\Request;
|
||
|
|
use Symfony\Component\HttpFoundation\Response;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Based on asm89/stack-cors
|
||
|
|
*/
|
||
|
|
class CorsService
|
||
|
|
{
|
||
|
|
private $options;
|
||
|
|
|
||
|
|
public function __construct(array $options = [])
|
||
|
|
{
|
||
|
|
$this->options = $this->normalizeOptions($options);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function normalizeOptions(array $options = [])
|
||
|
|
{
|
||
|
|
$options += [
|
||
|
|
'supportsCredentials' => false,
|
||
|
|
'maxAge' => 0,
|
||
|
|
];
|
||
|
|
|
||
|
|
// Make sure these values are arrays, if not specified in the backend settings.
|
||
|
|
$arrayKeys = [
|
||
|
|
'allowedOrigins',
|
||
|
|
'allowedHeaders',
|
||
|
|
'exposedHeaders',
|
||
|
|
'allowedMethods',
|
||
|
|
];
|
||
|
|
|
||
|
|
foreach ($arrayKeys as $key) {
|
||
|
|
if (!$options[$key]) {
|
||
|
|
$options[$key] = [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// normalize array('*') to true
|
||
|
|
if (in_array('*', $options['allowedOrigins'])) {
|
||
|
|
$options['allowedOrigins'] = true;
|
||
|
|
}
|
||
|
|
if (in_array('*', $options['allowedHeaders'])) {
|
||
|
|
$options['allowedHeaders'] = true;
|
||
|
|
} else {
|
||
|
|
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (in_array('*', $options['allowedMethods'])) {
|
||
|
|
$options['allowedMethods'] = true;
|
||
|
|
} else {
|
||
|
|
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $options;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function isActualRequestAllowed(Request $request)
|
||
|
|
{
|
||
|
|
return $this->checkOrigin($request);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function isCorsRequest(Request $request)
|
||
|
|
{
|
||
|
|
return $request->headers->has('Origin') && $request->headers->get('Origin') !== $request->getSchemeAndHttpHost();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function isPreflightRequest(Request $request)
|
||
|
|
{
|
||
|
|
return $this->isCorsRequest($request)
|
||
|
|
&& $request->getMethod() === 'OPTIONS'
|
||
|
|
&& $request->headers->has('Access-Control-Request-Method');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function addActualRequestHeaders(Response $response, Request $request)
|
||
|
|
{
|
||
|
|
if ( ! $this->checkOrigin($request)) {
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
|
||
|
|
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||
|
|
|
||
|
|
if ( ! $response->headers->has('Vary')) {
|
||
|
|
$response->headers->set('Vary', 'Origin');
|
||
|
|
} else {
|
||
|
|
$response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->options['supportsCredentials']) {
|
||
|
|
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->options['exposedHeaders']) {
|
||
|
|
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
|
||
|
|
}
|
||
|
|
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function handlePreflightRequest(Request $request)
|
||
|
|
{
|
||
|
|
if (true !== $check = $this->checkPreflightRequestConditions($request)) {
|
||
|
|
return $check;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->buildPreflightCheckResponse($request);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function buildPreflightCheckResponse(Request $request)
|
||
|
|
{
|
||
|
|
$response = new Response();
|
||
|
|
|
||
|
|
if ($this->options['supportsCredentials']) {
|
||
|
|
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||
|
|
}
|
||
|
|
|
||
|
|
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||
|
|
|
||
|
|
if ($this->options['maxAge']) {
|
||
|
|
$response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
|
||
|
|
}
|
||
|
|
|
||
|
|
$allowMethods = $this->options['allowedMethods'] === true
|
||
|
|
? strtoupper($request->headers->get('Access-Control-Request-Method'))
|
||
|
|
: implode(', ', $this->options['allowedMethods']);
|
||
|
|
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
|
||
|
|
|
||
|
|
$allowHeaders = $this->options['allowedHeaders'] === true
|
||
|
|
? strtoupper($request->headers->get('Access-Control-Request-Headers'))
|
||
|
|
: implode(', ', $this->options['allowedHeaders']);
|
||
|
|
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
|
||
|
|
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function checkPreflightRequestConditions(Request $request)
|
||
|
|
{
|
||
|
|
if ( ! $this->checkOrigin($request)) {
|
||
|
|
return $this->createBadRequestResponse(403, 'Origin not allowed');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! $this->checkMethod($request)) {
|
||
|
|
return $this->createBadRequestResponse(405, 'Method not allowed');
|
||
|
|
}
|
||
|
|
|
||
|
|
$requestHeaders = [];
|
||
|
|
// if allowedHeaders has been set to true ('*' allow all flag) just skip this check
|
||
|
|
if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
|
||
|
|
$headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
|
||
|
|
$requestHeaders = explode(',', $headers);
|
||
|
|
|
||
|
|
foreach ($requestHeaders as $header) {
|
||
|
|
if ( ! in_array(trim($header), $this->options['allowedHeaders'])) {
|
||
|
|
return $this->createBadRequestResponse(403, 'Header not allowed');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function createBadRequestResponse($code, $reason = '')
|
||
|
|
{
|
||
|
|
return new Response($reason, $code);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function checkOrigin(Request $request)
|
||
|
|
{
|
||
|
|
if ($this->options['allowedOrigins'] === true) {
|
||
|
|
// allow all '*' flag
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
$origin = $request->headers->get('Origin');
|
||
|
|
|
||
|
|
foreach ($this->options['allowedOrigins'] as $allowedOrigin) {
|
||
|
|
if (OriginMatcher::matches($allowedOrigin, $origin)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function checkMethod(Request $request)
|
||
|
|
{
|
||
|
|
if ($this->options['allowedMethods'] === true) {
|
||
|
|
// allow all '*' flag
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
$requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
|
||
|
|
|
||
|
|
return in_array($requestMethod, $this->options['allowedMethods']);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|