Add HTTP & SOCKS5 proxy support (#4321)

This can be configured using the `CHATTERINO2_PROXY_URL` environment variable.
The behaviour is similar to curl's CURLOPT_PROXY
This commit is contained in:
nerix 2023-02-12 00:16:51 +01:00 committed by GitHub
parent 98c2ff5607
commit c9a9e44e1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 166 additions and 0 deletions

View file

@ -12,6 +12,7 @@
- Minor: Added link to streamlink docs for easier user setup. (#4217)
- Minor: Added setting to turn off rendering of reply context. (#4224)
- Minor: Added setting to select which channels to log. (#4302)
- Minor: Added support for HTTP and Socks5 proxies through environment variables. (#4321)
- Minor: Remove sending part of the multipart emoji workaround (#4361)
- Bugfix: Fixed crash that would occur when performing certain actions after removing all tabs. (#4271)
- Bugfix: Fixed highlight sounds not reloading on change properly. (#4194)

View file

@ -195,6 +195,8 @@ set(SOURCE_FILES
providers/IvrApi.hpp
providers/LinkResolver.cpp
providers/LinkResolver.hpp
providers/NetworkConfigurationProvider.cpp
providers/NetworkConfigurationProvider.hpp
providers/RecentMessagesApi.cpp
providers/RecentMessagesApi.hpp

View file

@ -44,6 +44,17 @@ namespace {
return defaultValue;
}
boost::optional<QString> readOptionalStringEnv(const char *envName)
{
auto envString = std::getenv(envName);
if (envString != nullptr)
{
return QString(envString);
}
return boost::none;
}
uint16_t readPortEnv(const char *envName, uint16_t defaultValue)
{
auto envString = std::getenv(envName);
@ -89,6 +100,7 @@ Env::Env()
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 443))
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
, proxyUrl(readOptionalStringEnv("CHATTERINO2_PROXY_URL"))
{
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <boost/optional.hpp>
#include <QString>
namespace chatterino {
@ -16,6 +17,7 @@ public:
const QString twitchServerHost;
const uint16_t twitchServerPort;
const bool twitchServerSecure;
const boost::optional<QString> proxyUrl;
};
} // namespace chatterino

View file

@ -28,6 +28,7 @@ Q_LOGGING_CATEGORY(chatterinoMain, "chatterino.main", logThreshold);
Q_LOGGING_CATEGORY(chatterinoMessage, "chatterino.message", logThreshold);
Q_LOGGING_CATEGORY(chatterinoNativeMessage, "chatterino.nativemessage",
logThreshold);
Q_LOGGING_CATEGORY(chatterinoNetwork, "chatterino.network", logThreshold);
Q_LOGGING_CATEGORY(chatterinoNotification, "chatterino.notification",
logThreshold);
Q_LOGGING_CATEGORY(chatterinoNuulsuploader, "chatterino.nuulsuploader",

View file

@ -22,6 +22,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoLiveupdates);
Q_DECLARE_LOGGING_CATEGORY(chatterinoMain);
Q_DECLARE_LOGGING_CATEGORY(chatterinoMessage);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNativeMessage);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNetwork);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNotification);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNuulsuploader);
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);

View file

@ -1,9 +1,11 @@
#include "BrowserExtension.hpp"
#include "common/Args.hpp"
#include "common/Env.hpp"
#include "common/Modes.hpp"
#include "common/QLogging.hpp"
#include "common/Version.hpp"
#include "providers/IvrApi.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "RunGui.hpp"
#include "singletons/Paths.hpp"
@ -81,6 +83,8 @@ int main(int argc, char **argv)
attachToConsole();
}
NetworkConfigurationProvider::applyFromEnv(Env::get());
IvrApi::initialize();
Helix::initialize();

View file

@ -0,0 +1,76 @@
#include "providers/NetworkConfigurationProvider.hpp"
#include "common/Env.hpp"
#include "common/QLogging.hpp"
#include <QNetworkProxy>
#include <QSslConfiguration>
#include <QUrl>
namespace {
/**
* Creates a QNetworkProxy from a given URL.
*
* Creates an HTTP proxy by default, a Socks5 will be created if the scheme is 'socks5'.
*/
QNetworkProxy createProxyFromUrl(const QUrl &url)
{
QNetworkProxy proxy;
proxy.setHostName(url.host(QUrl::FullyEncoded));
proxy.setUser(url.userName(QUrl::FullyEncoded));
proxy.setPassword(url.password(QUrl::FullyEncoded));
proxy.setPort(url.port(1080));
if (url.scheme().compare(QStringLiteral("socks5"), Qt::CaseInsensitive) ==
0)
{
proxy.setType(QNetworkProxy::Socks5Proxy);
}
else
{
proxy.setType(QNetworkProxy::HttpProxy);
if (!proxy.user().isEmpty() || !proxy.password().isEmpty())
{
// for some reason, Qt doesn't set the Proxy-Authorization header
const auto auth = proxy.user() + ":" + proxy.password();
const auto base64 = auth.toUtf8().toBase64();
proxy.setRawHeader("Proxy-Authorization",
QByteArray("Basic ").append(base64));
}
}
return proxy;
}
/**
* Attempts to apply the proxy specified by `url` as the application proxy.
*/
void applyProxy(const QString &url)
{
auto proxyUrl = QUrl(url);
if (!proxyUrl.isValid() || proxyUrl.isEmpty())
{
qCDebug(chatterinoNetwork)
<< "Invalid or empty proxy url: " << proxyUrl;
return;
}
const auto proxy = createProxyFromUrl(proxyUrl);
QNetworkProxy::setApplicationProxy(proxy);
qCDebug(chatterinoNetwork) << "Set application proxy to" << proxy;
}
} // namespace
namespace chatterino {
void NetworkConfigurationProvider::applyFromEnv(const Env &env)
{
if (env.proxyUrl)
{
applyProxy(env.proxyUrl.get());
}
}
} // namespace chatterino

View file

@ -0,0 +1,61 @@
#pragma once
#include "common/QLogging.hpp"
#include <QNetworkProxy>
#include <websocketpp/connection.hpp>
#include <string>
namespace chatterino {
class Env;
/** This class manipulates the global network configuration (e.g. proxies). */
class NetworkConfigurationProvider
{
public:
/** This class should never be instantiated. */
NetworkConfigurationProvider() = delete;
/**
* Applies the configuration requested from the environment variables.
*
* Currently a proxy is applied if configured.
*/
static void applyFromEnv(const Env &env);
template <class C>
static void applyToWebSocket(
const std::shared_ptr<websocketpp::connection<C>> &connection)
{
const auto applicationProxy = QNetworkProxy::applicationProxy();
if (applicationProxy.type() != QNetworkProxy::HttpProxy)
{
return;
}
std::string url = "http://";
url += applicationProxy.hostName().toStdString();
url += ":";
url += std::to_string(applicationProxy.port());
websocketpp::lib::error_code ec;
connection->set_proxy(url, ec);
if (ec)
{
qCDebug(chatterinoNetwork)
<< "Couldn't set websocket proxy:" << ec.value();
return;
}
connection->set_proxy_basic_auth(
applicationProxy.user().toStdString(),
applicationProxy.password().toStdString(), ec);
if (ec)
{
qCDebug(chatterinoNetwork)
<< "Couldn't set websocket proxy auth:" << ec.value();
}
}
};
} // namespace chatterino

View file

@ -4,6 +4,7 @@
#include "common/Version.hpp"
#include "providers/liveupdates/BasicPubSubClient.hpp"
#include "providers/liveupdates/BasicPubSubWebsocket.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/PubSubHelpers.hpp"
#include "util/DebugCount.hpp"
#include "util/ExponentialBackoff.hpp"
@ -336,6 +337,8 @@ private:
return;
}
NetworkConfigurationProvider::applyToWebSocket(con);
this->websocketClient_.connect(con);
}

View file

@ -1,6 +1,7 @@
#include "providers/twitch/PubSubManager.hpp"
#include "common/QLogging.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/PubSubActions.hpp"
#include "providers/twitch/PubSubClient.hpp"
#include "providers/twitch/PubSubHelpers.hpp"
@ -514,6 +515,8 @@ void PubSub::addClient()
return;
}
NetworkConfigurationProvider::applyToWebSocket(con);
this->websocketClient.connect(con);
}