Merge pull request #69130 from m4xxx1m/hostname-validation-config

Add validation of IP addresses and domains in settings
This commit is contained in:
Konstantin Bogdanov 2024-09-03 17:09:17 +00:00 committed by GitHub
commit dc0b491f11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 207 additions and 1 deletions

View File

@ -18,4 +18,4 @@ target_compile_options (_poco_util
-Wno-zero-as-null-pointer-constant
)
target_include_directories (_poco_util SYSTEM PUBLIC "include")
target_link_libraries (_poco_util PUBLIC Poco::JSON Poco::XML)
target_link_libraries (_poco_util PUBLIC Poco::JSON Poco::XML Poco::Net)

View File

@ -241,6 +241,20 @@ namespace Util
/// If the value contains references to other properties (${<property>}), these
/// are expanded.
std::string getHost(const std::string & key) const;
/// Returns the string value of the host property with the given name.
/// Throws a NotFoundException if the key does not exist.
/// Throws a SyntaxException if the property is not a valid host (IP address or domain).
/// If the value contains references to other properties (${<property>}), these
/// are expanded.
std::string getHost(const std::string & key, const std::string & defaultValue) const;
/// If a property with the given key exists, returns the host property's string value,
/// otherwise returns the given default value.
/// Throws a SyntaxException if the property is not a valid host (IP address or domain).
/// If the value contains references to other properties (${<property>}), these
/// are expanded.
virtual void setString(const std::string & key, const std::string & value);
/// Sets the property with the given key to the given value.
/// An already existing value for the key is overwritten.
@ -339,12 +353,35 @@ namespace Util
static bool parseBool(const std::string & value);
void setRawWithEvent(const std::string & key, std::string value);
static void checkHostValidity(const std::string & value);
/// Throws a SyntaxException if the value is not a valid host (IP address or domain).
virtual ~AbstractConfiguration();
private:
std::string internalExpand(const std::string & value) const;
std::string uncheckedExpand(const std::string & value) const;
static bool isValidIPv4Address(const std::string & value);
/// IPv4 address considered valid if it is "0.0.0.0" or one of those,
/// defined by inet_aton() or inet_addr()
static bool isValidIPv6Address(const std::string & value);
/// IPv6 address considered valid if it is "::" or one of those,
/// defined by inet_pton() with AF_INET6 flag
/// (in this case it may have scope id and may be surrounded by '[', ']')
static bool isValidDomainName(const std::string & value);
/// <domain> ::= <subdomain> [ "." ]
/// <subdomain> ::= <label> | <subdomain> "." <label>
/// <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
/// <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
/// <let-dig-hyp> ::= <let-dig> | "-"
/// <let-dig> ::= <letter> | <digit>
/// <letter> ::= any one of the 52 alphabetic characters A through Z in
/// upper case and a through z in lower case
/// <digit> ::= any one of the ten digits 0 through 9
AbstractConfiguration(const AbstractConfiguration &);
AbstractConfiguration & operator=(const AbstractConfiguration &);

View File

@ -18,6 +18,7 @@
#include "Poco/NumberParser.h"
#include "Poco/NumberFormatter.h"
#include "Poco/String.h"
#include "Poco/Net/IPAddressImpl.h"
using Poco::Mutex;
@ -263,6 +264,41 @@ bool AbstractConfiguration::getBool(const std::string& key, bool defaultValue) c
}
std::string AbstractConfiguration::getHost(const std::string& key) const
{
Mutex::ScopedLock lock(_mutex);
std::string value;
if (getRaw(key, value))
{
std::string expandedValue = internalExpand(value);
checkHostValidity(expandedValue);
return expandedValue;
}
else
throw NotFoundException(key);
}
std::string AbstractConfiguration::getHost(const std::string& key, const std::string& defaultValue) const
{
Mutex::ScopedLock lock(_mutex);
std::string value;
if (getRaw(key, value))
{
std::string expandedValue = internalExpand(value);
checkHostValidity(expandedValue);
return expandedValue;
}
else
{
checkHostValidity(defaultValue);
return defaultValue;
}
}
void AbstractConfiguration::setString(const std::string& key, const std::string& value)
{
setRawWithEvent(key, value);
@ -529,4 +565,68 @@ void AbstractConfiguration::setRawWithEvent(const std::string& key, std::string
}
void AbstractConfiguration::checkHostValidity(const std::string& value)
{
if (!isValidIPv4Address(value) && !isValidIPv6Address(value) && !isValidDomainName(value))
{
throw SyntaxException("Property is not a valid host name", value);
}
}
bool AbstractConfiguration::isValidIPv4Address(const std::string& value)
{
using Poco::Net::Impl::IPv4AddressImpl;
IPv4AddressImpl empty4 = IPv4AddressImpl();
IPv4AddressImpl ipAddress = IPv4AddressImpl::parse(value);
return ipAddress != empty4 || value == "0.0.0.0";
}
bool AbstractConfiguration::isValidIPv6Address(const std::string& value)
{
#if defined(POCO_HAVE_IPv6)
using Poco::Net::Impl::IPv6AddressImpl;
IPv6AddressImpl empty6 = IPv6AddressImpl();
IPv6AddressImpl ipAddress = IPv6AddressImpl::parse(value);
return ipAddress != empty6 || value == "::";
#else
return false;
#endif
}
bool AbstractConfiguration::isValidDomainName(const std::string& value)
{
if (value.empty() || value == "." || value.length() > 253)
return false;
int labelLength = 0;
char oldChar = 0;
for (char ch : value)
{
if (ch == '.')
{
if (labelLength == 0 || labelLength > 63 || oldChar == '-')
return false;
labelLength = 0;
}
else if (isalnum(ch) || ch == '-')
{
if (labelLength == 0 && (ch == '-' || isdigit(ch)))
return false;
++labelLength;
}
else
{
return false;
}
oldChar = ch;
}
return oldChar == '.' || (labelLength > 0 && labelLength <= 63 && oldChar != '-');
}
} } // namespace Poco::Util

