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