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 <QtHttpServer/qhttpserverrouterrule.h>
6#include <QtHttpServer/qhttpserverresponder.h>
7
8#include <private/qhttpserverrouterrule_p.h>
9#include <private/qhttpserverrequest_p.h>
10
11#include <QtCore/qmetaobject.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/qregularexpression.h>
14#include <QtCore/qstringbuilder.h>
15#include <QtCore/qdebug.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_STATIC_LOGGING_CATEGORY(lcRouterRule, "qt.httpserver.router.rule")
20
21/*!
22 \class QHttpServerRouterRule
23 \since 6.4
24 \brief The QHttpServerRouterRule is the base class for QHttpServerRouter rules.
25 \inmodule QtHttpServer
26
27 QHttpServerRouterRule defines the relationship between a request path, an
28 HTTP method, and the corresponding handler callback. A QHttpServerRouter
29 is a collection of these rules, executing the appropriate handler when a
30 request matches both the path and method. The handler is responsible for
31 generating the response.
32
33 \section1 Paths and Patterns
34
35 Each QHttpServerRouterRule includes a path or pattern that determines
36 which requests it can handle. Paths may contain placeholders that
37 are passed to the handler. The examples below illustrate path patterns
38 using the QHttpServer::route() convenience method, though they can also
39 be set using the QHttpServerRouterRule constructor.
40
41 In the simplest case the path is a string with a leading \c "/":
42 \code
43 QHttpServer server;
44 server.route("/user", [] () { return "hello user"; } );
45 \endcode
46
47 This path pattern defines a rule that directs all requests to \c "/user"
48 to the specified handler, which in this case is a simple lambda function.
49 (Note that when using QHttpServerRouterRule directly, the handler syntax
50 differs—see below.)
51
52 A trailing \c "/" in the path pattern allows the rule to match additional
53 paths with arguments after the \c "/". When using the QHttpServer::route()
54 convenience method, the argument is automatically passed to the lambda
55 function:
56 \code
57 server.route("/user/", [] ( qint64 id ) { return "hello user"; } );
58 \endcode
59 This would match request paths such as \c "/user/1", \c "/user/2", and so
60 on.
61
62 \section2 Capturing Arguments in the Path
63
64 You can place arguments anywhere in the path pattern using the \c "<arg>"
65 placeholders, and multiple of them are supported in the path:
66 \code
67 server.route("/user/<arg>/history", [] (qint64 id){ return "hello user"; } );
68 server.route("/user/<arg>/history/", [] (qint64 id, qint64 page){ return "hello user"; } );
69 \endcode
70 For example, this would match a request like \c "/user/1/history/2". Any
71 data type registered in QHttpServerRouter::converters() can be used in
72 both the callback function and the corresponding placeholders in
73 the path.
74
75 \section1 Request Method
76
77 The request method corresponds to one of the values in
78 QHttpServerRequest::Method. If no method is specified when constructing
79 a rule, it will match requests of any known method.
80
81 \section1 Handler Signature
82
83 A handler is a callback function with the following signature:
84 \code
85 void (*)(const QRegularExpressionMatch &, const QHttpServerRequest &, QHttpServerResponder &);
86 \endcode
87 \list
88 \li The first argument receives any matched capture groups from the
89 path.
90 \li The second argument contains request details.
91 \li The third argument is used to send the response.
92 \endlist
93
94 \section1 Adding Rules to QHttpServerRouter
95
96 The example below demonstrates how to create and register a new rule with
97 a handler in \l QHttpServerRouter:
98 \code
99 template<typename ViewHandler>
100 void route(const char *path, const QHttpServerRequest::Methods methods, ViewHandler &&viewHandler)
101 {
102 auto rule = std::make_unique<QHttpServerRouterRule>(
103 path, methods, [this, viewHandler = std::forward<ViewHandler>(viewHandler)]
104 (QRegularExpressionMatch &match,
105 const QHttpServerRequest &request,
106 QHttpServerResponder &responder) mutable {
107 auto boundViewHandler = QHttpServerRouterRule::bindCaptured<ViewHandler>(
108 this, std::move(viewHandler), match);
109 boundViewHandler(); // Execute the handler
110 });
111
112 // Add rule to the router
113 router.addRule<ViewHandler>(std::move(rule));
114 }
115
116 // Valid:
117 route("/user/", [] (qint64 id) { } ); // Matches "/user/1", "/user/3", etc.
118 route("/user/<arg>/history", [] (qint64 id) { } ); // Matches "/user/1/history", "/user/2/history"
119 route("/user/<arg>/history/", [] (qint64 id, qint64 page) { } ); // Matches "/user/1/history/1", "/user/2/history/2"
120 \endcode
121
122 \note This is a low-level API. For higher-level alternatives, see
123 \l QHttpServer.
124
125 \note Regular expressions are not supported in path patterns, but you can
126 use \l QHttpServerRouter::addConverter() to match \c "<arg>" to a
127 specific type.
128*/
129
130/*! \fn template <typename Functor, typename ViewTraits = QHttpServerRouterViewTraits<Functor>> static typename ViewTraits::BindableType QHttpServerRouterRule::bindCaptured(QObject *receiver, Functor &&slot, const QRegularExpressionMatch &match) const
131
132 Binds the given \a receiver and \a slot with arguments extracted from the
133 URL. The function returns a bound callable that takes any remaining
134 arguments required by the handler, supplying them to \a slot after the
135 URL-derived values.
136
137 Each captured value from the URL (as a string) is converted to the
138 corresponding parameter type in the handler based on its position,
139 ensuring it can be passed as \a match.
140
141 \code
142 QHttpServerRouter router;
143
144 auto pageView = [] (const QString &page, const quint32 num) {
145 qDebug("page: %s, num: %d", qPrintable(page), num);
146 };
147 using ViewHandler = decltype(pageView);
148
149 auto rule = std::make_unique<QHttpServerRouterRule>(
150 "/<arg>/<arg>/log",
151 [&router, &pageView] (QRegularExpressionMatch &match,
152 const QHttpServerRequest &request,
153 QHttpServerResponder &&responder) {
154 // Bind and call viewHandler with match's captured string and quint32:
155 QHttpServerRouterRule::bindCaptured(pageView, match)();
156 });
157
158 router.addRule<ViewHandler>(std::move(rule));
159 \endcode
160*/
161
162/*! \fn template <typename Functor> QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern, const QHttpServerRequest::Methods methods, const QObject *receiver, Functor &&slot)
163
164 Creates a routing rule for \a pathPattern and \a methods, connecting it
165 to the specified \a receiver and \a slot.
166 \list
167 \li The \a slot can be a function pointer, non-mutable lambda, or any
168 other copyable callable with \c const call operator.
169 \li If \a slot is callable, \a receiver acts as its context object.
170 \li The handler remains valid until the \a receiver is destroyed.
171 \endlist
172 The rule can handle any combination of available HTTP methods.
173
174 \sa QHttpServerRequest::Methods
175*/
176
177/*! \fn template <typename Functor> QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern, const QObject *receiver, Functor &&slot)
178
179 \overload
180
181 This overload constructs a routing rule for \a pathPattern and associates
182 it with \a receiver and \a slot.
183 \list
184 \li It defaults to \l QHttpServerRequest::Method::AnyKnown, meaning
185 it will match any recognized HTTP method.
186 \li The \a slot can be a function pointer, non-mutable lambda, or any
187 other copyable callable with \c const call operator.
188 \li If \a slot is callable, \a receiver acts as its context object.
189 \li The handler remains valid until the \a receiver is destroyed.
190 \endlist
191*/
192QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern,
193 const QHttpServerRequest::Methods methods,
194 const QObject *context,
195 QtPrivate::QSlotObjectBase *slotObjRaw)
196 : QHttpServerRouterRule(new QHttpServerRouterRulePrivate{
197 .pathPattern: pathPattern, .methods: methods, .routerHandler: QtPrivate::SlotObjUniquePtr(slotObjRaw), .context: QPointer(context), .pathRegexp: {}})
198{
199 Q_ASSERT(slotObjRaw);
200}
201
202/*!
203 \internal
204*/
205QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d)
206 : d_ptr(d)
207{
208}
209
210/*!
211 Destroys a QHttpServerRouterRule.
212*/
213QHttpServerRouterRule::~QHttpServerRouterRule()
214{
215}
216
217/*!
218 Retrieves the context object associated with this rule. This object
219 serves as the receiver responsible for handling the request.
220*/
221const QObject *QHttpServerRouterRule::contextObject() const
222{
223 Q_D(const QHttpServerRouterRule);
224 return d->context;
225}
226
227/*!
228 Validates the Request Method. Returns \c true if the specified HTTP
229 method is valid.
230*/
231bool QHttpServerRouterRule::hasValidMethods() const
232{
233 Q_D(const QHttpServerRouterRule);
234 return d->methods & QHttpServerRequest::Method::AnyKnown;
235}
236
237/*!
238 Executes the rule. Processes the given \a request by checking if it
239 matches this rule.
240 \list
241 \li This function is called by QHttpServerRouter when a new
242 \a request is received.
243 \li If the \a request matches the rule, it handles the request by
244 sending a response through the provided \a responder and returns
245 \c true.
246 \li If there is no match, it returns \c false.
247 \endlist
248*/
249bool QHttpServerRouterRule::exec(const QHttpServerRequest &request,
250 QHttpServerResponder &responder) const
251{
252 Q_D(const QHttpServerRouterRule);
253 if (!d->routerHandler)
254 return false;
255
256 QRegularExpressionMatch match;
257 if (!matches(request, match: &match))
258 return false;
259
260 void *args[] = { nullptr, &match, const_cast<QHttpServerRequest *>(&request), &responder };
261 Q_ASSERT(d->routerHandler);
262 d->routerHandler->call(r: nullptr, a: args);
263 return true;
264}
265
266/*!
267 Determines whether the provided \a request meets the conditions of this
268 rule.
269 \list
270 \li This virtual function is called by \l exec() to evaluate the
271 \a request.
272 \li If the request matches, the details are stored in \a match
273 (which \e{must not} be \c nullptr), and the function returns \c true.
274 \li Otherwise, it returns \c false.
275 \endlist
276*/
277bool QHttpServerRouterRule::matches(const QHttpServerRequest &request,
278 QRegularExpressionMatch *match) const
279{
280 Q_D(const QHttpServerRouterRule);
281
282 if (d->methods && !(d->methods & request.method()))
283 return false;
284
285 *match = d->pathRegexp.match(subject: request.url().path());
286 return (match->hasMatch() && d->pathRegexp.captureCount() == match->lastCapturedIndex());
287}
288
289/*!
290 \internal
291*/
292bool QHttpServerRouterRule::createPathRegexp(std::initializer_list<QMetaType> metaTypes,
293 const QHash<QMetaType, QString> &converters)
294{
295 Q_D(QHttpServerRouterRule);
296
297 QString pathRegexp = d->pathPattern;
298 const QLatin1StringView arg("<arg>");
299 for (auto metaType : metaTypes) {
300 if (metaType.id() >= QMetaType::User
301 && !QMetaType::hasRegisteredConverterFunction(fromType: QMetaType::fromType<QString>(), toType: metaType)) {
302 qCWarning(lcRouterRule,
303 "%s has not registered a converter to QString. "
304 "Use QHttpServerRouter::addConveter<Type>(converter).",
305 metaType.name());
306 return false;
307 }
308
309 auto it = converters.constFind(key: metaType);
310 if (it == converters.end()) {
311 qCWarning(lcRouterRule, "Can not find converter for type: %s", metaType.name());
312 return false;
313 }
314
315 if (it->isEmpty())
316 continue;
317
318 const auto index = pathRegexp.indexOf(s: arg);
319 const QString &regexp = QLatin1Char('(') % *it % QLatin1Char(')');
320 if (index == -1)
321 pathRegexp.append(s: regexp);
322 else
323 pathRegexp.replace(i: index, len: arg.size(), after: regexp);
324 }
325
326 if (pathRegexp.indexOf(s: arg) != -1) {
327 qCWarning(lcRouterRule) << "not enough types or one of the types is not supported, regexp:"
328 << pathRegexp
329 << ", pattern:" << d->pathPattern
330 << ", types:" << metaTypes;
331 return false;
332 }
333
334 if (!pathRegexp.startsWith(c: QLatin1Char('^')))
335 pathRegexp = QLatin1Char('^') % pathRegexp;
336 if (!pathRegexp.endsWith(c: QLatin1Char('$')))
337 pathRegexp += u'$';
338
339 qCDebug(lcRouterRule) << "url pathRegexp:" << pathRegexp;
340
341 d->pathRegexp.setPattern(pathRegexp);
342 d->pathRegexp.optimize();
343 return true;
344}
345
346QT_END_NAMESPACE
347

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