Cors::update()   F
last analyzed

Complexity

Conditions 16
Paths 485

Size

Total Lines 98
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 0
loc 98
ccs 0
cts 70
cp 0
rs 3.2023
c 2
b 1
f 0
cc 16
eloc 50
nc 485
nop 1
crap 272

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 *
5
 * This file is part of the Apix Project.
6
 *
7
 * (c) Franck Cassedanne <franck at ouarz.net>
8
 *
9
 * @license     http://opensource.org/licenses/BSD-3-Clause  New BSD License
10
 *
11
 */
12
13
namespace Apix\Plugin;
14
15
use Apix\Service,
16
    Apix\HttpRequest,
17
    Apix\Exception;
18
19
/**
20
 * Apix plugin providing Cross-Origin Resource Sharing
21
 *
22
 * @see http://www.w3.org/TR/cors/
23
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
24
 */
25
class Cors extends PluginAbstractEntity
26
{
27
    public static $hook = array('entity', 'early');
28
29
    protected $annotation = 'api_cors';
30
31
    protected $options = array(
32
        'enable'    => true,            // whether to enable or not
33
34
        // -- whitelist (regex)
35
        'scheme'    => 'https?',        // allows both http and https
36
        'host'      => '.*\.info\.com', // the allowed host domain(s) or ip(s)
37
        'port'      => '(:[0-9]+)?',    // the alowed port(s)
38
39
        // -- CORS directives
40
        'allow-origin'      => 'origin', // 'origin', '*', 'null', string-list
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
41
        'allow-credentials' => false,    // wether to allow Cookies and HTTP Auth
42
        'expose-headers'    => null,     // comma-delimited HTTP headers exposed
43
44
        // -- preflight
45
        'max-age'       => 3600,         // TTL in seconds for preflight
46
        'allow-methods' => 'GET,POST',   // comma-delimited HTTP methods allowed
47
        'allow-headers' => 'x-apix',     // comma-delimited HTTP headers allowed
48
    );
49
50
    /**
51
     * @{@inheritdoc}
52
     */
53
    public function update(\SplSubject $entity)
0 ignored issues
show
Coding Style introduced by
update uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
54
    {
55
        $this->setEntity($entity);
0 ignored issues
show
Compatibility introduced by
$entity of type object<SplSubject> is not a sub-type of object<Apix\Entity>. It seems like you assume a concrete implementation of the interface SplSubject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
56
57
        // skip this plugin if it is disable.
58
        if ( !$this->getSubTagBool('enable', $this->options['enable']) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getSubTagBool('en...his->options['enable']) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
59
            return false;
60
        }
61
62
        if ( $host = $this->getSubTagString('host', $this->options['host']) ) {
63
            $this->options['host'] = $host;
64
        }
65
66
        // Grab the Origin: header.
67
        $http_origin = array_key_exists('HTTP_ORIGIN', $_SERVER)
68
                        ? $_SERVER['HTTP_ORIGIN']
69
                        : null;
70
71
        // If whitelisted then it is a valid CORS request.
72
        if (
73
            $http_origin
74
            // && $_SERVER['HTTP_HOST'] == $http_origin
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
75
            && self::isOriginAllowed(
76
                $http_origin,
77
                $this->options['host'],
78
                $this->options['port'], $this->options['scheme']
79
            )
80
        ) {
81
            $response = Service::get('response');
82
83
            // 5.1 Access-Control-Allow-Origin
84
            $http_origin = $this->options['allow-origin'] == 'origin'
85
                            ? $http_origin
86
                            : $this->options['allow-origin'];
87
            $response->setHeader('Access-Control-Allow-Origin', $http_origin);
88
89
            // 5.2 Access-Control-Allow-Credentials
90
            // The actual request can include user credentials
91
            // e.g. cookies, XmlHttpRequest.withCredentials=true
92
            if ($this->options['allow-credentials'] === true) {
93
                $response->setHeader('Access-Control-Allow-Credentials', true);
94
            }
95
96
            // 5.3 Access-Control-Expose-Headers
97
            // Which response headers are available (besides the generic ones)
98
            if ($this->options['expose-headers']) {
99
                $response->setHeader('Access-Control-Expose-Headers',
100
                                            $this->options['expose-headers']);
101
            }
102
103
            $request = $response->getRequest();
104
            if ( self::isPreflight($request) ) {
105
106
                // 5.4 Access-Control-Max-Age
107
                if ($this->options['max-age'] > 0) {
108
                    // Cache the request for the provided amount of seconds
109
                    $response->setHeader('Access-Control-Max-Age',
110
                                            (int) $this->options['max-age']);
111
                }
112
113
                // 5.5 Access-Control-Allow-Methods
114
                if ($request->hasHeader('Access-Control-Request-Method') ) {
115
                   if (!in_array(
116
                        $request->getHeader('Access-Control-Request-Method'),
117
                        self::split($this->options['allow-methods'])
118
                    )) {
119
                        return self::exception();
120
                    }
121
                    $response->setHeader(
122
                        'Access-Control-Allow-Methods',
123
                        $this->options['allow-methods']
124
                    );
125
                }
126
127
                // 5.6 Access-Control-Allow-Headers
128
                if ($request->hasHeader('Access-Control-Request-Headers')) {
129
                    $req_headers = self::split(
130
                        $request->getHeader('Access-Control-Request-Headers')
131
                    );
132
                    $allowed = self::split($this->options['allow-headers']);
133
                    foreach ($req_headers as $req_header) {
134
                        if (!in_array($req_header, $allowed)) {
135
                            return self::exception();
136
                        }
137
                    }
138
                    $response->setHeader(
139
                        'Access-Control-Allow-Headers',
140
                        $this->options['allow-headers']
141
                    );
142
                }
143
            }
144
145
            return true;
146
        }
147
148
        // so it must be an invalid CORS request
149
        return self::exception();
150
    }
151
152
    /**
153
     * Throws a \DomainException.
154
     *
155
     * @throw \DomainException
156
     */
157
    public static function exception()
158
    {
159
        throw new \DomainException('Not a valid CORS request.', 403);
160
    }
161
162
    /**
163
     * Split and trim the provided string.
164
     *
165
     * @param  string $str
166
     * @return array
167
     */
168
    public static function split($str)
169
    {
170
        return array_map('trim', explode(',', $str));
171
    }
172
173
    /**
174
     * Checks the provided origin as a CORS requests.
175
     *
176
     * @param  string  $host   The host domain(s) or IP(s) to match.
177
     * @param  string  $port   Default to any port or none provided.
178
     * @param  string  $scheme Default tp 'http' and 'https'.
179
     * @return boolean
180
     */
181
    public static function isOriginAllowed(
182
        $origin, $host, $port='(:[0-9]+)?', $scheme='https?'
183
    ) {
184
        $regex = '`^' . $scheme . ':\/\/' . $host . $port . '$`';
185
186
        return (bool) preg_match($regex, $origin);
187
    }
188
189
    /**
190
     * Checks for a preflighted request.
191
     *
192
     * @return boolean
193
     */
194
    public static function isPreflight(HttpRequest $request)
195
    {
196
        $method = $request->getMethod();
197
198
        return
199
            // uses methods other than...
200
            !in_array($method, array('GET', 'HEAD', 'POST'))
201
202
            // if POST is used with a Content-Type other than...
203
            or ( $method == 'POST'
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
204
                 and !in_array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
205
                        $request->getHeader('CONTENT_TYPE'),
206
                        array(
207
                            'text/plain',
208
                            'multipart/form-data',
209
                            'application/x-www-form-urlencoded'
210
                        )
211
                    )
212
                )
213
214
            // if it is set with some custom request headers
215
            or $request->hasHeader('Access-Control-Request-Headers');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
216
    }
217
218
}
219