1// Copyright (C) 2019 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 "qhttpserverrouter_p.h"
6
7#include <QtHttpServer/qhttpserverrouter.h>
8#include <QtHttpServer/qhttpserverrouterrule.h>
9#include <QtHttpServer/qhttpserverrequest.h>
10#include <QtHttpServer/qhttpserver.h>
11
12#include <private/qhttpserverrouterrule_p.h>
13
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/qmetatype.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_STATIC_LOGGING_CATEGORY(lcRouter, "qt.httpserver.router")
20
21using namespace Qt::StringLiterals;
22
23/*!
24 \internal
25*/
26static const QHash<QMetaType, QString> defaultConverters = {
27 { QMetaType::fromType<int>(), u"[+-]?\\d+"_s },
28 { QMetaType::fromType<long>(), u"[+-]?\\d+"_s },
29 { QMetaType::fromType<long long>(), u"[+-]?\\d+"_s },
30 { QMetaType::fromType<short>(), u"[+-]?\\d+"_s },
31
32 { QMetaType::fromType<unsigned int>(), u"[+]?\\d+"_s },
33 { QMetaType::fromType<unsigned long>(), u"[+]?\\d+"_s },
34 { QMetaType::fromType<unsigned long long>(), u"[+]?\\d+"_s },
35 { QMetaType::fromType<unsigned short>(), u"[+]?\\d+"_s },
36
37 { QMetaType::fromType<double>(), u"[+-]?(?:[0-9]+(?:[.][0-9]*)?|[.][0-9]+)"_s },
38 { QMetaType::fromType<float>(), u"[+-]?(?:[0-9]+(?:[.][0-9]*)?|[.][0-9]+)"_s },
39
40 { QMetaType::fromType<QString>(), u"[^/]+"_s },
41 { QMetaType::fromType<QByteArray>(), u"[^/]+"_s },
42
43 { QMetaType::fromType<QUrl>(), u".*"_s },
44
45 { QMetaType::fromType<void>(), u""_s },
46};
47
48/*!
49 \class QHttpServerRouter
50 \since 6.4
51 \brief Provides functions to bind a path to a \c ViewHandler.
52 \inmodule QtHttpServer
53
54 QHttpServerRouter is a rule-based system for routing HTTP requests to
55 their appropriate handlers. You can add QHttpServerRouterRule
56 instances, which define a request path and its corresponding handler.
57
58 Variable parts in the route can be specified with placeholders
59 (\c{"<arg>"}) in the request path, but it is not needed at the end. The
60 handler receives the matched values as a \l QRegularExpressionMatch. The
61 arguments can be of any type for which a \l{converters}{converter} is
62 available. The handler creation can be simplified with
63 \l QHttpServerRouterRule::bindCaptured().
64
65
66 \note A QHttpServerRouter instance must not be modifed by its rules.
67 \note This is a low-level routing API for an HTTP server.
68
69 Minimal example:
70
71 \code
72 auto pageView = [] (const quint64 page) {
73 qDebug() << "page" << page;
74 };
75 using ViewHandler = decltype(pageView);
76
77 QHttpServerRouter router;
78
79 // register callback pageView on request "/page/<number>"
80 // for example: "/page/10", "/page/15"
81 router.addRule<ViewHandler>(
82 new QHttpServerRouterRule("/page/", [=] (QRegularExpressionMatch &match,
83 const QHttpServerRequest &,
84 QHttpServerResponder &&) {
85 auto boundView = QHttpServerRouterRule::bindCaptured(pageView, match);
86
87 // it calls pageView
88 boundView();
89 }));
90 \endcode
91*/
92
93/*! \fn template <typename Type> bool QHttpServerRouter::addConverter(QAnyStringView regexp)
94
95 Adds a new converter for \e Type that can be parsed with \a regexp, and
96 returns \c true if this was successful, otherwise returns \c false. If
97 successful, the registered type can be used as argument in handlers for
98 \l{QHttpServerRouterRule}. The regular expression will be used to parse
99 the path pattern of the rule.
100
101 If there is already a converter of type \e Type, that converter's regexp
102 is replaced with \a regexp.
103
104 Custom converters can extend the available type conversions through the
105 \l QMetaType system.
106
107 Define a class with a QString constructor:
108 \snippet custom-converter/main.cpp Define class
109
110 To use a custom type with the \l{QHttpServer}{HTTP server}, register
111 it using this function and define a \l{QHttpServer::}{route} handler
112 using the new type:
113 \snippet custom-converter/main.cpp Add converter and route
114
115 \sa converters, clearConverters
116*/
117
118/*! \fn template <typename ViewHandler, typename ViewTraits = QHttpServerRouterViewTraits<ViewHandler>> QHttpServerRouterRule *QHttpServerRouter::addRule(std::unique_ptr<QHttpServerRouterRule> rule)
119 Adds a new \a rule to the router.
120
121 Returns a pointer to the new rule if successful; otherwise returns
122 \c nullptr.
123
124 Inside addRule, we determine ViewHandler arguments and generate a list of
125 their QMetaType::Type ids. Then we parse the path and replace each
126 \c{"<arg>"} with a regexp for its type from the list.
127
128 The provided \a rule must not modify the QHttpServerRouter instance.
129
130 \code
131 QHttpServerRouter router;
132
133 using ViewHandler = decltype([] (const QString &page, const quint32 num) { });
134
135 auto rule = std::make_unique<QHttpServerRouterRule>(
136 "/<arg>/<arg>/log",
137 [] (QRegularExpressionMatch &match,
138 const QHttpServerRequest &request,
139 QHttpServerResponder &&responder) {
140 });
141
142 router.addRule<ViewHandler>(std::move(rule));
143 \endcode
144*/
145
146QHttpServerRouterPrivate::QHttpServerRouterPrivate(QAbstractHttpServer *server)
147 : converters(defaultConverters), server(server)
148{}
149
150/*!
151 Creates a QHttpServerRouter object with default converters.
152
153 \sa converters
154*/
155QHttpServerRouter::QHttpServerRouter(QAbstractHttpServer *server)
156 : d_ptr(new QHttpServerRouterPrivate(server))
157{}
158
159/*!
160 Destroys a QHttpServerRouter.
161*/
162QHttpServerRouter::~QHttpServerRouter()
163{}
164
165/*!
166 Adds a new converter for \a metaType that can be parsed with \a regexp.
167 Having a converter for a \a metaType enables to use this type in a path
168 pattern of a \l QHttpServerRouterRule. The regular expression is used to
169 parse parameters of type \a metaType from the path pattern.
170
171 If there is already a converter of type \a metaType, that converter's
172 regexp is replaced with \a regexp.
173
174 \sa converters, clearConverters
175*/
176void QHttpServerRouter::addConverter(QMetaType metaType, QAnyStringView regexp)
177{
178 Q_D(QHttpServerRouter);
179 d->converters[metaType] = regexp.toString();
180}
181
182/*!
183 Removes the converter for type \a metaType.
184
185 \sa addConverter
186*/
187void QHttpServerRouter::removeConverter(QMetaType metaType)
188{
189 Q_D(QHttpServerRouter);
190 d->converters.remove(key: metaType);
191}
192
193/*!
194 Removes all converters.
195
196 \note clearConverters() does not set up default converters.
197
198 \sa converters, addConverter
199*/
200void QHttpServerRouter::clearConverters()
201{
202 Q_D(QHttpServerRouter);
203 d->converters.clear();
204}
205
206/*!
207 \fn const QHash<QMetaType, QString> &QHttpServerRouter::converters() const &
208 \fn QHash<QMetaType, QString> QHttpServerRouter::converters() &&
209
210 Returns a map of converter types and regular expressions that are registered
211 with this QHttpServerRouter. These are the types that can be used in path
212 patterns of \l{QHttpServerRouterRule}{QHttpServerRouterRules}.
213
214 The following converters are available by default:
215
216 \value QMetaType::Int
217 \value QMetaType::Long
218 \value QMetaType::LongLong
219 \value QMetaType::Short
220 \value QMetaType::UInt
221 \value QMetaType::ULong
222 \value QMetaType::ULongLong
223 \value QMetaType::UShort
224 \value QMetaType::Double
225 \value QMetaType::Float
226 \value QMetaType::QString
227 \value QMetaType::QByteArray
228 \value QMetaType::QUrl
229 \value QMetaType::Void An empty converter.
230
231 \sa addConverter, clearConverters
232*/
233const QHash<QMetaType, QString> &QHttpServerRouter::converters() const &
234{
235 Q_D(const QHttpServerRouter);
236 return d->converters;
237}
238
239QHash<QMetaType, QString> QHttpServerRouter::converters() &&
240{
241 Q_D(QHttpServerRouter);
242 return std::move(d->converters);
243}
244
245QHttpServerRouterRule *QHttpServerRouter::addRuleImpl(std::unique_ptr<QHttpServerRouterRule> rule,
246 std::initializer_list<QMetaType> metaTypes)
247{
248 Q_D(QHttpServerRouter);
249
250 if (!rule->hasValidMethods() || !rule->createPathRegexp(metaTypes, converters: d->converters)) {
251 return nullptr;
252 }
253 if (!d->verifyThreadAffinity(contextObject: rule->contextObject())) {
254 return nullptr;
255 }
256
257 return d->rules.emplace_back(args: std::move(rule)).get();
258}
259
260/*!
261 Handles each new \a request for the HTTP server using \a responder.
262
263 Iterates through the list of rules to find the first that matches,
264 then executes this rule, returning \c true. Returns \c false if no rule
265 matches the request.
266*/
267bool QHttpServerRouter::handleRequest(const QHttpServerRequest &request,
268 QHttpServerResponder &responder) const
269{
270 Q_D(const QHttpServerRouter);
271 for (const auto &rule : d->rules) {
272 if (!rule->contextObject())
273 continue;
274 if (!d->verifyThreadAffinity(contextObject: rule->contextObject()))
275 continue;
276 if (rule->exec(request, responder))
277 return true;
278 }
279
280 return false;
281}
282
283bool QHttpServerRouterPrivate::verifyThreadAffinity(const QObject *contextObject) const
284{
285 if (contextObject && (contextObject->thread() != server->thread())) {
286 qCWarning(lcRouter, "QHttpServerRouter: the context object must reside in the same thread");
287 return false;
288 }
289 return true;
290}
291
292QT_END_NAMESPACE
293

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