1 /*
2 * Copyright (C) 2016-2018 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 "protocolhttp.h"
19 #include "socket.h"
20 #include "protocolwebsocket.h"
21 #include "server.h"
22 #include "protocolhttp2.h"
23
24 #include <Cutelyst/Headers>
25 #include <Cutelyst/Context>
26 #include <Cutelyst/Response>
27
28 #include <QVariant>
29 #include <QIODevice>
30 #include <QEventLoop>
31 #include <QCoreApplication>
32 #include <QBuffer>
33 #include <QTimer>
34 #include <QCryptographicHash>
35 #include <QLoggingCategory>
36
37 #include <typeinfo>
38
39 using namespace Cutelyst;
40
41 Q_LOGGING_CATEGORY(CWSGI_HTTP, "cwsgi.http", QtWarningMsg)
Q_DECLARE_LOGGING_CATEGORY(CWSGI_SOCK)42 Q_DECLARE_LOGGING_CATEGORY(CWSGI_SOCK)
43
44 ProtocolHttp::ProtocolHttp(Server *wsgi, ProtocolHttp2 *upgradeH2c) : Protocol(wsgi)
45 , m_websocketProto(new ProtocolWebSocket(wsgi))
46 , m_upgradeH2c(upgradeH2c)
47 {
48 usingFrontendProxy = wsgi->usingFrontendProxy();
49 }
50
~ProtocolHttp()51 ProtocolHttp::~ProtocolHttp()
52 {
53 delete m_websocketProto;
54 }
55
type() const56 Protocol::Type ProtocolHttp::type() const
57 {
58 return Http11;
59 }
60
CrLfIndexIn(const char * str,int len,int from)61 inline int CrLfIndexIn(const char *str, int len, int from)
62 {
63 do {
64 const char *pch = static_cast<const char *>(memchr(str + from, '\r', size_t(len - from)));
65 if (pch != nullptr) {
66 int pos = int(pch - str);
67 if ((pos + 1) < len) {
68 if (*++pch == '\n') {
69 return pos;
70 } else {
71 from = ++pos;
72 continue;
73 }
74 }
75 }
76 break;
77 } while (true);
78
79 return -1;
80 }
81
parse(Socket * sock,QIODevice * io) const82 void ProtocolHttp::parse(Socket *sock, QIODevice *io) const
83 {
84 // Post buffering
85 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
86 if (protoRequest->status & Cutelyst::EngineRequest::Async) {
87 return;
88 }
89
90 if (protoRequest->connState == ProtoRequestHttp::ContentBody) {
91 qint64 bytesAvailable = io->bytesAvailable();
92 qint64 len;
93 qint64 remaining;
94
95 QIODevice *body = protoRequest->body;
96 do {
97 remaining = protoRequest->contentLength - body->size();
98 len = io->read(m_postBuffer, qMin(m_postBufferSize, remaining));
99 if (len == -1) {
100 qCWarning(CWSGI_HTTP) << "error while reading body" << len << protoRequest->headers;
101 sock->connectionClose();
102 return;
103 }
104 bytesAvailable -= len;
105 // qCDebug(CWSGI_HTTP) << "WRITE body" << protoRequest->contentLength << remaining << len << (remaining == len) << io->bytesAvailable();
106 body->write(m_postBuffer, len);
107 } while (bytesAvailable);
108
109 if (remaining == len) {
110 processRequest(sock, io);
111 }
112
113 return;
114 }
115
116 qint64 len = io->read(protoRequest->buffer + protoRequest->buf_size, m_bufferSize - protoRequest->buf_size);
117 if (len == -1) {
118 qCWarning(CWSGI_HTTP) << "Failed to read from socket" << io->errorString();
119 return;
120 }
121 protoRequest->buf_size += len;
122
123 while (protoRequest->last < protoRequest->buf_size) {
124 // qCDebug(CWSGI_HTTP) << Q_FUNC_INFO << QByteArray(protoRequest->buffer, protoRequest->buf_size);
125 int ix = CrLfIndexIn(protoRequest->buffer, protoRequest->buf_size, protoRequest->last);
126 if (ix != -1) {
127 qint64 len = ix - protoRequest->beginLine;
128 char *ptr = protoRequest->buffer + protoRequest->beginLine;
129 protoRequest->beginLine = ix + 2;
130 protoRequest->last = protoRequest->beginLine;
131
132 if (protoRequest->connState == ProtoRequestHttp::MethodLine) {
133 if (!protoRequest->elapsed.isValid()) {
134 protoRequest->elapsed.start();
135 }
136 parseMethod(ptr, ptr + len, sock);
137 protoRequest->connState = ProtoRequestHttp::HeaderLine;
138 protoRequest->contentLength = -1;
139 protoRequest->headers = Cutelyst::Headers();
140 // qCDebug(CWSGI_HTTP) << "--------" << protoRequest->method << protoRequest->path << protoRequest->query << protoRequest->protocol;
141
142 } else if (protoRequest->connState == ProtoRequestHttp::HeaderLine) {
143 if (len) {
144 parseHeader(ptr, ptr + len, sock);
145 } else {
146 if (protoRequest->contentLength > 0) {
147 protoRequest->connState = ProtoRequestHttp::ContentBody;
148 protoRequest->body = createBody(protoRequest->contentLength);
149 if (!protoRequest->body) {
150 qCWarning(CWSGI_HTTP) << "error while creating body, closing socket";
151 sock->connectionClose();
152 return;
153 }
154
155 ptr += 2;
156 len = qMin(protoRequest->contentLength, static_cast<qint64>(protoRequest->buf_size - protoRequest->last));
157 // qCDebug(CWSGI_HTTP) << "WRITE" << protoRequest->contentLength << len;
158 if (len) {
159 protoRequest->body->write(ptr, len);
160 }
161 protoRequest->last += len;
162
163 if (protoRequest->contentLength > len) {
164 // qCDebug(CWSGI_HTTP) << "WRITE more..." << protoRequest->contentLength << len;
165 // body is not completed yet
166 if (io->bytesAvailable()) {
167 // since we still have bytes available call this function
168 // so that the body parser reads the rest of available data
169 parse(sock, io);
170 }
171 return;
172 }
173 }
174
175 if (!processRequest(sock, io)) {
176 break;
177 }
178 }
179 }
180 } else {
181 if (!protoRequest->elapsed.isValid()) {
182 protoRequest->elapsed.start();
183 }
184 protoRequest->last = protoRequest->buf_size;
185 }
186 }
187
188 if (protoRequest->buf_size == m_bufferSize) {
189 // 414 Request-URI Too Long
190 }
191 }
192
createData(Socket * sock) const193 ProtocolData *ProtocolHttp::createData(Socket *sock) const
194 {
195 return new ProtoRequestHttp(sock, m_bufferSize);
196 }
197
processRequest(Socket * sock,QIODevice * io) const198 bool ProtocolHttp::processRequest(Socket *sock, QIODevice *io) const
199 {
200 auto request = static_cast<ProtoRequestHttp *>(sock->protoData);
201 // qCDebug(CWSGI_HTTP) << "processRequest" << sock->protoData->contentLength;
202 if (request->body) {
203 request->body->seek(0);
204 }
205
206 // When enabled try to upgrade to H2C
207 if (m_upgradeH2c && m_upgradeH2c->upgradeH2C(sock, io, *request)) {
208 return false;
209 }
210
211 ++sock->processing;
212 sock->engine->processRequest(request);
213
214 if (request->websocketUpgraded) {
215 return false; // Must read remaining data
216 }
217
218 if (request->status & Cutelyst::EngineRequest::Async) {
219 return false; // Need to break now
220 }
221
222 return true;
223 }
224
parseMethod(const char * ptr,const char * end,Socket * sock) const225 void ProtocolHttp::parseMethod(const char *ptr, const char *end, Socket *sock) const
226 {
227 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
228 const char *word_boundary = ptr;
229 while (*word_boundary != ' ' && word_boundary < end) {
230 ++word_boundary;
231 }
232 protoRequest->method = QString::fromLatin1(ptr, int(word_boundary - ptr));
233
234 // skip spaces
235 while (*word_boundary == ' ' && word_boundary < end) {
236 ++word_boundary;
237 }
238 ptr = word_boundary;
239
240 // skip leading slashes
241 while (*ptr == '/' && ptr <= end) {
242 ++ptr;
243 }
244
245 // find path end
246 while (*word_boundary != ' ' && *word_boundary != '?' && word_boundary < end) {
247 ++word_boundary;
248 }
249
250 // This will change the ptr but will only change less than size
251 protoRequest->setPath(const_cast<char *>(ptr), int(word_boundary - ptr));
252
253 if (*word_boundary == '?') {
254 ptr = word_boundary + 1;
255 while (*word_boundary != ' ' && word_boundary < end) {
256 ++word_boundary;
257 }
258 protoRequest->query = QByteArray(ptr, int(word_boundary - ptr));
259 } else {
260 protoRequest->query = QByteArray();
261 }
262
263 // skip spaces
264 while (*word_boundary == ' ' && word_boundary < end) {
265 ++word_boundary;
266 }
267 ptr = word_boundary;
268
269 while (*word_boundary != ' ' && word_boundary < end) {
270 ++word_boundary;
271 }
272 protoRequest->protocol = QString::fromLatin1(ptr, int(word_boundary - ptr));
273 }
274
275
normalizeHeaderKey(const char * str,int size)276 inline QString normalizeHeaderKey(const char *str, int size)
277 {
278 int i = 0;
279 QString key = QString::fromLatin1(str, size);
280 while (i < key.size()) {
281 QChar c = key[i];
282 if (c.isLetter()) {
283 if (c.isLower()) {
284 key[i] = c.toUpper();
285 }
286 } else if (c == QLatin1Char('-')) {
287 key[i] = QLatin1Char('_');
288 }
289 ++i;
290 }
291 return key;
292 }
293
parseHeader(const char * ptr,const char * end,Socket * sock) const294 void ProtocolHttp::parseHeader(const char *ptr, const char *end, Socket *sock) const
295 {
296 auto protoRequest = static_cast<ProtoRequestHttp *>(sock->protoData);
297 const char *word_boundary = ptr;
298 while (*word_boundary != ':' && word_boundary < end) {
299 ++word_boundary;
300 }
301 const QString key = normalizeHeaderKey(ptr, int(word_boundary - ptr));
302
303 while ((*word_boundary == ':' || *word_boundary == ' ') && word_boundary < end) {
304 ++word_boundary;
305 }
306 const QString value = QString::fromLatin1(word_boundary, int(end - word_boundary));
307
308 if (protoRequest->headerConnection == ProtoRequestHttp::HeaderConnectionNotSet && key == QLatin1String("CONNECTION")) {
309 if (value.compare(QLatin1String("close"), Qt::CaseInsensitive) == 0) {
310 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnectionClose;
311 } else {
312 protoRequest->headerConnection = ProtoRequestHttp::HeaderConnectionKeep;
313 }
314 } else if (protoRequest->contentLength < 0 && key == QLatin1String("CONTENT_LENGTH")) {
315 bool ok;
316 qint64 cl = value.toLongLong(&ok);
317 if (ok && cl >= 0) {
318 protoRequest->contentLength = cl;
319 }
320 } else if (!protoRequest->headerHost && key == QLatin1String("HOST")) {
321 protoRequest->serverAddress = value;
322 protoRequest->headerHost = true;
323 } else if (usingFrontendProxy) {
324 if (!protoRequest->X_Forwarded_For && (key == QLatin1String("X_FORWARDED_FOR") || key == QLatin1String("X_REAL_IP"))) {
325 protoRequest->remoteAddress = QHostAddress(value); // configure your reverse-proxy to list only one IP address
326 protoRequest->remotePort = 0; // unknown
327 protoRequest->X_Forwarded_For = true;
328 } else if (!protoRequest->X_Forwarded_Host && key == QLatin1String("X_FORWARDED_HOST")) {
329 protoRequest->serverAddress = value;
330 protoRequest->X_Forwarded_Host = true;
331 protoRequest->headerHost = true; // ignore a following Host: header (if any)
332 } else if (!protoRequest->X_Forwarded_Proto && key == QLatin1String("X_FORWARDED_PROTO")) {
333 protoRequest->isSecure = (value == QLatin1String("https"));
334 protoRequest->X_Forwarded_Proto = true;
335 }
336 }
337 protoRequest->headers.pushRawHeader(key, value);
338 }
339
ProtoRequestHttp(Socket * sock,int bufferSize)340 ProtoRequestHttp::ProtoRequestHttp(Socket *sock, int bufferSize) : ProtocolData(sock, bufferSize)
341 {
342 isSecure = sock->isSecure;
343 }
344
~ProtoRequestHttp()345 ProtoRequestHttp::~ProtoRequestHttp()
346 {
347
348 }
349
setupNewConnection(Socket * sock)350 void ProtoRequestHttp::setupNewConnection(Socket *sock)
351 {
352 serverAddress = sock->serverAddress;
353 remoteAddress = sock->remoteAddress;
354 remotePort = sock->remotePort;
355 }
356
writeHeaders(quint16 status,const Cutelyst::Headers & headers)357 bool ProtoRequestHttp::writeHeaders(quint16 status, const Cutelyst::Headers &headers)
358 {
359 if (websocketUpgraded && status != Cutelyst::Response::SwitchingProtocols) {
360 qCWarning(CWSGI_SOCK) << "Trying to write header while on an Websocket context";
361 return false;
362 }
363
364 int msgLen;
365 const char *msg = CWsgiEngine::httpStatusMessage(status, &msgLen);
366 QByteArray data(msg, msgLen);
367
368 const auto headersData = headers.data();
369 ProtoRequestHttp::HeaderConnection fallbackConnection = headerConnection;
370 headerConnection = ProtoRequestHttp::HeaderConnectionNotSet;
371
372 bool hasDate = false;
373 auto it = headersData.constBegin();
374 while (it != headersData.constEnd()) {
375 const QString &key = it.key();
376 const QString &value = it.value();
377 if (headerConnection == ProtoRequestHttp::HeaderConnectionNotSet && key == QLatin1String("CONNECTION")) {
378 if (value.compare(QLatin1String("close"), Qt::CaseInsensitive) == 0) {
379 headerConnection = ProtoRequestHttp::HeaderConnectionClose;
380 } else if (value.compare(QLatin1String("upgrade"), Qt::CaseInsensitive) == 0) {
381 headerConnection = ProtoRequestHttp::HeaderConnectionUpgrade;
382 } else {
383 headerConnection = ProtoRequestHttp::HeaderConnectionKeep;
384 }
385 } else if (!hasDate && key == QLatin1String("DATE")) {
386 hasDate = true;
387 }
388
389 QString ret(QLatin1String("\r\n") + Cutelyst::Engine::camelCaseHeader(key) + QLatin1String(": ") + value);
390 data.append(ret.toLatin1());
391
392 ++it;
393 }
394
395 if (headerConnection == ProtoRequestHttp::HeaderConnectionNotSet) {
396 if (fallbackConnection == ProtoRequestHttp::HeaderConnectionKeep
397 || (fallbackConnection != ProtoRequestHttp::HeaderConnectionClose && protocol == QLatin1String("HTTP/1.1"))) {
398 headerConnection = ProtoRequestHttp::HeaderConnectionKeep;
399 data.append("\r\nConnection: keep-alive", 24);
400 } else {
401 headerConnection = ProtoRequestHttp::HeaderConnectionClose;
402 data.append("\r\nConnection: close", 19);
403 }
404 }
405
406 if (!hasDate) {
407 data.append(static_cast<CWsgiEngine *>(sock->engine)->lastDate());
408 }
409 data.append("\r\n\r\n", 4);
410
411 return io->write(data) == data.size();
412 }
413
doWrite(const char * data,qint64 len)414 qint64 ProtoRequestHttp::doWrite(const char *data, qint64 len)
415 {
416 return io->write(data, len);
417 }
418
processingFinished()419 void ProtoRequestHttp::processingFinished()
420 {
421 if (websocketUpgraded) {
422 // need 2 byte header
423 websocket_need = 2;
424 websocket_phase = ProtoRequestHttp::WebSocketPhaseHeaders;
425 buf_size = 0;
426 return;
427 }
428
429 if (!sock->requestFinished()) {
430 // disconnected
431 return;
432 }
433
434 if (headerConnection == ProtoRequestHttp::HeaderConnectionClose) {
435 sock->connectionClose();
436 return;
437 }
438
439 if (last < buf_size) {
440 // move pipelined request to 0
441 int remaining = buf_size - last;
442 memmove(buffer, buffer + last, size_t(remaining));
443 resetData();
444 buf_size = remaining;
445
446 if (status & EngineRequest::Async) {
447 sock->proto->parse(sock, io);
448 }
449 } else {
450 resetData();
451 }
452 }
453
webSocketSendTextMessage(const QString & message)454 bool ProtoRequestHttp::webSocketSendTextMessage(const QString &message)
455 {
456 if (headerConnection != ProtoRequestHttp::HeaderConnectionUpgrade) {
457 return false;
458 }
459
460 const QByteArray rawMessage = message.toUtf8();
461 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeText, quint64(rawMessage.size()));
462 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
463 }
464
webSocketSendBinaryMessage(const QByteArray & message)465 bool ProtoRequestHttp::webSocketSendBinaryMessage(const QByteArray &message)
466 {
467 if (headerConnection != ProtoRequestHttp::HeaderConnectionUpgrade) {
468 return false;
469 }
470
471 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodeBinary, quint64(message.size()));
472 return doWrite(headers) == headers.size() && doWrite(message) == message.size();
473 }
474
webSocketSendPing(const QByteArray & payload)475 bool ProtoRequestHttp::webSocketSendPing(const QByteArray &payload)
476 {
477 if (headerConnection != ProtoRequestHttp::HeaderConnectionUpgrade) {
478 return false;
479 }
480
481 const QByteArray rawMessage = payload.left(125);
482 const QByteArray headers = ProtocolWebSocket::createWebsocketHeader(ProtoRequestHttp::OpCodePing, quint64(rawMessage.size()));
483 return doWrite(headers) == headers.size() && doWrite(rawMessage) == rawMessage.size();
484 }
485
webSocketClose(quint16 code,const QString & reason)486 bool ProtoRequestHttp::webSocketClose(quint16 code, const QString &reason)
487 {
488 if (headerConnection != ProtoRequestHttp::HeaderConnectionUpgrade) {
489 return false;
490 }
491
492 const QByteArray reply = ProtocolWebSocket::createWebsocketCloseReply(reason, code);
493 bool ret = doWrite(reply) == reply.size();
494 sock->requestFinished();
495 sock->connectionClose();
496 return ret;
497 }
498
socketDisconnected()499 void ProtoRequestHttp::socketDisconnected()
500 {
501 if (websocketUpgraded) {
502 if (websocket_finn_opcode != 0x88) {
503 Q_EMIT context->request()->webSocketClosed(1005, QString());
504 }
505 sock->requestFinished();
506 }
507 }
508
webSocketHandshakeDo(const QString & key,const QString & origin,const QString & protocol)509 bool ProtoRequestHttp::webSocketHandshakeDo(const QString &key, const QString &origin, const QString &protocol)
510 {
511 if (headerConnection == ProtoRequestHttp::HeaderConnectionUpgrade) {
512 return true;
513 }
514
515 if (sock->proto->type() != Protocol::Http11) {
516 qCWarning(CWSGI_SOCK) << "Upgrading a connection to websocket is only supported with the HTTP/1.1 protocol" << typeid(sock->proto).name();
517 return false;
518 }
519
520 const Cutelyst::Headers requestHeaders = context->request()->headers();
521 Cutelyst::Response *response = context->response();
522 Cutelyst::Headers &headers = response->headers();
523
524 response->setStatus(Cutelyst::Response::SwitchingProtocols);
525 headers.setHeader(QStringLiteral("UPGRADE"), QStringLiteral("WebSocket"));
526 headers.setHeader(QStringLiteral("CONNECTION"), QStringLiteral("Upgrade"));
527 const QString localOrigin = origin.isEmpty() ? requestHeaders.header(QStringLiteral("ORIGIN")) : origin;
528 headers.setHeader(QStringLiteral("SEC_WEBSOCKET_ORIGIN"), localOrigin.isEmpty() ? QStringLiteral("*") : localOrigin);
529
530 const QString wsProtocol = protocol.isEmpty() ? requestHeaders.header(QStringLiteral("SEC_WEBSOCKET_PROTOCOL")) : protocol;
531 if (!wsProtocol.isEmpty()) {
532 headers.setHeader(QStringLiteral("SEC_WEBSOCKET_PROTOCOL"), wsProtocol);
533 }
534
535 const QString localKey = key.isEmpty() ? requestHeaders.header(QStringLiteral("SEC_WEBSOCKET_KEY")) : key;
536 const QString wsKey = localKey + QLatin1String("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
537 if (wsKey.length() == 36) {
538 qCWarning(CWSGI_SOCK) << "Missing websocket key";
539 return false;
540 }
541
542 const QByteArray wsAccept = QCryptographicHash::hash(wsKey.toLatin1(), QCryptographicHash::Sha1).toBase64();
543 headers.setHeader(QStringLiteral("SEC_WEBSOCKET_ACCEPT"), QString::fromLatin1(wsAccept));
544
545 headerConnection = ProtoRequestHttp::HeaderConnectionUpgrade;
546 websocketUpgraded = true;
547 auto httpProto = static_cast<ProtocolHttp *>(sock->proto);
548 sock->proto = httpProto->m_websocketProto;
549
550 return writeHeaders(Cutelyst::Response::SwitchingProtocols, headers);
551 }
552
553 #include "moc_protocolhttp.cpp"
554