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#include "database/mariadb/MariaDBClient.h"
21#include "express/legacy/in/WebApp.h"
22#include "express/tls/in/WebApp.h"
23
24#ifndef DOXYGEN_SHOULD_SKIP_THIS
25
26#include "log/Logger.h"
27
28#include <cstddef>
29#include <iostream>
30#include <mysql.h>
31#include <openssl/ssl.h>
32#include <openssl/x509v3.h>
33#include <string>
34
35// IWYU pragma: no_include <openssl/ssl3.h>
36// IWYU pragma: no_include <openssl/x509.h>
37// IWYU pragma: no_include <openssl/types.h>
38// IWYU pragma: no_include <openssl/asn1.h>
39// IWYU pragma: no_include <openssl/obj_mac.h>
40// IWYU pragma: no_include <openssl/crypto.h>
41
42#endif /* DOXYGEN_SHOULD_SKIP_THIS */
43
44using namespace express;
45
47 const Router router;
48
49 router.get("/test/:variable(\\d)/:uri", [] APPLICATION(req, res) { // http://localhost:8080/test/1/urlstring
50 std::cout << "TEST" << std::endl;
51 });
52 router
53 .get(
54 "/query/:userId",
55 [] MIDDLEWARE(req, res, next) {
56 VLOG(1) << "Move on to the next route to query database";
57 next();
58 },
59 [&db] MIDDLEWARE(req, res, next) { // http://localhost:8080/query/123
60 VLOG(1) << "UserId: " << req->params["userId"];
61 std::string userId = req->params["userId"];
62
63 req->setAttribute<std::string, "html-table">(std::string());
64
65 req->getAttribute<std::string, "html-table">([&userId](std::string& table) {
66 table = "<html>\n"
67 " <head>\n"
68 " <title>"
69 "Response from snode.c for " +
70 userId +
71 "\n"
72 " </title>\n"
73 " </head>\n"
74 " <body>\n"
75 " <h1>Return for " +
76 userId +
77 "\n"
78 " </h1>\n"
79 " <body>\n"
80 " <table border = \"1\">\n";
81 });
82
83 int i = 0;
84 db.query(
85 "SELECT * FROM snodec where username = '" + userId + "'",
86 [next, req, i](const MYSQL_ROW row) mutable {
87 if (row != nullptr) {
88 i++;
89 req->getAttribute<std::string, "html-table">([row, &i](std::string& table) {
90 table.append(" <tr>\n"
91 " <td>\n" +
92 std::to_string(i) +
93 "\n"
94 " </td>\n"
95 " <td>\n" +
96 std::string(row[0]) +
97 "\n"
98 " </td>\n"
99 " <td>\n" +
100 row[1] +
101 "\n"
102 " </td>\n"
103 " </tr>\n");
104 });
105 } else {
106 req->getAttribute<std::string, "html-table">([](std::string& table) {
107 table.append(std::string(" </table>\n"
108 " </body>\n"
109 "</html>\n"));
110 });
111 VLOG(1) << "Move on to the next route to send result";
112 next();
113 }
114 },
115 [res, userId](const std::string& errorString, unsigned int errorNumber) {
116 VLOG(1) << "Error: " << errorString << " : " << errorNumber;
117 res->status(404).send(userId + ": " + errorString + " - " + std::to_string(errorNumber));
118 });
119 },
120 [] MIDDLEWARE(req, res, next) {
121 VLOG(1) << "And again 1: Move on to the next route to send result";
122 next();
123 },
124 [] MIDDLEWARE(req, res, next) {
125 VLOG(1) << "And again 2: Move on to the next route to send result";
126 next();
127 })
128 .get([] MIDDLEWARE(req, res, next) {
129 VLOG(1) << "And again 3: Move on to the next route to send result";
130 next();
131 })
132 .get([] APPLICATION(req, res) {
133 VLOG(1) << "SendResult";
134
135 req->getAttribute<std::string, "html-table">(
136 [res](std::string& table) {
137 res->send(table);
138 },
139 [res](const std::string&) {
140 res->end();
141 });
142 });
143 router.get("/account/:userId(\\d*)/:userName", [&db] APPLICATION(req, res) { // http://localhost:8080/account/123/perfectNDSgroup
144 VLOG(1) << "Show account of";
145 VLOG(1) << "UserId: " << req->params["userId"];
146 VLOG(1) << "UserName: " << req->params["userName"];
147
148 const std::string response = "<html>"
149 " <head>"
150 " <title>Response from snode.c</title>"
151 " </head>"
152 " <body>"
153 " <h1>Regex return</h1>"
154 " <ul>"
155 " <li>UserId: " +
156 req->params["userId"] +
157 " </li>"
158 " <li>UserName: " +
159 req->params["userName"] +
160 " </li>"
161 " </ul>"
162 " </body>"
163 "</html>";
164
165 const std::string userId = req->params["userId"];
166 const std::string userName = req->params["userName"];
167
168 db.exec(
169 "INSERT INTO `snodec`(`username`, `password`) VALUES ('" + userId + "','" + userName + "')",
170 [userId, userName]() {
171 VLOG(1) << "Inserted: -> " << userId << " - " << userName;
172 },
173 [](const std::string& errorString, unsigned int errorNumber) {
174 VLOG(1) << "Error: " << errorString << " : " << errorNumber;
175 });
176
177 res->send(response);
178 });
179 router.get("/asdf/:testRegex1(d\\d{3}e)/jklö/:testRegex2", [] APPLICATION(req, res) { // http://localhost:8080/asdf/d123e/jklö/hallo
180 VLOG(1) << "Testing Regex";
181 VLOG(1) << "Regex1: " << req->params["testRegex1"];
182 VLOG(1) << "Regex2: " << req->params["testRegex2"];
183
184 const std::string response = "<html>"
185 " <head>"
186 " <title>Response from snode.c</title>"
187 " </head>"
188 " <body>"
189 " <h1>Regex return</h1>"
190 " <ul>"
191 " <li>Regex 1: " +
192 req->params["testRegex1"] +
193 " </li>"
194 " <li>Regex 2: " +
195 req->params["testRegex2"] +
196 " </li>"
197 " </ul>"
198 " </body>"
199 "</html>";
200
201 res->send(response);
202 });
203 router.get("/search/:search", [] APPLICATION(req, res) { // http://localhost:8080/search/buxtehude123
204 VLOG(1) << "Show Search of";
205 VLOG(1) << "Search: " << req->params["search"];
206 VLOG(1) << "Queries: " << req->query("test");
207
208 res->send(req->params["search"]);
209 });
210 router.use([] APPLICATION(req, res) {
211 res->status(404).send("Not found: " + req->url);
212 });
213
214 return router;
215}
216
217int main(int argc, char* argv[]) {
218 WebApp::init(argc, argv);
219
220 const database::mariadb::MariaDBConnectionDetails details = {
221 .hostname = "localhost",
222 .username = "snodec",
223 .password = "pentium5",
224 .database = "snodec",
225 .port = 3306,
226 .socket = "/run/mysqld/mysqld.sock",
227 .flags = 0,
228 };
229
230 // CREATE USER 'snodec'@localhost IDENTIFIED BY 'pentium5'
231 // GRANT ALL PRIVILEGES ON *.* TO 'snodec'@localhost
232 // GRANT ALL PRIVILEGES ON 'snodec'.'snodec' TO 'snodec'@localhost
233 // CREATE DATABASE 'snodec';
234 // CREATE TABLE 'snodec' ('username' text NOT NULL, 'password' text NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
235
236 database::mariadb::MariaDBClient db(details);
237
238 {
239 legacy::in::WebApp legacyApp("legacy-testregex");
240
241 legacyApp.use(router(db));
242
243 legacyApp.listen(8080, [](const legacy::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
244 switch (state) {
245 case core::socket::State::OK:
246 VLOG(1) << "legacy-testregex: listening on '" << socketAddress.toString() << "'";
247 break;
248 case core::socket::State::DISABLED:
249 VLOG(1) << "legacy-testregex: disabled";
250 break;
251 case core::socket::State::ERROR:
252 VLOG(1) << "legacy-testregex: error occurred";
253 break;
254 case core::socket::State::FATAL:
255 VLOG(1) << "legacy-testregex: fatal error occurred";
256 break;
257 }
258 });
259
260 legacyApp.setOnConnect([](legacy::in::WebApp::SocketConnection* socketConnection) {
261 VLOG(1) << "OnConnect:";
262
263 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
264 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
265 });
266
267 legacyApp.setOnDisconnect([](legacy::in::WebApp::SocketConnection* socketConnection) {
268 VLOG(1) << "OnDisconnect:";
269
270 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
271 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
272 });
273
274 tls::in::WebApp tlsApp("tls-testregex");
275
276 tlsApp.use(legacyApp);
277
278 tlsApp.listen(8088, [](const tls::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
279 switch (state) {
280 case core::socket::State::OK:
281 VLOG(1) << "tls-testregex: listening on '" << socketAddress.toString() << "'";
282 break;
283 case core::socket::State::DISABLED:
284 VLOG(1) << "tls-testregex: disabled";
285 break;
286 case core::socket::State::ERROR:
287 VLOG(1) << "tls-testregex: error occurred";
288 break;
289 case core::socket::State::FATAL:
290 VLOG(1) << "tls-testregex: fatal error occurred";
291 break;
292 }
293 });
294
295 tlsApp.setOnConnect([](tls::in::WebApp::SocketConnection* socketConnection) {
296 VLOG(1) << "OnConnect:";
297
298 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
299 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
300 });
301
302 tlsApp.setOnConnected([](tls::in::WebApp::SocketConnection* socketConnection) {
303 VLOG(1) << "OnConnected:";
304
305 X509* client_cert = SSL_get_peer_certificate(socketConnection->getSSL());
306
307 if (client_cert != nullptr) {
308 const long verifyErr = SSL_get_verify_result(socketConnection->getSSL());
309
310 VLOG(1) << "\tClient certificate: " + std::string(X509_verify_cert_error_string(verifyErr));
311
312 char* str = X509_NAME_oneline(X509_get_subject_name(client_cert), nullptr, 0);
313 VLOG(1) << "\t Subject: " + std::string(str);
314 OPENSSL_free(str);
315
316 str = X509_NAME_oneline(X509_get_issuer_name(client_cert), nullptr, 0);
317 VLOG(1) << "\t Issuer: " + std::string(str);
318 OPENSSL_free(str);
319
320 // We could do all sorts of certificate verification stuff here before deallocating the certificate.
321
322 GENERAL_NAMES* subjectAltNames =
323 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(client_cert, NID_subject_alt_name, nullptr, nullptr));
324#ifdef __GNUC__
325#pragma GCC diagnostic push
326#ifdef __has_warning
327#if __has_warning("-Wused-but-marked-unused")
328#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
329#endif
330#endif
331#endif
332 const int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
333#ifdef __GNUC_
334#pragma GCC diagnostic pop
335#endif
336 VLOG(1) << "\t Subject alternative name count: " << altNameCount;
337 for (int32_t i = 0; i < altNameCount; ++i) {
338#ifdef __GNUC__
339#pragma GCC diagnostic push
340#ifdef __has_warning
341#if __has_warning("-Wused-but-marked-unused")
342#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
343#endif
344#endif
345#endif
346 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
347#ifdef __GNUC_
348#pragma GCC diagnostic pop
349#endif
350 if (generalName->type == GEN_URI) {
351 const std::string subjectAltName =
352 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.uniformResourceIdentifier)),
353 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.uniformResourceIdentifier)));
354 VLOG(1) << "\t SAN (URI): '" + subjectAltName;
355 } else if (generalName->type == GEN_DNS) {
356 const std::string subjectAltName =
357 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
358 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
359 VLOG(1) << "\t SAN (DNS): '" + subjectAltName;
360 } else {
361 VLOG(1) << "\t SAN (Type): '" + std::to_string(generalName->type);
362 }
363 }
364#ifdef __GNUC__
365#pragma GCC diagnostic push
366#ifdef __has_warning
367#if __has_warning("-Wused-but-marked-unused")
368#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
369#endif
370#endif
371#endif
372 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
373#ifdef __GNUC_
374#pragma GCC diagnostic pop
375#endif
376 X509_free(client_cert);
377 } else {
378 VLOG(1) << "\tClient certificate: no certificate";
379 }
380 });
381
382 tlsApp.setOnDisconnect([](tls::in::WebApp::SocketConnection* socketConnection) {
383 VLOG(1) << "OnDisconnect:";
384
385 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
386 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
387 });
388 }
389
390 return WebApp::start();
391}
#define APPLICATION(req, res)
Definition Router.h:45
#define MIDDLEWARE(req, res, next)
Definition Router.h:40
MariaDBClient(const MariaDBConnectionDetails &details)
static void init(int argc, char *argv[])
Definition WebApp.cpp:34
static int start(const utils::Timeval &timeOut={LONG_MAX, 0})
Definition WebApp.cpp:38
int main(int argc, char *argv[])
Router router(database::mariadb::MariaDBClient &db)
Definition testregex.cpp:46