cutelyst 3.9.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
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(u"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 QChar ch = contentType.at(i);
32 if (ch == u'\"') {
33 if ((quotes == 0 && i > start) || ++quotes == 2) {
34 break;
35 }
36 } else if (ch == u';') {
37 break;
38 } else {
39 boundary.append(ch.toLatin1());
40 }
41 }
42
43 if (boundary.isEmpty()) {
44 qCWarning(CUTELYST_MULTIPART) << "Boundary match was empty" << contentType;
45 return ret;
46 }
47 boundary.prepend("--", 2);
48
49 if (bufferSize < 1024) {
50 bufferSize = 1024;
51 }
52 char *buffer = new char[bufferSize];
53
54 ret = MultiPartFormDataParserPrivate::execute(buffer, bufferSize, body, boundary);
55
56 delete[] buffer;
57
58 return ret;
59}
60
61Uploads MultiPartFormDataParserPrivate::execute(char *buffer,
62 int bufferSize,
63 QIODevice *body,
64 const QByteArray &boundary)
65{
66 Uploads ret;
67 QByteArray headerLine;
68 Headers headers;
69 qint64 startOffset = 0;
70 qint64 pos = 0;
71 qint64 contentLength = body->size();
72 int bufferSkip = 0;
73 int boundarySize = boundary.size();
74 ParserState state = FindBoundary;
75 QByteArrayMatcher matcher(boundary);
76
77 while (pos < contentLength) {
78 qint64 len = body->read(buffer + bufferSkip, bufferSize - bufferSkip);
79 if (len < 0) {
80 qCWarning(CUTELYST_MULTIPART) << "Error while reading POST body" << body->errorString();
81 return ret;
82 }
83
84 pos += len;
85 len += bufferSkip;
86 bufferSkip = 0;
87 int i = 0;
88 while (i < len) {
89 switch (state) {
90 case FindBoundary:
91 i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
92 break;
93 case EndBoundaryCR:
94 // TODO the "--" case
95 if (buffer[i] != '\r') {
96 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryCR return!";
97 return ret;
98 }
99 state = EndBoundaryLF;
100 break;
101 case EndBoundaryLF:
102 if (buffer[i] != '\n') {
103 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryLF return!";
104 return ret;
105 }
106 state = StartHeaders;
107 break;
108 case StartHeaders:
109 if (headerLine.isEmpty() && buffer[i] == '\r') {
110 // nothing was read
111 state = EndHeaders;
112 } else {
113 char *pch = static_cast<char *>(memchr(buffer + i, '\r', len - i));
114 if (pch == NULL) {
115 headerLine.append(buffer + i, len - i);
116 i = len;
117 } else {
118 headerLine.append(buffer + i, pch - buffer - i);
119 i = pch - buffer;
120 state = FinishHeader;
121 }
122 }
123 break;
124 case FinishHeader:
125 if (buffer[i] == '\n') {
126 int dotdot = headerLine.indexOf(':');
127 headers.setHeader(QString::fromLatin1(headerLine.left(dotdot)),
128 QString::fromUtf8(headerLine.mid(dotdot + 1).trimmed()));
129 headerLine = QByteArray();
130 state = StartHeaders;
131 } else {
132 // qCDebug(CUTELYST_MULTIPART) << "FinishHeader return!";
133 return ret;
134 }
135 break;
136 case EndHeaders:
137 if (buffer[i] == '\n') {
138 state = StartData;
139 } else {
140 // qCDebug(CUTELYST_MULTIPART) << "EndHeaders return!";
141 return ret;
142 }
143 break;
144 case StartData:
145 // qCDebug(CUTELYST_MULTIPART) << "StartData" << body->pos() - len +
146 // i;
147 startOffset = pos - len + i;
148 state = EndData;
149 break;
150 case EndData:
151 i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
152
153 if (state == EndBoundaryCR) {
154 // qCDebug(CUTELYST_MULTIPART) << "EndData" << body->pos() -
155 // len + i - boundaryLength - 1;
156 const qint64 endOffset = pos - len + i - boundarySize - 1;
157 auto upload =
158 new Upload(new UploadPrivate(body, headers, startOffset, endOffset));
159 ret.append(upload);
160
161 headers = Headers();
162 } else {
163 // Boundary was not found so move the boundary size at end of the buffer
164 // to be sure we don't have the boundary in the middle of two chunks
165 bufferSkip = boundarySize - 1;
166 memmove(buffer, buffer + len - bufferSkip, bufferSkip);
167 }
168
169 break;
170 }
171 ++i;
172 }
173 }
174
175 return ret;
176}
177
178int MultiPartFormDataParserPrivate::findBoundary(char *buffer,
179 int len,
180 const QByteArrayMatcher &matcher,
181 int boundarySize,
182 MultiPartFormDataParserPrivate::ParserState &state)
183{
184 int i = matcher.indexIn(buffer, len);
185 // qCDebug(CUTELYST_MULTIPART) << "findBoundary" << QByteArray(buffer, len);
186 if (i != -1) {
187 // qCDebug(CUTELYST_MULTIPART) << "FindBoundary: found at" << i << body->pos() << len
188 // << body->pos() - len + i << i + boundaryLength;
189 state = EndBoundaryCR;
190 return i + boundarySize - 1;
191 }
192 return len;
193}
194
195#include "moc_multipartformdataparser_p.cpp"
void setHeader(const QString &field, const QString &value)
Definition headers.cpp:396
static Uploads parse(QIODevice *body, QStringView contentType, int bufferSize=4096)
Parser for multipart/formdata.
Cutelyst Upload handles file upload request
Definition upload.h:23
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QVector< Upload * > Uploads
Definition request.h:25
QByteArray & append(QByteArrayView data)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) &&
QByteArray mid(qsizetype pos, qsizetype len) &&
QByteArray & prepend(QByteArrayView ba)
void reserve(qsizetype size)
qsizetype size() const const
QByteArray trimmed() const const
qsizetype indexIn(QByteArrayView data, qsizetype from) const const
char toLatin1() const const
QString errorString() const const
virtual bool isSequential() const const
QByteArray read(qint64 maxSize)
virtual qint64 size() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QChar at(qsizetype n) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
qsizetype length() const const