mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-30 03:34:26 +03:00
sync code
This commit is contained in:
@@ -23,11 +23,12 @@ using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_API);
|
||||
|
||||
RestConn::RestConn(int _fd, I_MainLoop *_mainloop, const I_RestInvoke *_invoke)
|
||||
RestConn::RestConn(int _fd, I_MainLoop *_mainloop, const I_RestInvoke *_invoke, bool is_external)
|
||||
:
|
||||
fd(_fd),
|
||||
mainloop(_mainloop),
|
||||
invoke(_invoke)
|
||||
fd(_fd),
|
||||
mainloop(_mainloop),
|
||||
invoke(_invoke),
|
||||
is_external_ip(is_external)
|
||||
{}
|
||||
|
||||
RestConn::~RestConn()
|
||||
@@ -101,6 +102,12 @@ RestConn::parseConn() const
|
||||
return sendResponse("200 OK", invoke->invokeGet(identifier), false);
|
||||
}
|
||||
|
||||
if (is_external_ip) {
|
||||
dbgWarning(D_API) << "External IP tried to POST";
|
||||
sendResponse("500 Internal Server Error", "", false);
|
||||
stop();
|
||||
}
|
||||
|
||||
stringstream body;
|
||||
body.str(readSize(len));
|
||||
|
||||
|
@@ -21,7 +21,7 @@
|
||||
class RestConn
|
||||
{
|
||||
public:
|
||||
RestConn(int _fd, I_MainLoop *_mainloop, const I_RestInvoke *_invoke);
|
||||
RestConn(int _fd, I_MainLoop *_mainloop, const I_RestInvoke *_invoke, bool is_external = false);
|
||||
~RestConn();
|
||||
|
||||
void parseConn() const;
|
||||
@@ -35,6 +35,7 @@ private:
|
||||
int fd;
|
||||
I_MainLoop *mainloop;
|
||||
const I_RestInvoke *invoke;
|
||||
bool is_external_ip = false;
|
||||
};
|
||||
|
||||
#endif // __REST_CONN_H__
|
||||
|
@@ -47,6 +47,7 @@ public:
|
||||
void startNewConnection() const;
|
||||
|
||||
bool bindRestServerSocket(struct sockaddr_in &addr, vector<uint16_t> port_range);
|
||||
bool bindRestServerSocket(struct sockaddr_in6 &addr, vector<uint16_t> port_range);
|
||||
bool addRestCall(RestAction oper, const string &uri, unique_ptr<RestInit> &&init) override;
|
||||
bool addGetCall(const string &uri, const function<string()> &cb) override;
|
||||
uint16_t getListeningPort() const override { return listening_port; }
|
||||
@@ -73,10 +74,36 @@ private:
|
||||
bool
|
||||
RestServer::Impl::bindRestServerSocket(struct sockaddr_in &addr, vector<uint16_t> port_range)
|
||||
{
|
||||
dbgFlow(D_API) << "Binding IPv4 socket";
|
||||
for (uint16_t port : port_range) {
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == 0) return true;
|
||||
|
||||
if (errno == EADDRINUSE) {
|
||||
dbgDebug(D_API) << "Port " << port << " is already in use";
|
||||
} else {
|
||||
dbgDebug(D_API) << "Failed to bind to port " << port << " with error: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
RestServer::Impl::bindRestServerSocket(struct sockaddr_in6 &addr, vector<uint16_t> port_range)
|
||||
{
|
||||
dbgFlow(D_API) << "Binding IPv6 socket";
|
||||
for (uint16_t port : port_range) {
|
||||
addr.sin6_port = htons(port);
|
||||
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in6)) == 0) return true;
|
||||
|
||||
if (errno == EADDRINUSE) {
|
||||
dbgDebug(D_API) << "Port " << port << " is already in use";
|
||||
} else {
|
||||
dbgDebug(D_API) << "Failed to bind to port " << port << " with error: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -119,21 +146,55 @@ RestServer::Impl::init()
|
||||
mainloop = Singleton::Consume<I_MainLoop>::by<RestServer>();
|
||||
|
||||
auto init_connection = [this] () {
|
||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
auto allow_external_conn = "Nano service API Allow Get From External IP";
|
||||
auto conf_value = getConfiguration<bool>("connection", allow_external_conn);
|
||||
bool accept_get_from_external_ip = false;
|
||||
if (conf_value.ok()) {
|
||||
accept_get_from_external_ip = *conf_value;
|
||||
} else {
|
||||
auto env_value = Singleton::Consume<I_Environment>::by<RestServer>()->get<bool>(allow_external_conn);
|
||||
if (env_value.ok()) {
|
||||
accept_get_from_external_ip = *env_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (accept_get_from_external_ip) {
|
||||
fd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
} else {
|
||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
}
|
||||
dbgAssert(fd >= 0) << alert << "Failed to open a socket";
|
||||
|
||||
int socket_enable = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &socket_enable, sizeof(int)) < 0) {
|
||||
dbgWarning(D_API) << "Could not set the socket options";
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
bzero(&addr, sizeof(addr));
|
||||
if (accept_get_from_external_ip) {
|
||||
int option = 0;
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &option, sizeof(option)) < 0) {
|
||||
dbgWarning(D_API) << "Could not set the IPV6_V6ONLY option";
|
||||
}
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
struct sockaddr_in6 addr6;
|
||||
bzero(&addr6, sizeof(addr6));
|
||||
addr6.sin6_family = AF_INET6;
|
||||
addr6.sin6_addr = in6addr_any;
|
||||
|
||||
while (!bindRestServerSocket(addr, port_range)) {
|
||||
mainloop->yield(bind_retry_interval_msec);
|
||||
while (!bindRestServerSocket(addr6, port_range)) {
|
||||
mainloop->yield(bind_retry_interval_msec);
|
||||
}
|
||||
listening_port = ntohs(addr6.sin6_port);
|
||||
} else {
|
||||
struct sockaddr_in addr;
|
||||
bzero(&addr, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
while (!bindRestServerSocket(addr, port_range)) {
|
||||
mainloop->yield(bind_retry_interval_msec);
|
||||
}
|
||||
listening_port = ntohs(addr.sin_port);
|
||||
}
|
||||
|
||||
listen(fd, listen_limit);
|
||||
@@ -146,9 +207,12 @@ RestServer::Impl::init()
|
||||
"REST server listener",
|
||||
is_primary.ok() && *is_primary
|
||||
);
|
||||
|
||||
listening_port = ntohs(addr.sin_port);
|
||||
dbgInfo(D_API) << "REST server started: " << listening_port;
|
||||
dbgInfo(D_API)
|
||||
<< "REST server started: "
|
||||
<< listening_port
|
||||
<< ". Accepting: "
|
||||
<< (accept_get_from_external_ip ? "external" : "loopback")
|
||||
<< " connections";
|
||||
Singleton::Consume<I_Environment>::by<RestServer>()->registerValue<int>("Listening Port", listening_port);
|
||||
};
|
||||
|
||||
@@ -172,14 +236,30 @@ void
|
||||
RestServer::Impl::startNewConnection() const
|
||||
{
|
||||
dbgFlow(D_API) << "Starting a new connection";
|
||||
int new_socket = accept(fd, nullptr, nullptr);
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
int new_socket = accept(fd, (struct sockaddr *)&addr, &addr_len);
|
||||
if (new_socket < 0) {
|
||||
dbgWarning(D_API) << "Failed to accept a new socket";
|
||||
dbgWarning(D_API) << "Failed to accept a new socket: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
dbgDebug(D_API) << "Starting a new socket: " << new_socket;
|
||||
|
||||
RestConn conn(new_socket, mainloop, this);
|
||||
dbgDebug(D_API) << "Starting a new socket: " << new_socket;
|
||||
bool is_external = false;
|
||||
if (addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *) &addr;
|
||||
if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
|
||||
struct in_addr ipv4_addr;
|
||||
memcpy(&ipv4_addr, &addr_in6->sin6_addr.s6_addr[12], sizeof(ipv4_addr));
|
||||
is_external = ipv4_addr.s_addr != htonl(INADDR_LOOPBACK);
|
||||
} else {
|
||||
is_external = memcmp(&addr_in6->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)) != 0;
|
||||
}
|
||||
} else {
|
||||
struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr;
|
||||
is_external = addr_in->sin_addr.s_addr != htonl(INADDR_LOOPBACK);
|
||||
}
|
||||
RestConn conn(new_socket, mainloop, this, is_external);
|
||||
mainloop->addFileRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
new_socket,
|
||||
@@ -283,4 +363,5 @@ RestServer::preload()
|
||||
registerExpectedConfiguration<uint>("connection", "Nano service API Port Alternative");
|
||||
registerExpectedConfiguration<uint>("connection", "Nano service API Port Range start");
|
||||
registerExpectedConfiguration<uint>("connection", "Nano service API Port Range end");
|
||||
registerExpectedConfiguration<bool>("connection", "Nano service API Allow Get From External IP");
|
||||
}
|
||||
|
@@ -14,10 +14,49 @@
|
||||
#include "agent_details.h"
|
||||
#include "mock/mock_messaging.h"
|
||||
#include "tenant_manager.h"
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
static const string config_json_allow_external =
|
||||
"{\n"
|
||||
" \"connection\": {\n"
|
||||
" \"Nano service API Port Primary\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9777\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"Nano service API Port Alternative\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9778\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"Nano service API Allow Get From External IP\": [\n"
|
||||
" {\n"
|
||||
" \"value\": true\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
static const string config_json =
|
||||
"{\n"
|
||||
" \"connection\": {\n"
|
||||
" \"Nano service API Port Primary\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9777\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"Nano service API Port Alternative\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9778\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
USE_DEBUG_FLAG(D_API);
|
||||
USE_DEBUG_FLAG(D_MAINLOOP);
|
||||
|
||||
@@ -31,22 +70,6 @@ public:
|
||||
time_proxy.init();
|
||||
mainloop_comp.init();
|
||||
|
||||
string config_json =
|
||||
"{\n"
|
||||
" \"connection\": {\n"
|
||||
" \"Nano service API Port Primary\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9777\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"Nano service API Port Alternative\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 9778\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
istringstream ss(config_json);
|
||||
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss);
|
||||
|
||||
@@ -58,6 +81,9 @@ public:
|
||||
~RestConfigTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
|
||||
mainloop->stopAll();
|
||||
rest_server.fini();
|
||||
time_proxy.fini();
|
||||
mainloop_comp.fini();
|
||||
}
|
||||
@@ -133,7 +159,7 @@ int TestServer::g_num = 0;
|
||||
TEST_F(RestConfigTest, basic_flow)
|
||||
{
|
||||
env.preload();
|
||||
Singleton::Consume<I_Environment>::from(env)->registerValue<string>("Executable Name", "tmp_test_file");
|
||||
Singleton::Consume<I_Environment>::from(env)->registerValue<string>("Base Executable Name", "tmp_test_file");
|
||||
|
||||
config.preload();
|
||||
config.init();
|
||||
@@ -165,11 +191,15 @@ TEST_F(RestConfigTest, basic_flow)
|
||||
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
|
||||
I_MainLoop::Routine stop_routine = [&] () {
|
||||
EXPECT_EQ(connect(file_descriptor1, (struct sockaddr*)&sa, sizeof(struct sockaddr)), 0);
|
||||
EXPECT_EQ(connect(file_descriptor1, (struct sockaddr*)&sa, sizeof(struct sockaddr)), 0)
|
||||
<< "file_descriptor1 Error: "
|
||||
<< strerror(errno);
|
||||
string msg1 = "GET /stuff HTTP/1.1\r\n\r\n";
|
||||
EXPECT_EQ(write(file_descriptor1, msg1.data(), msg1.size()), static_cast<int>(msg1.size()));
|
||||
|
||||
EXPECT_EQ(connect(file_descriptor2, (struct sockaddr*)&sa, sizeof(struct sockaddr)), 0);
|
||||
EXPECT_EQ(connect(file_descriptor2, (struct sockaddr*)&sa, sizeof(struct sockaddr)), 0)
|
||||
<< "file_descriptor2 Error: "
|
||||
<< strerror(errno);
|
||||
string msg2 = "POST /add-test HTTP/1.1\r\nContent-Length: 10\r\n\r\n{\"num\": 5}";
|
||||
EXPECT_EQ(write(file_descriptor2, msg2.data(), msg2.size()), static_cast<int>(msg2.size()));
|
||||
|
||||
@@ -204,3 +234,159 @@ TEST_F(RestConfigTest, basic_flow)
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 6\r\n\r\nblabla"
|
||||
);
|
||||
}
|
||||
|
||||
string
|
||||
getLocalIPAddress() {
|
||||
char hostname[1024];
|
||||
hostname[1024 - 1] = '\0';
|
||||
|
||||
// Get the hostname
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
struct addrinfo hints, *info, *p;
|
||||
int gai_result;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET; // Use AF_INET for IPv4
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
// Get the address info
|
||||
if ((gai_result = getaddrinfo(hostname, nullptr, &hints, &info)) != 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ip_address;
|
||||
for (p = info; p != nullptr; p = p->ai_next) {
|
||||
void *addr;
|
||||
char ipstr[INET_ADDRSTRLEN];
|
||||
|
||||
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
|
||||
addr = &(ipv4->sin_addr);
|
||||
|
||||
// Convert the IP to a string and print it
|
||||
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
|
||||
if (std::string(ipstr) != "127.0.0.1") {
|
||||
ip_address = ipstr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(info); // Free the linked list
|
||||
|
||||
return ip_address;
|
||||
}
|
||||
|
||||
|
||||
TEST_F(RestConfigTest, not_loopback_flow)
|
||||
{
|
||||
env.preload();
|
||||
Singleton::Consume<I_Environment>::from(env)->registerValue<string>("Executable Name", "tmp_test_file");
|
||||
|
||||
|
||||
istringstream ss(config_json_allow_external);
|
||||
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss);
|
||||
|
||||
config.preload();
|
||||
config.init();
|
||||
|
||||
rest_server.init();
|
||||
time_proxy.init();
|
||||
mainloop_comp.init();
|
||||
|
||||
auto i_rest = Singleton::Consume<I_RestApi>::from(rest_server);
|
||||
ASSERT_TRUE(i_rest->addRestCall<TestServer>(RestAction::ADD, "test"));
|
||||
ASSERT_TRUE(i_rest->addGetCall("stuff", [] () { return string("blabla"); }));
|
||||
|
||||
int file_descriptor1 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
EXPECT_NE(file_descriptor1, -1);
|
||||
int file_descriptor2 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
EXPECT_NE(file_descriptor2, -1);
|
||||
|
||||
auto primary_port = getConfiguration<uint>("connection", "Nano service API Port Primary");
|
||||
auto second_port = getConfiguration<uint>("connection", "Nano service API Port Alternative");
|
||||
auto local_ip = getLocalIPAddress();
|
||||
struct sockaddr_in sa_primary;
|
||||
sa_primary.sin_family = AF_INET;
|
||||
sa_primary.sin_port = htons(primary_port.unpack());
|
||||
sa_primary.sin_addr.s_addr = inet_addr(local_ip.c_str());
|
||||
struct sockaddr_in sa_second;
|
||||
sa_second.sin_family = AF_INET;
|
||||
sa_second.sin_port = htons(second_port.unpack());
|
||||
sa_second.sin_addr.s_addr = inet_addr(local_ip.c_str());
|
||||
|
||||
int socket_enable = 1;
|
||||
EXPECT_EQ(setsockopt(file_descriptor1, SOL_SOCKET, SO_REUSEADDR, &socket_enable, sizeof(int)), 0);
|
||||
EXPECT_EQ(setsockopt(file_descriptor2, SOL_SOCKET, SO_REUSEADDR, &socket_enable, sizeof(int)), 0);
|
||||
|
||||
EXPECT_CALL(messaging, sendSyncMessage(_, _, _, _, _))
|
||||
.WillRepeatedly(Return(HTTPResponse(HTTPStatusCode::HTTP_OK, "")));
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
I_MainLoop::Routine stop_routine = [&] () {
|
||||
int socket_client_2 = -1;
|
||||
auto socket_client_1 = connect(file_descriptor1, (struct sockaddr*)&sa_primary, sizeof(struct sockaddr));
|
||||
dbgDebug(D_API) << "socket_client_1: " << socket_client_1;
|
||||
if (socket_client_1 == -1) {
|
||||
dbgDebug(D_API) << "Error: " << strerror(errno);
|
||||
socket_client_2 = connect(file_descriptor1, (struct sockaddr*)&sa_second, sizeof(struct sockaddr));
|
||||
dbgDebug(D_API) << "socket_client_2: " << socket_client_2;
|
||||
if (socket_client_2 == -1) {
|
||||
dbgDebug(D_API) << "Error: " << strerror(errno) << endl;
|
||||
} else {
|
||||
EXPECT_EQ(connect(file_descriptor2, (struct sockaddr*)&sa_second, sizeof(struct sockaddr)), 0);
|
||||
string msg2 = "POST /add-test HTTP/1.1\r\nContent-Length: 10\r\n\r\n{\"num\": 5}";
|
||||
EXPECT_EQ(write(file_descriptor2, msg2.data(), msg2.size()), static_cast<int>(msg2.size()));
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(connect(file_descriptor2, (struct sockaddr*)&sa_primary, sizeof(struct sockaddr)), 0);
|
||||
string msg2 = "POST /add-test HTTP/1.1\r\nContent-Length: 10\r\n\r\n{\"num\": 5}";
|
||||
EXPECT_EQ(write(file_descriptor2, msg2.data(), msg2.size()), static_cast<int>(msg2.size()));
|
||||
}
|
||||
EXPECT_TRUE(socket_client_1 != -1 || socket_client_2 != -1);
|
||||
string msg1 = "GET /stuff HTTP/1.1\r\n\r\n";
|
||||
EXPECT_EQ(write(file_descriptor1, msg1.data(), msg1.size()), static_cast<int>(msg1.size()));
|
||||
|
||||
mainloop->yield(true);
|
||||
|
||||
struct pollfd s_poll;
|
||||
s_poll.fd = file_descriptor1;
|
||||
s_poll.events = POLLIN;
|
||||
s_poll.revents = 0;
|
||||
while(poll(&s_poll, 1, 0) <= 0) {
|
||||
mainloop->yield(true);
|
||||
}
|
||||
|
||||
struct pollfd s_poll2;
|
||||
s_poll2.fd = file_descriptor2;
|
||||
s_poll2.events = POLLIN;
|
||||
s_poll2.revents = 0;
|
||||
while(poll(&s_poll2, 1, 0) <= 0) {
|
||||
mainloop->yield(true);
|
||||
}
|
||||
|
||||
mainloop->stopAll();
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
stop_routine,
|
||||
"RestConfigTest-alternative_port_used stop routine",
|
||||
true
|
||||
);
|
||||
mainloop->run();
|
||||
|
||||
char respose[1000];
|
||||
EXPECT_EQ(read(file_descriptor1, respose, 1000), 76);
|
||||
EXPECT_EQ(
|
||||
string(respose, 76),
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 6\r\n\r\nblabla"
|
||||
);
|
||||
|
||||
EXPECT_EQ(read(file_descriptor2, respose, 1000), 89);
|
||||
EXPECT_EQ(
|
||||
string(respose, 89),
|
||||
"HTTP/1.1 500 Internal Server Error\r\nContent-Type: application/json\r\nContent-Length: 0\r\n\r\n"
|
||||
);
|
||||
}
|
||||
|
@@ -437,7 +437,7 @@ TEST(RestSchema, server_schema)
|
||||
|
||||
|
||||
env.preload();
|
||||
Singleton::Consume<I_Environment>::from(env)->registerValue<string>("Executable Name", "tmp_test_file");
|
||||
Singleton::Consume<I_Environment>::from(env)->registerValue<string>("Base Executable Name", "tmp_test_file");
|
||||
|
||||
config.preload();
|
||||
config.init();
|
||||
|
Reference in New Issue
Block a user