Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
yunwuxin committed Oct 11, 2023
0 parents commit 2f8789f
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
composer.lock
vendor
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ThinkCors

ThinkPHP跨域扩展

## 安装

```
composer require topthink/think-cors
```

## 配置

配置文件位于 `config/cors.php`

```
[
'paths' => ['api/*'],
...
]
```
29 changes: 29 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "topthink/think-cors",
"description": "The Cors Library For ThinkPHP",
"license": "Apache-2.0",
"authors": [
{
"name": "yunwuxin",
"email": "[email protected]"
}
],
"require": {
"topthink/framework": "^6.0|^8.0"
},
"autoload": {
"psr-4": {
"think\\cors\\": "src"
}
},
"extra": {
"think": {
"services": [
"think\\cors\\Service"
],
"config": {
"cors": "src/config.php"
}
}
}
}
248 changes: 248 additions & 0 deletions src/HandleCors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
<?php

namespace think\cors;

use Closure;
use think\Config;
use think\Request;
use think\Response;

class HandleCors
{
/** @var string[] */
protected array $paths = [];
/** @var string[] */
protected array $allowedOrigins = [];
/** @var string[] */
protected array $allowedOriginsPatterns = [];
/** @var string[] */
protected array $allowedMethods = [];
/** @var string[] */
protected array $allowedHeaders = [];
/** @var string[] */
private array $exposedHeaders = [];
protected bool $supportsCredentials = false;
protected ?int $maxAge = 0;

protected bool $allowAllOrigins = false;
protected bool $allowAllMethods = false;
protected bool $allowAllHeaders = false;

public function __construct(Config $config)
{
$options = $config->get('cors', []);

$this->paths = $options['paths'] ?? $this->paths;
$this->allowedOrigins = $options['allowed_origins'] ?? $this->allowedOrigins;
$this->allowedOriginsPatterns = $options['allowed_origins_patterns'] ?? $this->allowedOriginsPatterns;
$this->allowedMethods = $options['allowed_methods'] ?? $this->allowedMethods;
$this->allowedHeaders = $options['allowed_headers'] ?? $this->allowedHeaders;
$this->supportsCredentials = $options['supports_credentials'] ?? $this->supportsCredentials;

$maxAge = $this->maxAge;
if (array_key_exists('max_age', $options)) {
$maxAge = $options['max_age'];
}
$this->maxAge = $maxAge === null ? null : (int) $maxAge;

$exposedHeaders = $options['exposed_headers'] ?? $this->exposedHeaders;
$this->exposedHeaders = $exposedHeaders === false ? [] : $exposedHeaders;

// Normalize case
$this->allowedHeaders = array_map('strtolower', $this->allowedHeaders);
$this->allowedMethods = array_map('strtoupper', $this->allowedMethods);

// Normalize ['*'] to true
$this->allowAllOrigins = in_array('*', $this->allowedOrigins);
$this->allowAllHeaders = in_array('*', $this->allowedHeaders);
$this->allowAllMethods = in_array('*', $this->allowedMethods);

// Transform wildcard pattern
if (!$this->allowAllOrigins) {
foreach ($this->allowedOrigins as $origin) {
if (strpos($origin, '*') !== false) {
$this->allowedOriginsPatterns[] = $this->convertWildcardToPattern($origin);
}
}
}
}

/**
* @param Request $request
* @param Closure $next
* @return Response
*/
public function handle($request, Closure $next)
{
if (!$this->hasMatchingPath($request)) {
return $next($request);
}

if ($this->isPreflightRequest($request)) {
return $this->handlePreflightRequest($request);
}

/** @var Response $response */
$response = $next($request);

return $this->addPreflightRequestHeaders($response, $request);
}

protected function addPreflightRequestHeaders(Response $response, Request $request): Response
{
$this->configureAllowedOrigin($response, $request);

if ($response->getHeader('Access-Control-Allow-Origin')) {
$this->configureAllowCredentials($response, $request);
$this->configureAllowedMethods($response, $request);
$this->configureAllowedHeaders($response, $request);
$this->configureMaxAge($response, $request);
}

return $response;
}

protected function configureAllowedOrigin(Response $response, Request $request): void
{
if ($this->allowAllOrigins === true && !$this->supportsCredentials) {
$response->header(['Access-Control-Allow-Origin' => '*']);
} elseif ($this->isSingleOriginAllowed()) {
$response->header(['Access-Control-Allow-Origin' => array_values($this->allowedOrigins)[0]]);
} else {
if ($this->isCorsRequest($request) && $this->isOriginAllowed($request)) {
$response->header(['Access-Control-Allow-Origin' => (string) $request->header('Origin')]);
}
}
}

protected function configureAllowCredentials(Response $response, Request $request): void
{
if ($this->supportsCredentials) {
$response->header(['Access-Control-Allow-Credentials' => 'true']);
}
}

protected function configureAllowedMethods(Response $response, Request $request): void
{
if ($this->allowAllMethods === true) {
$allowMethods = strtoupper((string) $request->header('Access-Control-Request-Method'));
} else {
$allowMethods = implode(', ', $this->allowedMethods);
}

$response->header(['Access-Control-Allow-Methods' => $allowMethods]);
}

protected function configureAllowedHeaders(Response $response, Request $request): void
{
if ($this->allowAllHeaders === true) {
$allowHeaders = (string) $request->header('Access-Control-Request-Headers');
} else {
$allowHeaders = implode(', ', $this->allowedHeaders);
}
$response->header(['Access-Control-Allow-Headers' => $allowHeaders]);
}

protected function configureMaxAge(Response $response, Request $request): void
{
if ($this->maxAge !== null) {
$response->header(['Access-Control-Max-Age' => (string) $this->maxAge]);
}
}

protected function handlePreflightRequest(Request $request)
{
$response = response('', 204);

return $this->addPreflightRequestHeaders($response, $request);
}

protected function isCorsRequest(Request $request)
{
return !!$request->header('Origin');
}

protected function isPreflightRequest(Request $request)
{
return $request->method() === 'OPTIONS' && $request->header('Access-Control-Request-Method');
}

protected function isSingleOriginAllowed(): bool
{
if ($this->allowAllOrigins === true || count($this->allowedOriginsPatterns) > 0) {
return false;
}

return count($this->allowedOrigins) === 1;
}

protected function isOriginAllowed(Request $request): bool
{
if ($this->allowAllOrigins === true) {
return true;
}

$origin = (string) $request->header('Origin');

if (in_array($origin, $this->allowedOrigins)) {
return true;
}

foreach ($this->allowedOriginsPatterns as $pattern) {
if (preg_match($pattern, $origin)) {
return true;
}
}

return false;
}

protected function hasMatchingPath(Request $request)
{
$url = $request->baseUrl();
$url = trim($url, '/');
if ($url === '') {
$url = '/';
}

$paths = $this->getPathsByHost($request->host(true));

foreach ($paths as $path) {
if ($path !== '/') {
$path = trim($path, '/');
}

if ($path === $url) {
return true;
}

$pattern = $this->convertWildcardToPattern($path);

if (preg_match($pattern, $url) === 1) {
return true;
}
}

return false;
}

protected function getPathsByHost($host)
{
$paths = $this->paths;

if (isset($paths[$host])) {
return $paths[$host];
}

return array_filter($paths, function ($path) {
return is_string($path);
});
}

protected function convertWildcardToPattern($pattern)
{
$pattern = preg_quote($pattern, '#');
$pattern = str_replace('\*', '.*', $pattern);
return '#^' . $pattern . '\z#u';
}
}
13 changes: 13 additions & 0 deletions src/Service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace think\cors;

class Service extends \think\Service
{
public function boot(): void
{
$this->app->event->listen('HttpRun', function () {
$this->app->middleware->add(HandleCors::class);
});
}
}
12 changes: 12 additions & 0 deletions src/config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

return [
'paths' => [],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_methods' => ['*'],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

0 comments on commit 2f8789f

Please sign in to comment.