CorsBuilder.php 5.8 KB

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