193 lines
5.1 KiB
C++
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
|