Cutelyst  1.11.0
multipartformdataparser.cpp
1 /*
2  * Copyright (C) 2014-2017 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "multipartformdataparser_p.h"
19 #include "upload_p.h"
20 #include "common.h"
21 
22 using namespace Cutelyst;
23 
24 Uploads MultiPartFormDataParser::parse(QIODevice *body, const QString &contentType, int bufferSize)
25 {
26  Uploads ret;
27  if (body->isSequential()) {
28  qCWarning(CUTELYST_MULTIPART) << "Parsing sequential body is not supported" << body;
29  return ret;
30  }
31 
32  int start = contentType.indexOf(QLatin1String("boundary="));
33  if (start == -1) {
34  qCWarning(CUTELYST_MULTIPART) << "No boudary match" << contentType;
35  return ret;
36  }
37 
38  start += 9;
39  QByteArray boundary;
40  const int len = contentType.length();
41  boundary.reserve(contentType.length() - start + 2);
42 
43  for (int i = start, quotes = 0; i < len; ++i) {
44  const QChar ch = contentType.at(i);
45  if (ch == QLatin1Char('\"')) {
46  if ((quotes == 0 && i > start) || ++quotes == 2) {
47  break;
48  }
49  } else if (ch == QLatin1Char(';')) {
50  break;
51  } else {
52  boundary.append(ch.toLatin1());
53  }
54  }
55 
56  if (boundary.isEmpty()) {
57  qCWarning(CUTELYST_MULTIPART) << "Boudary match was empty" << contentType;
58  return ret;
59  }
60  boundary.prepend("--", 2);
61 
62  if (bufferSize < 1024) {
63  bufferSize = 1024;
64  }
65  char *buffer = new char[bufferSize];
66 
67  ret = MultiPartFormDataParserPrivate::execute(buffer, bufferSize, body, boundary);
68 
69  delete [] buffer;
70 
71  return ret;
72 }
73 
74 Uploads MultiPartFormDataParserPrivate::execute(char *buffer, int bufferSize, QIODevice *body, const QByteArray &boundary)
75 {
76  Uploads ret;
77  QByteArray headerLine;
78  Headers headers;
79  qint64 startOffset;
80  qint64 pos = 0;
81  qint64 contentLength = body->size();
82  int bufferSkip = 0;
83  int boundarySize = boundary.size();
84  ParserState state = FindBoundary;
85  QByteArrayMatcher matcher(boundary);
86 
87  while (pos < contentLength) {
88  qint64 len = body->read(buffer + bufferSkip, bufferSize - bufferSkip);
89  if (len < 0) {
90  qCWarning(CUTELYST_MULTIPART) << "Error while reading POST body" << body->errorString();
91  return ret;
92  }
93 
94  pos += len;
95  len += bufferSkip;
96  bufferSkip = 0;
97  int i = 0;
98  while (i < len) {
99  switch (state) {
100  case FindBoundary:
101  i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
102  break;
103  case EndBoundaryCR:
104  // TODO the "--" case
105  if (buffer[i] != '\r') {
106 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryCR return!";
107  return ret;
108  }
109  state = EndBoundaryLF;
110  break;
111  case EndBoundaryLF:
112  if (buffer[i] != '\n') {
113 // qCDebug(CUTELYST_MULTIPART) << "EndBoundaryLF return!";
114  return ret;
115  }
116  state = StartHeaders;
117  break;
118  case StartHeaders:
119  if (headerLine.isEmpty() && buffer[i] == '\r') {
120  // nothing was read
121  state = EndHeaders;
122  } else {
123  char *pch = static_cast<char *>(memchr(buffer + i, '\r', len - i));
124  if (pch == NULL) {
125  headerLine.append(buffer + i, len - i);
126  i = len;
127  } else {
128  headerLine.append(buffer + i, pch - buffer - i);
129  i = pch - buffer;
130  state = FinishHeader;
131  }
132  }
133  break;
134  case FinishHeader:
135  if (buffer[i] == '\n') {
136  int dotdot = headerLine.indexOf(':');
137  headers.setHeader(QString::fromLatin1(headerLine.left(dotdot)),
138  QString::fromLatin1(headerLine.mid(dotdot + 1).trimmed()));
139  headerLine = QByteArray();
140  state = StartHeaders;
141  } else {
142 // qCDebug(CUTELYST_MULTIPART) << "FinishHeader return!";
143  return ret;
144  }
145  break;
146  case EndHeaders:
147  if (buffer[i] == '\n') {
148  state = StartData;
149  } else {
150 // qCDebug(CUTELYST_MULTIPART) << "EndHeaders return!";
151  return ret;
152  }
153  break;
154  case StartData:
155 // qCDebug(CUTELYST_MULTIPART) << "StartData" << body->pos() - len + i;
156  startOffset = pos - len + i;
157  state = EndData;
158  case EndData:
159  i += findBoundary(buffer + i, len - i, matcher, boundarySize, state);
160 
161  if (state == EndBoundaryCR) {
162 // qCDebug(CUTELYST_MULTIPART) << "EndData" << body->pos() - len + i - boundaryLength - 1;
163  int endOffset = pos - len + i - boundarySize - 1;
164  auto upload = new Upload(new UploadPrivate(body, headers, startOffset, endOffset));
165  ret.append(upload);
166 
167  headers = Headers();
168  } else {
169  // Boundary was not found so move the boundary size at end of the buffer
170  // to be sure we don't have the boundary in the middle of two chunks
171  bufferSkip = boundarySize - 1;
172  memmove(buffer, buffer + len - bufferSkip, bufferSkip);
173  }
174 
175  break;
176  }
177  ++i;
178  }
179  }
180 
181  return ret;
182 }
183 
184 int MultiPartFormDataParserPrivate::findBoundary(char *buffer, int len, const QByteArrayMatcher &matcher, int boundarySize, MultiPartFormDataParserPrivate::ParserState &state)
185 {
186  int i = matcher.indexIn(buffer, len);
187  // qCDebug(CUTELYST_MULTIPART) << "findBoundary" << QByteArray(buffer, len);
188  if (i != -1) {
189  // qCDebug(CUTELYST_MULTIPART) << "FindBoundary: found at" << i << body->pos() << len << body->pos() - len + i << i + boundaryLength;
190  state = EndBoundaryCR;
191  return i + boundarySize - 1;
192  }
193  return len;
194 }
195 
196 #include "moc_multipartformdataparser_p.cpp"
static Uploads parse(QIODevice *body, const QString &contentType, int bufferSize=4096)
Parser for multipart/formdata.
Cutelyst Upload handles file upload request
Definition: upload.h:35
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:358
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
QVector< Upload * > Uploads
Definition: request.h:35