Completed
Pull Request — master (#563)
by Richard
10:42
created

IPAddress::fromRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
ccs 5
cts 5
cp 1
crap 2
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
 * @link      http://xoops.org
23
 */
24
class IPAddress
25
{
26
27
    /** @var false|string presentation form of ip address, or false if invalid */
28
    protected $ip;
29
30
    /**
31
     * IPAddress constructor.
32
     * @param string $ip IP address
33
     */
34 5
    public function __construct($ip)
35
    {
36 5
        if (!filter_var((string) $ip, FILTER_VALIDATE_IP)) {
37 1
            $this->ip = false;
38
        } else {
39 5
            $this->ip = $this->normalize($ip);
40
        }
41 5
    }
42
43
    /**
44
     * Get IP address from the request server data
45
     *
46
     * @return IPAddress
47
     */
48 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...
49
    {
50 1
        $ip = (array_key_exists('REMOTE_ADDR', $_SERVER)) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
51 1
        $class = get_called_class();
52 1
        $instance = new $class($ip);
53 1
        return $instance;
54
    }
55
56
    /**
57
     * convert IP address into a normalized condensed notation
58
     *
59
     * @param string $ip ip address to normalize
60
     *
61
     * @return string|false normalized address or false on failure
62
     */
63 5
    protected function normalize($ip)
64
    {
65 5
        $normal = inet_ntop(inet_pton($ip));
66 5
        return $normal;
67
    }
68
69
    /**
70
     * return presentation form of address
71
     *
72
     * @return string|false
73
     */
74 3
    public function asReadable()
75
    {
76 3
        return $this->ip;
77
    }
78
79
    /**
80
     * get network (binary) form of address
81
     *
82
     * @return string|false
83
     */
84 2
    public function asBinary()
85
    {
86 2
        if (false === $this->ip) {
87
            return false;
88
        }
89 2
        $binary = inet_pton($this->ip);
90 2
        return $binary;
91
    }
92
93
    /**
94
     * get the ip version, 4 or 6, of address
95
     *
96
     * @return int|false integer 4 for IPV4, 6 for IPV6, or false if invalid
97
     */
98 2
    public function ipVersion()
99
    {
100 2
        if (false === $this->ip) {
101 1
            return false;
102 2
        } elseif (false !== filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
103 2
            return 4;
104 2
        } elseif (false !== filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
105 2
            return 6;
106
        }
107
        return false;
108
    }
109
110
    /**
111
     * Is this IP in the same subnet as the supplied address?
112
     *
113
     * Accepts net masks for both IPV4 and IPV6 and will select the appropriate one, to
114
     * allow checking policy against request input with minimal method calls.
115
     *
116
     * @param string $matchIp  presentation form ip address to compare
117
     * @param int    $netMask4 network mask, bits to match <= 32 for IPV4
118
     * @param int    $netMask6 network mask, bits to match <=128 for IPV6
119
     *
120
     * @return bool true if $this->ip and $matchIp are both in the specified subnet
121
     */
122 1
    public function sameSubnet($matchIp, $netMask4, $netMask6)
123
    {
124 1
        $match = new IPAddress($matchIp);
125 1
        if (false === $this->ipVersion() || ($this->ipVersion() !== $match->ipVersion())) {
126 1
            return false;
127
        }
128 1
        switch ($this->ipVersion()) {
129 1
            case 4:
130 1
                $mask = (-1) << (32 - $netMask4);
131 1
                return ((ip2long($this->ip) & $mask) === (ip2long($match->asReadable()) & $mask));
132
                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...
133 1
            case 6:
134 1
                $ipBits = $this->asBinaryString($this);
135 1
                $matchBits = $this->asBinaryString($match);
136 1
                $match = (0 === strncmp($ipBits, $matchBits, $netMask6));
137 1
                return $match;
138
                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...
139
        }
140
        return false;
141
    }
142
143
    /**
144
     * Convert an IP address to a binary character string (i.e. "01111111000000000000000000000001")
145
     *
146
     * @param IPAddress $ip address object
147
     *
148
     * @return string
149
     */
150 1
    protected function asBinaryString(IPAddress $ip)
151
    {
152 1
        $length = (4 === $ip->ipVersion()) ? 4 : 16;
153 1
        $binaryIp = $ip->asBinary();
154 1
        $bits = '';
155 1
        for ($i = 0; $i < $length; $i++) {
156 1
            $byte = decbin(ord($binaryIp[$i]));
157 1
            $bits .= substr("00000000" . $byte, -8);
158
        }
159 1
        return $bits;
160
    }
161
}
162