rssguard/src/librssguard/network-web/networkfactory.cpp
Martin Rotter 14cc68c40d fix build
2024-11-13 11:27:33 +01:00

362 lines
13 KiB
C++

// For license of this file, see <project-root-folder>/LICENSE.md.
#include "network-web/networkfactory.h"
#include "definitions/definitions.h"
#include "network-web/downloader.h"
#include <QEventLoop>
#include <QIcon>
#include <QMetaEnum>
#include <QPixmap>
#include <QRegularExpression>
#include <QTextDocument>
#include <QTimer>
QStringList NetworkFactory::extractFeedLinksFromHtmlPage(const QUrl& url, const QString& html) {
QStringList feeds;
QRegularExpression rx(QSL(FEED_REGEX_MATCHER), QRegularExpression::PatternOption::CaseInsensitiveOption);
QRegularExpression rx_href(QSL(FEED_HREF_REGEX_MATCHER), QRegularExpression::PatternOption::CaseInsensitiveOption);
rx_href.optimize();
QRegularExpressionMatchIterator it_rx = rx.globalMatch(html);
while (it_rx.hasNext()) {
QRegularExpressionMatch mat_tx = it_rx.next();
QString link_tag = mat_tx.captured();
QString feed_link = rx_href.match(link_tag).captured(1);
if (feed_link.startsWith(QL1S("//"))) {
feed_link = QSL(URI_SCHEME_HTTP) + feed_link.mid(2);
}
else if (feed_link.startsWith(QL1C('/'))) {
feed_link = url.toString(QUrl::UrlFormattingOption::RemovePath | QUrl::UrlFormattingOption::RemoveQuery |
QUrl::UrlFormattingOption::StripTrailingSlash) +
feed_link;
}
feeds.append(feed_link);
}
return feeds;
}
QPair<QByteArray, QByteArray> NetworkFactory::generateBasicAuthHeader(NetworkAuthentication protection,
const QString& username,
const QString& password) {
switch (protection) {
case NetworkFactory::NetworkAuthentication::Basic: {
if (username.isEmpty()) {
return {};
}
else {
QString basic_value = username + QSL(":") + password;
QString header_value = QSL("Basic ") + QString(basic_value.toUtf8().toBase64());
return QPair<QByteArray, QByteArray>(HTTP_HEADERS_AUTHORIZATION, header_value.toLocal8Bit());
}
}
case NetworkFactory::NetworkAuthentication::Token: {
QString header_value = QSL("Bearer ") + username;
return QPair<QByteArray, QByteArray>(HTTP_HEADERS_AUTHORIZATION, header_value.toLocal8Bit());
}
case NetworkFactory::NetworkAuthentication::NoAuthentication:
default:
return {};
}
}
QString NetworkFactory::networkErrorText(QNetworkReply::NetworkError error_code) {
switch (error_code) {
case QNetworkReply::ProtocolUnknownError:
case QNetworkReply::ProtocolFailure:
//: Network status.
return tr("protocol error");
case QNetworkReply::ContentAccessDenied:
return tr("access to content was denied");
case QNetworkReply::HostNotFoundError:
//: Network status.
return tr("host not found");
case QNetworkReply::OperationCanceledError:
case QNetworkReply::TimeoutError:
return tr("connection timed out or was cancelled");
case QNetworkReply::RemoteHostClosedError:
case QNetworkReply::ConnectionRefusedError:
//: Network status.
return tr("connection refused");
case QNetworkReply::ProxyTimeoutError:
//: Network status.
return tr("connection timed out");
case QNetworkReply::SslHandshakeFailedError:
//: Network status.
return tr("SSL handshake failed");
case QNetworkReply::ProxyConnectionClosedError:
case QNetworkReply::ProxyConnectionRefusedError:
//: Network status.
return tr("proxy server connection refused");
case QNetworkReply::TemporaryNetworkFailureError:
//: Network status.
return tr("temporary failure");
case QNetworkReply::AuthenticationRequiredError:
//: Network status.
return tr("authentication failed");
case QNetworkReply::ProxyAuthenticationRequiredError:
//: Network status.
return tr("proxy authentication required");
case QNetworkReply::ProxyNotFoundError:
//: Network status.
return tr("proxy server not found");
case QNetworkReply::NoError:
//: Network status.
return tr("no errors");
case QNetworkReply::UnknownContentError:
//: Network status.
return tr("unknown content");
case QNetworkReply::ContentNotFoundError:
//: Network status.
return tr("content not found");
default: {
QMetaEnum enumer = QMetaEnum::fromType<QNetworkReply::NetworkError>();
//: Network status.
return tr("unknown error (%1)").arg(enumer.valueToKey(error_code));
}
}
}
QString NetworkFactory::sanitizeUrl(const QString& url) {
static QRegularExpression reg_non_url(QSL("[^\\w\\-.~:\\/?#\\[\\]@!$&'()*+,;=% \\|]"),
QRegularExpression::PatternOption::UseUnicodePropertiesOption);
return QString(url).replace(reg_non_url, {});
}
QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QList<IconLocation>& urls,
int timeout,
QPixmap& output,
const QList<QPair<QByteArray, QByteArray>>& additional_headers,
const QNetworkProxy& custom_proxy) {
QNetworkReply::NetworkError network_result = QNetworkReply::NetworkError::UnknownNetworkError;
for (const auto& url : urls) {
if (url.m_url.isEmpty()) {
continue;
}
QByteArray icon_data;
if (url.m_isDirect) {
// Download directly.
network_result = performNetworkOperation(url.m_url,
timeout,
{},
icon_data,
QNetworkAccessManager::Operation::GetOperation,
additional_headers,
false,
{},
{},
custom_proxy)
.m_networkError;
if (network_result == QNetworkReply::NetworkError::NoError) {
QPixmap icon_pixmap;
icon_pixmap.loadFromData(icon_data);
output = icon_pixmap;
if (!output.isNull()) {
if (output.width() > 128) {
output = output.scaled(QSize(48, 48),
Qt::AspectRatioMode::KeepAspectRatio,
Qt::TransformationMode::SmoothTransformation);
}
break;
}
}
}
else {
// Duck Duck Go.
QUrl url_full = QUrl(url.m_url);
QString host = url_full.host();
if (host.startsWith(QSL("www."))) {
host = host.mid(4);
}
const QString ddg_icon_service = QSL("https://external-content.duckduckgo.com/ip3/%1.ico").arg(host);
// Google S2.
host = url_full.scheme() + QSL("://") + url_full.host();
const QString gs2_icon_service = QSL("https://t2.gstatic.com/faviconV2?"
"client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&"
"url=%1")
.arg(host);
for (const QString& service : {ddg_icon_service, gs2_icon_service}) {
network_result = performNetworkOperation(service,
timeout,
QByteArray(),
icon_data,
QNetworkAccessManager::Operation::GetOperation,
{},
false,
{},
{},
custom_proxy)
.m_networkError;
if (network_result == QNetworkReply::NetworkError::NoError) {
QPixmap icon_pixmap;
icon_pixmap.loadFromData(icon_data);
output = icon_pixmap;
if (!output.isNull()) {
if (output.width() > 128) {
output = output.scaled(QSize(48, 48),
Qt::AspectRatioMode::KeepAspectRatio,
Qt::TransformationMode::SmoothTransformation);
}
return network_result;
}
}
}
}
}
return network_result;
}
NetworkResult NetworkFactory::performNetworkOperation(const QString& url,
int timeout,
const QByteArray& input_data,
QByteArray& output,
QNetworkAccessManager::Operation operation,
const QList<QPair<QByteArray, QByteArray>>& additional_headers,
bool protected_contents,
const QString& username,
const QString& password,
const QNetworkProxy& custom_proxy) {
Downloader downloader;
QEventLoop loop;
NetworkResult result;
// We need to quit event loop when the download finishes.
QObject::connect(&downloader, &Downloader::completed, &loop, &QEventLoop::quit);
for (const auto& header : additional_headers) {
if (!header.first.isEmpty()) {
downloader.appendRawHeader(header.first, header.second);
}
}
if (custom_proxy.type() != QNetworkProxy::ProxyType::DefaultProxy) {
downloader.setProxy(custom_proxy);
}
downloader.manipulateData(url, operation, input_data, timeout, protected_contents, username, password);
loop.exec();
output = downloader.lastOutputData();
result.m_networkError = downloader.lastOutputError();
result.m_contentType = downloader.lastContentType();
result.m_cookies = downloader.lastCookies();
result.m_httpCode = downloader.lastHttpStatusCode();
result.m_headers = downloader.lastHeaders();
result.m_url = downloader.lastUrl();
qDebugNN << LOGSEC_NETWORK << "URLS\n" << url << "\n" << result.m_url.toString();
return result;
}
NetworkResult NetworkFactory::performNetworkOperation(const QString& url,
int timeout,
QHttpMultiPart* input_data,
QList<HttpResponse>& output,
QNetworkAccessManager::Operation operation,
const QList<QPair<QByteArray, QByteArray>>& additional_headers,
bool protected_contents,
const QString& username,
const QString& password,
const QNetworkProxy& custom_proxy) {
Downloader downloader;
QEventLoop loop;
NetworkResult result;
// We need to quit event loop when the download finishes.
QObject::connect(&downloader, &Downloader::completed, &loop, &QEventLoop::quit);
for (const auto& header : additional_headers) {
if (!header.first.isEmpty()) {
downloader.appendRawHeader(header.first, header.second);
}
}
if (custom_proxy.type() != QNetworkProxy::ProxyType::DefaultProxy) {
downloader.setProxy(custom_proxy);
}
downloader.manipulateData(url, operation, input_data, timeout, protected_contents, username, password);
loop.exec();
output = downloader.lastOutputMultipartData();
result.m_networkError = downloader.lastOutputError();
result.m_contentType = downloader.lastContentType();
result.m_cookies = downloader.lastCookies();
result.m_httpCode = downloader.lastHttpStatusCode();
result.m_headers = downloader.lastHeaders();
result.m_url = downloader.lastUrl();
qDebugNN << LOGSEC_NETWORK << "URLS\n" << url << "\n" << result.m_url.toString();
return result;
}
NetworkResult::NetworkResult()
: m_networkError(QNetworkReply::NetworkError::NoError), m_httpCode(0), m_contentType(QString()), m_cookies({}),
m_headers({}), m_url(QUrl()) {}
NetworkResult::NetworkResult(QNetworkReply::NetworkError err,
int http_code,
const QString& ct,
const QList<QNetworkCookie>& cook)
: m_networkError(err), m_httpCode(http_code), m_contentType(ct), m_cookies(cook), m_url({}) {}