Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 99 additions & 9 deletions daemon/remote/XmlRpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,25 +337,70 @@ class LoadLogXmlCommand: public LogXmlCommand
std::unique_ptr<GuardedDownloadQueue> m_downloadQueue;
};

class TestServerXmlCommand: public XmlCommand
class TestServerXmlCommand : public XmlCommand
{
public:
void Execute() override;

struct ServerParams
{
std::string host;
std::string username;
std::string password;
std::string cipher;
int port;
int timeout;
int certVerifLevel;
bool encryption;
};

private:
CString m_errText;

class TestConnection : public NntpConnection
{
public:
TestConnection(NewsServer* newsServer, TestServerXmlCommand* owner):
NntpConnection(newsServer), m_owner(owner) {}
TestConnection(NewsServer* newsServer, TestServerXmlCommand* owner) :
NntpConnection(newsServer), m_owner(owner) {
}
protected:
TestServerXmlCommand* m_owner;
virtual void PrintError(const char* errMsg) { m_owner->PrintError(errMsg); }
};

void PrintError(const char* errMsg);

std::optional<ServerParams> ParseRequestParams(const Json::value& value)
{
if (!value.is_object())
return std::nullopt;

const Json::object& obj = value.as_object();
if (!obj.contains("params") || !obj.at("params").is_array())
return std::nullopt;

const Json::array& paramsArray = obj.at("params").as_array();
if (paramsArray.size() < 8)
return std::nullopt;

try
{
ServerParams p;
p.host = Json::value_to<std::string>(paramsArray.at(0));
p.port = Json::value_to<int>(paramsArray.at(1));
p.username = Json::value_to<std::string>(paramsArray.at(2));
p.password = Json::value_to<std::string>(paramsArray.at(3));
p.encryption = Json::value_to<bool>(paramsArray.at(4));
p.cipher = Json::value_to<std::string>(paramsArray.at(5));
p.timeout = Json::value_to<int>(paramsArray.at(6));
p.certVerifLevel = Json::value_to<int>(paramsArray.at(7));
return p;
}
catch (const std::exception&)
{
return std::nullopt;
}
}
};

class TestServerSpeedXmlCommand: public SafeXmlCommand
Expand Down Expand Up @@ -3781,12 +3826,13 @@ GuardedMessageList LoadLogXmlCommand::GuardMessages()
return GuardedMessageList(&m_messages, nullptr);
}

