Completed
Push — master ( fe4c2e...b05117 )
by Michael
12s
created

IPAddress   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 138
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 68.63%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 20
c 1
b 1
f 0
lcom 1
cbo 0
dl 0
loc 138
rs 10
ccs 35
cts 51
cp 0.6863

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A fromRequest() 0 7 2
A normalize() 0 5 1
A asReadable() 0 4 1
A asBinary() 0 8 2
A ipVersion() 0 11 4
B sameSubnet() 0 20 5
A asBinaryString() 0 11 3
1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 */
11
12
namespace Xmf;
13
14
/**
15
 * Language
16
 *
17
 * @category  Xmf\IPAddress
18
 * @package   Xmf
19
 * @author    trabis <[email protected]>
20
 * @copyright 2016 XOOPS Project (http://xoops.org)
21
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
22
 * @version   Release: 1.0
23
 * @link      http://xoops.org
24
 * @since     1.0
25
 */
26
class IPAddress
27
{
28
29
    /** @var false|string presentation form of ip address, or false if invalid */
30
    protected $ip;
31
32
    /**
33
     * IPAddress constructor.
34
     * @param string $ip IP address
35
     */
36 1
    public function __construct($ip)
37
    {
38 1
        if (!filter_var((string) $ip, FILTER_VALIDATE_IP)) {
39
            $this->ip = false;
40
        } else {
41 1
            $this->ip = $this->normalize($ip);
42
        }
43 1
    }
44
45
    /**
46
     * Get IP address from the request server data
47
     *
48
     * @return IPAddress
49
     */
50 1
    public static function fromRequest()
0 ignored issues
show
Coding Style introduced by
fromRequest 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...
51
    {
52 1
        $ip = (array_key_exists('REMOTE_ADDR', $_SERVER)) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
53 1
        $class = get_called_class();
54 1
        $instance = new $class($ip);
55 1
        return $instance;
56
    }
57
58
    /**
59
     * convert IP address into a normalized condensed notation
60
     *
61
     * @param string $ip ip address to normalize
62
     *
63
     * @return string|false normalized address or false on failure
64
     */
65
    protected function normalize($ip)
66
    {
67
        $normal = inet_ntop(inet_pton($ip));
68
        return $normal;
69
    }
70
71
    /**
72
     * return presentation form of address
73
     *
74
     * @return string|false
75
     */
76 1
    public function asReadable()
77
    {
78 1
        return $this->ip;
79
    }
80
81
    /**
82
     * get network (binary) form of address
83
     *
84
     * @return string|false
85
     */
86 1
    public function asBinary()
87
    {
88 1
        if (false === $this->ip) {
89
            return false;
90
        }
91 1
        $binary = inet_pton($this->ip);
92 1
        return $binary;
93
    }
94
95
    /**
96
     * get the ip version, 4 or 6, of address
97
     *
98
     * @return int|false integer 4 for IPV4, 6 for IPV6, or false if invalid
99
     */
100 1
    public function ipVersion()
101
    {
102 1
        if (false === $this->ip) {
103 1
            return false;
104 1
        } elseif (false !== filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
105 1
            return 4;
106 1
        } elseif (false !== filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
107 1
            return 6;
108
        }
109
        return false;
110
    }
111
112
    /**
113
     * Is this IP in the same subnet as the supplied address?
114
     *
115
     * Accepts net masks for both IPV4 and IPV6 and will select the appropriate one, to
116
     * allow checking policy against request input with minimal method calls.
117
     *
118
     * @param string $matchIp  presentation form ip address to compare
119
     * @param int    $netMask4 network mask, bits to match <= 32 for IPV4
120
     * @param int    $netMask6 network mask, bits to match <=128 for IPV6
121
     *
122
     * @return bool true if $this->ip and $matchIp are both in the specified subnet
123
     */
124 1
    public function sameSubnet($matchIp, $netMask4, $netMask6)
125
    {
126 1
        $match = new IPAddress($matchIp);
127 1
        if (false === $this->ipVersion() || ($this->ipVersion() !== $match->ipVersion())) {
128 1
            return false;
129
        }
130 1
        switch ($this->ipVersion()) {
131 1
            case 4:
132 1
                $mask = (-1) << (32 - $netMask4);
133 1
                return ((ip2long($this->ip) & $mask) === (ip2long($match->asReadable()) & $mask));
134
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
135 1
            case 6:
136 1
                $ipBits = $this->asBinaryString($this);
137 1
                $matchBits = $this->asBinaryString($match);
138 1
                $match = (0 === strncmp($ipBits, $matchBits, $netMask6));
139 1
                return $match;
140
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
141
        }
142
        return false;
143
    }
144
145
    /**
146
     * Convert an IP address to a binary character string (i.e. "01111111000000000000000000000001")
147
     *
148
     * @param IPAddress $ip address object
149
     *
150
     * @return string
151
     */
152
    protected function asBinaryString(IPAddress $ip)
153
    {
154
        $length = (4 === $ip->ipVersion()) ? 4 : 16;
155
        $binaryIp = $ip->asBinary();
156
        $bits = '';
157
        for ($i = 0; $i < $length; $i++) {
158
            $byte = decbin(ord($binaryIp[$i]));
159
            $bits .= substr("00000000" . $byte, -8);
160
        }
161
        return $bits;
162
    }
163
}
164