View File

@ -0,0 +1,69 @@
#include <Poco/AutoPtr.h>
#include <Poco/DOM/DOMParser.h>
#include <Poco/Util/XMLConfiguration.h>
#include <gtest/gtest.h>
TEST(Common, ConfigHostValidation)
{
std::string xml(R"CONFIG(<clickhouse>
<IPv4_1>0.0.0.0</IPv4_1>
<IPv4_2>192.168.0.1</IPv4_2>
<IPv4_3>127.0.0.1</IPv4_3>
<IPv4_4>255.255.255.255</IPv4_4>
<IPv6_1>2001:0db8:85a3:0000:0000:8a2e:0370:7334</IPv6_1>
<IPv6_2>2001:DB8::8a2e:370:7334</IPv6_2>
<IPv6_3>::1</IPv6_3>
<IPv6_4>::</IPv6_4>
<Domain_1>www.example.com.</Domain_1>
<Domain_2>a.co</Domain_2>
<Domain_3>localhost</Domain_3>
<Domain_4>xn--fiqs8s.xn--fiqz9s</Domain_4>
<IPv4_Invalid_1>192.168.1.256</IPv4_Invalid_1>
<IPv4_Invalid_2>192.168.1.1.1</IPv4_Invalid_2>
<IPv4_Invalid_3>192.168.1.99999999999999999999</IPv4_Invalid_3>
<IPv4_Invalid_4>192.168.1.a</IPv4_Invalid_4>
<IPv6_Invalid_1>2001:0db8:85a3:::8a2e:0370:7334</IPv6_Invalid_1>
<IPv6_Invalid_2>1200::AB00:1234::2552:7777:1313</IPv6_Invalid_2>
<IPv6_Invalid_3>1200::AB00:1234:Q000:2552:7777:1313</IPv6_Invalid_3>
<IPv6_Invalid_4>1200:AB00:1234:2552:7777:1313:FFFF</IPv6_Invalid_4>
<Domain_Invalid_1>example.com..</Domain_Invalid_1>
<Domain_Invalid_2>5example.com</Domain_Invalid_2>
<Domain_Invalid_3>example.com-</Domain_Invalid_3>
<Domain_Invalid_4>exa_mple.com</Domain_Invalid_4>
</clickhouse>)CONFIG");
Poco::XML::DOMParser dom_parser;
Poco::AutoPtr<Poco::XML::Document> document = dom_parser.parseString(xml);
Poco::AutoPtr<Poco::Util::XMLConfiguration> config = new Poco::Util::XMLConfiguration(document);
EXPECT_NO_THROW(config->getHost("IPv4_1"));
EXPECT_NO_THROW(config->getHost("IPv4_2"));
EXPECT_NO_THROW(config->getHost("IPv4_3"));
EXPECT_NO_THROW(config->getHost("IPv4_4"));
EXPECT_NO_THROW(config->getHost("IPv6_1"));
EXPECT_NO_THROW(config->getHost("IPv6_2"));
EXPECT_NO_THROW(config->getHost("IPv6_3"));
EXPECT_NO_THROW(config->getHost("IPv6_4"));
EXPECT_NO_THROW(config->getHost("Domain_1"));
EXPECT_NO_THROW(config->getHost("Domain_2"));
EXPECT_NO_THROW(config->getHost("Domain_3"));
EXPECT_NO_THROW(config->getHost("Domain_4"));
EXPECT_THROW(config->getHost("IPv4_Invalid_1"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv4_Invalid_2"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv4_Invalid_3"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv4_Invalid_4"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv6_Invalid_1"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv6_Invalid_2"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv6_Invalid_3"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("IPv6_Invalid_4"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("Domain_Invalid_1"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("Domain_Invalid_2"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("Domain_Invalid_3"), Poco::SyntaxException);
EXPECT_THROW(config->getHost("Domain_Invalid_4"), Poco::SyntaxException);
}