1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qipaddress_p.h"
6#include "private/qlocale_tools_p.h"
7#include "private/qtools_p.h"
8#include "qvarlengtharray.h"
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::StringLiterals;
13
14namespace QIPAddressUtils {
15
16static QString number(quint8 val)
17{
18 QString zero = QStringLiteral("0");
19 return val ? qulltoa(l: val, base: 10, zero) : zero;
20}
21
22typedef QVarLengthArray<char, 64> Buffer;
23static const QChar *checkedToAscii(Buffer &buffer, const QChar *begin, const QChar *end)
24{
25 const auto *const ubegin = reinterpret_cast<const char16_t *>(begin);
26 const auto *const uend = reinterpret_cast<const char16_t *>(end);
27 auto *src = ubegin;
28
29 buffer.resize(sz: uend - ubegin + 1);
30 char *dst = buffer.data();
31
32 while (src != uend) {
33 if (*src >= 0x7f)
34 return reinterpret_cast<const QChar *>(src);
35 *dst++ = *src++;
36 }
37 *dst = '\0';
38 return nullptr;
39}
40
41static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptLeadingZero);
42bool parseIp4(IPv4Address &address, const QChar *begin, const QChar *end)
43{
44 Q_ASSERT(begin != end);
45 Buffer buffer;
46 if (checkedToAscii(buffer, begin, end))
47 return false;
48
49 const char *ptr = buffer.data();
50 return parseIp4Internal(address, ptr, acceptLeadingZero: true);
51}
52
53static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptLeadingZero)
54{
55 address = 0;
56 int dotCount = 0;
57 const char *const stop = ptr + qstrlen(str: ptr);
58 while (dotCount < 4) {
59 if (!acceptLeadingZero && *ptr == '0' &&
60 ptr[1] != '.' && ptr[1] != '\0')
61 return false;
62
63 auto [ll, used] = qstrntoull(nptr: ptr, size: stop - ptr, base: 0);
64 const quint32 x = quint32(ll);
65 if (used <= 0 || ll != x)
66 return false;
67
68 if (ptr[used] == '.' || dotCount == 3) {
69 if (x & ~0xff)
70 return false;
71 address <<= 8;
72 } else if (dotCount == 2) {
73 if (x & ~0xffff)
74 return false;
75 address <<= 16;
76 } else if (dotCount == 1) {
77 if (x & ~0xffffff)
78 return false;
79 address <<= 24;
80 }
81 address |= x;
82
83 if (dotCount == 3 || ptr[used] == '\0')
84 return ptr[used] == '\0';
85 if (ptr[used] != '.')
86 return false;
87
88 ++dotCount;
89 ptr += used + 1;
90 }
91 return false;
92}
93
94void toString(QString &appendTo, IPv4Address address)
95{
96 // reconstructing is easy
97 // use the fast operator% that pre-calculates the size
98 appendTo += number(val: address >> 24) % u'.'
99 % number(val: address >> 16) % u'.'
100 % number(val: address >> 8) % u'.'
101 % number(val: address);
102}
103
104/*!
105 \internal
106 \since 5.0
107
108 Parses one IPv6 address from \a begin to \a end and stores the
109 representation in \a address. Returns null if everything was parsed
110 correctly, or the pointer to the first bad character where parsing failed.
111 If the parsing failed for a reason not related to a particular character,
112 returns \a end.
113*/
114const QChar *parseIp6(IPv6Address &address, const QChar *begin, const QChar *end)
115{
116 Q_ASSERT(begin != end);
117 Buffer buffer;
118 const QChar *ret = checkedToAscii(buffer, begin, end);
119 if (ret)
120 return ret;
121
122 const char *ptr = buffer.data();
123 const char *const stop = ptr + buffer.size();
124
125 // count the colons
126 int colonCount = 0;
127 int dotCount = 0;
128 while (*ptr) {
129 if (*ptr == ':')
130 ++colonCount;
131 if (*ptr == '.')
132 ++dotCount;
133 ++ptr;
134 }
135 // IPv4-in-IPv6 addresses are stricter in what they accept
136 if (dotCount != 0 && dotCount != 3)
137 return end;
138
139 memset(s: address, c: 0, n: sizeof address);
140 if (colonCount == 2 && end - begin == 2) // "::"
141 return nullptr;
142
143 // if there's a double colon ("::"), this is how many zeroes it means
144 int zeroWordsToFill;
145 ptr = buffer.data();
146
147 // there are two cases where 8 colons are allowed: at the ends
148 // so test that before the colon-count test
149 if ((ptr[0] == ':' && ptr[1] == ':') ||
150 (ptr[end - begin - 2] == ':' && ptr[end - begin - 1] == ':')) {
151 zeroWordsToFill = 9 - colonCount;
152 } else if (colonCount < 2 || colonCount > 7) {
153 return end;
154 } else {
155 zeroWordsToFill = 8 - colonCount;
156 }
157 if (dotCount)
158 --zeroWordsToFill;
159
160 int pos = 0;
161 while (pos < 15) {
162 if (*ptr == ':') {
163 // empty field, we hope it's "::"
164 if (zeroWordsToFill < 1)
165 return begin + (ptr - buffer.data());
166 if (pos == 0 || pos == colonCount * 2) {
167 if (ptr[0] == '\0' || ptr[1] != ':')
168 return begin + (ptr - buffer.data());
169 ++ptr;
170 }
171 pos += zeroWordsToFill * 2;
172 zeroWordsToFill = 0;
173 ++ptr;
174 continue;
175 }
176
177 auto [ll, used] = qstrntoull(nptr: ptr, size: stop - ptr, base: 16);
178 quint16 x = ll;
179
180 // Reject malformed fields:
181 // - failed to parse
182 // - too many hex digits
183 if (used <= 0 || used > 4)
184 return begin + (ptr - buffer.data());
185
186 if (ptr[used] == '.') {
187 // this could be an IPv4 address
188 // it's only valid in the last element
189 if (pos != 12)
190 return begin + (ptr - buffer.data());
191
192 IPv4Address ip4;
193 if (!parseIp4Internal(address&: ip4, ptr, acceptLeadingZero: false))
194 return begin + (ptr - buffer.data());
195
196 address[12] = ip4 >> 24;
197 address[13] = ip4 >> 16;
198 address[14] = ip4 >> 8;
199 address[15] = ip4;
200 return nullptr;
201 }
202
203 address[pos++] = x >> 8;
204 address[pos++] = x & 0xff;
205
206 if (ptr[used] == '\0')
207 break;
208 if (ptr[used] != ':')
209 return begin + (used + ptr - buffer.data());
210 ptr += used + 1;
211 }
212 return pos == 16 ? nullptr : end;
213}
214
215static inline QChar toHex(uchar c)
216{
217 return QChar::fromLatin1(c: QtMiscUtils::toHexLower(value: c));
218}
219
220void toString(QString &appendTo, const IPv6Address address)
221{
222 // the longest IPv6 address possible is:
223 // "1111:2222:3333:4444:5555:6666:255.255.255.255"
224 // however, this function never generates that. The longest it does
225 // generate without an IPv4 address is:
226 // "1111:2222:3333:4444:5555:6666:7777:8888"
227 // and the longest with an IPv4 address is:
228 // "::ffff:255.255.255.255"
229 static const int Ip6AddressMaxLen = sizeof "1111:2222:3333:4444:5555:6666:7777:8888";
230 static const int Ip6WithIp4AddressMaxLen = sizeof "::ffff:255.255.255.255";
231
232 // check for the special cases
233 const quint64 zeroes[] = { 0, 0 };
234 bool embeddedIp4 = false;
235
236 // we consider embedded IPv4 for:
237 // ::ffff:x.x.x.x
238 // ::x.x.x.y except if the x are 0 too
239 if (memcmp(s1: address, s2: zeroes, n: 10) == 0) {
240 if (address[10] == 0xff && address[11] == 0xff) {
241 embeddedIp4 = true;
242 } else if (address[10] == 0 && address[11] == 0) {
243 if (address[12] != 0 || address[13] != 0 || address[14] != 0) {
244 embeddedIp4 = true;
245 } else if (address[15] == 0) {
246 appendTo.append(s: "::"_L1);
247 return;
248 }
249 }
250 }
251
252 // QString::reserve doesn't shrink, so it's fine to us
253 appendTo.reserve(asize: appendTo.size() +
254 (embeddedIp4 ? Ip6WithIp4AddressMaxLen : Ip6AddressMaxLen));
255
256 // for finding where to place the "::"
257 int zeroRunLength = 0; // in octets
258 int zeroRunOffset = 0; // in octets
259 for (int i = 0; i < 16; i += 2) {
260 if (address[i] == 0 && address[i + 1] == 0) {
261 // found a zero, scan forward to see how many more there are
262 int j;
263 for (j = i; j < 16; j += 2) {
264 if (address[j] != 0 || address[j+1] != 0)
265 break;
266 }
267
268 if (j - i > zeroRunLength) {
269 zeroRunLength = j - i;
270 zeroRunOffset = i;
271 i = j;
272 }
273 }
274 }
275
276 const QChar colon = u':';
277 if (zeroRunLength < 4)
278 zeroRunOffset = -1;
279 else if (zeroRunOffset == 0)
280 appendTo.append(c: colon);
281
282 for (int i = 0; i < 16; i += 2) {
283 if (i == zeroRunOffset) {
284 appendTo.append(c: colon);
285 i += zeroRunLength - 2;
286 continue;
287 }
288
289 if (i == 12 && embeddedIp4) {
290 IPv4Address ip4 = address[12] << 24 |
291 address[13] << 16 |
292 address[14] << 8 |
293 address[15];
294 toString(appendTo, address: ip4);
295 return;
296 }
297
298 if (address[i]) {
299 if (address[i] >> 4) {
300 appendTo.append(c: toHex(c: address[i] >> 4));
301 appendTo.append(c: toHex(c: address[i] & 0xf));
302 appendTo.append(c: toHex(c: address[i + 1] >> 4));
303 appendTo.append(c: toHex(c: address[i + 1] & 0xf));
304 } else if (address[i] & 0xf) {
305 appendTo.append(c: toHex(c: address[i] & 0xf));
306 appendTo.append(c: toHex(c: address[i + 1] >> 4));
307 appendTo.append(c: toHex(c: address[i + 1] & 0xf));
308 }
309 } else if (address[i + 1] >> 4) {
310 appendTo.append(c: toHex(c: address[i + 1] >> 4));
311 appendTo.append(c: toHex(c: address[i + 1] & 0xf));
312 } else {
313 appendTo.append(c: toHex(c: address[i + 1] & 0xf));
314 }
315
316 if (i != 14)
317 appendTo.append(c: colon);
318 }
319}
320
321}
322QT_END_NAMESPACE
323

source code of qtbase/src/corelib/io/qipaddress.cpp