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