Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions defaults/application.ini
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ EnableHttpMethodOverride=false
# the client, if true.
EnableForwardedForHeader=false

# Specify IP addresses of the proxy servers to work the feature of
# X-Forwarded-For header.
# If false is specified, only the right-most IP address in X-Forwarded-For
# request header is parsed, otherwise all IP addresses in header are parsed
# from right side to left stopping at the last non-trusted IP address.
ParseForwardedForHeaderRecursively=false

# Specify trusted proxy servers to work the feature of X-Forwarded-For header.
# Set to a quoted comma-delimited list of IP addresses or subnets,
# for example: "192.168.1.10, 10.0.1.0/24, 10.0.2.0/255.255.255.0"
TrustedProxyServers=

# Sets the timeout in seconds during which a keep-alive HTTP connection
Expand Down
1 change: 1 addition & 0 deletions src/tappsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class AttributeMap : public QMap<int, QString> {
insert(Tf::EnableCsrfProtectionModule, "EnableCsrfProtectionModule");
insert(Tf::EnableHttpMethodOverride, "EnableHttpMethodOverride");
insert(Tf::EnableForwardedForHeader, "EnableForwardedForHeader");
insert(Tf::ParseForwardedForHeaderRecursively, "ParseForwardedForHeaderRecursively");
insert(Tf::TrustedProxyServers, "TrustedProxyServers");
insert(Tf::HttpKeepAliveTimeout, "HttpKeepAliveTimeout");
insert(Tf::LDPreload, "LDPreload");
Expand Down
1 change: 1 addition & 0 deletions src/tfnamespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ enum AppAttribute {
SessionCookieSameSite,
//
EnableForwardedForHeader,
ParseForwardedForHeaderRecursively,
TrustedProxyServers,
};

Expand Down
70 changes: 41 additions & 29 deletions src/thttprequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,18 @@ QList<THttpRequest> THttpRequest::generate(const QByteArray &byteArray, const QH
return reqList;
}

typedef QPair<QHostAddress, int> NetworkSubnet;

static bool isInAnySubnet(const QHostAddress &ip, const QList<NetworkSubnet> &subnets)
{
for (const auto &subnet : subnets) {
if (ip.isInSubnet(subnet.first, subnet.second)) {
return true;
}
}
return false;
}

/*!
Returns a originating IP address of the client by parsing the 'X-Forwarded-For'
header of the request. To enable this feature, edit application.ini and
Expand All @@ -611,44 +623,44 @@ QList<THttpRequest> THttpRequest::generate(const QByteArray &byteArray, const QH
QHostAddress THttpRequest::originatingClientAddress() const
{
static const bool EnableForwardedForHeader = Tf::appSettings()->value(Tf::EnableForwardedForHeader, false).toBool();
static const QStringList TrustedProxyServers = []() { // delimiter: comma or space
QStringList servers;
for (auto &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toStringList()) {
servers << s.simplified().split(QLatin1Char(' '));
}

QHostAddress ip;
for (QMutableListIterator<QString> it(servers); it.hasNext();) {
auto &s = it.next();
if (!ip.setAddress(s)) { // check IP address
it.remove();
static const bool ParseForwardedForHeaderRecursively = Tf::appSettings()->value(Tf::ParseForwardedForHeaderRecursively, false).toBool();
static const bool ListeningOnUnixDomainSocket = Tf::appSettings()->value(Tf::ListenPort).toString().trimmed().startsWith(QStringLiteral("unix:"));
static const bool TrustUnixDomainSocketProxy = true;
static const QList<NetworkSubnet> TrustProxyServersInSubnets = []() {
QList<NetworkSubnet> subnets;
for (const QString &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*,\\s*")), QString::SkipEmptyParts)) {
NetworkSubnet subnet = QHostAddress::parseSubnet(s);
if (subnet.first.protocol() != QAbstractSocket::UnknownNetworkLayerProtocol) {
subnets.append(subnet);
} else {
tSystemWarn("Invalid IP address or subnet '%s' in TrustedProxyServers parameter", qPrintable(s));
}
}
return servers;
return subnets;
}();

QString remoteHost;
if (EnableForwardedForHeader) {
if (TrustedProxyServers.isEmpty()) {
T_ONCE(tWarn("TrustedProxyServers parameter of config is empty!"));
}
QHostAddress remoteAddress = clientAddress();

auto hosts = QString::fromLatin1(header().rawHeader(QByteArrayLiteral("X-Forwarded-For"))).simplified().split(QRegularExpression("\\s?,\\s?"), QString::SkipEmptyParts);
if (hosts.isEmpty()) {
tWarn("'X-Forwarded-For' header is empty");
} else {
for (auto &proxy : TrustedProxyServers) {
hosts.removeAll(proxy);
}

if (!hosts.isEmpty()) {
remoteHost = hosts.last();
if (EnableForwardedForHeader) {
const QByteArray forwardedForHeader = header().rawHeader(QByteArrayLiteral("X-Forwarded-For"));
if (!forwardedForHeader.isEmpty()) {
QStringList hosts = QString::fromLatin1(forwardedForHeader).trimmed().split(QRegularExpression(QStringLiteral("\\s*,\\s*")));

bool trustProxy = (ListeningOnUnixDomainSocket && TrustUnixDomainSocketProxy) || isInAnySubnet(remoteAddress, TrustProxyServersInSubnets);
while (trustProxy && !hosts.isEmpty()) {
QHostAddress host;
if (!host.setAddress(hosts.takeLast()))
break;
remoteAddress = host;
if (!ParseForwardedForHeaderRecursively)
break;
trustProxy = isInAnySubnet(remoteAddress, TrustProxyServersInSubnets);
}
}
}
return (remoteHost.isEmpty()) ? clientAddress() : QHostAddress(remoteHost);
}

return remoteAddress;
}

QIODevice *THttpRequest::rawBody()
{
Expand Down