1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qhttpserverrequestfilter_p.h"
6
7#include <QtCore/qdatetime.h>
8
9#include <algorithm>
10
11QT_BEGIN_NAMESPACE
12
13const int QHttpServerRequestFilterPrivate::cPeriodDurationMSec = 1000;
14
15// compromise value to remove some garbage without processing the entire array.
16static constexpr int cCleanupThreshold = 10;
17
18unsigned int QHttpServerRequestFilter::maxRequestPerPeriod() const
19{
20 return m_config.rateLimitPerSecond();
21}
22
23void QHttpServerRequestFilter::setConfiguration(const QHttpServerConfiguration &config)
24{
25 m_config = config;
26}
27
28bool QHttpServerRequestFilter::isRequestAllowed(const QHostAddress &peerAddress) const
29{
30 const auto matches = [](const QHostAddress &addr) {
31 return [&addr] (const auto &subnet) {
32 return addr.isInSubnet(subnet);
33 };
34 };
35
36 if (const auto whitelist = m_config.whitelist(); !whitelist.empty())
37 return std::any_of(first: whitelist.cbegin(), last: whitelist.cend(), pred: matches(peerAddress));
38
39 const auto blacklist = m_config.blacklist();
40 return std::none_of(first: blacklist.cbegin(), last: blacklist.cend(), pred: matches(peerAddress));
41}
42
43bool QHttpServerRequestFilter::isRequestWithinRate(const QHostAddress &peerAddress)
44{
45 return isRequestWithinRate(peerAddress, currTimeMSec: QDateTime::currentMSecsSinceEpoch());
46}
47
48bool QHttpServerRequestFilter::isRequestWithinRate(const QHostAddress &peerAddress,
49 qint64 currTimeMSec)
50{
51 using namespace QHttpServerRequestFilterPrivate;
52
53 if (m_config.rateLimitPerSecond() == 0)
54 return true;
55
56 const auto it = ipInfo.tryEmplace(key: peerAddress, args: currTimeMSec + cPeriodDurationMSec).iterator;
57
58 bool result = true;
59 if (it->isGarbage(currTime: currTimeMSec)) {
60 // did not make any requests for a whole period? start the new one.
61 it->m_thisPeriodEnd = currTimeMSec + cPeriodDurationMSec;
62 it->m_nRequests = 1;
63 } else if (currTimeMSec > it->m_thisPeriodEnd) {
64 // showed up during next period, update info
65 it->m_thisPeriodEnd += cPeriodDurationMSec;
66 it->m_nRequests = 1;
67 } else {
68 // check whether we exceeded
69 if (++it->m_nRequests > maxRequestPerPeriod())
70 result = false; // too many requests
71 }
72
73 // clean more garbage then we create
74 cleanIpInfoGarbage(it, currTime: currTimeMSec);
75
76 return result;
77}
78
79void QHttpServerRequestFilter::cleanIpInfoGarbage(QHash<QHostAddress, IpInfo>::iterator it,
80 qint64 currTime)
81{
82 Q_ASSERT(ipInfo.begin() != ipInfo.end());
83
84 const auto myIp = it.key();
85 ++it;
86 // check the range after the current ip
87 for (int i = 0; i < cCleanupThreshold; ++i) {
88 if (it == ipInfo.end())
89 it = ipInfo.begin();
90
91 if (it.key() == myIp)
92 break;
93
94 if (it->isGarbage(currTime))
95 it = ipInfo.erase(it);
96 else
97 ++it;
98 }
99}
100
101bool QHttpServerRequestFilter::IpInfo::isGarbage(qint64 currTime) const
102{
103 // ip info is garbage if we got no requests during next period
104 return (currTime >= m_thisPeriodEnd + QHttpServerRequestFilterPrivate::cPeriodDurationMSec);
105}
106
107QT_END_NAMESPACE
108

source code of qthttpserver/src/httpserver/qhttpserverrequestfilter.cpp