]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3210] refactor string utilities
authorAndrei Pavel <andrei@isc.org>
Mon, 4 Mar 2024 09:49:45 +0000 (11:49 +0200)
committerAndrei Pavel <andrei@isc.org>
Thu, 21 Mar 2024 16:30:04 +0000 (18:30 +0200)
- Rename util/strutil.h to util/str.h to escape redundancy.
- Simplify trim function.
- Remove unused functions.
- Get rid of the regex conditional compilation that helped Kea build
  with ancient compilers. Lack of proper regex functionality now results
  in failure in configure.ac.

50 files changed:
configure.ac
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp6/json_config_parser.cc
src/hooks/dhcp/flex_option/flex_option.cc
src/hooks/dhcp/flex_option/flex_option.h
src/hooks/dhcp/high_availability/ha_config.cc
src/hooks/dhcp/lease_cmds/lease_cmds.cc
src/lib/cc/server_tag.cc
src/lib/database/database_connection.cc
src/lib/dhcp/classify.cc
src/lib/dhcp/duid.h
src/lib/dhcp/duid_factory.cc
src/lib/dhcp/hwaddr.cc
src/lib/dhcp/option4_client_fqdn.cc
src/lib/dhcp/option4_dnr.h
src/lib/dhcp/option6_client_fqdn.cc
src/lib/dhcp/option_classless_static_route.cc
src/lib/dhcp/option_data_types.cc
src/lib/dhcp/option_definition.cc
src/lib/dhcp/option_string.cc
src/lib/dhcp/tests/option_classless_static_route_unittest.cc
src/lib/dhcpsrv/cfg_duid.cc
src/lib/dhcpsrv/cfg_iface.cc
src/lib/dhcpsrv/host.cc
src/lib/dhcpsrv/lease.cc
src/lib/dhcpsrv/parsers/base_network_parser.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/option_data_parser.cc
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h
src/lib/dhcpsrv/tests/d2_client_unittest.cc
src/lib/hooks/hooks_parser.cc
src/lib/http/basic_auth_config.cc
src/lib/http/http_header.cc
src/lib/log/compiler/message.cc
src/lib/log/logger.cc
src/lib/log/logger_impl.cc
src/lib/log/message_reader.cc
src/lib/log/tests/logger_example.cc
src/lib/process/d_cfg_mgr.cc
src/lib/tcp/tcp_connection.cc
src/lib/tcp/tcp_stream_msg.cc
src/lib/util/Makefile.am
src/lib/util/str.cc [new file with mode: 0644]
src/lib/util/str.h [moved from src/lib/util/strutil.h with 52% similarity]
src/lib/util/strutil.cc [deleted file]
src/lib/util/tests/str_unittests.cc [new file with mode: 0644]
src/lib/util/tests/strutil_unittest.cc [deleted file]
src/lib/yang/adaptor_host.cc

index cf289880f465e9b28ba9bb6385ccadeacd5b0d02..453a33315d27a253f940895594f433fc8e4ddb74 100644 (file)
@@ -667,8 +667,8 @@ int main() {
 # When cross-compiling we don't have any way to check if regex is
 # usable or not.
 # Let's be optimistic and assume it is by testing only the negative case.
-if test "x$usable_regex" != "xno" ; then
-        AC_DEFINE(USE_REGEX, 1, [Define to 1 if C++11 regex is usable])
+if test "${usable_regex}" = 'no'; then
+  AC_MSG_ERROR([Need proper regex functionality.])]
 fi
 
 # Check for NETCONF support. If NETCONF was enabled in the build, and this check
index 7c75edde28d07403cdc25b48b5e7addea91db63f..cead6efbfbb100327f54644fdd99c939e4d48025 100644 (file)
@@ -49,7 +49,7 @@
 #include <hooks/hooks_log.h>
 #include <hooks/hooks_manager.h>
 #include <stats/stats_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <log/logger.h>
 #include <cryptolink/cryptolink.h>
 #include <process/cfgrpt/config_report.h>
index 6164c3c7d9d26f92f77460edac53f5588b1986ea..0b5c90c6a51e9abd9a26500ed2e1aa89edd41c4e 100644 (file)
@@ -42,7 +42,7 @@
 #include <process/config_ctl_parser.h>
 #include <util/encode/encode.h>
 #include <util/multi_threading_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
index 8fd4e15743201c3bc10d25cf3831d3575206338d..62d8df423908c56c286a6152c724c7dd13de993e 100644 (file)
@@ -45,7 +45,7 @@
 #include <process/config_ctl_parser.h>
 #include <util/encode/encode.h>
 #include <util/multi_threading_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <util/triplet.h>
 
 #include <boost/algorithm/string.hpp>
index f32d429d0d19de617c93b4914f9077f3ad8d706b..5313216ad2c8e30addb93ffac072b79ca4d31e97 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <flex_option.h>
 #include <flex_option_log.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <cc/simple_parser.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/libdhcp++.h>
index 0bdbdce56867507ab1cff722b48f6440aa57f0ff..1d60a85920eab2d7af542a60b4f3d1490d011528 100644 (file)
@@ -17,7 +17,7 @@
 #include <dhcp/std_option_defs.h>
 #include <eval/evaluate.h>
 #include <eval/token.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/classification.hpp>
index a8d70e27e1336cd4708b8fa09a9a60e8304e7788..89d3f8a0a1e747925ee3c6ca415fa1313da0009a 100644 (file)
@@ -14,7 +14,7 @@
 #include <dhcpsrv/network.h>
 #include <exceptions/exceptions.h>
 #include <util/multi_threading_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <ha_log.h>
 #include <ha_config.h>
 #include <ha_service_states.h>
index 534d6f55161ac0da6a7f360aff8fbfce7b5a5556..7c6ebf0579a81b35292a5583aa21f781e075265b 100644 (file)
@@ -29,7 +29,7 @@
 #include <stats/stats_mgr.h>
 #include <util/encode/encode.h>
 #include <util/multi_threading_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/algorithm/string.hpp>
