Completed
Push — next ( bac8c4 )
by Jonathan
04:12
created

Whip::addCustomHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4286
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
The MIT License (MIT)
5
6
Copyright (c) 2015 Vectorface, Inc.
7
8
Permission is hereby granted, free of charge, to any person obtaining a copy
9
of this software and associated documentation files (the "Software"), to deal
10
in the Software without restriction, including without limitation the rights
11
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
copies of the Software, and to permit persons to whom the Software is
13
furnished to do so, subject to the following conditions:
14
15
The above copyright notice and this permission notice shall be included in
16
all copies or substantial portions of the Software.
17
18
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
THE SOFTWARE.
25
*/
26
27
namespace Vectorface\Whip;
28
29
use \Exception;
30
use Vectorface\Whip\IpRange\IpWhitelist;
31
32
/**
33
 * A class for accurately looking up a client's IP address.
34
 * This class checks a call time configurable list of headers in the $_SERVER
35
 * superglobal to determine the client's IP address.
36
 * @copyright Vectorface, Inc 2015
37
 * @author Daniel Bruce <[email protected]>
38
 * @author Cory Darby <[email protected]>
39
 */
40
class Whip
41
{
42
43
    /** Indicates all header methods will be used. */
44
    const ALL_METHODS        = 255;
45
    /** Indicates the REMOTE_ADDR method will be used. */
46
    const REMOTE_ADDR        = 1;
47
    /** Indicates a set of possible proxy headers will be used. */
48
    const PROXY_HEADERS      = 2;
49
    /** Indicates any CloudFlare specific headers will be used. */
50
    const CLOUDFLARE_HEADERS = 4;
51
    /** Indicates any Incapsula specific headers will be used. */
52
    const INCAPSULA_HEADERS  = 8;
53
    /** Indicates custom listed headers will be used. */
54
    const CUSTOM_HEADERS     = 128;
55
56
    /** The array of mapped header strings. */
57
    private static $headers = array(
58
        self::CUSTOM_HEADERS     => array(),
59
        self::INCAPSULA_HEADERS  => array(
60
            'HTTP_INCAP_CLIENT_IP'
61
        ),
62
        self::CLOUDFLARE_HEADERS => array(
63
            'HTTP_CF_CONNECTING_IP'
64
        ),
65
        self::PROXY_HEADERS      => array(
66
            'HTTP_CLIENT_IP',
67
            'HTTP_X_FORWARDED_FOR',
68
            'HTTP_X_FORWARDED',
69
            'HTTP_X_CLUSTER_CLIENT_IP',
70
            'HTTP_FORWARDED_FOR',
71
            'HTTP_FORWARDED',
72
            'HTTP_X_REAL_IP',
73
        ),
74
        self::REMOTE_ADDR        => array(
75
            'REMOTE_ADDR'
76
        ),
77
    );
78
79
    /** the bitmask of enabled methods */
80
    private $enabled;
81
82
    /** the array of IP whitelist ranges to check against */
83
    private $whitelist;
84
85
    /** an array holding the source of addresses we will check */
86
    private $source;
87
88
    /**
89
     * Constructor for the class.
90
     * @param int $enabled The bitmask of enabled headers.
91
     * @param array $whitelists The array of IP ranges to be whitelisted.
92
     */
93 18
    public function __construct($enabled = self::ALL_METHODS, array $whitelists = array())
0 ignored issues
show
Coding Style introduced by
__construct 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...
94
    {
95 18
        $this->enabled   = (int) $enabled;
96 18
        $this->source    = $_SERVER;
97 18
        $this->whitelist = array();
98 18
        foreach ($whitelists as $header => $ipRanges) {
99 10
            $this->whitelist[$header] = new IpWhitelist($ipRanges);
100 18
        }
101 18
    }
102
103
    /**
104
     * Adds a custom header to the list.
105
     * @param string $header The custom header to add.
106
     * @return Whip Returns $this.
107
     */
108 1
    public function addCustomHeader($header)
109
    {
110 1
        self::$headers[self::CUSTOM_HEADERS][] = $header;
111 1
        return $this;
112
    }
113
114
    /**
115
     * Sets the source array to use to lookup the addresses. If not specified,
116
     * the class will fallback to $_SERVER.
117
     * @param array $source The source array.
118
     * @return Whip Returns $this.
119
     */
120 1
    public function setSource(array $source)
121
    {
122 1
        $this->source = $source;
123 1
        return $this;
124
    }
125
126
    /**
127
     * Returns the IP address of the client using the given methods.
128
     * @param array $source (optional) The source array. By default, the class
129
     *        will use the value passed to Whip::setSource or fallback to
130
     *        $_SERVER.
131
     * @return string Returns the IP address as a string or false if no
132
     *         IP address could be found.
133
     */
134 18
    public function getIpAddress($source = null)
0 ignored issues
show
Coding Style introduced by
getIpAddress 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...
135
    {
136 18
        $source = is_array($source) ? $source : $this->source;
137 18
        $localAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
138 18
        foreach (self::$headers as $key => $headers) {
139 18
            if (!($key & $this->enabled) || !$this->isIpWhitelisted($key, $localAddress)) {
140
                // skip this header if not enabled or if the local address
141
                // is not whitelisted
142 16
                continue;
143
            }
144 14
            return $this->extractAddressFromHeaders($source, $headers);
145 4
        }
146 4
        return false;
147
    }
148
149
    /**
150
     * Returns the valid IP address or false if no valid IP address was found.
151
     * @param array $source (optional) The source array. By default, the class
152
     *        will use the value passed to Whip::setSource or fallback to
153
     *        $_SERVER.
154
     * @return string|false Returns the IP address (as a string) of the client or false
155
     *         if no valid IP address was found.
156
     */
157 3
    public function getValidIpAddress($source = null)
158
    {
159 3
        $ipAddress = $this->getIpAddress($source);
160 3
        if (false === $ipAddress || false === @inet_pton($ipAddress)) {
161 1
            return false;
162
        }
163 2
        return $ipAddress;
164
    }
165
166
    /**
167
     * Finds the first element in $headers that is present in $_SERVER and
168
     * returns the IP address mapped to that value.
169
     * If the IP address is a list of comma separated values, the last value
170
     * in the list will be returned.
171
     * If no IP address is found, we return false.
172
     * @param array $source  The source array to pull the data from.
173
     * @param array $headers The list of headers to check.
174
     * @return string|false Returns the IP address as a string or false if no IP
175
     *         IP address was found.
176
     */
177 14
    private function extractAddressFromHeaders($source, $headers)
178
    {
179 14
        foreach ($headers as $header) {
180 13
            if (empty($source[$header])) {
181 7
                continue;
182
            }
183 12
            $list = explode(',', $source[$header]);
184 12
            return trim(end($list));
185 2
        }
186 2
        return false;
187
    }
188
189
    /**
190
     * Returns whether or not the given IP address is whitelisted for the given
191
     * source key.
192
     * @param string $key The source key.
193
     * @param string $ipAddress The IP address.
194
     * @return boolean Returns true if the IP address is whitelisted and false
195
     *         otherwise. Returns true if the source does not have a whitelist
196
     *         specified.
197
     */
198 18
    private function isIpWhitelisted($key, $ipAddress)
199
    {
200 18
        if (!isset($this->whitelist[$key])) {
201 8
            return true;
202
        }
203 10
        return $this->whitelist[$key]->isIpWhitelisted($ipAddress);
204
    }
205
}
206