UsenetSearch/src/UsenetClient.cpp

193 lines
5.1 KiB
C++

#include <codecvt>
#include <fstream>
#include <locale>
#include <memory>
#include <string>
#include "usenetsearch/Except.h"
#include "usenetsearch/StringUtils.h"
#include "usenetsearch/UsenetClient.h"
namespace usenetsearch {
// NntpListEntry serialization -------------------------------------------------
std::ostream& operator<<(std::ofstream& out, const NntpListEntry& obj)
{
out.write(reinterpret_cast<const char*>(&obj.count), sizeof(obj.count));
out.write(reinterpret_cast<const char*>(&obj.high), sizeof(obj.high));
out.write(reinterpret_cast<const char*>(&obj.low), sizeof(obj.low));
out << obj.name;
out << obj.status;
return out;
}
std::ifstream& operator>>(std::ifstream& in, NntpListEntry& obj)
{
in.read(reinterpret_cast<char*>(&obj.count), sizeof(obj.count));
in.read(reinterpret_cast<char*>(&obj.high), sizeof(obj.high));
in.read(reinterpret_cast<char*>(&obj.low), sizeof(obj.low));
in >> obj.name;
in >> obj.status;
return in;
}
// UsenetClient class ----------------------------------------------------------
void UsenetClient::Authenticate(
const std::wstring& user,
const std::wstring& password)
{
// Send user name
Write(L"AUTHINFO USER " + user + L"\r\n");
auto response = ReadLine();
if (IsError(response))
{
throw UsenetClientException(
response.code,
"Error authenticating with NNTP server: "
+ m_conv.to_bytes(response.message)
);
}
// Send password
Write(L"AUTHINFO PASS " + password + L"\r\n");
response = ReadLine();
if (IsError(response))
{
throw UsenetClientException(
response.code,
"Error authenticating with NNTP server: "
+ m_conv.to_bytes(response.message)
);
}
}
void UsenetClient::Connect(
const std::string& host,
std::uint16_t port,
bool useSSL)
{
// Establish connection.
m_useSSL = useSSL;
try
{
m_tcp = std::make_unique<usenetsearch::TcpConnection>();
m_tcp->Connect(host, port);
if (useSSL)
{
m_ssl = std::make_unique<SSLConnection>(std::move(m_tcp));
m_ssl->Connect();
}
}
catch (const UsenetSearchException& e)
{
throw UsenetClientException(
e.Code(),
"Error while trying to connect to host: " + host + ":"
+ std::to_string(port) + " - " + e.what()
);
}
// Read server banner.
const auto serverHello = ReadLine();
if (IsError(serverHello))
{
throw UsenetClientException(
serverHello.code,
"Error received from NNTP server: "
+ m_conv.to_bytes(serverHello.message)
);
}
}
bool UsenetClient::IsError(const NntpMessage& msg) const
{
if (msg.code >= 400) return true;
return false;
}
std::vector<NntpListEntry> UsenetClient::List()
{
Write(L"LIST COUNTS\r\n");
/* In response, we should get a 215 response followed by the list of news
groups ending in a period on it's own line. */
const auto response = ReadLine();
if (IsError(response))
{
throw UsenetClientException(
response.code,
"Failed to fetch newsgroup list from server, "
+ std::string{"server responded with: "}
+ m_conv.to_bytes(response.message)
);
}
const auto listStr = ReadUntil(L"\r\n.\r\n");
// parse the list.
const auto lines = StringSplit(listStr, std::wstring{L"\r\n"});
std::vector<NntpListEntry> result;
for (const auto& line: lines)
{
NntpListEntry entry;
const auto fields = StringSplit(line, std::wstring{L" "});
if (fields.size() == 5)
{
entry.name = fields[0];
entry.high = std::stoul(fields[1]);
entry.low = std::stoul(fields[2]);
entry.count = std::stoul(fields[3]);
entry.status = fields[4];
result.emplace_back(entry);
}
}
return result;
}
NntpMessage UsenetClient::ReadLine()
{
NntpMessage result{};
std::wstring line;
line = ReadUntil(L"\r\n");
if (line.length() < 4)
{
throw UsenetSearchException(EPROTONOSUPPORT,
"NNTP protocol error - invalid response from server: "
+ m_conv.to_bytes(line));
}
std::wstring codeStr = line.substr(0, 3);
result.code = std::stoi(codeStr);
result.message = line.substr(4, line.length());
return result;
}
std::wstring UsenetClient::ReadUntil(const std::wstring& deliminator)
{
std::wstring result;
const std::string deliminatorStr = m_conv.to_bytes(deliminator);
std::string resultStr;
if (m_useSSL)
{
resultStr = m_ssl->ReadUntil(deliminatorStr);
}
else
{
resultStr = m_tcp->ReadUntil(deliminatorStr);
}
result = m_conv.from_bytes(resultStr);
return result;
}
void UsenetClient::Write(const std::wstring& message)
{
const std::string toSend = m_conv.to_bytes(message);
if (m_useSSL)
{
m_ssl->Write(toSend);
}
else
{
m_tcp->Write(toSend);
}
}
} // namespace usenetsearch