qBittorrent
Http::RequestParser Class Reference

#include <requestparser.h>

Collaboration diagram for Http::RequestParser:

Classes

struct  ParseResult
 

Public Types

enum class  ParseStatus { OK , Incomplete , BadRequest }
 

Static Public Member Functions

static ParseResult parse (const QByteArray &data)
 

Static Public Attributes

static const long MAX_CONTENT_SIZE = 64 * 1024 * 1024
 

Private Member Functions

 RequestParser ()
 
ParseResult doParse (const QByteArray &data)
 
bool parseStartLines (QStringView data)
 
bool parseRequestLine (const QString &line)
 
bool parsePostMessage (const QByteArray &data)
 
bool parseFormData (const QByteArray &data)
 

Private Attributes

Request m_request
 

Detailed Description

Definition at line 37 of file requestparser.h.

Member Enumeration Documentation

◆ ParseStatus

Enumerator
OK 
Incomplete 
BadRequest 

Definition at line 40 of file requestparser.h.

41  {
42  OK,
43  Incomplete,
44  BadRequest
45  };

Constructor & Destructor Documentation

◆ RequestParser()

RequestParser::RequestParser ( )
private

Definition at line 78 of file requestparser.cpp.

79 {
80 }

Member Function Documentation

◆ doParse()

RequestParser::ParseResult RequestParser::doParse ( const QByteArray &  data)
private

Definition at line 88 of file requestparser.cpp.

89 {
90  // we don't handle malformed requests which use double `LF` as delimiter
91  const int headerEnd = data.indexOf(EOH);
92  if (headerEnd < 0)
93  {
94  qDebug() << Q_FUNC_INFO << "incomplete request";
95  return {ParseStatus::Incomplete, Request(), 0};
96  }
97 
98  const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
99  if (!parseStartLines(httpHeaders))
100  {
101  qWarning() << Q_FUNC_INFO << "header parsing error";
102  return {ParseStatus::BadRequest, Request(), 0};
103  }
104 
105  const int headerLength = headerEnd + EOH.length();
106 
107  // handle supported methods
109  return {ParseStatus::OK, m_request, headerLength};
111  {
112  const auto parseContentLength = [this]() -> int
113  {
114  // [rfc7230] 3.3.2. Content-Length
115 
116  const QString rawValue = m_request.headers.value(HEADER_CONTENT_LENGTH);
117  if (rawValue.isNull()) // `HEADER_CONTENT_LENGTH` does not exist
118  return 0;
119  return Utils::String::parseInt(rawValue).value_or(-1);
120  };
121 
122  const int contentLength = parseContentLength();
123  if (contentLength < 0)
124  {
125  qWarning() << Q_FUNC_INFO << "bad request: content-length invalid";
126  return {ParseStatus::BadRequest, Request(), 0};
127  }
128  if (contentLength > MAX_CONTENT_SIZE)
129  {
130  qWarning() << Q_FUNC_INFO << "bad request: message too long";
131  return {ParseStatus::BadRequest, Request(), 0};
132  }
133 
134  if (contentLength > 0)
135  {
136  const QByteArray httpBodyView = midView(data, headerLength, contentLength);
137  if (httpBodyView.length() < contentLength)
138  {
139  qDebug() << Q_FUNC_INFO << "incomplete request";
140  return {ParseStatus::Incomplete, Request(), 0};
141  }
142 
143  if (!parsePostMessage(httpBodyView))
144  {
145  qWarning() << Q_FUNC_INFO << "message body parsing error";
146  return {ParseStatus::BadRequest, Request(), 0};
147  }
148  }
149 
150  return {ParseStatus::OK, m_request, (headerLength + contentLength)};
151  }
152 
153  qWarning() << Q_FUNC_INFO << "unsupported request method: " << m_request.method;
154  return {ParseStatus::BadRequest, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented"
155 }
static const long MAX_CONTENT_SIZE
Definition: requestparser.h:57
bool parseStartLines(QStringView data)
bool parsePostMessage(const QByteArray &data)
const char HEADER_REQUEST_METHOD_GET[]
Definition: types.h:60
const char HEADER_REQUEST_METHOD_POST[]
Definition: types.h:62
const char HEADER_CONTENT_LENGTH[]
Definition: types.h:45
const char HEADER_REQUEST_METHOD_HEAD[]
Definition: types.h:61
const QByteArray midView(const QByteArray &in, int pos, int len=-1)
Definition: bytearray.cpp:61
std::optional< int > parseInt(const QString &string)
Definition: string.cpp:82
HeaderMap headers
Definition: types.h:106
QString method
Definition: types.h:104

References anonymous_namespace{requestparser.cpp}::EOH, Http::HEADER_CONTENT_LENGTH, Http::HEADER_REQUEST_METHOD_GET, Http::HEADER_REQUEST_METHOD_HEAD, Http::HEADER_REQUEST_METHOD_POST, Http::Request::method, Utils::ByteArray::midView(), and Utils::String::parseInt().

Referenced by parse().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse()

RequestParser::ParseResult RequestParser::parse ( const QByteArray &  data)
static

Definition at line 82 of file requestparser.cpp.

83 {
84  // Warning! Header names are converted to lowercase
85  return RequestParser().doParse(data);
86 }

References doParse().

Referenced by Http::Connection::read().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseFormData()

bool RequestParser::parseFormData ( const QByteArray &  data)
private

Definition at line 309 of file requestparser.cpp.

310 {
311  const QVector<QByteArray> list = splitToViews(data, EOH, Qt::KeepEmptyParts);
312 
313  if (list.size() != 2)
314  {
315  qWarning() << Q_FUNC_INFO << "multipart/form-data format error";
316  return false;
317  }
318 
319  const QString headers = QString::fromLatin1(list[0]);
320  const QByteArray payload = viewWithoutEndingWith(list[1], CRLF);
321 
322  HeaderMap headersMap;
323  const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
324  for (const auto &line : headerLines)
325  {
326  if (line.trimmed().startsWith(QString::fromLatin1(HEADER_CONTENT_DISPOSITION), Qt::CaseInsensitive))
327  {
328  // extract out filename & name
329  const QList<QStringView> directives = line.split(u';', Qt::SkipEmptyParts);
330 
331  for (const auto &directive : directives)
332  {
333  const int idx = directive.indexOf('=');
334  if (idx < 0)
335  continue;
336 
337  const QString name = directive.left(idx).trimmed().toString().toLower();
338  const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
339  headersMap[name] = value;
340  }
341  }
342  else
343  {
344  if (!parseHeaderLine(line.toString(), headersMap))
345  return false;
346  }
347  }
348 
349  // pick data
350  const QLatin1String filename("filename");
351  const QLatin1String name("name");
352 
353  if (headersMap.contains(filename))
354  {
355  m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload});
356  }
357  else if (headersMap.contains(name))
358  {
359  m_request.posts[headersMap[name]] = payload;
360  }
361  else
362  {
363  // malformed
364  qWarning() << Q_FUNC_INFO << "multipart/form-data header error";
365  return false;
366  }
367 
368  return true;
369 }
const char CRLF[]
Definition: types.h:75
const char HEADER_CONTENT_TYPE[]
Definition: types.h:47
const char HEADER_CONTENT_DISPOSITION[]
Definition: types.h:43
QMap< QString, QString > HeaderMap
Definition: types.h:99
QVector< QByteArray > splitToViews(const QByteArray &in, const QByteArray &sep, const Qt::SplitBehavior behavior=Qt::KeepEmptyParts)
Definition: bytearray.cpp:34
T unquote(const T &str, const QString &quotes=QChar('"'))
Definition: string.h:45
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
const QByteArray viewWithoutEndingWith(const QByteArray &in, const QByteArray &str)
bool parseHeaderLine(const QStringView line, HeaderMap &out)
dictionary headers
Definition: helpers.py:44
QVector< UploadedFile > files
Definition: types.h:109
QHash< QString, QString > posts
Definition: types.h:108

