Header::constructProxyHeader()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 11
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 9
nc 1
nop 0
crap 3
1
<?php
2
namespace SamIT\Proxy;
3
4
class Header
5
{
6
    const CMD_PROXY = 1;
7
    const CMD_LOCAL = 0;
8
    const UNSPECIFIED_PROTOCOL = "\x00";
9
    const TCP4 = "\x11";
10
    const UDP4 = "\x12";
11
    const TCP6 = "\x21";
12
    const UDP6 = "\x22";
13
    const USTREAM = "\x31";
14
    const USOCK = "\x32";
15
16
    // 12 bytes.
17
    protected static $signatures = [
18
        2 => "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A",
19
        1 => "PROXY"
20
    ];
21
    // 4 bits
22
    public $version = 2;
23
    // 4 bits
24
    public $command = self::CMD_PROXY;
25
26
    // 1 byte
27
    public $protocol = self::TCP4;
28
29
    protected static $lengths = [
30
        self::TCP4 => 12,
31
        self::UDP4 => 12,
32
        self::TCP6 => 36,
33
        self::UDP6 => 36,
34
        self::USTREAM => 216,
35
        self::USOCK => 216,
36
    ];
37
38
    /**
39
     * @var string The address of the client.
40
     */
41
    public $sourceAddress;
42
43
    /**
44
     * @var string The address to which the client connected.
45
     */
46
    public $targetAddress;
47
48
    /**
49
     * @var int The port of the client
50
     */
51
    public $sourcePort;
52
53
    /**
54
     * @var int The port to which the client connected.
55
     */
56
    public $targetPort;
57
58
59 1
    protected function getProtocol()
60
    {
61 1
        if ($this->version == 2) {
62 1
            return $this->protocol;
63
        } else {
64
            return array_flip((new \ReflectionClass($this))->getConstants())[$this->protocol];
65
        }
66
    }
67 1
    protected function getVersionCommand() {
68 1
        if ($this->version == 2) {
69 1
            return chr(($this->version << 4) + $this->command);
70
        }
71
    }
72
    /**
73
     * @return uint16_t
74
     */
75 1
    protected function getAddressLength()
76
    {
77 1
        if ($this->version == 2) {
78 1
            return pack('n', self::$lengths[$this->protocol]);
79
        }
80
81
    }
82
83 1 View Code Duplication
    protected function encodeAddress($address, $protocol) {
84 1
        if ($this->version == 1) {
85
            return $address;
86
        }
87
        switch ($protocol) {
88 1
            case self::TCP4:
89 1
            case self::UDP4:
90 1
            case self::TCP6:
91 1
            case self::UDP6:
92 1
                $result = inet_pton($address);
93 1
                break;
94
            case self::USTREAM:
95
            case self::USOCK:
96
                throw new \Exception("Unix socket not (yet) supported.");
97
            default:
98
                throw new \UnexpectedValueException("Invalid protocol.");
99
        }
100 1
        return $result;
101
    }
102
103 1 View Code Duplication
    protected static function decodeAddress($version, $address, $protocol)
104
    {
105 1
        if ($version == 1) {
106
            return $address;
107
        }
108
        switch ($protocol) {
109 1
            case self::TCP4:
110 1
            case self::UDP4:
111 1
            case self::TCP6:
112 1
            case self::UDP6:
113 1
                $result = inet_ntop($address);
114 1
                break;
115
            case self::USTREAM:
116
            case self::USOCK:
117
                throw new \Exception("Unix socket not (yet) supported.");
118
            default:
119
                throw new \UnexpectedValueException("Invalid protocol.");
120
121
        }
122 1
        return $result;
123
    }
124
125
    /**
126
     * @return string
127
     * @throws \Exception
128
     */
129 1
    protected function getAddresses()
130
    {
131 1
        return $this->encodeAddress($this->sourceAddress, $this->protocol) . ($this->version == 1 ? " " : "") .$this->encodeAddress($this->targetAddress, $this->protocol);
132
    }
133
134 1 View Code Duplication
    protected function encodePort($port, $protocol) {
135 1
        if ($this->version == 1) {
136
            return $port;
137
        }
138
        switch ($protocol) {
139 1
            case self::TCP4:
140 1
            case self::UDP4:
141 1
            case self::TCP6:
142 1
            case self::UDP6:
143 1
                $result = pack('n', $port);
144 1
                break;
145
            case self::USTREAM:
146
            case self::USOCK:
147
                throw new \Exception("Unix socket not (yet) supported.");
148
            default:
149
                throw new \UnexpectedValueException("Invalid protocol.");
150
151
        }
152 1
        return $result;
153
    }
154
155
    /**
156
     * @return string
157
     * @throws \Exception
158
     */
159 1
    protected function getPorts()
160
    {
161 1
        return $this->encodePort($this->sourcePort, $this->protocol) . ($this->version == 1 ? " " : "") . $this->encodePort($this->targetPort, $this->protocol);
162
    }
163
164
    /**
165
     * @return string
166
     */
167 1
    protected function getSignature()
168
    {
169 1
        return self::$signatures[$this->version];
170
    }
171
172
    /**
173
     * Constructs the header by concatenating all relevant fields.
174
     * @return string
175
     */
176 1
    public function constructProxyHeader() {
177 1
        return implode($this->version == 1 ? "\x20" : "", array_filter([
178 1
            $this->getSignature(),
179 1
            $this->getVersionCommand(),
180 1
            $this->getProtocol(),
181 1
            $this->getAddressLength(),
182 1
            $this->getAddresses(),
183 1
            $this->getPorts(),
184 1
            $this->version == 1 ? "\r\n" : null
185 1
        ]));
186
    }
187
188 1
    public function __toString()
189
    {
190 1
        return $this->constructProxyHeader();
191
    }
192
193
    /**
194
     * This function creates the forwarding header. This header should be sent over the upstream connection as soon as
195
     * it is established.
196
     * @param string $sourceAddress
197
     * @param int $sourcePort
198
     * @param string $targetAddress
199
     * @param int $targetPort
200
     * @return self
201
     * @throws \Exception
202
     */
203 2
    public static function createForward4($sourceAddress, $sourcePort, $targetAddress, $targetPort, $version = 2) {
204 2
        $result = new static();
205 2
        $result->version = $version;
206 2
        $result->sourceAddress = $sourceAddress;
207 2
        $result->targetPort = $targetPort;
208 2
        $result->targetAddress = $targetAddress;
209 2
        $result->sourcePort = $sourcePort;
210 2
        return $result;
211
    }
212
213
    /**
214
     * @param string $data
215
     * @return Header|null
216
     */
217 1
    public static function parseHeader(&$data)
218
    {
219 1
        foreach(self::$signatures as $version => $signature) {
220
            // Match.
221 1
            if (strncmp($data, $signature, strlen($signature)) === 0) {
222 1
                if ($version === 1) {
223
                    $result = self::parseVersion1($data);
224
                    break;
225 1
                } elseif ($version === 2) {
226 1
                    $result = self::parseVersion2($data);
227 1
                    break;
228
                }
229
            }
230 1
        }
231 1
        if (isset($result)) {
232 1
            $constructed = $result->constructProxyHeader();
233 1
            if (strncmp($constructed, $data, strlen($constructed)) === 0) {
234 1
                $data = substr($data, strlen($constructed));
235 1
                return $result;
236
            }
237
        }
238
    }
239
240
    protected static function parseVersion1($data)
241
    {
242
        $parts = explode("\x20", $data);
243
        if (count($parts) === 7 && $parts[6] === "\r\n") {
244
            $result = new Header();
245
            $result->version = 1;
246
            $result->protocol = $parts[1];
247
            $result->sourceAddress = $parts[2];
248
            $result->targetAddress = $parts[3];
249
            $result->sourcePort = $parts[4];
250
            $result->targetPort = $parts[5];
251
            return $result;
252
        }
253
    }
254
255 1
    protected static function parseVersion2($data)
256
    {
257 1
        $version = ord(substr($data, 12, 1)) >> 4;
258 1
        $command = ord(substr($data, 12, 1)) % 16;
259 1
        $protocol = substr($data, 13, 1);
260
261 1
        $pos = 16;
262 1
        $sourceAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2  - 2), $protocol);
263 1
        $pos += self::$lengths[$protocol] / 2  - 2;
264 1
        $targetAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2  - 2), $protocol);
265 1
        $pos += self::$lengths[$protocol] / 2  - 2;
266 1
        $sourcePort = unpack('n', substr($data, $pos, 2))[1];
267 1
        $targetPort = unpack('n', substr($data, $pos + 2, 2))[1];
268
269 1
        $result = new Header();
270 1
        $result->version = 2;
271 1
        $result->command = $command;
272 1
        $result->protocol = $protocol;
273 1
        $result->sourceAddress = $sourceAddress;
274 1
        $result->targetAddress = $targetAddress;
275 1
        $result->sourcePort = $sourcePort;
276 1
        $result->targetPort = $targetPort;
277 1
        return $result;
278
    }
279
280
}
281