1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * A parser for IPv6 address.
24 */
25class CIPv6Parser extends CParser {
26
27	const STATE_NEW = 0;
28	const STATE_AFTER_DIGITS = 1;
29	const STATE_AFTER_COLON = 2;
30	const STATE_AFTER_DBLCOLON = 3;
31
32	/**
33	 * @var CIPv4Parser
34	 */
35	private $ipv4_parser;
36
37	public function __construct() {
38		$this->ipv4_parser = new CIPv4Parser();
39	}
40
41	/**
42	 * @param string $source
43	 * @param int    $pos
44	 *
45	 * @return int
46	 */
47	public function parse($source, $pos = 0) {
48		$this->length = 0;
49		$this->match = '';
50
51		$state = self::STATE_NEW;
52		$colons = 0;
53		$dbl_colons = 0;
54
55		for ($p = $pos; isset($source[$p]); $p++) {
56			switch ($state) {
57				case self::STATE_NEW:
58					if (self::parseDoubleColon($source, $p)) {
59						if ($dbl_colons++ == 1) {
60							return self::PARSE_FAIL;
61						}
62						$state = self::STATE_AFTER_DBLCOLON;
63					}
64					elseif (self::parseXDigits($source, $p)) {
65						$state = self::STATE_AFTER_DIGITS;
66					}
67					else {
68						return self::PARSE_FAIL;
69					}
70					break;
71
72				case self::STATE_AFTER_COLON:
73					if (self::parseXDigits($source, $p)) {
74						$state = self::STATE_AFTER_DIGITS;
75					}
76					else {
77						return self::PARSE_FAIL;
78					}
79					break;
80
81				case self::STATE_AFTER_DBLCOLON:
82					if (self::parseXDigits($source, $p)) {
83						$state = self::STATE_AFTER_DIGITS;
84					}
85					else {
86						break 2;
87					}
88					break;
89
90				case self::STATE_AFTER_DIGITS:
91					if (self::parseDoubleColon($source, $p)) {
92						if ($dbl_colons++ == 1) {
93							return self::PARSE_FAIL;
94						}
95						$state = self::STATE_AFTER_DBLCOLON;
96					}
97					elseif ($source[$p] == ':') {
98						if ($colons++ == 7) {
99							return self::PARSE_FAIL;
100						}
101						$state = self::STATE_AFTER_COLON;
102					}
103					else {
104						break 2;
105					}
106					break;
107			}
108		}
109
110		if ($state == self::STATE_AFTER_COLON || $state == self::STATE_NEW) {
111			return self::PARSE_FAIL;
112		}
113
114		if (isset($source[$p]) && $source[$p] == '.' && $state == self::STATE_AFTER_DIGITS
115				&& ($colons != 0 || $dbl_colons != 0)) {
116			if (($dbl_colons == 0 && $colons != 6) || ($dbl_colons == 1 && $colons > 4)) {
117				return self::PARSE_FAIL;
118			}
119
120			while ($source[$p - 1] != ':') {
121				$p--;
122			}
123
124			if ($this->ipv4_parser->parse($source, $p) == self::PARSE_FAIL) {
125				return self::PARSE_FAIL;
126			}
127
128			$p += $this->ipv4_parser->getLength();
129		}
130		elseif (($dbl_colons == 0 && $colons != 7) || ($dbl_colons == 1 && $colons > 5)) {
131			return self::PARSE_FAIL;
132		}
133
134		$this->length = $p - $pos;
135		$this->match = substr($source, $pos, $this->length);
136
137		return (isset($source[$pos + $this->length]) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS);
138	}
139
140	private static function parseDoubleColon($source, &$pos) {
141		$p = $pos;
142
143		if ($source[$p] !== ':') {
144			return false;
145		}
146		$p++;
147
148		if (!isset($source[$p]) || $source[$p] !== ':') {
149			return false;
150		}
151
152		$pos = $p;
153
154		return true;
155	}
156
157	private static function parseXDigits($source, &$pos) {
158		$p = $pos;
159
160		while (isset($source[$p]) && ctype_xdigit($source[$p])) {
161			$p++;
162		}
163
164		if ($p == $pos || $p - $pos > 4) {
165			return false;
166		}
167
168		$pos = $p - 1;
169
170		return true;
171	}
172}
173