LCOV - code coverage report
Current view: top level - extra/webserver/webserver/tools - SPWebToolsErrors.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 190 204 93.1 %
Date: 2024-05-12 00:16:13 Functions: 12 12 100.0 %

          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 "SPWebTools.h"
      24             : #include "SPWebRoot.h"
      25             : #include "SPDbUser.h"
      26             : #include "SPDbContinueToken.h"
      27             : #include "SPDbQuery.h"
      28             : #include "SPSqlHandle.h"
      29             : 
      30             : namespace STAPPLER_VERSIONIZED stappler::web::tools {
      31             : 
      32          25 : static Status makeUnavailablePage(Request &req) {
      33          25 :         req.runPug("virtual://html/errors_unauthorized.pug", [&] (pug::Context &exec, const pug::Template &) -> bool {
      34          25 :                 exec.set("version", Value(config::getWebserverVersionString()));
      35          25 :                 return true;
      36             :         });
      37          25 :         return HTTP_UNAUTHORIZED;
      38             : }
      39             : 
      40          75 : Status ErrorsGui::onTranslateName(Request &req) {
      41          75 :         req.setResponseHeader("WWW-Authenticate", req.host().getHostInfo().hostname);
      42             : 
      43          75 :         auto t = req.acquireDbTransaction();
      44          75 :         if (!t) {
      45           0 :                 return HTTP_INTERNAL_SERVER_ERROR;
      46             :         }
      47             : 
      48          75 :         auto u = req.getAuthorizedUser();
      49          75 :         if (u && u->isAdmin()) {
      50          75 :                 auto errorScheme = req.host().getErrorScheme();
      51          75 :                 auto &d = req.getInfo().queryData;
      52          75 :                 if (d.hasValue("delete")) {
      53          25 :                         if (auto id = StringView(d.getString("delete")).readInteger().get(0)) {
      54          25 :                                 u = req.getAuthorizedUser();
      55          25 :                                 if (u && u->isAdmin()) {
      56          25 :                                         if (errorScheme) {
      57          25 :                                                 errorScheme->remove(t, id);
      58             :                                         }
      59             :                                 }
      60             :                         }
      61             : 
      62          25 :                         auto c = d.getString("c");
      63          25 :                         auto tag = d.getString("tag");
      64             : 
      65          25 :                         StringStream url;
      66          25 :                         url << req.getInfo().url.path;
      67             : 
      68          25 :                         if (!c.empty()) {
      69          25 :                                 auto token = db::ContinueToken(d.getString("c"));
      70          25 :                                 db::Query q;
      71          25 :                                 if (!tag.empty()) {
      72          25 :                                         q.select("tags", db::Comparation::Includes, Value(tag));
      73             :                                 }
      74          25 :                                 token.refresh(*errorScheme, t, q);
      75             : 
      76          25 :                                 url << "?c=" << token.encode();
      77          25 :                         }
      78          25 :                         if (!tag.empty()) {
      79          25 :                                 if (c.empty()) {
      80           0 :                                         url << "?tag=" << tag;
      81             :                                 } else {
      82          25 :                                         url << "&tag=" << tag;
      83             :                                 }
      84             :                         }
      85          25 :                         return req.redirectTo(url.str());
      86          25 :                 }
      87             : 
      88          50 :                 String selectedTag;
      89          50 :                 if (d.isString("tag")) {
      90          25 :                         selectedTag = d.getString("tag");
      91             :                 }
      92             : 
      93          50 :                 Value errorsData;
      94          50 :                 auto token = d.isString("c") ? db::ContinueToken(d.getString("c")) : db::ContinueToken("__oid", 25, true);
      95             : 
      96          50 :                 if (errorScheme) {
      97          50 :                         db::Query q;
      98          50 :                         if (!selectedTag.empty()) {
      99          25 :                                 q.select("tags", db::Comparation::Includes, Value(selectedTag));
     100             :                         }
     101          50 :                         errorsData = token.perform(*errorScheme, t, q);
     102          50 :                 }
     103             : 
     104          50 :                 req.runPug("virtual://html/errors.pug", [&] (pug::Context &exec, const pug::Template &tpl) -> bool {
     105          50 :                         ServerGui::defineBasics(exec, req, u);
     106             : 
     107          50 :                         if (!selectedTag.empty()) {
     108          25 :                                 exec.set("selectedTag", Value(selectedTag));
     109             :                         }
     110             : 
     111          50 :                         if (auto iface = dynamic_cast<db::sql::SqlHandle *>(t.getAdapter().getBackendInterface())) {
     112          50 :                                 Value ret;
     113             : 
     114          50 :                                 auto driver = iface->getDriver();
     115             : 
     116          50 :                                 auto tagUnwrapQuery = (driver->getDriverName() == "sqlite")
     117          50 :                                                 ? toString("SELECT __oid, __unwrap_value as tag FROM ", errorScheme->getName(), ", sp_unwrap(tags) as unwrap")
     118         100 :                                                 : toString("SELECT __oid, unnest(tags) as tag FROM ", errorScheme->getName());
     119             : 
     120          50 :                                 auto query = toString("SELECT tag, COUNT(*) FROM (", tagUnwrapQuery, ") s GROUP BY tag;;");
     121          50 :                                 iface->performSimpleSelect(query, [&] (db::Result &res) {
     122         150 :                                         for (auto it : res) {
     123         700 :                                                 ret.addValue(Value({
     124         200 :                                                         pair("tag", Value(it.toString(0))),
     125         200 :                                                         pair("count", Value(it.toInteger(1))),
     126         200 :                                                         pair("selected", Value(it.toString(0) == selectedTag))
     127         400 :                                                 }));
     128             :                                         }
     129          50 :                                 });
     130             : 
     131          50 :                                 exec.set("tags", move(ret));
     132          50 :                         }
     133             : 
     134          50 :                         if (errorsData.size() > 0) {
     135          50 :                                 exec.set("errors", true, &errorsData);
     136             :                         }
     137             : 
     138          50 :                         auto current = token.encode();
     139             : 
     140             :                         Value cursor({
     141         100 :                                 pair("start", Value(token.getStart())),
     142         100 :                                 pair("end", Value(token.getEnd())),
     143         100 :                                 pair("current", Value(current)),
     144         100 :                                 pair("total", Value(token.getTotal())),
     145         450 :                         });
     146             : 
     147          50 :                         if (token.hasNext()) {
     148           0 :                                 cursor.setString(token.encodeNext(), "next");
     149             :                         }
     150             : 
     151          50 :                         if (token.hasPrev()) {
     152           0 :                                 cursor.setString(token.encodePrev(), "prev");
     153             :                         }
     154             : 
     155          50 :                         exec.set("cursor", move(cursor));
     156          50 :                         return true;
     157          50 :                 });
     158          50 :                 return DONE;
     159          50 :         } else {
     160           0 :                 return makeUnavailablePage(req);
     161             :         }
     162             : }
     163             : 
     164          50 : Status HandlersGui::onTranslateName(Request &req) {
     165          50 :         req.setResponseHeader("WWW-Authenticate", req.host().getHostInfo().hostname);
     166             : 
     167          50 :         auto u = req.getAuthorizedUser();
     168          50 :         if (u && u->isAdmin()) {
     169          25 :                 auto &hdl = req.host().getRequestHandlers();
     170             : 
     171          25 :                 Value servh;
     172          25 :                 Value ret;
     173         500 :                 for (auto &it : hdl) {
     174         475 :                         auto &v = (it.second.component == "root") ? servh.emplace() : ret.emplace();
     175         475 :                         v.setValue(it.first, "name");
     176         475 :                         if (!it.second.data.isNull()) {
     177           0 :                                 v.setValue(it.second.data, "data");
     178             :                         }
     179         475 :                         if (it.second.scheme) {
     180         250 :                                 v.setString(it.second.scheme->getName(), "scheme");
     181             :                         }
     182         475 :                         if (!it.second.component.empty()) {
     183         475 :                                 if (it.second.component == "root") {
     184         175 :                                         v.setBool(true, "server");
     185             :                                 }
     186         475 :                                 v.setString(it.second.component, "component");
     187             :                         }
     188         475 :                         if (it.first.back() == '/') {
     189         400 :                                 v.setBool(true, "forSubPaths");
     190             :                         }
     191         475 :                         if (it.second.map) {
     192          25 :                                 auto base = StringView(it.first);
     193          25 :                                 if (base.ends_with("/")) {
     194          25 :                                         base = StringView(base, base.size() - 1);
     195             :                                 }
     196          25 :                                 auto &m = v.emplace("map");
     197         125 :                                 for (auto &iit : it.second.map->getHandlers()) {
     198         100 :                                         auto &mVal = m.emplace();
     199         100 :                                         mVal.setString(iit.getName(), "name");
     200         100 :                                         mVal.setString(toString(base, iit.getPattern()), "pattern");
     201             : 
     202         100 :                                         switch (iit.getMethod()) {
     203          25 :                                         case RequestMethod::Get: mVal.setString("GET", "method"); break;
     204           0 :                                         case RequestMethod::Put: mVal.setString("PUT", "method"); break;
     205          75 :                                         case RequestMethod::Post: mVal.setString("POST", "method"); break;
     206           0 :                                         case RequestMethod::Delete: mVal.setString("DELETE", "method"); break;
     207           0 :                                         case RequestMethod::Connect: mVal.setString("CONNECT", "method"); break;
     208           0 :                                         case RequestMethod::Options: mVal.setString("OPTIONS", "method"); break;
     209           0 :                                         case RequestMethod::Trace: mVal.setString("TRACE", "method"); break;
     210           0 :                                         case RequestMethod::Patch: mVal.setString("PATCH", "method"); break;
     211           0 :                                         default: break;
     212             :                                         }
     213             : 
     214         100 :                                         auto &qScheme = iit.getQueryScheme().getFields();
     215         100 :                                         if (!qScheme.empty()) {
     216          50 :                                                 auto &qVal = mVal.emplace("query");
     217         150 :                                                 for (auto &it : qScheme) {
     218         100 :                                                         auto &v = qVal.addValue(it.second.getTypeDesc());
     219         100 :                                                         v.setString(it.first, "name");
     220             :                                                 }
     221             :                                         }
     222             : 
     223         100 :                                         auto &iScheme = iit.getInputScheme().getFields();
     224         100 :                                         if (!iScheme.empty()) {
     225          50 :                                                 auto &qVal = mVal.emplace("input");
     226         225 :                                                 for (auto &it : iScheme) {
     227         175 :                                                         auto &v = qVal.addValue(it.second.getTypeDesc());
     228         175 :                                                         v.setString(it.first, "name");
     229             :                                                 }
     230             :                                         }
     231             :                                 }
     232             :                         }
     233             :                 }
     234             : 
     235         200 :                 for (auto &it : servh.asArray()) {
     236         175 :                         ret.addValue(move(it));
     237             :                 }
     238             : 
     239          25 :                 req.runPug("virtual://html/handlers.pug", [&] (pug::Context &exec, const pug::Template &tpl) -> bool {
     240          25 :                         ServerGui::defineBasics(exec, req, u);
     241          25 :                         exec.set("handlers", std::move(ret));
     242          25 :                         return true;
     243             :                 });
     244          25 :                 return DONE;
     245          25 :         } else {
     246          25 :                 return makeUnavailablePage(req);
     247             :         }
     248             : }
     249             : 
     250         250 : Status ReportsGui::onTranslateName(Request &req) {
     251         250 :         req.setResponseHeader("WWW-Authenticate", req.host().getHostInfo().hostname);
     252             : 
     253         250 :         auto u = req.getAuthorizedUser();
     254         250 :         if (u && u->isAdmin()) {
     255         250 :                 auto reportsAddress = req.host().getDocumentRootPath(".reports");
     256             : 
     257         225 :                 auto readTime = [&] (StringView name) {
     258         225 :                         if (name.starts_with("crash.")) {
     259         152 :                                 name.skipUntil<StringView::CharGroup<CharGroupId::Numbers>>();
     260         304 :                                 return Time::microseconds(name.readInteger(10).get());
     261             :                         } else {
     262          73 :                                 name.skipUntil<StringView::CharGroup<CharGroupId::Numbers>>();
     263         146 :                                 return Time::milliseconds(name.readInteger(10).get());
     264             :                         }
     265             :                 };
     266             : 
     267         250 :                 Value paths;
     268         250 :                 Value file;
     269         250 :                 if (_subPath.empty() || _subPath == "/") {
     270         100 :                         filesystem::ftw(reportsAddress, [&] (StringView path, bool isFile) {
     271         250 :                                 if (isFile) {
     272         150 :                                         auto name = filepath::lastComponent(path);
     273         150 :                                         if (name.starts_with("crash.") || name.starts_with("update.")) {
     274         150 :                                                 auto &info = paths.emplace();
     275         150 :                                                 info.setString(name, "name");
     276         150 :                                                 if (auto t = readTime(name)) {
     277         150 :                                                         info.setInteger(t.toMicros(), "time");
     278         150 :                                                         info.setString(t.toHttp<Interface>(), "date");
     279             :                                                 }
     280             :                                         }
     281             :                                 }
     282         250 :                         }, 1);
     283             : 
     284         100 :                         if (paths) {
     285          75 :                                 std::sort(paths.asArray().begin(), paths.asArray().end(), [&] (const Value &l, const Value &r) {
     286         129 :                                         return l.getInteger("time") > r.getInteger("time");
     287             :                                 });
     288             :                         }
     289         150 :                 } else if (_subPathVec.size() == 1) {
     290         150 :                         auto name = _subPathVec.front();
     291             : 
     292         150 :                         auto reportsAddress = filepath::merge<Interface>(req.host().getDocumentRootPath(".reports"), name);
     293             : 
     294         150 :                         filesystem::Stat stat;
     295         150 :                         auto exists = filesystem::stat(reportsAddress, stat);
     296         150 :                         if (exists && !stat.isDir) {
     297         150 :                                 if (req.getInfo().queryData.getBool("remove")) {
     298          75 :                                         filesystem::remove(reportsAddress);
     299          75 :                                         return req.redirectTo(StringView(_originPath, _originPath.size() - _subPath.size()));
     300             :                                 }
     301          75 :                                 auto data = filesystem::readTextFile<Interface>(reportsAddress);
     302          75 :                                 if (!data.empty()) {
     303          75 :                                         file.setString(move(data), "data");
     304          75 :                                         auto name = filepath::lastComponent(reportsAddress);
     305          75 :                                         file.setString(name, "name");
     306          75 :                                         if (auto t = readTime(name)) {
     307          75 :                                                 file.setInteger(t.toMicros(), "time");
     308          75 :                                                 file.setString(t.toHttp<Interface>(), "date");
     309             :                                         }
     310             :                                 }
     311          75 :                         }
     312         150 :                 }
     313             : 
     314         175 :                 req.runPug("virtual://html/reports.pug", [&] (pug::Context &exec, const pug::Template &tpl) -> bool {
     315         175 :                         ServerGui::defineBasics(exec, req, u);
     316         175 :                         if (paths) {
     317          75 :                                 exec.set("files", move(paths));
     318         100 :                         } else if (file) {
     319          75 :                                 exec.set("file", move(file));
     320             :                         }
     321         175 :                         return true;
     322             :                 });
     323             : 
     324         175 :                 return DONE;
     325         250 :         } else {
     326           0 :                 return makeUnavailablePage(req);
     327             :         }
     328             : }
     329             : 
     330             : }

Generated by: LCOV version 1.14