cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
multipartformdataparser.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2014-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "common.h"
6#include "multipartformdataparser_p.h"
7#include "upload_p.h"
8
9using namespace Cutelyst;
10
12{
13 Uploads ret;
14 if (body->isSequential()) {
15 qCWarning(CUTELYST_MULTIPART) << "Parsing sequential body is not supported" << body;
16 return ret;
17 }
18
19 int start = contentType.indexOf("boundary=");
20 if (start == -1) {
21 qCWarning(CUTELYST_MULTIPART) << "No boundary match" << contentType;
22 return ret;
23 }
24
25 start += 9;
26 QByteArray boundary;
27 const int len = contentType.length();
28 boundary.reserve(contentType.length() - start + 2);
29
30 for (int i = start, quotes = 0; i < len; ++i) {
31 const char ch = contentType.at(i);
32 if (ch == '\"') {
33 if (quotes == 0 && i > start) {
34 break;
35 } else if (++quotes == 2) {
36 break;
37 }
38 } else if (ch == ';') {
39 break;
40 } else {
41 boundary.append(ch);
42 }
43 }
44
45 if (boundary.isEmpty()) {
46 qCWarning(CUTELYST_MULTIPART) << "Boundary match was empty" << contentType;
47 return ret;
48 }
49 boundary.prepend("--", 2);
50
51 if (bufferSize < 1024) {
52 bufferSize = 1024;
53 }
54 char *buffer = new char[bufferSize];
55
56 ret = MultiPartFormDataParserPrivate::execute(buffer, bufferSize, body, boundary);
57
58 delete[] buffer;
59
60 return ret;
61}
62
63Uploads MultiPartFormDataParserPrivate::execute(char *buffer,
64 int bufferSize,
65 QIODevice *body,
66 const QByteArray &boundary)
67{
68 Uploads ret;
69 QByteArray headerLine;
70 Headers headers;
71 qint64 startOffset = 0;
72 qint64 pos = 0;
73 qint64 contentLength = body->size();
74 int bufferSkip = 0;
75 int boundarySize = boundary.size();
76 ParserState state = FindBoundary;
77 QByteArrayMatcher matcher(boundary);
78
79 while (pos < contentLength) {
80 qint64 len = body->read(buffer + bufferSkip, bufferSize - bufferSkip);
81 if (len < 0) {
82 qCWarning(CUTELYST_MULTIPART) << "Error while reading POST body" << body->errorString();
83 return ret;
84 }
85
86 pos += len;
87 len += bufferSkip;
88 bufferSkip = 0;
89 int i = 0;
90 while (i < len) {
91 switch (state) {
92 case FindBoundary:
93 i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
94 break;
95 case EndBoundaryCR:
96 // TODO the "--" case
97 if (buffer[i] != '\r') {
98 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryCR return!";
99 return ret;
100 }
101 state = EndBoundaryLF;
102 break;
103 case EndBoundaryLF:
104 if (buffer[i] != '\n') {
105 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryLF return!";
106 return ret;
107 }
108 state = StartHeaders;
109 break;
110 case StartHeaders:
111 if (headerLine.isEmpty() && buffer[i] == '\r') {
112 // nothing was read
113 state = EndHeaders;
114 } else {
115 char *pch = static_cast<char *>(memchr(buffer + i, '\r', len - i));
116 if (pch == nullptr) {
117 headerLine.append(buffer + i, len - i);
118 i = len;
119 } else {
120 headerLine.append(buffer + i, pch - buffer - i);
121 i = pch - buffer;
122 state = FinishHeader;
123 }
124 }
125 break;
126 case FinishHeader:
127 if (buffer[i] == '\n') {
128 int dotdot = headerLine.indexOf(':');
129 headers.setHeader(
130 headerLine.left(dotdot),
131 QByteArrayView{headerLine}.sliced(dotdot + 1).trimmed().toByteArray());
132 headerLine = {};
133 state = StartHeaders;
134 } else {
135 // qCDebug(CUTELYST_MULTIPART) << "FinishHeader return!";
136 return ret;
137 }
138 break;
139 case EndHeaders:
140 if (buffer[i] == '\n') {
141 state = StartData;
142 } else {
143 // qCDebug(CUTELYST_MULTIPART) << "EndHeaders return!";
144 return ret;
145 }
146 break;
147 case StartData:
148 // qCDebug(CUTELYST_MULTIPART) << "StartData" << body->pos() - len +
149 // i;
150 startOffset = pos - len + i;
151 state = EndData;
152 break;
153 case EndData:
154 i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
155
156 if (state == EndBoundaryCR) {
157 // qCDebug(CUTELYST_MULTIPART) << "EndData" << body->pos() -
158 // len + i - boundaryLength - 1;
159 const qint64 endOffset = pos - len + i - boundarySize - 1;
160 auto upload =
161 new Upload(new UploadPrivate(body, headers, startOffset, endOffset));
162 ret.append(upload);
163
164 headers = Headers();
165 } else {
166 // Boundary was not found so move the boundary size at end of the buffer
167 // to be sure we don't have the boundary in the middle of two chunks
168 bufferSkip = boundarySize - 1;
169 memmove(buffer, buffer + len - bufferSkip, bufferSkip);
170 }
171
172 break;
173 }
174 ++i;
175 }
176 }
177
178 return ret;
179}
180
181int MultiPartFormDataParserPrivate::findBoundary(char *buffer,
182 int len,
183 const QByteArrayMatcher &matcher,
184 int boundarySize,
185 MultiPartFormDataParserPrivate::ParserState &state)
186{
187 int i = matcher.indexIn(buffer, len);
188 // qCDebug(CUTELYST_MULTIPART) << "findBoundary" << QByteArray(buffer, len);
189 if (i != -1) {
190 // qCDebug(CUTELYST_MULTIPART) << "FindBoundary: found at" << i << body->pos() << len
191 // << body->pos() - len + i << i + boundaryLength;
192 state = EndBoundaryCR;
193 return i + boundarySize - 1;
194 }
195 return len;
196}
197
198#include "moc_multipartformdataparser_p.cpp"
Container for HTTP headers.
Definition headers.h:24
void setHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:440
static Uploads parse(QIODevice *body, QByteArrayView contentType, int bufferSize=4096)
Parser for multipart/formdata.
Cutelyst Upload handles file upload requests.
Definition upload.h:26
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) const const
QByteArray & prepend(QByteArrayView ba)
void reserve(qsizetype size)
qsizetype size() const const
QByteArray sliced(qsizetype pos) const const
QByteArray trimmed() const const
qsizetype indexIn(QByteArrayView data, qsizetype from) const const
char at(qsizetype n) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
qsizetype length() const const
QString errorString() const const
virtual bool isSequential() const const
QByteArray read(qint64 maxSize)
virtual qint64 size() const const
void append(QList::parameter_type value)