diff --git a/framework/base/Response.php b/framework/base/Response.php index 0c140ae..d86d35f 100644 --- a/framework/base/Response.php +++ b/framework/base/Response.php @@ -17,6 +17,8 @@ class Response public $format = 'html'; public $formatCallback = []; public $contentType; + public $statusCode = 200; + public $version; private $_headers = []; private $_cookies = []; @@ -73,7 +75,6 @@ protected function sendHeaders() $this->_headers['Content-Type'] = [$this->contentType]; } - foreach ($this->_headers as $name => $values) { $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name))); // set replace for first occurrence of header but false afterwards to allow multiple @@ -84,6 +85,9 @@ protected function sendHeaders() } } + $version = isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0' ? '1.0' : '1.1'; + header("HTTP/{$version} {$this->statusCode}"); + $request = Dee::$app->request; $enableCookieValidation = $request->enableCookieValidation; if ($enableCookieValidation && !$request->cookieValidationKey) { diff --git a/framework/filters/Cors.php b/framework/filters/Cors.php new file mode 100644 index 0000000..d33def2 --- /dev/null +++ b/framework/filters/Cors.php @@ -0,0 +1,98 @@ + + * @since 1.0 + */ +class Cors extends Filter +{ + /** + * @var array Basic headers handled for the CORS requests. + */ + public $cors = [ + 'Origin' => ['*'], + 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], + 'Access-Control-Request-Headers' => ['*'], + 'Access-Control-Allow-Credentials' => null, + 'Access-Control-Max-Age' => 86400, + 'Access-Control-Expose-Headers' => [], + ]; + + public function before(): boolean + { + $request = Dee::$app->request; + $response = Dee::$app->response; + + $requestHeaders = $responseHeaders = []; + foreach (array_keys($this->cors) as $name) { + $sname = 'HTTP_' . strtoupper(str_replace([' ', '-'], ['_', '_'], $name)); + if (isset($_SERVER[$sname])) { + $requestHeaders[$name] = $_SERVER[$sname]; + } + } + + if (isset($requestHeaders['Origin'], $this->cors['Origin'])) { + if (in_array($requestHeaders['Origin'], $this->cors['Origin'], true)) { + $responseHeaders['Access-Control-Allow-Origin'] = $requestHeaders['Origin']; + } + if (in_array('*', $this->cors['Origin'], true)) { + if (!isset($this->cors['Access-Control-Allow-Credentials']) || !$this->cors['Access-Control-Allow-Credentials']) { + $responseHeaders['Access-Control-Allow-Origin'] = '*'; + } + } + } + + $requestHeaderField = 'Access-Control-Request-Headers'; + $responseHeaderField = 'Access-Control-Allow-Headers'; + if (!isset($requestHeaders[$requestHeaderField], $this->cors[$requestHeaderField])) { + return; + } + if (in_array('*', $this->cors[$requestHeaderField])) { + $responseHeaders[$responseHeaderField] = $this->headerize($requestHeaders[$requestHeaderField]); + } else { + $requestedData = preg_split('/[\\s,]+/', $requestHeaders[$requestHeaderField], -1, PREG_SPLIT_NO_EMPTY); + $acceptedData = array_uintersect($requestedData, $this->cors[$requestHeaderField], 'strcasecmp'); + if (!empty($acceptedData)) { + $responseHeaders[$responseHeaderField] = implode(', ', $acceptedData); + } + } + + if (isset($requestHeaders['Access-Control-Request-Method'])) { + $responseHeaders['Access-Control-Allow-Methods'] = implode(', ', $this->cors['Access-Control-Request-Method']); + } + + if (isset($this->cors['Access-Control-Allow-Credentials'])) { + $responseHeaders['Access-Control-Allow-Credentials'] = $this->cors['Access-Control-Allow-Credentials'] ? 'true' + : 'false'; + } + + if (isset($this->cors['Access-Control-Max-Age']) && $request->getMethod() === 'OPTIONS') { + $responseHeaders['Access-Control-Max-Age'] = $this->cors['Access-Control-Max-Age']; + } + + if (isset($this->cors['Access-Control-Expose-Headers'])) { + $responseHeaders['Access-Control-Expose-Headers'] = implode(', ', $this->cors['Access-Control-Expose-Headers']); + } + + if (isset($this->cors['Access-Control-Allow-Headers'])) { + $responseHeaders['Access-Control-Allow-Headers'] = implode(', ', $this->cors['Access-Control-Allow-Headers']); + } + + foreach ($responseHeaders as $name => $value) { + $response->setHeader($name, $value); + } + + if ($request->getMethod() === 'OPTIONS' && $request->header('Access-Control-Request-Method')) { + $response->statusCode = 200; + return false; + } + return true; + } +}