References Http::CRLF, anonymous_namespace{requestparser.cpp}::EOH, Http::HEADER_CONTENT_DISPOSITION, Http::HEADER_CONTENT_TYPE, nova3.helpers::headers, anonymous_namespace{requestparser.cpp}::parseHeaderLine(), Utils::ByteArray::splitToViews(), Utils::String::unquote(), anonymous_namespace{preferences.cpp}::value(), and anonymous_namespace{requestparser.cpp}::viewWithoutEndingWith().

Here is the call graph for this function:

◆ parsePostMessage()

bool RequestParser::parsePostMessage ( const QByteArray &  data)
private

Definition at line 243 of file requestparser.cpp.

244 {
245  // parse POST message-body
246  const QString contentType = m_request.headers[HEADER_CONTENT_TYPE];
247  const QString contentTypeLower = contentType.toLower();
248 
249  // application/x-www-form-urlencoded
250  if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED))
251  {
252  // [URL Standard] 5.1 application/x-www-form-urlencoded parsing
253  const QByteArray processedData = QByteArray(data).replace('+', ' ');
254 
255  QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded));
256  while (i.hasNext())
257  {
258  const QStringPair pair = i.next();
259  m_request.posts[pair.first] = pair.second;
260  }
261 
262  return true;
263  }
264 
265  // multipart/form-data
266  if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA))
267  {
268  // [rfc2046] 5.1.1. Common Syntax
269 
270  // find boundary delimiter
271  const QLatin1String boundaryFieldName("boundary=");
272  const int idx = contentType.indexOf(boundaryFieldName);
273  if (idx < 0)
274  {
275  qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!";
276  return false;
277  }
278 
279  const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).mid(idx + boundaryFieldName.size())).toLatin1();
280  if (delimiter.isEmpty())
281  {
282  qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
283  return false;
284  }
285 
286  // split data by "dash-boundary"
287  const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
288  QVector<QByteArray> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
289  if (multipart.isEmpty())
290  {
291  qWarning() << Q_FUNC_INFO << "multipart empty";
292  return false;
293  }
294 
295  // remove the ending delimiter
296  const QByteArray endDelimiter = QByteArray("--") + delimiter + QByteArray("--") + CRLF;
297  multipart.push_back(viewWithoutEndingWith(multipart.takeLast(), endDelimiter));
298 
299  return std::all_of(multipart.cbegin(), multipart.cend(), [this](const QByteArray &part)
300  {
301  return this->parseFormData(part);
302  });
303  }
304 
305  qWarning() << Q_FUNC_INFO << "unknown content type:" << contentType;
306  return false;
307 }
const char CONTENT_TYPE_FORM_ENCODED[]
Definition: types.h:71
const char CONTENT_TYPE_FORM_DATA[]
Definition: types.h:72
QPair< QString, QString > QStringPair

