CorsService.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. namespace Asm89\Stack;
  3. use Symfony\Component\HttpKernel\HttpKernelInterface;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\Response;
  6. class CorsService
  7. {
  8. private $options;
  9. public function __construct(array $options = array())
  10. {
  11. $this->options = $this->normalizeOptions($options);
  12. }
  13. private function normalizeOptions(array $options = array())
  14. {
  15. $options += array(
  16. 'allowedOrigins' => array(),
  17. 'supportsCredentials' => false,
  18. 'allowedHeaders' => array(),
  19. 'exposedHeaders' => array(),
  20. 'allowedMethods' => array(),
  21. 'maxAge' => 0,
  22. );
  23. // normalize array('*') to true
  24. if (in_array('*', $options['allowedOrigins'])) {
  25. $options['allowedOrigins'] = true;
  26. }
  27. if (in_array('*', $options['allowedHeaders'])) {
  28. $options['allowedHeaders'] = true;
  29. } else {
  30. $options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
  31. }
  32. if (in_array('*', $options['allowedMethods'])) {
  33. $options['allowedMethods'] = true;
  34. } else {
  35. $options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
  36. }
  37. return $options;
  38. }
  39. public function isActualRequestAllowed(Request $request)
  40. {
  41. return $this->checkOrigin($request);
  42. }
  43. public function isCorsRequest(Request $request)
  44. {
  45. return $request->headers->has('Origin');
  46. }
  47. public function isPreflightRequest(Request $request)
  48. {
  49. return $this->isCorsRequest($request)
  50. &&$request->getMethod() === 'OPTIONS'
  51. && $request->headers->has('Access-Control-Request-Method');
  52. }
  53. public function addActualRequestHeaders(Response $response, Request $request)
  54. {
  55. if ( ! $this->checkOrigin($request)) {
  56. return $response;
  57. }
  58. $response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
  59. if ( ! $response->headers->has('Vary')) {
  60. $response->headers->set('Vary', 'Origin');
  61. } else {
  62. $response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
  63. }
  64. if ($this->options['supportsCredentials']) {
  65. $response->headers->set('Access-Control-Allow-Credentials', 'true');
  66. }
  67. if ($this->options['exposedHeaders']) {
  68. $response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
  69. }
  70. return $response;
  71. }
  72. public function handlePreflightRequest(Request $request)
  73. {
  74. if (true !== $check = $this->checkPreflightRequestConditions($request)) {
  75. return $check;
  76. }
  77. return $this->buildPreflightCheckResponse($request);
  78. }
  79. private function buildPreflightCheckResponse(Request $request)
  80. {
  81. $response = new Response();
  82. if ($this->options['supportsCredentials']) {
  83. $response->headers->set('Access-Control-Allow-Credentials', 'true');
  84. }
  85. $response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
  86. if ($this->options['maxAge']) {
  87. $response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
  88. }
  89. $allowMethods = $this->options['allowedMethods'] === true
  90. ? strtoupper($request->headers->get('Access-Control-Request-Method'))
  91. : implode(', ', $this->options['allowedMethods']);
  92. $response->headers->set('Access-Control-Allow-Methods', $allowMethods);
  93. $allowHeaders = $this->options['allowedHeaders'] === true
  94. ? strtoupper($request->headers->get('Access-Control-Request-Headers'))
  95. : implode(', ', $this->options['allowedHeaders']);
  96. $response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
  97. return $response;
  98. }
  99. private function checkPreflightRequestConditions(Request $request)
  100. {
  101. if ( ! $this->checkOrigin($request)) {
  102. return $this->createBadRequestResponse(403, 'Origin not allowed');
  103. }
  104. if ( ! $this->checkMethod($request)) {
  105. return $this->createBadRequestResponse(405, 'Method not allowed');
  106. }
  107. $requestHeaders = array();
  108. // if allowedHeaders has been set to true ('*' allow all flag) just skip this check
  109. if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
  110. $headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
  111. $requestHeaders = explode(',', $headers);
  112. foreach ($requestHeaders as $header) {
  113. if ( ! in_array(trim($header), $this->options['allowedHeaders'])) {
  114. return $this->createBadRequestResponse(403, 'Header not allowed');
  115. }
  116. }
  117. }
  118. return true;
  119. }
  120. private function createBadRequestResponse($code, $reason = '')
  121. {
  122. return new Response($reason, $code);
  123. }
  124. private function checkOrigin(Request $request) {
  125. if ($this->options['allowedOrigins'] === true) {
  126. // allow all '*' flag
  127. return true;
  128. }
  129. $origin = $request->headers->get('Origin');
  130. return in_array($origin, $this->options['allowedOrigins']);
  131. }
  132. private function checkMethod(Request $request) {
  133. if ($this->options['allowedMethods'] === true) {
  134. // allow all '*' flag
  135. return true;
  136. }
  137. $requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
  138. return in_array($requestMethod, $this->options['allowedMethods']);
  139. }
  140. }