util/AddressClass.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | #include "HostMatch.h" | ||
| 3 | #include <QHostAddress> | ||
| 4 | |||
| 5 | // Local URL admission only. A public DNS name may still resolve privately; | ||
| 6 | // Chromium performs its own DNS resolution, so this is not a rebinding guard. | ||
| 7 | namespace AddressClass { | ||
| 8 | 35 | inline bool isPublicAddress(const QHostAddress &address) { | |
| 9 |
10/11✓ Branch 2 → 3 taken 35 times.
✓ Branch 3 → 4 taken 35 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 35 times.
✓ Branch 5 → 6 taken 30 times.
✓ Branch 5 → 8 taken 5 times.
✓ Branch 6 → 7 taken 30 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 28 times.
✓ Branch 10 → 11 taken 7 times.
✓ Branch 10 → 12 taken 28 times.
|
35 | if (address.isNull() || address.isLoopback() || address.isMulticast()) return false; |
| 10 | 28 | bool mapped = false; | |
| 11 |
1/1✓ Branch 12 → 13 taken 28 times.
|
28 | const quint32 ipv4 = address.toIPv4Address(&mapped); |
| 12 |
2/2✓ Branch 13 → 14 taken 20 times.
✓ Branch 13 → 20 taken 8 times.
|
28 | if (mapped) { |
| 13 | struct Range { quint32 network; int bits; }; | ||
| 14 | static constexpr Range denied[] = { | ||
| 15 | {0x00000000, 8}, {0x0a000000, 8}, {0x64400000, 10}, {0x7f000000, 8}, | ||
| 16 | {0xa9fe0000, 16}, {0xac100000, 12}, {0xc0000000, 24}, {0xc0000200, 24}, | ||
| 17 | {0xc0a80000, 16}, {0xc6120000, 15}, {0xc6336400, 24}, {0xcb007100, 24}, | ||
| 18 | {0xe0000000, 3} | ||
| 19 | }; | ||
| 20 |
2/2✓ Branch 18 → 15 taken 153 times.
✓ Branch 18 → 19 taken 7 times.
|
160 | for (const auto &range : denied) { |
| 21 | 153 | const quint32 mask = 0xffffffffU << (32 - range.bits); | |
| 22 |
2/2✓ Branch 15 → 16 taken 13 times.
✓ Branch 15 → 17 taken 140 times.
|
153 | if ((ipv4 & mask) == range.network) return false; |
| 23 | } | ||
| 24 | 7 | return true; | |
| 25 | } | ||
| 26 |
2/3✓ Branch 20 → 21 taken 8 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 8 times.
|
8 | if (address.protocol() != QAbstractSocket::IPv6Protocol) return false; |
| 27 |
2/2✓ Branch 34 → 25 taken 37 times.
✓ Branch 34 → 35 taken 5 times.
|
42 | for (const auto &range : {"::/96", "fc00::/7", "fe80::/10", "fec0::/10", "ff00::/8", "2001:db8::/32"}) |
| 28 |
5/5✓ Branch 26 → 27 taken 37 times.
✓ Branch 27 → 28 taken 37 times.
✓ Branch 28 → 29 taken 37 times.
✓ Branch 31 → 32 taken 3 times.
✓ Branch 31 → 33 taken 34 times.
|
37 | if (address.isInSubnet(QHostAddress::parseSubnet(QString::fromLatin1(range)))) return false; |
| 29 | // S106-223: a 6to4 address (2002::/16) carries an IPv4 address in its bits | ||
| 30 | // 16…47; it is public only when that IPv4 address is. | ||
| 31 |
4/4✓ Branch 38 → 40 taken 5 times.
✓ Branch 40 → 41 taken 5 times.
✓ Branch 44 → 45 taken 4 times.
✓ Branch 44 → 55 taken 1 time.
|
5 | if (address.isInSubnet(QHostAddress::parseSubnet(QStringLiteral("2002::/16")))) { |
| 32 |
1/1✓ Branch 45 → 46 taken 4 times.
|
4 | const Q_IPV6ADDR bytes = address.toIPv6Address(); |
| 33 | 4 | const quint32 embedded = (quint32(bytes[2]) << 24) | (quint32(bytes[3]) << 16) | (quint32(bytes[4]) << 8) | bytes[5]; | |
| 34 |
2/2✓ Branch 50 → 51 taken 4 times.
✓ Branch 51 → 52 taken 4 times.
|
4 | return isPublicAddress(QHostAddress(embedded)); |
| 35 | } | ||
| 36 | // Global unicast currently occupies 2000::/3; fail closed elsewhere. | ||
| 37 |
2/2✓ Branch 58 → 59 taken 1 time.
✓ Branch 59 → 60 taken 1 time.
|
1 | return address.isInSubnet(QHostAddress::parseSubnet(QStringLiteral("2000::/3"))); |
| 38 | } | ||
| 39 | |||
| 40 | 67 | inline bool isPublicLiteralOrName(const QString &input) { | |
| 41 |
2/2✓ Branch 2 → 3 taken 67 times.
✓ Branch 3 → 4 taken 67 times.
|
67 | QString host = input.trimmed().toLower(); |
| 42 |
4/4✓ Branch 6 → 7 taken 67 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 65 times.
✓ Branch 8 → 9 taken 2 times.
|
67 | if (host.endsWith('.')) host.chop(1); |
| 43 |
3/9✓ Branch 10 → 11 taken 67 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 16 taken 67 times.
✗ Branch 13 → 14 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 23 taken 67 times.
✗ Branch 19 → 20 not taken.
|
67 | if (host.startsWith('[') && host.endsWith(']')) host = host.mid(1, host.size() - 2); |
| 44 |
7/7✓ Branch 24 → 25 taken 64 times.
✓ Branch 24 → 28 taken 3 times.
✓ Branch 26 → 27 taken 64 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 63 times.
✓ Branch 30 → 31 taken 4 times.
✓ Branch 30 → 32 taken 63 times.
|
67 | if (host.isEmpty() || host.contains('%')) return false; |
| 45 | 4 | static const QRegularExpression numerical(QStringLiteral( | |
| 46 |
4/7✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 45 taken 61 times.
✓ Branch 34 → 35 taken 2 times.
✗ Branch 34 → 45 not taken.
✓ Branch 39 → 40 taken 2 times.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 130 not taken.
|
67 | "^(?:0x[0-9a-f]+|[0-9]+)(?:\\.(?:0x[0-9a-f]+|[0-9]+)){0,3}$")); |
| 47 | 4 | static const QRegularExpression dottedDecimal(QStringLiteral( | |
| 48 |
4/7✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 58 taken 61 times.
✓ Branch 47 → 48 taken 2 times.
✗ Branch 47 → 58 not taken.
✓ Branch 52 → 53 taken 2 times.
✗ Branch 138 → 139 not taken.
✗ Branch 138 → 140 not taken.
|
67 | "^(?:0|[1-9][0-9]{0,2})(?:\\.(?:0|[1-9][0-9]{0,2})){3}$")); |
| 49 |
13/18✓ Branch 59 → 60 taken 63 times.
✓ Branch 60 → 61 taken 63 times.
✓ Branch 61 → 62 taken 23 times.
✓ Branch 61 → 67 taken 40 times.
✓ Branch 63 → 64 taken 23 times.
✓ Branch 64 → 65 taken 23 times.
✓ Branch 65 → 66 taken 5 times.
✓ Branch 65 → 67 taken 18 times.
✓ Branch 68 → 69 taken 23 times.
✓ Branch 68 → 70 taken 40 times.
✓ Branch 70 → 71 taken 63 times.
✗ Branch 70 → 72 not taken.
✓ Branch 72 → 73 taken 5 times.
✓ Branch 72 → 74 taken 58 times.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
✗ Branch 146 → 147 not taken.
✗ Branch 146 → 148 not taken.
|
86 | if (numerical.match(host).hasMatch() && !dottedDecimal.match(host).hasMatch()) return false; |
| 50 |
1/1✓ Branch 74 → 75 taken 58 times.
|
58 | const QHostAddress address(host); |
| 51 |
4/4✓ Branch 75 → 76 taken 58 times.
✓ Branch 76 → 77 taken 31 times.
✓ Branch 76 → 79 taken 27 times.
✓ Branch 77 → 78 taken 31 times.
|
58 | if (!address.isNull()) return isPublicAddress(address); |
| 52 |
4/4✓ Branch 80 → 81 taken 27 times.
✓ Branch 81 → 82 taken 27 times.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 83 → 85 taken 26 times.
|
27 | if (numerical.match(host).hasMatch()) return false; |
| 53 |
1/1✓ Branch 85 → 86 taken 26 times.
|
26 | host = HostMatch::canonicalName(host); |
| 54 |
7/7✓ Branch 89 → 90 taken 25 times.
✓ Branch 89 → 93 taken 1 time.
✓ Branch 91 → 92 taken 25 times.
✓ Branch 92 → 93 taken 3 times.
✓ Branch 92 → 94 taken 22 times.
✓ Branch 95 → 96 taken 4 times.
✓ Branch 95 → 97 taken 22 times.
|
26 | if (host.isEmpty() || !host.contains('.')) return false; |
| 55 |
2/2✓ Branch 106 → 99 taken 82 times.
✓ Branch 106 → 107 taken 17 times.
|
99 | for (const auto &suffix : {"localhost", "local", "internal", "home.arpa"}) |
| 56 |
4/4✓ Branch 100 → 101 taken 82 times.
✓ Branch 101 → 102 taken 82 times.
✓ Branch 103 → 104 taken 5 times.
✓ Branch 103 → 105 taken 77 times.
|
82 | if (HostMatch::isHostOrSubdomain(host, QString::fromLatin1(suffix))) return false; |
| 57 | 17 | return true; | |
| 58 | 67 | } | |
| 59 | } | ||
| 60 |