References Http::CONTENT_TYPE_FORM_DATA, Http::CONTENT_TYPE_FORM_ENCODED, Http::CRLF, Http::HEADER_CONTENT_TYPE, Utils::ByteArray::splitToViews(), Utils::String::unquote(), and anonymous_namespace{requestparser.cpp}::viewWithoutEndingWith().

Here is the call graph for this function:

◆ parseRequestLine()

bool RequestParser::parseRequestLine ( const QString &  line)
private

Definition at line 192 of file requestparser.cpp.

193 {
194  // [rfc7230] 3.1.1. Request Line
195 
196  const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"));
197  const QRegularExpressionMatch match = re.match(line);
198 
199  if (!match.hasMatch())
200  {
201  qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
202  return false;
203  }
204 
205  // Request Methods
206  m_request.method = match.captured(1);
207 
208  // Request Target
209  const QByteArray url {match.captured(2).toLatin1()};
210  const int sepPos = url.indexOf('?');
211  const QByteArray pathComponent = ((sepPos == -1) ? url : midView(url, 0, sepPos));
212 
213  m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent));
214 
215  if (sepPos >= 0)
216  {
217  const QByteArray query = midView(url, (sepPos + 1));
218 
219  // [rfc3986] 2.4 When to Encode or Decode
220  // URL components should be separated before percent-decoding
221  for (const QByteArray &param : asConst(splitToViews(query, "&")))
222  {
223  const int eqCharPos = param.indexOf('=');
224  if (eqCharPos <= 0) continue; // ignores params without name
225 
226  const QByteArray nameComponent = midView(param, 0, eqCharPos);
227  const QByteArray valueComponent = midView(param, (eqCharPos + 1));
228  const QString paramName = QString::fromUtf8(QByteArray::fromPercentEncoding(nameComponent).replace('+', ' '));
229  const QByteArray paramValue = valueComponent.isNull()
230  ? QByteArray("")
231  : QByteArray::fromPercentEncoding(valueComponent).replace('+', ' ');
232 
233  m_request.query[paramName] = paramValue;
234  }
235  }
236 
237  // HTTP-version
238  m_request.version = match.captured(3);
239 
240  return true;
241 }
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
QString version
Definition: types.h:103
QString path
Definition: types.h:105
QHash< QString, QByteArray > query
Definition: types.h:107

References asConst(), Utils::ByteArray::midView(), and Utils::ByteArray::splitToViews().

Here is the call graph for this function:

◆ parseStartLines()

bool RequestParser::parseStartLines ( QStringView  data)
private

Definition at line 157 of file requestparser.cpp.

158 {
159  // we don't handle malformed request which uses `LF` for newline
160  const QList<QStringView> lines = data.split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
161 
162  // [rfc7230] 3.2.2. Field Order
163  QStringList requestLines;
164  for (const auto &line : lines)
165  {
166  if (line.at(0).isSpace() && !requestLines.isEmpty())
167  {
168  // continuation of previous line
169  requestLines.last() += line.toString();
170  }
171  else
172  {
173  requestLines += line.toString();
174  }
175  }
176 
177  if (requestLines.isEmpty())
178  return false;
179 
180  if (!parseRequestLine(requestLines[0]))
181  return false;
182 
183  for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i)
184  {
186  return false;
187  }
188 
189  return true;
190 }
bool parseRequestLine(const QString &line)

References Http::CRLF, and anonymous_namespace{requestparser.cpp}::parseHeaderLine().

Here is the call graph for this function:

Member Data Documentation

◆ m_request

Request Http::RequestParser::m_request
private

Definition at line 69 of file requestparser.h.

◆ MAX_CONTENT_SIZE

const long Http::RequestParser::MAX_CONTENT_SIZE = 64 * 1024 * 1024
static

Definition at line 57 of file requestparser.h.

Referenced by Http::Connection::read().


The documentation for this class was generated from the following files: