| 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 | |
| 17 | QT_BEGIN_NAMESPACE |
| 18 | |
| 19 | Q_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 | */ |
| 192 | QHttpServerRouterRule::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 | */ |
| 205 | QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d) |
| 206 | : d_ptr(d) |
| 207 | { |
| 208 | } |
| 209 | |
| 210 | /*! |
| 211 | Destroys a QHttpServerRouterRule. |
| 212 | */ |
| 213 | QHttpServerRouterRule::~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 | */ |
| 221 | const 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 | */ |
| 231 | bool 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 | */ |
| 249 | bool 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 | */ |
| 277 | bool 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 | */ |
| 292 | bool 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 ®exp = 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 | |
| 346 | QT_END_NAMESPACE |
| 347 | |