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