LCOV - code coverage report
Current view: top level - extra/webserver/webserver/server - SPWebHostController.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 129 279 46.2 %
Date: 2024-05-12 00:16:13 Functions: 15 38 39.5 %

          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 "SPWebHostController.h"
      24             : #include "SPWebHostComponent.h"
      25             : #include "SPWebWasmComponent.h"
      26             : #include "SPWebHost.h"
      27             : #include "SPWebRoot.h"
      28             : #include "SPWebDbd.h"
      29             : 
      30             : #include "SPValid.h"
      31             : #include "SPDbFieldExtensions.h"
      32             : #include "SPJsonWebToken.h"
      33             : #include "SPDso.h"
      34             : #include "SPWebVirtualFile.h"
      35             : #include "SPPugCache.h"
      36             : 
      37             : namespace STAPPLER_VERSIONIZED stappler::web {
      38             : 
      39           0 : HostController::~HostController() { }
      40             : 
      41          25 : HostController::HostController(Root *root, pool_t *pool)
      42          25 : : _root(root), _rootPool(pool)
      43             : #if DEBUG
      44          25 : , _pugCache(pug::Template::Options::getPretty(), [this] (const StringView &str) { handleTemplateError(str); })
      45             : #else
      46             : , _pugCache(pug::Template::Options::getDefault(), [this] (const StringView &str) { handleTemplateError(str); })
      47             : #endif
      48             : {
      49          25 :         _rootPool = memory::pool::acquire();
      50          25 :         auto d = VirtualFile::getList();
      51         350 :         for (auto &it : d) {
      52         325 :                 if (it.name.ends_with(".pug") || it.name.ends_with(".spug")) {
      53         200 :                         _pugCache.addTemplate(toString("virtual:/", it.name), it.content.str<Interface>());
      54             :                 } else {
      55         125 :                         _pugCache.addContent(toString("virtual:/", it.name), it.content.str<Interface>());
      56             :                 }
      57             :         }
      58             : 
      59          25 :         memset(_hostSecret.data(), 0, _hostSecret.size());
      60          25 : }
      61             : 
      62          25 : db::Scheme HostController::makeUserScheme() const {
      63             :         return db::Scheme(config::USER_SCHEME_NAME, {
      64          50 :                 db::Field::Text("name", db::Transform::Alias, db::Flags::Required),
      65          50 :                 db::Field::Bytes("pubkey", db::Transform::PublicKey, db::Flags::Indexed),
      66          50 :                 db::Field::Password("password", db::PasswordSalt(config::DEFAULT_PASSWORD_SALT), db::Flags::Required | db::Flags::Protected),
      67          50 :                 db::Field::Boolean("isAdmin", Value(false)),
      68          75 :                 db::Field::Extra("data", Vector<db::Field>({
      69          50 :                         db::Field::Text("email", db::Transform::Email),
      70          50 :                         db::Field::Text("public"),
      71          50 :                         db::Field::Text("desc"),
      72             :                 })),
      73          50 :                 db::Field::Text("email", db::Transform::Email, db::Flags::Unique),
      74          50 :         });
      75             : }
      76             : 
      77          25 : db::Scheme HostController::makeFileScheme() const {
      78             :         return db::Scheme(config::FILE_SCHEME_NAME, {
      79          50 :                 db::Field::Text("location", db::Transform::Url),
      80          50 :                 db::Field::Text("type", db::Flags::ReadOnly),
      81          50 :                 db::Field::Integer("size", db::Flags::ReadOnly),
      82          50 :                 db::Field::Integer("mtime", db::Flags::AutoMTime | db::Flags::ReadOnly),
      83          50 :                 db::Field::Extra("image", Vector<db::Field>{
      84          50 :                         db::Field::Integer("width"),
      85          50 :                         db::Field::Integer("height"),
      86             :                 })
      87          50 :         });
      88             : }
      89             : 
      90          25 : db::Scheme HostController::makeErrorScheme() const {
      91             :         return db::Scheme(config::ERROR_SCHEME_NAME, {
      92          50 :                 db::Field::Boolean("hidden", Value(false)),
      93          50 :                 db::Field::Boolean("delivered", Value(false)),
      94          50 :                 db::Field::Text("name"),
      95          50 :                 db::Field::Text("documentRoot"),
      96          50 :                 db::Field::Text("url"),
      97          50 :                 db::Field::Text("request"),
      98          50 :                 db::Field::Text("ip"),
      99          50 :                 db::Field::Data("headers"),
     100          50 :                 db::Field::Data("data"),
     101          50 :                 db::Field::Integer("time"),
     102         100 :                 db::Field::Custom(new db::FieldTextArray("tags", db::Flags::Indexed,
     103          50 :                                 db::DefaultFn([&] (const Value &data) -> Value {
     104          50 :                         Vector<String> tags;
     105         125 :                         for (auto &it : data.getArray("data")) {
     106          75 :                                 auto text = it.getString("source");
     107          75 :                                 if (!text.empty()) {
     108          75 :                                         emplace_ordered(tags, text);
     109             :                                 }
     110          75 :                         }
     111             : 
     112          50 :                         Value ret;
     113         125 :                         for (auto &it : tags) {
     114          75 :                                 ret.addString(it);
     115             :                         }
     116         100 :                         return ret;
     117         100 :                 })))
     118          50 :         });
     119             : }
     120             : 
     121          25 : bool HostController::loadComponent(const Host &serv, const HostComponentInfo &info) {
     122          25 :         switch (info.type) {
     123          25 :         case HostComponentType::Dso:
     124          25 :                 return loadDsoComponent(serv, info);
     125             :                 break;
     126           0 :         case HostComponentType::Wasm:
     127           0 :                 return loadWasmComponent(serv, info);
     128             :                 break;
     129             :         }
     130           0 :         return false;
     131             : }
     132             : 
     133          25 : void HostController::initComponents(const Host &serv, const Vector<HostComponentInfo> &val) {
     134          50 :         for (auto &it : val) {
     135          25 :                 loadComponent(serv, it);
     136             :         }
     137          25 : }
     138             : 
     139           0 : void HostController::initSession(const Value &val) {
     140           0 :         _session.init(val);
     141           0 : }
     142             : 
     143           0 : void HostController::initWebhook(const Value &val) {
     144           0 :         _webhook.init(val);
     145           0 : }
     146             : 
     147           0 : void HostController::setSessionParam(StringView n, StringView v) {
     148           0 :         _session.setParam(n, v);
     149           0 : }
     150           0 : void HostController::setWebhookParam(StringView n, StringView v) {
     151           0 :         _webhook.setParam(n, v);
     152           0 : }
     153             : 
     154           0 : void HostController::setForceHttps() {
     155           0 :         _forceHttps = true;
     156           0 : }
     157             : 
     158           0 : void HostController::setHostSecret(StringView w) {
     159           0 :         if (w.starts_with("sha2:")) {
     160           0 :                 w += "sha2:"_len;
     161           0 :                 _hostSecret = crypto::Gost3411_512::hmac(w, w);
     162           0 :         } else if (w.starts_with("gost:")) {
     163           0 :                 w += "gost:"_len;
     164           0 :                 _hostSecret = crypto::Gost3411_512::hmac(w, w);
     165             :         } else {
     166           0 :                 _hostSecret = crypto::Gost3411_512::hmac(w, w);
     167             :         }
     168           0 : }
     169             : 
     170           0 : void HostController::setHostKey(crypto::PrivateKey &&key) {
     171           0 :         _hostPrivKey = move(key);
     172           0 :         _hostPubKey = _hostPrivKey.exportPublic();
     173           0 : }
     174             : 
     175           0 : void HostController::addAllowed(StringView r) {
     176           0 :         auto p = valid::readIpRange(r);
     177           0 :         if (p.first && p.second) {
     178           0 :                 _allowedIps.emplace_back(p.first, p.second);
     179             :         }
     180           0 : }
     181             : 
     182          25 : void HostController::init(const Host &serv) {
     183          25 :         _defaultUserScheme = makeUserScheme();
     184          25 :         _defaultFileScheme = makeFileScheme();
     185          25 :         _defaultErrorScheme = makeErrorScheme();
     186             : 
     187          25 :         _schemes.emplace(_defaultUserScheme.getName(), &_defaultUserScheme);
     188          25 :         _schemes.emplace(_defaultFileScheme.getName(), &_defaultFileScheme);
     189          25 :         _schemes.emplace(_defaultErrorScheme.getName(), &_defaultErrorScheme);
     190             : 
     191          25 :         if (!_componentsToLoad.empty()) {
     192          25 :                 initComponents(serv, _componentsToLoad);
     193             :         }
     194          25 : }
     195             : 
     196           0 : bool HostController::initKeyPair(const Host &serv, const db::Adapter &a, BytesView fp) {
     197           0 :         if (_hostPrivKey.generate(crypto::KeyType::GOST3410_2012_512)) {
     198           0 :                 _hostPubKey = _hostPrivKey.exportPublic();
     199             :         }
     200             : 
     201           0 :         Bytes priv;
     202           0 :         _hostPrivKey.exportPem([&] (BytesView data) {
     203           0 :                 priv = data.bytes<Interface>();
     204           0 :         });
     205             : 
     206           0 :         crypto::PrivateKey tmpKey(config::INTERNAL_PRIVATE_KEY);
     207             : 
     208           0 :         auto tok = AesToken<Interface>::create(AesToken<Interface>::Keys{ nullptr, &tmpKey, BytesView(_hostSecret) });
     209           0 :         tok.setBytes(move(priv), "priv");
     210           0 :         if (auto d = tok.exportData(AesToken<Interface>::Fingerprint(crypto::HashFunction::GOST_3411, fp))) {
     211             :                 std::array<uint8_t, string::Sha512::Length + 4> data;
     212           0 :                 memcpy(data.data(), "srv:", 4);
     213           0 :                 memcpy(data.data() + 4, fp.data(), fp.size());
     214             : 
     215           0 :                 a.set(data, d, TimeInterval::seconds(60 * 60 * 365 * 100)); // 100 years
     216           0 :                 return true;
     217           0 :         }
     218           0 :         return false;
     219           0 : }
     220             : 
     221           0 : void HostController::initHostKeys(const Host &serv, const db::Adapter &a) {
     222           0 :         if (_hostPrivKey) {
     223           0 :                 return;
     224             :         }
     225             : 
     226           0 :         auto fp = crypto::Gost3411_512::hmac(_hostInfo.hostname, _hostSecret);
     227             : 
     228             :         std::array<uint8_t, string::Sha512::Length + 4> data;
     229           0 :         memcpy(data.data(), "srv:", 4);
     230           0 :         memcpy(data.data() + 4, fp.data(), fp.size());
     231             : 
     232           0 :         if (auto d = a.get(data)) {
     233           0 :                 crypto::PrivateKey tmpKey(config::INTERNAL_PRIVATE_KEY);
     234           0 :                 if (auto tok = AesToken<Interface>::parse(d,
     235           0 :                                 AesToken<Interface>::Fingerprint(crypto::HashFunction::GOST_3411, BytesView(fp)),
     236           0 :                                 AesToken<Interface>::Keys{ nullptr, &tmpKey, BytesView(_hostSecret) })) {
     237           0 :                         auto &d = tok.getBytes("priv");
     238           0 :                         _hostPrivKey.import(d);
     239           0 :                         if (_hostPrivKey) {
     240           0 :                                 _hostPubKey = _hostPrivKey.exportPublic();
     241             :                         }
     242           0 :                 }
     243           0 :         }
     244             : 
     245           0 :         if (!_hostPubKey) {
     246           0 :                 initKeyPair(serv, a, fp);
     247             :         }
     248             : }
     249             : 
     250          25 : void HostController::handleChildInit(const Host &host, pool_t *p) {
     251          25 :         _rootPool = p;
     252          50 :         for (auto &it : _components) {
     253          25 :                 _currentComponent = it.second->getName();
     254          25 :                 it.second->handleChildInit(host);
     255          25 :                 _currentComponent = StringView();
     256             :         }
     257             : 
     258          25 :         _childInit = true;
     259             : 
     260          25 :         auto pool = getCurrentPool();
     261             : 
     262             :         db::sql::Driver::Handle db;
     263          25 :         if (!_dbParams.empty()) {
     264             :                 // run custom dbd
     265          25 :                 _customDbd = DbdModule::create(_rootPool, _root, move(_dbParams));
     266          25 :                 _dbDriver = _customDbd->getDriver();
     267          25 :                 db = _customDbd->openConnection(pool);
     268             :         } else {
     269             :                 // setup apache httpd dbd
     270           0 :                 db = _root->dbdOpen(pool, host);
     271           0 :                 if (db.get()) {
     272           0 :                         _dbDriver = openInternalDriver(db);
     273             :                 } else {
     274           0 :                         log::error("web::HostController", "fail to open internal DB connection");
     275             :                 }
     276             :         }
     277             : 
     278          25 :         if (!_schemes.empty() && !db.get()) {
     279           0 :                 _loadingFalled = true;
     280             :         }
     281             : 
     282          25 :         if (!_loadingFalled) {
     283          25 :                 db::Scheme::initSchemes(_schemes);
     284             : 
     285          25 :                 perform_temporary([&, this] {
     286          25 :                         _dbDriver->performWithStorage(db, [&, this] (const db::Adapter &storage) {
     287          50 :                                 storage.init(db::BackendInterface::Config{StringView(_hostInfo.hostname), host.getFileScheme()}, _schemes);
     288             : 
     289          25 :                                 if (_hostSecret != string::Sha512::Buf{0}) {
     290           0 :                                         initHostKeys(host, storage);
     291             :                                 }
     292             : 
     293          50 :                                 for (auto &it : _components) {
     294          25 :                                         _currentComponent = it.second->getName();
     295          25 :                                         it.second->handleStorageInit(host, storage);
     296          25 :                                         _currentComponent = String();
     297             :                                 }
     298          25 :                         });
     299          25 :                 }, p);
     300             :         }
     301             : 
     302          25 :         if (db.get()) {
     303          25 :                 if (_customDbd) {
     304          25 :                         _customDbd->closeConnection(db);
     305             :                 } else {
     306           0 :                         _root->dbdClose(host, db);
     307             :                 }
     308             :         }
     309          25 : }
     310             : 
     311        3425 : void HostController::initTransaction(db::Transaction &t) {
     312        6850 :         for (auto &it : _components) {
     313        3425 :                 it.second->initTransaction(t);
     314             :         }
     315        3425 : }
     316             : 
     317           0 : void HostController::setDbParams(StringView str) {
     318           0 :         perform([&, this] {
     319           0 :                 Root::parseParameterList(_dbParams, str);
     320           0 :         }, _rootPool);
     321           0 : }
     322             : 
     323         550 : db::sql::Driver::Handle HostController::openConnection(pool_t *pool, bool bindConnection) const {
     324         550 :         if (_customDbd) {
     325         550 :                 auto h = _customDbd->openConnection(pool);
     326         550 :                 if (bindConnection) {
     327           0 :                         pool::cleanup_register(pool, [h, dbd = _customDbd] {
     328           0 :                                 dbd->closeConnection(h);
     329           0 :                         });
     330             :                 }
     331         550 :                 return h;
     332             :         } else {
     333           0 :                 auto h = _root->dbdOpen(pool, Host(const_cast<HostController *>(this)));
     334           0 :                 if (bindConnection) {
     335           0 :                         pool::cleanup_register(pool, [h, this] {
     336           0 :                                 _root->dbdClose(Host(const_cast<HostController *>(this)), h);
     337           0 :                         });
     338             :                 }
     339           0 :                 return h;
     340             :         }
     341             : }
     342             : 
     343         550 : void HostController::closeConnection(db::sql::Driver::Handle h) const {
     344         550 :         if (_customDbd) {
     345         550 :                 _customDbd->closeConnection(h);
     346             :         } else {
     347           0 :                 _root->dbdClose(Host(const_cast<HostController *>(this)), h);
     348             :         }
     349         550 : }
     350             : 
     351           0 : db::sql::Driver *HostController::openInternalDriver(db::sql::Driver::Handle) {
     352           0 :         log::error("web::HostController", "VirtualServerConfig::openInternalDriver is not implemented");
     353           0 :         return nullptr;
     354             : }
     355             : 
     356          25 : bool HostController::loadDsoComponent(const Host &serv, const HostComponentInfo &info) {
     357          25 :         Dso h;
     358          25 :         if (info.file.empty()) {
     359          25 :                 h = Dso(StringView(), DsoFlags::Self);
     360             :         } else {
     361           0 :                 auto path = resolvePath(info.file);
     362           0 :                 if (path.empty()) {
     363           0 :                         return false;
     364             :                 }
     365             : 
     366           0 :                 h = Dso(StringView(path));
     367           0 :         }
     368             : 
     369          25 :         if (h) {
     370          25 :                 auto sym = h.sym<HostComponent::Symbol>(info.symbol);
     371          25 :                 if (sym) {
     372          25 :                         auto comp = sym(serv, info);
     373          25 :                         if (comp) {
     374          25 :                                 _components.emplace(comp->getName(), comp);
     375          25 :                                 _typedComponents.emplace(std::type_index(typeid(*comp)), comp);
     376          25 :                                 return true;
     377             :                         } else {
     378           0 :                                 log::error("web::HostController", "Symbol ", info.symbol ," not found in DSO '", info.file, "'");
     379             :                         }
     380             :                 } else {
     381           0 :                         log::error("web::HostController", "DSO: ", h.getError());
     382             :                 }
     383             :         } else {
     384           0 :                 log::error("web::HostController", "DSO: ", h.getError());
     385             :         }
     386           0 :         return false;
     387          25 : }
     388             : 
     389           0 : bool HostController::loadWasmComponent(const Host &serv, const HostComponentInfo &info) {
     390           0 :         auto module = loadWasmModule(info.name, info.file);
     391           0 :         if (!module) {
     392           0 :                 return false;
     393             :         }
     394             : 
     395           0 :         auto comp = WasmComponent::load(serv, info, module);
     396           0 :         if (comp) {
     397           0 :                 _components.emplace(comp->getName().str<Interface>(), comp);
     398           0 :                 return true;
     399             :         } else {
     400           0 :                 log::error("web::HostController", "Wasm: fail to load component: ", info.name, " from ", info.file);
     401             :         }
     402           0 :         return false;
     403             : }
     404             : 
     405           0 : wasm::Module *HostController::loadWasmModule(StringView name, StringView str) {
     406           0 :         auto path = resolvePath(str);
     407           0 :         if (path.empty()) {
     408           0 :                 return nullptr;
     409             :         }
     410             : 
     411           0 :         auto it = _wasmModules.find(path);
     412           0 :         if (it != _wasmModules.end()) {
     413           0 :                 return it->second;
     414             :         }
     415             : 
     416           0 :         auto mod = Rc<wasm::Module>::create(name, FilePath(path));
     417           0 :         if (mod) {
     418           0 :                 _wasmModules.emplace(StringView(path).pdup(_wasmModules.get_allocator()), mod);
     419           0 :                 return mod;
     420             :         }
     421           0 :         return nullptr;
     422           0 : }
     423             : 
     424           0 : String HostController::resolvePath(StringView path) const {
     425           0 :         for (auto &it : _sourceRoot) {
     426           0 :                 auto str = filepath::merge<Interface>(it, path);
     427           0 :                 if (str.front() == '/') {
     428           0 :                         if (filesystem::exists(str)) {
     429           0 :                                 return str;
     430             :                         }
     431             :                 } else {
     432           0 :                         str = filesystem::currentDir<Interface>(str);
     433           0 :                         if (filesystem::exists(str)) {
     434           0 :                                 return str;
     435             :                         }
     436             :                 }
     437           0 :         }
     438             : 
     439           0 :         auto str = filepath::merge<Interface>(_hostInfo.documentRoot, path);
     440           0 :         if (filesystem::exists(str)) {
     441           0 :                 return str;
     442             :         }
     443           0 :         return String();
     444           0 : }
     445             : 
     446           0 : void HostController::handleTemplateError(const StringView &str) {
     447             : #if DEBUG
     448           0 :         std::cout << "[Template]: " << str << "\n";
     449             : #endif
     450           0 :         log::error("Template", "Template compilation error", Value(str));
     451           0 : }
     452             : 
     453             : }

Generated by: LCOV version 1.14