-
Notifications
You must be signed in to change notification settings - Fork 2
/
CorsBuilder.php
214 lines (189 loc) · 5.88 KB
/
CorsBuilder.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;
use Psr\Http\Message\MessageInterface;
/**
* A builder object that assists in defining Cross Origin Request related
* headers.
*
* Each of the methods in this object provide a fluent interface. Once you've
* set all the headers you want to use, the `build()` method can be used to return
* a modified Response.
*
* It is most convenient to get this object via `Request::cors()`.
*
* @see \Cake\Http\Response::cors()
*/
class CorsBuilder
{
/**
* The response object this builder is attached to.
*
* @var \Psr\Http\Message\MessageInterface
*/
protected $_response;
/**
* The request's Origin header value
*
* @var string
*/
protected $_origin;
/**
* Whether the request was over SSL.
*
* @var bool
*/
protected $_isSsl;
/**
* The headers that have been queued so far.
*
* @var array<string, mixed>
*/
protected $_headers = [];
/**
* Constructor.
*
* @param \Psr\Http\Message\MessageInterface $response The response object to add headers onto.
* @param string $origin The request's Origin header.
* @param bool $isSsl Whether the request was over SSL.
*/
public function __construct(MessageInterface $response, string $origin, bool $isSsl = false)
{
$this->_origin = $origin;
$this->_isSsl = $isSsl;
$this->_response = $response;
}
/**
* Apply the queued headers to the response.
*
* If the builder has no Origin, or if there are no allowed domains,
* or if the allowed domains do not match the Origin header no headers will be applied.
*
* @return \Psr\Http\Message\MessageInterface A new instance of the response with new headers.
*/
public function build(): MessageInterface
{
$response = $this->_response;
if (empty($this->_origin)) {
return $response;
}
if (isset($this->_headers['Access-Control-Allow-Origin'])) {
foreach ($this->_headers as $key => $value) {
$response = $response->withHeader($key, $value);
}
}
return $response;
}
/**
* Set the list of allowed domains.
*
* Accepts a string or an array of domains that have CORS enabled.
* You can use `*.example.com` wildcards to accept subdomains, or `*` to allow all domains
*
* @param array<string>|string $domains The allowed domains
* @return $this
*/
public function allowOrigin($domains)
{
$allowed = $this->_normalizeDomains((array)$domains);
foreach ($allowed as $domain) {
if (!preg_match($domain['preg'], $this->_origin)) {
continue;
}
$value = $domain['original'] === '*' ? '*' : $this->_origin;
$this->_headers['Access-Control-Allow-Origin'] = $value;
break;
}
return $this;
}
/**
* Normalize the origin to regular expressions and put in an array format
*
* @param array<string> $domains Domain names to normalize.
* @return array
*/
protected function _normalizeDomains(array $domains): array
{
$result = [];
foreach ($domains as $domain) {
if ($domain === '*') {
$result[] = ['preg' => '@.@', 'original' => '*'];
continue;
}
$original = $preg = $domain;
if (strpos($domain, '://') === false) {
$preg = ($this->_isSsl ? 'https://' : 'http://') . $domain;
}
$preg = '@^' . str_replace('\*', '.*', preg_quote($preg, '@')) . '$@';
$result[] = compact('original', 'preg');
}
return $result;
}
/**
* Set the list of allowed HTTP Methods.
*
* @param array<string> $methods The allowed HTTP methods
* @return $this
*/
public function allowMethods(array $methods)
{
$this->_headers['Access-Control-Allow-Methods'] = implode(', ', $methods);
return $this;
}
/**
* Enable cookies to be sent in CORS requests.
*
* @return $this
*/
public function allowCredentials()
{
$this->_headers['Access-Control-Allow-Credentials'] = 'true';
return $this;
}
/**
* Allowed headers that can be sent in CORS requests.
*
* @param array<string> $headers The list of headers to accept in CORS requests.
* @return $this
*/
public function allowHeaders(array $headers)
{
$this->_headers['Access-Control-Allow-Headers'] = implode(', ', $headers);
return $this;
}
/**
* Define the headers a client library/browser can expose to scripting
*
* @param array<string> $headers The list of headers to expose CORS responses
* @return $this
*/
public function exposeHeaders(array $headers)
{
$this->_headers['Access-Control-Expose-Headers'] = implode(', ', $headers);
return $this;
}
/**
* Define the max-age preflight OPTIONS requests are valid for.
*
* @param string|int $age The max-age for OPTIONS requests in seconds
* @return $this
*/
public function maxAge($age)
{
$this->_headers['Access-Control-Max-Age'] = $age;
return $this;
}
}