Cors   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 1
Metric Value
wmc 18
eloc 43
c 6
b 1
f 1
dl 0
loc 152
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A before() 0 13 3
A isApplicable() 0 7 2
A serve() 0 16 3
A areHeadersAllowed() 0 13 2
A preflight() 0 16 3
A isOriginAllowed() 0 7 2
A canPreflight() 0 9 3
1
<?php
2
3
/*
4
 * This file is part of the PHALCON-EXT package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace PhalconExt\Http\Middleware;
13
14
use Phalcon\Http\Request;
15
use Phalcon\Http\Response;
16
use PhalconExt\Http\BaseMiddleware;
17
18
/**
19
 * Cors middleware with preflight.
20
 *
21
 * @author  Jitendra Adhikari <[email protected]>
22
 * @license MIT
23
 *
24
 * @link    https://github.com/adhocore/phalcon-ext
25
 */
26
class Cors extends BaseMiddleware
27
{
28
    /** @var string */
29
    protected $origin;
30
31
    protected $configKey = 'cors';
32
33
    /**
34
     * Handle the cors.
35
     *
36
     * @param Request  $request
37
     * @param Response $response
38
     *
39
     * @return bool
40
     */
41
    public function before(Request $request, Response $response): bool
42
    {
43
        $this->origin = $request->getHeader('Origin');
44
45
        if (!$this->isApplicable($request)) {
46
            return true;
47
        }
48
49
        if ($this->canPreflight($request)) {
50
            return $this->preflight($request, $response);
51
        }
52
53
        return $this->serve($response);
54
    }
55
56
    /**
57
     * If cors is applicable for this request.
58
     *
59
     * Not applicable if origin is empty or same as current host.
60
     *
61
     * @param Request $request
62
     *
63
     * @return bool
64
     */
65
    protected function isApplicable(Request $request): bool
66
    {
67
        if (empty($this->origin)) {
68
            return false;
69
        }
70
71
        return $this->origin !== $request->getScheme() . '://' . $request->getHttpHost();
72
    }
73
74
    /**
75
     * Check if request can be served as preflight.
76
     *
77
     * @param Request $request
78
     *
79
     * @return bool
80
     */
81
    protected function canPreflight(Request $request): bool
82
    {
83
        if (empty($request->getHeader('Access-Control-Request-Method')) ||
84
            $request->getMethod() !== 'OPTIONS'
85
        ) {
86
            return false;
87
        }
88
89
        return $this->isOriginAllowed();
90
    }
91
92
    /**
93
     * Handle preflight.
94
     *
95
     * @param Request  $request
96
     * @param Response $response
97
     *
98
     * @return bool
99
     */
100
    protected function preflight(Request $request, Response $response): bool
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

100
    protected function preflight(Request $request, /** @scrutinizer ignore-unused */ Response $response): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
101
    {
102
        if (!\in_array($request->getHeader('Access-Control-Request-Method'), $this->config['allowedMethods'])) {
103
            return $this->abort(405, 'Request method not allowed.');
104
        }
105
106
        if (!$this->areHeadersAllowed($request->getHeader('Access-Control-Request-Headers'))) {
107
            return $this->abort(403, 'Request header not allowed');
108
        }
109
110
        return $this->abort(200, null, [
111
            'Access-Control-Allow-Origin'      => $this->origin,
112
            'Access-Control-Allow-Credentials' => 'true',
113
            'Access-Control-Allow-Methods'     => \implode(',', $this->config['allowedMethods']),
114
            'Access-Control-Allow-Headers'     => \implode(',', $this->config['allowedHeaders']),
115
            'Access-Control-Max-Age'           => $this->config['maxAge'],
116
        ]);
117
    }
118
119
    /**
120
     * Check if cors headers from client are allowed.
121
     *
122
     * @param string|null $corsRequestHeaders
123
     *
124
     * @return bool
125
     */
126
    protected function areHeadersAllowed(string $corsRequestHeaders = null)
127
    {
128
        if ('' === \trim($corsRequestHeaders)) {
0 ignored issues
show
Bug introduced by
It seems like $corsRequestHeaders can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
        if ('' === \trim(/** @scrutinizer ignore-type */ $corsRequestHeaders)) {
Loading history...
129
            return true;
130
        }
131
132
        // Normalize request headers for comparison.
133
        $corsRequestHeaders = \array_map(
134
            'strtolower',
135
            \explode(',', \str_replace(' ', '', $corsRequestHeaders))
136
        );
137
138
        return empty(\array_diff($corsRequestHeaders, $this->config['allowedHeaders']));
139
    }
140
141
    /**
142
     * Serve cors headers.
143
     *
144
     * @param Response $response
145
     *
146
     * @return bool
147
     */
148
    public function serve(Response $response): bool
149
    {
150
        if (!$this->isOriginAllowed()) {
151
            return $this->abort(403, 'Forbidden Origin');
152
        }
153
154
        $response
155
            ->setHeader('Access-Control-Allow-Origin', $this->origin)
156
            ->setHeader('Access-Control-Allow-Credentials', 'true');
157
158
        // Optionally set expose headers.
159
        if ($this->config['exposedHeaders'] ?? null) {
160
            $response->setHeader('Access-Control-Expose-Headers', \implode(', ', $this->config['exposedHeaders']));
161
        }
162
163
        return true;
164
    }
165
166
    /**
167
     * If origin is white listed.
168
     *
169
     * @return bool
170
     */
171
    protected function isOriginAllowed(): bool
172
    {
173
        if (\in_array('*', $this->config['allowedOrigins'])) {
174
            return true;
175
        }
176
177
        return \in_array($this->origin, $this->config['allowedOrigins']);
178
    }
179
}
180