File indexing completed on 2024-04-28 16:52:16

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>
0003 
0004 #include "ipvalidator.h"
0005 
0006 #include <QRegularExpression>
0007 
0008 // AF_INET + inet_pton() for Linux, FreeBSD
0009 #include <arpa/inet.h>
0010 #include <netinet/in.h>
0011 #include <sys/socket.h>
0012 #include <sys/types.h>
0013 
0014 namespace
0015 {
0016 
0017 /// Whether @c netmask is within range for the given IP version
0018 QValidator::State checkNetmaskInRange(IPValidator::IPVersion ipVersion, int netmask)
0019 {
0020     const auto maxNetmask = ipVersion == IPValidator::IPVersion::IPv4 ? 32 : 128;
0021     if (netmask >= 0 && netmask <= maxNetmask) {
0022         return QValidator::Acceptable;
0023     }
0024 
0025     return QValidator::Invalid;
0026 }
0027 
0028 /// Whether @c nmStr contains a valid netmask
0029 QValidator::State checkNetmaskValid(IPValidator::IPVersion ipVersion, const QString &nmStr)
0030 {
0031     if (nmStr.isEmpty()) {
0032         return QValidator::Intermediate;
0033     }
0034 
0035     bool valid = false;
0036     const auto netmask = nmStr.toInt(&valid);
0037     if (!valid) {
0038         return QValidator::Invalid;
0039     }
0040     return checkNetmaskInRange(ipVersion, netmask);
0041 }
0042 
0043 /// Whether @c addr contains a valid IP address
0044 QValidator::State checkAddressValid(IPValidator::IPVersion ipVersion, const QString &addr)
0045 {
0046     const auto addrChar = addr.toLatin1();
0047     const int af = ipVersion == IPValidator::IPVersion::IPv4 ? AF_INET : AF_INET6;
0048     char buf[sizeof(struct in6_addr)]; // Enough for both ipv4 and v6
0049     // Ask standard library to convert the string representation to a binary representation
0050     // and check whether it succeeded or not, rather than using a regular expression.
0051     if (inet_pton(af, addrChar.constData(), buf) == 1) {
0052         return QValidator::Acceptable;
0053     }
0054 
0055     return QValidator::Intermediate;
0056 }
0057 
0058 QRegularExpression regexForAddress(IPValidator::IPVersion version)
0059 {
0060     if (version == IPValidator::IPVersion::IPv4) {
0061         return QRegularExpression{QStringLiteral(R"(^[0-9\./]+$)")};
0062     } else {
0063         return QRegularExpression{QStringLiteral(R"(^[a-fA-F0-9:\./]+$)")};
0064     }
0065 }
0066 
0067 } // namespace
0068 
0069 IPValidator::IPValidator(QObject *parent)
0070     : QValidator(parent)
0071 {
0072 }
0073 
0074 IPValidator::IPVersion IPValidator::ipVersion() const
0075 {
0076     return mIPVersion;
0077 }
0078 
0079 void IPValidator::setIPVersion(IPVersion version)
0080 {
0081     if (mIPVersion != version) {
0082         mIPVersion = version;
0083         Q_EMIT ipVersionChanged(mIPVersion);
0084     }
0085 }
0086 
0087 QValidator::State IPValidator::validate(QString &arg, int &pos) const
0088 {
0089     Q_UNUSED(pos);
0090 
0091     const auto rx = regexForAddress(mIPVersion);
0092     // Quick regex check to immediatelly reject anything that does not look like an IP address.
0093     if (!rx.match(arg).hasMatch()) {
0094         return QValidator::Invalid;
0095     }
0096 
0097     const auto parts = arg.split(QLatin1Char('/'));
0098     // Check there's at most one split
0099     if (parts.size() < 1 || parts.size() > 2) {
0100         return QValidator::Invalid;
0101     }
0102 
0103     // Validate the address
0104     const auto addressValidity = checkAddressValid(mIPVersion, parts[0]);
0105     if (addressValidity == QValidator::Invalid) {
0106         return QValidator::Invalid;
0107     }
0108 
0109     // Validate netmask, if present.
0110     const auto netmaskValidity = parts.size() == 2 ? checkNetmaskValid(mIPVersion, parts[1]) : QValidator::Acceptable;
0111     if (netmaskValidity == QValidator::Invalid) {
0112         return QValidator::Invalid;
0113     }
0114 
0115     return std::min(addressValidity, netmaskValidity);
0116 }