mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-02-24 02:09:00 -05:00
Compare commits
10 Commits
main
...
support_fo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f1a61443 | ||
|
|
a3b246fd6d | ||
|
|
75ff043476 | ||
|
|
0f7458c693 | ||
|
|
add7c4cc3a | ||
|
|
fcdc3a7eaf | ||
|
|
db3705dd23 | ||
|
|
3b34130cfc | ||
|
|
b773e93a95 | ||
|
|
e481164258 |
@@ -85,6 +85,10 @@ namespace kiwix {
|
||||
namespace
|
||||
{
|
||||
|
||||
void error(const std::string& msg) {
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
bool ipAvailable(const std::string addr)
|
||||
{
|
||||
auto interfaces = kiwix::getNetworkInterfacesIPv4Or6();
|
||||
@@ -99,6 +103,84 @@ bool ipAvailable(const std::string addr)
|
||||
return false;
|
||||
}
|
||||
|
||||
class InSockAddr
|
||||
{
|
||||
private: // data
|
||||
struct sockaddr_in v4 = {0};
|
||||
struct sockaddr_in6 v6 = {0};
|
||||
|
||||
public: // functions
|
||||
explicit InSockAddr(int port)
|
||||
{
|
||||
v4.sin_family = AF_INET;
|
||||
v4.sin_port = htons(port);
|
||||
v6.sin6_family = AF_INET6;
|
||||
v6.sin6_port = htons(port);
|
||||
}
|
||||
|
||||
IpAddress setAnyAddress(IpMode ipMode)
|
||||
{
|
||||
v6.sin6_addr = in6addr_any;
|
||||
v4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
IpAddress a = kiwix::getBestPublicIps();
|
||||
if (ipMode == IpMode::IPV6) {
|
||||
a.addr = "";
|
||||
} else if (ipMode == IpMode::IPV4) {
|
||||
a.addr6 = "";
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
IpMode setAddress(const IpAddress& a)
|
||||
{
|
||||
const std::string addrStr = !a.addr.empty() ? a.addr : a.addr6;
|
||||
|
||||
const int r1 = inet_pton(AF_INET, a.addr.c_str(), &v4.sin_addr.s_addr);
|
||||
const int r2 = inet_pton(AF_INET6, a.addr6.c_str(), &v6.sin6_addr.s6_addr);
|
||||
|
||||
if ( r1 != 1 && r2 != 1 ) {
|
||||
error("invalid IP address: " + addrStr);
|
||||
}
|
||||
|
||||
if ( !ipAvailable(addrStr) ) {
|
||||
error("IP address is not available on this system: " + addrStr);
|
||||
}
|
||||
|
||||
return !a.addr.empty() ? IpMode::IPV4 : IpMode::IPV6;
|
||||
}
|
||||
|
||||
struct sockaddr* sockaddr(IpMode ipMode) const
|
||||
{
|
||||
return (ipMode==IpMode::ALL || ipMode==IpMode::IPV6)
|
||||
? (struct sockaddr*)&v6
|
||||
: (struct sockaddr*)&v4;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
int getMHDFlags(IpMode ipMode, bool verbose)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int flags = MHD_USE_SELECT_INTERNALLY;
|
||||
#else
|
||||
int flags = MHD_USE_POLL_INTERNALLY;
|
||||
#endif
|
||||
|
||||
if (ipMode == IpMode::ALL) {
|
||||
flags |= MHD_USE_DUAL_STACK;
|
||||
} else if (ipMode == IpMode::IPV6) {
|
||||
flags |= MHD_USE_IPv6;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
flags |= MHD_USE_DEBUG;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
std::string
|
||||
fullURL2LocalURL(const std::string& fullUrl, const std::string& rootLocation)
|
||||
{
|
||||
@@ -450,81 +532,61 @@ InternalServer::InternalServer(LibraryPtr library,
|
||||
|
||||
InternalServer::~InternalServer() = default;
|
||||
|
||||
bool InternalServer::start() {
|
||||
#ifdef _WIN32
|
||||
int flags = MHD_USE_SELECT_INTERNALLY;
|
||||
#else
|
||||
int flags = MHD_USE_POLL_INTERNALLY;
|
||||
#endif
|
||||
if (m_verbose.load())
|
||||
flags |= MHD_USE_DEBUG;
|
||||
|
||||
|
||||
struct sockaddr_in sockAddr4={0};
|
||||
sockAddr4.sin_family = AF_INET;
|
||||
sockAddr4.sin_port = htons(m_port);
|
||||
struct sockaddr_in6 sockAddr6={0};
|
||||
sockAddr6.sin6_family = AF_INET6;
|
||||
sockAddr6.sin6_port = htons(m_port);
|
||||
struct MHD_Daemon* InternalServer::startMHD(int flags,
|
||||
struct sockaddr* sockaddr)
|
||||
{
|
||||
return MHD_start_daemon(flags,
|
||||
m_port,
|
||||
NULL,
|
||||
NULL,
|
||||
&staticHandlerCallback,
|
||||
this,
|
||||
MHD_OPTION_SOCK_ADDR, sockaddr,
|
||||
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
||||
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
||||
MHD_OPTION_END);
|
||||
}
|
||||
|
||||
void InternalServer::startMHD() {
|
||||
InSockAddr inSockAddr(m_port);
|
||||
if (m_addr.addr.empty() && m_addr.addr6.empty()) { // No ip address provided
|
||||
if (m_ipMode == IpMode::AUTO) m_ipMode = IpMode::ALL;
|
||||
sockAddr6.sin6_addr = in6addr_any;
|
||||
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
IpAddress bestIps = kiwix::getBestPublicIps();
|
||||
if (m_ipMode == IpMode::IPV4 || m_ipMode == IpMode::ALL) m_addr.addr = bestIps.addr;
|
||||
if (m_ipMode == IpMode::IPV6 || m_ipMode == IpMode::ALL) m_addr.addr6 = bestIps.addr6;
|
||||
m_addr = inSockAddr.setAnyAddress(m_ipMode);
|
||||
} else {
|
||||
const std::string addr = !m_addr.addr.empty() ? m_addr.addr : m_addr.addr6;
|
||||
|
||||
if (m_ipMode != kiwix::IpMode::AUTO) {
|
||||
std::cerr << "ERROR: When an IP address is provided the IP mode must not be set" << std::endl;
|
||||
return false;
|
||||
error("When an IP address is provided the IP mode must not be set");
|
||||
}
|
||||
|
||||
bool validV4 = inet_pton(AF_INET, m_addr.addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
|
||||
bool validV6 = inet_pton(AF_INET6, m_addr.addr6.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
|
||||
|
||||
if (!validV4 && !validV6) {
|
||||
std::cerr << "ERROR: invalid IP address: " << addr << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ipAvailable(addr)) {
|
||||
std::cerr << "ERROR: IP address is not available on this system: " << addr << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ipMode = !m_addr.addr.empty() ? IpMode::IPV4 : IpMode::IPV6;
|
||||
m_ipMode = inSockAddr.setAddress(m_addr);
|
||||
}
|
||||
|
||||
if (m_ipMode == IpMode::ALL) {
|
||||
flags|=MHD_USE_DUAL_STACK;
|
||||
} else if (m_ipMode == IpMode::IPV6) {
|
||||
flags|=MHD_USE_IPv6;
|
||||
const int flags = getMHDFlags(m_ipMode, m_verbose.load());
|
||||
mp_daemon = startMHD(flags, inSockAddr.sockaddr(m_ipMode));
|
||||
if (mp_daemon == nullptr && m_ipMode == IpMode::ALL) {
|
||||
// MHD_USE_DUAL_STACK (set in IpMode::ALL case) fails on systems with IPv6
|
||||
// disabled. Let's retry in IPv4-only mode.
|
||||
m_ipMode = IpMode::IPV4;
|
||||
m_addr.addr6 = "";
|
||||
const int flags = getMHDFlags(m_ipMode, m_verbose.load());
|
||||
mp_daemon = startMHD(flags, inSockAddr.sockaddr(m_ipMode));
|
||||
}
|
||||
|
||||
struct sockaddr* sockaddr = (m_ipMode==IpMode::ALL || m_ipMode==IpMode::IPV6)
|
||||
? (struct sockaddr*)&sockAddr6
|
||||
: (struct sockaddr*)&sockAddr4;
|
||||
|
||||
mp_daemon = MHD_start_daemon(flags,
|
||||
m_port,
|
||||
NULL,
|
||||
NULL,
|
||||
&staticHandlerCallback,
|
||||
this,
|
||||
MHD_OPTION_SOCK_ADDR, sockaddr,
|
||||
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
||||
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
||||
MHD_OPTION_END);
|
||||
if (mp_daemon == nullptr) {
|
||||
std::cerr << "Unable to instantiate the HTTP daemon. The port " << m_port
|
||||
<< " is maybe already occupied or need more permissions to be open. "
|
||||
"Please try as root or with a port number higher or equal to 1024."
|
||||
<< std::endl;
|
||||
error("Unable to instantiate the HTTP daemon. "
|
||||
"The port " + kiwix::to_string(m_port) + " is maybe already occupied"
|
||||
" or need more permissions to be open. "
|
||||
"Please try as root or with a port number higher or equal to 1024.");
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalServer::start() {
|
||||
try {
|
||||
startMHD();
|
||||
} catch (const std::runtime_error& err ) {
|
||||
std::cerr << "ERROR: " << err.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto server_start_time = std::chrono::system_clock::now().time_since_epoch();
|
||||
m_server_id = kiwix::to_string(server_start_time.count());
|
||||
return true;
|
||||
|
||||
@@ -126,6 +126,8 @@ class InternalServer {
|
||||
IpMode getIpMode() const { return m_ipMode; }
|
||||
|
||||
private: // functions
|
||||
void startMHD();
|
||||
struct MHD_Daemon* startMHD(int flags, struct sockaddr* sockaddr);
|
||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||
std::unique_ptr<Response> build_redirect(const std::string& bookName, const zim::Item& item) const;
|
||||
std::unique_ptr<Response> build_homepage(const RequestContext& request);
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "../include/library.h"
|
||||
#include "../include/manager.h"
|
||||
#include "testing_tools.h"
|
||||
using namespace kiwix::testing;
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace
|
||||
@@ -58,27 +61,6 @@ class NameMapperTest : public ::testing::Test {
|
||||
std::shared_ptr<kiwix::Library> lib;
|
||||
};
|
||||
|
||||
class CapturedStderr
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
std::streambuf* const sbuf;
|
||||
public:
|
||||
CapturedStderr()
|
||||
: sbuf(std::cerr.rdbuf())
|
||||
{
|
||||
std::cerr.rdbuf(buffer.rdbuf());
|
||||
}
|
||||
|
||||
CapturedStderr(const CapturedStderr&) = delete;
|
||||
|
||||
~CapturedStderr()
|
||||
{
|
||||
std::cerr.rdbuf(sbuf);
|
||||
}
|
||||
|
||||
operator std::string() const { return buffer.str(); }
|
||||
};
|
||||
|
||||
#if _WIN32
|
||||
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\zero_four_2021-10.zim' and"
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "../src/tools/stringTools.h"
|
||||
|
||||
#include "testing_tools.h"
|
||||
using namespace kiwix::testing;
|
||||
|
||||
const std::string ROOT_PREFIX("/ROOT%23%3F");
|
||||
|
||||
@@ -2306,3 +2308,74 @@ TEST_F(ServerTest, EmptyPatternSearchDoesNotError)
|
||||
{
|
||||
EXPECT_EQ(200, zfs1_->GET("/ROOT%23%3F/search?content=zimfile")->status);
|
||||
}
|
||||
|
||||
#define EXPECT_ERROR(MSG, SERVER_SETUP_CODE) \
|
||||
{ \
|
||||
kiwix::Server server(kiwix::Library::create()); \
|
||||
CapturedStderr stderror; \
|
||||
SERVER_SETUP_CODE; \
|
||||
EXPECT_FALSE(server.start()); \
|
||||
EXPECT_EQ(std::string(stderror), std::string("ERROR: ") + MSG + "\n"); \
|
||||
}
|
||||
|
||||
TEST(ServerNegativeTest, IpAddressAndIpModeAreMutuallyExclusive)
|
||||
{
|
||||
EXPECT_ERROR("When an IP address is provided the IP mode must not be set",
|
||||
server.setAddress("127.0.0.1");
|
||||
server.setIpMode(kiwix::IpMode::IPV4);
|
||||
);
|
||||
|
||||
EXPECT_ERROR("When an IP address is provided the IP mode must not be set",
|
||||
server.setAddress("[::1]");
|
||||
server.setIpMode(kiwix::IpMode::IPV6);
|
||||
);
|
||||
|
||||
EXPECT_ERROR("When an IP address is provided the IP mode must not be set",
|
||||
server.setAddress("localhost");
|
||||
server.setIpMode(kiwix::IpMode::ALL);
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ServerNegativeTest, InvalidIpAddressDetection)
|
||||
{
|
||||
EXPECT_ERROR("invalid IP address: 1.2.3",
|
||||
server.setAddress("1.2.3");
|
||||
);
|
||||
|
||||
EXPECT_ERROR("invalid IP address: 127.0.0.256",
|
||||
server.setAddress("127.0.0.256");
|
||||
);
|
||||
|
||||
EXPECT_ERROR("invalid IP address: localhost",
|
||||
server.setAddress("localhost");
|
||||
);
|
||||
|
||||
EXPECT_ERROR("invalid IP address: fe80::94d2:16e7:5f3e:89bx",
|
||||
server.setAddress("[fe80::94d2:16e7:5f3e:89bx]");
|
||||
);
|
||||
|
||||
// We assume that our unit tests won't be run on Google's DNS server
|
||||
EXPECT_ERROR("IP address is not available on this system: 8.8.8.8",
|
||||
server.setAddress("8.8.8.8");
|
||||
);
|
||||
|
||||
// According to the spec, IPv6 addresses 2001:db8::/32 are reserved
|
||||
// for documentation and example source code
|
||||
EXPECT_ERROR("IP address is not available on this system: 2001:db8::",
|
||||
server.setAddress("[2001:db8::]");
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ServerNegativeTest, UnusablePort)
|
||||
{
|
||||
// Occupy port 8910
|
||||
httplib::Server portOccupant;
|
||||
ASSERT_TRUE(portOccupant.bind_to_port("127.0.0.1", 8910));
|
||||
|
||||
// Try to listen on the same port
|
||||
EXPECT_ERROR("Unable to instantiate the HTTP daemon. The port 8910 is maybe "
|
||||
"already occupied or need more permissions to be open. Please "
|
||||
"try as root or with a port number higher or equal to 1024.",
|
||||
server.setPort(8910);
|
||||
);
|
||||
}
|
||||
|
||||
57
test/testing_tools.h
Normal file
57
test/testing_tools.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2026 Veloman Yunkan
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef LIBKIWIX_TESTING_TOOLS_H
|
||||
#define LIBKIWIX_TESTING_TOOLS_H
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace testing
|
||||
{
|
||||
|
||||
class CapturedStderr
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
std::streambuf* const sbuf;
|
||||
public:
|
||||
CapturedStderr()
|
||||
: sbuf(std::cerr.rdbuf())
|
||||
{
|
||||
std::cerr.rdbuf(buffer.rdbuf());
|
||||
}
|
||||
|
||||
CapturedStderr(const CapturedStderr&) = delete;
|
||||
|
||||
~CapturedStderr()
|
||||
{
|
||||
std::cerr.rdbuf(sbuf);
|
||||
}
|
||||
|
||||
operator std::string() const { return buffer.str(); }
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // LIBKIWIX_TESTING_TOOLS_H
|
||||
Reference in New Issue
Block a user