cutelyst 5.0.1
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
11Uploads MultiPartFormDataParser::parse(QIODevice *body, QByteArrayView contentType, int bufferSize)
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 const 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:466
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.