cutelyst  4.8.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 
9 using 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) || ++quotes == 2) {
34  break;
35  }
36  } else if (ch == ';') {
37  break;
38  } else {
39  boundary.append(ch);
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 
61 Uploads 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(headerLine.left(dotdot),
128  headerLine.mid(dotdot + 1).trimmed());
129  headerLine = {};
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 
178 int 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"
QByteArray trimmed() const const
void reserve(qsizetype size)
static Uploads parse(QIODevice *body, QByteArrayView contentType, int bufferSize=4096)
Parser for multipart/formdata.
QString errorString() const const
bool isEmpty() const const
Container for HTTP headers.
Definition: headers.h:23
virtual bool isSequential() const const
QByteArray read(qint64 maxSize)
Cutelyst Upload handles file upload requests.
Definition: upload.h:25
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
virtual qint64 size() const const
The Cutelyst namespace holds all public Cutelyst API.
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray & append(QByteArrayView data)
qsizetype length() const const
QByteArray left(qsizetype len) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
void append(QList< T > &&value)
char at(qsizetype n) const const
QByteArray & prepend(QByteArrayView ba)
qsizetype size() const const
qsizetype indexIn(QByteArrayView data, qsizetype from) const const
void setHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:437