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 
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) {
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 
63 Uploads 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 
181 int 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"
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 sliced(qsizetype pos) 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:466