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 "SPWebRequestHandler.h"
24 : #include "SPWebInputFilter.h"
25 : #include "SPWebHost.h"
26 : #include "SPWebRoot.h"
27 : #include "SPDbFile.h"
28 :
29 : namespace STAPPLER_VERSIONIZED stappler::web {
30 :
31 3050 : Status RequestHandler::onRequestRecieved(Request & rctx, StringView originPath, StringView path, const Value &data) {
32 3050 : _request = rctx;
33 3050 : _originPath = originPath;
34 3050 : _subPath = path;
35 3050 : _options = data;
36 3050 : _subPathVec = UrlView::parsePath<Interface>(_subPath);
37 3050 : return OK;
38 : }
39 :
40 3025 : Status RequestHandler::onPostReadRequest(Request &) { return OK; }
41 25 : Status RequestHandler::onTranslateName(Request &) { return DECLINED; }
42 675 : Status RequestHandler::onQuickHandler(Request &, int v) { return DECLINED; }
43 0 : void RequestHandler::onInsertFilter(Request &) { }
44 0 : Status RequestHandler::onHandler(Request &) { return DECLINED; }
45 :
46 650 : void RequestHandler::onFilterInit(InputFilter *f) { }
47 825 : void RequestHandler::onFilterUpdate(InputFilter *f) { }
48 0 : void RequestHandler::onFilterComplete(InputFilter *f) { }
49 :
50 25 : const Value &RequestHandler::getOptions() const { return _options; }
51 :
52 0 : void RequestHandler::setAccessRole(db::AccessRoleId role) { _accessRole = role; }
53 3050 : db::AccessRoleId RequestHandler::getAccessRole() const { return _accessRole; }
54 :
55 150 : Status DataHandler::writeResult(Value &data) {
56 150 : auto status = _request.getInfo().status;
57 150 : if (status >= 400) {
58 0 : return status;
59 : }
60 :
61 150 : data.setInteger(Time::now().toMicros(), "date");
62 : #if DEBUG
63 150 : auto &debug = _request.getDebugMessages();
64 150 : if (!debug.empty()) {
65 0 : data.setArray(debug, "debug");
66 : }
67 : #endif
68 150 : auto &error = _request.getErrorMessages();
69 150 : if (!error.empty()) {
70 25 : data.setArray(error, "errors");
71 : }
72 :
73 150 : _request.writeData(data, allowJsonP());
74 150 : return DONE;
75 : }
76 :
77 150 : static bool isMethodAllowed(RequestMethod r, DataHandler::AllowMethod a) {
78 150 : if ((r == RequestMethod::Get && (a & DataHandler::AllowMethod::Get) != 0)
79 0 : || (r == RequestMethod::Delete && (a & DataHandler::AllowMethod::Delete) != 0)
80 0 : || (r == RequestMethod::Put && (a & DataHandler::AllowMethod::Put) != 0)
81 300 : || (r == RequestMethod::Post && (a & DataHandler::AllowMethod::Post) != 0)) {
82 150 : return true;
83 : }
84 :
85 0 : return false;
86 : }
87 :
88 150 : Status DataHandler::onTranslateName(Request &rctx) {
89 150 : if (!isMethodAllowed(rctx.getInfo().method, _allow)) {
90 0 : return HTTP_METHOD_NOT_ALLOWED;
91 : }
92 :
93 300 : if ((rctx.getInfo().method == RequestMethod::Get && (_allow & AllowMethod::Get) != AllowMethod::None)
94 300 : || (rctx.getInfo().method == RequestMethod::Delete && (_allow & AllowMethod::Delete) != AllowMethod::None)) {
95 150 : bool result = false;
96 150 : Value data;
97 :
98 150 : Value input;
99 150 : result = processDataHandler(rctx, data, input);
100 150 : data.setBool(result, "OK");
101 150 : return writeResult(data);
102 150 : }
103 :
104 0 : return DECLINED;
105 : }
106 :
107 25 : void DataHandler::onInsertFilter(Request &rctx) {
108 50 : if ((rctx.getInfo().method == RequestMethod::Post && (_allow & AllowMethod::Post) != AllowMethod::None)
109 50 : || (rctx.getInfo().method == RequestMethod::Put && (_allow & AllowMethod::Put) != AllowMethod::None)) {
110 25 : rctx.setInputConfig(_config);
111 : }
112 :
113 25 : if (rctx.getInfo().method == RequestMethod::Put || rctx.getInfo().method == RequestMethod::Post) {
114 25 : auto ex = InputFilter::insert(rctx);
115 25 : if (ex != InputFilter::Exception::None) {
116 0 : if (ex == InputFilter::Exception::TooLarge) {
117 0 : rctx.setStatus(HTTP_REQUEST_ENTITY_TOO_LARGE);
118 0 : } else if (ex == InputFilter::Exception::Unrecognized) {
119 0 : rctx.setStatus(HTTP_UNSUPPORTED_MEDIA_TYPE);
120 : }
121 : }
122 : }
123 25 : }
124 :
125 25 : Status DataHandler::onHandler(Request &) {
126 25 : return OK;
127 : }
128 :
129 0 : void DataHandler::onFilterComplete(InputFilter *filter) {
130 0 : bool result = false;
131 0 : Value data;
132 0 : Request rctx(filter->getRequest());
133 0 : _filter = filter;
134 :
135 0 : Value input(filter->getData());
136 0 : for (auto &it : filter->getFiles()) {
137 0 : input.setInteger(it.negativeId(), it.name);
138 : }
139 :
140 0 : result = processDataHandler(rctx, data, input);
141 :
142 0 : data.setBool(result, "OK");
143 0 : writeResult(data);
144 0 : }
145 :
146 0 : FilesystemHandler::FilesystemHandler(const String &path, size_t cacheTime) : _path(path), _cacheTime(cacheTime) { }
147 0 : FilesystemHandler::FilesystemHandler(const String &path, const String &ct, size_t cacheTime)
148 0 : : _path(path), _contentType(ct), _cacheTime(cacheTime) { }
149 :
150 0 : bool FilesystemHandler::isRequestPermitted(Request &) {
151 0 : return true;
152 : }
153 0 : Status FilesystemHandler::onTranslateName(Request &rctx) {
154 0 : auto &info = rctx.getInfo();
155 0 : if (info.url.path == "/") {
156 0 : return rctx.sendFile(stappler::filesystem::writablePath<Interface>(_path), std::move(_contentType), _cacheTime);
157 : } else {
158 0 : auto npath = stappler::filesystem::writablePath<Interface>(info.url.path, true);
159 0 : if (stappler::filesystem::exists(npath) && _subPath != "/") {
160 0 : return DECLINED;
161 : }
162 0 : return rctx.sendFile(stappler::filesystem::writablePath<Interface>(_path), std::move(_contentType), _cacheTime);
163 0 : }
164 : }
165 :
166 0 : bool RequestHandlerMap::Handler::isPermitted() { return false; }
167 100 : Status RequestHandlerMap::Handler::onRequest() { return DECLINED; }
168 0 : Value RequestHandlerMap::Handler::onData() { return Value(); }
169 :
170 100 : RequestHandlerMap::Handler::Handler() { }
171 0 : RequestHandlerMap::Handler::~Handler() { }
172 :
173 100 : void RequestHandlerMap::Handler::onParams(const HandlerInfo *info, Value &&val) {
174 100 : _info = info;
175 100 : _params = std::move(val);
176 100 : }
177 :
178 100 : Status RequestHandlerMap::Handler::onTranslateName(Request &rctx) {
179 100 : auto &info = rctx.getInfo();
180 100 : if (info.method != _info->getMethod()) {
181 0 : return HTTP_METHOD_NOT_ALLOWED;
182 : }
183 :
184 100 : switch (info.method) {
185 100 : case RequestMethod::Post:
186 : case RequestMethod::Put:
187 : case RequestMethod::Patch:
188 100 : return OK;
189 : break;
190 0 : default: {
191 0 : if (!processQueryFields(Value(info.queryData))) {
192 0 : return HTTP_BAD_REQUEST;
193 : }
194 0 : bool hasLocation = false;
195 0 : auto ret = onRequest();
196 0 : if (ret == DECLINED) {
197 0 : if (auto data = onData()) {
198 0 : auto loc = _request.getResponseHeader("Location");
199 0 : if (!loc.empty()) {
200 0 : hasLocation = true;
201 : } else {
202 0 : data.setBool(true, "OK");
203 0 : return writeResult(data);
204 : }
205 : } else {
206 0 : auto loc = _request.getResponseHeader("Location");
207 0 : if (!loc.empty()) {
208 0 : hasLocation = true;
209 : } else {
210 0 : Value retVal({ stappler::pair("OK", Value(false)) });
211 0 : return writeResult(retVal);
212 0 : }
213 0 : }
214 : }
215 0 : if (ret <= 0 && hasLocation) {
216 0 : return HTTP_SEE_OTHER;
217 : }
218 0 : return ret;
219 : break;
220 : }
221 : }
222 : return HTTP_BAD_REQUEST;
223 : }
224 :
225 100 : void RequestHandlerMap::Handler::onInsertFilter(Request &rctx) {
226 100 : auto &info = rctx.getInfo();
227 100 : switch (info.method) {
228 100 : case RequestMethod::Post:
229 : case RequestMethod::Put:
230 : case RequestMethod::Patch: {
231 100 : auto cfg = _info->getInputConfig();
232 100 : if ((cfg.required & db::InputConfig::Require::Data) == db::InputConfig::Require::None) {
233 25 : cfg.required |= db::InputConfig::Require::Data;
234 : }
235 100 : if ((cfg.required & db::InputConfig::Require::Files) == db::InputConfig::Require::None
236 100 : && (cfg.required & db::InputConfig::Require::FilesAsData) == db::InputConfig::Require::None) {
237 50 : cfg.required |= db::InputConfig::Require::Files;
238 : }
239 100 : rctx.setInputConfig(cfg);
240 :
241 100 : auto ex = InputFilter::insert(rctx);
242 100 : if (ex != InputFilter::Exception::None) {
243 0 : if (ex == InputFilter::Exception::TooLarge) {
244 0 : rctx.setStatus(HTTP_REQUEST_ENTITY_TOO_LARGE);
245 0 : } else if (ex == InputFilter::Exception::Unrecognized) {
246 0 : rctx.setStatus(HTTP_UNSUPPORTED_MEDIA_TYPE);
247 : }
248 : }
249 100 : break;
250 : }
251 0 : default:
252 0 : break;
253 : }
254 100 : }
255 :
256 100 : Status RequestHandlerMap::Handler::onHandler(Request &) {
257 100 : return OK;
258 : }
259 :
260 100 : void RequestHandlerMap::Handler::onFilterComplete(InputFilter *filter) {
261 100 : auto &info = _request.getInfo();
262 :
263 100 : _filter = filter;
264 :
265 100 : if (!processQueryFields(Value(info.queryData))) {
266 0 : _request.setStatus(HTTP_BAD_REQUEST);
267 0 : return;
268 : }
269 100 : if (!processInputFields(_filter)) {
270 0 : _request.setStatus(HTTP_BAD_REQUEST);
271 0 : return;
272 : }
273 :
274 100 : bool hasLocation = false;
275 100 : auto ret = onRequest();
276 100 : if (ret == DECLINED) {
277 100 : if (auto data = onData()) {
278 100 : auto loc = _request.getResponseHeader("Location");
279 100 : if (!loc.empty()) {
280 0 : hasLocation = true;
281 : } else {
282 100 : data.setBool(true, "OK");
283 100 : writeResult(data);
284 : }
285 : } else {
286 0 : auto loc = _request.getResponseHeader("Location");
287 0 : if (!loc.empty()) {
288 0 : hasLocation = true;
289 : } else {
290 0 : Value retVal({ stappler::pair("OK", Value(false)) });
291 0 : writeResult(retVal);
292 0 : return;
293 0 : }
294 100 : }
295 : }
296 100 : if (ret > 0) {
297 0 : _request.setStatus(ret);
298 100 : } else if (hasLocation) {
299 0 : _request.setStatus(HTTP_SEE_OTHER);
300 : }
301 : }
302 :
303 100 : bool RequestHandlerMap::Handler::processQueryFields(Value &&args) {
304 100 : _queryFields = std::move(args);
305 100 : if (_info->getQueryScheme().getFields().empty()) {
306 75 : return true;
307 : }
308 :
309 25 : _info->getQueryScheme().transform(_queryFields, db::Scheme::TransformAction::ProtectedCreate);
310 :
311 25 : bool success = true;
312 75 : for (auto &it : _info->getQueryScheme().getFields()) {
313 50 : auto &val = _queryFields.getValue(it.first);
314 50 : if (val.isNull() && it.second.hasFlag(db::Flags::Required)) {
315 0 : _request.addError("HandlerMap", "No value for required field",
316 0 : Value({ std::make_pair("field", Value(it.first)) }));
317 0 : success = false;
318 : }
319 : }
320 25 : return success;
321 : }
322 :
323 100 : bool RequestHandlerMap::Handler::processInputFields(InputFilter *filter) {
324 100 : _inputFields = std::move(filter->getData());
325 :
326 100 : if (_info->getInputScheme().getFields().empty()) {
327 25 : return true;
328 : }
329 :
330 75 : _info->getInputScheme().transform(_inputFields, db::Scheme::TransformAction::ProtectedCreate);
331 :
332 75 : auto &cfg = _request.getInputConfig();
333 75 : for (auto &it : filter->getFiles()) {
334 0 : if (auto f = _info->getInputScheme().getField(it.name)) {
335 0 : if ((cfg.required & db::InputConfig::Require::FilesAsData) != db::InputConfig::Require::None && db::InputConfig::isFileAsDataSupportedForType(it.type)) {
336 : // do nothing
337 : } else {
338 0 : if (db::File::validateFileField(_request.host().getRoot(), *f, it)) {
339 0 : _inputFields.setInteger(it.negativeId(), it.name);
340 : }
341 : }
342 : }
343 : }
344 :
345 75 : bool success = true;
346 350 : for (auto &it : _info->getInputScheme().getFields()) {
347 275 : auto &val = _inputFields.getValue(it.first);
348 275 : if (val.isNull() && it.second.hasFlag(db::Flags::Required)) {
349 0 : _request.addError("HandlerMap", "No value for required field",
350 0 : Value({ std::make_pair("field", Value(it.first)) }));
351 0 : success = false;
352 : }
353 : }
354 75 : return success;
355 : }
356 :
357 100 : Status RequestHandlerMap::Handler::writeResult(Value &data) {
358 100 : auto status = _request.getInfo().status;
359 100 : if (status >= 400) {
360 0 : return status;
361 : }
362 :
363 100 : data.setInteger(Time::now().toMicros(), "date");
364 : #if DEBUG
365 100 : auto &debug = _request.getDebugMessages();
366 100 : if (!debug.empty()) {
367 0 : data.setArray(debug, "debug");
368 : }
369 : #endif
370 100 : auto &error = _request.getErrorMessages();
371 100 : if (!error.empty()) {
372 0 : data.setArray(error, "errors");
373 : }
374 :
375 100 : _request.writeData(data, allowJsonP());
376 100 : return DONE;
377 : }
378 :
379 0 : db::InputFile *RequestHandlerMap::Handler::getInputFile(const StringView &name) {
380 0 : if (!_filter) {
381 0 : return nullptr;
382 : }
383 :
384 0 : for (auto &it : _filter->getFiles()) {
385 0 : if (it.name == name) {
386 0 : return ⁢
387 : }
388 : }
389 :
390 0 : return nullptr;
391 : }
392 :
393 :
394 100 : RequestHandlerMap::HandlerInfo::HandlerInfo(const StringView &name, RequestMethod m, const StringView &pt,
395 100 : Function<Handler *()> &&cb, Value &&opts)
396 100 : : name(name.str<Interface>()), method(m), pattern(pt.str<Interface>()), handler(std::move(cb)), options(std::move(opts))
397 100 : , queryFields(name), inputFields(name) {
398 100 : StringView p(pattern);
399 250 : while (!p.empty()) {
400 150 : auto tmp = p.readUntil<StringView::Chars<':'>>();
401 150 : if (!tmp.empty()) {
402 150 : fragments.emplace_back(Fragment::Text, tmp);
403 : }
404 150 : if (p.is(':')) {
405 100 : auto tmp = p;
406 100 : ++ p;
407 100 : auto ptrn = p.readUntil<StringView::Chars<'/', '.', ':', '#', '?', ','>>();
408 100 : if (!ptrn.empty()) {
409 100 : fragments.emplace_back(Fragment::Pattern, StringView(tmp.data(), ptrn.size() + 1));
410 : }
411 : }
412 : }
413 100 : }
414 :
415 50 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::HandlerInfo::addQueryFields(std::initializer_list<db::Field> il) {
416 50 : queryFields.define(il);
417 50 : return *this;
418 : }
419 0 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::HandlerInfo::addQueryFields(Vector<db::Field> &&il) {
420 0 : queryFields.define(std::move(il));
421 0 : return *this;
422 : }
423 :
424 50 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::HandlerInfo::addInputFields(std::initializer_list<db::Field> il) {
425 50 : inputFields.define(il);
426 50 : return *this;
427 : }
428 0 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::HandlerInfo::addInputFields(Vector<db::Field> &&il) {
429 0 : inputFields.define(std::move(il));
430 0 : return *this;
431 : }
432 :
433 75 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::HandlerInfo::setInputConfig(db::InputConfig cfg) {
434 75 : inputFields.setConfig(cfg);
435 75 : return *this;
436 : }
437 :
438 400 : Value RequestHandlerMap::HandlerInfo::match(const StringView &path, size_t &match) const {
439 400 : size_t nmatch = 0;
440 1200 : Value ret({ stappler::pair("path", Value(path)) });
441 400 : auto it = fragments.begin();
442 400 : StringView r(path);
443 975 : while (!r.empty() && it != fragments.end()) {
444 700 : switch (it->type) {
445 450 : case Fragment::Text:
446 450 : if (r.starts_with(StringView(it->string))) {
447 325 : r += it->string.size();
448 325 : nmatch += it->string.size();
449 325 : ++ it;
450 : } else {
451 125 : return Value();
452 : }
453 325 : break;
454 250 : case Fragment::Pattern:
455 250 : if (StringView(it->string).is(':')) {
456 250 : StringView name(it->string.data() + 1, it->string.size() - 1);
457 250 : if (name.empty()) {
458 0 : return Value();
459 : }
460 :
461 250 : ++ it;
462 250 : if (it != fragments.end()) {
463 200 : auto tmp = r.readUntilString(it->string);
464 200 : if (tmp.empty()) {
465 0 : return Value();
466 : }
467 200 : ret.setString(tmp, name.str<Interface>());
468 : } else {
469 50 : ret.setString(r, name.str<Interface>());
470 50 : r += r.size();
471 : }
472 : }
473 250 : break;
474 : }
475 : }
476 :
477 275 : if (!r.empty() || it != fragments.end()) {
478 150 : return Value();
479 : }
480 :
481 125 : match = nmatch;
482 125 : return ret;
483 400 : }
484 :
485 100 : RequestHandlerMap::Handler *RequestHandlerMap::HandlerInfo::onHandler(Value &&p) const {
486 100 : if (auto h = handler()) {
487 100 : h->onParams(this, std::move(p));
488 100 : return h;
489 : }
490 0 : return nullptr;
491 : }
492 :
493 400 : RequestMethod RequestHandlerMap::HandlerInfo::getMethod() const {
494 400 : return method;
495 : }
496 :
497 100 : const db::InputConfig &RequestHandlerMap::HandlerInfo::getInputConfig() const {
498 100 : return inputFields.getConfig();
499 : }
500 :
501 100 : StringView RequestHandlerMap::HandlerInfo::getName() const {
502 100 : return name;
503 : }
504 100 : StringView RequestHandlerMap::HandlerInfo::getPattern() const {
505 100 : return pattern;
506 : }
507 0 : const Value &RequestHandlerMap::HandlerInfo::getOptions() const {
508 0 : return options;
509 : }
510 :
511 250 : const db::Scheme &RequestHandlerMap::HandlerInfo::getQueryScheme() const {
512 250 : return queryFields;
513 : }
514 350 : const db::Scheme &RequestHandlerMap::HandlerInfo::getInputScheme() const {
515 350 : return inputFields;
516 : }
517 :
518 25 : RequestHandlerMap::RequestHandlerMap() { }
519 :
520 0 : RequestHandlerMap::~RequestHandlerMap() { }
521 :
522 100 : RequestHandlerMap::Handler *RequestHandlerMap::onRequest(Request &req, const StringView &ipath) const {
523 100 : auto &reqInfo = req.getInfo();
524 100 : StringView path(ipath.empty() ? StringView("/") : ipath);
525 100 : const HandlerInfo *info = nullptr;
526 100 : Value params;
527 100 : size_t score = 0;
528 500 : for (auto &it : _handlers) {
529 400 : size_t pscore = 0;
530 400 : if (auto val = it.match(path, pscore)) {
531 125 : if (pscore > score || (pscore == score && info && it.getMethod() == reqInfo.method && it.getMethod() != info->getMethod())) {
532 125 : params = std::move(val);
533 125 : if (it.getMethod() == reqInfo.method || !info) {
534 125 : info = ⁢
535 125 : score = pscore;
536 : }
537 : }
538 400 : }
539 : }
540 :
541 100 : if (info) {
542 100 : return info->onHandler(std::move(params));
543 : }
544 :
545 0 : return nullptr;
546 100 : }
547 :
548 25 : const Vector<RequestHandlerMap::HandlerInfo> &RequestHandlerMap::getHandlers() const {
549 25 : return _handlers;
550 : }
551 :
552 100 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::addHandler(const StringView &name, RequestMethod m, const StringView &pattern,
553 : Function<Handler *()> &&cb, Value &&opts) {
554 100 : _handlers.emplace_back(name, m, pattern, std::move(cb), std::move(opts));
555 100 : return _handlers.back();
556 : }
557 :
558 :
559 : class HandlerCallback : public RequestHandlerMap::Handler {
560 : public: // simplified interface
561 0 : HandlerCallback(const Function<bool(Handler &)> &accessControl, const Function<Value(Handler &)> &process)
562 0 : : _accessControl(accessControl), _process(process) { }
563 :
564 0 : virtual ~HandlerCallback() { }
565 :
566 0 : virtual bool isPermitted() override { return _process && _accessControl && _accessControl(*this); }
567 0 : virtual Value onData() override {
568 0 : auto ret = _process(*this);
569 0 : if (ret) {
570 0 : if (_info->getOptions().isString("location")) {
571 0 : auto locVar = _info->getOptions().getString("location");
572 0 : auto loc = StringView(_request.getInfo().queryData.getString(locVar));
573 0 : if (!loc.empty()) {
574 0 : if (loc.starts_with("/") || loc.starts_with(StringView(_request.getFullHostname()))) {
575 0 : _request.redirectTo(loc);
576 : }
577 : }
578 0 : }
579 : }
580 0 : return ret;
581 0 : }
582 :
583 : public:
584 : Function<bool(Handler &)> _accessControl;
585 : Function<Value(Handler &)> _process;
586 : };
587 :
588 0 : RequestHandlerMap::HandlerInfo &RequestHandlerMap::addHandler(const StringView &name, RequestMethod m, const StringView &pattern,
589 : Function<bool(Handler &)> &&accessControl, Function<Value(Handler &)> &&process, Value &&opts) {
590 0 : return addHandler(name, m, pattern, [accessControl = std::move(accessControl), process = std::move(process)] () -> Handler * {
591 0 : return new HandlerCallback(accessControl, process);
592 0 : }, std::move(opts));
593 : }
594 :
595 : }
|