// string testserver(string host, int port, string username, string password, bool encryption, string cipher, int timeout);
// string testserver(string host, int port, string username, string password, bool encryption, string cipher, int timeout, int certVerifLevel);
void TestServerXmlCommand::Execute()
{
const char* XML_RESPONSE_STR_BODY = "<string>%s</string>";
const char* JSON_RESPONSE_STR_BODY = "\"%s\"";

TestServerXmlCommand::ServerParams params;
char* host;
int port;
char* username;
Expand All @@ -3796,25 +3842,69 @@ void TestServerXmlCommand::Execute()
int timeout;
int certVerifLevel;

if (!NextParamAsStr(&host) || !NextParamAsInt(&port) || !NextParamAsStr(&username) ||
if (IsJson() && m_request)
{
const auto jsonResult = Json::Deserialize(m_request);
if (!jsonResult)
{
BuildErrorResponse(2, "Invalid JSON");
return;
}
auto paramsResult = ParseRequestParams(*jsonResult);
if (!paramsResult)
{
BuildErrorResponse(2, "Invalid parameters");
return;
}
params = std::move(*paramsResult);
}
else if (!NextParamAsStr(&host) || !NextParamAsInt(&port) || !NextParamAsStr(&username) ||
!NextParamAsStr(&password) || !NextParamAsBool(&encryption) ||
!NextParamAsStr(&cipher) || !NextParamAsInt(&timeout) ||
!NextParamAsInt(&certVerifLevel))
{
BuildErrorResponse(2, "Invalid parameter");
return;
}
else
{
params.host = host;
params.port = port;
params.username = username;
params.password = password;
params.encryption = encryption;
params.cipher = cipher;
params.timeout = timeout;
params.certVerifLevel = certVerifLevel;
}

if (certVerifLevel < 0 || certVerifLevel >= Options::ECertVerifLevel::Count)
if (params.certVerifLevel < 0 || params.certVerifLevel >= Options::ECertVerifLevel::Count)
{
BuildErrorResponse(2, "Invalid parameter (Certificate Verification Level).");
return;
}

NewsServer server(0, true, "test server", host, port, 0, username, password, false,
encryption, cipher, 1, 0, 0, 0, false, certVerifLevel);
NewsServer server(
0,
true,
"test server",
params.host.c_str(),
params.port,
0,
params.username.c_str(),
params.password.c_str(),
false,
params.encryption,
params.cipher.c_str(),
1,
0,
0,
0,
false,
params.certVerifLevel
);
TestConnection connection(&server, this);
connection.SetTimeout(timeout == 0 ? g_Options->GetArticleTimeout() : timeout);
connection.SetTimeout(params.timeout == 0 ? g_Options->GetArticleTimeout() : params.timeout);
connection.SetSuppressErrors(false);

bool ok = connection.Connect();
Expand Down
4 changes: 2 additions & 2 deletions daemon/util/Json.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <https://nzbget.com>.
*
* Copyright (C) 2023-2024 Denis <denis@nzbget.com>
* Copyright (C) 2023-2025 Denis <denis@nzbget.com>
*
* 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
Expand Down Expand Up @@ -41,7 +41,7 @@ namespace Json
return parser.release();
}

std::optional<JsonValue> Deserialize(const std::string& jsonStr) noexcept
std::optional<JsonValue> Deserialize(std::string_view jsonStr) noexcept
{
ErrorCode ec;
JsonValue value = parse(jsonStr, ec);
Expand Down
6 changes: 3 additions & 3 deletions daemon/util/Json.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <https://nzbget.com>.
*
* Copyright (C) 2023-2024 Denis <denis@nzbget.com>
* Copyright (C) 2023-2025 Denis <denis@nzbget.com>
*
* 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
Expand All @@ -23,7 +23,7 @@
#include <boost/json.hpp>
#include <iostream>
#include <optional>
#include <string>
#include <string_view>

namespace Json
{
Expand All @@ -35,7 +35,7 @@ namespace Json
using ErrorCode = boost::system::error_code;

std::optional<JsonValue> Deserialize(std::basic_istream<char>& is) noexcept;
std::optional<JsonValue> Deserialize(const std::string& jsonStr) noexcept;
std::optional<JsonValue> Deserialize(std::string_view jsonStr) noexcept;
std::string Serialize(const JsonObject& json) noexcept;
}

Expand Down
72 changes: 52 additions & 20 deletions daemon/util/Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1379,39 +1379,71 @@ const char* WebUtil::JsonFindField(const char* jsonText, const char* fieldName,

const char* WebUtil::JsonNextValue(const char* jsonText, int* valueLength)
{
if (!jsonText || !valueLength)
{
return nullptr;
}

const char* pstart = jsonText;
while (*pstart && strchr(" ,[]__{__:}\r\n\t\f", *pstart))
{
pstart++;
}

while (*pstart && strchr(" ,[{:\r\n\t\f", *pstart)) pstart++;
if (!*pstart) return nullptr;
if (!*pstart)
{
*valueLength = 0;
return nullptr;
}

const char* pend = pstart;
bool isString = (*pstart == '"');

char ch = *pend;
bool str = ch == '"';
if (str)
if (isString)
{
ch = *++pend;
}
while (ch)
{
if (ch == '\\')
pend++; // Move past the opening quote
while (true)
{
if (!*++pend || !*++pend) return nullptr;
ch = *pend;
// This check runs at the start of each loop.
// If we run out of characters before finding a closing quote, it's an error.
if (!*pend)
{
*valueLength = 0;
return nullptr; // unclosed string
}

if (*pend == '\\')
{
pend++; // Move to the character being escaped.

// This checks for a dangling escape at the very end of the input.
// For the input "hello\", `pend` would point to '\0' here.
if (!*pend) {
*valueLength = 0;
return nullptr; // CATCHES DANGLING ESCAPE
}
pend++; // Move past the character that was escaped.
}
else if (*pend == '"')
{
pend++; // Include the closing quote.
break; // End of string found.
}
else
{
pend++; // Normal character.
}
}
if (str && ch == '"')
}
else
{
while (*pend && !strchr(" ,]}:\r\n\t\f", *pend))
{
pend++;
break;
}
else if (!str && strchr(" ,]}\r\n\t\f", ch))
{
break;
}
ch = *++pend;
}

*valueLength = (int)(pend - pstart);
*valueLength = static_cast<int>(pend - pstart);
return pstart;
}

Expand Down
8 changes: 7 additions & 1 deletion docs/api/TESTSERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ string testserver(
string password,
bool encryption,
string cipher,
int timeout
int timeout,
int certVerificationLevel
);
```

Expand All @@ -24,6 +25,11 @@ Tries to connect to a server.
- **encryption** `(bool)` - The inscription should be used.
- **cipher** `(string)` - Cipher for use.
- **timeout** `(int)` - Connection timeout.
- **certVerificationLevel** `(int)` - Certificate verification level:
This is an enumerated type where the following integer values are recognized:
- **0:** None - NO certificate signing check, NO certificate hostname check
- **1:** Minimal - certificate signing check, NO certificate hostname check
- **2:** Strict - certificate signing check, certificate hostname check

### Return value
`string` result.
Loading