index 80e5184ec5ed3c8125efaab41f31a27e7083ae7c..94e4ce0f8433ecd166004be5c3753e47fa0c4cc7 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <cc/server_tag.h>
 #include <exceptions/exceptions.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/algorithm/string.hpp>
 
 namespace isc {
index fca514d7da9d04062327183e27fc3a0d69b144af..607c706376236b4547e2b626634370f26fd707c3 100644 (file)
@@ -12,7 +12,7 @@
 #include <database/db_log.h>
 #include <database/db_messages.h>
 #include <exceptions/exceptions.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string.hpp>
 #include <vector>
index 6803afb363661adcfa62d30b41665ccc2c04c0b0..f42a9e3710fd66ebdd2d6b47f17f1857624afbed 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <cc/data.h>
 #include <dhcp/classify.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string/classification.hpp>
 #include <boost/algorithm/string/constants.hpp>
index 2ec1b163d2018a9d4a504b41bfb305bff540c0f2..2239c232c2cecb37b784d49c99eb5bfb60bbdf3e 100644 (file)
@@ -8,10 +8,14 @@
 #define DUID_H
 
 #include <asiolink/io_address.h>
-#include <util/strutil.h>
-#include <boost/shared_ptr.hpp>
+#include <util/str.h>
+
+#include <cstdint>
+#include <iomanip>
 #include <vector>
-#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
 #include <unistd.h>
 
 namespace isc {
index 43c33638dc46b3b2c1b6ddbdbfd5bb0fb800dfab..ea1ad8ca77c7e5d4e5d13f5b17b6b06140ff210b 100644 (file)
@@ -11,7 +11,7 @@
 #include <exceptions/exceptions.h>
 #include <util/io.h>
 #include <util/range_utilities.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <ctime>
 #include <fstream>
 #include <stdlib.h>
index 9f51fb891e18949b328eea68f45b912cf893dbac..a1b3680257dc8c96676ef4bfa6f371b96d648604 100644 (file)
@@ -9,7 +9,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/dhcp4.h>
 #include <exceptions/exceptions.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <iomanip>
 #include <sstream>
 #include <vector>
index 17b804b57be23760753dbf7714cfc57121dec01d..ae2d5b62d918b55c78a4ac0d866e965cc8c98d99 100644 (file)
@@ -11,7 +11,7 @@
 #include <dns/labelsequence.h>
 #include <util/buffer.h>
 #include <util/io.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <sstream>
 
 namespace isc {
index 7c498ceab15fa378d4f71498a7f8b1bb1c171fdc..02893d45986e09bcfa0978fda1e30a3006a93152 100644 (file)
@@ -16,7 +16,7 @@
 #include <dhcp/option_data_types.h>
 #include <dns/name.h>
 #include <util/encode/utf8.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <map>
 #include <set>
index dd28599fa4578b9281b8519e2e8dd33f7377f826..ba4d056425218c141b1497686bf3c81d78a7100d 100644 (file)
@@ -11,7 +11,7 @@
 #include <dns/labelsequence.h>
 #include <util/buffer.h>
 #include <util/io.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <sstream>
 
 namespace isc {
index cb4ed0cf139956aca53b22b2264fc1640e56f6f8..6d95df974977dc93634003d944985c7760ccdd03 100644 (file)
@@ -7,7 +7,7 @@
 #include <config.h>
 
 #include <asiolink/io_error.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <option_classless_static_route.h>
 
index 0308d8c6a3489a11e25cd5885dd0e7f0eee92b10..6be31e8452afc1c825fed30405559e206e5e5d88 100644 (file)
@@ -9,7 +9,7 @@
 #include <dhcp/option_data_types.h>
 #include <dns/labelsequence.h>
 #include <dns/name.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <util/encode/encode.h>
 #include <algorithm>
 #include <limits>
index 54d569b66050a3c25b6fe7155f15262c9fb89ec2..0c9cac91201c800e1b0534213cf01c145246a073 100644 (file)
@@ -31,7 +31,7 @@
 #include <util/encode/encode.h>
 #include <dns/labelsequence.h>
 #include <dns/name.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/algorithm/string/classification.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/replace.hpp>
index 5f1d0d4d69b2fcc547c4d93da10d9d5f79d714fb..90918d99854030066b3a1e32709bc0cf76a255c1 100644 (file)
@@ -7,7 +7,7 @@
 #include <config.h>
 
 #include <dhcp/option_string.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <sstream>
 
 namespace isc {
index 0b99b2610417af9b0c6bfe0939e7ceac4f644415..8eb2775d4ae24bf4f257e96115aa9bd09b02b9fb 100644 (file)
@@ -7,7 +7,7 @@
 #include <config.h>
 
 #include <dhcp/option_classless_static_route.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <gtest/gtest.h>
 
index 24be637ab782e54018af90e5f8bf12fa1ec67e52..831772d429d4115e8dfb9c8a95284d770adb433d 100644 (file)
@@ -9,7 +9,7 @@
 #include <dhcp/duid_factory.h>
 #include <dhcpsrv/cfg_duid.h>
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <iostream>
 #include <string>
 #include <string.h>
index 78e61ba23dba34fb030a94912b8c0faf8114d17b..3991e223962158a96e91391061f46fba9cb38439 100644 (file)
@@ -11,7 +11,7 @@
 #include <dhcpsrv/timer_mgr.h>
 #include <util/reconnect_ctl.h>
 #include <util/multi_threading_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <algorithm>
 #include <functional>
 
index 4390dba32269b23e23b89b3247870af1abd50589..5d45192ee422a921d2ee9addf4c8687c133111eb 100644 (file)
@@ -14,7 +14,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/foreach.hpp>
 #include <sstream>
index 9dad4083d187951be414ede2d68777c7e5594927..651b9f58b7a2f096b15bab9c16394feda890a167 100644 (file)
@@ -10,7 +10,7 @@
 #include <asiolink/addr_utilities.h>
 #include <dhcpsrv/lease.h>
 #include <util/pointer_util.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/algorithm/string.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <sstream>
index 60717c295b88f0a1674fac623a6f6085a136981f..c70c44d36a73a4e29d3316a595d624068ca5c277 100644 (file)
@@ -9,7 +9,7 @@
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/parsers/base_network_parser.h>
 #include <util/optional.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 using namespace isc::data;
 using namespace isc::util;
index 78077b5986964bffe242eac421764297c65d4ce5..5fc899f6f127f97d14dba6d66e00374cb86bd3da 100644 (file)
@@ -19,7 +19,7 @@
 #include <dhcpsrv/parsers/simple_parser6.h>
 #include <dhcpsrv/cfg_mac_source.h>
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
index 8ec49a03096c5c72c1630a2c7381a5671f05c664..cd544d58339647c4643fa491bd8a9948d2baa835 100644 (file)
@@ -16,7 +16,7 @@
 #include <dhcpsrv/parsers/simple_parser4.h>
 #include <dhcpsrv/parsers/simple_parser6.h>
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/make_shared.hpp>
 #include <limits>
 #include <vector>
index 763e678e9ad6d142d07ca96fb20ee7d091c8bec6..30a6ad81249f98a4c1e25dbd22d8b4b526354bf9 100644 (file)
@@ -28,7 +28,7 @@
 #include <log/logger_specification.h>
 #include <dhcp/pkt.h>
 #include <stats/stats_mgr.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/make_shared.hpp>
 
index 8489249242a13a0601eccab2e3d2b02a1d997dc1..c329eb6421be664bd6c88336598bfb91e19124f5 100644 (file)
@@ -31,7 +31,7 @@
 #include <cc/user_context.h>
 #include <cc/simple_parser.h>
 #include <util/optional.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/shared_ptr.hpp>
 #include <vector>
index 98efdadced0eae7505f26d31f4d86abb2349e9eb..6bab3abeed522e58d1aab9a47403272c4a256c69 100644 (file)
@@ -10,7 +10,7 @@
 #include <dhcpsrv/d2_client_mgr.h>
 #include <testutils/test_to_element.h>
 #include <exceptions/exceptions.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
index 32296214dce14828273be9263a0a4674c7ad3319..56029896fe77c31278df1f0f71e25344ad04bbf0 100644 (file)
@@ -10,7 +10,7 @@
 #include <cc/dhcp_config_error.h>
 #include <hooks/hooks_parser.h>
 #include <boost/algorithm/string.hpp>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <vector>
 
 using namespace std;
index 1e6a727094361b2999d3bb32e55c672db19d958f..84c0c548ec01ff3bba86f4162bee887dfec6c73f 100644 (file)
@@ -9,7 +9,7 @@
 #include <http/auth_log.h>
 #include <http/basic_auth_config.h>
 #include <util/filesystem.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 using namespace isc;
 using namespace isc::data;
index a543e664b26a8529aeccf330064f58ed7380aea6..555eb3caa8863152fb54ab85898c0b1763365146 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <exceptions/exceptions.h>
 #include <http/http_header.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/lexical_cast.hpp>
 
 namespace isc {
index 91a582f95816c73ed7f9af029551ad942075aa83..81f408742bbe74a97ee3df8e2970337c53cb75ae 100644 (file)
@@ -22,7 +22,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/filesystem.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <log/log_messages.h>
 #include <log/message_dictionary.h>
index e036a096a82af4e9182834e0a7e47ad141026995..e8e0f74a9849f8276dd5c6bdfa8fe3a09dcb1514 100644 (file)
@@ -16,7 +16,7 @@
 #include <log/message_dictionary.h>
 #include <log/message_types.h>
 
-#include <util/strutil.h>
+#include <util/str.h>
 
 using namespace std;
 
index d5ca88fd3a3a461c3aaf85b73e502da700909e98..b5e9c8dc8e4349b40d24b81f17defaaaf4c36df3 100644 (file)
@@ -38,7 +38,7 @@
 #include <log/interprocess/interprocess_sync_file.h>
 #include <log/interprocess/interprocess_sync_null.h>
 
-#include <util/strutil.h>
+#include <util/str.h>
 
 // Note: as log4cplus and the Kea logger have many concepts in common, and
 // thus many similar names, to disambiguate types we don't "use" the log4cplus
index 2b48608733d9ff6ca81a456828c963184923ddba..bf797d9ffaba4f62f2c2a9b47e9e692fc987cc1e 100644 (file)
@@ -16,7 +16,7 @@
 #include <log/log_messages.h>
 #include <log/message_exception.h>
 #include <log/message_reader.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 using namespace std;
 
index ff3d512296ac13e9766cb5aba4faac9429d308cd..86e9b76431c499425255a238b9450112f8d83b8f 100644 (file)
@@ -24,7 +24,7 @@
 #include <string>
 #include <vector>
 
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <log/logger.h>
 #include <log/logger_level.h>
index 24ead16d3b9a1f7640251151d361aa5c3f59eb0d..72bc4dea56bb55f6b7bfad66992eda912b37dbed 100644 (file)
@@ -13,7 +13,7 @@
 #include <process/daemon.h>
 #include <process/redact_config.h>
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
index 0445b35a90f9d37b40e208847f6b9411473f59b3..a3fa375bfd10dfe1874850cd0430b683f41b90d1 100644 (file)
@@ -11,7 +11,7 @@
 #include <tcp/tcp_connection_pool.h>
 #include <tcp/tcp_log.h>
 #include <tcp/tcp_messages.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <boost/make_shared.hpp>
 
 #include <iomanip>
index 89fd5e82471d1c9a73bde5c80d9e4b7377505aef..64332af840d41c3f6995476524d59b10690ed001 100644 (file)
@@ -7,7 +7,7 @@
 #include <config.h>
 
 #include <tcp/tcp_stream_msg.h>
-#include <util/strutil.h>
+#include <util/str.h>
 
 #include <iomanip>
 #include <sstream>
index 828e0fe7eda7ae375bdc022a853fe7262fc3b1a0..90cb5e71184fa187aa2dc248f5e71f7e21d5813f 100644 (file)
@@ -32,7 +32,7 @@ libkea_util_la_SOURCES += staged_value.h
 libkea_util_la_SOURCES += state_model.cc state_model.h
 libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
 libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h
-libkea_util_la_SOURCES += strutil.h strutil.cc
+libkea_util_la_SOURCES += str.h str.cc
 libkea_util_la_SOURCES += thread_pool.h
 libkea_util_la_SOURCES += triplet.h
 libkea_util_la_SOURCES += unlock_guard.h
@@ -77,7 +77,7 @@ libkea_util_include_HEADERS = \
        state_model.h \
        stopwatch.h \
        stopwatch_impl.h \
-       strutil.h \
+       str.h \
        thread_pool.h \
        triplet.h \
        unlock_guard.h \
diff --git a/src/lib/util/str.cc b/src/lib/util/str.cc
new file mode 100644 (file)
index 0000000..9c3a3b8
--- /dev/null
@@ -0,0 +1,345 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/encode/encode.h>
+#include <util/str.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <exception>
+#include <iomanip>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace str {
+
+string
+trim(const string& input) {
+    if (input.empty()) {
+        return string();
+    }
+    static const char* blanks = " \t\n";
+
+    // Search for first non-blank character in the string.
+    size_t const first(input.find_first_not_of(blanks));
+    if (first == string::npos) {
+        return string();
+    }
+
+    // String not all blanks, so look for last character.
+    size_t const last(input.find_last_not_of(blanks));
+
+    // Extract the trimmed substring.
+    return input.substr(first, (last - first + 1));
+}
+
+vector<string>
+tokens(const string& text, const string& delim, bool escape) {
+    vector<string> result;
+    string token;
+    bool in_token = false;
+    bool escaped = false;
+    for (auto const& c : text) {
+        if (delim.find(c) != string::npos) {
+            // Current character is a delimiter
+            if (!in_token) {
+                // Two or more delimiters, eat them
+            } else if (escaped) {
+                // Escaped delimiter in a token: reset escaped and keep it
+                escaped = false;
+                token.push_back(c);
+            } else {
+                // End of the current token: save it if not empty
+                if (!token.empty()) {
+                    result.push_back(token);
+                }
+                // Reset state
+                in_token = false;
+                token.clear();
+            }
+        } else if (escape && (c == '\\')) {
+            // Current character is the escape character
+            if (!in_token) {
+                // The escape character is the first character of a new token
+                in_token = true;
+            }
+            if (escaped) {
+                // Escaped escape: reset escaped and keep one character
+                escaped = false;
+                token.push_back(c);
+            } else {
+                // Remember to keep the next character
+                escaped = true;
+            }
+        } else {
+            // Not a delimiter nor an escape
+            if (!in_token) {
+                // First character of a new token
+                in_token = true;
+            }
+            if (escaped) {
+                // Escaped common character: as escape was false
+                escaped = false;
+                token.push_back('\\');
+                token.push_back(c);
+            } else {
+                // The common case: keep it
+                token.push_back(c);
+            }
+        }
+    }
+    // End of input: close and save the current token if not empty
+    if (escaped) {
+        // Pending escape
+        token.push_back('\\');
+    }
+    if (!token.empty()) {
+        result.push_back(token);
+    }
+
+    return (result);
+}
+
+char
+toUpper(char const chr) {
+    return (toupper(chr));
+}
+
+void
+uppercase(string& text) {
+    transform(text.begin(), text.end(), text.begin(), toUpper);
+}
+
+char
+toLower(char const chr) {
+    return (tolower(static_cast<int>(chr)));
+}
+
+void
+lowercase(string& text) {
+    transform(text.begin(), text.end(), text.begin(), toLower);
+}
+
+vector<uint8_t>
+quotedStringToBinary(const string& quoted_string) {
+    vector<uint8_t> binary;
+    // Remove whitespace before and after the quotes.
+    string trimmed_string = trim(quoted_string);
+
+    // We require two quote characters, so the length of the string must be
+    // equal to 2 at minimum, and it must start and end with quotes.
+    if ((trimmed_string.length() > 1) &&
+        ((trimmed_string[0] == '\'') && (trimmed_string[trimmed_string.length() - 1] == '\''))) {
+        // Remove quotes and trim the text inside the quotes.
+        trimmed_string = trim(trimmed_string.substr(1, trimmed_string.length() - 2));
+        // Copy string contents into the vector.
+        binary.assign(trimmed_string.begin(), trimmed_string.end());
+    }
+    // Return resulting vector or empty vector.
+    return (binary);
+}
+
+void
+decodeColonSeparatedHexString(const string& hex_string, vector<uint8_t>& binary) {
+    decodeSeparatedHexString(hex_string, ":", binary);
+}
+
+void
+decodeSeparatedHexString(const string& hex_string, const string& sep, vector<uint8_t>& binary) {
+    vector<string> split_text;
+    boost::split(split_text, hex_string, boost::is_any_of(sep),
+                 boost::algorithm::token_compress_off);
+
+    vector<uint8_t> binary_vec;
+    for (size_t i = 0; i < split_text.size(); ++i) {
+        // If there are multiple tokens and the current one is empty, it
+        // means that two consecutive colons were specified. This is not
+        // allowed.
+        if ((split_text.size() > 1) && split_text[i].empty()) {
+            isc_throw(BadValue, "two consecutive separators ('"
+                                    << sep << "') specified in a decoded string '" << hex_string
+                                    << "'");
+
+            // Between a colon we expect at most two characters.
+        } else if (split_text[i].size() > 2) {
+            isc_throw(BadValue, "invalid format of the decoded string"
+                                    << " '" << hex_string << "'");
+
+        } else if (!split_text[i].empty()) {
+            stringstream s;
+            s << "0x";
+
+            for (unsigned int j = 0; j < split_text[i].length(); ++j) {
+                // Check if we're dealing with hexadecimal digit.
+                if (!isxdigit(split_text[i][j])) {
+                    isc_throw(BadValue, "'" << split_text[i][j]
+                                            << "' is not a valid hexadecimal digit in"
+                                            << " decoded string '" << hex_string << "'");
+                }
+                s << split_text[i][j];
+            }
+
+            // The stream should now have one or two hexadecimal digits.
+            // Let's convert it to a number and store in a temporary
+            // vector.
+            unsigned int binary_value;
+            s >> hex >> binary_value;
+
+            binary_vec.push_back(static_cast<uint8_t>(binary_value));
+        }
+    }
+
+    // All ok, replace the data in the output vector with a result.
+    binary.swap(binary_vec);
+}
+
+void
+decodeFormattedHexString(const string& hex_string, vector<uint8_t>& binary) {
+    // If there is at least one colon we assume that the string
+    // comprises octets separated by colons (e.g. MAC address notation).
+    if (hex_string.find(':') != string::npos) {
+        decodeSeparatedHexString(hex_string, ":", binary);
+    } else if (hex_string.find(' ') != string::npos) {
+        decodeSeparatedHexString(hex_string, " ", binary);
+    } else {
+        ostringstream s;
+
+        // If we have odd number of digits we'll have to prepend '0'.
+        if (hex_string.length() % 2 != 0) {
+            s << "0";
+        }
+
+        // It is ok to use '0x' prefix in a string.
+        if ((hex_string.length() > 2) && (hex_string.substr(0, 2) == "0x")) {
+            // Exclude '0x' from the decoded string.
+            s << hex_string.substr(2);
+
+        } else {
+            // No '0x', so decode the whole string.
+            s << hex_string;
+        }
+
+        try {
+            // Decode the hex string.
+            encode::decodeHex(s.str(), binary);
+
+        } catch (...) {
+            isc_throw(BadValue, "'" << hex_string
+                                    << "' is not a valid"
+                                       " string of hexadecimal digits");
+        }
+    }
+}
+
+class StringSanitizerImpl {
+public:
+    /// @brief Constructor.
+    StringSanitizerImpl(const string& char_set, const string& char_replacement)
+        : char_set_(char_set), char_replacement_(char_replacement) {
+        if (char_set.size() > StringSanitizer::MAX_DATA_SIZE) {
+            isc_throw(BadValue, "char set size: '" << char_set.size() << "' exceeds max size: '"
+                                                   << StringSanitizer::MAX_DATA_SIZE << "'");
+        }
+
+        if (char_replacement.size() > StringSanitizer::MAX_DATA_SIZE) {
+            isc_throw(BadValue, "char replacement size: '"
+                                    << char_replacement.size() << "' exceeds max size: '"
+                                    << StringSanitizer::MAX_DATA_SIZE << "'");
+        }
+        try {
+            scrub_exp_ = regex(char_set, regex::extended);
+        } catch (const exception& ex) {
+            isc_throw(BadValue, "invalid regex: '" << char_set_ << "', " << ex.what());
+        }
+    }
+
+    string scrub(const string& original) {
+        stringstream result;
+        try {
+            regex_replace(ostream_iterator<char>(result), original.begin(), original.end(),
+                          scrub_exp_, char_replacement_);
+        } catch (const exception& ex) {
+            isc_throw(BadValue, "replacing '" << char_set_ << "' with '" << char_replacement_
+                                              << "' in '" << original << "' failed: ,"
+                                              << ex.what());
+        }
+
+        return (result.str());
+    }
+
+private:
+    /// @brief The char set data for regex.
+    string char_set_;
+
+    /// @brief The char replacement data for regex.
+    string char_replacement_;
+
+    regex scrub_exp_;
+};
+
+// @note The regex engine is implemented using recursion and can cause
+// stack overflow if the input data is too large. An arbitrary size of
+// 4096 should be enough for all cases.
+const uint32_t StringSanitizer::MAX_DATA_SIZE = 4096;
+
+StringSanitizer::StringSanitizer(const string& char_set, const string& char_replacement)
+    : impl_(new StringSanitizerImpl(char_set, char_replacement)) {
+}
+
+string
+StringSanitizer::scrub(const string& original) {
+    return (impl_->scrub(original));
+}
+
+bool
+isPrintable(const string& content) {
+    for (char const ch : content) {
+        if (isprint(ch) == 0) {
+            return (false);
+        }
+    }
+    return (true);
+}
+
+bool
+isPrintable(const vector<uint8_t>& content) {
+    for (uint8_t const ch : content) {
+        if (isprint(ch) == 0) {
+            return (false);
+        }
+    }
+    return (true);
+}
+
+string
+dumpAsHex(const uint8_t* data, size_t length) {
+    stringstream output;
+    for (unsigned int i = 0; i < length; i++) {
+        if (i) {
+            output << ":";
+        }
+
+        output << setfill('0') << setw(2) << hex << static_cast<unsigned short>(data[i]);
+    }
+
+    return (output.str());
+}
+
+}  // namespace str
+}  // namespace util
+}  // namespace isc
similarity index 52%
rename from src/lib/util/strutil.h
rename to src/lib/util/str.h
index 8f3cd13ae81ba9234444192c94a0482b352f1649..1e5d4c405f6235abd94080cd536c547b7d83dadd 100644 (file)
@@ -4,19 +4,20 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-#ifndef STRUTIL_H
-#define STRUTIL_H
+#ifndef KEA_UTIL_STR_H
+#define KEA_UTIL_STR_H
+
+#include <exceptions/exceptions.h>
 
 #include <algorithm>
-#include <cctype>
-#include <stdint.h>
-#include <string>
-#include <iomanip>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
 #include <sstream>
+#include <string>
 #include <vector>
-#include <exceptions/exceptions.h>
+
 #include <boost/lexical_cast.hpp>
-#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace util {
@@ -27,59 +28,52 @@ namespace str {
 ///
 /// @brief A standard string util exception that is thrown if getToken or
 /// numToToken are called with bad input data
-///
 class StringTokenError : public Exception {
 public:
-    StringTokenError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
+    StringTokenError(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {
+    }
 };
 
-/// @brief Normalize Backslash
-///
-/// Only relevant to Windows, this replaces all "\" in a string with "/"
-/// and returns the result.  On other systems it is a no-op.  Note
-/// that Windows does recognize file names with the "\" replaced by "/"
-/// (at least in system calls, if not the command line).
-///
-/// @param name Name to be substituted
-void normalizeSlash(std::string& name);
-
-/// @brief Trim Leading and Trailing Spaces
+/// @brief Trim leading and trailing spaces.
 ///
 /// Returns a copy of the input string but with any leading or trailing spaces
 /// or tabs removed.
 ///
-/// @param instring Input string to modify
+/// @param input Input string to modify.
 ///
-/// @return String with leading and trailing spaces removed
-std::string trim(const std::string& instring);
+/// @return String with leading and trailing spaces removed.
+std::string
+trim(const std::string& input);
 
 /// @brief Finds the "trimmed" end of a buffer
 ///
 /// Works backward from the end of the buffer, looking for the first
 /// character not equal to the trim value, and returns an iterator
-/// pointing that that position.
+/// pointing to that position.
 ///
 /// @param begin - Forward iterator pointing to the beginning of the
-/// buffer to trim
+/// buffer to trim.
 /// @param end - Forward iterator pointing to the untrimmed end of
-/// the buffer to trim
+/// the buffer to trim.
 /// @param trim_val - byte value to trim off
 ///
 /// @return Iterator pointing the first character from the end of the
-/// buffer not equal to the  trim value
-template<typename Iterator>
+/// buffer not equal to the trim value.
+template <typename Iterator>
 Iterator
-seekTrimmed(Iterator begin, Iterator end, uint8_t trim_val) {
-    for (; end != begin && *(end - 1) == trim_val; --end);
+seekTrimmed(Iterator const& begin, Iterator end, uint8_t const trim_val) {
+    while (end != begin && *(end - 1) == trim_val) {
+        --end;
+    }
     return (end);
 }
 
-/// @brief Split String into Tokens
+/// @brief Split string into tokens.
 ///
 /// Splits a string into tokens (the tokens being delimited by one or more of
-/// the delimiter characters) and returns the tokens in a vector array. Note
-/// that adjacent delimiters are considered to be a single delimiter.
+/// the delimiter characters) and returns the tokens in a vector.
+/// Adjacent delimiters are considered to be a single delimiter.
 ///
 /// Special cases are:
 /// -# The empty string is considered to be zero tokens.
@@ -102,120 +96,46 @@ seekTrimmed(Iterator begin, Iterator end, uint8_t trim_val) {
 /// @param escape Use backslash to escape delimiter characters
 ///
 /// @return Vector of tokens.
-std::vector<std::string> tokens(const std::string& text,
-        const std::string& delim = std::string(" \t\n"),
-        bool escape = false);
+std::vector<std::string>
+tokens(const std::string& text, const std::string& delim = " \t\n", bool escape = false);
 
-/// @brief Uppercase Character
+/// @brief Convert character to uppercase.
 ///
-/// Used in uppercase() to pass as an argument to std::transform().  The
-/// function std::toupper() can't be used as it takes an "int" as its argument;
+/// Used in uppercase() to pass as a parameter to std::transform().  The
+/// function std::toupper() can't be used as it takes an "int" as its parameter;
 /// this confuses the template expansion mechanism because dereferencing a
 /// string::iterator returns a char.
 ///
 /// @param chr Character to be upper-cased.
 ///
-/// @return Uppercase version of the argument
-inline char toUpper(char chr) {
-    return (static_cast<char>(std::toupper(static_cast<int>(chr))));
-}
+/// @return Uppercase version of the input character.
+char
+toUpper(char const chr);
 
-/// @brief Uppercase String
-///
-/// A convenience function to uppercase a string.
+/// @brief Convert string to uppercase.
 ///
 /// @param text String to be upper-cased.
-inline void uppercase(std::string& text) {
-    std::transform(text.begin(), text.end(), text.begin(),
-        isc::util::str::toUpper);
-}
+void
+uppercase(std::string& text);
 
-/// @brief Lowercase Character
+/// @brief Convert character to lowercase.
 ///
-/// Used in lowercase() to pass as an argument to std::transform().  The
-/// function std::tolower() can't be used as it takes an "int" as its argument;
+/// Used in lowercase() to pass as a parameter to std::transform().  The
+/// function std::tolower() can't be used as it takes an "int" as its parameter;
 /// this confuses the template expansion mechanism because dereferencing a
 /// string::iterator returns a char.
 ///
 /// @param chr Character to be lower-cased.
 ///
-/// @return Lowercase version of the argument
-inline char toLower(char chr) {
-    return (static_cast<char>(std::tolower(static_cast<int>(chr))));
-}
+/// @return Lowercase version of the input character.
+char
+toLower(char const chr);
 
-/// @brief Lowercase String
-///
-/// A convenience function to lowercase a string
+/// @brief Convert string to lowercase.
 ///
 /// @param text String to be lower-cased.
-inline void lowercase(std::string& text) {
-    std::transform(text.begin(), text.end(), text.begin(),
-        isc::util::str::toLower);
-}
-
-/// @brief Apply Formatting
-///
-/// Given a printf-style format string containing only "%s" place holders
-/// (others are ignored) and a vector of strings, this produces a single string
-/// with the placeholders replaced.
-///
-/// @param format Format string
-/// @param args Vector of argument strings
-///
-/// @return Resultant string
-std::string format(const std::string& format,
-    const std::vector<std::string>& args);
-
-
-/// @brief Returns one token from the given stringstream
-///
-/// Using the >> operator, with basic error checking
-///
-/// @throw StringTokenError if the token cannot be read from the stream
-///
-/// @param iss stringstream to read one token from
-///
-/// @return the first token read from the stringstream
-std::string getToken(std::istringstream& iss);
-
-/// @brief Converts a string token to an *unsigned* integer.
-///
-/// The value is converted using a lexical cast, with error and bounds
-/// checking.
-///
-/// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
-/// wide to store resulting integers.
-///
-/// BitSize is the maximum number of bits that the resulting integer can take.
-/// This function first checks whether the given token can be converted to
-/// an integer of NumType type.  It then confirms the conversion result is
-/// within the valid range, i.e., [0, 2^BitSize - 1].  The second check is
-/// necessary because lexical_cast<T> where T is an unsigned integer type
-/// doesn't correctly reject negative numbers when compiled with SunStudio.
-///
-/// @throw StringTokenError if the value is out of range, or if it
-///        could not be converted
-///
-/// @param num_token the string token to convert
-///
-/// @return the converted value, of type NumType
-template <typename NumType, int BitSize>
-NumType
-tokenToNum(const std::string& num_token) {
-    NumType num;
-    try {
-        num = boost::lexical_cast<NumType>(num_token);
-    } catch (const boost::bad_lexical_cast&) {
-        isc_throw(StringTokenError, "Invalid SRV numeric parameter: " <<
-                  num_token);
-    }
-    if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
-        isc_throw(StringTokenError, "Numeric SRV parameter out of range: " <<
-                  num);
-    }
-    return (num);
-}
+void
+lowercase(std::string& text);
 
 /// @brief Converts a string in quotes into vector.
 ///
@@ -263,16 +183,12 @@ decodeSeparatedHexString(const std::string& hex_string,
 /// @brief Converts a string of hexadecimal digits with colons into
 ///  a vector.
 ///
-/// Convenience method which calls @c decodeSeparatedHexString() passing
-/// in a colon for the separator.
-
 /// @param hex_string Input string.
 /// @param binary Vector receiving converted string into binary.
 ///
 /// @throw isc::BadValue if the format of the input string is invalid.
 void
-decodeColonSeparatedHexString(const std::string& hex_string,
-                              std::vector<uint8_t>& binary);
+decodeColonSeparatedHexString(const std::string& hex_string, std::vector<uint8_t>& binary);
 
 /// @brief Converts a formatted string of hexadecimal digits into
 /// a vector.
@@ -293,24 +209,17 @@ decodeColonSeparatedHexString(const std::string& hex_string,
 ///
 /// @throw isc::BadValue if the format of the input string is invalid.
 void
-decodeFormattedHexString(const std::string& hex_string,
-                         std::vector<uint8_t>& binary);
+decodeFormattedHexString(const std::string& hex_string, std::vector<uint8_t>& binary);
 
 /// @brief Forward declaration to the @c StringSanitizer implementation.
 class StringSanitizerImpl;
 
 /// @brief Type representing the pointer to the @c StringSanitizerImpl.
-typedef boost::shared_ptr<StringSanitizerImpl> StringSanitizerImplPtr;
+using StringSanitizerImplPtr = std::shared_ptr<StringSanitizerImpl>;
 
-/// @brief Implements a regular expression based string scrubber
-///
-/// The implementation uses C++11 regex IF the environment supports it
-/// (tested in configure.ac). If not it falls back to C lib regcomp/regexec.
-/// Older compilers, such as pre Gnu g++ 4.9.0, provided only experimental
-/// implementations of regex which are recognized as buggy.
+/// @brief Implements a regular expression based string scrubber.
 class StringSanitizer {
 public:
-
     /// @brief Constructor.
     ///
     /// Compiles the given character set into a regular expression, and
@@ -324,23 +233,17 @@ public:
     /// @param char_replacement string of one or more characters to use as the
     /// replacement for invalid characters.
     ///
-    /// @throw BadValue if given an invalid regular expression
-    StringSanitizer(const std::string& char_set,
-                    const std::string& char_replacement);
-
-    /// @brief Destructor.
-    ///
-    /// Destroys the implementation instance.
-    ~StringSanitizer();
+    /// @throw BadValue if given an invalid regular expression.
+    StringSanitizer(const std::string& char_set, const std::string& char_replacement);
 
-    /// Returns a scrubbed copy of a given string
+    /// @brief Returns a scrubbed copy of a given string.
     ///
     /// Replaces all occurrences of characters described by the regular
     /// expression with the character replacement.
     ///
-    /// @param original the string to scrub
+    /// @param original The string to be scrubbed.
     ///
-    /// @throw Unexpected if an error occurs during scrubbing
+    /// @throw Unexpected if an error occurs during scrubbing.
     std::string scrub(const std::string& original);
 
     /// @brief The maximum size for regex parameters.
@@ -356,48 +259,34 @@ private:
 };
 
 /// @brief Type representing the pointer to the @c StringSanitizer.
-typedef boost::shared_ptr<StringSanitizer> StringSanitizerPtr;
+using StringSanitizerPtr = std::unique_ptr<StringSanitizer>;
 
-/// @brief Check if a string is printable
+/// @brief Check if a string is printable.
 ///
-/// @param content String to check for printable characters
+/// @param content String to check for printable characters.
 ///
-/// @return True if empty or contains only printable characters, False otherwise
-inline bool
-isPrintable(const std::string& content) {
-    for (auto const& ch : content) {
-        if (isprint(static_cast<int>(ch)) == 0) {
-            return (false);
-        }
-    }
-    return (true);
-}
+/// @return True if empty or contains only printable characters, False otherwise.
+bool
+isPrintable(const std::string& content);
 
-/// @brief Check if a byte vector is printable
+/// @brief Check if a byte vector is printable.
 ///
-/// @param content Vector to check for printable characters
+/// @param content Vector to check for printable characters.
 ///
-/// @return True if empty or contains only printable characters, False otherwise
-inline bool
-isPrintable(const std::vector<uint8_t>& content) {
-    for (auto const& ch : content) {
-        if (isprint(static_cast<int>(ch)) == 0) {
-            return (false);
-        }
-    }
-    return (true);
-}
-
+/// @return True if empty or contains only printable characters, False otherwise.
+bool
+isPrintable(const std::vector<uint8_t>& content);
 
-/// @brief Dumps a buffer of bytes as a string of hexadecimal digits
+/// @brief Dumps a buffer of bytes as a string of hexadecimal digits.
 ///
-/// @param data pointer to the data to dump
-/// @param length number of bytes to dump. Caller should ensure the length
+/// @param data Pointer to the data to dump.
+/// @param length Number of bytes to dump. Caller should ensure the length
 /// does not exceed the buffer.
-std::string dumpAsHex(const uint8_t* data, size_t length);
+std::string
+dumpAsHex(const uint8_t* data, size_t length);
 
-} // namespace str
-} // namespace util
-} // namespace isc
+}  // namespace str
+}  // namespace util
+}  // namespace isc
 
-#endif // STRUTIL_H
+#endif  // KEA_UTIL_STR_H
diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc
deleted file mode 100644 (file)
index 7c3b2e6..0000000
+++ /dev/null
@@ -1,467 +0,0 @@
-// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config.h>
-
-#include <util/encode/encode.h>
-#include <util/strutil.h>
-
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/constants.hpp>
-#include <boost/algorithm/string/split.hpp>
-
-#include <numeric>
-#include <iostream>
-#include <sstream>
-
-// Early versions of C++11 regex were buggy, use it if we
-// can otherwise, we fall back to regcomp/regexec.  For more info see:
-// https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions
-#ifdef USE_REGEX
-#include <regex>
-#else
-#include <sys/types.h>
-#include <regex.h>
-#endif
-
-#include <string.h>
-
-using namespace std;
-
-namespace isc {
-namespace util {
-namespace str {
-
-// Normalize slashes
-
-void
-normalizeSlash(std::string& name) {
-    if (!name.empty()) {
-        size_t pos = 0;
-        while ((pos = name.find('\\', pos)) != std::string::npos) {
-            name[pos] = '/';
-        }
-    }
-}
-
-// Trim String
-
-string
-trim(const string& instring) {
-    string retstring = "";
-    if (!instring.empty()) {
-        static const char* blanks = " \t\n";
-
-        // Search for first non-blank character in the string
-        size_t first = instring.find_first_not_of(blanks);
-        if (first != string::npos) {
-
-            // String not all blanks, so look for last character
-            size_t last = instring.find_last_not_of(blanks);
-
-            // Extract the trimmed substring
-            retstring = instring.substr(first, (last - first + 1));
-        }
-    }
-
-    return (retstring);
-}
-
-// Tokenize string.  As noted in the header, this is locally written to avoid
-// another dependency on a Boost library.
-
-vector<string>
-tokens(const std::string& text, const std::string& delim, bool escape) {
-    vector<string> result;
-    string token;
-    bool in_token = false;
-    bool escaped = false;
-    for (auto const& c : text) {
-        if (delim.find(c) != string::npos) {
-            // Current character is a delimiter
-            if (!in_token) {
-                // Two or more delimiters, eat them
-            } else if (escaped) {
-                // Escaped delimiter in a token: reset escaped and keep it
-                escaped = false;
-                token.push_back(c);
-            } else {
-                // End of the current token: save it if not empty
-                if (!token.empty()) {
-                    result.push_back(token);
-                }
-                // Reset state
-                in_token = false;
-                token.clear();
-            }
-        } else if (escape && (c == '\\')) {
-            // Current character is the escape character
-            if (!in_token) {
-                // The escape character is the first character of a new token
-                in_token = true;
-            }
-            if (escaped) {
-                // Escaped escape: reset escaped and keep one character
-                escaped = false;
-                token.push_back(c);
-            } else {
-                // Remember to keep the next character
-                escaped = true;
-            }
-        } else {
-            // Not a delimiter nor an escape
-            if (!in_token) {
-                // First character of a new token
-                in_token = true;
-            }
-            if (escaped) {
-                // Escaped common character: as escape was false
-                escaped = false;
-                token.push_back('\\');
-                token.push_back(c);
-            } else {
-                // The common case: keep it
-                token.push_back(c);
-            }
-        }
-    }
-    // End of input: close and save the current token if not empty
-    if (escaped) {
-        // Pending escape
-        token.push_back('\\');
-    }
-    if (!token.empty()) {
-        result.push_back(token);
-    }
-
-    return (result);
-}
-
-// Local function to pass to accumulate() for summing up string lengths.
-
-namespace {
-
-size_t
-lengthSum(string::size_type curlen, const string& cur_string) {
-    return (curlen + cur_string.size());
-}
-
-}
-
-// Provide printf-style formatting.
-
-std::string
-format(const std::string& format, const std::vector<std::string>& args) {
-
-    static const string flag = "%s";
-
-    // Initialize return string.  To speed things up, we'll reserve an
-    // appropriate amount of space - current string size, plus length of all
-    // the argument strings, less two characters for each argument (the %s in
-    // the format string is being replaced).
-    string result;
-    size_t length = accumulate(args.begin(), args.end(), format.size(),
-        lengthSum) - (args.size() * flag.size());
-    result.reserve(length);
-
-    // Iterate through replacing all tokens
-    result = format;
-    size_t tokenpos = 0;    // Position of last token replaced
-    std::vector<std::string>::size_type i = 0; // Index into argument array
-
-    while ((i < args.size()) && (tokenpos != string::npos)) {
-        tokenpos = result.find(flag, tokenpos);
-        if (tokenpos != string::npos) {
-            result.replace(tokenpos, flag.size(), args[i++]);
-        }
-    }
-
-    return (result);
-}
-
-std::string
-getToken(std::istringstream& iss) {
-    string token;
-    iss >> token;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(StringTokenError, "could not read token from string");
-    }
-    return (token);
-}
-
-std::vector<uint8_t>
-quotedStringToBinary(const std::string& quoted_string) {
-    std::vector<uint8_t> binary;
-    // Remove whitespace before and after the quotes.
-    std::string trimmed_string = trim(quoted_string);
-
-    // We require two quote characters, so the length of the string must be
-    // equal to 2 at minimum, and it must start and end with quotes.
-    if ((trimmed_string.length() > 1) && ((trimmed_string[0] == '\'') &&
-        (trimmed_string[trimmed_string.length()-1] == '\''))) {
-        // Remove quotes and trim the text inside the quotes.
-        trimmed_string = trim(trimmed_string.substr(1, trimmed_string.length() - 2));
-        // Copy string contents into the vector.
-        binary.assign(trimmed_string.begin(), trimmed_string.end());
-    }
-    // Return resulting vector or empty vector.
-    return (binary);
-}
-
-void
-decodeColonSeparatedHexString(const std::string& hex_string,
-                              std::vector<uint8_t>& binary) {
-    decodeSeparatedHexString(hex_string, ":", binary);
-}
-
-void
-decodeSeparatedHexString(const std::string& hex_string, const std::string& sep,
-                         std::vector<uint8_t>& binary) {
-    std::vector<std::string> split_text;
-    boost::split(split_text, hex_string, boost::is_any_of(sep),
-                 boost::algorithm::token_compress_off);
-
-    std::vector<uint8_t> binary_vec;
-    for (size_t i = 0; i < split_text.size(); ++i) {
-
-        // If there are multiple tokens and the current one is empty, it
-        // means that two consecutive colons were specified. This is not
-        // allowed.
-        if ((split_text.size() > 1) && split_text[i].empty()) {
-            isc_throw(isc::BadValue, "two consecutive separators ('" << sep << "') specified in"
-                      " a decoded string '" << hex_string << "'");
-
-        // Between a colon we expect at most two characters.
-        } else if (split_text[i].size() > 2) {
-            isc_throw(isc::BadValue, "invalid format of the decoded string"
-                      << " '" << hex_string << "'");
-
-        } else if (!split_text[i].empty()) {
-            std::stringstream s;
-            s << "0x";
-
-            for (unsigned int j = 0; j < split_text[i].length(); ++j) {
-                // Check if we're dealing with hexadecimal digit.
-                if (!isxdigit(split_text[i][j])) {
-                    isc_throw(isc::BadValue, "'" << split_text[i][j]
-                              << "' is not a valid hexadecimal digit in"
-                              << " decoded string '" << hex_string << "'");
-                }
-                s << split_text[i][j];
-            }
-
-            // The stream should now have one or two hexadecimal digits.
-            // Let's convert it to a number and store in a temporary
-            // vector.
-            unsigned int binary_value;
-            s >> std::hex >> binary_value;
-
-            binary_vec.push_back(static_cast<uint8_t>(binary_value));
-        }
-
-    }
-
-    // All ok, replace the data in the output vector with a result.
-    binary.swap(binary_vec);
-}
-
-
-void
-decodeFormattedHexString(const std::string& hex_string,
-                         std::vector<uint8_t>& binary) {
-    // If there is at least one colon we assume that the string
-    // comprises octets separated by colons (e.g. MAC address notation).
-    if (hex_string.find(':') != std::string::npos) {
-        decodeSeparatedHexString(hex_string, ":", binary);
-    } else if (hex_string.find(' ') != std::string::npos) {
-        decodeSeparatedHexString(hex_string, " ", binary);
-    } else {
-        std::ostringstream s;
-
-        // If we have odd number of digits we'll have to prepend '0'.
-        if (hex_string.length() % 2 != 0) {
-            s << "0";
-        }
-
-        // It is ok to use '0x' prefix in a string.
-        if ((hex_string.length() > 2) && (hex_string.substr(0, 2) == "0x")) {
-            // Exclude '0x' from the decoded string.
-            s << hex_string.substr(2);
-
-        } else {
-            // No '0x', so decode the whole string.
-            s << hex_string;
-        }
-
-        try {
-            // Decode the hex string.
-            encode::decodeHex(s.str(), binary);
-
-        } catch (...) {
-            isc_throw(isc::BadValue, "'" << hex_string << "' is not a valid"
-                      " string of hexadecimal digits");
-        }
-    }
-}
-
-class StringSanitizerImpl {
-public:
-    /// @brief Constructor.
-    StringSanitizerImpl(const std::string& char_set, const std::string& char_replacement)
-        : char_set_(char_set), char_replacement_(char_replacement) {
-        if (char_set.size() > StringSanitizer::MAX_DATA_SIZE) {
-            isc_throw(isc::BadValue, "char set size: '" << char_set.size()
-                      << "' exceeds max size: '"
-                      << StringSanitizer::MAX_DATA_SIZE << "'");
-        }
-
-        if (char_replacement.size() > StringSanitizer::MAX_DATA_SIZE) {
-            isc_throw(isc::BadValue, "char replacement size: '"
-                      << char_replacement.size() << "' exceeds max size: '"
-                      << StringSanitizer::MAX_DATA_SIZE << "'");
-        }
-#ifdef USE_REGEX
-        try {
-            scrub_exp_ = std::regex(char_set, std::regex::extended);
-        } catch (const std::exception& ex) {
-            isc_throw(isc::BadValue, "invalid regex: '"
-                      << char_set_ << "', " << ex.what());
-        }
-#else
-        int ec = regcomp(&scrub_exp_, char_set_.c_str(), REG_EXTENDED);
-        if (ec) {
-            char errbuf[512] = "";
-            static_cast<void>(regerror(ec, &scrub_exp_, errbuf, sizeof(errbuf)));
-            regfree(&scrub_exp_);
-            isc_throw(isc::BadValue, "invalid regex: '" << char_set_ << "', " << errbuf);
-        }
-#endif
-    }
-
-    /// @brief Destructor.
-    ~StringSanitizerImpl() {
-#ifndef USE_REGEX
-        regfree(&scrub_exp_);
-#endif
-    }
-
-    std::string scrub(const std::string& original) {
-#ifdef USE_REGEX
-        std::stringstream result;
-        try {
-            std::regex_replace(std::ostream_iterator<char>(result),
-                               original.begin(), original.end(),
-                               scrub_exp_, char_replacement_);
-        } catch (const std::exception& ex) {
-            isc_throw(isc::BadValue, "replacing '" << char_set_ << "' with '"
-                   << char_replacement_ << "' in '" << original << "' failed: ,"
-                   << ex.what());
-        }
-
-        return (result.str());
-#else
-        // In order to handle embedded nuls, we have to process in nul-terminated
-        // chunks.  We iterate over the original data, doing pattern replacement
-        // on each chunk.
-        const char* orig_data = original.data();
-        const char* dead_end = orig_data + original.size();
-        const char* start_from = orig_data;
-        stringstream result;
-
-        while (start_from < dead_end) {
-            // Iterate over original string, match by match.
-            regmatch_t matches[2];  // n matches + 1
-            const char* end_at = start_from + strlen(start_from);
-
-            while (start_from < end_at) {
-                // Look for the next match
-                if (regexec(&scrub_exp_, start_from, 1, matches, 0) == REG_NOMATCH) {
-                    // No matches, so add in the remainder
-                    result << start_from;
-                    start_from = end_at + 1;
-                    break;
-                }
-
-                // Shouldn't happen, but one never knows eh?
-                if (matches[0].rm_so == -1) {
-                    isc_throw(isc::Unexpected, "matched but so is -1?");
-                }
-
-                // Add everything from starting point up to the current match
-                const char* match_at = start_from + matches[0].rm_so;
-                while (start_from < match_at) {
-                    result << *start_from;
-                    ++start_from;
-                }
-
-                // Add in the replacement
-                result << char_replacement_;
-
-                // Move past the match.
-                ++start_from;
-            }
-
-            // if we have an embedded nul, replace it and continue
-            if (start_from < dead_end) {
-                // Add in the replacement
-                result << char_replacement_;
-                start_from = end_at + 1;
-            }
-        }
-
-        return (result.str());
-#endif
-    }
-
-private:
-    /// @brief The char set data for regex.
-    std::string char_set_;
-
-    /// @brief The char replacement data for regex.
-    std::string char_replacement_;
-
-#ifdef USE_REGEX
-    regex scrub_exp_;
-#else
-    regex_t scrub_exp_;
-#endif
-};
-
-// @note The regex engine is implemented using recursion and can cause
-// stack overflow if the input data is too large. An arbitrary size of
-// 4096 should be enough for all cases.
-const uint32_t StringSanitizer::MAX_DATA_SIZE = 4096;
-
-StringSanitizer::StringSanitizer(const std::string& char_set,
-                                 const std::string& char_replacement)
-    : impl_(new StringSanitizerImpl(char_set, char_replacement)) {
-}
-
-StringSanitizer::~StringSanitizer() {
-}
-
-std::string
-StringSanitizer::scrub(const std::string& original) {
-    return (impl_->scrub(original));
-}
-
-std::string dumpAsHex(const uint8_t* data, size_t length) {
-    std::stringstream output;
-    for (unsigned int i = 0; i < length; i++) {
-        if (i) {
-            output << ":";
-        }
-
-        output << std::setfill('0') << std::setw(2) << std::hex
-               << static_cast<unsigned short>(data[i]);
-    }
-
-    return (output.str());
-}
-
-} // namespace str
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/tests/str_unittests.cc b/src/lib/util/tests/str_unittests.cc
new file mode 100644 (file)
index 0000000..accaf21
--- /dev/null
@@ -0,0 +1,514 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <testutils/gtest_utils.h>
+#include <util/encode/encode.h>
+#include <util/str.h>
+
+#include <cstdint>
+#include <exception>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::util::str;
+using namespace std;
+
+namespace {
+
+/// @brief Fixture used to test StringSanitizer.
+struct StringUtilTest : ::testing::Test {
+    /// @brief Pass string through scrub and check the result.
+    ///
+    /// @param original The string to sanitize.
+    /// @param char_set The regular expression string describing invalid characters.
+    /// @param char_replacement - character(s) which replace invalid
+    /// characters
+    /// @param expected - expected sanitized string
+    void checkScrub(const string& original,
+                    const string& char_set,
+                    const string& char_replacement,
+                    const string& expected) {
+        StringSanitizerPtr ss;
+        string sanitized;
+
+        try {
+            ss.reset(new StringSanitizer(char_set, char_replacement));
+        } catch (const exception& ex) {
+            ADD_FAILURE() << "Could not construct sanitizer:" << ex.what();
+            return;
+        }
+
+        try {
+            sanitized = ss->scrub(original);
+        } catch (const exception& ex) {
+            ADD_FAILURE() << "Could not scrub string:" << ex.what();
+            return;
+        }
+
+        EXPECT_EQ(sanitized, expected);
+    }
+
+    /// @brief Check that hex strings with colons can be decoded.
+    ///
+    /// @param input Input string to be decoded.
+    /// @param reference The expected result.
+    void checkColonSeparated(const string& input, const string& reference) {
+        // Create a reference vector.
+        vector<uint8_t> reference_vector;
+        ASSERT_NO_THROW_LOG(decodeHex(reference, reference_vector));
+
+        // Fill the output vector with some garbage to make sure that
+        // the data is erased when a string is decoded successfully.
+        vector<uint8_t> decoded(1, 10);
+        ASSERT_NO_THROW_LOG(decodeColonSeparatedHexString(input, decoded));
+
+        // Get the string representation of the decoded data for logging
+        // purposes.
+        string encoded;
+        ASSERT_NO_THROW_LOG(encoded = encodeHex(decoded));
+
+        // Check if the decoded data matches the reference.
+        EXPECT_EQ(decoded, reference_vector) << "decoded data don't match the reference, input='"
+                                             << input << "', reference='" << reference
+                                             << "'"
+                                                ", decoded='"
+                                             << encoded << "'";
+    }
+
+    /// @brief Check that formatted hex strings can be decoded.
+    ///
+    /// @param input Input string to be decoded.
+    /// @param reference The expected result.
+    void checkFormatted(const string& input, const string& reference) {
+        // Create a reference vector.
+        vector<uint8_t> reference_vector;
+        ASSERT_NO_THROW_LOG(decodeHex(reference, reference_vector));
+
+        // Fill the output vector with some garbage to make sure that
+        // the data is erased when a string is decoded successfully.
+        vector<uint8_t> decoded(1, 10);
+        ASSERT_NO_THROW_LOG(decodeFormattedHexString(input, decoded));
+
+        // Get the string representation of the decoded data for logging
+        // purposes.
+        string encoded;
+        ASSERT_NO_THROW_LOG(encoded = encodeHex(decoded));
+
+        // Check if the decoded data matches the reference.
+        EXPECT_EQ(decoded, reference_vector)
+            << "decoded data don't match the reference, input='" << input << "', reference='"
+            << reference << "', decoded='" << encoded << "'";
+    }
+
+    /// @brief Convenience function which calls quotedStringToBinary
+    /// and converts returned vector back to string.
+    ///
+    /// @param s Input string.
+    ///
+    /// @return String holding a copy of a vector returned by the
+    /// quotedStringToBinary.
+    string checkQuoted(const string& s) {
+        vector<uint8_t> vec = quotedStringToBinary(s);
+        string s2(vec.begin(), vec.end());
+        return (s2);
+    }
+};
+
+// Check that leading and trailing space trimming works.
+TEST_F(StringUtilTest, Trim) {
+    // Empty and full string.
+    EXPECT_EQ("", trim(""));
+    EXPECT_EQ("abcxyz", trim("abcxyz"));
+
+    // Trim right-most blanks
+    EXPECT_EQ("ABC", trim("ABC   "));
+    EXPECT_EQ("ABC", trim("ABC\t\t  \n\t"));
+
+    // Left-most blank trimming
+    EXPECT_EQ("XYZ", trim("  XYZ"));
+    EXPECT_EQ("XYZ", trim("\t\t  \tXYZ"));
+
+    // Right and left, with embedded spaces
+    EXPECT_EQ("MN \t OP", trim("\t\tMN \t OP \t"));
+}
+
+// Check tokenization.
+TEST_F(StringUtilTest, Tokens) {
+    vector<string> result;
+
+    // Default delimiters
+
+    // Degenerate cases
+    result = tokens("");  // Empty string
+    EXPECT_EQ(0, result.size());
+
+    result = tokens(" \n ");  // String is all delimiters
+    EXPECT_EQ(0, result.size());
+
+    result = tokens("abc");  // String has no delimiters
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+
+    // String containing leading and/or trailing delimiters, no embedded ones.
+    result = tokens("\txyz");  // One leading delimiter
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = tokens("\t \nxyz");  // Multiple leading delimiters
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = tokens("xyz\n");  // One trailing delimiter
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = tokens("xyz  \t");  // Multiple trailing
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = tokens("\t xyz \n");  // Leading and trailing
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    // Embedded delimiters
+    result = tokens("abc\ndef");  // 2 tokens, one separator
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+
+    result = tokens("abc\t\t\ndef");  // 2 tokens, 3 separators
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+
+    result = tokens("abc\n  \tdef\t\tghi");
+    ASSERT_EQ(3, result.size());  // Multiple tokens, many delims
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+    EXPECT_EQ(string("ghi"), result[2]);
+
+    // Embedded and non-embedded delimiters
+
+    result = tokens("\t\t  \nabc\n  \tdef\t\tghi   \n\n");
+    ASSERT_EQ(3, result.size());  // Multiple tokens, many delims
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+    EXPECT_EQ(string("ghi"), result[2]);
+
+    // Non-default delimiter
+    result = tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+    ASSERT_EQ(6, result.size());
+    EXPECT_EQ(string("alpha"), result[0]);
+    EXPECT_EQ(string("beta"), result[1]);
+    EXPECT_EQ(string(" "), result[2]);
+    EXPECT_EQ(string("gamma"), result[3]);
+    EXPECT_EQ(string("delta"), result[4]);
+    EXPECT_EQ(string("epsilon"), result[5]);
+
+    // Non-default delimiters (plural)
+    result = tokens("+*--alpha*beta+ -gamma**delta+epsilon-+**", "*+-");
+    ASSERT_EQ(6, result.size());
+    EXPECT_EQ(string("alpha"), result[0]);
+    EXPECT_EQ(string("beta"), result[1]);
+    EXPECT_EQ(string(" "), result[2]);
+    EXPECT_EQ(string("gamma"), result[3]);
+    EXPECT_EQ(string("delta"), result[4]);
+    EXPECT_EQ(string("epsilon"), result[5]);
+
+    // Escaped delimiter
+    result = tokens("foo\\,bar", ",", true);
+    EXPECT_EQ(1, result.size());
+    EXPECT_EQ(string("foo,bar"), result[0]);
+
+    // Escaped escape
+    result = tokens("foo\\\\,bar", ",", true);
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("foo\\"), result[0]);
+    EXPECT_EQ(string("bar"), result[1]);
+
+    // Double escapes
+    result = tokens("foo\\\\\\\\,\\bar", ",", true);
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("foo\\\\"), result[0]);
+    EXPECT_EQ(string("\\bar"), result[1]);
+
+    // Escaped standard character
+    result = tokens("fo\\o,bar", ",", true);
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("fo\\o"), result[0]);
+    EXPECT_EQ(string("bar"), result[1]);
+
+    // Escape at the end
+    result = tokens("foo,bar\\", ",", true);
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("foo"), result[0]);
+    EXPECT_EQ(string("bar\\"), result[1]);
+
+    // Escape opening a token
+    result = tokens("foo,\\,,bar", ",", true);
+    ASSERT_EQ(3, result.size());
+    EXPECT_EQ(string("foo"), result[0]);
+    EXPECT_EQ(string(","), result[1]);
+    EXPECT_EQ(string("bar"), result[2]);
+}
+
+// Check changing of case.
+TEST_F(StringUtilTest, ChangeCase) {
+    string mixed("abcDEFghiJKLmno123[]{=+--+]}");
+    string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
+    string lower("abcdefghijklmno123[]{=+--+]}");
+
+    string test = mixed;
+    lowercase(test);
+    EXPECT_EQ(lower, test);
+
+    test = mixed;
+    uppercase(test);
+    EXPECT_EQ(upper, test);
+}
+
+TEST_F(StringUtilTest, quotedStringToBinary) {
+    // No opening or closing quote should result in empty string.
+    EXPECT_TRUE(quotedStringToBinary("'").empty());
+    EXPECT_TRUE(quotedStringToBinary("").empty());
+    EXPECT_TRUE(quotedStringToBinary("  ").empty());
+    EXPECT_TRUE(quotedStringToBinary("'circuit id").empty());
+    EXPECT_TRUE(quotedStringToBinary("circuit id'").empty());
+
+    // If there is only opening and closing quote, an empty
+    // vector should be returned.
+    EXPECT_TRUE(quotedStringToBinary("''").empty());
+
+    // Both opening and ending quote is present.
+    EXPECT_EQ("circuit id", checkQuoted("'circuit id'"));
+    EXPECT_EQ("remote id", checkQuoted("  ' remote id'"));
+    EXPECT_EQ("duid", checkQuoted("  ' duid'"));
+    EXPECT_EQ("duid", checkQuoted("'duid    '  "));
+    EXPECT_EQ("remote'id", checkQuoted("  ' remote'id  '"));
+    EXPECT_EQ("remote id'", checkQuoted("'remote id''"));
+    EXPECT_EQ("'remote id", checkQuoted("''remote id'"));
+
+    // Multiple quotes.
+    EXPECT_EQ("'", checkQuoted("'''"));
+    EXPECT_EQ("''", checkQuoted("''''"));
+}
+
+TEST_F(StringUtilTest, decodeColonSeparatedHexString) {
+    // Test valid strings.
+    checkColonSeparated("A1:02:C3:d4:e5:F6", "A102C3D4E5F6");
+    checkColonSeparated("A:02:3:d:E5:F6", "0A02030DE5F6");
+    checkColonSeparated("A:B:C:D", "0A0B0C0D");
+    checkColonSeparated("1", "01");
+    checkColonSeparated("1e", "1E");
+    checkColonSeparated("", "");
+
+    // Test invalid strings.
+    vector<uint8_t> decoded;
+    // Whitespaces.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("   ", decoded), BadValue,
+                     "invalid format of the decoded string '   '");
+    // Whitespace before digits.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString(" A1", decoded), BadValue,
+                     "invalid format of the decoded string ' A1'");
+    // Two consecutive colons.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("A::01", decoded), BadValue,
+                     "two consecutive separators (':') specified in a decoded string 'A::01'");
+    // Three consecutive colons.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("A:::01", decoded), BadValue,
+                     "two consecutive separators (':') specified in a decoded string 'A:::01'");
+    // Whitespace within a string.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("A :01", decoded), BadValue,
+                     "' ' is not a valid hexadecimal digit in decoded string 'A :01'");
+    // Terminating colon.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("0A:01:", decoded), BadValue,
+                     "two consecutive separators (':') specified in a decoded string '0A:01:'");
+    // Opening colon.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString(":0A:01", decoded), BadValue,
+                     "two consecutive separators (':') specified in a decoded string ':0A:01'");
+    // Three digits before the colon.
+    EXPECT_THROW_MSG(decodeColonSeparatedHexString("0A1:B1", decoded), BadValue,
+                     "invalid format of the decoded string '0A1:B1'");
+}
+
+TEST_F(StringUtilTest, decodeFormattedHexString) {
+    // Colon separated.
+    checkFormatted("1:A7:B5:4:23", "01A7B50423");
+    // Space separated.
+    checkFormatted("1 A7 B5 4 23", "01A7B50423");
+    // No colons, even number of digits.
+    checkFormatted("17a534", "17A534");
+    // Odd number of digits.
+    checkFormatted("A3A6f78", "0A3A6F78");
+    // '0x' prefix.
+    checkFormatted("0xA3A6f78", "0A3A6F78");
+    // '0x' prefix with a special value of 0.
+    checkFormatted("0x0", "00");
+    // Empty string.
+    checkFormatted("", "");
+
+    vector<uint8_t> decoded;
+    // Dangling colon.
+    EXPECT_THROW_MSG(decodeFormattedHexString("0a:", decoded), BadValue,
+                     "two consecutive separators (':') specified in a decoded string '0a:'");
+    // Dangling space.
+    EXPECT_THROW_MSG(decodeFormattedHexString("0a ", decoded), BadValue,
+                     "two consecutive separators (' ') specified in a decoded string '0a '");
+    // '0x' prefix and spaces.
+    EXPECT_THROW_MSG(decodeFormattedHexString("0x01 02", decoded), BadValue,
+                     "invalid format of the decoded string '0x01 02'");
+    // '0x' prefix and colons.
+    EXPECT_THROW_MSG(decodeFormattedHexString("0x01:02", decoded), BadValue,
+                     "invalid format of the decoded string '0x01:02'");
+    // colon and spaces mixed
+    EXPECT_THROW_MSG(decodeFormattedHexString("01:02 03", decoded), BadValue,
+                     "invalid format of the decoded string '01:02 03'");
+    // Missing colon.
+    EXPECT_THROW_MSG(decodeFormattedHexString("01:0203", decoded), BadValue,
+                     "invalid format of the decoded string '01:0203'");
+    // Missing space.
+    EXPECT_THROW_MSG(decodeFormattedHexString("01 0203", decoded), BadValue,
+                     "invalid format of the decoded string '01 0203'");
+    // Invalid prefix.
+    EXPECT_THROW_MSG(decodeFormattedHexString("x0102", decoded), BadValue,
+                     "'x0102' is not a valid string of hexadecimal digits");
+    // Invalid prefix again.
+    EXPECT_THROW_MSG(decodeFormattedHexString("1x0102", decoded), BadValue,
+                     "'1x0102' is not a valid string of hexadecimal digits");
+}
+
+// Verifies StringSantizer class
+TEST_F(StringUtilTest, stringSanitizer) {
+    // Bad regular expression should throw.
+    StringSanitizerPtr ss;
+    ASSERT_THROW_MSG(ss.reset(new StringSanitizer("[bogus-regex", "")), BadValue,
+                     "invalid regex: '[bogus-regex', Invalid range in bracket expression.");
+
+    string good_data(StringSanitizer::MAX_DATA_SIZE, '0');
+    string bad_data(StringSanitizer::MAX_DATA_SIZE + 1, '0');
+
+    ASSERT_NO_THROW_LOG(ss.reset(new StringSanitizer(good_data, good_data)));
+
+    ASSERT_THROW_MSG(ss.reset(new StringSanitizer(bad_data, "")), BadValue,
+                     "char set size: '4097' exceeds max size: '4096'");
+    ASSERT_THROW_MSG(ss.reset(new StringSanitizer("", bad_data)), BadValue,
+                     "char replacement size: '4097' exceeds max size: '4096'");
+
+    // List of invalid chars should work: (b,c,2 are invalid)
+    checkScrub("abc.123", "[b-c2]", "*", "a**.1*3");
+    // Inverted list of valid chars should work: (b,c,2 are valid)
+    checkScrub("abc.123", "[^b-c2]", "*", "*bc**2*");
+
+    // A string of all valid chars should return an identical string.
+    checkScrub("-_A--B__Cabc34567_-", "[^A-Ca-c3-7_-]", "x", "-_A--B__Cabc34567_-");
+
+    // Replacing with a character should work.
+    checkScrub("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "*", "A*b*c*JoE3*_x*B*Y*e");
+
+    // Removing (i.e.replacing with an "empty" string) should work.
+    checkScrub("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "", "AbcJoE3_xBYe");
+
+    // More than one non-matching in a row should work.
+    checkScrub("%%A%%B%%C%%", "[^A-Za-z0-9_]", "x", "xxAxxBxxCxx");
+
+    // Removing more than one non-matching in a row should work.
+    checkScrub("%%A%%B%%C%%", "[^A-Za-z0-9_]", "", "ABC");
+
+    // Replacing with a string should work.
+    checkScrub("%%A%%B%%C%%", "[^A-Za-z0-9_]", "xyz", "xyzxyzAxyzxyzBxyzxyzCxyzxyz");
+
+    // Dots as valid chars work.
+    checkScrub("abc.123", "[^A-Za-z0-9_.]", "*", "abc.123");
+
+    string withNulls("\000ab\000c.12\0003", 10);
+    checkScrub(withNulls, "[^A-Za-z0-9_.]", "*", "*ab*c.12*3");
+}
+
+// Verifies templated buffer iterator seekTrimmed() function
+TEST_F(StringUtilTest, seekTrimmed) {
+    // Empty buffer should be fine.
+    vector<uint8_t> buffer;
+    auto begin = buffer.end();
+    auto end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 0));
+    EXPECT_EQ(0, distance(begin, end));
+
+    // Buffer of only trim values, should be fine.
+    buffer = {1, 1};
+    begin = buffer.begin();
+    end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 1));
+    EXPECT_EQ(0, distance(begin, end));
+
+    // One trailing null should trim off.
+    buffer = {'o', 'n', 'e', 0};
+    begin = buffer.begin();
+    end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 0));
+    EXPECT_EQ(3, distance(begin, end));
+
+    // More than one trailing null should trim off.
+    buffer = {'t', 'h', 'r', 'e', 'e', 0, 0, 0};
+    begin = buffer.begin();
+    end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 0));
+    EXPECT_EQ(5, distance(begin, end));
+
+    // Embedded null should be left in place.
+    buffer = {'e', 'm', 0, 'b', 'e', 'd'};
+    begin = buffer.begin();
+    end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 0));
+    EXPECT_EQ(6, distance(begin, end));
+
+    // Leading null should be left in place.
+    buffer = {0, 'l', 'e', 'a', 'd', 'i', 'n', 'g'};
+    begin = buffer.begin();
+    end = buffer.end();
+    ASSERT_NO_THROW_LOG(end = seekTrimmed(begin, end, 0));
+    EXPECT_EQ(8, distance(begin, end));
+}
+
+// Verifies isPrintable predicate on strings.
+TEST_F(StringUtilTest, stringIsPrintable) {
+    string content;
+
+    // Empty is printable.
+    EXPECT_TRUE(isPrintable(content));
+
+    // Check Abcd.
+    content = "Abcd";
+    EXPECT_TRUE(isPrintable(content));
+
+    // Add a control character (not printable).
+    content += "\a";
+    EXPECT_FALSE(isPrintable(content));
+}
+
+// Verifies isPrintable predicate on byte vectors.
+TEST_F(StringUtilTest, vectorIsPrintable) {
+    vector<uint8_t> content;
+
+    // Empty is printable.
+    EXPECT_TRUE(isPrintable(content));
+
+    // Check Abcd.
+    content = {0x41, 0x62, 0x63, 0x64};
+    EXPECT_TRUE(isPrintable(content));
+
+    // Add a control character (not printable).
+    content.push_back('\a');
+    EXPECT_FALSE(isPrintable(content));
+}
+
+}  // namespace
diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc
deleted file mode 100644 (file)
index 5372ba0..0000000
+++ /dev/null
@@ -1,642 +0,0 @@
-// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config.h>
-
-#include <exceptions/exceptions.h>
-#include <util/strutil.h>
-#include <util/encode/encode.h>
-
-#include <gtest/gtest.h>
-
-#include <stdint.h>
-#include <string>
-
-using namespace isc;
-using namespace isc::util;
-using namespace isc::util::str;
-using namespace std;
-
-namespace {
-
-// Check for slash replacement
-
-TEST(StringUtilTest, Slash) {
-
-    string instring = "";
-    isc::util::str::normalizeSlash(instring);
-    EXPECT_EQ("", instring);
-
-    instring = "C:\\A\\B\\C.D";
-    isc::util::str::normalizeSlash(instring);
-    EXPECT_EQ("C:/A/B/C.D", instring);
-
-    instring = "// \\ //";
-    isc::util::str::normalizeSlash(instring);
-    EXPECT_EQ("// / //", instring);
-}
-
-// Check that leading and trailing space trimming works
-
-TEST(StringUtilTest, Trim) {
-
-    // Empty and full string.
-    EXPECT_EQ("", isc::util::str::trim(""));
-    EXPECT_EQ("abcxyz", isc::util::str::trim("abcxyz"));
-
-    // Trim right-most blanks
-    EXPECT_EQ("ABC", isc::util::str::trim("ABC   "));
-    EXPECT_EQ("ABC", isc::util::str::trim("ABC\t\t  \n\t"));
-
-    // Left-most blank trimming
-    EXPECT_EQ("XYZ", isc::util::str::trim("  XYZ"));
-    EXPECT_EQ("XYZ", isc::util::str::trim("\t\t  \tXYZ"));
-
-    // Right and left, with embedded spaces
-    EXPECT_EQ("MN \t OP", isc::util::str::trim("\t\tMN \t OP \t"));
-}
-
-// Check tokenization.  Note that ASSERT_EQ is used to check the size of the
-// returned vector; if not as expected, the following references may be invalid
-// so should not be used.
-
-TEST(StringUtilTest, Tokens) {
-    vector<string>  result;
-
-    // Default delimiters
-
-    // Degenerate cases
-    result = isc::util::str::tokens("");          // Empty string
-    EXPECT_EQ(0, result.size());
-
-    result = isc::util::str::tokens(" \n ");      // String is all delimiters
-    EXPECT_EQ(0, result.size());
-
-    result = isc::util::str::tokens("abc");       // String has no delimiters
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("abc"), result[0]);
-
-    // String containing leading and/or trailing delimiters, no embedded ones.
-    result = isc::util::str::tokens("\txyz");     // One leading delimiter
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("xyz"), result[0]);
-
-    result = isc::util::str::tokens("\t \nxyz");  // Multiple leading delimiters
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("xyz"), result[0]);
-
-    result = isc::util::str::tokens("xyz\n");     // One trailing delimiter
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("xyz"), result[0]);
-
-    result = isc::util::str::tokens("xyz  \t");   // Multiple trailing
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("xyz"), result[0]);
-
-    result = isc::util::str::tokens("\t xyz \n"); // Leading and trailing
-    ASSERT_EQ(1, result.size());
-    EXPECT_EQ(string("xyz"), result[0]);
-
-    // Embedded delimiters
-    result = isc::util::str::tokens("abc\ndef");  // 2 tokens, one separator
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("abc"), result[0]);
-    EXPECT_EQ(string("def"), result[1]);
-
-    result = isc::util::str::tokens("abc\t\t\ndef");  // 2 tokens, 3 separators
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("abc"), result[0]);
-    EXPECT_EQ(string("def"), result[1]);
-
-    result = isc::util::str::tokens("abc\n  \tdef\t\tghi");
-    ASSERT_EQ(3, result.size());                // Multiple tokens, many delims
-    EXPECT_EQ(string("abc"), result[0]);
-    EXPECT_EQ(string("def"), result[1]);
-    EXPECT_EQ(string("ghi"), result[2]);
-
-    // Embedded and non-embedded delimiters
-
-    result = isc::util::str::tokens("\t\t  \nabc\n  \tdef\t\tghi   \n\n");
-    ASSERT_EQ(3, result.size());                // Multiple tokens, many delims
-    EXPECT_EQ(string("abc"), result[0]);
-    EXPECT_EQ(string("def"), result[1]);
-    EXPECT_EQ(string("ghi"), result[2]);
-
-    // Non-default delimiter
-    result = isc::util::str::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
-    ASSERT_EQ(6, result.size());
-    EXPECT_EQ(string("alpha"), result[0]);
-    EXPECT_EQ(string("beta"), result[1]);
-    EXPECT_EQ(string(" "), result[2]);
-    EXPECT_EQ(string("gamma"), result[3]);
-    EXPECT_EQ(string("delta"), result[4]);
-    EXPECT_EQ(string("epsilon"), result[5]);
-
-    // Non-default delimiters (plural)
-    result = isc::util::str::tokens("+*--alpha*beta+ -gamma**delta+epsilon-+**",
-        "*+-");
-    ASSERT_EQ(6, result.size());
-    EXPECT_EQ(string("alpha"), result[0]);
-    EXPECT_EQ(string("beta"), result[1]);
-    EXPECT_EQ(string(" "), result[2]);
-    EXPECT_EQ(string("gamma"), result[3]);
-    EXPECT_EQ(string("delta"), result[4]);
-    EXPECT_EQ(string("epsilon"), result[5]);
-
-    // Escaped delimiter
-    result = isc::util::str::tokens("foo\\,bar", ",", true);
-    EXPECT_EQ(1, result.size());
-    EXPECT_EQ(string("foo,bar"), result[0]);
-
-    // Escaped escape
-    result = isc::util::str::tokens("foo\\\\,bar", ",", true);
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("foo\\"), result[0]);
-    EXPECT_EQ(string("bar"), result[1]);
-
-    // Double escapes
-    result = isc::util::str::tokens("foo\\\\\\\\,\\bar", ",", true);
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("foo\\\\"), result[0]);
-    EXPECT_EQ(string("\\bar"), result[1]);
-
-    // Escaped standard character
-    result = isc::util::str::tokens("fo\\o,bar", ",", true);
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("fo\\o"), result[0]);
-    EXPECT_EQ(string("bar"), result[1]);
-
-    // Escape at the end
-    result = isc::util::str::tokens("foo,bar\\", ",", true);
-    ASSERT_EQ(2, result.size());
-    EXPECT_EQ(string("foo"), result[0]);
-    EXPECT_EQ(string("bar\\"), result[1]);
-
-    // Escape opening a token
-    result = isc::util::str::tokens("foo,\\,,bar", ",", true);
-    ASSERT_EQ(3, result.size());
-    EXPECT_EQ(string("foo"), result[0]);
-    EXPECT_EQ(string(","), result[1]);
-    EXPECT_EQ(string("bar"), result[2]);
-}
-
-// Changing case
-
-TEST(StringUtilTest, ChangeCase) {
-    string mixed("abcDEFghiJKLmno123[]{=+--+]}");
-    string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
-    string lower("abcdefghijklmno123[]{=+--+]}");
-
-    string test = mixed;
-    isc::util::str::lowercase(test);
-    EXPECT_EQ(lower, test);
-
-    test = mixed;
-    isc::util::str::uppercase(test);
-    EXPECT_EQ(upper, test);
-}
-
-// Formatting
-
-TEST(StringUtilTest, Formatting) {
-
-    vector<string> args;
-    args.push_back("arg1");
-    args.push_back("arg2");
-    args.push_back("arg3");
-
-    string format1 = "This is a string with no tokens";
-    EXPECT_EQ(format1, isc::util::str::format(format1, args));
-
-    string format2 = "";    // Empty string
-    EXPECT_EQ(format2, isc::util::str::format(format2, args));
-
-    string format3 = "   ";    // Empty string
-    EXPECT_EQ(format3, isc::util::str::format(format3, args));
-
-    string format4 = "String with %d non-string tokens %lf";
-    EXPECT_EQ(format4, isc::util::str::format(format4, args));
-
-    string format5 = "String with %s correct %s number of tokens %s";
-    string result5 = "String with arg1 correct arg2 number of tokens arg3";
-    EXPECT_EQ(result5, isc::util::str::format(format5, args));
-
-    string format6 = "String with %s too %s few tokens";
-    string result6 = "String with arg1 too arg2 few tokens";
-    EXPECT_EQ(result6, isc::util::str::format(format6, args));
-
-    string format7 = "String with %s too %s many %s tokens %s !";
-    string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
-    EXPECT_EQ(result7, isc::util::str::format(format7, args));
-
-    string format8 = "String with embedded%s%s%stokens";
-    string result8 = "String with embeddedarg1arg2arg3tokens";
-    EXPECT_EQ(result8, isc::util::str::format(format8, args));
-
-    // Handle an empty vector
-    args.clear();
-    string format9 = "%s %s";
-    EXPECT_EQ(format9, isc::util::str::format(format9, args));
-}
-
-TEST(StringUtilTest, getToken) {
-    string s("a b c");
-    istringstream ss(s);
-    EXPECT_EQ("a", isc::util::str::getToken(ss));
-    EXPECT_EQ("b", isc::util::str::getToken(ss));
-    EXPECT_EQ("c", isc::util::str::getToken(ss));
-    EXPECT_THROW(isc::util::str::getToken(ss), isc::util::str::StringTokenError);
-}
-
-int32_t tokenToNumCall_32_16(const string& token) {
-    return isc::util::str::tokenToNum<int32_t, 16>(token);
-}
-
-int16_t tokenToNumCall_16_8(const string& token) {
-    return isc::util::str::tokenToNum<int16_t, 8>(token);
-}
-
-TEST(StringUtilTest, tokenToNum) {
-    uint32_t num32 = tokenToNumCall_32_16("0");
-    EXPECT_EQ(0, num32);
-    num32 = tokenToNumCall_32_16("123");
-    EXPECT_EQ(123, num32);
-    num32 = tokenToNumCall_32_16("65535");
-    EXPECT_EQ(65535, num32);
-
-    EXPECT_THROW(tokenToNumCall_32_16(""),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_32_16("a"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_32_16("-1"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_32_16("65536"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_32_16("1234567890"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_32_16("-1234567890"),
-                 isc::util::str::StringTokenError);
-
-    uint16_t num16 = tokenToNumCall_16_8("123");
-    EXPECT_EQ(123, num16);
-    num16 = tokenToNumCall_16_8("0");
-    EXPECT_EQ(0, num16);
-    num16 = tokenToNumCall_16_8("255");
-    EXPECT_EQ(255, num16);
-
-    EXPECT_THROW(tokenToNumCall_16_8(""),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_16_8("a"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_16_8("-1"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_16_8("256"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_16_8("1234567890"),
-                 isc::util::str::StringTokenError);
-    EXPECT_THROW(tokenToNumCall_16_8("-1234567890"),
-                 isc::util::str::StringTokenError);
-
-}
-
-/// @brief Convenience function which calls quotedStringToBinary
-/// and converts returned vector back to string.
-///
-/// @param s Input string.
-/// @return String holding a copy of a vector returned by the
-/// quotedStringToBinary.
-std::string testQuoted(const std::string& s) {
-    std::vector<uint8_t> vec = str::quotedStringToBinary(s);
-    std::string s2(vec.begin(), vec.end());
-    return (s2);
-}
-
-TEST(StringUtilTest, quotedStringToBinary) {
-    // No opening or closing quote should result in empty string.
-    EXPECT_TRUE(str::quotedStringToBinary("'").empty());
-    EXPECT_TRUE(str::quotedStringToBinary("").empty());
-    EXPECT_TRUE(str::quotedStringToBinary("  ").empty());
-    EXPECT_TRUE(str::quotedStringToBinary("'circuit id").empty());
-    EXPECT_TRUE(str::quotedStringToBinary("circuit id'").empty());
-
-    // If there is only opening and closing quote, an empty
-    // vector should be returned.
-    EXPECT_TRUE(str::quotedStringToBinary("''").empty());
-
-    // Both opening and ending quote is present.
-    EXPECT_EQ("circuit id", testQuoted("'circuit id'"));
-    EXPECT_EQ("remote id", testQuoted("  ' remote id'"));
-    EXPECT_EQ("duid", testQuoted("  ' duid'"));
-    EXPECT_EQ("duid", testQuoted("'duid    '  "));
-    EXPECT_EQ("remote'id", testQuoted("  ' remote'id  '"));
-    EXPECT_EQ("remote id'", testQuoted("'remote id''"));
-    EXPECT_EQ("'remote id", testQuoted("''remote id'"));
-
-    // Multiple quotes.
-    EXPECT_EQ("'", testQuoted("'''"));
-    EXPECT_EQ("''", testQuoted("''''"));
-}
-
-/// @brief Test that hex string with colons can be decoded.
-///
-/// @param input Input string to be decoded.
-/// @param reference A string without colons representing the
-/// decoded data.
-void testColonSeparated(const std::string& input,
-                        const std::string& reference) {
-    // Create a reference vector.
-    std::vector<uint8_t> reference_vector;
-    ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
-
-    // Fill the output vector with some garbage to make sure that
-    // the data is erased when a string is decoded successfully.
-    std::vector<uint8_t> decoded(1, 10);
-    ASSERT_NO_THROW(decodeColonSeparatedHexString(input, decoded));
-
-    // Get the string representation of the decoded data for logging
-    // purposes.
-    std::string encoded;
-    ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
-
-    // Check if the decoded data matches the reference.
-    EXPECT_TRUE(decoded == reference_vector)
-        << "decoded data don't match the reference, input='"
-        << input << "', reference='" << reference << "'"
-        ", decoded='" << encoded << "'";
-}
-
-TEST(StringUtilTest, decodeColonSeparatedHexString) {
-    // Test valid strings.
-    testColonSeparated("A1:02:C3:d4:e5:F6", "A102C3D4E5F6");
-    testColonSeparated("A:02:3:d:E5:F6", "0A02030DE5F6");
-    testColonSeparated("A:B:C:D", "0A0B0C0D");
-    testColonSeparated("1", "01");
-    testColonSeparated("1e", "1E");
-    testColonSeparated("", "");
-
-    // Test invalid strings.
-    std::vector<uint8_t> decoded;
-    // Whitespaces.
-    EXPECT_THROW(decodeColonSeparatedHexString("   ", decoded),
-                 isc::BadValue);
-    // Whitespace before digits.
-    EXPECT_THROW(decodeColonSeparatedHexString(" A1", decoded),
-                 isc::BadValue);
-    // Two consecutive colons.
-    EXPECT_THROW(decodeColonSeparatedHexString("A::01", decoded),
-                 isc::BadValue);
-    // Three consecutive colons.
-    EXPECT_THROW(decodeColonSeparatedHexString("A:::01", decoded),
-                 isc::BadValue);
-    // Whitespace within a string.
-    EXPECT_THROW(decodeColonSeparatedHexString("A :01", decoded),
-                 isc::BadValue);
-    // Terminating colon.
-    EXPECT_THROW(decodeColonSeparatedHexString("0A:01:", decoded),
-                 isc::BadValue);
-    // Opening colon.
-    EXPECT_THROW(decodeColonSeparatedHexString(":0A:01", decoded),
-                 isc::BadValue);
-    // Three digits before the colon.
-    EXPECT_THROW(decodeColonSeparatedHexString("0A1:B1", decoded),
-                 isc::BadValue);
-}
-
-void testFormatted(const std::string& input,
-                   const std::string& reference) {
-    // Create a reference vector.
-    std::vector<uint8_t> reference_vector;
-    ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
-
-    // Fill the output vector with some garbage to make sure that
-    // the data is erased when a string is decoded successfully.
-    std::vector<uint8_t> decoded(1, 10);
-    ASSERT_NO_THROW(decodeFormattedHexString(input, decoded));
-
-    // Get the string representation of the decoded data for logging
-    // purposes.
-    std::string encoded;
-    ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
-
-    // Check if the decoded data matches the reference.
-    EXPECT_TRUE(decoded == reference_vector)
-        << "decoded data don't match the reference, input='"
-        << input << "', reference='" << reference << "'"
-        ", decoded='" << encoded << "'";
-}
-
-TEST(StringUtilTest, decodeFormattedHexString) {
-    // Colon separated.
-    testFormatted("1:A7:B5:4:23", "01A7B50423");
-    // Space separated.
-    testFormatted("1 A7 B5 4 23", "01A7B50423");
-    // No colons, even number of digits.
-    testFormatted("17a534", "17A534");
-    // Odd number of digits.
-    testFormatted("A3A6f78", "0A3A6F78");
-    // '0x' prefix.
-    testFormatted("0xA3A6f78", "0A3A6F78");
-    // '0x' prefix with a special value of 0.
-    testFormatted("0x0", "00");
-    // Empty string.
-    testFormatted("", "");
-
-    std::vector<uint8_t> decoded;
-    // Dangling colon.
-    EXPECT_THROW(decodeFormattedHexString("0a:", decoded),
-                 isc::BadValue);
-    // Dangling space.
-    EXPECT_THROW(decodeFormattedHexString("0a ", decoded),
-                 isc::BadValue);
-    // '0x' prefix and spaces.
-    EXPECT_THROW(decodeFormattedHexString("0x01 02", decoded),
-                 isc::BadValue);
-    // '0x' prefix and colons.
-    EXPECT_THROW(decodeFormattedHexString("0x01:02", decoded),
-                 isc::BadValue);
-    // colon and spaces mixed
-    EXPECT_THROW(decodeFormattedHexString("01:02 03", decoded),
-                 isc::BadValue);
-    // Missing colon.
-    EXPECT_THROW(decodeFormattedHexString("01:0203", decoded),
-                 isc::BadValue);
-    // Missing space.
-    EXPECT_THROW(decodeFormattedHexString("01 0203", decoded),
-                 isc::BadValue);
-    // Invalid prefix.
-    EXPECT_THROW(decodeFormattedHexString("x0102", decoded),
-                 isc::BadValue);
-    // Invalid prefix again.
-    EXPECT_THROW(decodeFormattedHexString("1x0102", decoded),
-                 isc::BadValue);
-}
-
-/// @brief Function used to test StringSantitizer
-/// @param original - string to sanitize
-/// @param char_set - regular expression string describing invalid
-/// characters
-/// @param char_replacement - character(s) which replace invalid
-/// characters
-/// @param expected - expected sanitized string
-void sanitizeStringTest(
-    const std::string& original,
-    const std::string& char_set,
-    const std::string& char_replacement,
-    const std::string& expected) {
-
-    StringSanitizerPtr ss;
-    std::string sanitized;
-
-    try {
-        ss.reset(new StringSanitizer(char_set, char_replacement));
-    } catch (const std::exception& ex) {
-        ADD_FAILURE() << "Could not construct sanitizer:" << ex.what();
-        return;
-    }
-
-    try {
-        sanitized = ss->scrub(original);
-    } catch (const std::exception& ex) {
-        ADD_FAILURE() << "Could not scrub string:" << ex.what();
-        return;
-    }
-
-    EXPECT_EQ(sanitized, expected);
-}
-
-// Verifies StringSantizer class
-TEST(StringUtilTest, stringSanitizer) {
-    // Bad regular expression should throw.
-    StringSanitizerPtr ss;
-    ASSERT_THROW(ss.reset(new StringSanitizer("[bogus-regex","")), BadValue);
-
-    std::string good_data(StringSanitizer::MAX_DATA_SIZE, '0');
-    std::string bad_data(StringSanitizer::MAX_DATA_SIZE + 1, '0');
-
-    ASSERT_NO_THROW(ss.reset(new StringSanitizer(good_data, good_data)));
-
-    ASSERT_THROW(ss.reset(new StringSanitizer(bad_data, "")), BadValue);
-    ASSERT_THROW(ss.reset(new StringSanitizer("", bad_data)), BadValue);
-
-    // List of invalid chars should work: (b,c,2 are invalid)
-    sanitizeStringTest("abc.123", "[b-c2]", "*",
-                       "a**.1*3");
-    // Inverted list of valid chars should work: (b,c,2 are valid)
-    sanitizeStringTest("abc.123", "[^b-c2]", "*",
-                       "*bc**2*");
-
-    // A string of all valid chars should return an identical string.
-    sanitizeStringTest("-_A--B__Cabc34567_-", "[^A-Ca-c3-7_-]", "x",
-                       "-_A--B__Cabc34567_-");
-
-    // Replacing with a character should work.
-    sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "*",
-                       "A*b*c*JoE3*_x*B*Y*e");
-
-    // Removing (i.e.replacing with an "empty" string) should work.
-    sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "",
-                       "AbcJoE3_xBYe");
-
-    // More than one non-matching in a row should work.
-    sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "x",
-                       "xxAxxBxxCxx");
-
-    // Removing more than one non-matching in a row should work.
-    sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "",
-                       "ABC");
-
-    // Replacing with a string should work.
-    sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "xyz",
-                       "xyzxyzAxyzxyzBxyzxyzCxyzxyz");
-
-    // Dots as valid chars work.
-    sanitizeStringTest("abc.123", "[^A-Za-z0-9_.]", "*",
-                       "abc.123");
-
-    std::string withNulls("\000ab\000c.12\0003",10);
-    sanitizeStringTest(withNulls, "[^A-Za-z0-9_.]", "*",
-                       "*ab*c.12*3");
-}
-
-// Verifies templated buffer iterator seekTrimmed() function
-TEST(StringUtilTest, seekTrimmed) {
-
-    // Empty buffer should be fine.
-    std::vector<uint8_t> buffer;
-    auto begin = buffer.end();
-    auto end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
-    EXPECT_EQ(0, std::distance(begin, end));
-
-    // Buffer of only trim values, should be fine.
-    buffer = { 1, 1 };
-    begin = buffer.begin();
-    end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 1));
-    EXPECT_EQ(0, std::distance(begin, end));
-
-    // One trailing null should trim off.
-    buffer = {'o', 'n', 'e', 0 };
-    begin = buffer.begin();
-    end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
-    EXPECT_EQ(3, std::distance(begin, end));
-
-    // More than one trailing null should trim off.
-    buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
-    begin = buffer.begin();
-    end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
-    EXPECT_EQ(5, std::distance(begin, end));
-
-    // Embedded null should be left in place.
-    buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
-    begin = buffer.begin();
-    end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
-    EXPECT_EQ(6, std::distance(begin, end));
-
-    // Leading null should be left in place.
-    buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
-    begin = buffer.begin();
-    end = buffer.end();
-    ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
-    EXPECT_EQ(8, std::distance(begin, end));
-}
-
-// Verifies isPrintable predicate on strings.
-TEST(StringUtilTest, stringIsPrintable) {
-    string content;
-
-    // Empty is printable.
-    EXPECT_TRUE(isPrintable(content));
-
-    // Check Abcd.
-    content = "Abcd";
-    EXPECT_TRUE(isPrintable(content));
-
-    // Add a control character (not printable).
-    content += "\a";
-    EXPECT_FALSE(isPrintable(content));
-}
-
-// Verifies isPrintable predicate on byte vectors.
-TEST(StringUtilTest, vectorIsPrintable) {
-    vector<uint8_t> content;
-
-    // Empty is printable.
-    EXPECT_TRUE(isPrintable(content));
-
-    // Check Abcd.
-    content = { 0x41, 0x62, 0x63, 0x64 };
-    EXPECT_TRUE(isPrintable(content));
-
-    // Add a control character (not printable).
-    content.push_back('\a');
-    EXPECT_FALSE(isPrintable(content));
-}
-
-} // end of anonymous namespace
index 78f427e651f7f3a91d9d7260e7e2e4aa3a640b60..931696f2149e069cabb983378e094139eef356ab 100644 (file)
@@ -7,7 +7,7 @@
 #include <config.h>
 
 #include <util/encode/encode.h>
-#include <util/strutil.h>
+#include <util/str.h>
 #include <yang/adaptor_host.h>
 
 #include <iomanip>