Cutelyst  3.5.0
multipartformdataparser.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2014-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "multipartformdataparser_p.h"
6 #include "upload_p.h"
7 #include "common.h"
8 
9 using namespace Cutelyst;
10 
11 Uploads MultiPartFormDataParser::parse(QIODevice *body, const QString &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(QLatin1String("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 == QLatin1Char('\"')) {
33  if ((quotes == 0 && i > start) || ++quotes == 2) {
34  break;
35  }
36  } else if (ch == QLatin1Char(';')) {
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 
61 Uploads MultiPartFormDataParserPrivate::execute(char *buffer, int bufferSize, QIODevice *body, const QByteArray &boundary)
62 {
63  Uploads ret;
64  QByteArray headerLine;
65  Headers headers;
66  qint64 startOffset;
67  qint64 pos = 0;
68  qint64 contentLength = body->size();
69  int bufferSkip = 0;
70  int boundarySize = boundary.size();
71  ParserState state = FindBoundary;
72  QByteArrayMatcher matcher(boundary);
73 
74  while (pos < contentLength) {
75  qint64 len = body->read(buffer + bufferSkip, bufferSize - bufferSkip);
76  if (len < 0) {
77  qCWarning(CUTELYST_MULTIPART) << "Error while reading POST body" << body->errorString();
78  return ret;
79  }
80 
81  pos += len;
82  len += bufferSkip;
83  bufferSkip = 0;
84  int i = 0;
85  while (i < len) {
86  switch (state) {
87  case FindBoundary:
88  i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
89  break;
90  case EndBoundaryCR:
91  // TODO the "--" case
92  if (buffer[i] != '\r') {
93 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryCR return!";
94  return ret;
95  }
96  state = EndBoundaryLF;
97  break;
98  case EndBoundaryLF:
99  if (buffer[i] != '\n') {
100 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryLF return!";
101  return ret;
102  }
103  state = StartHeaders;
104  break;
105  case StartHeaders:
106  if (headerLine.isEmpty() && buffer[i] == '\r') {
107  // nothing was read
108  state = EndHeaders;
109  } else {
110  char *pch = static_cast<char *>(memchr(buffer + i, '\r', len - i));
111  if (pch == NULL) {
112  headerLine.append(buffer + i, len - i);
113  i = len;
114  } else {
115  headerLine.append(buffer + i, pch - buffer - i);
116  i = pch - buffer;
117  state = FinishHeader;
118  }
119  }
120  break;
121  case FinishHeader:
122  if (buffer[i] == '\n') {
123  int dotdot = headerLine.indexOf(':');
124  headers.setHeader(QString::fromLatin1(headerLine.left(dotdot)),
125  QString::fromUtf8(headerLine.mid(dotdot + 1).trimmed()));
126  headerLine = QByteArray();
127  state = StartHeaders;
128  } else {
129 // qCDebug(CUTELYST_MULTIPART) << "FinishHeader return!";
130  return ret;
131  }
132  break;
133  case EndHeaders:
134  if (buffer[i] == '\n') {
135  state = StartData;
136  } else {
137 // qCDebug(CUTELYST_MULTIPART) << "EndHeaders return!";
138  return ret;
139  }
140  break;
141  case StartData:
142 // qCDebug(CUTELYST_MULTIPART) << "StartData" << body->pos() - len + i;
143  startOffset = pos - len + i;
144  state = EndData;
145  case EndData:
146  i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
147 
148  if (state == EndBoundaryCR) {
149 // qCDebug(CUTELYST_MULTIPART) << "EndData" << body->pos() - len + i - boundaryLength - 1;
150  const qint64 endOffset = pos - len + i - boundarySize - 1;
151  auto upload = new Upload(new UploadPrivate(body, headers, startOffset, endOffset));
152  ret.append(upload);
153 
154  headers = Headers();
155  } else {
156  // Boundary was not found so move the boundary size at end of the buffer
157  // to be sure we don't have the boundary in the middle of two chunks
158  bufferSkip = boundarySize - 1;
159  memmove(buffer, buffer + len - bufferSkip, bufferSkip);
160  }
161 
162  break;
163  }
164  ++i;
165  }
166  }
167 
168  return ret;
169 }
170 
171 int MultiPartFormDataParserPrivate::findBoundary(char *buffer, int len, const QByteArrayMatcher &matcher, int boundarySize, MultiPartFormDataParserPrivate::ParserState &state)
172 {
173  int i = matcher.indexIn(buffer, len);
174  // qCDebug(CUTELYST_MULTIPART) << "findBoundary" << QByteArray(buffer, len);
175  if (i != -1) {
176  // qCDebug(CUTELYST_MULTIPART) << "FindBoundary: found at" << i << body->pos() << len << body->pos() - len + i << i + boundaryLength;
177  state = EndBoundaryCR;
178  return i + boundarySize - 1;
179  }
180  return len;
181 }
182 
183 #include "moc_multipartformdataparser_p.cpp"
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
static Uploads parse(QIODevice *body, const QString &contentType, int bufferSize=4096)
Parser for multipart/formdata.
void append(const T &value)
QByteArray trimmed() const const
int indexIn(const QByteArray &ba, int from) const const
void reserve(int size)
QString errorString() const const
bool isEmpty() const const
virtual bool isSequential() const const
Cutelyst Upload handles file upload request
Definition: upload.h:22
int indexOf(char ch, int from) const const
QString fromUtf8(const char *str, int size)
virtual qint64 size() const const
QByteArray & prepend(char ch)
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:400
qint64 read(char *data, qint64 maxSize)
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
QByteArray mid(int pos, int len) const const
QByteArray & append(char ch)
char toLatin1() const const
QByteArray left(int len) const const
const QChar at(int position) const const
int length() const const
QString fromLatin1(const char *str, int size)
int size() const const