-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
System info tab and Server Speed Tests (#303)
- added a new "STATUS" page that displays information about the user's environment and hardware and a server speed tests; - added new API-methods: - struct sysinfo() - returns information about the user's environment and hardware; - bool testserverspeed(url, serverId) - puts nzb file to be downloaded by the target server. - API-method "status" now has 3 extra fields: - TotalDiskSpaceLo - Total disk space on ‘DestDir’, in bytes. This field contains the low 32-bits of 64-bit value - TotalDiskSpaceHi - Total disk space on ‘DestDir’, in bytes. This field contains the high 32-bits of 64-bit value - TotalDiskSpaceMB - Total disk space on ‘DestDir’, in megabytes.` - fixed NZB generator: the last segment was incorrect. ## Lib changes - Boost.Asio - cross-platform library for network.
- Loading branch information
Showing
59 changed files
with
3,209 additions
and
232 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
/* | ||
* This file is part of nzbget. See <https://nzbget.com>. | ||
* | ||
* Copyright (C) 2024 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 | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) 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, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
|
||
#include "nzbget.h" | ||
|
||
#include "HttpClient.h" | ||
#include "Util.h" | ||
|
||
namespace HttpClient | ||
{ | ||
namespace asio = boost::asio; | ||
using tcp = boost::asio::ip::tcp; | ||
|
||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
namespace ssl = boost::asio::ssl; | ||
#endif | ||
|
||
HttpClient::HttpClient() noexcept(false) | ||
: m_context{} | ||
, m_resolver{ m_context } | ||
{ | ||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
m_sslContext.set_default_verify_paths(); | ||
#endif | ||
} | ||
|
||
const std::string HttpClient::GetLocalIP() const | ||
{ | ||
return m_localIP; | ||
} | ||
|
||
std::future<Response> HttpClient::GET(std::string host_) | ||
{ | ||
return std::async(std::launch::async, [this, host = std::move(host_)] | ||
{ | ||
auto endpoints = m_resolver.resolve(host, GetProtocol()); | ||
auto socket = GetSocket(); | ||
|
||
Connect(socket, endpoints, host); | ||
Write(socket, "GET", host); | ||
|
||
return MakeResponse(socket); | ||
} | ||
); | ||
} | ||
|
||
std::string HttpClient::GetProtocol() const | ||
{ | ||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
return "https"; | ||
#else | ||
return "http"; | ||
#endif | ||
} | ||
|
||
Response HttpClient::MakeResponse(Socket& socket) | ||
{ | ||
Response response; | ||
|
||
asio::streambuf buf; | ||
response.statusCode = ReadStatusCode(socket, buf); | ||
response.headers = ReadHeaders(socket, buf); | ||
response.body = ReadBody(socket, buf); | ||
|
||
return response; | ||
} | ||
|
||
void HttpClient::Connect(Socket& socket, const Endpoints& endpoints, const std::string& host) | ||
{ | ||
asio::connect(socket.lowest_layer(), endpoints); | ||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
DoHandshake(socket, host); | ||
#endif | ||
SaveLocalIP(socket); | ||
} | ||
|
||
void HttpClient::Write(Socket& socket, const std::string& method, const std::string& host) | ||
{ | ||
asio::write(socket, asio::buffer(GetHeaders(method, host))); | ||
} | ||
|
||
std::string HttpClient::ReadBody(Socket& socket, boost::asio::streambuf& buf) | ||
{ | ||
boost::system::error_code ec; | ||
asio::read_until(socket, buf, "\0", ec); | ||
if (ec) | ||
{ | ||
throw std::runtime_error("Failed to read the response body: " + ec.message()); | ||
} | ||
|
||
auto bodyBuf = buf.data(); | ||
std::string body(asio::buffers_begin(bodyBuf), asio::buffers_begin(bodyBuf) + bodyBuf.size()); | ||
Util::Trim(body); | ||
|
||
return body; | ||
} | ||
|
||
Headers HttpClient::ReadHeaders(Socket& socket, boost::asio::streambuf& buf) | ||
{ | ||
asio::read_until(socket, buf, "\r\n\r\n"); | ||
|
||
std::istream stream(&buf); | ||
|
||
Headers headers; | ||
std::string line; | ||
while (std::getline(stream, line) && line != "\r") | ||
{ | ||
size_t colonPos = line.find(":"); | ||
if (colonPos != std::string::npos) | ||
{ | ||
std::string key = line.substr(0, colonPos); | ||
std::string value = line.substr(colonPos + 1); | ||
Util::Trim(value); | ||
headers.emplace(std::move(key), std::move(value)); | ||
} | ||
} | ||
|
||
return headers; | ||
} | ||
|
||
unsigned HttpClient::ReadStatusCode(Socket& socket, boost::asio::streambuf& buf) | ||
{ | ||
asio::read_until(socket, buf, "\r\n"); | ||
|
||
std::istream stream(&buf); | ||
|
||
std::string httpVersion; | ||
unsigned statusCode; | ||
|
||
stream >> httpVersion; | ||
stream >> statusCode; | ||
|
||
if (!stream || httpVersion.find("HTTP/") == std::string::npos) | ||
{ | ||
throw std::runtime_error("Invalid response"); | ||
} | ||
|
||
return statusCode; | ||
} | ||
|
||
std::string HttpClient::GetHeaders(const std::string& method, const std::string& host) const | ||
{ | ||
std::string headers = method + " / HTTP/1.1\r\n"; | ||
headers += "Host: " + host + "\r\n"; | ||
headers += std::string("User-Agent: nzbget/") + Util::VersionRevision() + "\r\n"; | ||
#ifndef DISABLE_GZIP | ||
headers += "Accept-Encoding: gzip\r\n"; | ||
#endif | ||
headers += "Accept: */*\r\n"; | ||
headers += "Connection: close\r\n\r\n"; | ||
|
||
return headers; | ||
} | ||
|
||
void HttpClient::SaveLocalIP(Socket& socket) | ||
{ | ||
m_localIP = socket.lowest_layer().local_endpoint().address().to_string(); | ||
} | ||
|
||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
Socket HttpClient::GetSocket() | ||
{ | ||
return ssl::stream<tcp::socket>{ m_context, m_sslContext }; | ||
} | ||
|
||
void HttpClient::DoHandshake(Socket& socket, const std::string& host) | ||
{ | ||
if (!SSL_set_tlsext_host_name(socket.native_handle(), host.c_str())) | ||
{ | ||
throw std::runtime_error("Failed to configure SNI TLS extension."); | ||
} | ||
|
||
socket.handshake(ssl::stream_base::handshake_type::client); | ||
} | ||
#else | ||
Socket HttpClient::GetSocket() | ||
{ | ||
return tcp::socket{ m_context }; | ||
} | ||
#endif | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* This file is part of nzbget. See <https://nzbget.com>. | ||
* | ||
* Copyright (C) 2024 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 | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) 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, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
|
||
#ifndef HTTP_CLIENT_H | ||
#define HTTP_CLIENT_H | ||
|
||
#include <string> | ||
#include <thread> | ||
#include <boost/asio.hpp> | ||
|
||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
#include <boost/asio/ssl.hpp> | ||
#endif | ||
|
||
namespace HttpClient | ||
{ | ||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
using Socket = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>; | ||
#else | ||
using Socket = boost::asio::ip::tcp::socket; | ||
#endif | ||
|
||
using Headers = std::unordered_map<std::string, std::string>; | ||
using Endpoints = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; | ||
|
||
struct Response | ||
{ | ||
Headers headers; | ||
std::string body; | ||
unsigned statusCode; | ||
}; | ||
|
||
class HttpClient final | ||
{ | ||
public: | ||
HttpClient() noexcept(false); | ||
HttpClient(const HttpClient&) = delete; | ||
HttpClient operator=(const HttpClient&) = delete; | ||
~HttpClient() = default; | ||
|
||
std::future<Response> GET(std::string host); | ||
const std::string GetLocalIP() const; | ||
|
||
private: | ||
void Connect(Socket& socket, const Endpoints& endpoints, const std::string& host); | ||
void Write(Socket& socket, const std::string& method, const std::string& host); | ||
Response MakeResponse(Socket& socket); | ||
std::string GetHeaders(const std::string& method, const std::string& host) const; | ||
void SaveLocalIP(Socket& socket); | ||
unsigned ReadStatusCode(Socket& socket, boost::asio::streambuf& buf); | ||
Headers ReadHeaders(Socket& socket, boost::asio::streambuf& buf); | ||
std::string ReadBody(Socket& socket, boost::asio::streambuf& buf); | ||
Socket GetSocket(); | ||
std::string GetProtocol() const; | ||
boost::asio::io_context m_context; | ||
boost::asio::ip::tcp::resolver m_resolver; | ||
std::string m_localIP; | ||
#if !defined(DISABLE_TLS) && defined(HAVE_OPENSSL) | ||
void DoHandshake(Socket& socket, const std::string& host); | ||
boost::asio::ssl::context m_sslContext{ boost::asio::ssl::context::tlsv13_client }; | ||
#endif | ||
}; | ||
} | ||
|
||
#endif |
Oops, something went wrong.