LCOV - code coverage report
Current view: top level - extra/webserver/webserver/server - SPWebHost.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 289 681 42.4 %
Date: 2024-05-12 00:16:13 Functions: 59 105 56.2 %

          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             : }

Generated by: LCOV version 1.14