SNode.C
Loading...
Searching...
No Matches
testregex.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "database/mariadb/MariaDBClient.h"
43#include "express/legacy/in/WebApp.h"
44#include "express/tls/in/WebApp.h"
45
46#ifndef DOXYGEN_SHOULD_SKIP_THIS
47
48#include "log/Logger.h"
49
50#include <cstddef>
51#include <iostream>
52#include <mysql.h>
53#include <openssl/ssl.h>
54#include <openssl/x509v3.h>
55#include <string>
56
57// IWYU pragma: no_include <openssl/ssl3.h>
58// IWYU pragma: no_include <openssl/x509.h>
59// IWYU pragma: no_include <openssl/types.h>
60// IWYU pragma: no_include <openssl/asn1.h>
61// IWYU pragma: no_include <openssl/obj_mac.h>
62// IWYU pragma: no_include <openssl/crypto.h>
63
64#endif /* DOXYGEN_SHOULD_SKIP_THIS */
65
66using namespace express;
67
68Router router(database::mariadb::MariaDBClient& db) {
69 const Router router;
70
71 router.get(R"(/r1/:id([^/]+))", [] APPLICATION(req, res) {
72 std::cout << "Hier 1a ************" << std::endl;
73 std::cout << "Params: " << req->params["id"] << std::endl;
74 res->send("Done\n");
75 });
76
77 router.get(R"(/r1/:id/:subcollection)", [] APPLICATION(req, res) {
78 std::cout << "Hier 1b ************" << std::endl;
79 std::cout << "Params: " << req->params["id"] << std::endl;
80 std::cout << "Params: " << req->params["subcollection"] << std::endl;
81 res->send("Done\n");
82 });
83
84 router.get(R"(/r2/:id)", [] APPLICATION(req, res) {
85 std::cout << "Hier 2a ************" << std::endl;
86 std::cout << "Params: " << req->params["id"] << std::endl;
87 res->send("Done\n");
88 });
89
90 router.get(R"(/r2/:id/:subcollection)", [] APPLICATION(req, res) {
91 std::cout << "Hier 2b ************" << std::endl;
92 std::cout << "Params: " << req->params["id"] << std::endl;
93 std::cout << "Params: " << req->params["subcollection"] << std::endl;
94 res->send("Done\n");
95 });
96
97 router.get("/test/:variable(\\d)/:uri", [] APPLICATION(req, res) { // http://localhost:8080/test/1/urlstring
98 std::cout << "Hier 3 ************" << std::endl;
99 std::cout << "Params: " << req->params["variable"] << std::endl;
100 std::cout << "Params: " << req->params["uri"] << std::endl;
101 res->send("Done\n");
102 });
103
104 router
105 .get(
106 "/query/:userId",
107 [] MIDDLEWARE(req, res, next) {
108 VLOG(1) << "Move on to the next route to query database";
109 next();
110 },
111 [&db] MIDDLEWARE(req, res, next) { // http://localhost:8080/query/123
112 VLOG(1) << "UserId: " << req->params["userId"];
113 std::string userId = req->params["userId"];
114
115 req->setAttribute<std::string, "html-table">(std::string());
116
117 req->getAttribute<std::string, "html-table">([&userId](std::string& table) {
118 table = "<html>\n"
119 " <head>\n"
120 " <title>"
121 "Response from snode.c for " +
122 userId +
123 "\n"
124 " </title>\n"
125 " </head>\n"
126 " <body>\n"
127 " <h1>Return for " +
128 userId +
129 "\n"
130 " </h1>\n"
131 " <body>\n"
132 " <table border = \"1\">\n";
133 });
134
135 int i = 0;
136 db.query(
137 "SELECT * FROM snodec where username = '" + userId + "'",
138 [next, req, i](const MYSQL_ROW row) mutable {
139 if (row != nullptr) {
140 i++;
141 req->getAttribute<std::string, "html-table">([row, &i](std::string& table) {
142 table.append(" <tr>\n"
143 " <td>\n" +
144 std::to_string(i) +
145 "\n"
146 " </td>\n"
147 " <td>\n" +
148 std::string(row[0]) +
149 "\n"
150 " </td>\n"
151 " <td>\n" +
152 row[1] +
153 "\n"
154 " </td>\n"
155 " </tr>\n");
156 });
157 } else {
158 req->getAttribute<std::string, "html-table">([](std::string& table) {
159 table.append(std::string(" </table>\n"
160 " </body>\n"
161 "</html>\n"));
162 });
163 VLOG(1) << "Move on to the next route to send result";
164 next();
165 }
166 },
167 [res, userId](const std::string& errorString, unsigned int errorNumber) {
168 VLOG(1) << "Error: " << errorString << " : " << errorNumber;
169 res->status(404).send(userId + ": " + errorString + " - " + std::to_string(errorNumber));
170 });
171 },
172 [] MIDDLEWARE(req, res, next) {
173 VLOG(1) << "And again 1: Move on to the next route to send result";
174 next();
175 },
176 [] MIDDLEWARE(req, res, next) {
177 VLOG(1) << "And again 2: Move on to the next route to send result";
178 next();
179 })
180 .get([] MIDDLEWARE(req, res, next) {
181 VLOG(1) << "And again 3: Move on to the next route to send result";
182 next();
183 })
184 .get([] APPLICATION(req, res) {
185 VLOG(1) << "SendResult";
186
187 req->getAttribute<std::string, "html-table">(
188 [res](std::string& table) {
189 res->send(table);
190 },
191 [res](const std::string&) {
192 res->end();
193 });
194 });
195 router.get("/account/:userId(\\d*)/:userName", [&db] APPLICATION(req, res) { // http://localhost:8080/account/123/perfectNDSgroup
196 VLOG(1) << "Show account of";
197 VLOG(1) << "UserId: " << req->params["userId"];
198 VLOG(1) << "UserName: " << req->params["userName"];
199
200 const std::string response = "<html>"
201 " <head>"
202 " <title>Response from snode.c</title>"
203 " </head>"
204 " <body>"
205 " <h1>Regex return</h1>"
206 " <ul>"
207 " <li>UserId: " +
208 req->params["userId"] +
209 " </li>"
210 " <li>UserName: " +
211 req->params["userName"] +
212 " </li>"
213 " </ul>"
214 " </body>"
215 "</html>";
216
217 const std::string userId = req->params["userId"];
218 const std::string userName = req->params["userName"];
219
220 db.exec(
221 "INSERT INTO `snodec`(`username`, `password`) VALUES ('" + userId + "','" + userName + "')",
222 [userId, userName]() {
223 VLOG(1) << "Inserted: -> " << userId << " - " << userName;
224 },
225 [](const std::string& errorString, unsigned int errorNumber) {
226 VLOG(1) << "Error: " << errorString << " : " << errorNumber;
227 });
228
229 res->send(response);
230 });
231 router.get("/asdf/:testRegex1(d\\d{3}e)/jklö/:testRegex2", [] APPLICATION(req, res) { // http://localhost:8080/asdf/d123e/jklö/hallo
232 VLOG(1) << "Testing Regex";
233 VLOG(1) << "Regex1: " << req->params["testRegex1"];
234 VLOG(1) << "Regex2: " << req->params["testRegex2"];
235
236 const std::string response = "<html>"
237 " <head>"
238 " <title>Response from snode.c</title>"
239 " </head>"
240 " <body>"
241 " <h1>Regex return</h1>"
242 " <ul>"
243 " <li>Regex 1: " +
244 req->params["testRegex1"] +
245 " </li>"
246 " <li>Regex 2: " +
247 req->params["testRegex2"] +
248 " </li>"
249 " </ul>"
250 " </body>"
251 "</html>";
252
253 res->send(response);
254 });
255 router.get("/search/:search", [] APPLICATION(req, res) { // http://localhost:8080/search/buxtehude123
256 VLOG(1) << "Show Search of";
257 VLOG(1) << "Search: " << req->params["search"];
258 VLOG(1) << "Queries: " << req->query("test");
259
260 res->send(req->params["search"]);
261 });
262 router.use([] APPLICATION(req, res) {
263 res->status(404).send("Not found: " + req->url);
264 });
265
266 return router;
267}
268
269int main(int argc, char* argv[]) {
270 WebApp::init(argc, argv);
271
272 const database::mariadb::MariaDBConnectionDetails details = {
273 .hostname = "localhost",
274 .username = "snodec",
275 .password = "pentium5",
276 .database = "snodec",
277 .port = 3306,
278 .socket = "/run/mysqld/mysqld.sock",
279 .flags = 0,
280 };
281
282 // CREATE USER 'snodec'@localhost IDENTIFIED BY 'pentium5'
283 // GRANT ALL PRIVILEGES ON *.* TO 'snodec'@localhost
284 // GRANT ALL PRIVILEGES ON 'snodec'.'snodec' TO 'snodec'@localhost
285 // CREATE DATABASE 'snodec';
286 // CREATE TABLE 'snodec' ('username' text NOT NULL, 'password' text NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
287
288 database::mariadb::MariaDBClient db(details);
289
290 {
291 legacy::in::WebApp legacyApp("legacy-testregex");
292
293 legacyApp.use(router(db));
294
295 legacyApp.listen(8080, [](const legacy::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
296 switch (state) {
297 case core::socket::State::OK:
298 VLOG(1) << "legacy-testregex: listening on '" << socketAddress.toString() << "'";
299 break;
300 case core::socket::State::DISABLED:
301 VLOG(1) << "legacy-testregex: disabled";
302 break;
303 case core::socket::State::ERROR:
304 VLOG(1) << "legacy-testregex: error occurred";
305 break;
306 case core::socket::State::FATAL:
307 VLOG(1) << "legacy-testregex: fatal error occurred";
308 break;
309 }
310 });
311
312 legacyApp.setOnConnect([](legacy::in::WebApp::SocketConnection* socketConnection) {
313 VLOG(1) << "OnConnect:";
314
315 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
316 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
317 });
318
319 legacyApp.setOnDisconnect([](legacy::in::WebApp::SocketConnection* socketConnection) {
320 VLOG(1) << "OnDisconnect:";
321
322 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
323 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
324 });
325
326 tls::in::WebApp tlsApp("tls-testregex");
327
328 tlsApp.use(legacyApp);
329
330 tlsApp.listen(8088, [](const tls::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
331 switch (state) {
332 case core::socket::State::OK:
333 VLOG(1) << "tls-testregex: listening on '" << socketAddress.toString() << "'";
334 break;
335 case core::socket::State::DISABLED:
336 VLOG(1) << "tls-testregex: disabled";
337 break;
338 case core::socket::State::ERROR:
339 VLOG(1) << "tls-testregex: error occurred";
340 break;
341 case core::socket::State::FATAL:
342 VLOG(1) << "tls-testregex: fatal error occurred";
343 break;
344 }
345 });
346
347 tlsApp.setOnConnect([](tls::in::WebApp::SocketConnection* socketConnection) {
348 VLOG(1) << "OnConnect:";
349
350 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
351 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
352 });
353
354 tlsApp.setOnConnected([](tls::in::WebApp::SocketConnection* socketConnection) {
355 VLOG(1) << "OnConnected:";
356
357 X509* client_cert = SSL_get_peer_certificate(socketConnection->getSSL());
358
359 if (client_cert != nullptr) {
360 const long verifyErr = SSL_get_verify_result(socketConnection->getSSL());
361
362 VLOG(1) << "\tClient certificate: " + std::string(X509_verify_cert_error_string(verifyErr));
363
364 char* str = X509_NAME_oneline(X509_get_subject_name(client_cert), nullptr, 0);
365 VLOG(1) << "\t Subject: " + std::string(str);
366 OPENSSL_free(str);
367
368 str = X509_NAME_oneline(X509_get_issuer_name(client_cert), nullptr, 0);
369 VLOG(1) << "\t Issuer: " + std::string(str);
370 OPENSSL_free(str);
371
372 // We could do all sorts of certificate verification stuff here before deallocating the certificate.
373
374 GENERAL_NAMES* subjectAltNames =
375 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(client_cert, NID_subject_alt_name, nullptr, nullptr));
376#ifdef __GNUC__
377#pragma GCC diagnostic push
378#ifdef __has_warning
379#if __has_warning("-Wused-but-marked-unused")
380#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
381#endif
382#endif
383#endif
384 const int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
385#ifdef __GNUC_
386#pragma GCC diagnostic pop
387#endif
388 VLOG(1) << "\t Subject alternative name count: " << altNameCount;
389 for (int32_t i = 0; i < altNameCount; ++i) {
390#ifdef __GNUC__
391#pragma GCC diagnostic push
392#ifdef __has_warning
393#if __has_warning("-Wused-but-marked-unused")
394#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
395#endif
396#endif
397#endif
398 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
399#ifdef __GNUC_
400#pragma GCC diagnostic pop
401#endif
402 if (generalName->type == GEN_URI) {
403 const std::string subjectAltName =
404 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.uniformResourceIdentifier)),
405 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.uniformResourceIdentifier)));
406 VLOG(1) << "\t SAN (URI): '" + subjectAltName;
407 } else if (generalName->type == GEN_DNS) {
408 const std::string subjectAltName =
409 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
410 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
411 VLOG(1) << "\t SAN (DNS): '" + subjectAltName;
412 } else {
413 VLOG(1) << "\t SAN (Type): '" + std::to_string(generalName->type);
414 }
415 }
416#ifdef __GNUC__
417#pragma GCC diagnostic push
418#ifdef __has_warning
419#if __has_warning("-Wused-but-marked-unused")
420#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
421#endif
422#endif
423#endif
424 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
425#ifdef __GNUC_
426#pragma GCC diagnostic pop
427#endif
428 X509_free(client_cert);
429 } else {
430 VLOG(1) << "\tClient certificate: no certificate";
431 }
432 });
433
434 tlsApp.setOnDisconnect([](tls::in::WebApp::SocketConnection* socketConnection) {
435 VLOG(1) << "OnDisconnect:";
436
437 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
438 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
439 });
440 }
441
442 return WebApp::start();
443}
#define APPLICATION(req, res)
Definition Router.h:68
#define MIDDLEWARE(req, res, next)
Definition Router.h:63
MariaDBCommandSequence & exec(const std::string &sql, const std::function< void(void)> &onExec, const std::function< void(const std::string &, unsigned int)> &onError)
MariaDBClient(const MariaDBConnectionDetails &details)
int main(int argc, char *argv[])
Router router(database::mariadb::MariaDBClient &db)
Definition testregex.cpp:68