11
11
12
12
#include " HTTPConnection.h"
13
13
14
+ #include < assert.h>
15
+
14
16
#include < QBuffer>
15
17
#include < QCryptographicHash>
16
18
#include < QTcpSocket>
@@ -29,6 +31,88 @@ const char* HTTPConnection::StatusCode404 = "404 Not Found";
29
31
const char * HTTPConnection::StatusCode500 = " 500 Internal server error" ;
30
32
const char * HTTPConnection::DefaultContentType = " text/plain; charset=ISO-8859-1" ;
31
33
34
+
35
+ class MemoryStorage : public HTTPConnection ::Storage {
36
+ public:
37
+ static std::unique_ptr<MemoryStorage> make (qint64 size);
38
+ virtual ~MemoryStorage () = default ;
39
+
40
+ const QByteArray& content () const override { return _array; }
41
+ qint64 bytesLeftToWrite () const override { return _array.size () - _bytesWritten; }
42
+ void write (const QByteArray& data) override ;
43
+
44
+ private:
45
+ MemoryStorage (qint64 size) { _array.resize (size); }
46
+
47
+ QByteArray _array;
48
+ qint64 _bytesWritten { 0 };
49
+ };
50
+
51
+ std::unique_ptr<MemoryStorage> MemoryStorage::make (qint64 size) {
52
+ return std::unique_ptr<MemoryStorage>(new MemoryStorage (size));
53
+ }
54
+
55
+ void MemoryStorage::write (const QByteArray& data) {
56
+ assert (data.size () <= bytesLeftToWrite ());
57
+ memcpy (_array.data () + _bytesWritten, data.data (), data.size ());
58
+ _bytesWritten += data.size ();
59
+ }
60
+
61
+
62
+ class FileStorage : public HTTPConnection ::Storage {
63
+ public:
64
+ static std::unique_ptr<FileStorage> make (qint64 size);
65
+ virtual ~FileStorage ();
66
+
67
+ const QByteArray& content () const override { return _wrapperArray; };
68
+ qint64 bytesLeftToWrite () const override { return _mappedMemorySize - _bytesWritten; }
69
+ void write (const QByteArray& data) override ;
70
+
71
+ private:
72
+ FileStorage (std::unique_ptr<QTemporaryFile> file, uchar* mapped, qint64 size);
73
+
74
+ // Byte array is const because any edit will trigger a deep copy
75
+ // and pull all the data we want to keep on disk in memory.
76
+ const QByteArray _wrapperArray;
77
+ std::unique_ptr<QTemporaryFile> _file;
78
+
79
+ uchar* const _mappedMemoryAddress { nullptr };
80
+ const qint64 _mappedMemorySize { 0 };
81
+ qint64 _bytesWritten { 0 };
82
+ };
83
+
84
+ std::unique_ptr<FileStorage> FileStorage::make (qint64 size) {
85
+ auto file = std::unique_ptr<QTemporaryFile>(new QTemporaryFile ());
86
+ file->open (); // Open for resize
87
+ file->resize (size);
88
+ auto mapped = file->map (0 , size); // map the entire file
89
+
90
+ return std::unique_ptr<FileStorage>(new FileStorage (std::move (file), mapped, size));
91
+ }
92
+
93
+ // Use QByteArray::fromRawData to avoid a new allocation and access the already existing
94
+ // memory directly as long as all operations on the array are const.
95
+ FileStorage::FileStorage (std::unique_ptr<QTemporaryFile> file, uchar* mapped, qint64 size) :
96
+ _wrapperArray(QByteArray::fromRawData(reinterpret_cast <char *>(mapped), size)),
97
+ _file(std::move(file)),
98
+ _mappedMemoryAddress(mapped),
99
+ _mappedMemorySize(size)
100
+ {
101
+ }
102
+
103
+ FileStorage::~FileStorage () {
104
+ _file->unmap (_mappedMemoryAddress);
105
+ _file->close ();
106
+ }
107
+
108
+ void FileStorage::write (const QByteArray& data) {
109
+ assert (data.size () <= bytesLeftToWrite ());
110
+ // We write directly to the mapped memory
111
+ memcpy (_mappedMemoryAddress + _bytesWritten, data.data (), data.size ());
112
+ _bytesWritten += data.size ();
113
+ }
114
+
115
+
32
116
HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) :
33
117
QObject(parentManager),
34
118
_parentManager(parentManager),
@@ -61,7 +145,7 @@ QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
61
145
return QHash<QString, QString>();
62
146
}
63
147
64
- QUrlQuery form { _requestContent };
148
+ QUrlQuery form { _requestContent-> content () };
65
149
QHash<QString, QString> pairs;
66
150
for (auto pair : form.queryItems ()) {
67
151
auto key = QUrl::fromPercentEncoding (pair.first .toLatin1 ().replace (' +' , ' ' ));
@@ -96,7 +180,7 @@ QList<FormData> HTTPConnection::parseFormData() const {
96
180
QByteArray end = " \r\n --" + boundary + " --\r\n " ;
97
181
98
182
QList<FormData> data;
99
- QBuffer buffer (const_cast <QByteArray*>(&_requestContent));
183
+ QBuffer buffer (const_cast <QByteArray*>(&_requestContent-> content () ));
100
184
buffer.open (QIODevice::ReadOnly);
101
185
while (buffer.canReadLine ()) {
102
186
QByteArray line = buffer.readLine ().trimmed ();
@@ -106,12 +190,13 @@ QList<FormData> HTTPConnection::parseFormData() const {
106
190
QByteArray line = buffer.readLine ().trimmed ();
107
191
if (line.isEmpty ()) {
108
192
// content starts after this line
109
- int idx = _requestContent.indexOf (end, buffer.pos ());
193
+ int idx = _requestContent-> content () .indexOf (end, buffer.pos ());
110
194
if (idx == -1 ) {
111
195
qWarning () << " Missing end boundary." << _address;
112
196
return data;
113
197
}
114
- datum.second = _requestContent.mid (buffer.pos (), idx - buffer.pos ());
198
+ datum.second = QByteArray::fromRawData (_requestContent->content ().data () + buffer.pos (),
199
+ idx - buffer.pos ());
115
200
data.append (datum);
116
201
buffer.seek (idx + end.length ());
117
202
@@ -255,7 +340,24 @@ void HTTPConnection::readHeaders() {
255
340
_parentManager->handleHTTPRequest (this , _requestUrl);
256
341
257
342
} else {
258
- _requestContent.resize (clength.toInt ());
343
+ bool success = false ;
344
+ auto length = clength.toInt (&success);
345
+ if (!success) {
346
+ qWarning () << " Invalid header." << _address << trimmed;
347
+ respond (" 400 Bad Request" , " The header was malformed." );
348
+ return ;
349
+ }
350
+
351
+ // Storing big requests in memory gets expensive, especially on servers
352
+ // with limited memory. So we store big requests in a temporary file on disk
353
+ // and map it to faster read/write access.
354
+ static const int MAX_CONTENT_SIZE_IN_MEMORY = 10 * 1000 * 1000 ;
355
+ if (length < MAX_CONTENT_SIZE_IN_MEMORY) {
356
+ _requestContent = MemoryStorage::make (length);
357
+ } else {
358
+ _requestContent = FileStorage::make (length);
359
+ }
360
+
259
361
connect (_socket, SIGNAL (readyRead ()), SLOT (readContent ()));
260
362
261
363
// read any content immediately available
@@ -284,12 +386,13 @@ void HTTPConnection::readHeaders() {
284
386
}
285
387
286
388
void HTTPConnection::readContent () {
287
- int size = _requestContent.size ();
288
- if (_socket->bytesAvailable () < size) {
289
- return ;
290
- }
291
- _socket->read (_requestContent.data (), size);
292
- _socket->disconnect (this , SLOT (readContent ()));
389
+ auto size = std::min (_socket->bytesAvailable (), _requestContent->bytesLeftToWrite ());
390
+
391
+ _requestContent->write (_socket->read (size));
293
392
294
- _parentManager->handleHTTPRequest (this , _requestUrl.path ());
393
+ if (_requestContent->bytesLeftToWrite () == 0 ) {
394
+ _socket->disconnect (this , SLOT (readContent ()));
395
+
396
+ _parentManager->handleHTTPRequest (this , _requestUrl.path ());
397
+ }
295
398
}
0 commit comments