Skip to content

Commit

Permalink
Add HTTP & SOCKS5 proxy support (#4321)
Browse files Browse the repository at this point in the history
This can be configured using the `CHATTERINO2_PROXY_URL` environment variable.
The behaviour is similar to curl's CURLOPT_PROXY
  • Loading branch information
Nerixyz authored Feb 11, 2023
1 parent 98c2ff5 commit c9a9e44
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions src/common/Env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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"))
{
}

Expand Down
2 changes: 2 additions & 0 deletions src/common/Env.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <boost/optional.hpp>
#include <QString>

namespace chatterino {
Expand All @@ -16,6 +17,7 @@ class Env
const QString twitchServerHost;
const uint16_t twitchServerPort;
const bool twitchServerSecure;
const boost::optional<QString> proxyUrl;
};

} // namespace chatterino
1 change: 1 addition & 0 deletions src/common/QLogging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/common/QLogging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -81,6 +83,8 @@ int main(int argc, char **argv)
attachToConsole();
}

NetworkConfigurationProvider::applyFromEnv(Env::get());

IvrApi::initialize();
Helix::initialize();

Expand Down
76 changes: 76 additions & 0 deletions src/providers/NetworkConfigurationProvider.cpp
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions src/providers/NetworkConfigurationProvider.hpp
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/providers/liveupdates/BasicPubSubManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -336,6 +337,8 @@ class BasicPubSubManager
return;
}

NetworkConfigurationProvider::applyToWebSocket(con);

this->websocketClient_.connect(con);
}

Expand Down
3 changes: 3 additions & 0 deletions src/providers/twitch/PubSubManager.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -514,6 +515,8 @@ void PubSub::addClient()
return;
}

NetworkConfigurationProvider::applyToWebSocket(con);

this->websocketClient.connect(con);
}

Expand Down

0 comments on commit c9a9e44

Please sign in to comment.