cutelyst  4.4.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"
Container for HTTP headers.
Definition: headers.h:24
void setHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:436
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 mid(qsizetype pos, qsizetype len) const const
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 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)