LCOV - code coverage report
Current view: top level - extra/webserver/webserver/resource - SPWebResourceHandler.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 199 229 86.9 %
Date: 2024-05-12 00:16:13 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /**
       2             :  Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
       3             : 
       4             :  Permission is hereby granted, free of charge, to any person obtaining a copy
       5             :  of this software and associated documentation files (the "Software"), to deal
       6             :  in the Software without restriction, including without limitation the rights
       7             :  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8             :  copies of the Software, and to permit persons to whom the Software is
       9             :  furnished to do so, subject to the following conditions:
      10             : 
      11             :  The above copyright notice and this permission notice shall be included in
      12             :  all copies or substantial portions of the Software.
      13             : 
      14             :  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15             :  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16             :  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17             :  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18             :  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19             :  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      20             :  THE SOFTWARE.
      21             :  **/
      22             : 
      23             : #include "SPWebResourceHandler.h"
      24             : #include "SPWebResource.h"
      25             : #include "SPWebRequest.h"
      26             : #include "SPWebRequestController.h"
      27             : #include "SPWebOutput.h"
      28             : #include "SPWebRoot.h"
      29             : #include "SPWebInputFilter.h"
      30             : 
      31             : namespace STAPPLER_VERSIONIZED stappler::web {
      32             : 
      33             : SP_COVERAGE_TRIVIAL
      34             : static void exitWithResourceHandlerError(Request &rctx, StringView text, const Value &source = Value(), Value &&data = Value()) {
      35             :         rctx.setStatus(HTTP_BAD_REQUEST);
      36             :         if (source.isNull()) {
      37             :                 Root::getCurrent()->error("ResourceHandler", text, Value(data));
      38             :         }
      39             : }
      40             : 
      41        2125 : ResourceHandler::ResourceHandler(const db::Scheme &scheme, const Value &val)
      42        2125 : : _scheme(scheme), _value(val) { }
      43             : 
      44        4250 : bool ResourceHandler::isRequestPermitted(Request &rctx) {
      45        4250 :         _transaction = db::Transaction::acquire(db::Adapter(rctx.getController()->acquireDatabase()));
      46        4250 :         return true;
      47             : }
      48             : 
      49        2125 : Status ResourceHandler::onTranslateName(Request &rctx) {
      50        2125 :         if (!isRequestPermitted(rctx)) { return HTTP_FORBIDDEN; }
      51             : 
      52        2125 :         _method = rctx.getInfo().method;
      53        2125 :         switch (_method) {
      54        2125 :         case RequestMethod::Get:
      55             :         case RequestMethod::Post:
      56             :         case RequestMethod::Put:
      57             :         case RequestMethod::Patch:
      58             :         case RequestMethod::Delete:
      59        2125 :                 break;
      60           0 :         default:
      61           0 :                 return HTTP_NOT_IMPLEMENTED;
      62             :                 break;
      63             :         }
      64             : 
      65        2125 :         auto &data = rctx.getInfo().queryData;
      66        2125 :         if (data.isString("METHOD")) {
      67         100 :                 auto method = data.getString("METHOD");
      68         100 :                 if (_method == RequestMethod::Get) {
      69          25 :                         if (method == "DELETE") {
      70          25 :                                 _method = RequestMethod::Delete;
      71             :                         }
      72          75 :                 } else if (_method == RequestMethod::Post) {
      73          50 :                         if (method == "PUT") {
      74           0 :                                 _method = RequestMethod::Put;
      75          50 :                         } else if (method == "PATCH") {
      76          50 :                                 _method = RequestMethod::Patch;
      77             :                         }
      78             :                 }
      79         100 :         }
      80             : 
      81        2125 :         _resource = getResource(rctx);
      82        2125 :         if (!_resource) { return HTTP_NOT_FOUND; }
      83             : 
      84        2100 :         auto user = rctx.getAuthorizedUser();
      85        2100 :         if (!user && data.isString("token")) {
      86           0 :                 user = rctx.getUser();
      87             :         }
      88             : 
      89        2100 :         _resource->setUser(user);
      90        2100 :         _resource->setFilterData(_value);
      91             : 
      92        2100 :         auto args = rctx.getInfo().url.query;
      93        2100 :         if (!args.empty() && args.front() == '(') {
      94         575 :                 _resource->applyQuery(data);
      95             :         }
      96             : 
      97        2100 :         switch (_method) {
      98        1450 :         case RequestMethod::Get: {
      99        1450 :                 auto modified = rctx.getRequestHeader("if-modified-since");
     100        1450 :                 auto mt = modified.empty() ? 0 : Time::fromHttp(modified).toSeconds();
     101             : 
     102        1450 :                 if (auto d = _resource->getSourceDelta()) {
     103          75 :                         rctx.setResponseHeader("Last-Modified", d.toHttp<Interface>());
     104          75 :                         if (mt >= d.toSeconds()) {
     105          25 :                                 return HTTP_NOT_MODIFIED;
     106             :                         }
     107             :                 }
     108             : 
     109        1425 :                 if (mt > 0 && _resource->getType() == ResourceType::Object) {
     110          25 :                         if (auto res = dynamic_cast<ResourceObject *>(_resource)) {
     111          25 :                                 if (auto objMtime = res->getObjectMtime()) {
     112          25 :                                         rctx.setResponseHeader("Last-Modified", Time::microseconds(objMtime).toHttp<Interface>());
     113          25 :                                         if (mt >= uint64_t(objMtime / 1000000)) {
     114          25 :                                                 return HTTP_NOT_MODIFIED;
     115             :                                         }
     116             :                                 }
     117             :                         }
     118             :                 }
     119             : 
     120        1400 :                 _resource->prepare(db::QueryList::SimpleGet);
     121             : 
     122        1400 :                 if (!rctx.getInfo().headerRequest) {
     123        1375 :                         return writeToRequest(rctx);
     124             :                 } else {
     125          25 :                         return writeInfoToReqest(rctx);
     126             :                 }
     127             :                 break;
     128             :         }
     129         150 :         case RequestMethod::Delete:
     130         150 :                 _resource->prepare();
     131         150 :                 if (_resource->removeObject()) {
     132         150 :                         if (data.isString("location")) {
     133           0 :                                 return rctx.redirectTo(String(data.getString("location")));
     134         150 :                         } else if (data.isString("target")) {
     135           0 :                                 auto &target = data.getString("target");
     136           0 :                                 if (!target.empty() && (StringView(target).starts_with(StringView(rctx.getFullHostname())) || target[0] == '/')) {
     137           0 :                                         return rctx.redirectTo(String(target));
     138             :                                 }
     139             :                         }
     140         150 :                         return HTTP_NO_CONTENT;
     141             :                 }
     142           0 :                 break;
     143         300 :         case RequestMethod::Post:
     144         300 :                 _resource->prepare();
     145         300 :                 if (_resource->prepareCreate()) {
     146         300 :                         return DECLINED;
     147             :                 }
     148           0 :                 break;
     149         150 :         case RequestMethod::Put:
     150         150 :                 _resource->prepare();
     151         150 :                 if (_resource->prepareUpdate()) {
     152         150 :                         return DECLINED;
     153             :                 }
     154           0 :                 break;
     155          50 :         case RequestMethod::Patch:
     156          50 :                 _resource->prepare();
     157          50 :                 if (_resource->prepareAppend()) {
     158          50 :                         return DECLINED;
     159             :                 }
     160           0 :                 break;
     161           0 :         default:
     162           0 :                 break;
     163             :         }
     164             : 
     165           0 :         return getHintedStatus(HTTP_FORBIDDEN);
     166             : }
     167             : 
     168         525 : void ResourceHandler::onInsertFilter(Request &rctx) {
     169         525 :         if (_method == RequestMethod::Post || _method == RequestMethod::Put || _method == RequestMethod::Patch) {
     170         500 :                 rctx.setInputConfig(db::InputConfig{
     171         500 :                         _resource->getInputFlags(),
     172         500 :                         _resource->getMaxRequestSize(),
     173         500 :                         _resource->getMaxVarSize(),
     174         500 :                         _resource->getMaxFileSize()
     175             :                 });
     176             : 
     177         500 :                 auto ex = InputFilter::insert(rctx);
     178         500 :                 if (ex != InputFilter::Exception::None) {
     179           0 :                         rctx.setStatus(InputFilter::getStatusForException(ex));
     180             :                 }
     181         525 :         } else if (_method != RequestMethod::Get && _method != RequestMethod::Delete) {
     182           0 :                 exitWithResourceHandlerError(rctx, "Input data can not be recieved, no available filters");
     183             :         }
     184         525 : }
     185             : 
     186         525 : Status ResourceHandler::onHandler(Request &rctx) {
     187         525 :         auto num = rctx.getInfo().method;
     188         525 :         if (num == RequestMethod::Get) {
     189          25 :                 return DECLINED;
     190             :         } else {
     191         500 :                 return OK;
     192             :         }
     193             : }
     194             : 
     195         500 : void ResourceHandler::onFilterComplete(InputFilter *filter) {
     196         500 :         auto rctx = filter->getRequest();
     197         500 :         if (_method == RequestMethod::Put) {
     198             :                 // we should update our resource
     199         150 :                 auto result = _resource->updateObject(filter->getData(), filter->getFiles());
     200         150 :                 if (result) {
     201         150 :                         auto &target = rctx.getInfo().queryData.getValue("target");
     202         150 :                         if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
     203           0 :                                 rctx.setStatus(rctx.redirectTo(String(target.getString())));
     204             :                         } else {
     205         150 :                                 writeDataToRequest(rctx, move(result));
     206         150 :                                 rctx.setStatus(HTTP_OK);
     207             :                         }
     208             :                 } else {
     209           0 :                         exitWithResourceHandlerError(rctx, "Fail to perform update", result, Value(filter->getData()));
     210             :                 }
     211         500 :         } else if (_method == RequestMethod::Post) {
     212         300 :                 auto &d = filter->getData();
     213         300 :                 auto tmp = d;
     214         300 :                 auto result = _resource->createObject(d, filter->getFiles());
     215         300 :                 if (result) {
     216         300 :                         auto &target = rctx.getInfo().queryData.getValue("target");
     217         300 :                         if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
     218           0 :                                 rctx.setStatus(rctx.redirectTo(String(target.getString())));
     219             :                         } else {
     220         300 :                                 writeDataToRequest(rctx, move(result));
     221         300 :                                 rctx.setStatus(HTTP_CREATED);
     222             :                         }
     223             :                 } else {
     224           0 :                         exitWithResourceHandlerError(rctx, "Fail to perform create", result, Value(move(tmp)));
     225             :                 }
     226         350 :         } else if (_method == RequestMethod::Patch) {
     227          50 :                 auto result = _resource->appendObject(filter->getData());
     228          50 :                 if (result) {
     229          50 :                         auto &target = rctx.getInfo().queryData.getValue("target");
     230          50 :                         if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
     231           0 :                                 rctx.setStatus(rctx.redirectTo(String(target.getString())));
     232             :                         } else {
     233          50 :                                 writeDataToRequest(rctx, move(result));
     234          50 :                                 rctx.setStatus(HTTP_OK);
     235             :                         }
     236             :                 } else {
     237           0 :                         exitWithResourceHandlerError(rctx, "Fail to perform append", result, Value(filter->getData()));
     238             :                 }
     239          50 :         }
     240         500 : }
     241             : 
     242        2125 : Resource *ResourceHandler::getResource(Request &rctx) {
     243        2125 :         return Resource::resolve(_transaction, _scheme, _subPath, _value);
     244             : }
     245             : 
     246        1850 : Status ResourceHandler::writeDataToRequest(Request &rctx, Value &&result) {
     247        1850 :         Value origin;
     248        1850 :         origin.setInteger(_resource->getSourceDelta().toMicroseconds(), "delta");
     249             : 
     250        1850 :         if (auto &t = _resource->getQueries().getContinueToken()) {
     251         375 :                 if (t.isInit()) {
     252             :                         Value cursor({
     253         750 :                                 pair("start", Value(t.getStart())),
     254         750 :                                 pair("end", Value(t.getEnd())),
     255         750 :                                 pair("total", Value(t.getTotal())),
     256         750 :                                 pair("count", Value(t.getCount())),
     257         750 :                                 pair("field", Value(t.getField())),
     258        4125 :                         });
     259             : 
     260         375 :                         if (t.hasNext()) {
     261         250 :                                 cursor.setString(t.encodeNext(), "next");
     262             :                         }
     263             : 
     264         375 :                         if (t.hasPrev()) {
     265         250 :                                 cursor.setString(t.encodePrev(), "prev");
     266             :                         }
     267             : 
     268         375 :                         origin.setValue(move(cursor), "cursor");
     269         375 :                 }
     270             :         }
     271        3700 :         return output::writeResourceData(rctx, move(result), move(origin));
     272        1850 : }
     273             : 
     274          25 : Status ResourceHandler::writeInfoToReqest(Request &rctx) {
     275          25 :         Value result(_resource->getResultObject());
     276          25 :         if (_resource->getType() == ResourceType::File) {
     277           0 :                 return output::writeResourceFileHeader(rctx, result);
     278             :         }
     279             : 
     280          25 :         return getHintedStatus(HTTP_NO_CONTENT);
     281          25 : }
     282             : 
     283        1375 : Status ResourceHandler::writeToRequest(Request &rctx) {
     284        1375 :         if (_resource->getType() == ResourceType::File) {
     285          25 :                 Value result(_resource->getResultObject());
     286          25 :                 if (result) {
     287          25 :                         return output::writeResourceFileData(rctx, move(result));
     288             :                 }
     289          25 :         } else {
     290        1350 :                 Value result(_resource->getResultObject());
     291        1350 :                 if (result) {
     292        1350 :                         return writeDataToRequest(rctx, move(result));
     293             :                 }
     294        1350 :         }
     295             : 
     296           0 :         return getHintedStatus(HTTP_NOT_FOUND);
     297             : }
     298             : 
     299          25 : Status ResourceHandler::getHintedStatus(Status s) const {
     300          25 :         auto status = _resource->getStatus();
     301          25 :         if (status != HTTP_OK) {
     302           0 :                 return status;
     303             :         }
     304          25 :         return s;
     305             : }
     306             : 
     307          25 : ResourceMultiHandler::ResourceMultiHandler(const Map<StringView, const Scheme *> &schemes)
     308          25 : : _schemes(schemes) { }
     309             : 
     310          50 : bool ResourceMultiHandler::isRequestPermitted(Request &rctx) {
     311          50 :         _transaction = db::Transaction::acquire(db::Adapter(rctx.getController()->acquireDatabase()));
     312          50 :         return true;
     313             : }
     314             : 
     315          25 : Status ResourceMultiHandler::onTranslateName(Request &rctx) {
     316          25 :         if (!isRequestPermitted(rctx)) {
     317           0 :                 return HTTP_FORBIDDEN;
     318             :         }
     319             : 
     320          25 :         auto &data = rctx.getInfo().queryData;
     321          25 :         auto user = rctx.getAuthorizedUser();
     322          25 :         if (!user && data.isString("token")) {
     323           0 :                 user = rctx.getUser();
     324             :         }
     325             : 
     326          25 :         int64_t targetDelta = 0;
     327          25 :         Time deltaMax;
     328          25 :         Value result;
     329          25 :         Value delta;
     330          25 :         Vector<Pair<String, Resource *>> resources;
     331          25 :         resources.reserve(data.size());
     332             : 
     333          25 :         if (data.isInteger("delta")) {
     334          25 :                 targetDelta = data.getInteger("delta");
     335             :         }
     336             : 
     337         100 :         for (auto &it : data.asDict()) {
     338          75 :                 StringView path(it.first);
     339          75 :                 auto scheme = path.readUntil<StringView::Chars<'/'>>();
     340          75 :                 if (path.is('/')) {
     341          50 :                         ++ path;
     342             :                 }
     343          75 :                 auto s_it = _schemes.find(scheme.str<Interface>());
     344          75 :                 if (s_it != _schemes.end()) {
     345          50 :                         if (auto resource = Resource::resolve(_transaction, *s_it->second, path)) {
     346          50 :                                 resource->setUser(user);
     347          50 :                                 resource->applyQuery(it.second);
     348          50 :                                 if (targetDelta > 0 && resource->isDeltaApplicable() && !resource->getQueryDelta()) {
     349          50 :                                         resource->setQueryDelta(Time::microseconds(targetDelta));
     350             :                                 }
     351          50 :                                 resource->prepare();
     352             : 
     353          50 :                                 if (resource->hasDelta()) {
     354          50 :                                         deltaMax = max(deltaMax, resource->getSourceDelta());
     355          50 :                                         delta.setInteger(resource->getSourceDelta().toMicroseconds(), it.first);
     356             :                                 }
     357             : 
     358          50 :                                 resources.emplace_back(it.first, resource);
     359             :                         }
     360             :                 }
     361             :         }
     362             : 
     363          25 :         if (deltaMax && delta.size() == resources.size()) {
     364          25 :                 rctx.setResponseHeader("Last-Modified", deltaMax.toHttp<Interface>());
     365          25 :                 if (targetDelta > 0) {
     366          25 :                         auto modified = rctx.getRequestHeader("if-modified-since");
     367          25 :                         if (Time::fromHttp(modified).toSeconds() >= deltaMax.toSeconds()) {
     368           0 :                                 return HTTP_NOT_MODIFIED;
     369             :                         }
     370             :                 }
     371             :         }
     372             : 
     373          75 :         for (auto &it : resources) {
     374          50 :                 if (!rctx.getInfo().headerRequest) {
     375          50 :                         result.setValue(it.second->getResultObject(), it.first);
     376             :                 }
     377             :         }
     378             : 
     379          25 :         if (!result.empty()) {
     380          25 :                 resultData.setValue(move(delta), "delta");
     381          25 :                 return writeDataToRequest(rctx, move(result));
     382             :         } else {
     383           0 :                 return HTTP_NOT_FOUND;
     384             :         }
     385             : 
     386             :         return HTTP_NOT_IMPLEMENTED;
     387          25 : }
     388             : 
     389          25 : Status ResourceMultiHandler::writeDataToRequest(Request &rctx, Value &&result) {
     390          25 :         return output::writeResourceData(rctx, move(result), move(resultData));
     391             : }
     392             : 
     393             : }

Generated by: LCOV version 1.14