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 "SPWebMultipartParser.h"
24 : #include "SPValid.h"
25 : #include "SPDbFile.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::web {
28 :
29 75 : MultipartParser::MultipartParser(const db::InputConfig &c, size_t s, const StringView &b)
30 75 : : InputParser(c, s) {
31 75 : boundary.reserve(b.size() + 4);
32 75 : boundary.append("\r\n--");
33 75 : boundary.append(b.data(), b.size());
34 75 : match = 2;
35 75 : }
36 :
37 375 : Value * MultipartParser::flushVarName(StringView &r) {
38 375 : VarState cstate = VarState::Key;
39 375 : Value *current = nullptr;
40 1125 : while (!r.empty()) {
41 750 : StringView str = r.readUntil<chars::Chars<char, '[', ']'>>();
42 750 : current = flushString(str, current, cstate);
43 750 : if (!current) {
44 0 : break;
45 : }
46 750 : if (!r.empty()) {
47 650 : switch (cstate) {
48 275 : case VarState::Key:
49 275 : switch (r[0]) {
50 275 : case '[': cstate = VarState::SubKey; break;
51 0 : default: cstate = VarState::End; break;
52 : }
53 275 : break;
54 325 : case VarState::SubKey:
55 325 : switch (r[0]) {
56 325 : case ']': cstate = VarState::SubKeyEnd; break;
57 0 : default: cstate = VarState::End; break;
58 : }
59 325 : break;
60 50 : case VarState::SubKeyEnd:
61 50 : switch (r[0]) {
62 50 : case '[': cstate = VarState::SubKey; break;
63 0 : default: cstate = VarState::End; break;
64 : }
65 50 : break;
66 0 : default:
67 0 : return nullptr;
68 : break;
69 : }
70 650 : ++ r;
71 : }
72 : }
73 375 : return current;
74 : }
75 :
76 1000 : void MultipartParser::flushLiteral(StringView &r, bool quoted) {
77 1000 : auto tmp = r;
78 1000 : if (!quoted) {
79 750 : tmp.trimChars<StringView::WhiteSpace>();
80 : }
81 1000 : switch (header) {
82 125 : case Header::ContentDispositionFileName:
83 125 : file.assign(tmp.data(), tmp.size());
84 125 : break;
85 500 : case Header::ContentDispositionName:
86 500 : name.assign(tmp.data(), tmp.size());
87 500 : break;
88 125 : case Header::ContentDispositionSize:
89 125 : size = strtol(r.data(), nullptr, 10);
90 125 : break;
91 125 : case Header::ContentType:
92 125 : type.assign(tmp.data(), tmp.size());
93 125 : break;
94 125 : case Header::ContentEncoding:
95 125 : encoding.assign(tmp.data(), tmp.size());
96 125 : break;
97 0 : default:
98 0 : break;
99 : }
100 1000 : }
101 :
102 8300 : void MultipartParser::flushData(const BytesView &r) {
103 8300 : switch (data) {
104 7750 : case Data::File:
105 7750 : if (r.size() + files.back().writeSize >= getConfig().maxFileSize) {
106 0 : files.back().close();
107 0 : files.pop_back();
108 0 : data = Data::Skip;
109 : } else {
110 7750 : files.back().write((const char *)r.data(), r.size());
111 : }
112 7750 : break;
113 175 : case Data::FileAsData:
114 175 : if (r.size() + streamBuf.size() >= getConfig().maxFileSize) {
115 0 : files.back().close();
116 0 : files.pop_back();
117 0 : data = Data::Skip;
118 : } else {
119 175 : streamBuf.write((const char *)r.data(), r.size());
120 : }
121 175 : break;
122 375 : case Data::Var:
123 375 : if (r.size() + buf.size() >= getConfig().maxVarSize) {
124 0 : buf.clear();
125 0 : data = Data::Skip;
126 : } else {
127 375 : buf.put(r.data(), r.size());
128 : }
129 375 : break;
130 0 : case Data::Skip:
131 0 : break;
132 : }
133 8300 : }
134 :
135 75 : bool MultipartParser::readBegin(BytesView &r) {
136 75 : StringView tmp = r.toStringView();
137 :
138 75 : if (match == 0) {
139 0 : tmp.skipUntil<StringView::Chars<'-'>>();
140 : }
141 3150 : while (match < boundary.length() && tmp.is(boundary.at(match))) {
142 3075 : ++ match; ++ tmp;
143 : }
144 75 : if (tmp.empty()) {
145 0 : r = BytesView((const uint8_t *)tmp.data(), r.size() - (tmp.data() - (const char *)r.data()));
146 0 : return true;
147 75 : } else if (match == boundary.length()) {
148 75 : state = State::BeginBlock;
149 75 : target = &root;
150 75 : match = 0;
151 : } else {
152 0 : match = 0;
153 0 : return false;
154 : }
155 75 : buf.clear();
156 :
157 75 : r = BytesView((const uint8_t *)tmp.data(), r.size() - (tmp.data() - (const char *)r.data()));
158 75 : return true;
159 : }
160 :
161 575 : void MultipartParser::readBlock(BytesView &r) {
162 1150 : while (!r.empty()) {
163 1150 : if (buf.size() == 0 && r.is('-')) {
164 75 : buf.putc(char(r[0]));
165 1075 : } else if (buf.size() == 1 && buf.get().is('-') && r.is('-')) {
166 75 : state = State::End;
167 75 : return;
168 : } else {
169 1000 : if (r.is('\n')) {
170 500 : state = State::HeaderLine;
171 500 : header = Header::Begin;
172 500 : name.clear();
173 500 : type.clear();
174 500 : encoding.clear();
175 500 : file.clear();
176 500 : size = 0;
177 500 : buf.clear();
178 500 : ++ r;
179 500 : return;
180 : } else {
181 500 : ++ r;
182 : }
183 : }
184 : }
185 : }
186 :
187 1275 : void MultipartParser::readHeaderBegin(StringView &r) {
188 1275 : StringView str = r.readUntil<StringView::Chars<'\n', ':'>>();
189 1275 : if (r.is(':')) {
190 775 : StringView tmp;
191 775 : if (buf.empty()) {
192 775 : tmp = str;
193 : } else {
194 0 : buf.put(str.data(), str.size());
195 0 : tmp = buf.get();
196 : }
197 :
198 775 : tmp.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
199 775 : if (strncasecmp(tmp.data(), "Content-Disposition", "Content-Disposition"_len) == 0) {
200 500 : header = Header::ContentDisposition;
201 275 : } else if (strncasecmp(tmp.data(), "Content-Type", "Content-Type"_len) == 0) {
202 125 : header = Header::ContentType;
203 150 : } else if (strncasecmp(tmp.data(), "Content-Transfer-Encoding", "Content-Transfer-Encoding"_len) == 0) {
204 125 : header = Header::ContentEncoding;
205 : } else {
206 25 : header = Header::Unknown;
207 : }
208 :
209 775 : buf.clear();
210 775 : r ++;
211 500 : } else if (r.empty()) {
212 0 : buf.put(str.data(), str.size());
213 500 : } else if (r.is('\n')) {
214 500 : auto tmp = buf.get();
215 500 : str.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
216 500 : tmp.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
217 500 : if ((tmp.empty() || tmp.is('\r')) && (str.empty() || str.is('\r'))) {
218 500 : state = State::Data;
219 500 : if (!file.empty() || !type.empty()) {
220 125 : if ((config.required & db::InputConfig::Require::FilesAsData) != db::InputConfig::Require::None
221 50 : && (size == 0 || size < getConfig().maxFileSize)
222 175 : && db::InputConfig::isFileAsDataSupportedForType(type)) {
223 50 : streamBuf.clear();
224 50 : data = Data::FileAsData;
225 75 : } else if ((config.required & db::InputConfig::Require::Files) != 0
226 75 : && (size == 0 || size < getConfig().maxFileSize)) {
227 75 : files.emplace_back(std::move(name), std::move(type), std::move(encoding), std::move(file), size, files.size());
228 75 : data = Data::File;
229 : } else {
230 0 : data = Data::Skip;
231 : }
232 : } else {
233 750 : if ((config.required & db::InputConfig::Require::Data) != 0 &&
234 375 : (size == 0 || size < getConfig().maxVarSize)) {
235 375 : data = Data::Var;
236 : } else {
237 0 : data = Data::Skip;
238 : }
239 : }
240 :
241 500 : name.empty();
242 500 : type.empty();
243 500 : encoding.empty();
244 500 : file.empty();
245 500 : size = 0;
246 : }
247 500 : header = Header::Begin; // next header
248 500 : buf.clear();
249 500 : r ++;
250 : }
251 1275 : }
252 :
253 500 : void MultipartParser::readHeaderContentDisposition(StringView &r) {
254 500 : StringView str = r.readUntil<StringView::Chars<'\n', ';'>>();
255 500 : if (r.is(';')) {
256 500 : StringView tmp;
257 500 : if (buf.empty()) {
258 500 : tmp = str;
259 : } else {
260 0 : buf.put(str.data(), str.size());
261 0 : tmp = buf.get();
262 : }
263 :
264 500 : tmp.skipChars<StringView::CharGroup<CharGroupId::WhiteSpace>>();
265 500 : if (strncasecmp(tmp.data(), "form-data", "form-data"_len) == 0) {
266 500 : header = Header::ContentDispositionParams;
267 : } else {
268 0 : header = Header::Unknown;
269 : }
270 :
271 500 : buf.clear();
272 500 : r ++;
273 0 : } else if (r.empty()) {
274 0 : buf.put(str.data(), str.size());
275 0 : } else if (r.is('\n')) {
276 0 : header = Header::Begin; // next header
277 0 : buf.clear();
278 0 : r ++;
279 : }
280 500 : }
281 :
282 825 : void MultipartParser::readHeaderContentDispositionParam(StringView &r) {
283 825 : if (buf.empty()) {
284 825 : r.skipChars<StringView::Chars<';', ' '>>();
285 : }
286 825 : StringView str = r.readUntil<StringView::Chars<'\n', '='>>();
287 825 : if (r.is('=')) {
288 825 : StringView tmp;
289 825 : if (buf.empty()) {
290 825 : tmp = str;
291 : } else {
292 0 : buf.put(str.data(), str.size());
293 0 : tmp = buf.get();
294 : }
295 :
296 825 : if (strncasecmp(tmp.data(), "name", "name"_len) == 0) {
297 500 : header = Header::ContentDispositionName;
298 500 : literal = Literal::None;
299 325 : } else if (strncasecmp(tmp.data(), "filename", "filename"_len) == 0) {
300 125 : header = Header::ContentDispositionFileName;
301 125 : literal = Literal::None;
302 200 : } else if (strncasecmp(tmp.data(), "size", "size"_len) == 0) {
303 125 : header = Header::ContentDispositionSize;
304 125 : literal = Literal::None;
305 : } else {
306 75 : header = Header::ContentDispositionUnknown;
307 : }
308 :
309 825 : buf.clear();
310 825 : r ++;
311 0 : } else if (r.empty()) {
312 0 : buf.put(str.data(), str.size());
313 0 : } else if (r.is('\n')) {
314 0 : header = Header::Begin; // next header
315 0 : buf.clear();
316 0 : r ++;
317 : }
318 825 : }
319 :
320 250 : void MultipartParser::readHeaderValue(StringView &r) {
321 250 : auto &max = getConfig().maxVarSize;
322 250 : StringView str = r.readUntil<StringView::Chars<'\n'>>();
323 250 : if (r.empty()) {
324 0 : if (buf.size() + str.size() < max) {
325 0 : buf.put(str.data(), str.size());
326 : } else {
327 0 : header = Header::Unknown; // skip processing
328 : }
329 250 : } else if (r.is('\n')) {
330 250 : StringView tmp;
331 250 : if (buf.empty()) {
332 250 : if (str.size() < max) {
333 250 : tmp = str;
334 : }
335 : } else {
336 0 : if (str.size() + buf.size() < max) {
337 0 : buf.put(str.data(), str.size());
338 0 : tmp = buf.get();
339 : }
340 : }
341 :
342 250 : if (!tmp.empty()) {
343 250 : flushLiteral(tmp, false);
344 : }
345 :
346 250 : header = Header::Begin; // next header
347 250 : literal = Literal::None;
348 250 : buf.clear();
349 250 : r ++;
350 : }
351 250 : }
352 :
353 25 : void MultipartParser::readHeaderDummy(StringView &r) {
354 25 : r.skipUntil<StringView::Chars<'\n'>>();
355 25 : if (r.is('\n')) {
356 25 : header = Header::Begin; // next header
357 25 : literal = Literal::None;
358 25 : buf.clear();
359 25 : r ++;
360 : }
361 25 : }
362 :
363 500 : void MultipartParser::readPlainLiteral(StringView &r) {
364 500 : auto &max = getConfig().maxVarSize;
365 500 : StringView str = r.readUntil<StringView::Chars<'\n', ';'>>();
366 500 : if (r.is(';') || r.is('\n')) {
367 500 : StringView tmp;
368 500 : if (buf.empty()) {
369 500 : if (str.size() < max) {
370 500 : tmp = str;
371 : } else {
372 0 : header = Header::ContentDispositionUnknown;
373 : }
374 : } else {
375 0 : if (str.size() + buf.size() < max) {
376 0 : buf.put(str.data(), str.size());
377 0 : tmp = buf.get();
378 : } else {
379 0 : header = Header::ContentDispositionUnknown;
380 : }
381 : }
382 :
383 500 : if (!tmp.empty()) {
384 500 : flushLiteral(tmp, false);
385 : }
386 :
387 500 : header = r.is(';') ? Header::ContentDispositionParams : Header::Begin;
388 500 : literal = Literal::None;
389 500 : buf.clear();
390 500 : r ++;
391 0 : } else if (r.empty()) {
392 0 : if (str.size() + buf.size() < max) {
393 0 : buf.put(str.data(), str.size());
394 : } else {
395 0 : header = Header::ContentDispositionUnknown;
396 : }
397 : }
398 500 : }
399 :
400 250 : void MultipartParser::readQuotedLiteral(StringView &r) {
401 250 : auto &max = getConfig().maxVarSize;
402 250 : StringView str = r.readUntil<StringView::Chars<'\n', '"'>>();
403 250 : if (r.is('"')) {
404 250 : StringView tmp;
405 250 : if (buf.empty()) {
406 250 : if (str.size() < max) {
407 250 : tmp = str;
408 : } else {
409 0 : header = Header::ContentDispositionUnknown;
410 : }
411 : } else {
412 0 : if (buf.size() + str.size() < max) {
413 0 : buf.put(str.data(), str.size());
414 0 : tmp = buf.get();
415 : } else {
416 0 : header = Header::ContentDispositionUnknown;
417 : }
418 : }
419 :
420 250 : flushLiteral(tmp, true);
421 250 : buf.clear();
422 250 : r ++;
423 250 : header = Header::ContentDispositionParams;
424 250 : literal = Literal::None;
425 0 : } else if (r.empty()) {
426 0 : if (buf.size() + str.size() < max) {
427 0 : buf.put(str.data(), str.size());
428 : } else {
429 0 : header = Header::ContentDispositionUnknown;
430 : }
431 0 : } else if (r.is('\n')) {
432 0 : header = Header::Begin; // next header
433 0 : literal = Literal::None;
434 0 : buf.clear();
435 0 : r ++;
436 : }
437 250 : }
438 :
439 1500 : void MultipartParser::readHeaderContentDispositionValue(StringView &r) {
440 1500 : switch (literal) {
441 750 : case Literal::None:
442 750 : if (r.is('"')) {
443 250 : literal = Literal::Quoted;
444 250 : r ++;
445 500 : } else if (r.is('\n')) {
446 0 : header = Header::Begin; // next header
447 0 : buf.clear();
448 0 : r ++;
449 500 : } else if (!r.is<StringView::CharGroup<CharGroupId::WhiteSpace>>()) {
450 500 : literal = Literal::Plain;
451 : } else {
452 0 : header = Header::ContentDispositionParams;
453 : }
454 750 : break;
455 500 : case Literal::Plain:
456 500 : readPlainLiteral(r);
457 500 : break;
458 250 : case Literal::Quoted:
459 250 : readQuotedLiteral(r);
460 250 : break;
461 : }
462 1500 : }
463 :
464 75 : void MultipartParser::readHeaderContentDispositionDummy(StringView &r) {
465 75 : r.skipUntil<StringView::Chars<'\n', ';'>>();
466 75 : if (r.is(';')) {
467 25 : header = Header::ContentDispositionParams;
468 25 : literal = Literal::None;
469 25 : buf.clear();
470 25 : r ++;
471 50 : } else if (r.is('\n')) {
472 50 : header = Header::Begin; // next header
473 50 : literal = Literal::None;
474 50 : buf.clear();
475 50 : r ++;
476 : }
477 75 : }
478 :
479 4450 : void MultipartParser::readHeader(BytesView &r) {
480 4450 : StringView tmp = r.toStringView();
481 :
482 4450 : switch (header) {
483 1275 : case Header::Begin:
484 1275 : readHeaderBegin(tmp);
485 1275 : break;
486 500 : case Header::ContentDisposition:
487 500 : readHeaderContentDisposition(tmp);
488 500 : break;
489 825 : case Header::ContentDispositionParams:
490 825 : readHeaderContentDispositionParam(tmp);
491 825 : break;
492 1500 : case Header::ContentDispositionName:
493 : case Header::ContentDispositionFileName:
494 : case Header::ContentDispositionSize:
495 1500 : readHeaderContentDispositionValue(tmp);
496 1500 : break;
497 75 : case Header::ContentDispositionUnknown:
498 75 : readHeaderContentDispositionDummy(tmp);
499 75 : break;
500 250 : case Header::ContentType:
501 : case Header::ContentEncoding:
502 250 : readHeaderValue(tmp);
503 250 : break;
504 25 : case Header::Unknown:
505 25 : readHeaderDummy(tmp);
506 25 : break;
507 : }
508 :
509 4450 : r = BytesView((const uint8_t *)tmp.data(), r.size() - (tmp.data() - (const char *)r.data()));
510 4450 : }
511 :
512 8800 : void MultipartParser::readData(BytesView &r) {
513 8800 : if (match == 0) {
514 4500 : flushData(r.readUntil<uint8_t('\r')>());
515 4500 : if (r.empty()) {
516 200 : return;
517 : } else {
518 4300 : match = 1;
519 4300 : r ++;
520 : }
521 : } else {
522 25325 : while (!r.empty() && r[0] == boundary[match] && match < boundary.length()) {
523 21025 : match ++;
524 21025 : r ++;
525 : }
526 :
527 4300 : if (match == boundary.length()) {
528 500 : state = State::BeginBlock;
529 500 : target = &root;
530 500 : if (data == Data::Var) {
531 375 : StringView tmp(name);
532 375 : auto current = flushVarName(tmp);
533 375 : if (current) {
534 375 : current->setString(buf.str());
535 : }
536 375 : buf.clear();
537 125 : } else if (data == Data::FileAsData) {
538 50 : root.setValue(data::read<Interface>(streamBuf.weak()), std::move(name));
539 50 : streamBuf.clear();
540 : }
541 500 : match = 0;
542 3800 : } else if (!r.empty() && r[0] != boundary[match]) {
543 3800 : BytesView tmp((const uint8_t *)boundary.data(), match);
544 3800 : flushData(tmp);
545 3800 : match = 0;
546 : }
547 : }
548 : }
549 :
550 275 : bool MultipartParser::run(BytesView r) {
551 14175 : while (!r.empty()) {
552 13975 : switch (state) {
553 75 : case State::Begin: // skip preambula
554 75 : if (!readBegin(r)) {
555 0 : return false;
556 : }
557 75 : break;
558 575 : case State::BeginBlock: // wait for CRLF then headers or "--" then EOF
559 575 : readBlock(r);
560 575 : break;
561 4450 : case State::HeaderLine:
562 4450 : readHeader(r);
563 4450 : break;
564 8800 : case State::Data:
565 8800 : readData(r);
566 8800 : break;
567 75 : case State::End:
568 75 : return true;
569 : break;
570 : }
571 : }
572 200 : return true;
573 : }
574 :
575 75 : void MultipartParser::finalize() {
576 :
577 75 : }
578 :
579 750 : auto MultipartParser::flushString(StringView &r, Value *cur, VarState varState) -> Value * {
580 750 : auto str = string::urldecode<Interface>(r);
581 :
582 750 : switch (varState) {
583 375 : case VarState::Key:
584 375 : if (!str.empty()) {
585 375 : if (target->hasValue(str)) {
586 175 : cur = &target->getValue(str);
587 : } else {
588 200 : cur = &target->setValue(Value(true), str);
589 : }
590 : }
591 375 : break;
592 325 : case VarState::SubKey:
593 325 : if (cur) {
594 325 : if (!str.empty() && valid::validateNumber(str)) {
595 125 : auto num = StringView(str).readInteger().get();
596 125 : if (cur->isArray()) {
597 100 : if (num < int64_t(cur->size())) {
598 75 : cur = &cur->getValue(num);
599 125 : return cur;
600 25 : } else if (num == int64_t(cur->size())) {
601 25 : cur = &cur->addValue(Value(true));
602 25 : return cur;
603 : }
604 25 : } else if (!cur->isDictionary() && num == 0) {
605 25 : cur->setArray(typename Value::ArrayType());
606 25 : cur = &cur->addValue(Value(true));
607 25 : return cur;
608 : }
609 : }
610 200 : if (str.empty()) {
611 100 : if (!cur->isArray()) {
612 50 : cur->setArray(typename Value::ArrayType());
613 : }
614 100 : cur = &cur->addValue(Value(true));
615 : } else {
616 100 : if (!cur->isDictionary()) {
617 50 : cur->setDict(typename Value::DictionaryType());
618 : }
619 100 : if (cur->hasValue(str)) {
620 25 : cur = &cur->getValue(str);
621 : } else {
622 75 : cur = &cur->setValue(Value(true), str);
623 : }
624 : }
625 : }
626 200 : break;
627 0 : case VarState::Value:
628 : case VarState::End:
629 0 : if (cur) {
630 0 : if (!str.empty()) {
631 0 : cur->setString(str);
632 : }
633 0 : cur = nullptr;
634 : }
635 0 : break;
636 50 : default:
637 50 : break;
638 : }
639 :
640 625 : return cur;
641 750 : }
642 :
643 : }
|