2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2018 Mike Tzou (Chocobo1)
4 * Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
5 * Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "requestparser.h"
36 #include <QRegularExpression>
37 #include <QStringList>
41 #include "base/global.h"
42 #include "base/utils/bytearray.h"
43 #include "base/utils/string.h"
46 using namespace Utils::ByteArray
;
47 using QStringPair
= QPair
<QString
, QString
>;
51 const QByteArray EOH
= QByteArray(CRLF
).repeated(2);
53 const QByteArray
viewWithoutEndingWith(const QByteArray
&in
, const QByteArray
&str
)
56 return QByteArray::fromRawData(in
.constData(), (in
.size() - str
.size()));
60 bool parseHeaderLine(const QStringView line
, HeaderMap
&out
)
62 // [rfc7230] 3.2. Header Fields
63 const int i
= line
.indexOf(':');
66 qWarning() << Q_FUNC_INFO
<< "invalid http header:" << line
;
70 const QString name
= line
.left(i
).trimmed().toString().toLower();
71 const QString value
= line
.mid(i
+ 1).trimmed().toString();
78 RequestParser::RequestParser()
82 RequestParser::ParseResult
RequestParser::parse(const QByteArray
&data
)
84 // Warning! Header names are converted to lowercase
85 return RequestParser().doParse(data
);
88 RequestParser::ParseResult
RequestParser::doParse(const QByteArray
&data
)
90 // we don't handle malformed requests which use double `LF` as delimiter
91 const int headerEnd
= data
.indexOf(EOH
);
94 qDebug() << Q_FUNC_INFO
<< "incomplete request";
95 return {ParseStatus::Incomplete
, Request(), 0};
98 const QString httpHeaders
= QString::fromLatin1(data
.constData(), headerEnd
);
99 if (!parseStartLines(httpHeaders
))
101 qWarning() << Q_FUNC_INFO
<< "header parsing error";
102 return {ParseStatus::BadRequest
, Request(), 0};
105 const int headerLength
= headerEnd
+ EOH
.length();
107 // handle supported methods
108 if ((m_request
.method
== HEADER_REQUEST_METHOD_GET
) || (m_request
.method
== HEADER_REQUEST_METHOD_HEAD
))
109 return {ParseStatus::OK
, m_request
, headerLength
};
110 if (m_request
.method
== HEADER_REQUEST_METHOD_POST
)
113 const int contentLength
= m_request
.headers
[HEADER_CONTENT_LENGTH
].toInt(&ok
);
114 if (!ok
|| (contentLength
< 0))
116 qWarning() << Q_FUNC_INFO
<< "bad request: content-length invalid";
117 return {ParseStatus::BadRequest
, Request(), 0};
119 if (contentLength
> MAX_CONTENT_SIZE
)
121 qWarning() << Q_FUNC_INFO
<< "bad request: message too long";
122 return {ParseStatus::BadRequest
, Request(), 0};
125 if (contentLength
> 0)
127 const QByteArray httpBodyView
= midView(data
, headerLength
, contentLength
);
128 if (httpBodyView
.length() < contentLength
)
130 qDebug() << Q_FUNC_INFO
<< "incomplete request";
131 return {ParseStatus::Incomplete
, Request(), 0};
134 if (!parsePostMessage(httpBodyView
))
136 qWarning() << Q_FUNC_INFO
<< "message body parsing error";
137 return {ParseStatus::BadRequest
, Request(), 0};
141 return {ParseStatus::OK
, m_request
, (headerLength
+ contentLength
)};
144 qWarning() << Q_FUNC_INFO
<< "unsupported request method: " << m_request
.method
;
145 return {ParseStatus::BadRequest
, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented"
148 bool RequestParser::parseStartLines(const QStringView data
)
150 // we don't handle malformed request which uses `LF` for newline
151 const QList
<QStringView
> lines
= data
.split(QString::fromLatin1(CRLF
), Qt::SkipEmptyParts
);
153 // [rfc7230] 3.2.2. Field Order
154 QStringList requestLines
;
155 for (const auto &line
: lines
)
157 if (line
.at(0).isSpace() && !requestLines
.isEmpty())
159 // continuation of previous line
160 requestLines
.last() += line
.toString();
164 requestLines
+= line
.toString();
168 if (requestLines
.isEmpty())
171 if (!parseRequestLine(requestLines
[0]))
174 for (auto i
= ++(requestLines
.begin()); i
!= requestLines
.end(); ++i
)
176 if (!parseHeaderLine(*i
, m_request
.headers
))
183 bool RequestParser::parseRequestLine(const QString
&line
)
185 // [rfc7230] 3.1.1. Request Line
187 const QRegularExpression
re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"));
188 const QRegularExpressionMatch match
= re
.match(line
);
190 if (!match
.hasMatch())
192 qWarning() << Q_FUNC_INFO
<< "invalid http header:" << line
;
197 m_request
.method
= match
.captured(1);
200 const QByteArray url
{match
.captured(2).toLatin1()};
201 const int sepPos
= url
.indexOf('?');
202 const QByteArray pathComponent
= ((sepPos
== -1) ? url
: midView(url
, 0, sepPos
));
204 m_request
.path
= QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent
));
208 const QByteArray query
= midView(url
, (sepPos
+ 1));
210 // [rfc3986] 2.4 When to Encode or Decode
211 // URL components should be separated before percent-decoding
212 for (const QByteArray
¶m
: asConst(splitToViews(query
, "&")))
214 const int eqCharPos
= param
.indexOf('=');
215 if (eqCharPos
<= 0) continue; // ignores params without name
217 const QByteArray nameComponent
= midView(param
, 0, eqCharPos
);
218 const QByteArray valueComponent
= midView(param
, (eqCharPos
+ 1));
219 const QString paramName
= QString::fromUtf8(QByteArray::fromPercentEncoding(nameComponent
).replace('+', ' '));
220 const QByteArray paramValue
= valueComponent
.isNull()
222 : QByteArray::fromPercentEncoding(valueComponent
).replace('+', ' ');
224 m_request
.query
[paramName
] = paramValue
;
229 m_request
.version
= match
.captured(3);
234 bool RequestParser::parsePostMessage(const QByteArray
&data
)
236 // parse POST message-body
237 const QString contentType
= m_request
.headers
[HEADER_CONTENT_TYPE
];
238 const QString contentTypeLower
= contentType
.toLower();
240 // application/x-www-form-urlencoded
241 if (contentTypeLower
.startsWith(CONTENT_TYPE_FORM_ENCODED
))
243 // [URL Standard] 5.1 application/x-www-form-urlencoded parsing
244 const QByteArray processedData
= QByteArray(data
).replace('+', ' ');
246 QListIterator
<QStringPair
> i(QUrlQuery(processedData
).queryItems(QUrl::FullyDecoded
));
249 const QStringPair pair
= i
.next();
250 m_request
.posts
[pair
.first
] = pair
.second
;
256 // multipart/form-data
257 if (contentTypeLower
.startsWith(CONTENT_TYPE_FORM_DATA
))
259 // [rfc2046] 5.1.1. Common Syntax
261 // find boundary delimiter
262 const QLatin1String
boundaryFieldName("boundary=");
263 const int idx
= contentType
.indexOf(boundaryFieldName
);
266 qWarning() << Q_FUNC_INFO
<< "Could not find boundary in multipart/form-data header!";
270 const QByteArray delimiter
= Utils::String::unquote(QStringView(contentType
).mid(idx
+ boundaryFieldName
.size())).toLatin1();
271 if (delimiter
.isEmpty())
273 qWarning() << Q_FUNC_INFO
<< "boundary delimiter field empty!";
277 // split data by "dash-boundary"
278 const QByteArray dashDelimiter
= QByteArray("--") + delimiter
+ CRLF
;
279 QVector
<QByteArray
> multipart
= splitToViews(data
, dashDelimiter
, Qt::SkipEmptyParts
);
280 if (multipart
.isEmpty())
282 qWarning() << Q_FUNC_INFO
<< "multipart empty";
286 // remove the ending delimiter
287 const QByteArray endDelimiter
= QByteArray("--") + delimiter
+ QByteArray("--") + CRLF
;
288 multipart
.push_back(viewWithoutEndingWith(multipart
.takeLast(), endDelimiter
));
290 return std::all_of(multipart
.cbegin(), multipart
.cend(), [this](const QByteArray
&part
)
292 return this->parseFormData(part
);
296 qWarning() << Q_FUNC_INFO
<< "unknown content type:" << contentType
;
300 bool RequestParser::parseFormData(const QByteArray
&data
)
302 const QVector
<QByteArray
> list
= splitToViews(data
, EOH
, Qt::KeepEmptyParts
);
304 if (list
.size() != 2)
306 qWarning() << Q_FUNC_INFO
<< "multipart/form-data format error";
310 const QString headers
= QString::fromLatin1(list
[0]);
311 const QByteArray payload
= viewWithoutEndingWith(list
[1], CRLF
);
313 HeaderMap headersMap
;
314 const QList
<QStringView
> headerLines
= QStringView(headers
).split(QString::fromLatin1(CRLF
), Qt::SkipEmptyParts
);
315 for (const auto &line
: headerLines
)
317 if (line
.trimmed().startsWith(QString::fromLatin1(HEADER_CONTENT_DISPOSITION
), Qt::CaseInsensitive
))
319 // extract out filename & name
320 const QList
<QStringView
> directives
= line
.split(u
';', Qt::SkipEmptyParts
);
322 for (const auto &directive
: directives
)
324 const int idx
= directive
.indexOf('=');
328 const QString name
= directive
.left(idx
).trimmed().toString().toLower();
329 const QString value
= Utils::String::unquote(directive
.mid(idx
+ 1).trimmed()).toString();
330 headersMap
[name
] = value
;
335 if (!parseHeaderLine(line
.toString(), headersMap
))
341 const QLatin1String
filename("filename");
342 const QLatin1String
name("name");
344 if (headersMap
.contains(filename
))
346 m_request
.files
.append({headersMap
[filename
], headersMap
[HEADER_CONTENT_TYPE
], payload
});
348 else if (headersMap
.contains(name
))
350 m_request
.posts
[headersMap
[name
]] = payload
;
355 qWarning() << Q_FUNC_INFO
<< "multipart/form-data header error";