because the parameters specified for the library (or the files those
parameters point to) may have changed.
+As of Kea 2.6.2, hook libraries may only be loaded from the default installation
+directory determined during compilation and shown in the config report as
+"Hooks directory". This value may be overridden at startup by setting the
+environment variable ``KEA_HOOKS_PATH`` to the desired path. If a path other
+than this value is used in a ``library`` element Kea will emit an error and refuse
+to load the library. For ease of use ``library`` elements may simply omit path
+components, specifying the file name only as shown below:
+
+.. code-block:: json
+
+ {
+ "Dhcp6": {
+ "hooks-libraries": [
+ {
+ "library": "first_custom_hooks_example.so"
+ },
+ {
+ "library": "second_custom_hooks_example.so"
+ }
+ ]
+ }
+ }
+
+This snippet (on Debian 12) is equivalent to:
+
+.. code-block:: json
+
+ {
+ "Dhcp6": {
+ "hooks-libraries": [
+ {
+ "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/first_custom_hooks_example.so"
+ },
+ {
+ "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/second_custom_hooks_example.so"
+ }
+ ]
+ }
+ }
+
Libraries may have additional parameters that are not mandatory, in the
sense that there may be libraries that do not require them. However, for any
given library there is often a requirement to specify a certain
-// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2025 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
#include <process/testutils/d_test_stubs.h>
#include <process/d_cfg_mgr.h>
#include <http/basic_auth_config.h>
+#include <hooks/hooks_parser.h>
#include <agent/tests/test_callout_libraries.h>
#include <agent/tests/test_data_files_config.h>
#include <boost/pointer_cast.hpp>
/// @brief Class used for testing CfgMgr
class AgentParserTest : public isc::process::ConfigParseTest {
public:
+ /// @brief Constructor.
+ AgentParserTest() {
+ resetHooksPath();
+ }
+
+ /// @brief Destructor.
+ virtual ~AgentParserTest() {
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : CA_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
+ }
/// @brief Tries to load input text as a configuration
///
// name. In particular, it checks if such a library exists. Therefore we
// can't use AGENT_CONFIGS[4] as is, but need to run it through path replacer.
TEST_F(AgentParserTest, configParseHooks) {
+ setHooksTestPath();
+
// Create the configuration with proper lib path.
std::string cfg = pathReplacer(AGENT_CONFIGS[4], CALLOUT_LIBRARY);
// The configuration should be successful.
CFG_FILE="@abs_top_builddir@/src/bin/agent/tests/test_config.json"
# Path to the Control Agent log file.
LOG_FILE="@abs_top_builddir@/src/bin/agent/tests/test.log"
+# Set env KEA_HOOKS_PATH to override DEFAULT_HOOKS_PATH
+export KEA_HOOKS_PATH="@abs_top_builddir@/src/bin/agent/tests/.libs"
# Control Agent configuration to be stored in the configuration file.
CONFIG="{
#include <process/testutils/d_test_stubs.h>
#include <agent/ca_cfg_mgr.h>
#include <agent/parser_context.h>
+#include <hooks/hooks_parser.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
using namespace isc::config;
using namespace isc::data;
using namespace isc::process;
+using namespace isc::hooks;
using namespace isc::test;
namespace {
public:
CtrlAgentGetCfgTest()
: rcode_(-1) {
+ resetHooksPath();
srv_.reset(new NakedAgentCfgMgr());
// Create fresh context.
resetConfiguration();
~CtrlAgentGetCfgTest() {
resetConfiguration();
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : CA_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
}
/// @brief Parse and Execute configuration
/// Test a configuration
TEST_F(CtrlAgentGetCfgTest, simple) {
+ setHooksTestPath();
// get the simple configuration
std::string simple_file = string(CFG_EXAMPLES) + "/" + "simple.json";
// Basic callout library with context_create and three "standard" callouts.
static const char* CALLOUT_LIBRARY = "@abs_builddir@/.libs/libcallout.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* CA_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
} // anonymous namespace
#endif // TEST_LIBRARIES_H
#include <d2srv/d2_config.h>
#include <d2srv/d2_simple_parser.h>
#include <dhcpsrv/testutils/config_result_check.h>
+#include <hooks/hooks_parser.h>
#include <process/testutils/d_test_stubs.h>
#include <test_data_files_config.h>
#include <util/encode/encode.h>
/// @brief Constructor
D2CfgMgrTest():cfg_mgr_(new D2CfgMgr()), d2_params_() {
+ resetHooksPath();
}
/// @brief Destructor
~D2CfgMgrTest() {
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : D2_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
}
/// @brief Configuration manager instance.
/// as it would be done by d2_process in response to a configuration update
/// event.
TEST_F(D2CfgMgrTest, fullConfig) {
+ setHooksTestPath();
+
// Create a configuration with all of application level parameters, plus
// both the forward and reverse ddns managers. Both managers have two
// domains with three servers per domain.
# Path to the D2 log file.
LOG_FILE="@abs_top_builddir@/src/bin/d2/tests/test.log"
# D2 configuration to be stored in the configuration file.
+# Set env KEA_HOOKS_PATH to override DEFAULT_HOOKS_PATH
+export KEA_HOOKS_PATH="@abs_top_builddir@/src/bin/d2/tests/.libs"
CONFIG="{
\"DhcpDdns\":
{
-// Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2025 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
#include <d2/tests/test_configured_libraries.h>
#include <dhcp_ddns/ncr_io.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <process/testutils/d_test_stubs.h>
#include <boost/date_time/posix_time/posix_time.hpp>
D2ProcessTest() :
D2Process("d2test",
asiolink::IOServicePtr(new isc::asiolink::IOService())) {
+ resetHooksPath();
HooksManager::setTestMode(false);
D2Controller::instance();
}
/// @brief Destructor
virtual ~D2ProcessTest() {
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : D2_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
}
/// @brief Callback that will invoke shutdown method.
/// @brief Check the configured callout (positive case).
TEST_F(D2ProcessTest, configuredNoFail) {
+ setHooksTestPath();
const char* config = "{\n"
"\"hooks-libraries\": [ {\n"
" \"library\": \"%LIBRARY%\",\n"
/// @brief Check the configured callout (negative case).
TEST_F(D2ProcessTest, configuredFail) {
+ setHooksTestPath();
const char* config = "{\n"
"\"user-context\": { \"error\": \"Fail!\" },\n"
"\"hooks-libraries\": [ {\n"
#include <d2/parser_context.h>
#include <d2srv/d2_cfg_mgr.h>
#include <d2srv/d2_config.h>
+#include <hooks/hooks_parser.h>
#include <process/testutils/d_test_stubs.h>
#include <testutils/user_context_utils.h>
#include <gtest/gtest.h>
using namespace isc::data;
using namespace isc::process;
using namespace isc::test;
+using namespace isc::hooks;
namespace {
public:
D2GetConfigTest()
: rcode_(-1) {
+ resetHooksPath();
srv_.reset(new D2CfgMgr());
// Enforce not verbose mode.
Daemon::setVerbose(false);
~D2GetConfigTest() {
static_cast<void>(remove(test_file_name.c_str()));
resetConfiguration();
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : D2_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
}
/// @brief Parse and Execute configuration
/// Test a configuration
TEST_F(D2GetConfigTest, sample1) {
+ setHooksTestPath();
// get the sample1 configuration
std::string sample1_file = string(CFG_EXAMPLES) + "/" + "sample1.json";
// Basic callout library with context_create and three "standard" callouts.
static const char* CALLOUT_LIBRARY = "@abs_builddir@/.libs/libcallout.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* D2_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
} // anonymous namespace
#endif // D2_TEST_CALLOUT_LIBRARIES_H
// content of the error entry.
static const char* CONFIGURED_LIBRARY = "@abs_builddir@/.libs/libconfigured.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* D2_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
} // anonymous namespace
#endif // D2_TEST_CONFIGURED_LIBRARIES_H
#include <dhcpsrv/testutils/config_result_check.h>
#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <process/config_ctl_info.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
}
// Reset configuration for each test.
resetConfiguration();
+
+ resetHooksPath();
}
~Dhcp4ParserTest() {
// ... and delete the hooks library marker files if present
static_cast<void>(remove(LOAD_MARKER_FILE));
static_cast<void>(remove(UNLOAD_MARKER_FILE));
- };
+
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : DHCP4_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
+ }
// Checks if the result of DHCP server configuration has
// expected code (0 for success, other for failures).
// Verify the configuration of hooks libraries with two being specified.
TEST_F(Dhcp4ParserTest, LibrariesSpecified) {
+ setHooksTestPath();
+
// Marker files should not be present.
EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
#include <testutils/sandbox.h>
#include "marker_file.h"
-#include "test_libraries.h"
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
# Path to test hooks library
HOOK_FAIL_POLL_PATH="@abs_top_builddir@/src/bin/dhcp4/tests/.libs/libco4.so"
# Kea configuration to be stored in the configuration file.
+
+# Set env KEA_HOOKS_PATH to override DEFAULT_HOOKS_PATH
+export KEA_HOOKS_PATH="@abs_top_builddir@/src/bin/dhcp4/tests/.libs"
+
CONFIG="{
\"Dhcp4\":
{
#include <dhcp4/tests/test_libraries.h>
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <hooks/callout_manager.h>
#include <stats/stats_mgr.h>
#include <util/multi_threading_mgr.h>
LoadUnloadDhcpv4SrvTest() {
reset();
MultiThreadingMgr::instance().setMode(false);
+ resetHooksPath();
}
/// @brief Destructor
server_.reset();
reset();
MultiThreadingMgr::instance().setMode(false);
+ resetHooksPath();
};
/// @brief Reset hooks data
CfgMgr::instance().clear();
}
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : DHCP4_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
+ }
};
// Checks if callouts installed on buffer4_receive are indeed called and the
// Checks if callouts installed on the dhcp4_srv_configured ared indeed called
// and all the necessary parameters are passed.
TEST_F(LoadUnloadDhcpv4SrvTest, Dhcpv4SrvConfigured) {
+ setHooksTestPath();
for (auto const& parameters : vector<string>{
"",
R"(, "parameters": { "mode": "fail-without-error" } )",
// Checks that postponed hook start service can fail.
TEST_F(LoadUnloadDhcpv4SrvTest, startServiceFail) {
+ setHooksTestPath();
+
boost::shared_ptr<ControlledDhcpv4Srv> srv(new ControlledDhcpv4Srv(0));
// Ensure no marker files to start with.
const char* const CALLOUT_LIBRARY_3 = "@abs_builddir@/.libs/libco3.so";
const char* const CALLOUT_LIBRARY_4 = "@abs_builddir@/.libs/libco4.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* DHCP4_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
// Name of a library which is not present.
const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
#include <dhcpsrv/testutils/config_result_check.h>
#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <process/config_ctl_info.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
}
// Reset configuration for each test.
resetConfiguration();
+
+ resetHooksPath();
}
~Dhcp6ParserTest() {
// ... and delete the hooks library marker files if present
static_cast<void>(remove(LOAD_MARKER_FILE));
static_cast<void>(remove(UNLOAD_MARKER_FILE));
+
+ resetHooksPath();
};
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : DHCP6_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
+ }
+
// Checks if the result of DHCP server configuration has
// expected code (0 for success, other for failures).
// Also stores result in rcode_ and comment_.
// Verify the configuration of hooks libraries with two being specified.
TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+ setHooksTestPath();
+
// Marker files should not be present.
EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
#include <util/chrono_time_utils.h>
#include "marker_file.h"
-#include "test_libraries.h"
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
# Path to test hooks library
HOOK_FAIL_POLL_PATH="@abs_top_builddir@/src/bin/dhcp6/tests/.libs/libco4.so"
# Kea configuration to be stored in the configuration file.
+# Set env KEA_HOOKS_PATH to override DEFAULT_HOOKS_PATH
+export KEA_HOOKS_PATH="@abs_top_builddir@/src/bin/dhcp6/tests/.libs"
+
CONFIG="{
\"Dhcp6\":
{
#include <dhcp6/tests/test_libraries.h>
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <hooks/callout_manager.h>
#include <stats/stats_mgr.h>
#include <util/buffer.h>
LoadUnloadDhcpv6SrvTest() : Dhcpv6SrvTest() {
reset();
MultiThreadingMgr::instance().setMode(false);
+ resetHooksPath();
}
/// @brief Destructor
server_.reset();
reset();
MultiThreadingMgr::instance().setMode(false);
- };
+ resetHooksPath();
+ }
/// @brief Reset hooks data
///
CfgMgr::instance().clear();
}
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param explicit_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : DHCP6_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
+ }
};
// Checks if callouts installed on buffer6_receive are indeed called and the
// Checks if callouts installed on the dhcp6_srv_configured ared indeed called
// and all the necessary parameters are passed.
TEST_F(LoadUnloadDhcpv6SrvTest, Dhcpv6SrvConfigured) {
+ setHooksTestPath();
for (auto const& parameters : vector<string>{
"",
R"(, "parameters": { "mode": "fail-without-error" } )",
// Checks that postponed hook start service can fail.
TEST_F(LoadUnloadDhcpv6SrvTest, startServiceFail) {
+ setHooksTestPath();
boost::shared_ptr<ControlledDhcpv6Srv> srv(new ControlledDhcpv6Srv(0));
// Ensure no marker files to start with.
// Name of a library which is not present.
const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* DHCP6_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
} // anonymous namespace
ParseConfigTest()
:family_(AF_INET6) {
reset_context();
+ resetHooksPath();
}
~ParseConfigTest() {
reset_context();
CfgMgr::instance().clear();
+ resetHooksPath();
+ }
+
+ /// @brief Sets the Hooks path from which hooks can be loaded.
+ /// @param custom_path path to use as the hooks path.
+ void setHooksTestPath(const std::string explicit_path = "") {
+ HooksLibrariesParser::getHooksPath(true,
+ (!explicit_path.empty() ?
+ explicit_path : DHCPSRV_HOOKS_TEST_PATH));
+ }
+
+ /// @brief Resets the hooks path to DEFAULT_HOOKS_PATH.
+ void resetHooksPath() {
+ HooksLibrariesParser::getHooksPath(true);
}
/// @brief Parses a configuration.
// hooks-libraries element that contains a single library.
TEST_F(ParseConfigTest, oneHooksLibrary) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// hooks-libraries element that contains two libraries
TEST_F(ParseConfigTest, twoHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Configure with two libraries, then reconfigure with the same libraries.
TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Configure the hooks with two libraries, then reconfigure with the same
// libraries, but in reverse order.
TEST_F(ParseConfigTest, reconfigureReverseHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Configure the hooks with two libraries, then reconfigure with
// no libraries.
TEST_F(ParseConfigTest, reconfigureZeroHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Check with a set of libraries, some of which are invalid.
TEST_F(ParseConfigTest, invalidHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Check that trying to reconfigure with an invalid set of libraries fails.
TEST_F(ParseConfigTest, reconfigureInvalidHooksLibraries) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Check that if hooks-libraries contains invalid syntax, it is detected.
TEST_F(ParseConfigTest, invalidSyntaxHooksLibraries) {
-
+ setHooksTestPath("/opt/lib");
// Element holds a mixture of (valid) maps and non-maps.
string config1 = "{ \"hooks-libraries\": [ "
"{ \"library\": \"/opt/lib/lib1\" }, "
"{ \"library\": \"/opt/lib/lib1\" }, "
"{ \"library\": \"\" } "
"] }";
- string error3 = "value of 'library' element must not be blank";
+ string error3 = "Configuration parsing failed: hooks library configuration error:"
+ " path: '' has no filename (<string>:1:69)";
rcode = parseConfiguration(config3);
ASSERT_NE(0, rcode);
"{ \"library\": \"/opt/lib/lib1\" }, "
"{ \"library\": \" \" } "
"] }";
- string error4 = "value of 'library' element must not be blank";
+ string error4 = "Configuration parsing failed: hooks library configuration error:"
+ " path: '' has no filename (<string>:1:69)";
rcode = parseConfiguration(config4);
ASSERT_NE(0, rcode);
- EXPECT_TRUE(error_text_.find(error3) != string::npos) <<
+ EXPECT_TRUE(error_text_.find(error4) != string::npos) <<
"Error text returned from parse failure is " << error_text_;
// Element holds valid maps, except one that does not contain a
// Check that some parameters may have configuration parameters configured.
TEST_F(ParseConfigTest, HooksLibrariesParameters) {
+ setHooksTestPath();
// Check that no libraries are currently loaded
vector<string> hooks_libraries = HooksManager::getLibraryNames();
EXPECT_TRUE(hooks_libraries.empty());
// Name of a library which is not present.
static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
+// Test path to use for in place of DEFAULT_HOOKS_PATH
+static const char* DHCPSRV_HOOKS_TEST_PATH = "@abs_builddir@/.libs";
+
+
} // anonymous namespace
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDEFAULT_HOOKS_PATH=\"$(libdir)/kea/hooks\"
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(KEA_CXXFLAGS)
#include <cc/dhcp_config_error.h>
#include <hooks/hooks_parser.h>
#include <boost/algorithm/string.hpp>
+#include <util/filesystem.h>
#include <util/str.h>
#include <vector>
using namespace isc::data;
using namespace isc::hooks;
using namespace isc::dhcp;
+using namespace isc::util::file;
namespace isc {
namespace hooks {
+std::string
+HooksLibrariesParser::getHooksPath(bool reset /* = false */, const std::string explicit_path /* = "" */) {
+ static std::string default_hooks_path = "";
+ if (default_hooks_path.empty() || reset) {
+ if (explicit_path.empty()) {
+ default_hooks_path = std::string(std::getenv("KEA_HOOKS_PATH") ?
+ std::getenv("KEA_HOOKS_PATH")
+ : DEFAULT_HOOKS_PATH);
+ } else {
+ default_hooks_path = explicit_path;
+ }
+ }
+
+ return (default_hooks_path);
+}
+
// @todo use the flat style, split into list and item
void
}
// Get the name of the library and add it to the list after
- // removing quotes.
- libname = (entry_item.second)->stringValue();
-
- // Remove leading/trailing quotes and any leading/trailing
- // spaces.
- boost::erase_all(libname, "\"");
- libname = isc::util::str::trim(libname);
- if (libname.empty()) {
+ // validating it.
+ try {
+ libname = validatePath((entry_item.second)->stringValue());
+ } catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "hooks library configuration"
- " error: value of 'library' element must not be"
- " blank (" <<
- entry_item.second->getPosition() << ")");
+ " error: " << ex.what() << " ("
+ << entry_item.second->getPosition() << ")");
}
// Note we have found the library name.
}
}
+std::string
+HooksLibrariesParser::validatePath(const std::string libpath,
+ bool enforce_path /* = true */) {
+ return (FileManager::validatePath(HooksLibrariesParser::getHooksPath(),
+ libpath, enforce_path));
+}
+
}
}
/// @param libraries parsed libraries information will be stored here
/// @param value pointer to the content to be parsed
void parse(HooksConfig& libraries, isc::data::ConstElementPtr value);
+
+ /// @brief Validates a library path against the supported path for
+ /// hooks libraries.
+ ///
+ /// @param libpath library path to validate.
+ /// @param enforce_path enables validation against the supported path.
+ /// If false verifies only that the path contains a file name.
+ ///
+ /// @return validated path
+ static std::string validatePath(const std::string libpath,
+ bool enforce_path = true);
+
+ /// @brief Fetches the supported Hooks path.
+ ///
+ /// The first call to this function with no arguments will set the default
+ /// hooks path to either the value of DEFAULT_HOOKS_PATH or the environment
+ /// variable KEA_HOOKS_PATH if it is defined. Subsequent calls with no
+ /// arguments will simply return this value.
+ ///
+ /// @param reset recalculate when true, defaults to false. This is for
+ /// testing purposes only.
+ /// @param explicit_path set default hooks path to this value. This is
+ /// for testing purposes only.
+ ///
+ /// @return String containing the default hooks path.
+ static std::string getHooksPath(bool reset = false,
+ const std::string explicit_path = "");
};
}; // namespace isc::hooks
SUBDIRS = .
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDEFAULT_HOOKS_PATH=\"$(libdir)/kea/hooks\"
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(KEA_CXXFLAGS)
-// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2025 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
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
#include <hooks/server_hooks.h>
+#include <testutils/gtest_utils.h>
#include <hooks/tests/common_test_class.h>
#define TEST_ASYNC_CALLOUT
EXPECT_FALSE(unparked);
}
+/// @brief Test fixture for hooks parsing.
+class HooksParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ HooksParserTest() {
+ // Save current value of the environment path.
+ char* env_path = std::getenv("KEA_HOOKS_PATH");
+ if (env_path) {
+ original_path_ = std::string(env_path);
+ }
+
+ // Clear the environment path.
+ unsetenv("KEA_HOOKS_PATH");
+ }
+
+ /// @brief Destructor
+ ~HooksParserTest() {
+ // Restore the original environment path.
+ if (!original_path_.empty()) {
+ setenv("KEA_HOOKS_PATH", original_path_.c_str(), 1);
+ } else {
+ unsetenv("KEA_HOOKS_PATH");
+ }
+ }
+
+ /// @brief Retains the environment variable's original value.
+ std::string original_path_;
+};
+
+TEST_F(HooksParserTest, getHooksPath) {
+ ASSERT_FALSE(std::getenv("KEA_HOOKS_PATH"));
+ auto hooks_path = HooksLibrariesParser::getHooksPath(true);
+ EXPECT_EQ(hooks_path, DEFAULT_HOOKS_PATH);
+}
+
+TEST_F(HooksParserTest, getHooksPathWithEnv) {
+ std::string evar("KEA_HOOKS_PATH=/tmp");
+ putenv(const_cast<char*>(evar.c_str()));
+ ASSERT_TRUE(std::getenv("KEA_HOOKS_PATH"));
+ auto hooks_path = HooksLibrariesParser::getHooksPath(true);
+ EXPECT_EQ(hooks_path, "/tmp");
+}
+
+TEST_F(HooksParserTest, getHooksPathExplicit) {
+ auto hooks_path = HooksLibrariesParser::getHooksPath(true, "/explicit/path");
+ EXPECT_EQ(hooks_path, "/explicit/path");
+}
+
+// Verifies HooksParser::validatePath() when enforce_path is true.
+TEST_F(HooksParserTest, validatePathEnforcePath) {
+ HooksLibrariesParser::getHooksPath(true);
+ std::string def_path(HooksLibrariesParser::getHooksPath());
+ struct Scenario {
+ int line_;
+ std::string lib_path_;
+ std::string exp_path_;
+ std::string exp_error_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // Invalid parent path.
+ __LINE__,
+ "/var/lib/bs/mylib.so",
+ "",
+ string("invalid path specified: '/var/lib/bs', supported path is '" + def_path + "'")
+ },
+ {
+ // No file name.
+ __LINE__,
+ def_path + "/",
+ "",
+ string ("path: '" + def_path + "/' has no filename")
+ },
+ {
+ // File name only is valid.
+ __LINE__,
+ "mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // Valid full path.
+ __LINE__,
+ def_path + "/mylib.so",
+ def_path + "/mylib.so",
+ ""
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ std::ostringstream oss;
+ oss << " Scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::string validated_path;
+ if (scenario.exp_error_.empty()) {
+ ASSERT_NO_THROW_LOG(validated_path =
+ HooksLibrariesParser::validatePath(scenario.lib_path_));
+ EXPECT_EQ(validated_path, scenario.exp_path_);
+ } else {
+ ASSERT_THROW_MSG(validated_path =
+ HooksLibrariesParser::validatePath(scenario.lib_path_),
+ BadValue, scenario.exp_error_);
+ }
+ }
+}
+
+// Verifies HooksParser::validatePath() when enforce_path is false.
+TEST_F(HooksParserTest, validatePathEnforcePathFalse) {
+ HooksLibrariesParser::getHooksPath(true);
+ std::string def_path(HooksLibrariesParser::getHooksPath());
+ struct Scenario {
+ int line_;
+ std::string lib_path_;
+ std::string exp_path_;
+ std::string exp_error_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // Invalid parent path will fly.
+ __LINE__,
+ "/var/lib/bs/mylib.so",
+ "/var/lib/bs/mylib.so",
+ "",
+ },
+ {
+ // No file name.
+ __LINE__,
+ def_path + "/",
+ "",
+ string ("path: '" + def_path + "/' has no filename")
+ },
+ {
+ // File name only is valid.
+ __LINE__,
+ "mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // Valid full path.
+ __LINE__,
+ def_path + "/mylib.so",
+ def_path + "/mylib.so",
+ ""
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ std::ostringstream oss;
+ oss << " Scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::string validated_path;
+ if (scenario.exp_error_.empty()) {
+ ASSERT_NO_THROW_LOG(validated_path =
+ HooksLibrariesParser::validatePath(scenario.lib_path_, false));
+ EXPECT_EQ(validated_path, scenario.exp_path_);
+ } else {
+ ASSERT_THROW_MSG(validated_path =
+ HooksLibrariesParser::validatePath(scenario.lib_path_, false),
+ BadValue, scenario.exp_error_);
+ }
+ }
+}
} // Anonymous namespace
-// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-2025 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
#include <util/filesystem.h>
#include <util/str.h>
-#include <algorithm>
-#include <cctype>
-#include <cerrno>
-#include <cstring>
+#include <cstdio>
+#include <cstdlib>
#include <fstream>
-#include <iostream>
#include <string>
+#include <filesystem>
+#include <iostream>
+#include <dirent.h>
#include <fcntl.h>
-#include <sys/stat.h>
+using namespace isc;
using namespace isc::util::str;
using namespace std;
return ((statbuf.st_mode & S_IFMT) == S_IFREG);
}
+Umask::Umask(mode_t mask) : orig_umask_(umask(S_IWGRP | S_IWOTH)) {
+ umask(orig_umask_ | mask);
+}
+
+Umask::~Umask() {
+ umask(orig_umask_);
+}
+
+bool
+isSocket(string const& path) {
+ struct stat statbuf;
+ if (::stat(path.c_str(), &statbuf) < 0) {
+ return (false);
+ }
+ return ((statbuf.st_mode & S_IFMT) == S_IFSOCK);
+}
+
Path::Path(string const& full_name) {
if (!full_name.empty()) {
bool dir_present = false;
if (last_slash != string::npos) {
// Found the last slash, so extract directory component and
// set where the scan for the last_dot should terminate.
- parent_path_ = full_name.substr(0, last_slash + 1);
+ parent_path_ = full_name.substr(0, last_slash);
if (last_slash == full_name.size()) {
- // The entire string was a directory, so exit not and don't
+ // The entire string was a directory, so exit and don't
// do any more searching.
return;
}
string
Path::str() const {
- return (parent_path_ + stem_ + extension_);
+ return (parent_path_ + ((parent_path_.empty() || parent_path_ == "/") ? string() : "/") + stem_ + extension_);
}
string
string const trimmed_replacement(trim(replacement));
if (trimmed_replacement.empty()) {
parent_path_ = string();
- } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
+ } else if (trimmed_replacement == "/") {
parent_path_ = trimmed_replacement;
+ } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
+ parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1);
} else {
- parent_path_ = trimmed_replacement + '/';
+ parent_path_ = trimmed_replacement;
}
return (*this);
}
+TemporaryDirectory::TemporaryDirectory() {
+ char dir[]("/tmp/kea-tmpdir-XXXXXX");
+ char const* dir_name = mkdtemp(dir);
+ if (!dir_name) {
+ isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
+ }
+ dir_name_ = string(dir_name);
+}
+
+TemporaryDirectory::~TemporaryDirectory() {
+ DIR *dir(opendir(dir_name_.c_str()));
+ if (!dir) {
+ return;
+ }
+
+ std::unique_ptr<DIR, void(*)(DIR*)> defer(dir, [](DIR* d) { closedir(d); });
+
+ struct dirent *i;
+ string filepath;
+ while ((i = readdir(dir))) {
+ if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
+ continue;
+ }
+
+ filepath = dir_name_ + '/' + i->d_name;
+ remove(filepath.c_str());
+ }
+
+ rmdir(dir_name_.c_str());
+}
+
+string TemporaryDirectory::dirName() {
+ return dir_name_;
+}
+
+std::string
+FileManager::validatePath(const std::string supported_path_str, const std::string input_path_str,
+ bool enforce_path /* = true */) {
+ // Remove the trailing "/" if it present so comparison to
+ // input's parent path functions.
+ auto supported_path_copy(supported_path_str);
+ if (supported_path_copy.back() == '/') {
+ supported_path_copy.pop_back();
+ }
+
+ Path input_path(trim(input_path_str));
+ auto filename = input_path.filename();
+ if (filename.empty()) {
+ isc_throw(BadValue, "path: '" << input_path.str() << "' has no filename");
+ }
+
+ auto parent_path = input_path.parentPath();
+ if (!parent_path.empty()) {
+ if (!enforce_path) {
+ // Security set to lax, let it fly.
+ return (input_path_str);
+ }
+
+ // We only allow absolute path equal to default. Catch an invalid path.
+ if (parent_path != supported_path_copy) {
+ isc_throw(BadValue, "invalid path specified: '"
+ << parent_path << "', supported path is '"
+ << supported_path_copy << "'");
+ }
+ }
+
+ std::string valid_path(supported_path_copy + "/" + filename);
+ return (valid_path);
+}
+
} // namespace file
} // namespace util
} // namespace isc
-// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-2025 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
#ifndef KEA_UTIL_FILESYSTEM_H
#define KEA_UTIL_FILESYSTEM_H
+#include <sys/stat.h>
#include <string>
namespace isc {
namespace util {
namespace file {
-/// \brief Get the content of a regular file.
+/// @brief Get the content of a regular file.
///
-/// \param file_name The file name.
+/// @param file_name The file name.
///
-/// \return The content of the file.
-/// \throw BadValue when the file can't be opened or is not a regular one.
+/// @return The content of the file.
+/// @throw BadValue when the file can't be opened or is not a regular one.
std::string
getContent(const std::string& file_name);
-/// \brief Check if there is a file or directory at the given path.
+/// @brief Check if there is a file or directory at the given path.
///
-/// \param path The path being checked.
+/// @param path The path being checked.
///
-/// \return True if the path points to a file or a directory, false otherwise.
+/// @return True if the path points to a file or a directory, false otherwise.
bool
exists(const std::string& path);
-/// \brief Check if there is a directory at the given path.
+/// @brief Check if there is a directory at the given path.
///
-/// \param path The path being checked.
+/// @param path The path being checked.
///
-/// \return True if the path points to a directory, false otherwise including
+/// @return True if the path points to a directory, false otherwise including
/// if the pointed location does not exist.
bool
isDir(const std::string& path);
-/// \brief Check if there is a file at the given path.
+/// @brief Check if there is a file at the given path.
///
-/// \param path The path being checked.
+/// @param path The path being checked.
///
-/// \return True if the path points to a file, false otherwise including
+/// @return True if the path points to a file, false otherwise including
/// if the pointed location does not exist.
bool
isFile(const std::string& path);
-/// \brief Paths on a filesystem
+/// @brief RAII device to limit access of created files.
+struct Umask {
+ /// @brief Constructor
+ ///
+ /// Set wanted bits in umask.
+ Umask(mode_t mask);
+
+ /// @brief Destructor.
+ ///
+ /// Restore umask.
+ ~Umask();
+
+private:
+ /// @brief Original umask.
+ mode_t orig_umask_;
+};
+
+bool
+isSocket(const std::string& path);
+
+/// @brief Paths on a filesystem
struct Path {
- /// \brief Constructor
+ /// @brief Constructor
///
/// Splits the full name into components.
Path(std::string const& path);
- /// \brief Get the path in textual format.
+ /// @brief Get the path in textual format.
///
/// Counterpart for std::filesystem::path::string.
///
- /// \return stored filename.
+ /// @return stored filename.
std::string str() const;
- /// \brief Get the parent path.
+ /// @brief Get the parent path.
///
/// Counterpart for std::filesystem::path::parent_path.
///
- /// \return parent path of current path.
+ /// @return parent path of current path.
std::string parentPath() const;
- /// \brief Get the base name of the file without the extension.
+ /// @brief Get the base name of the file without the extension.
///
/// Counterpart for std::filesystem::path::stem.
///
- /// \return the base name of current path without the extension.
+ /// @return the base name of current path without the extension.
std::string stem() const;
- /// \brief Get the extension of the file.
+ /// @brief Get the extension of the file.
///
/// Counterpart for std::filesystem::path::extension.
///
- /// \return extension of current path.
+ /// @return extension of current path.
std::string extension() const;
- /// \brief Get the name of the file, extension included.
+ /// @brief Get the name of the file, extension included.
///
/// Counterpart for std::filesystem::path::filename.
///
- /// \return name + extension of current path.
+ /// @return name + extension of current path.
std::string filename() const;
- /// \brief Identifies the extension in {replacement}, trims it, and
+ /// @brief Identifies the extension in {replacement}, trims it, and
/// replaces this instance's extension with it.
///
/// Counterpart for std::filesystem::path::replace_extension.
/// The change is done in the members and {this} is returned to allow call
/// chaining.
///
- /// \param replacement The extension to replace with.
+ /// @param replacement The extension to replace with.
///
- /// \return The current instance after the replacement was done.
+ /// @return The current instance after the replacement was done.
Path& replaceExtension(std::string const& replacement = std::string());
- /// \brief Trims {replacement} and replaces this instance's parent path with
+ /// @brief Trims {replacement} and replaces this instance's parent path with
/// it.
///
/// The change is done in the members and {this} is returned to allow call
/// chaining.
///
- /// \param replacement The parent path to replace with.
+ /// @param replacement The parent path to replace with.
///
- /// \return The current instance after the replacement was done.
+ /// @return The current instance after the replacement was done.
Path& replaceParentPath(std::string const& replacement = std::string());
private:
- /// \brief Parent path.
+ /// @brief Parent path.
std::string parent_path_;
- /// \brief Stem.
+ /// @brief Stem.
std::string stem_;
- /// \brief File name extension.
+ /// @brief File name extension.
std::string extension_;
};
+struct TemporaryDirectory {
+ TemporaryDirectory();
+ ~TemporaryDirectory();
+ std::string dirName();
+private:
+ std::string dir_name_;
+};
+
+/// @brief Class that provides basic file related tasks.
+class FileManager {
+public:
+ /// @brief Validates a file path against a supported path.
+ ///
+ /// If the input path specifies a parent path and file name, the parent path
+ /// is validated against the supported path. If they match, the function returns
+ /// the validated path. If the input path contains only a file name the function
+ /// returns valid path using the supported path and the input path name.
+ ///
+ /// @param supported_path_str absolute path specifying the supported path
+ /// of the file against which the input path is validated.
+ /// @param input_path_str file path to validate.
+ /// @param enforce_path enables validation against the supported path. If false
+ /// verifies only that the path contains a file name.
+ ///
+ /// @return validated path as a string (supported path + input file name)
+ ///
+ /// @throw BadValue if the input path does not include a file name or if the
+ /// it the parent path does not path the supported path.
+ static std::string validatePath(const std::string supported_path_str,
+ const std::string input_path_str,
+ bool enforce_path = true);
+};
+
} // namespace file
} // namespace util
} // namespace isc
#include <util/filesystem.h>
#include <fstream>
+#include <list>
#include <string>
#include <gtest/gtest.h>
EXPECT_FALSE(isFile(TEST_DATA_BUILDDIR));
}
+/// @brief Check Umask.
+TEST_F(FileUtilTest, umask) {
+ // Protect the test itself assuming that Umask does what we expect...
+ Umask m0(0);
+ mode_t orig = umask(0);
+ {
+ Umask m(S_IROTH);
+ EXPECT_EQ(S_IROTH, umask(S_IRWXO));
+ }
+ EXPECT_EQ(0, umask(orig));
+}
+
/// @brief Check that the components are split correctly.
TEST(PathTest, components) {
// Complete name
Path fname("/alpha/beta/gamma.delta");
- EXPECT_EQ("/alpha/beta/", fname.parentPath());
+ EXPECT_EQ("/alpha/beta/gamma.delta", fname.str());
+ EXPECT_EQ("/alpha/beta", fname.parentPath());
EXPECT_EQ("gamma", fname.stem());
EXPECT_EQ(".delta", fname.extension());
EXPECT_EQ("gamma.delta", fname.filename());
/// @brief Check replaceExtension.
TEST(PathTest, replaceExtension) {
Path fname("a.b");
+ EXPECT_EQ("a.b", fname.str());
EXPECT_EQ("a", fname.replaceExtension("").str());
EXPECT_EQ("a.f", fname.replaceExtension(".f").str());
EXPECT_EQ("a.b", fname.str());
fname.replaceParentPath("/just/some/dir/");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
fname.replaceParentPath("/just/some/dir");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
fname.replaceParentPath("/");
EXPECT_EQ("a.b", fname.str());
fname = Path("/first/a.b");
- EXPECT_EQ("/first/", fname.parentPath());
+ EXPECT_EQ("/first", fname.parentPath());
EXPECT_EQ("/first/a.b", fname.str());
fname.replaceParentPath("/just/some/dir");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
}
+
+
+// Verifies FileManager::validatePath() when enforce_path is true.
+TEST(FileManager, validatePathEnforcePath) {
+ std::string def_path(TEST_DATA_BUILDDIR + '/');
+ struct Scenario {
+ int line_;
+ std::string lib_path_;
+ std::string exp_path_;
+ std::string exp_error_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // Invalid parent path.
+ __LINE__,
+ "/var/lib/bs/mylib.so",
+ "",
+ string("invalid path specified: '/var/lib/bs', supported path is '" + def_path + "'")
+ },
+ {
+ // No file name.
+ __LINE__,
+ def_path + "/",
+ "",
+ string ("path: '" + def_path + "/' has no filename")
+ },
+ {
+ // File name only is valid.
+ __LINE__,
+ "mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // Valid full path.
+ __LINE__,
+ def_path + "/mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // White space for file name.
+ __LINE__,
+ " ",
+ "",
+ string("path: '' has no filename")
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ std::ostringstream oss;
+ oss << " Scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::string validated_path;
+ if (scenario.exp_error_.empty()) {
+ ASSERT_NO_THROW_LOG(validated_path =
+ FileManager::validatePath(def_path, scenario.lib_path_));
+ EXPECT_EQ(validated_path, scenario.exp_path_);
+ } else {
+ ASSERT_THROW_MSG(validated_path =
+ FileManager::validatePath(def_path, scenario.lib_path_),
+ BadValue, scenario.exp_error_);
+ }
+ }
+}
+
+// Verifies FileManager::validatePath() when enforce_path is false.
+TEST(FileManager, validatePathEnforcePathFalse) {
+ std::string def_path(TEST_DATA_BUILDDIR);
+ struct Scenario {
+ int line_;
+ std::string lib_path_;
+ std::string exp_path_;
+ std::string exp_error_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // Invalid parent path but shouldn't care.
+ __LINE__,
+ "/var/lib/bs/mylib.so",
+ "/var/lib/bs/mylib.so",
+ ""
+ },
+ {
+ // No file name.
+ __LINE__,
+ def_path + "/",
+ "",
+ string ("path: '" + def_path + "/' has no filename")
+ },
+ {
+ // File name only is valid.
+ __LINE__,
+ "mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // Valid full path.
+ __LINE__,
+ def_path + "/mylib.so",
+ def_path + "/mylib.so",
+ ""
+ },
+ {
+ // White space for file name.
+ __LINE__,
+ " ",
+ "",
+ string("path: '' has no filename")
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ std::ostringstream oss;
+ oss << " Scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::string validated_path;
+ if (scenario.exp_error_.empty()) {
+ ASSERT_NO_THROW_LOG(validated_path =
+ FileManager::validatePath(def_path, scenario.lib_path_, false));
+ EXPECT_EQ(validated_path, scenario.exp_path_);
+ } else {
+ ASSERT_THROW_MSG(validated_path =
+ FileManager::validatePath(def_path, scenario.lib_path_, false),
+ BadValue, scenario.exp_error_);
+ }
+ }
+}
+
} // namespace