Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWebHost.h"
24 : #include "SPWebHostController.h"
25 : #include "SPWebHostComponent.h"
26 : #include "SPWebRequestController.h"
27 : #include "SPWebResourceHandler.h"
28 : #include "SPWebWebsocketConnection.h"
29 : #include "SPWebAsyncTask.h"
30 : #include "SPWebRoot.h"
31 : #include "SPWebRequest.h"
32 : #include "SPWebTools.h"
33 : #include "SPWebDbd.h"
34 :
35 : #include "SPDbUser.h"
36 : #include "SPValid.h"
37 :
38 : namespace STAPPLER_VERSIONIZED stappler::web {
39 :
40 6350 : static HostController *getHostFromContext(pool_t *p, uint32_t tag, const void *ptr) {
41 6350 : switch (tag) {
42 150 : case uint32_t(config::TAG_HOST): return (HostController *)ptr; break;
43 4700 : case uint32_t(config::TAG_REQUEST): return ((RequestController *)ptr)->getHost(); break;
44 650 : case uint32_t(config::TAG_WEBSOCKET): return ((WebsocketConnection *)ptr)->getHost().getController(); break;
45 : }
46 850 : return nullptr;
47 : }
48 :
49 5500 : Host Host::getCurrent() {
50 5500 : HostController *ret = nullptr;
51 5500 : pool::foreach_info(&ret, [] (void *ud, pool_t *p, uint32_t tag, const void *data) -> bool {
52 6350 : auto ptr = getHostFromContext(p, tag, data);
53 6350 : if (ptr) {
54 5500 : *((HostController **)ud) = ptr;
55 5500 : return false;
56 : }
57 850 : return true;
58 : });
59 :
60 11000 : return Host(ret);
61 : }
62 :
63 100 : Host::Host() : _config(nullptr) { }
64 17425 : Host::Host(HostController *cfg) : _config(cfg) { }
65 :
66 0 : Host & Host::operator =(HostController *cfg) {
67 0 : _config = cfg;
68 0 : return *this;
69 : }
70 :
71 50 : Host::Host(Host &&other) : _config(other._config) { }
72 :
73 0 : Host & Host::operator =(Host &&other) {
74 0 : _config = other._config;
75 0 : return *this;
76 : }
77 :
78 1050 : Host::Host(const Host &other) : _config(other._config) { }
79 :
80 100 : Host & Host::operator =(const Host &other) {
81 100 : _config = other._config;
82 100 : return *this;
83 : }
84 :
85 25 : void Host::handleChildInit(pool_t *rootPool) {
86 300 : perform([&, this] {
87 25 : _config->handleChildInit(*this, rootPool);
88 :
89 25 : filesystem::mkdir(filepath::merge<Interface>(_config->_hostInfo.documentRoot, ".reports"));
90 25 : filesystem::mkdir(filepath::merge<Interface>(_config->_hostInfo.documentRoot, "uploads"));
91 :
92 25 : _config->_currentComponent = StringView("root");
93 25 : tools::registerTools(config::TOOLS_SERVER_PREFIX, *this);
94 25 : _config->_currentComponent = StringView();
95 :
96 25 : addProtectedLocation("/.reports");
97 25 : addProtectedLocation("/uploads");
98 :
99 25 : AsyncTask::perform(*this, [&, this] (AsyncTask &task) {
100 25 : task.addExecuteFn([serv = *this] (const AsyncTask &task) -> bool {
101 25 : serv.processReports();
102 25 : return true;
103 : });
104 25 : });
105 50 : }, rootPool, config::TAG_HOST, _config);
106 25 : }
107 :
108 : enum class HostReportType {
109 : Crash,
110 : Update,
111 : Error,
112 : };
113 :
114 : template <typename Callback> static
115 0 : void Host_prepareEmail(HostController *cfg, Callback &&cb, HostReportType type) {
116 : /*StringStream data;
117 : auto &webhookInfo = cfg->getWebhookInfo();
118 : if (!webhookInfo.url.empty() && !webhookInfo.name.empty()) {
119 : auto &from = webhookInfo.name;
120 : auto &to = webhookInfo.extra.getString("to");
121 : auto &title = webhookInfo.extra.getString("title");
122 :
123 : NetworkHandle notify;
124 : notify.init(network::Method::Smtp, webhookInfo.url);
125 : notify.setAuthority(from, webhookInfo.extra.getString("password"));
126 : notify.setMailFrom(from);
127 : notify.addMailTo(to);
128 :
129 : data << "From: " << from << " <" << from << ">\r\n"
130 : << "Content-Type: text/plain; charset=utf-8\r\n"
131 : << "To: " << to << " <" << to << ">\r\n";
132 :
133 : switch (type) {
134 : case HostReportType::Crash:
135 : data << "Subject: Serenity Crash report";
136 : break;
137 : case HostReportType::Update:
138 : data << "Subject: Serenity Update report";
139 : break;
140 : case HostReportType::Error:
141 : data << "Subject: Serenity Error report";
142 : break;
143 : }
144 :
145 : if (!title.empty()) {
146 : data << " (" << title << ")";
147 : }
148 : data << "\r\n\r\n";
149 :
150 : cb(data);
151 :
152 : StringView r(data.data(), data.size());
153 : notify.setSendCallback([&] (char *buf, size_t size) -> size_t {
154 : auto writeSize = std::min(size, r.size());
155 : memcpy(buf, r.data(), writeSize);
156 : r.offset(writeSize);
157 : return writeSize;
158 : }, data.size());
159 :
160 : notify.perform();
161 : }*/
162 0 : }
163 :
164 25 : void Host::processReports() const {
165 25 : if (_config->getWebhookInfo().format != "email") {
166 25 : return;
167 : }
168 :
169 0 : Vector<Pair<StringView, HostReportType>> crashFiles;
170 0 : String path = filepath::absolute<Interface>(".reports", true);
171 0 : filesystem::ftw(path, [&] (const StringView &view, bool isFile) {
172 0 : if (isFile) {
173 0 : StringView r(view);
174 0 : r.skipString(path);
175 0 : if (r.starts_with("/crash.")) {
176 0 : crashFiles.emplace_back(view, HostReportType::Crash);
177 0 : } else if (r.starts_with("/update.")) {
178 0 : crashFiles.emplace_back(view, HostReportType::Update);
179 : }
180 : }
181 0 : });
182 :
183 0 : Vector<Pair<String, HostReportType>> crashData;
184 0 : for (auto &it : crashFiles) {
185 0 : crashData.emplace_back(filesystem::readTextFile<Interface>(it.first), it.second);
186 0 : filesystem::remove(it.first);
187 : }
188 :
189 0 : for (auto &it : crashData) {
190 0 : Host_prepareEmail(_config, [&] (StringStream &data) {
191 : data << it.first << "\r\n";
192 : }, it.second);
193 : }
194 0 : }
195 :
196 575 : void Host::performWithStorage(const Callback<void(const db::Transaction &)> &cb, bool openNewConnecton) const {
197 575 : if (!openNewConnecton) {
198 575 : if (auto t = db::Transaction::acquireIfExists()) {
199 25 : cb(t);
200 25 : return;
201 : }
202 : }
203 :
204 550 : auto targetPool = pool::acquire();
205 550 : perform([&, this] {
206 550 : auto handle = _config->openConnection(targetPool, false);
207 550 : if (handle.get()) {
208 550 : _config->_dbDriver->performWithStorage(handle, [&] (const db::Adapter &a) {
209 550 : if (auto t = db::Transaction::acquire(a)) {
210 1100 : cb(t);
211 550 : t.release();
212 : }
213 550 : });
214 550 : _config->closeConnection(handle);
215 : }
216 550 : }, targetPool);
217 : }
218 :
219 2850 : db::BackendInterface *Host::acquireDbForRequest(const Request &req) const {
220 2850 : if (_config->_customDbd) {
221 2850 : auto handle = _config->_customDbd->openConnection(req.pool());
222 2850 : if (handle.get()) {
223 2850 : pool::cleanup_register(req.pool(), [handle, dbd = _config->_customDbd] {
224 2850 : dbd->closeConnection(handle);
225 2850 : });
226 :
227 2850 : return _config->_dbDriver->acquireInterface(handle, req.pool());
228 : }
229 : } else {
230 0 : auto handle = getRoot()->dbdAcquire(req);
231 0 : if (handle.get()) {
232 0 : return _config->_dbDriver->acquireInterface(handle, req.pool());
233 : }
234 : }
235 0 : return nullptr;
236 : }
237 :
238 0 : bool Host::setHostKey(BytesView priv) const {
239 0 : auto key = crypto::PrivateKey(priv);
240 0 : if (key) {
241 0 : setHostKey(move(key));
242 0 : return true;
243 : }
244 0 : return false;
245 0 : }
246 :
247 0 : void Host::setHostKey(crypto::PrivateKey &&priv) const {
248 0 : _config->setHostKey(move(priv));
249 0 : }
250 :
251 0 : const crypto::PublicKey &Host::getHostPublicKey() const {
252 0 : return _config->_hostPubKey;
253 : }
254 :
255 0 : const crypto::PrivateKey &Host::getHostPrivateKey() const {
256 0 : return _config->_hostPrivKey;
257 : }
258 :
259 0 : BytesView Host::getHostSecret() const {
260 0 : return _config->_hostSecret;
261 : }
262 :
263 0 : void Host::addSourceRoot(StringView file) {
264 0 : _config->_sourceRoot.emplace_back(file.pdup(_config->_rootPool));
265 0 : }
266 :
267 0 : void Host::addComponentByParams(StringView str) {
268 0 : HostComponentType type = HostComponentType::Dso;
269 :
270 0 : StringView r(str);
271 0 : if (r.starts_with("wasm:") || r.starts_with("WASM:")) {
272 0 : log::error("webserver::Host", "Wasm component defined as regular");
273 0 : return;
274 : }
275 :
276 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
277 :
278 0 : StringView handlerParams;
279 0 : if (r.is('"')) {
280 0 : ++ r;
281 0 : handlerParams = r.readUntil<StringView::Chars<'"'>>();
282 0 : if (r.is('"')) {
283 0 : ++ r;
284 : }
285 : } else {
286 0 : handlerParams = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
287 : }
288 :
289 0 : StringView args[3];
290 0 : int64_t idx = 0;
291 0 : while (!handlerParams.empty() && idx < 3) {
292 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
293 0 : args[idx] = handlerParams.readUntil<StringView::Chars<':'>>();
294 0 : if (handlerParams.is(':')) {
295 0 : ++ handlerParams;
296 : }
297 0 : ++ idx;
298 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
299 : }
300 :
301 0 : if (idx == 1) {
302 0 : HostComponentInfo h;
303 0 : h.type = type;
304 0 : h.symbol = args[0].pdup(_config->_rootPool);
305 0 : h.name = h.symbol;
306 0 : _config->_componentsToLoad.emplace_back(std::move(h));
307 0 : } else if (idx >= 2) {
308 0 : HostComponentInfo h;
309 0 : h.type = type;
310 0 : h.symbol = args[idx - 1].pdup(_config->_rootPool);
311 0 : h.file = args[idx - 2].pdup(_config->_rootPool);
312 :
313 0 : if (idx == 3) {
314 0 : h.name = args[0].pdup(_config->_rootPool);
315 : }
316 :
317 0 : while (!r.empty()) {
318 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
319 0 : StringView params, n, v;
320 0 : if (r.is('"')) {
321 0 : ++ r;
322 0 : params = r.readUntil<StringView::Chars<'"'>>();
323 0 : if (r.is('"')) {
324 0 : ++ r;
325 : }
326 : } else {
327 0 : params = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
328 : }
329 :
330 0 : if (!params.empty()) {
331 0 : n = params.readUntil<StringView::Chars<'='>>();
332 0 : ++ params;
333 0 : v = params;
334 :
335 0 : if (!n.empty()) {
336 0 : if (v.empty()) {
337 0 : h.data.setBool(true, n);
338 : } else {
339 0 : h.data.setString(v, n);
340 : }
341 : }
342 : }
343 : }
344 :
345 0 : _config->_componentsToLoad.emplace_back(std::move(h));
346 0 : }
347 : }
348 :
349 0 : void Host::addWasmComponentByParams(StringView path, StringView command) {
350 0 : HostComponentInfo h;
351 0 : h.type = HostComponentType::Wasm;
352 0 : h.file = path.pdup();
353 :
354 0 : auto name = command.readUntil<StringView::Chars<'#'>>();
355 0 : if (command.is('#')) {
356 0 : ++ command;
357 : } else {
358 0 : log::error("webserver::Host", "Wasm component function name is missed: ", path);
359 0 : return;
360 : }
361 :
362 0 : auto c = command.readUntil<StringView::Chars<'?'>>();
363 0 : if (command.is('?')) {
364 0 : ++ command;
365 0 : if (command.is('(')) {
366 0 : h.data = data::read<Interface>(command);
367 : } else {
368 0 : h.data = data::readUrlencoded<Interface>(command, 256);
369 : }
370 : }
371 :
372 0 : h.name = name.pdup();
373 0 : h.symbol = c.pdup();
374 :
375 0 : _config->_componentsToLoad.emplace_back(std::move(h));
376 0 : }
377 :
378 0 : void Host::addAllow(StringView ips) {
379 0 : ips.split<StringView::CharGroup<CharGroupId::WhiteSpace>>([&, this] (StringView r) {
380 0 : _config->addAllowed(r);
381 0 : });
382 :
383 0 : }
384 :
385 0 : void Host::setSessionParams(StringView str) {
386 0 : StringView r(str);
387 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
388 0 : while (!r.empty()) {
389 0 : StringView params, n, v;
390 0 : if (r.is('"')) {
391 0 : ++ r;
392 0 : params = r.readUntil<StringView::Chars<'"'>>();
393 0 : if (r.is('"')) {
394 0 : ++ r;
395 : }
396 : } else {
397 0 : params = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
398 : }
399 :
400 0 : if (!params.empty()) {
401 0 : n = params.readUntil<StringView::Chars<'='>>();
402 0 : ++ params;
403 0 : v = params;
404 :
405 0 : if (!n.empty() && ! v.empty()) {
406 0 : _config->setSessionParam(n, v);
407 : }
408 : }
409 :
410 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
411 : }
412 0 : }
413 :
414 0 : void Host::setHostSecret(StringView w) {
415 0 : if (!w.empty()) {
416 0 : _config->setHostSecret(w);
417 : }
418 0 : }
419 :
420 0 : void Host::setWebHookParams(StringView str) {
421 0 : StringView r(str);
422 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
423 0 : while (!r.empty()) {
424 0 : StringView params, n, v;
425 0 : if (r.is('"')) {
426 0 : ++ r;
427 0 : params = r.readUntil<StringView::Chars<'"'>>();
428 0 : if (r.is('"')) {
429 0 : ++ r;
430 : }
431 : } else {
432 0 : params = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
433 : }
434 :
435 0 : if (!params.empty()) {
436 0 : n = params.readUntil<StringView::Chars<'='>>();
437 0 : ++ params;
438 0 : v = params;
439 :
440 0 : if (!n.empty() && ! v.empty()) {
441 0 : _config->setWebhookParam(n, v);
442 : }
443 : }
444 :
445 0 : r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
446 : }
447 0 : }
448 :
449 0 : void Host::setProtectedList(StringView str) {
450 0 : str.split<StringView::Chars<' '>>([&, this] (StringView &value) {
451 0 : addProtectedLocation(value);
452 0 : });
453 0 : }
454 :
455 0 : void Host::setDbParams(StringView w) {
456 0 : _config->setDbParams(w);
457 0 : }
458 :
459 50 : void Host::addProtectedLocation(const StringView &value) {
460 50 : _config->_protectedList.emplace(value.pdup(_config->_rootPool));
461 50 : }
462 :
463 0 : void Host::setForceHttps() {
464 0 : _config->setForceHttps();
465 0 : }
466 :
467 100 : pool_t *Host::getThreadPool() const {
468 100 : return _config->_rootPool;
469 : }
470 :
471 2275 : const HostInfo &Host::getHostInfo() const {
472 2275 : return _config->getHostInfo();
473 : }
474 :
475 575 : const SessionInfo &Host::getSessionInfo() const {
476 575 : return _config->getSessionInfo();
477 : }
478 :
479 399 : Root *Host::getRoot() const {
480 399 : return _config->getRoot();
481 : }
482 :
483 350 : pug::Cache *Host::getPugCache() const {
484 350 : return &_config->_pugCache;
485 : }
486 :
487 0 : db::sql::Driver *Host::getDbDriver() const {
488 0 : return _config->_dbDriver;
489 : }
490 :
491 : template <typename T>
492 3100 : auto Host_resolvePath(Map<StringView, T> &map, const StringView &path) -> typename Map<StringView, T>::iterator {
493 3100 : auto it = map.begin();
494 3100 : auto ret = map.end();
495 59525 : for (; it != map.end(); it ++) {
496 56575 : auto &p = it->first;
497 56575 : if (p.size() - 1 <= path.size()) {
498 43700 : if (p.back() == '/') {
499 39450 : if (p.size() == 1 || (path.starts_with(StringView(p).sub(0, p.size() - 1))
500 39450 : && (path.size() == p.size() - 1 || path.at(p.size() - 1) == '/' ))) {
501 2900 : if (ret == map.end() || ret->first.size() < p.size()) {
502 2900 : ret = it;
503 : }
504 : }
505 7250 : } else if (p == path) {
506 150 : ret = it;
507 150 : break;
508 : }
509 : }
510 : }
511 3100 : return ret;
512 : }
513 :
514 0 : void Host::checkBroadcasts() {
515 0 : AsyncTask::perform(*this, [&, this] (AsyncTask &task) {
516 0 : task.addExecuteFn([this] (const AsyncTask &task) -> bool {
517 0 : task.performWithStorage([this] (const db::Transaction &t) {
518 0 : _config->_broadcastId = t.getAdapter().getBackendInterface()->processBroadcasts([&, this] (BytesView bytes) {
519 0 : handleBroadcast(bytes);
520 0 : }, _config->_broadcastId);
521 0 : });
522 0 : return true;
523 : });
524 0 : });
525 0 : }
526 :
527 0 : void Host::handleHeartBeat(pool_t *pool) {
528 0 : perform([&, this] {
529 0 : auto now = Time::now();
530 0 : if (!_config->_loadingFalled) {
531 0 : if (now - _config->_lastDatabaseCleanup > config::DEFAULT_DATABASE_CLEANUP_INTERVAL) {
532 0 : db::sql::Driver::Handle handle = _config->openConnection(pool, false);
533 0 : if (handle.get()) {
534 0 : _config->_dbDriver->performWithStorage(handle, [&, this] (const db::Adapter &a) {
535 0 : _config->_lastDatabaseCleanup = now;
536 0 : a.makeSessionsCleanup();
537 0 : });
538 0 : _config->closeConnection(handle);
539 : }
540 : }
541 :
542 0 : for (auto &it : _config->_components) {
543 0 : it.second->handleHeartbeat(*this);
544 : }
545 : }
546 0 : if (now - _config->_lastTemplateUpdate > config::DEFAULT_PUG_UPDATE_INTERVAL) {
547 0 : if (!_config->_pugCache.isNotifyAvailable()) {
548 0 : _config->_pugCache.update(pool);
549 : }
550 0 : _config->_lastTemplateUpdate = now;
551 : }
552 0 : }, pool, config::TAG_HOST, _config);
553 0 : }
554 :
555 0 : void Host::handleBroadcast(const Value &val) {
556 0 : if (val.getBool("system")) {
557 0 : _config->_root->handleBroadcast(val);
558 0 : return;
559 : }
560 :
561 0 : if (!val.hasValue("data")) {
562 0 : return;
563 : }
564 :
565 : /*if (val.getBool("message") && !val.getBool("exclusive")) {
566 : String url = String(config::getServerToolsPrefix()) + config::getServerToolsShell();
567 : auto it = Host_resolvePath(_config->_websockets, url);
568 : if (it != _config->_websockets.end() && it->second) {
569 : it->second->receiveBroadcast(val);
570 : }
571 : }
572 :
573 : auto &url = val.getString("url");
574 : if (!url.empty()) {
575 : auto it = Host_resolvePath(_config->_websockets, url);
576 : if (it != _config->_websockets.end() && it->second) {
577 : it->second->receiveBroadcast(val.getValue("data"));
578 : }
579 : }*/
580 : }
581 :
582 0 : void Host::handleBroadcast(const BytesView &bytes) {
583 0 : handleBroadcast(data::read<Interface>(bytes));
584 0 : }
585 :
586 525 : bool Host::isSecureAuthAllowed(const Request &rctx) const {
587 525 : auto userIp = rctx.getInfo().useragentIp;
588 525 : if (rctx.isSecureConnection() || strncmp(userIp.data(), "127.", 4) == 0 || userIp == "::1") {
589 525 : return true;
590 : }
591 :
592 0 : if (auto ip = valid::readIp(userIp)) {
593 0 : for (auto &it : _config->_allowedIps) {
594 0 : if (ip >= it.first && ip <= it.second) {
595 0 : return true;
596 : }
597 : }
598 : }
599 :
600 0 : return false;
601 : }
602 :
603 500 : static bool Host_processAuth(Request &rctx, StringView auth) {
604 500 : StringView r(auth);
605 500 : r.skipChars<StringView::WhiteSpace>();
606 500 : auto method = r.readUntil<StringView::WhiteSpace>().str<Interface>();
607 500 : string::apply_tolower_c(method);
608 500 : if (method == "basic" && rctx.config()->isSecureAuthAllowed()) {
609 500 : r.skipChars<StringView::WhiteSpace>();
610 500 : auto str = stappler::base64::decode<Interface>(r);
611 500 : StringView source((const char *)str.data(), str.size());
612 500 : StringView user = source.readUntil<StringView::Chars<':'>>();
613 500 : if (source.is(':')) {
614 500 : ++ source;
615 :
616 500 : if (!user.empty() && !source.empty()) {
617 500 : if (rctx.performWithStorage([&] (const db::Transaction &t) {
618 500 : auto u = db::User::get(t, user, source);
619 500 : if (u) {
620 500 : rctx.setUser(u);
621 500 : if (u->isAdmin()) {
622 500 : auto &args = rctx.getInfo().queryData;
623 500 : if (args.hasValue("__FORCE_ROLE__")) {
624 0 : auto role = args.getInteger("__FORCE_ROLE__");
625 0 : rctx.setAccessRole(db::AccessRoleId(role));
626 : }
627 : }
628 500 : return true;
629 : }
630 0 : return false;
631 : })) {
632 500 : return true;
633 : }
634 : }
635 : }
636 500 : } else if (method == "pkey") {
637 0 : r.skipChars<StringView::WhiteSpace>();
638 0 : auto d = stappler::data::read<Interface>(stappler::base64::decode<Interface>(r));
639 0 : if (d.isArray() && d.size() == 2 && d.isBytes(0) && d.isBytes(1)) {
640 0 : auto &key = d.getBytes(0);
641 0 : auto &sig = d.getBytes(1);
642 :
643 0 : crypto::PublicKey pk;
644 :
645 : do {
646 0 : if (key.size() < 128 || sig.size() < 128) {
647 0 : break;
648 : }
649 :
650 0 : if (memcmp(key.data(), "ssh-", 4) == 0) {
651 0 : if (!pk.importOpenSSH(StringView((const char *)key.data(), key.size()))) {
652 0 : break;
653 : }
654 : } else {
655 0 : if (!pk.import(key)) {
656 0 : break;
657 : }
658 : }
659 :
660 0 : if (!pk.verify(key, sig, crypto::SignAlgorithm::RSA_SHA512)) {
661 0 : break;
662 : }
663 :
664 0 : bool complete = false;
665 0 : pk.exportDer([&] (BytesView data) {
666 0 : rctx.performWithStorage([&] (const db::Transaction &t) {
667 0 : if (auto u = db::User::get(t, *rctx.host().getUserScheme(), data)) {
668 0 : rctx.setUser(u);
669 0 : complete = true;
670 : }
671 0 : return false;
672 : });
673 0 : });
674 0 : if (complete) {
675 0 : return true;
676 : }
677 : } while (0);
678 0 : }
679 0 : }
680 0 : return false;
681 500 : }
682 :
683 3050 : static Status Host_onRequestRecieved(Request &rctx, RequestHandler &h) {
684 3050 : auto auth = rctx.getRequestHeader("Authorization");
685 3050 : if (!auth.empty()) {
686 500 : Host_processAuth(rctx, auth);
687 : }
688 :
689 3050 : auto origin = rctx.getRequestHeader("Origin");
690 3050 : if (origin.empty()) {
691 3050 : return OK;
692 : }
693 :
694 0 : if (rctx.getInfo().method != RequestMethod::Options) {
695 : // non-preflightted request
696 0 : if (h.isCorsPermitted(rctx, origin)) {
697 0 : rctx.setResponseHeader("Access-Control-Allow-Origin", origin);
698 0 : rctx.setResponseHeader("Access-Control-Allow-Credentials", "true");
699 :
700 0 : rctx.setErrorHeader("Access-Control-Allow-Origin", origin);
701 0 : rctx.setErrorHeader("Access-Control-Allow-Credentials", "true");
702 0 : return OK;
703 : } else {
704 0 : return HTTP_METHOD_NOT_ALLOWED;
705 : }
706 : } else {
707 0 : auto method = rctx.getRequestHeader("Access-Control-Request-Method");
708 0 : auto headers = rctx.getRequestHeader("Access-Control-Request-Headers");
709 :
710 0 : if (h.isCorsPermitted(rctx, origin, true, method, headers)) {
711 0 : rctx.setResponseHeader("Access-Control-Allow-Origin", origin);
712 0 : rctx.setResponseHeader("Access-Control-Allow-Credentials", "true");
713 :
714 0 : auto c_methods = h.getCorsAllowMethods(rctx);
715 0 : if (!c_methods.empty()) {
716 0 : rctx.setResponseHeader("Access-Control-Allow-Methods", c_methods);
717 0 : } else if (!method.empty()) {
718 0 : rctx.setResponseHeader("Access-Control-Allow-Methods", method);
719 : }
720 :
721 0 : auto c_headers = h.getCorsAllowHeaders(rctx);
722 0 : if (!c_headers.empty()) {
723 0 : rctx.setResponseHeader("Access-Control-Allow-Headers", c_headers);
724 0 : } else if (!headers.empty()) {
725 0 : rctx.setResponseHeader("Access-Control-Allow-Headers", headers);
726 : }
727 :
728 0 : auto c_maxAge = h.getCorsMaxAge(rctx);
729 0 : if (!c_maxAge.empty()) {
730 0 : rctx.setResponseHeader("Access-Control-Max-Age", c_maxAge);
731 : }
732 :
733 0 : return DONE;
734 : } else {
735 0 : return HTTP_METHOD_NOT_ALLOWED;
736 : }
737 : }
738 : }
739 :
740 3100 : Status Host::handleRequest(Request &req) {
741 3100 : if (_config->_forceHttps) {
742 0 : StringView uri(req.getInfo().url.path);
743 0 : if (uri.starts_with("/.well-known/acme-challenge/")) {
744 0 : auto path = filepath::merge<Interface>(_config->getHostInfo().documentRoot, uri);
745 0 : if (filesystem::exists(path)) {
746 0 : req.setFilename(path);
747 0 : return DONE;
748 : }
749 0 : }
750 :
751 0 : if (!req.isSecureConnection()) {
752 0 : auto p = req.getInfo().url.port;
753 0 : if (p.empty() || p == "80") {
754 0 : return req.redirectTo(toString("https://", req.getInfo().url.host, req.getInfo().unparserUri));
755 0 : } else if (p == "8080") {
756 0 : return req.redirectTo(toString("https://", req.getInfo().url.host, ":8443", req.getInfo().unparserUri));
757 : } else {
758 0 : return req.redirectTo(toString("https://", req.getInfo().url.host, ":", p, req.getInfo().unparserUri));
759 : }
760 : }
761 : }
762 :
763 3100 : if (_config->_loadingFalled) {
764 0 : return HTTP_SERVICE_UNAVAILABLE;
765 : }
766 :
767 3100 : auto path = req.getInfo().url.path;
768 :
769 3100 : if (!_config->_protectedList.empty()) {
770 3100 : StringView path_v(path);
771 3100 : auto lb_it = _config->_protectedList.lower_bound(path);
772 3100 : if (lb_it != _config->_protectedList.end() && path_v == *lb_it) {
773 0 : return HTTP_NOT_FOUND;
774 : } else {
775 3100 : -- lb_it;
776 3100 : StringView lb_v(*lb_it);
777 3100 : if (path_v.is(lb_v)) {
778 0 : if (path_v.size() == lb_v.size() || lb_v.back() == '/' || (path_v.size() > lb_v.size() && path_v[lb_v.size()] == '/')) {
779 0 : return HTTP_NOT_FOUND;
780 : }
781 : }
782 : }
783 : }
784 :
785 : // Websocket handshake
786 3100 : auto connection = req.getRequestHeader("connection").ptolower_c(req.pool());
787 3100 : auto upgrade = req.getRequestHeader("upgrade").ptolower_c(req.pool());
788 3100 : if (connection.find("upgrade") != String::npos && upgrade == "websocket") {
789 : // try websocket
790 0 : auto it = Host_resolvePath(_config->_websockets, path);
791 0 : if (it != _config->_websockets.end() && it->second) {
792 0 : auto auth = req.getRequestHeader("Authorization");
793 0 : if (!auth.empty()) {
794 0 : Host_processAuth(req, auth);
795 : }
796 0 : return it->second->accept(req);
797 : }
798 0 : return DECLINED;
799 : }
800 :
801 3100 : for (auto &it : _config->_preRequest) {
802 0 : auto ret = it(req);
803 0 : if (ret == DONE || ret > 0) {
804 0 : return ret;
805 : }
806 : }
807 :
808 3100 : auto ret = Host_resolvePath(_config->_requests, path);
809 3100 : if (ret != _config->_requests.end() && (ret->second.callback || ret->second.map)) {
810 3050 : StringView subPath((ret->first.back() == '/')?path.sub(ret->first.size() - 1):"");
811 3050 : StringView originPath = subPath.size() == 0 ? StringView(path) : StringView(ret->first);
812 3050 : if (originPath.back() == '/' && !subPath.empty()) {
813 2125 : originPath = StringView(originPath).sub(0, originPath.size() - 1);
814 : }
815 :
816 3050 : RequestHandler *h = nullptr;
817 3050 : if (ret->second.map) {
818 100 : h = ret->second.map->onRequest(req, subPath);
819 2950 : } else if (ret->second.callback) {
820 2950 : h = ret->second.callback();
821 : }
822 3050 : if (h) {
823 3050 : auto role = h->getAccessRole();
824 3050 : if (role != db::AccessRoleId::Nobody) {
825 0 : req.setAccessRole(role);
826 : }
827 :
828 3050 : Status preflight = h->onRequestRecieved(req, move(originPath), move(subPath), ret->second.data);
829 3050 : if (preflight > 0 || preflight == DONE) {
830 0 : req.getController()->startResponseTransmission();
831 0 : return preflight;
832 : }
833 :
834 3050 : preflight = Host_onRequestRecieved(req, *h);
835 3050 : if (preflight > 0 || preflight == DONE) {
836 0 : req.getController()->startResponseTransmission();
837 0 : return preflight;
838 : }
839 3050 : req.setRequestHandler(h);
840 : }
841 : } else {
842 50 : if (path.size() > 1 && path.back() == '/') {
843 0 : auto name = path.sub(0, path.size() - 1);
844 0 : auto it = _config->_requests.find(name);
845 0 : if (it != _config->_requests.end()) {
846 0 : return req.redirectTo(name);
847 : }
848 : }
849 : }
850 :
851 3100 : auto &data = req.getInfo().queryData;
852 3100 : if (data.hasValue("basic_auth")) {
853 0 : if (req.getController()->isSecureAuthAllowed()) {
854 0 : if (req.getAuthorizedUser()) {
855 0 : return req.redirectTo(req.getInfo().url.url);
856 : }
857 0 : return HTTP_UNAUTHORIZED;
858 : }
859 : }
860 :
861 3100 : return OK;
862 : }
863 :
864 3425 : void Host::initTransaction(db::Transaction &t) {
865 3425 : _config->initTransaction(t);
866 3425 : }
867 :
868 0 : CompressionInfo *Host::getCompressionConfig() const {
869 0 : return &_config->_compression;
870 : }
871 :
872 400 : String Host::getDocumentRootPath(StringView sub) const {
873 400 : if (sub.empty()) {
874 0 : return _config->_hostInfo.documentRoot.str<Interface>();
875 : } else {
876 400 : return filepath::merge<Interface>(_config->_hostInfo.documentRoot, sub);
877 : }
878 : }
879 :
880 0 : HostComponent *Host::getHostComponent(const StringView &name) const {
881 0 : auto it = _config->_components.find(name);
882 0 : if (it != _config->_components.end()) {
883 0 : return it->second;
884 : }
885 0 : return nullptr;
886 : }
887 :
888 0 : HostComponent *Host::getHostComponent(std::type_index name) const {
889 0 : auto it = _config->_typedComponents.find(name);
890 0 : if (it != _config->_typedComponents.end()) {
891 0 : return it->second;
892 : }
893 0 : return nullptr;
894 : }
895 :
896 0 : void Host::addComponentWithName(const StringView &name, HostComponent *comp) {
897 0 : _config->_components.emplace(name, comp);
898 0 : _config->_typedComponents.emplace(std::type_index(typeid(*comp)), comp);
899 0 : if (_config->_childInit) {
900 0 : comp->handleChildInit(*this);
901 : }
902 0 : }
903 :
904 350 : const Map<StringView, HostComponent *> &Host::getComponents() const {
905 350 : return _config->_components;
906 : }
907 :
908 0 : void Host::addPreRequest(Function<Status(Request &)> &&req) const {
909 0 : _config->_preRequest.emplace_back(std::move(req));
910 0 : }
911 :
912 175 : void Host::addHandler(StringView path, const HandlerCallback &cb, const Value &d) const {
913 175 : if (!path.empty() && path.front() == '/') {
914 175 : _config->_requests.emplace(path.pdup(_config->_rootPool),
915 350 : RequestSchemeInfo{_config->_currentComponent, cb, d});
916 : }
917 175 : }
918 150 : void Host::addResourceHandler(StringView path, const db::Scheme &scheme) const {
919 150 : path = path.pdup(_config->_rootPool);
920 150 : if (!path.empty() && path.front() == '/') {
921 300 : _config->_requests.emplace(path,
922 150 : RequestSchemeInfo{_config->_currentComponent,
923 4050 : [s = &scheme] () -> RequestHandler * {
924 2025 : return new ResourceHandler(*s, Value());
925 : }, Value(), &scheme});
926 : }
927 150 : auto it = _config->_resources.find(&scheme);
928 150 : if (it == _config->_resources.end()) {
929 125 : _config->_resources.emplace(&scheme, ResourceSchemeInfo{path, Value()});
930 : }
931 150 : }
932 :
933 100 : void Host::addResourceHandler(StringView path, const db::Scheme &scheme, const Value &val) const {
934 100 : path = path.pdup(_config->_rootPool);
935 100 : if (!path.empty() && path.front() == '/') {
936 200 : _config->_requests.emplace(path,
937 100 : RequestSchemeInfo{_config->_currentComponent,
938 200 : [s = &scheme, val] () -> RequestHandler * {
939 100 : return new ResourceHandler(*s, val);
940 : }, Value(), &scheme});
941 : }
942 100 : auto it = _config->_resources.find(&scheme);
943 100 : if (it == _config->_resources.end()) {
944 25 : _config->_resources.emplace(&scheme, ResourceSchemeInfo{path, val});
945 : }
946 100 : }
947 :
948 25 : void Host::addMultiResourceHandler(StringView path, std::initializer_list<Pair<const StringView, const db::Scheme *>> &&schemes) const {
949 25 : if (!path.empty() && path.front() == '/') {
950 25 : path = path.pdup(_config->_rootPool);
951 50 : _config->_requests.emplace(path,
952 50 : RequestSchemeInfo{_config->_currentComponent,
953 25 : [s = Map<StringView, const db::Scheme *>(move(schemes))] () -> RequestHandler * {
954 25 : return new ResourceMultiHandler(s);
955 : }, Value()});
956 : }
957 25 : }
958 :
959 0 : void Host::addHandler(std::initializer_list<StringView> paths, const HandlerCallback &cb, const Value &d) const {
960 0 : for (auto &it : paths) {
961 0 : if (!it.empty() && it.front() == '/') {
962 0 : _config->_requests.emplace(it.pdup(_config->_rootPool),
963 0 : RequestSchemeInfo{_config->_currentComponent, cb, d});
964 : }
965 : }
966 0 : }
967 :
968 25 : void Host::addHandler(StringView path, const RequestHandlerMap *map) const {
969 25 : if (!path.empty() && path.front() == '/') {
970 25 : path = path.pdup(_config->_rootPool);
971 50 : _config->_requests.emplace(path,
972 25 : RequestSchemeInfo{_config->_currentComponent, nullptr, Value(), nullptr, map});
973 : }
974 25 : }
975 :
976 0 : void Host::addHandler(std::initializer_list<StringView> paths, const RequestHandlerMap *map) const {
977 0 : for (auto &it : paths) {
978 0 : if (!it.empty() && it.front() == '/') {
979 0 : _config->_requests.emplace(it.pdup(_config->_rootPool),
980 0 : RequestSchemeInfo{_config->_currentComponent, nullptr, Value(), nullptr, map});
981 : }
982 : }
983 0 : }
984 :
985 25 : void Host::addWebsocket(StringView str, WebsocketManager *m) const {
986 25 : _config->_websockets.emplace(str.pdup(_config->_rootPool), m);
987 25 : }
988 :
989 200 : const db::Scheme * Host::exportScheme(const db::Scheme &scheme) const {
990 200 : _config->_schemes.emplace(scheme.getName(), &scheme);
991 200 : return &scheme;
992 : }
993 :
994 2525 : const db::Scheme * Host::getScheme(const StringView &name) const {
995 2525 : auto it = _config->_schemes.find(name);
996 2525 : if (it != _config->_schemes.end()) {
997 2500 : return it->second;
998 : }
999 25 : return nullptr;
1000 : }
1001 :
1002 875 : const db::Scheme * Host::getFileScheme() const {
1003 875 : return getScheme(config::FILE_SCHEME_NAME);
1004 : }
1005 :
1006 1100 : const db::Scheme * Host::getUserScheme() const {
1007 1100 : return getScheme(config::USER_SCHEME_NAME);
1008 : }
1009 :
1010 125 : const db::Scheme * Host::getErrorScheme() const {
1011 125 : return getScheme(config::ERROR_SCHEME_NAME);
1012 : }
1013 :
1014 0 : db::Scheme * Host::getMutable(const db::Scheme *s) const {
1015 0 : if (!_config->_childInit) {
1016 0 : return const_cast<db::Scheme *>(s);
1017 : }
1018 0 : return nullptr;
1019 : }
1020 :
1021 0 : StringView Host::getResourcePath(const db::Scheme &scheme) const {
1022 0 : auto it = _config->_resources.find(&scheme);
1023 0 : if (it != _config->_resources.end()) {
1024 0 : return it->second.path;
1025 : }
1026 0 : return String();
1027 : }
1028 :
1029 50 : const Map<StringView, const db::Scheme *> &Host::getSchemes() const {
1030 50 : return _config->_schemes;
1031 : }
1032 0 : const Map<const db::Scheme *, ResourceSchemeInfo> &Host::getResources() const {
1033 0 : return _config->_resources;
1034 : }
1035 :
1036 50 : const Map<StringView, RequestSchemeInfo> &Host::getRequestHandlers() const {
1037 50 : return _config->_requests;
1038 : }
1039 :
1040 : struct Host_ErrorList {
1041 : RequestController *request;
1042 : Vector<Value> errors;
1043 : };
1044 :
1045 : struct Host_ErrorReporterFlags {
1046 : bool isProtected = false;
1047 : };
1048 :
1049 75 : void Host::reportError(const Value &d) {
1050 75 : if (auto obj = pool::get<Host_ErrorReporterFlags>("Host_ErrorReporterFlags")) {
1051 0 : if (obj->isProtected) {
1052 0 : return;
1053 : }
1054 : }
1055 75 : if (auto req = Request::getCurrent()) {
1056 75 : perform([&] {
1057 75 : if (auto objVal = Request(req).getObject<Host_ErrorList>("Host_ErrorList")) {
1058 25 : objVal->errors.emplace_back(d);
1059 : } else {
1060 50 : auto obj = new Host_ErrorList{req.config()};
1061 50 : obj->errors.emplace_back(d);
1062 50 : Request rctx(req);
1063 50 : rctx.storeObject(obj, "Host_ErrorList", [obj] {
1064 50 : Host(obj->request->getHost()).runErrorReportTask(Request(obj->request), obj->errors);
1065 50 : });
1066 50 : }
1067 75 : }, req.pool());
1068 : } else {
1069 0 : runErrorReportTask(Request(nullptr), Vector<Value>{d});
1070 75 : }
1071 : }
1072 :
1073 100 : bool Host::performTask(AsyncTask *task, bool performFirst) const {
1074 100 : return _config->_root->performTask(*this, task, performFirst);
1075 : }
1076 :
1077 0 : bool Host::scheduleTask(AsyncTask *task, TimeInterval t) const {
1078 0 : return _config->_root->scheduleTask(*this, task, t);
1079 : }
1080 :
1081 50 : void Host::runErrorReportTask(const Request &req, const Vector<Value> &errors) {
1082 50 : if (errors.empty()) {
1083 0 : return;
1084 : }
1085 50 : if (!_config->_childInit) {
1086 0 : for (auto &it : errors) {
1087 0 : std::cout << "[Error]: " << data::EncodeFormat::Pretty << it << "\n";
1088 : }
1089 0 : return;
1090 : }
1091 :
1092 50 : if (_config->_loadingFalled) {
1093 0 : return;
1094 : }
1095 :
1096 50 : AsyncTask::perform(Host(*this), [&, this, c = req.getController()] (AsyncTask &task) {
1097 50 : Value *err = nullptr;
1098 50 : if (c) {
1099 650 : err = new Value {
1100 100 : pair("documentRoot", Value(getHostInfo().documentRoot)),
1101 100 : pair("name", Value(getHostInfo().hostname)),
1102 100 : pair("url", Value(toString(req.getInfo().url.host, req.getInfo().unparserUri))),
1103 100 : pair("request", Value(req.getInfo().requestLine)),
1104 100 : pair("ip", Value(req.getInfo().useragentIp)),
1105 100 : pair("time", Value(Time::now().toMicros()))
1106 350 : };
1107 :
1108 50 : c->foreachRequestHeaders([&] (StringView key, StringView value) {
1109 250 : err->emplace("headers").setString(value, key);
1110 250 : });
1111 : } else {
1112 0 : err = new Value {
1113 0 : pair("documentRoot", Value(getHostInfo().documentRoot)),
1114 0 : pair("name", Value(getHostInfo().hostname)),
1115 0 : pair("time", Value(Time::now().toMicros()))
1116 0 : };
1117 : }
1118 50 : auto &d = err->emplace("data");
1119 125 : for (auto &it : errors) {
1120 75 : d.addValue(it);
1121 : }
1122 50 : task.addExecuteFn([err] (const AsyncTask &task) -> bool {
1123 50 : Host_ErrorReporterFlags *obj = pool::get<Host_ErrorReporterFlags>("Host_ErrorReporterFlags");
1124 50 : if (obj) {
1125 0 : obj->isProtected = true;
1126 : } else {
1127 50 : obj = new Host_ErrorReporterFlags{true};
1128 50 : pool::store(obj, "Host_ErrorReporterFlags");
1129 : }
1130 :
1131 50 : if (task.getHost()._config->_loadingFalled) {
1132 0 : return false;
1133 : }
1134 :
1135 50 : task.performWithStorage([&] (const db::Transaction &t) {
1136 50 : t.performAsSystem([&] () -> bool {
1137 50 : if (auto errScheme = task.getHost().getErrorScheme()) {
1138 100 : if (errScheme->create(t, *err)) {
1139 50 : return true;
1140 : }
1141 : }
1142 0 : std::cout << "Fail to report error: " << *err << "\n";
1143 0 : return false;
1144 : });
1145 50 : });
1146 50 : return true;
1147 : });
1148 50 : });
1149 : }
1150 :
1151 : }
|