LCOV - code coverage report
Current view: top level - extra/webserver/webserver/tools - SPWebToolsShell.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 698 753 92.7 %
Date: 2024-05-12 00:16:13 Functions: 126 130 96.9 %

          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 "SPWebHost.h"
      25             : #include "SPWebRequestController.h"
      26             : #include "SPWebHostComponent.h"
      27             : #include "SPWebInputFilter.h"
      28             : #include "SPWebWebsocket.h"
      29             : #include "SPWebResource.h"
      30             : #include "SPWebOutput.h"
      31             : #include "SPWebRoot.h"
      32             : #include "SPSqlHandle.h"
      33             : #include "SPDbAdapter.h"
      34             : #include "SPValid.h"
      35             : 
      36             : namespace STAPPLER_VERSIONIZED stappler::web::tools {
      37             : 
      38             : struct SocketCommand;
      39             : 
      40             : class ShellSocketHandler : public WebsocketHandler {
      41             : public:
      42             :         enum class ShellMode {
      43             :                 Plain,
      44             :                 Html,
      45             :         };
      46             : 
      47             :         ShellSocketHandler(WebsocketManager *m, pool_t *pool, StringView url, int64_t userId);
      48             : 
      49             :         void setShellMode(ShellMode m) { _mode = m; }
      50             :         ShellMode getShellMode() const { return _mode; }
      51             : 
      52         300 :         db::User *getUser() const { return _user; }
      53         575 :         const Vector<SocketCommand *> &getCommands() const { return _cmds; }
      54         575 :         const Vector<Pair<StringView, const Map<String, HostComponent::Command> *>> &getExternals() const { return _external; }
      55             : 
      56             :         bool onCommand(StringView &r);
      57             : 
      58             :         virtual void handleBegin() override;
      59             : 
      60             :         // Data frame was recieved from network
      61             :         virtual bool handleFrame(WebsocketFrameType t, const Bytes &b) override;
      62             : 
      63             :         // Message was recieved from broadcast
      64             :         virtual bool handleMessage(const Value &val) override;
      65             : 
      66             :         void sendCmd(const StringView &v);
      67             :         void sendError(const String &str);
      68             :         void sendData(const Value & data);
      69             : 
      70             : protected:
      71             :         Vector<SocketCommand *> _cmds;
      72             :         Vector<Pair<StringView, const Map<String, HostComponent::Command> *>> _external;
      73             :         ShellMode _mode = ShellMode::Plain;
      74             :         db::User *_user = nullptr;
      75             :         int64_t _userId = 0;
      76             : };
      77             : 
      78             : struct SocketCommand : AllocBase {
      79         525 :         SocketCommand(const String &str) : name(str) { }
      80             : 
      81             :         virtual bool run(ShellSocketHandler &h, StringView &r) = 0;
      82             :         virtual StringView desc() const = 0;
      83             :         virtual StringView help() const = 0;
      84             : 
      85             :         String name;
      86             : };
      87             : 
      88             : struct DebugCmd : SocketCommand {
      89          25 :         DebugCmd() : SocketCommand("debug") { }
      90             : 
      91         100 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
      92         100 :                 if (!r.empty()) {
      93          50 :                         if (r.is("on")) {
      94          25 :                                 Root::getCurrent()->setDebugEnabled(true);
      95          25 :                                 h.send("Try to enable debug mode, wait a second, until all servers receive your message");
      96          25 :                         } else if (r.is("off")) {
      97          25 :                                 Root::getCurrent()->setDebugEnabled(false);
      98          25 :                                 h.send("Try to disable debug mode, wait a second, until all servers receive your message");
      99             :                         }
     100             :                 } else {
     101          50 :                         if (Root::getCurrent()->isDebugEnabled()) {
     102          25 :                                 h.send("Debug mode: On");
     103             :                         } else {
     104          25 :                                 h.send("Debug mode: Off");
     105             :                         }
     106             :                 }
     107         100 :                 return true;
     108             :         }
     109             : 
     110          50 :         virtual StringView desc() const override {
     111          50 :                 return "on|off - Switch server debug mode";
     112             :         }
     113          25 :         virtual StringView help() const override {
     114          25 :                 return "on|off - Switch server debug mode";
     115             :         }
     116             : };
     117             : 
     118             : struct ListCmd : SocketCommand {
     119          25 :         ListCmd() : SocketCommand("meta") { }
     120             : 
     121          75 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     122          75 :                 if (r.empty()) {
     123          25 :                         Value ret;
     124          25 :                         auto &schemes = h.manager()->host().getSchemes();
     125         300 :                         for (auto &it : schemes) {
     126         275 :                                 ret.addString(it.first);
     127             :                         }
     128          25 :                         h.sendData(ret);
     129          75 :                 } else if (r == "all") {
     130          25 :                         Value ret;
     131          25 :                         auto &schemes = h.manager()->host().getSchemes();
     132         300 :                         for (auto &it : schemes) {
     133         275 :                                 auto &val = ret.emplace(it.first);
     134         275 :                                 auto &fields = it.second->getFields();
     135        2500 :                                 for (auto &fit : fields) {
     136        2225 :                                         val.setValue(fit.second.getTypeDesc(), fit.first);
     137             :                                 }
     138             :                         }
     139          25 :                         h.sendData(ret);
     140          25 :                 } else {
     141          25 :                         Value ret;
     142          25 :                         auto cmd = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     143          25 :                         auto scheme = h.manager()->host().getScheme(cmd);
     144          25 :                         if (scheme) {
     145          25 :                                 auto &fields = scheme->getFields();
     146         175 :                                 for (auto &fit : fields) {
     147         150 :                                         ret.setValue(fit.second.getTypeDesc(), fit.first);
     148             :                                 }
     149          25 :                                 h.sendData(ret);
     150             :                         }
     151          25 :                 }
     152          75 :                 return true;
     153             :         }
     154             : 
     155          50 :         virtual StringView desc() const override {
     156          50 :                 return "all|<name> - Get information about data scheme";
     157             :         }
     158          25 :         virtual StringView help() const override {
     159          25 :                 return "all|<name> - Get information about data scheme";
     160             :         }
     161             : };
     162             : 
     163             : struct ResourceCmd : SocketCommand {
     164         250 :         ResourceCmd(const String &str) : SocketCommand(str) { }
     165             : 
     166         375 :         const db::Scheme *acquireScheme(ShellSocketHandler &h, const StringView &scheme) {
     167         375 :                 return h.manager()->host().getScheme(scheme);
     168             :         }
     169             : 
     170         275 :         Resource *acquireResource(const db::Transaction &t, ShellSocketHandler &h, const StringView &scheme, const StringView &path,
     171             :                         const StringView &resolve, const Value &val = Value()) {
     172         275 :                 Resource *ret = nullptr;
     173         275 :                 if (!scheme.empty()) {
     174         275 :                         auto s =  acquireScheme(h, scheme);
     175         275 :                         if (s) {
     176         250 :                                 ret =  Resource::resolve(t, *s,
     177         250 :                                                 path.empty()
     178         500 :                                                 ? String("/")
     179         250 :                                                 : (path.is<StringView::CharGroup<CharGroupId::Numbers>>())
     180         250 :                                                         ? StringView(toString("/id", path))
     181             :                                                         : path);
     182         250 :                                 if (ret) {
     183         250 :                                         ret->setUser(h.getUser());
     184         250 :                                         if (!resolve.empty()) {
     185           0 :                                                 if (resolve.front() == '(') {
     186           0 :                                                         ret->applyQuery(data::read<Interface>(resolve));
     187             :                                                 }
     188             :                                         }
     189         250 :                                         if (!val.empty()) {
     190         100 :                                                 ret->applyQuery(val);
     191             :                                         }
     192         250 :                                         ret->prepare();
     193             :                                 } else {
     194           0 :                                         h.sendError(toString("Fail to resolve resource \"", path, "\" for scheme ", scheme));
     195             :                                 }
     196             :                         } else {
     197          25 :                                 h.sendError(toString("No such scheme: ", scheme));
     198             :                         }
     199             :                 } else {
     200           0 :                         h.sendError(toString("Scheme is not defined"));
     201             :                 }
     202         275 :                 return ret;
     203             :         }
     204             : };
     205             : 
     206             : struct GetCmd : ResourceCmd {
     207          25 :         GetCmd() : ResourceCmd("get") { }
     208             : 
     209          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     210          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     211          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     212             : 
     213          25 :                 StringView path("/");
     214          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     215          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     216          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     217             :                 }
     218             : 
     219          25 :                 auto resolve = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     220             : 
     221          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     222          25 :                         if (auto r = acquireResource(t, h, schemeName, path, resolve)) {
     223          25 :                                 auto data = r->getResultObject();
     224          25 :                                 h.sendData(data);
     225          25 :                                 delete r;
     226          25 :                         }
     227          25 :                 });
     228          25 :                 return true;
     229             :         }
     230             : 
     231          50 :         virtual StringView desc() const override {
     232          50 :                 return "<scheme> <path> <resolve> - Get data from scheme";
     233             :         }
     234          25 :         virtual StringView help() const override {
     235          25 :                 return "<scheme> <path> <resolve> - Get data from scheme";
     236             :         }
     237             : };
     238             : 
     239             : struct HistoryCmd : ResourceCmd {
     240          25 :         HistoryCmd() : ResourceCmd("history") { }
     241             : 
     242          50 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     243          50 :                 bool ret = false;
     244          50 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     245          50 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     246             : 
     247          50 :                 int64_t time = r.readInteger().get(0);
     248             : 
     249          50 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     250             : 
     251          50 :                 auto schemeView = schemeName;
     252          50 :                 StringView field; uint64_t tag = 0;
     253          50 :                 schemeName = schemeView.readUntilString("::");
     254          50 :                 if (schemeView.is("::")) {
     255          25 :                         schemeView += 2;
     256          25 :                         auto tmpField = schemeView.readUntilString("::");
     257          25 :                         if (schemeView.is("::")) {
     258          25 :                                 schemeView += 2;
     259          25 :                                 auto tmpTag = schemeView.readInteger().get(0);
     260          25 :                                 if (tmpTag > 0 && !tmpField.empty()) {
     261          25 :                                         field = tmpField;
     262          25 :                                         tag = tmpTag;
     263             :                                 }
     264             :                         }
     265             :                 }
     266             : 
     267          50 :                 if (auto s = acquireScheme(h, schemeName)) {
     268          50 :                         h.performWithStorage([&] (const db::Transaction &t) {
     269          50 :                                 if (auto a = dynamic_cast<db::sql::SqlHandle *>(t.getAdapter().getBackendInterface())) {
     270          50 :                                         if (field.empty()) {
     271          25 :                                                 h.sendData(a->getHistory(*s, Time::microseconds(time), true));
     272          25 :                                         } else if (auto f = s->getField(field)) {
     273          25 :                                                 if (f->getType() == db::Type::View) {
     274          25 :                                                         h.sendData(a->getHistory(*static_cast<const db::FieldView *>(f->getSlot()), s, tag, Time::microseconds(time), true));
     275             :                                                 }
     276             :                                         }
     277          50 :                                         ret = true;
     278             :                                 }
     279          50 :                         });
     280             :                 }
     281             : 
     282          50 :                 return ret;
     283             :         }
     284             : 
     285          50 :         virtual StringView desc() const override {
     286          50 :                 return "<scheme> <time> - Changelog for scheme or view";
     287             :         }
     288          25 :         virtual StringView help() const override {
     289             :                 return "\thistory <scheme|view> <time> - Changelog for scheme or view\n\n"
     290             :                                 "Scheme can be defined by it's name\n"
     291          25 :                                 "View can be defined as <scheme>::<field>::<tag>\n";
     292             :         }
     293             : };
     294             : 
     295             : struct DeltaCmd : ResourceCmd {
     296          25 :         DeltaCmd() : ResourceCmd("delta") { }
     297             : 
     298          50 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     299          50 :                 bool ret = false;
     300          50 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     301          50 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     302             : 
     303          50 :                 int64_t time = r.readInteger().get(0);
     304          50 :                 auto schemeView = schemeName;
     305          50 :                 StringView field; uint64_t tag = 0;
     306          50 :                 schemeName = schemeView.readUntilString("::");
     307          50 :                 if (schemeView.is("::")) {
     308          25 :                         schemeView += 2;
     309          25 :                         auto tmpField = schemeView.readUntilString("::");
     310          25 :                         if (schemeView.is("::")) {
     311          25 :                                 schemeView += 2;
     312          25 :                                 auto tmpTag = schemeView.readInteger().get(0);
     313          25 :                                 if (tmpTag > 0 && !tmpField.empty()) {
     314          25 :                                         field = tmpField;
     315          25 :                                         tag = tmpTag;
     316             :                                 }
     317             :                         }
     318             :                 }
     319             : 
     320          50 :                 if (auto s = acquireScheme(h, schemeName)) {
     321          50 :                         h.performWithStorage([&] (const db::Transaction &t) {
     322          50 :                                 if (auto a = dynamic_cast<db::sql::SqlHandle *>(t.getAdapter().getBackendInterface())) {
     323          50 :                                         if (field.empty()) {
     324          25 :                                                 h.sendData(a->getDeltaData(*s, Time::microseconds(time)));
     325          25 :                                         } else if (auto f = s->getField(field)) {
     326          25 :                                                 if (f->getType() == db::Type::View) {
     327          25 :                                                         h.sendData(a->getDeltaData(*s, *static_cast<const db::FieldView *>(f->getSlot()), Time::microseconds(time), tag));
     328             :                                                 }
     329             :                                         }
     330          50 :                                         ret = true;
     331             :                                 }
     332          50 :                         });
     333             :                 }
     334             : 
     335          50 :                 return ret;
     336             :         }
     337             : 
     338          50 :         virtual StringView desc() const override {
     339          50 :                 return "<scheme> <time> [<field> <tag>] - Delta for scheme";
     340             :         }
     341          25 :         virtual StringView help() const override {
     342             :                 return "\tdelta <scheme|view> <time> - Changelog for scheme or view\n\n"
     343             :                                 "Scheme can be defined by it's name\n"
     344          25 :                                 "View can be defined as <scheme>::<field>::<tag>\n";
     345             :         }
     346             : };
     347             : 
     348             : struct MultiCmd : ResourceCmd {
     349          25 :         MultiCmd() : ResourceCmd("multi") { }
     350             : 
     351          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     352          25 :                 r.skipUntil<StringView::Chars<'('>>();
     353          25 :                 if (r.is('(')) {
     354          25 :                         Value result;
     355          25 :                         Value requests = data::read<Interface>(r);
     356          25 :                         if (requests.isDictionary()) {
     357         100 :                                 for (auto &it : requests.asDict()) {
     358          75 :                                         StringView path(it.first);
     359          75 :                                         StringView scheme = path.readUntil<StringView::Chars<'/'>>();
     360          75 :                                         if (path.is('/')) {
     361          50 :                                                 ++ path;
     362             :                                         }
     363             : 
     364          75 :                                         h.performWithStorage([&, this] (const db::Transaction &t) {
     365          75 :                                                 if (auto r = acquireResource(t, h, scheme, path, StringView(), it.second)) {
     366          50 :                                                         result.setValue(r->getResultObject(), it.first);
     367          50 :                                                         delete r;
     368             :                                                 }
     369          75 :                                         });
     370             :                                 }
     371             :                         }
     372          25 :                         h.sendData(result);
     373          25 :                 }
     374             : 
     375          25 :                 return true;
     376             :         }
     377             : 
     378          50 :         virtual StringView desc() const override {
     379          50 :                 return "<request> - perform multi-request";
     380             :         }
     381          25 :         virtual StringView help() const override {
     382          25 :                 return "<request> - perform multi-request";
     383             :         }
     384             : };
     385             : 
     386             : struct CreateCmd : ResourceCmd {
     387          25 :         CreateCmd() : ResourceCmd("create") { }
     388             : 
     389          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     390          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     391          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     392             : 
     393          25 :                 StringView path("/");
     394          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     395          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     396          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     397             :                 }
     398             : 
     399          25 :                 bool success = false;
     400          50 :                 Value patch = (r.is('{') || r.is('[') || r.is('(')) ? data::read<Interface>(r) : UrlView::parseArgs<Interface>(r, 1_KiB);
     401          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     402          25 :                         if (auto r = acquireResource(t, h, schemeName, path, StringView())) {
     403          25 :                                 Vector<db::InputFile> f;
     404          25 :                                 if (r->prepareCreate()) {
     405          25 :                                         if (auto ret = r->createObject(patch, f)) {
     406          25 :                                                 h.sendData(ret);
     407          25 :                                                 success = true;
     408          25 :                                         }
     409             :                                 } else {
     410           0 :                                         h.sendError(toString("Action for scheme ", schemeName, " is forbidden for ", h.getUser()->getName()));
     411             :                                 }
     412          25 :                                 delete r;
     413          25 :                         }
     414          25 :                 });
     415             : 
     416          25 :                 if (!success) {
     417           0 :                         h.sendData(patch);
     418           0 :                         h.sendError("Fail to create object with data:");
     419             :                 }
     420             : 
     421          25 :                 return true;
     422          25 :         }
     423             : 
     424          50 :         virtual StringView desc() const override {
     425          50 :                 return "<scheme> <path> <data> - Create object for scheme";
     426             :         }
     427          25 :         virtual StringView help() const override {
     428          25 :                 return "<scheme> <path> <data> - Create object for scheme";
     429             :         }
     430             : };
     431             : 
     432             : struct UpdateCmd : ResourceCmd {
     433          25 :         UpdateCmd() : ResourceCmd("update") { }
     434             : 
     435          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     436          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     437          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     438             : 
     439          25 :                 StringView path("/");
     440          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     441          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     442          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     443             :                 }
     444             : 
     445          25 :                 bool success = false;
     446          50 :                 Value patch = (r.is('{') || r.is('[') || r.is('(')) ? data::read<Interface>(r) : UrlView::parseArgs<Interface>(r, 1_KiB);
     447          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     448          25 :                         if (auto r = acquireResource(t, h, schemeName, path, StringView())) {
     449          25 :                                 Vector<db::InputFile> f;
     450          25 :                                 if (r->prepareUpdate()) {
     451          25 :                                         if (auto ret = r->updateObject(patch, f)) {
     452          25 :                                                 h.sendData(ret);
     453          25 :                                                 success = true;
     454          25 :                                         }
     455             :                                 } else {
     456           0 :                                         h.sendError(toString("Action for scheme ", schemeName, " is forbidden for ", h.getUser()->getName()));
     457             :                                 }
     458          25 :                                 delete r;
     459          25 :                         }
     460          25 :                 });
     461             : 
     462          25 :                 if (!success) {
     463           0 :                         h.sendData(patch);
     464           0 :                         h.sendError("Fail to update object with data:");
     465             :                 }
     466             : 
     467          25 :                 return true;
     468          25 :         }
     469             : 
     470          50 :         virtual StringView desc() const override {
     471          50 :                 return "<scheme> <path> <data> - Update object for scheme";
     472             :         }
     473          25 :         virtual StringView help() const override {
     474          25 :                 return "<scheme> <path> <data>  - Update object for scheme";
     475             :         }
     476             : };
     477             : 
     478             : struct UploadCmd : ResourceCmd {
     479          25 :         UploadCmd() : ResourceCmd("upload") { }
     480             : 
     481          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     482          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     483          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     484             : 
     485          25 :                 StringView path("/");
     486          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     487          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     488          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     489             :                 }
     490             : 
     491          25 :                 bool success = false;
     492          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     493          25 :                         if (auto r = acquireResource(t, h, schemeName, path, StringView())) {
     494          25 :                                 if (r->prepareCreate()) {
     495          25 :                                         Bytes bkey = valid::makeRandomBytes<Interface>(8);
     496          25 :                                         String key = base16::encode<Interface>(bkey);
     497             : 
     498             :                                         Value token({
     499          50 :                                                 pair("scheme", Value(schemeName)),
     500          50 :                                                 pair("path", Value(path)),
     501          50 :                                                 pair("resolve", Value("")),
     502          50 :                                                 pair("user", Value(h.getUser()->getObjectId())),
     503         225 :                                         });
     504             : 
     505          25 :                                         h.performWithStorage([&] (const db::Transaction &t) {
     506          25 :                                                 t.getAdapter().set(key, token, TimeInterval::seconds(5));
     507          25 :                                         });
     508             : 
     509          25 :                                         auto tv = Time::now().toMicros();
     510          25 :                                         StringStream str;
     511          25 :                                         str << "<p id=\"tmp" << tv << "\"><input type=\"file\" name=\"content\" onchange=\""
     512          50 :                                                                 "upload(this.files[0], '" << toString(h.getUrl(), "/upload/", key) << "', 'content');"
     513          50 :                                                                 "var elem = document.getElementById('tmp" << tv << "');elem.parentNode.removeChild(elem);"
     514          25 :                                                         "\"></p>";
     515             : 
     516          25 :                                         h.send(str.str());
     517          25 :                                         success = true;
     518          25 :                                 }
     519          25 :                                 delete r;
     520             :                         }
     521          25 :                 });
     522             : 
     523          25 :                 if (!success) {
     524           0 :                         h.sendError("Fail to prepare upload");
     525             :                 }
     526          25 :                 return true;
     527             :         }
     528             : 
     529          50 :         virtual StringView desc() const override {
     530          50 :                 return "<scheme> <path> - Upload file for scheme resource";
     531             :         }
     532          25 :         virtual StringView help() const override {
     533          25 :                 return "<scheme> <path> - Update file for scheme resource";
     534             :         }
     535             : };
     536             : 
     537             : struct AppendCmd : ResourceCmd {
     538          25 :         AppendCmd() : ResourceCmd("append") { }
     539             : 
     540          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     541          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     542          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     543             : 
     544          25 :                 StringView path("/");
     545          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     546          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     547          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     548             :                 }
     549             : 
     550          25 :                 bool success = false;
     551          50 :                 Value patch = (r.is('{') || r.is('[') || r.is('(')) ? data::read<Interface>(r) : UrlView::parseArgs<Interface>(r, 1_KiB);
     552          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     553          25 :                         if (auto r = acquireResource(t, h, schemeName, path, StringView())) {
     554          25 :                                 if (r->prepareAppend()) {
     555          25 :                                         if (auto ret = r->appendObject(patch)) {
     556          25 :                                                 h.sendData(ret);
     557          25 :                                                 success = true;
     558          25 :                                         }
     559             :                                 } else {
     560           0 :                                         h.sendError(toString("Action for scheme ", schemeName, " is forbidden for ", h.getUser()->getName()));
     561             :                                 }
     562          25 :                                 delete r;
     563             :                         }
     564          25 :                 });
     565             : 
     566          25 :                 if (!success) {
     567           0 :                         h.sendData(patch);
     568           0 :                         h.sendError("Fail to update object with data:");
     569             :                 }
     570             : 
     571          25 :                 return true;
     572          25 :         }
     573             : 
     574          50 :         virtual StringView desc() const override {
     575          50 :                 return "<scheme> <path> <data> - Append object for scheme";
     576             :         }
     577          25 :         virtual StringView help() const override {
     578          25 :                 return "<scheme> <path> <data> - Append object for scheme";
     579             :         }
     580             : };
     581             : 
     582             : struct DeleteCmd : ResourceCmd {
     583          25 :         DeleteCmd() : ResourceCmd("delete") { }
     584             : 
     585          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     586          25 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     587          25 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     588             : 
     589          25 :                 StringView path("/");
     590          25 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     591          25 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     592          25 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     593             :                 }
     594             : 
     595          25 :                 bool success = false;
     596          25 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     597          25 :                         if (auto r = acquireResource(t, h, schemeName, path, StringView())) {
     598          25 :                                 if (r->removeObject()) {
     599          25 :                                         success = true;
     600          25 :                                         h.sendData(Value(true));
     601             :                                 } else {
     602           0 :                                         h.sendError(toString("Action for scheme ", schemeName, " is forbidden for ", h.getUser()->getName()));
     603             :                                 }
     604          25 :                                 delete r;
     605             :                         }
     606          25 :                 });
     607             : 
     608          25 :                 if (!success) {
     609           0 :                         h.sendError("Fail to delete object");
     610             :                 }
     611             : 
     612          25 :                 return true;
     613             :         }
     614             : 
     615          50 :         virtual StringView desc() const override {
     616          50 :                 return "<scheme> <path> - Delete object for scheme";
     617             :         }
     618          25 :         virtual StringView help() const override {
     619          25 :                 return "<scheme> <path> - Delete object for scheme";
     620             :         }
     621             : };
     622             : 
     623             : struct SearchCmd : ResourceCmd {
     624          25 :         SearchCmd() : ResourceCmd("search") { }
     625             : 
     626          50 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     627          50 :                 auto schemeName = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     628          50 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     629             : 
     630          50 :                 StringView path("/");
     631          50 :                 if (r.is('/') || r.is<StringView::CharGroup<CharGroupId::Numbers>>()) {
     632          50 :                         path = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     633          50 :                         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     634             :                 }
     635          50 :                 Value data;
     636          50 :                 if (r.is('(')) {
     637          25 :                         data = data::serenity::read<Interface>(r);
     638             :                 }
     639             : 
     640          50 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     641          50 :                 if (!r.empty()) {
     642          25 :                         data.setString(r, "search");
     643             :                 }
     644             : 
     645          50 :                 h.performWithStorage([&, this] (const db::Transaction &t) {
     646          50 :                         if (auto res = acquireResource(t, h, schemeName, path, StringView(), data)) {
     647          50 :                                 if (auto val = res->getResultObject()) {
     648          50 :                                         h.sendData(val);
     649             :                                 } else {
     650           0 :                                         h.sendError(toString(schemeName, ": nothing is found"));
     651          50 :                                 }
     652          50 :                                 delete res;
     653             :                         }
     654          50 :                 });
     655             : 
     656          50 :                 return true;
     657          50 :         }
     658             : 
     659          50 :         virtual StringView desc() const override {
     660          50 :                 return "<scheme> <path> <text> - Run full-text search";
     661             :         }
     662          25 :         virtual StringView help() const override {
     663          25 :                 return "<scheme> <path> <text> - Run full-text search";
     664             :         }
     665             : };
     666             : 
     667             : struct HandlersCmd : SocketCommand {
     668          25 :         HandlersCmd() : SocketCommand("handlers") { }
     669             : 
     670          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     671          25 :                 auto serv = h.manager()->host();
     672          25 :                 auto &hdl = serv.getRequestHandlers();
     673             : 
     674          25 :                 Value ret;
     675         500 :                 for (auto &it : hdl) {
     676         475 :                         auto &v = ret.emplace(it.first);
     677         475 :                         if (!it.second.data.isNull()) {
     678           0 :                                 v.setValue(it.second.data, "data");
     679             :                         }
     680         475 :                         if (it.second.scheme) {
     681         250 :                                 v.setString(it.second.scheme->getName(), "scheme");
     682             :                         }
     683         475 :                         if (!it.second.component.empty()) {
     684         475 :                                 v.setString(it.second.component, "component");
     685             :                         }
     686         475 :                         if (it.first.back() == '/') {
     687         400 :                                 v.setBool(true, "forSubPaths");
     688             :                         }
     689             :                 }
     690             : 
     691          25 :                 h.sendData(ret);
     692          25 :                 return true;
     693          25 :         }
     694             : 
     695          50 :         virtual StringView desc() const override {
     696          50 :                 return " - Information about registered handlers";
     697             :         }
     698          25 :         virtual StringView help() const override {
     699          25 :                 return " - Information about registered handlers";
     700             :         }
     701             : };
     702             : 
     703             : struct CloseCmd : SocketCommand {
     704          25 :         CloseCmd() : SocketCommand("exit") { }
     705             : 
     706           0 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     707           0 :                 h.send("Connection will be closed by your request");
     708           0 :                 return false;
     709             :         }
     710             : 
     711          50 :         virtual StringView desc() const override {
     712          50 :                 return " - close current connection";
     713             :         }
     714          25 :         virtual StringView help() const override {
     715          25 :                 return " - close current connection";
     716             :         }
     717             : };
     718             : 
     719             : struct EchoCmd : SocketCommand {
     720          25 :         EchoCmd() : SocketCommand("echo") { }
     721             : 
     722          50 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     723          50 :                 if (!r.empty()) { h.send(r); }
     724          50 :                 return true;
     725             :         }
     726             : 
     727          50 :         virtual StringView desc() const override {
     728          50 :                 return "<message> - display message in current terminal";
     729             :         }
     730          25 :         virtual StringView help() const override {
     731          25 :                 return "<message> - display message in current terminal";
     732             :         }
     733             : };
     734             : 
     735             : struct ParseCmd : SocketCommand {
     736          25 :         ParseCmd() : SocketCommand("parse") { }
     737             : 
     738          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     739          50 :                 Value patch = (r.is('{') || r.is('[') || r.is('(')) ? data::read<Interface>(r) : UrlView::parseArgs<Interface>(r, 1_KiB);
     740          25 :                 h.sendData(patch);
     741          25 :                 return true;
     742          25 :         }
     743             : 
     744          50 :         virtual StringView desc() const override {
     745          50 :                 return "<message> - parse message as object changeset";
     746             :         }
     747          25 :         virtual StringView help() const override {
     748          25 :                 return "<message> - parse message as object changeset";
     749             :         }
     750             : };
     751             : 
     752             : struct MsgCmd : SocketCommand {
     753          25 :         MsgCmd() : SocketCommand("msg") { }
     754             : 
     755          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     756         175 :                 h.sendBroadcast(Value({
     757          50 :                         std::make_pair("user", Value(h.getUser()->getName())),
     758          50 :                         std::make_pair("event", Value("message")),
     759          50 :                         std::make_pair("message", Value(r)),
     760         100 :                 }));
     761          25 :                 return true;
     762             :         }
     763             : 
     764          50 :         virtual StringView desc() const override {
     765          50 :                 return "<message> - display message in all opened terminals";
     766             :         }
     767          25 :         virtual StringView help() const override {
     768          25 :                 return "<message> - display message in all opened terminals";
     769             :         }
     770             : };
     771             : 
     772             : struct CountCmd : SocketCommand {
     773          25 :         CountCmd() : SocketCommand("count") { }
     774             : 
     775          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     776          25 :                 StringStream resp;
     777          25 :                 resp << "Users on socket: " << h.manager()->size();
     778          25 :                 h.send(resp.weak());
     779          25 :                 return true;
     780          25 :         }
     781             : 
     782          50 :         virtual StringView desc() const override {
     783          50 :                 return " - display number of opened terminals";
     784             :         }
     785          25 :         virtual StringView help() const override {
     786          25 :                 return " - display number of opened terminals";
     787             :         }
     788             : };
     789             : 
     790             : struct HelpCmd : SocketCommand {
     791          25 :         HelpCmd() : SocketCommand("help") { }
     792             : 
     793         575 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     794         575 :                 auto & cmds = h.getCommands();
     795         575 :                 auto & externals = h.getExternals();
     796         575 :                 StringStream stream;
     797         575 :                 if (r.empty()) {
     798          25 :                         stream << "Loaded components:\n";
     799          50 :                         for (auto &it : h.manager()->host().getComponents()) {
     800          25 :                                 stream << "  " << it.second->getName() << " - " << it.second->getVersion() << "\n";
     801             :                         }
     802             : 
     803          25 :                         stream << "Available commands:\n";
     804         550 :                         for (auto &it : cmds) {
     805         525 :                                 stream << "  - " << it->name << " " << it->desc() << "\n";
     806             :                         }
     807             : 
     808          50 :                         for (auto &it : externals) {
     809          25 :                                 if (it.second && !it.second->empty()) {
     810          25 :                                         stream << " From component: " << it.first << "\n";
     811          50 :                                         for (auto &eit : *it.second) {
     812          25 :                                                 stream << "  - " << eit.second.name << " " << eit.second.desc << "\n";
     813             :                                         }
     814             :                                 }
     815             :                         }
     816             :                 } else {
     817         550 :                         bool found = false;
     818        6325 :                         for (auto &it : cmds) {
     819        6300 :                                 if (r == it->name) {
     820         525 :                                         stream << "  - " << it->name << " " << it->desc() << "\n" << it->help();
     821         525 :                                         found = true;
     822         525 :                                         break;
     823             :                                 }
     824             :                         }
     825             : 
     826         550 :                         if (!found) {
     827          25 :                                 for (auto &it : externals) {
     828          25 :                                         stream << " From component: " << it.first << "\n";
     829          25 :                                         for (auto &eit : *it.second) {
     830          25 :                                                 if (r == eit.second.name) {
     831          25 :                                                         stream << "  - " << eit.second.name << " " << eit.second.desc << "\n" << eit.second.help;
     832          25 :                                                         found = true;
     833          25 :                                                         break;
     834             :                                                 }
     835             :                                         }
     836          25 :                                         if (found) {
     837          25 :                                                 break;
     838             :                                         }
     839             :                                 }
     840             :                         }
     841             :                 }
     842         575 :                 h.send(stream.weak());
     843         575 :                 return true;
     844         575 :         }
     845             : 
     846          50 :         virtual StringView desc() const override {
     847          50 :                 return "<>|<cmd> - display command list or information about command";
     848             :         }
     849          25 :         virtual StringView help() const override {
     850          25 :                 return "<>|<cmd> - display command list or information about command";
     851             :         }
     852             : };
     853             : 
     854             : struct GenPasswordCmd : SocketCommand {
     855          25 :         GenPasswordCmd() : SocketCommand("generate_password") { }
     856             : 
     857          25 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     858          25 :                 auto len = r.readInteger().get(6);
     859          25 :                 h.send(valid::generatePassword<Interface>(len));
     860          25 :                 return true;
     861             :         }
     862             : 
     863          50 :         virtual StringView desc() const override {
     864          50 :                 return " - generate password with <length>";
     865             :         }
     866          25 :         virtual StringView help() const override {
     867          25 :                 return " - generate password with <length>";
     868             :         }
     869             : };
     870             : 
     871             : struct TimeCmd : SocketCommand {
     872          25 :         TimeCmd() : SocketCommand("time") { }
     873             : 
     874         100 :         virtual bool run(ShellSocketHandler &h, StringView &r) override {
     875         100 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     876         100 :                 int64_t t = r.readInteger().get(0);
     877         100 :                 r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     878             : 
     879         100 :                 if (t) {
     880          75 :                         if (r == "s" || r == "sec") {
     881          25 :                                 h.send(toString(t, " sec -> ", Time::seconds(t).toHttp<Interface>()));
     882          50 :                         } else if (r == "ms" || r == "msec") {
     883          25 :                                 h.send(toString(t, " ms -> ", Time::milliseconds(t).toHttp<Interface>()));
     884          25 :                         } else if (r == "mcs" || r == "μs" || r == "mcsec" || r.empty()) {
     885          25 :                                 h.send(toString(t, " μs -> ", Time::microseconds(t).toHttp<Interface>()));
     886             :                         } else {
     887           0 :                                 h.send("Invalid input");
     888             :                         }
     889          25 :                 } else if (r.empty()) {
     890          25 :                         auto now = Time::now();
     891          25 :                         h.send(toString(now.toMicros(), " μs -> ", now.toHttp<Interface>()));
     892             :                 } else {
     893           0 :                         h.send("Invalid input");
     894             :                 }
     895             : 
     896         100 :                 return true;
     897             :         }
     898             : 
     899          50 :         virtual StringView desc() const override {
     900          50 :                 return " - convert integer timestamp to human-readable format";
     901             :         }
     902          25 :         virtual StringView help() const override {
     903             :                 return " - convert integer timestamp to human-readable format.\n"
     904             :                                 "    <int>s | <int>sec - as seconds\n"
     905             :                                 "    <int>ms | <int>msec - as milliseconds\n"
     906          25 :                                 "    <int>mcs | <int>mcsec | <int> (no suffix) - as microseconds";
     907             :         }
     908             : };
     909             : 
     910          25 : ShellSocketHandler::ShellSocketHandler(WebsocketManager *m, pool_t *pool, StringView url, int64_t userId)
     911          25 : : WebsocketHandler(m, pool, url, 600_sec), _userId(userId) {
     912          50 :         _cmds.push_back(new ListCmd());
     913          50 :         _cmds.push_back(new HandlersCmd());
     914          50 :         _cmds.push_back(new HistoryCmd());
     915          50 :         _cmds.push_back(new DeltaCmd());
     916          50 :         _cmds.push_back(new GetCmd());
     917          50 :         _cmds.push_back(new MultiCmd());
     918          50 :         _cmds.push_back(new CreateCmd());
     919          50 :         _cmds.push_back(new UpdateCmd());
     920          50 :         _cmds.push_back(new AppendCmd());
     921          50 :         _cmds.push_back(new UploadCmd());
     922          50 :         _cmds.push_back(new DeleteCmd());
     923          50 :         _cmds.push_back(new SearchCmd());
     924          50 :         _cmds.push_back(new DebugCmd());
     925          50 :         _cmds.push_back(new CloseCmd());
     926          50 :         _cmds.push_back(new EchoCmd());
     927          50 :         _cmds.push_back(new ParseCmd());
     928          50 :         _cmds.push_back(new MsgCmd());
     929          50 :         _cmds.push_back(new CountCmd());
     930          50 :         _cmds.push_back(new HelpCmd());
     931          50 :         _cmds.push_back(new GenPasswordCmd());
     932          50 :         _cmds.push_back(new TimeCmd());
     933             : 
     934          25 :         auto serv = m->host();
     935          25 :         _external.reserve(serv.getComponents().size());
     936          50 :         for (auto &it : serv.getComponents()) {
     937          25 :                 if (!it.second->getCommands().empty()) {
     938          25 :                         _external.emplace_back(StringView(it.first).pdup(pool), &it.second->getCommands());
     939             :                 }
     940             :         }
     941          25 : }
     942             : 
     943        1350 : bool ShellSocketHandler::onCommand(StringView &r) {
     944        1350 :         auto cmd = r.readUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     945        1350 :         r.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
     946             : 
     947       19400 :         for (auto &it : _cmds) {
     948       19375 :                 if (cmd == it->name) {
     949        1325 :                         return it->run(*this, r);
     950             :                 }
     951             :         }
     952             : 
     953          25 :         for (auto &it : _external) {
     954          25 :                 for (auto &eit : *it.second) {
     955          25 :                         if (cmd == eit.second.name) {
     956          25 :                                 bool ret = false;
     957          25 :                                 performWithStorage([&, this] (const db::Transaction &t) {
     958          25 :                                         t.setRole(db::AccessRoleId::Admin);
     959          25 :                                         ret = eit.second.callback(r, [&, this] (const Value &val) {
     960          25 :                                                 send(val);
     961          25 :                                         });
     962          25 :                                 });
     963          25 :                                 return ret;
     964             :                         }
     965             :                 }
     966             :         }
     967             : 
     968           0 :         sendError("No such command, use 'help' to list available commands");
     969           0 :         return true;
     970             : }
     971             : 
     972          25 : void ShellSocketHandler::handleBegin() {
     973          25 :         performWithStorage([&, this] (const db::Transaction &t) {
     974          25 :                 web::perform([&, this] {
     975          25 :                         _user = db::User::get(t.getAdapter(), _userId);
     976          25 :                 }, _pool);
     977          25 :         });
     978             : 
     979         125 :         sendBroadcast(Value({
     980          50 :                 std::make_pair("user", Value(_user->getName())),
     981          50 :                 std::make_pair("event", Value("enter")),
     982          75 :         }));
     983             : 
     984          25 :         StringStream resp;
     985          25 :         resp << "Serenity: Welcome. " << _user->getName() << "!";
     986          25 :         send(resp.weak());
     987             : 
     988         475 :         for (auto &it : _cmds) {
     989         475 :                 if ("help" == it->name) {
     990          25 :                         StringView r;
     991          25 :                         it->run(*this, r);
     992          25 :                         break;
     993             :                 }
     994             :         }
     995          25 : }
     996             : 
     997        1350 : bool ShellSocketHandler::handleFrame(WebsocketFrameType t, const Bytes &b) {
     998        1350 :         if (t == WebsocketFrameType::Text) {
     999        1350 :                 StringView r((const char *)b.data(), b.size());
    1000        1350 :                 sendCmd(r);
    1001        1350 :                 return onCommand(r);
    1002             :         }
    1003           0 :         return true;
    1004             : }
    1005             : 
    1006         100 : bool ShellSocketHandler::handleMessage(const Value &val) {
    1007         100 :         if (val.isBool("message") && val.getBool("message")) {
    1008          50 :                 if (auto &d = val.getValue("data")) {
    1009          50 :                         if (d.getString("source") == "Database-Query") {
    1010          25 :                                 StringStream resp;
    1011          25 :                                 resp << " - [Query] " << d.getString("text") << "\n";
    1012          25 :                                 send(resp.weak());
    1013          25 :                         } else {
    1014          25 :                                 sendData(d);
    1015             :                         }
    1016             :                 }
    1017          50 :         } else  if (val.isString("event")) {
    1018          50 :                 auto &ev = val.getString("event");
    1019          50 :                 if (ev == "enter") {
    1020          25 :                         StringStream resp;
    1021          25 :                         resp << "User " << val.getString("user") << " connected.";
    1022          25 :                         send(resp.weak());
    1023          50 :                 } else if (ev == "message") {
    1024          25 :                         StringStream resp;
    1025          25 :                         resp << "- [" << val.getString("user") << "] " << val.getString("message");
    1026          25 :                         send(resp.weak());
    1027          25 :                 }
    1028             :         }
    1029             : 
    1030         100 :         return true;
    1031             : }
    1032             : 
    1033        1350 : void ShellSocketHandler::sendCmd(const StringView &v) {
    1034        1350 :         StringStream stream;
    1035        1350 :         stream << _user->getName() << "@" << _manager->host().getHostInfo().hostname << ": " << v;
    1036        1350 :         send(stream.weak());
    1037        1350 : }
    1038             : 
    1039          25 : void ShellSocketHandler::sendError(const String &str) {
    1040          25 :         StringStream stream;
    1041          25 :         switch(_mode) {
    1042          25 :         case ShellMode::Plain: stream << "Error: " << str << "\n"; break;
    1043           0 :         case ShellMode::Html: stream << "<span class=\"error\">Error:</span> " << str; break;
    1044             :         };
    1045          25 :         send(stream.weak());
    1046          25 : }
    1047             : 
    1048         450 : void ShellSocketHandler::sendData(const Value & data) {
    1049         450 :         String stream;
    1050         450 :         switch(_mode) {
    1051         450 :         case ShellMode::Plain:
    1052         450 :                 data::write([&] (StringView str) {
    1053      237100 :                         stream.append(str.data(), str.size());
    1054      237100 :                 }, data, data::EncodeFormat::Json);
    1055         450 :                 break;
    1056           0 :         case ShellMode::Html:
    1057           0 :                 stream.append("<p>");
    1058           0 :                 output::formatJsonAsHtml([&] (StringView str) {
    1059           0 :                         stream.append(str.data(), str.size());
    1060           0 :                 }, data);
    1061           0 :                 stream.append("</p>");
    1062           0 :                 break;
    1063             :         };
    1064         450 :         send(stream);
    1065         450 : }
    1066             : 
    1067          25 : WebsocketHandler * ShellSocket::onAccept(const Request &req, pool_t *pool) {
    1068          25 :         WebsocketHandler *ret = nullptr;
    1069          25 :         Request rctx(req);
    1070          25 :         if (!req.getController()->isSecureAuthAllowed()) {
    1071           0 :                 rctx.setStatus(HTTP_FORBIDDEN);
    1072           0 :                 return nullptr;
    1073             :         }
    1074             : 
    1075          25 :         if (auto user = req.getAuthorizedUser()) {
    1076           0 :                 if (!user || !user->isAdmin()) {
    1077           0 :                         rctx.setStatus(HTTP_FORBIDDEN);
    1078             :                 } else {
    1079           0 :                         web::perform([&, this] {
    1080           0 :                                 ret = new ShellSocketHandler(this, pool, req.getInfo().url.path, user->getObjectId());
    1081           0 :                         }, pool);
    1082             :                 }
    1083             :         }
    1084             : 
    1085          25 :         if (!ret) {
    1086          25 :                 auto &data = rctx.getInfo().queryData;
    1087          25 :                 if (data.isString("name") && data.isString("passwd")) {
    1088          25 :                         auto &name = data.getString("name");
    1089          25 :                         auto &passwd = data.getString("passwd");
    1090             : 
    1091          25 :                         rctx.performWithStorage([&] (const db::Transaction &t) {
    1092          25 :                                 db::User * user = db::User::get(t, name, passwd);
    1093          25 :                                 if (!user || !user->isAdmin()) {
    1094           0 :                                         rctx.setStatus(HTTP_FORBIDDEN);
    1095             :                                 } else {
    1096          25 :                                         rctx.setUser(user);
    1097          25 :                                         web::perform([&, this] {
    1098          50 :                                                 ret = new ShellSocketHandler(this, pool, req.getInfo().url.path, user->getObjectId());
    1099          25 :                                         }, pool);
    1100             :                                 }
    1101          25 :                                 return true;
    1102             :                         });
    1103             :                 }
    1104             :         }
    1105          25 :         return ret;
    1106          25 : }
    1107             : 
    1108           0 : bool ShellSocket::onBroadcast(const Value &) {
    1109           0 :         return true;
    1110             : }
    1111             : 
    1112          50 : Status ShellGui::onPostReadRequest(Request &rctx) {
    1113          50 :         auto &info = rctx.getInfo();
    1114          50 :         if (info.method == RequestMethod::Get) {
    1115          25 :                 if (_subPath.empty()) {
    1116          25 :                         auto userScheme = rctx.host().getUserScheme();
    1117          25 :                         size_t count = 0;
    1118          25 :                         bool hasDb = false;
    1119          25 :                         if (userScheme) {
    1120          25 :                                 rctx.performWithStorage([&] (const db::Transaction &t) {
    1121          25 :                                         count = userScheme->count(t, db::Query());
    1122          25 :                                         hasDb = true;
    1123          25 :                                         return true;
    1124             :                                 });
    1125             :                         }
    1126             : 
    1127          25 :                         rctx.setContentType("text/html;charset=UTF-8");
    1128          25 :                         rctx.runPug("virtual://html/server.pug", [&] (pug::Context &exec, const pug::Template &) -> bool {
    1129          25 :                                 exec.set("count", Value(count));
    1130          25 :                                 exec.set("setup", Value(count != 0));
    1131          25 :                                 exec.set("hasDb", Value(hasDb));
    1132          25 :                                 exec.set("version", Value(config::getWebserverVersionString()));
    1133          25 :                                 return true;
    1134             :                         });
    1135             : 
    1136          25 :                         return DONE;
    1137             :                 } else {
    1138           0 :                         return HTTP_NOT_FOUND;
    1139             :                 }
    1140          25 :         } else if (info.method == RequestMethod::Post) {
    1141          25 :                 StringView path(_subPath);
    1142          25 :                 if (!path.is("/upload/")) {
    1143           0 :                         return HTTP_NOT_IMPLEMENTED;
    1144             :                 } else {
    1145          25 :                         path += "/upload/"_len;
    1146          25 :                         Status status = HTTP_NOT_FOUND;
    1147          25 :                         rctx.performWithStorage([&, this] (const db::Transaction &t) {
    1148          25 :                                 auto data = t.getAdapter().get(path);
    1149          25 :                                 if (!path.empty()) {
    1150          25 :                                         if (data) {
    1151          25 :                                                 t.getAdapter().clear(path);
    1152             :                                         } else {
    1153          25 :                                                 status = HTTP_NOT_FOUND;
    1154           0 :                                                 return false;
    1155             :                                         }
    1156             : 
    1157          25 :                                         auto scheme = rctx.host().getScheme(data.getString("scheme"));
    1158          25 :                                         auto path = data.getString("path");
    1159          25 :                                         auto user = db::User::get(t, data.getInteger("user"));
    1160          25 :                                         if (scheme && !path.empty() && user) {
    1161          25 :                                                 if (Resource *r = Resource::resolve(t, *scheme, path)) {
    1162          25 :                                                         r->setUser(user);
    1163          25 :                                                         if (r->prepareCreate()) {
    1164          25 :                                                                 _resource = r;
    1165          25 :                                                                 status = OK;
    1166             :                                                         }
    1167             :                                                 }
    1168             :                                         }
    1169          25 :                                 }
    1170          25 :                                 return true;
    1171          25 :                         });
    1172          25 :                         return status;
    1173             :                 }
    1174             :                 return HTTP_NOT_FOUND;
    1175             :         }
    1176           0 :         return DECLINED;
    1177             : }
    1178             : 
    1179          25 : void ShellGui::onInsertFilter(Request &rctx) {
    1180          25 :         if (!_resource) {
    1181           0 :                 return;
    1182             :         }
    1183             : 
    1184          25 :         rctx.setInputConfig(db::InputConfig({
    1185             :                 db::InputConfig::Require::Files,
    1186          25 :                 _resource->getMaxRequestSize(),
    1187          25 :                 _resource->getMaxVarSize(),
    1188          25 :                 _resource->getMaxFileSize()
    1189             :         }));
    1190             : 
    1191          25 :         auto &info = rctx.getInfo();
    1192          25 :         if (info.method == RequestMethod::Put || info.method == RequestMethod::Post) {
    1193          25 :                 auto ex = InputFilter::insert(rctx);
    1194          25 :                 if (ex != InputFilter::Exception::None) {
    1195           0 :                         if (ex == InputFilter::Exception::TooLarge) {
    1196           0 :                                 rctx.setStatus(HTTP_REQUEST_ENTITY_TOO_LARGE);
    1197           0 :                         } else if (ex == InputFilter::Exception::Unrecognized) {
    1198           0 :                                 rctx.setStatus(HTTP_UNSUPPORTED_MEDIA_TYPE);
    1199             :                         }
    1200             :                 }
    1201             :         }
    1202             : }
    1203             : 
    1204          25 : Status ShellGui::onHandler(Request &) {
    1205          25 :         return OK;
    1206             : }
    1207             : 
    1208          25 : void ShellGui ::onFilterComplete(InputFilter *filter) {
    1209          25 :         Request rctx(filter->getRequest());
    1210          25 :         Value data;
    1211          25 :         data.setBool(false, "OK");
    1212          25 :         if (_resource) {
    1213          25 :                 _request.performWithStorage([&, this] (const db::Transaction &t) {
    1214          50 :                         return t.performAsSystem([&, this] {
    1215          50 :                                 data.setValue(_resource->createObject(filter->getData(), filter->getFiles()), "result");
    1216          25 :                                 data.setBool(true, "OK");
    1217          25 :                                 return true;
    1218          50 :                         });
    1219             :                 });
    1220             :         }
    1221             : 
    1222          25 :         data.setInteger(Time::now().toMicros(), "date");
    1223             : #if DEBUG
    1224          25 :         auto &debug = _request.getDebugMessages();
    1225          25 :         if (!debug.empty()) {
    1226           0 :                 data.setArray(debug, "debug");
    1227             :         }
    1228             : #endif
    1229          25 :         auto &error = _request.getErrorMessages();
    1230          25 :         if (!error.empty()) {
    1231           0 :                 data.setArray(error, "errors");
    1232             :         }
    1233             : 
    1234          25 :         _request.writeData(data, false);
    1235          25 : }
    1236             : 
    1237             : }

Generated by: LCOV version 1.14