LCOV - code coverage report
Current view: top level - extra/webserver/webserver/filter - SPWebInputFilter.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 188 202 93.1 %
Date: 2024-05-12 00:16:13 Functions: 37 37 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 "SPWebInputFilter.h"
      24             : #include "SPWebHostController.h"
      25             : #include "SPWebRequest.h"
      26             : #include "SPWebRequestController.h"
      27             : #include "SPWebRoot.h"
      28             : #include "SPWebMultipartParser.h"
      29             : #include "SPDbFile.h"
      30             : 
      31             : namespace STAPPLER_VERSIONIZED stappler::web {
      32             : 
      33             : class FileParser : public InputParser {
      34             : public:
      35         100 :         FileParser(const db::InputConfig &c, const StringView &ct, const StringView &name, size_t cl)
      36         100 :         : InputParser(c, cl) {
      37         100 :                 if (cl < getConfig().maxFileSize) {
      38         200 :                         db::InputFile fileObj(name.str<Interface>(), ct.str<Interface>(), String(), String(), cl, files.size());
      39         100 :                         files.emplace_back(move(fileObj));
      40         100 :                         file = &files.back();
      41         100 :                 }
      42         100 :         }
      43             : 
      44             :         SP_COVERAGE_TRIVIAL
      45             :         virtual ~FileParser() { }
      46             : 
      47         100 :         virtual bool run(BytesView data) override {
      48         100 :                 if (file && !skip) {
      49         100 :                         if (file->writeSize + data.size() < getConfig().maxFileSize) {
      50         100 :                                 file->write((const char *)data.data(), data.size());
      51             :                         } else {
      52           0 :                                 file->close();
      53           0 :                                 skip = true;
      54           0 :                                 return false;
      55             :                         }
      56             :                 }
      57         100 :                 return true;
      58             :         }
      59         100 :         virtual void finalize() override { }
      60             : 
      61             : protected:
      62             :         bool skip = false;
      63             :         db::InputFile *file = nullptr;
      64             : };
      65             : 
      66             : class DataParser : public InputParser {
      67             : public:
      68         450 :         DataParser(const db::InputConfig &c, size_t s) : InputParser(c, s) { }
      69             : 
      70             :         SP_COVERAGE_TRIVIAL
      71             :         virtual ~DataParser() { }
      72             : 
      73         450 :         virtual bool run(BytesView data) override {
      74         450 :                 stream.write((const char *)data.data(), data.size());
      75         450 :                 return true;
      76             :         }
      77         450 :         virtual void finalize() override {
      78         450 :                 root = data::read<Interface>(stream.weak());
      79         450 :         }
      80             : 
      81             : protected:
      82             :         StringStream stream;
      83             : };
      84             : 
      85             : class UrlEncodeParser : public InputParser {
      86             : public:
      87             :         using Reader = StringView;
      88             : 
      89          25 :         UrlEncodeParser(const db::InputConfig &cfg, size_t len)
      90          25 :         : InputParser(cfg, len) { }
      91             : 
      92             :         SP_COVERAGE_TRIVIAL
      93             :         virtual ~UrlEncodeParser() { }
      94             : 
      95          25 :         virtual bool run(BytesView data) override {
      96          25 :                 stream.write((const char *)data.data(), data.size());
      97          25 :                 return true;
      98             :         }
      99          25 :         virtual void finalize() override {
     100          25 :                 root = data::readUrlencoded<Interface>(stream.weak());
     101          25 :         }
     102             : 
     103             : protected:
     104             :         StringStream stream;
     105             : };
     106             : 
     107         650 : InputParser::InputParser(const db::InputConfig &cfg, size_t len)
     108         650 : : config(cfg), length(len) { }
     109             : 
     110         650 : void InputParser::cleanup() {
     111         825 :         for (auto &it : files) {
     112         175 :                 it.close();
     113             :         }
     114         650 :         files.clear();
     115         650 : }
     116             : 
     117        9625 : const db::InputConfig &InputParser::getConfig() const {
     118        9625 :         return config;
     119             : }
     120             : 
     121         650 : static InputFilter::Accept getAcceptedData(const Request &req, InputFilter::Exception &e) {
     122         650 :         Request r = req;
     123         650 :         auto &cfg = r.getInputConfig();
     124         650 :         InputFilter::Accept ret = InputFilter::Accept::None;
     125             : 
     126         650 :         size_t cl = 0;
     127         650 :         StringView ct;
     128             : 
     129         650 :         auto &info = req.getInfo();
     130             : 
     131             :         auto reportError = [&] (InputFilter::Exception ex, StringView info) SP_COVERAGE_TRIVIAL -> InputFilter::Accept {
     132             :                 switch (ex) {
     133             :                 case InputFilter::Exception::Unrecognized:
     134             :                         req.addError("InputFilter", "No data to process", Value{
     135             :                                 std::make_pair("content", Value(ct)),
     136             :                                 std::make_pair("available", Value(info)),
     137             :                         });
     138             :                         break;
     139             :                 case InputFilter::Exception::TooLarge:
     140             :                         req.addError("InputFilter", "Request size is out of limits", Value{
     141             :                                 std::make_pair("length", Value(int64_t(cl))),
     142             :                                 std::make_pair("local", Value(int64_t(cfg.maxRequestSize))),
     143             :                                 std::make_pair("global", Value(int64_t(config::MAX_INPUT_POST_SIZE))),
     144             :                         });
     145             :                         break;
     146             :                 default:
     147             :                         break;
     148             :                 }
     149             :                 e = ex;
     150             :                 return ret;
     151         650 :         };
     152             : 
     153         650 :         if (info.method == RequestMethod::Post || info.method == RequestMethod::Put || info.method == RequestMethod::Patch) {
     154         650 :                 ct = r.getRequestHeader("Content-Type");
     155         650 :                 auto b = r.getRequestHeader("Content-Length");
     156             : 
     157         650 :                 if (!b.empty()) {
     158         650 :                         cl = b.readInteger(10).get(0);
     159             :                 }
     160             : 
     161         650 :                 if (cl > config::MAX_INPUT_POST_SIZE || cl > cfg.maxRequestSize) {
     162           0 :                         return reportError(InputFilter::Exception::TooLarge, StringView());
     163             :                 }
     164             : 
     165         650 :                 if (!ct.empty() && cl != 0 && cl < config::MAX_INPUT_POST_SIZE) {
     166         650 :                         if ((cfg.required & db::InputConfig::Require::Data) != db::InputConfig::Require::None
     167         650 :                                         || (cfg.required & db::InputConfig::Require::FilesAsData) != db::InputConfig::Require::None) {
     168         575 :                                 if (ct.starts_with("multipart/form-data; boundary=")) {
     169          75 :                                         ret = InputFilter::Accept::Multipart;
     170         500 :                                 } else if (ct.starts_with(data::MIME_JSON) || ct.starts_with(data::MIME_CBOR) || ct.starts_with(data::MIME_SERENITY)) {
     171         450 :                                         ret = InputFilter::Accept::Json;
     172          50 :                                 } else if (ct.starts_with(data::MIME_URLENCODED)) {
     173          25 :                                         ret = InputFilter::Accept::Urlencoded;
     174             :                                 }
     175             :                         }
     176         650 :                         if (ret == InputFilter::Accept::None) {
     177         100 :                                 if ((cfg.required & db::InputConfig::Require::Files) != db::InputConfig::Require::None
     178         100 :                                                 || (cfg.required & db::InputConfig::Require::Body) != db::InputConfig::Require::None) {
     179         100 :                                         if (ct.starts_with("multipart/form-data; boundary=")) {
     180           0 :                                                 ret = InputFilter::Accept::Multipart;
     181             :                                         } else {
     182         100 :                                                 ret = InputFilter::Accept::Files;
     183             :                                         }
     184             :                                 }
     185             :                         }
     186             :                 } else {
     187           0 :                         e = InputFilter::Exception::Unrecognized;
     188             :                 }
     189             :         }
     190         650 :         if (ret == InputFilter::Accept::None) {
     191           0 :                 return reportError(InputFilter::Exception::Unrecognized, StringView("data, body"));
     192             :         }
     193         650 :         return ret;
     194         650 : }
     195             : 
     196         100 : db::InputFile *InputFilter::getFileFromContext(int64_t id) {
     197         100 :         auto req = Request::getCurrent();
     198         100 :         if (req) {
     199         100 :                 auto f = req.getInputFilter();
     200         100 :                 if (f) {
     201         100 :                         return f->getInputFile(id);
     202             :                 }
     203             :         }
     204           0 :         return nullptr;
     205         100 : }
     206             : 
     207             : SP_COVERAGE_TRIVIAL
     208             : Status InputFilter::getStatusForException(Exception ex) {
     209             :         switch (ex) {
     210             :         case InputFilter::Exception::TooLarge:
     211             :                 return HTTP_REQUEST_ENTITY_TOO_LARGE;
     212             :                 break;
     213             :         case InputFilter::Exception::Unrecognized:
     214             :                 return HTTP_UNSUPPORTED_MEDIA_TYPE;
     215             :                 break;
     216             :         default:
     217             :                 break;
     218             :         }
     219             :         return HTTP_BAD_REQUEST;
     220             : }
     221             : 
     222         650 : InputFilter::Exception InputFilter::insert(const Request &r) {
     223        1300 :         return perform([&] () -> InputFilter::Exception {
     224         650 :                 Exception e = Exception::None;
     225         650 :                 auto accept = getAcceptedData(r, e);
     226         650 :                 if (accept == Accept::None) {
     227           0 :                         return e;
     228             :                 }
     229             : 
     230         650 :                 auto f = r.config()->makeInputFilter(accept);
     231         650 :                 Request(r).setInputFilter(f);
     232         650 :                 return e;
     233        1950 :         }, r.pool(), config::TAG_REQUEST, r.config());
     234             : }
     235             : 
     236         650 : InputFilter::InputFilter(const Request &r, Accept a) : _body() {
     237         650 :         _accept = a;
     238         650 :         _request = r;
     239             : 
     240         650 :         _isStarted = false;
     241         650 :         _isCompleted = false;
     242             : 
     243         650 :         auto b = _request.getRequestHeader("Content-Length");
     244             : 
     245         650 :         if (!b.empty()) {
     246         650 :                 _contentLength = b.readInteger(10).get(0);
     247             :         }
     248         650 : }
     249             : 
     250         650 : Status InputFilter::init() {
     251         650 :         _startTime =_time = Time::now();
     252         650 :         _isStarted = true;
     253             : 
     254         650 :         if (_accept == Accept::Multipart) {
     255          75 :                 auto ct = _request.getRequestHeader("Content-Type");
     256          75 :                 ct.skipUntilString("boundary=");
     257          75 :                 if (ct.starts_with("boundary=")) {
     258         225 :                         _parser = new MultipartParser(_request.getInputConfig(), _contentLength,
     259         225 :                                         ct.sub("boundary="_len));
     260             :                 }
     261         575 :         } else if (_accept == Accept::Urlencoded) {
     262          25 :                 _parser = new UrlEncodeParser(_request.getInputConfig(), _contentLength);
     263         550 :         } else if (_accept == Accept::Json) {
     264         450 :                 _parser = new DataParser(_request.getInputConfig(), _contentLength);
     265         100 :         } else if (_accept == Accept::Files) {
     266         100 :                 const auto &ct = _request.getRequestHeader("Content-Type");
     267         100 :                 const auto &name = _request.getRequestHeader("X-File-Name");
     268             : 
     269         100 :                 _parser = new FileParser(_request.getInputConfig(), ct, name, _contentLength);
     270             :         }
     271             : 
     272         650 :         if (isBodySavingAllowed()) {
     273          50 :                 _body.reserve(_contentLength);
     274             :         }
     275             : 
     276         650 :         if (_parser) {
     277         650 :                 _request.config()->getHost()->getRoot()->handleFilterInit(this);
     278             :         }
     279             : 
     280         650 :         return OK;
     281             : }
     282             : 
     283         850 : bool InputFilter::step(BytesView data) {
     284         850 :         if (getConfig().required == db::InputConfig::Require::None || _accept == Accept::None) {
     285           0 :                 return false;
     286             :         }
     287             : 
     288         850 :         if (_read + data.size() > _contentLength) {
     289           0 :                 _unupdated += _contentLength - _read;
     290           0 :                 _read = _contentLength;
     291             :         } else {
     292         850 :                 _read += data.size();
     293         850 :                 _unupdated += data.size();
     294             :         }
     295             : 
     296         850 :         auto t = Time::now();
     297         850 :         _timer = t - _time;
     298         850 :         _time = t;
     299             : 
     300         850 :         if (_timer > getConfig().updateTime || _unupdated > (_contentLength * getConfig().updateFrequency)) {
     301         825 :                 _request.config()->getHost()->getRoot()->handleFilterUpdate(this);
     302         825 :                 _timer = TimeInterval();
     303         825 :                 _unupdated = 0;
     304             :         }
     305             : 
     306         850 :         if (isBodySavingAllowed()) {
     307          75 :                 _body.write((const char *)data.data(), data.size());
     308             :         }
     309             : 
     310        1700 :         if (_parser && (
     311         850 :                         (_accept == Accept::Urlencoded && isDataParsingAllowed()) ||
     312         825 :                         (_accept == Accept::Multipart && (isFileUploadAllowed() || isDataParsingAllowed()) ) ||
     313         550 :                         (_accept == Accept::Json && isDataParsingAllowed()) ||
     314         100 :                         (_accept == Accept::Files && isFileUploadAllowed()))) {
     315         850 :                 if (!_parser->run(data)) {
     316           0 :                         return false;
     317             :                 }
     318             :         }
     319         850 :         return true;
     320             : }
     321             : 
     322         650 : void InputFilter::finalize() {
     323        1300 :         if (_parser && (
     324         650 :                         (_accept == Accept::Urlencoded && isDataParsingAllowed()) ||
     325         625 :                         (_accept == Accept::Multipart && (isFileUploadAllowed() || isDataParsingAllowed()) ) ||
     326         550 :                         (_accept == Accept::Json && isDataParsingAllowed()) ||
     327         100 :                         (_accept == Accept::Files && isFileUploadAllowed()))) {
     328         650 :                 _parser->finalize();
     329             :         }
     330         650 :         _eos = true;
     331         650 :         _isCompleted = true;
     332         650 :         _request.config()->getHost()->getRoot()->handleFilterComplete(this);
     333         650 :         if (_parser) {
     334         650 :                 _parser->cleanup();
     335             :         }
     336         650 : }
     337             : 
     338          25 : size_t InputFilter::getContentLength() const {
     339          25 :         return _contentLength;
     340             : }
     341          25 : size_t InputFilter::getBytesRead() const {
     342          25 :         return _read;
     343             : }
     344          25 : size_t InputFilter::getBytesReadSinceUpdate() const {
     345          25 :         return _unupdated;
     346             : }
     347             : 
     348          25 : Time InputFilter::getStartTime() const {
     349          25 :         return _startTime;
     350             : }
     351          25 : TimeInterval InputFilter::getElapsedTime() const {
     352          50 :         return Time::now() - _startTime;
     353             : }
     354          25 : TimeInterval InputFilter::getElapsedTimeSinceUpdate() const {
     355          50 :         return Time::now() - _time;
     356             : }
     357             : 
     358         550 : bool InputFilter::isFileUploadAllowed() const {
     359         550 :         return (getConfig().required & db::InputConfig::Require::Files) != db::InputConfig::Require::None;
     360             : }
     361         950 : bool InputFilter::isDataParsingAllowed() const {
     362         950 :         return (getConfig().required & db::InputConfig::Require::Data) != db::InputConfig::Require::None;
     363             : }
     364        1500 : bool InputFilter::isBodySavingAllowed() const {
     365        1500 :         return (getConfig().required & db::InputConfig::Require::Body) != db::InputConfig::Require::None;
     366             : }
     367             : 
     368          25 : bool InputFilter::isCompleted() const {
     369          25 :         return _isCompleted;
     370             : }
     371             : 
     372          50 : const StringStream & InputFilter::getBody() const {
     373          50 :         return _body;
     374             : }
     375         650 : Value & InputFilter::getData() {
     376         650 :         return _parser->getData();
     377             : }
     378         550 : Vector<db::InputFile> &InputFilter::getFiles() {
     379         550 :         return _parser->getFiles();
     380             : }
     381             : 
     382         100 : db::InputFile * InputFilter::getInputFile(int64_t idx) const {
     383         100 :         if (idx < 0) {
     384         100 :                 idx = -(idx + 1);
     385             :         }
     386             : 
     387         100 :         auto &files = _parser->getFiles();
     388         100 :         if (size_t(idx) >= files.size()) {
     389           0 :                 return nullptr;
     390             :         }
     391         100 :         return &files.at(size_t(idx));
     392             : }
     393             : 
     394        5550 : const db::InputConfig & InputFilter::getConfig() const {
     395        5550 :         return _request.getInputConfig();
     396             : }
     397             : 
     398        2675 : Request InputFilter::getRequest() const {
     399        2675 :         return _request;
     400             : }
     401             : 
     402        2150 : memory::pool_t *InputFilter::getPool() const {
     403        2150 :         return _request.pool();
     404             : }
     405             : 
     406             : }

Generated by: LCOV version 1.14