MythTV  master
websocket.cpp
Go to the documentation of this file.
1 
2 // Own header
3 #include "websocket.h"
4 
5 // MythTV headers
6 #include "mythlogging.h"
7 #include "mythcorecontext.h"
8 #include "mythevent.h"
9 #include "codecutil.h"
11 
12 // QT headers
13 #include <QThread>
14 #include <QTcpSocket>
15 #include <QSslCipher>
16 #include <QCryptographicHash>
17 #include <QtCore>
18 #include <QtGlobal>
19 
21 //
23 
25  m_threadPool("WebSocketServerPool")
26 {
27  setObjectName("WebSocketServer");
28  // Number of connections processed concurrently
29  int maxThreads = qMax(QThread::idealThreadCount() * 2, 2); // idealThreadCount can return -1
30  // Don't allow more connections than we can process, it will simply cause
31  // browsers to stall
32  setMaxPendingConnections(maxThreads);
33  m_threadPool.setMaxThreadCount(maxThreads);
34 
35  LOG(VB_HTTP, LOG_NOTICE, QString("WebSocketServer(): Max Thread Count %1")
37 }
38 
40 {
41  m_rwlock.lockForWrite();
42  m_running = false;
43  m_rwlock.unlock();
44 
46 }
47 
49 {
50 
52  auto *server = dynamic_cast<PrivTcpServer *>(QObject::sender());
53  if (server)
54  type = server->GetServerType();
55 
57  new WebSocketWorkerThread(*this, socket, type
58 #ifndef QT_NO_OPENSSL
59  , m_sslConfig
60 #endif
61  ),
62  QString("WebSocketServer%1").arg(socket));
63 }
64 
66 //
68 
71 #ifndef QT_NO_OPENSSL
72  , const QSslConfiguration& sslConfig
73 #endif
74  )
75  :
76  m_webSocketServer(webSocketServer), m_socketFD(sock),
77  m_connectionType(type)
78 #ifndef QT_NO_OPENSSL
79  , m_sslConfig(sslConfig)
80 #endif
81 {
82 }
83 
85 {
86  auto *worker = new WebSocketWorker(m_webSocketServer, m_socketFD,
88 #ifndef QT_NO_OPENSSL
89  , m_sslConfig
90 #endif
91  );
92  worker->Exec();
93 
94  worker->deleteLater();
95 }
96 
98 //
100 
103 #ifndef QT_NO_OPENSSL
104  , const QSslConfiguration& sslConfig
105 #endif
106  )
107  : m_eventLoop(new QEventLoop()),
108  m_webSocketServer(webSocketServer),
109  m_socketFD(sock), m_connectionType(type),
110  m_heartBeat(new QTimer())
111 #ifndef QT_NO_OPENSSL
112  , m_sslConfig(sslConfig)
113 #endif
114 {
115  setObjectName(QString("WebSocketWorker(%1)")
116  .arg(m_socketFD));
117  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker(%1): New connection")
118  .arg(m_socketFD));
119 
120  // For now, until it's refactored, register the only extension here
122 
123  SetupSocket();
124 
125  m_isRunning = true;
126 }
127 
129 {
130  CleanupSocket();
131 
132  // If extensions weren't deregistered then it's our responsibility to
133  // delete them
134  while (!m_extensions.isEmpty())
135  {
136  WebSocketExtension *extension = m_extensions.takeFirst();
137  extension->deleteLater();
138  extension = nullptr;
139  }
140 
141  m_eventLoop->deleteLater();
142  m_eventLoop = nullptr;
143  delete m_heartBeat;
144 }
145 
147 {
148  m_eventLoop->exec();
149 }
150 
152 {
153  blockSignals(true);
154  m_isRunning = false;
155  LOG(VB_HTTP, LOG_INFO, "WebSocketWorker::CloseConnection()");
156  if (m_eventLoop)
157  m_eventLoop->exit();
158 }
159 
161 {
163  {
164 
165 #ifndef QT_NO_OPENSSL
166  auto *pSslSocket = new QSslSocket();
167  if (pSslSocket->setSocketDescriptor(m_socketFD)
168  && gCoreContext->CheckSubnet(pSslSocket))
169  {
170  pSslSocket->setSslConfiguration(m_sslConfig);
171  pSslSocket->startServerEncryption();
172  if (pSslSocket->waitForEncrypted(5000))
173  {
174  LOG(VB_HTTP, LOG_INFO, "SSL Handshake occurred, connection encrypted");
175  LOG(VB_HTTP, LOG_INFO, QString("Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
176  }
177  else
178  {
179  LOG(VB_HTTP, LOG_WARNING, "SSL Handshake FAILED, connection terminated");
180  delete pSslSocket;
181  pSslSocket = nullptr;
182  }
183  }
184  else
185  {
186  delete pSslSocket;
187  pSslSocket = nullptr;
188  }
189 
190  if (pSslSocket)
191  m_socket = dynamic_cast<QTcpSocket *>(pSslSocket);
192  else
193  return;
194 #else
195  return;
196 #endif
197  }
198  else // Plain old unencrypted socket
199  {
200  m_socket = new QTcpSocket();
201  m_socket->setSocketDescriptor(m_socketFD);
203  {
204  delete m_socket;
205  m_socket = nullptr;
206  return;
207  }
208 
209  }
210 
211  m_socket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
212 
213  connect(m_socket, SIGNAL(readyRead()), SLOT(doRead()));
214  connect(m_socket, SIGNAL(disconnected()), SLOT(CloseConnection()));
215 
216  // Setup heartbeat
217  m_heartBeat->setInterval(20000); // 20 second
218  m_heartBeat->setSingleShot(false);
219  connect(m_heartBeat, SIGNAL(timeout()), SLOT(SendHeartBeat()));
220 }
221 
223 {
224  if (m_socket->error() != QAbstractSocket::UnknownSocketError)
225  {
226  LOG(VB_HTTP, LOG_WARNING, QString("WebSocketWorker(%1): Error %2 (%3)")
227  .arg(m_socketFD)
228  .arg(m_socket->errorString())
229  .arg(m_socket->error()));
230  }
231 
232  int writeTimeout = 5000; // 5 Seconds
233  // Make sure any data in the buffer is flushed before the socket is closed
234  while (m_webSocketServer.IsRunning() &&
235  m_socket->isValid() &&
236  m_socket->state() == QAbstractSocket::ConnectedState &&
237  m_socket->bytesToWrite() > 0)
238  {
239  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker(%1): "
240  "Waiting for %2 bytes to be written "
241  "before closing the connection.")
242  .arg(m_socketFD)
243  .arg(m_socket->bytesToWrite()));
244 
245  // If the client stops reading for longer than 'writeTimeout' then
246  // stop waiting for them. We can't afford to leave the socket
247  // connected indefinitely, it could be used by another client.
248  //
249  // NOTE: Some clients deliberately stall as a way of 'pausing' A/V
250  // streaming. We should create a new server extension or adjust the
251  // timeout according to the User-Agent, instead of increasing the
252  // standard timeout. However we should ALWAYS have a timeout.
253  if (!m_socket->waitForBytesWritten(writeTimeout))
254  {
255  LOG(VB_GENERAL, LOG_WARNING, QString("WebSocketWorker(%1): "
256  "Timed out waiting to write bytes to "
257  "the socket, waited %2 seconds")
258  .arg(m_socketFD)
259  .arg(writeTimeout / 1000));
260  break;
261  }
262  }
263 
264  if (m_socket->bytesToWrite() > 0)
265  {
266  LOG(VB_HTTP, LOG_WARNING, QString("WebSocketWorker(%1): "
267  "Failed to write %2 bytes to "
268  "socket, (%3)")
269  .arg(m_socketFD)
270  .arg(m_socket->bytesToWrite())
271  .arg(m_socket->errorString()));
272  }
273 
274  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker(%1): Connection %2 closed.")
275  .arg(m_socketFD)
276  .arg(m_socket->socketDescriptor()));
277 
278  m_socket->close();
279  m_socket->deleteLater();
280  m_socket = nullptr;
281 }
282 
284 {
285  if (!m_isRunning)
286  return;
287 
288  if (!m_webSocketServer.IsRunning() || !m_socket->isOpen())
289  {
290  CloseConnection();
291  return;
292  }
293 
294  if (m_webSocketMode)
295  {
297  }
298  else
299  {
302  }
303 
304  if (!m_webSocketMode)
305  {
306  LOG(VB_HTTP, LOG_WARNING, "WebSocketServer: Timed out waiting for connection upgrade");
307  CloseConnection();
308  }
309 }
310 
311 bool WebSocketWorker::ProcessHandshake(QTcpSocket *socket)
312 {
313  QByteArray line;
314 
315  // Minimum length of the request line is 16
316  // i.e. "GET / HTTP/1.1\r\n"
317  while (socket->bytesAvailable() < 16)
318  {
319  if (!socket->waitForReadyRead(5000)) // 5 second timeout
320  return false;
321  }
322 
323  // Set a maximum length for a request line of 255 bytes, it's an
324  // arbitrary limit but a reasonable one for now
325  line = socket->readLine(255).trimmed(); // Strip newline
326  if (line.isEmpty())
327  return false;
328 
329 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
330  QStringList tokens = QString(line).split(' ', QString::SkipEmptyParts);
331 #else
332  QStringList tokens = QString(line).split(' ', Qt::SkipEmptyParts);
333 #endif
334 
335  if (tokens.length() != 3) // Anything but 3 is invalid - {METHOD} {HOST/PATH} {PROTOCOL}
336  {
337  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Invalid number of tokens in Request line");
338  return false;
339  }
340 
341  if (tokens[0] != "GET") // RFC 6455 - Request method MUST be GET
342  {
343  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid method: %1").arg(tokens[0]));
344  return false;
345  }
346 
347  QString path = tokens[1];
348 
349  if (path.contains('#')) // RFC 6455 - Fragments MUST NOT be used
350  {
351  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Path contains illegal fragment");
352  return false;
353  }
354 
355  if (!tokens[2].startsWith("HTTP/")) // Must be HTTP
356  {
357  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid protocol: %1").arg(tokens[2]));
358  return false;
359  }
360 
361  QString versionStr = tokens[2].section('/', 1, 1);
362 
363  int majorVersion = versionStr.section('.', 0, 0).toInt();
364  int minorVersion = versionStr.section('.', 1, 1).toInt();
365 
366  if ((majorVersion < 1) || (minorVersion < 1)) // RFC 6455 - HTTP version MUST be at least 1.1
367  {
368  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid HTTP version: %1.%2").arg(majorVersion).arg(minorVersion));
369  return false;
370  }
371 
372  // Read Header
373  line = socket->readLine(2000).trimmed(); // 2KB Maximum
374 
375  QMap<QString, QString> requestHeaders;
376  while (!line.isEmpty())
377  {
378  QString strLine = QString(line);
379  QString sName = strLine.section( ':', 0, 0 ).trimmed();
380  QString sValue = strLine.section( ':', 1 ).trimmed();
381  sValue.replace(" ",""); // Remove all internal whitespace
382 
383  if (!sName.isEmpty() && !sValue.isEmpty())
384  requestHeaders.insert(sName.toLower(), sValue);
385 
386  line = socket->readLine(2000).trimmed(); // 2KB Maximum
387  }
388 
389  // Dump request header
390  QMap<QString, QString>::iterator it;
391  for ( it = requestHeaders.begin();
392  it != requestHeaders.end();
393  ++it )
394  {
395  LOG(VB_HTTP, LOG_INFO, QString("(Request Header) %1: %2")
396  .arg(it.key()).arg(*it));
397  }
398 
399  if (!requestHeaders.contains("connection")) // RFC 6455 - 1.3. Opening Handshake
400  {
401  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing 'Connection' header");
402  return false;
403  }
404 
405 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
406  QStringList connectionValues = requestHeaders["connection"].split(',', QString::SkipEmptyParts);
407 #else
408  QStringList connectionValues = requestHeaders["connection"].split(',', Qt::SkipEmptyParts);
409 #endif
410  if (!connectionValues.contains("Upgrade", Qt::CaseInsensitive)) // RFC 6455 - 1.3. Opening Handshake
411  {
412  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Invalid 'Connection' header");
413  return false;
414  }
415 
416  if (!requestHeaders.contains("upgrade") ||
417  requestHeaders["upgrade"].toLower() != "websocket") // RFC 6455 - 1.3. Opening Handshake
418  {
419  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing or invalid 'Upgrade' header");
420  return false;
421  }
422 
423  if (!requestHeaders.contains("sec-websocket-version")) // RFC 6455 - 1.3. Opening Handshake
424  {
425  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing 'Sec-WebSocket-Version' header");
426  return false;
427  }
428 
429  if (requestHeaders["sec-websocket-version"] != "13") // RFC 6455 - 1.3. Opening Handshake
430  {
431  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Unsupported WebSocket protocol "
432  "version. We speak '13' the client speaks '%1'")
433  .arg(requestHeaders["sec-websocket-version"]));
434  return false;
435  }
436 
437  if (!requestHeaders.contains("sec-websocket-key") ||
438  QByteArray::fromBase64(requestHeaders["sec-websocket-key"].toLatin1()).length() != 16) // RFC 6455 - 1.3. Opening Handshake
439  {
440  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing or invalid 'sec-websocket-key' header");
441  return false;
442  }
443 
444  // If we're running the Autobahn fuzz/unit tester then we need to disable
445  // the heartbeat and 'echo' text/binary frames back to the sender
446  if (requestHeaders.contains("user-agent") &&
447  requestHeaders["user-agent"].contains("AutobahnTestSuite"))
448  m_fuzzTesting = true;
449 
450  QString key = requestHeaders["sec-websocket-key"];
451  const QString magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
452 
453  // Response token - Watch very closely, there's a lot happening here
454  QString acceptToken = QCryptographicHash::hash(key.append(magicString).toUtf8(),
455  QCryptographicHash::Sha1).toBase64();
456 
457  QMap<QString, QString> responseHeaders;
458  responseHeaders.insert("Connection", "Upgrade");
459  responseHeaders.insert("Upgrade", "websocket");
460  responseHeaders.insert("Sec-WebSocket-Accept", acceptToken);
461 
462  socket->write("HTTP/1.1 101 Switching Protocols\r\n");
463 
464  QString header("%1: %2\r\n");
465  for (it = responseHeaders.begin(); it != responseHeaders.end(); ++it)
466  {
467  socket->write(header.arg(it.key()).arg(*it).toLatin1().constData());
468  LOG(VB_HTTP, LOG_INFO, QString("(Response Header) %1: %2")
469  .arg(it.key()).arg(*it));
470  }
471 
472  socket->write("\r\n");
473  socket->waitForBytesWritten();
474 
475  m_webSocketMode = true;
476  // Start the heart beat
477  if (!m_fuzzTesting)
478  m_heartBeat->start();
479 
480  return true;
481 }
482 
483 void WebSocketWorker::ProcessFrames(QTcpSocket *socket)
484 {
485  while (m_isRunning && socket && socket->bytesAvailable() >= 2) // No header? Return and wait for more
486  {
487  uint8_t headerSize = 2; // Smallest possible header size is 2 bytes, greatest is 14 bytes
488 
489  QByteArray header = socket->peek(headerSize); // Read header to establish validity and size of frame
490 
491  WebSocketFrame frame;
492  // FIN
493  frame.m_finalFrame = (bool)(header[0] & 0x80);
494  // Reserved bits
495  if (header.at(0) & 0x70)
496  {
497  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() "
498  "- Invalid data in reserved fields");
499  SendClose(kCloseProtocolError, "Invalid data in reserved fields");
500  return;
501  }
502  // Operation code
503  uint8_t opCode = (header.at(0) & 0xF);
504  if ((opCode > WebSocketFrame::kOpBinaryFrame &&
505  opCode < WebSocketFrame::kOpClose) ||
506  (opCode > WebSocketFrame::kOpPong))
507  {
508  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessFrames() "
509  "- Invalid OpCode (%1)")
510  .arg(QString::number(opCode, 16)));
511  SendClose(kCloseProtocolError, "Invalid OpCode");
512  return;
513  }
514  frame.m_opCode = (WebSocketFrame::OpCode)opCode;
515  frame.m_isMasked = (((header.at(1) >> 7) & 0xFE) != 0);
516 
517  if (frame.m_isMasked)
518  headerSize += 4; // Add 4 bytes for the mask
519 
520  frame.m_payloadSize = (header.at(1) & 0x7F);
521  // Handle 16 or 64bit payload size headers
522  if (frame.m_payloadSize >= 126)
523  {
524  uint8_t payloadHeaderSize = 2; // 16bit payload size
525  if (frame.m_payloadSize == 127)
526  payloadHeaderSize = 8; // 64bit payload size
527 
528  headerSize += payloadHeaderSize; // Add bytes for extended payload size
529 
530  if (socket->bytesAvailable() < headerSize)
531  return; // Return and wait for more
532 
533  header = socket->peek(headerSize); // Peek the entire header
534  QByteArray payloadHeader = header.mid(2,payloadHeaderSize);
535  frame.m_payloadSize = 0;
536  for (int i = 0; i < payloadHeaderSize; i++)
537  {
538  frame.m_payloadSize |= ((uint8_t)payloadHeader.at(i) << ((payloadHeaderSize - (i + 1)) * 8));
539  }
540  }
541  else
542  {
543  if (socket->bytesAvailable() < headerSize)
544  return; // Return and wait for more
545  header = socket->peek(headerSize); // Peek the entire header including mask
546  }
547 
548  while ((uint64_t)socket->bytesAvailable() < (frame.m_payloadSize + header.length()))
549  {
550  if (!socket->waitForReadyRead(2000)) // Wait 2 seconds for the next chunk of the frame
551  {
552 
553  m_errorCount++;
554 
555  if (m_errorCount == 5)
556  {
557  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Timed out waiting for rest of frame to arrive.");
559  }
560  return;
561  }
562  }
563 
566 
567  LOG(VB_HTTP, LOG_DEBUG, QString("Read Header: %1").arg(QString(header.toHex())));
568  LOG(VB_HTTP, LOG_DEBUG, QString("Final Frame: %1").arg(frame.m_finalFrame ? "Yes" : "No"));
569  LOG(VB_HTTP, LOG_DEBUG, QString("Op Code: %1").arg(QString::number(frame.m_opCode)));
570  LOG(VB_HTTP, LOG_DEBUG, QString("Payload Size: %1 Bytes").arg(QString::number(frame.m_payloadSize)));
571  LOG(VB_HTTP, LOG_DEBUG, QString("Total Payload Size: %1 Bytes").arg(QString::number( m_readFrame.m_payloadSize)));
572 
573  if (!m_fuzzTesting &&
574  frame.m_payloadSize > qPow(2,20)) // Set 1MB limit on payload per frame
575  {
576  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Frame payload larger than limit of 1MB");
577  SendClose(kCloseTooLarge, "Frame payload larger than limit of 1MB");
578  return;
579  }
580 
581  if (!m_fuzzTesting &&
582  m_readFrame.m_payloadSize > qPow(2,22)) // Set 4MB limit on total payload
583  {
584  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Total payload larger than limit of 4MB");
585  SendClose(kCloseTooLarge, "Total payload larger than limit of 4MB");
586  return;
587  }
588 
589  socket->read(headerSize); // Discard header from read buffer
590 
591  frame.m_payload = socket->read(frame.m_payloadSize);
592 
593  // Unmask payload
594  if (frame.m_isMasked)
595  {
596  frame.m_mask = header.right(4);
597  for (uint i = 0; i < frame.m_payloadSize; i++)
598  frame.m_payload[i] = frame.m_payload.at(i) ^ frame.m_mask[i % 4];
599  }
600 
604  {
605  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Incomplete multi-part frame? Expected continuation.");
606  SendClose(kCloseProtocolError, "Incomplete multi-part frame? Expected continuation.");
607  return;
608  }
609 
610  // Check control frame validity
611  if (frame.m_opCode >= 0x08)
612  {
613  if (!frame.m_finalFrame)
614  {
615  SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
616  return;
617  }
618  if (frame.m_payloadSize > 125)
619  {
620  SendClose(kCloseProtocolError, "Control frames MUST NOT have payload greater than 125 bytes");
621  return;
622  }
623  }
624 
625  switch (frame.m_opCode)
626  {
629  {
630  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Received Continuation Frame out of sequence");
631  SendClose(kCloseProtocolError, "Received Continuation Frame out of sequence");
632  return;
633  }
634 
635  m_readFrame.m_payload.append(frame.m_payload);
636 
638  {
639  m_readFrame.m_finalFrame = true;
640  frame = m_readFrame;
641  // Fall through to appropriate handler for complete payload
642  }
643  else
644  break;
645  [[clang::fallthrough]];
648  HandleDataFrame(frame);
649  break;
651  SendPong(frame.m_payload);
652  break;
654  break;
656  if (!frame.m_finalFrame)
657  SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
658  else
660  break;
661  default:
662  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Received Unknown Frame Type");
663  break;
664  }
665 
666  frame.reset();
667  }
668 }
669 
671 {
672 }
673 
675 {
676  if (frame.m_finalFrame)
677  {
678  switch (frame.m_opCode)
679  {
681  if (!CodecUtil::isValidUTF8(frame.m_payload))
682  {
683  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Message is not valid UTF-8");
684  SendClose(kCloseBadData, "Message is not valid UTF-8");
685  return;
686  }
687  // For Debugging and fuzz testing
688  if (m_fuzzTesting)
689  SendText(frame.m_payload);
690  for (auto *const extension : qAsConst(m_extensions))
691  {
692  if (extension->HandleTextFrame(frame))
693  break;
694  }
695  break;
697  if (m_fuzzTesting)
698  SendBinary(frame.m_payload);
699  for (auto *const extension : qAsConst(m_extensions))
700  {
701  if (extension->HandleBinaryFrame(frame))
702  break;
703  }
704  break;
705  default:
706  break;
707  }
708  m_readFrame.reset();
709  }
710  else
711  {
712  // Start of new fragmented frame
713  m_readFrame.reset();
714  m_readFrame.m_opCode = frame.m_opCode;
717  m_readFrame.m_fragmented = true;
718  m_readFrame.m_finalFrame = false;
719  }
720 }
721 
722 void WebSocketWorker::HandleCloseConnection(const QByteArray &payload)
723 {
724  uint16_t code = kCloseNormal;
725 
726  if (payload.length() == 1)
727  {
728  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Invalid close payload");
729  SendClose(kCloseProtocolError, "Invalid close payload");
730  return;
731  }
732 
733  if (payload.length() >= 2)
734  {
735  code = (payload[0] << 8);
736  code |= payload[1] & 0xFF;
737  }
738 
739  if ((code < 1000) ||
740  ((code > 1003) && (code < 1007)) ||
741  ((code > 1011) && (code < 3000)) ||
742  (code > 4999))
743  {
744  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Invalid close code received");
745  SendClose(kCloseProtocolError, "Invalid close code");
746  return;
747  }
748 
749  QString closeMessage;
750  if (payload.length() > 2)
751  {
752  QByteArray messageBytes = payload.mid(2);
753  if (!CodecUtil::isValidUTF8(messageBytes))
754  {
755  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Message is not valid UTF-8");
756  SendClose(kCloseBadData, "Message is not valid UTF-8");
757  return;
758  }
759  closeMessage = QString(messageBytes);
760  }
761 
762  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker - Received CLOSE frame - [%1] %2")
763  .arg(QString::number(code)).arg(closeMessage));
764  SendClose((ErrorCode)code);
765 }
766 
768  const QByteArray &payload)
769 {
770  QByteArray frame;
771 
772  int payloadSize = payload.length();
773 
774  if (payloadSize >= qPow(2,64))
775  {
776  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::CreateFrame() - Payload "
777  "exceeds the allowed size for a single frame");
778  return frame;
779  }
780 
781  if (type >= 0x08)
782  {
783  //if (!m_finalFrame)
784  // SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
785  if (payloadSize > 125)
786  {
787  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::CreateFrame() - Control frames MUST NOT have payload greater than 125bytes");
788  return frame;
789  }
790  }
791 
792  uint16_t header = 0;
793  uint16_t extendedHeader = 0; // For payloads > 125 bytes
794  uint64_t superHeader = 0; // For payloads > 65,535 bytes
795 
796  // Only support single frames for now
797  header |= (1 << 15); // FIN (1 bit)
798  // RSV 1 to RSV 3 (1 bit each)
799  header |= (type << 8); // OpCode (4 bits)
800  header |= (0 << 7); // Mask (1 bit) (Off)
801  if (payloadSize < 126)
802  {
803  header |= payloadSize;
804  frame.insert(2, payload.constData(), payloadSize);
805  }
806  else if (payloadSize <= 65535)
807  {
808  header |= 126;
809  extendedHeader = payloadSize;
810  frame.insert(4, payload.constData(), payloadSize);
811  }
812  else
813  {
814  header |= 127;
815  superHeader = payloadSize;
816  frame.insert(10, payload.constData(), payloadSize);
817  }
818 
819  frame[0] = (uint8_t)((header >> 8) & 0xFF);
820  frame[1] = (uint8_t)(header & 0xFF);
821  if (extendedHeader > 0)
822  {
823  frame[2] = (extendedHeader >> 8) & 0xFF;
824  frame[3] = extendedHeader & 0xFF;
825  }
826  else if (superHeader > 0)
827  {
828  frame[2] = (superHeader >> 56) & 0xFF;
829  frame[3] = (superHeader >> 48) & 0xFF;
830  frame[4] = (superHeader >> 40) & 0xFF;
831  frame[5] = (superHeader >> 32) & 0xFF;
832  frame[6] = (superHeader >> 24) & 0xFF;
833  frame[7] = (superHeader >> 16) & 0xFF;
834  frame[8] = (superHeader >> 8) & 0xFF;
835  frame[9] = (superHeader & 0xFF);
836  }
837 
838  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker::CreateFrame() - Frame size %1").arg(QString::number(frame.length())));
839 
840  return frame;
841 }
842 
843 bool WebSocketWorker::SendFrame(const QByteArray &frame)
844 {
845  if (!m_socket || !m_socket->isOpen() || !m_socket->isWritable())
846  {
848  return false;
849  }
850 
851  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker::SendFrame() - '%1'...").arg(QString(frame.left(64).toHex())));
852 
853  m_socket->write(frame.constData(), frame.length());
854 
855  return true;
856 }
857 
858 bool WebSocketWorker::SendText(const QString &message)
859 {
860  return SendText(message.trimmed().toUtf8());
861 }
862 
863 bool WebSocketWorker::SendText(const QByteArray& message)
864 {
865  if (!CodecUtil::isValidUTF8(message))
866  {
867  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::SendText('%1...') - "
868  "Text contains invalid UTF-8 character codes. Discarded.")
869  .arg(QString(message.left(20))));
870  return false;
871  }
872 
873  QByteArray frame = CreateFrame(WebSocketFrame::kOpTextFrame, message);
874 
875  return !frame.isEmpty() && SendFrame(frame);
876 }
877 
878 bool WebSocketWorker::SendBinary(const QByteArray& data)
879 {
880  QByteArray frame = CreateFrame(WebSocketFrame::kOpBinaryFrame, data);
881 
882  return !frame.isEmpty() && SendFrame(frame);
883 }
884 
885 bool WebSocketWorker::SendPing(const QByteArray& payload)
886 {
887  QByteArray frame = CreateFrame(WebSocketFrame::kOpPing, payload);
888 
889  return !frame.isEmpty() && SendFrame(frame);
890 }
891 
892 bool WebSocketWorker::SendPong(const QByteArray& payload)
893 {
894  QByteArray frame = CreateFrame(WebSocketFrame::kOpPong, payload);
895 
896  return !frame.isEmpty() && SendFrame(frame);
897 }
898 
900  const QString &message)
901 {
902  LOG(VB_HTTP, LOG_DEBUG, "WebSocketWorker::SendClose()");
903  QByteArray payload;
904  payload.resize(2);
905  payload[0] = (uint8_t)(errCode >> 8);
906  payload[1] = (uint8_t)(errCode & 0xFF);
907 
908  if (!message.isEmpty())
909  payload.append(message.toUtf8());
910 
911  QByteArray frame = CreateFrame(WebSocketFrame::kOpClose, payload);
912 
913  if (!frame.isEmpty() && SendFrame(frame))
914  {
915  CloseConnection();
916  return true;
917  }
918 
919  CloseConnection();
920  return false;
921 }
922 
924 {
925  SendPing(QByteArray("HeartBeat"));
926 }
927 
929 {
930  if (!extension)
931  return;
932 
933  connect(extension, SIGNAL(SendTextMessage(const QString &)),
934  this, SLOT(SendText(const QString &)));
935  connect(extension, SIGNAL(SendBinaryMessage(const QByteArray &)),
936  this, SLOT(SendBinary(const QByteArray &)));
937 
938  m_extensions.append(extension);
939 }
940 
942 {
943  if (!extension)
944  return;
945 
946  for (auto it = m_extensions.begin(); it != m_extensions.end(); ++it)
947  {
948  if ((*it) == extension)
949  {
950  it = m_extensions.erase(it);
951  break;
952  }
953  }
954 }
955 
956 
WebSocketWorker::m_webSocketServer
WebSocketServer & m_webSocketServer
Definition: websocket.h:274
WebSocketWorkerThread::m_webSocketServer
WebSocketServer & m_webSocketServer
Definition: websocket.h:171
mythevent.h
bool
bool
Definition: pxsup2dast.c:30
kSSLServer
@ kSSLServer
Definition: serverpool.h:33
WebSocketWorker::ProcessFrames
void ProcessFrames(QTcpSocket *socket)
Returns false if an error occurs.
Definition: websocket.cpp:483
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
WebSocketWorker::m_eventLoop
QEventLoop * m_eventLoop
Definition: websocket.h:273
WebSocketFrame::m_opCode
OpCode m_opCode
Definition: websocket.h:116
ServerPool::setMaxPendingConnections
void setMaxPendingConnections(int n)
Definition: serverpool.h:94
WebSocketWorker::kCloseBadData
@ kCloseBadData
Definition: websocket.h:232
WebSocketWorker::ErrorCode
ErrorCode
Definition: websocket.h:220
WebSocketFrame::m_finalFrame
bool m_finalFrame
Definition: websocket.h:113
WebSocketFrame
A representation of a single WebSocket frame.
Definition: websocket.h:76
CodecUtil::isValidUTF8
static bool isValidUTF8(const QByteArray &data)
Definition: codecutil.cpp:6
WebSocketFrame::m_isMasked
bool m_isMasked
Definition: websocket.h:117
codecutil.h
MThreadPool::maxThreadCount
int maxThreadCount(void) const
Definition: mthreadpool.cpp:510
WebSocketWorker::~WebSocketWorker
~WebSocketWorker() override
Definition: websocket.cpp:128
WebSocketWorkerThread::m_socketFD
qt_socket_fd_t m_socketFD
Definition: websocket.h:172
arg
arg(title).arg(filename).arg(doDelete))
websocket.h
WebSocketWorker::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:289
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
WebSocketServer::newTcpConnection
void newTcpConnection(qt_socket_fd_t socket) override
Definition: websocket.cpp:48
WebSocketWorker::m_extensions
QList< WebSocketExtension * > m_extensions
Definition: websocket.h:294
WebSocketWorker::HandleDataFrame
void HandleDataFrame(const WebSocketFrame &frame)
Definition: websocket.cpp:674
MThreadPool::Stop
void Stop(void)
Definition: mthreadpool.cpp:267
WebSocketExtension
Base class for extensions.
Definition: websocket.h:137
WebSocketFrame::kOpTextFrame
@ kOpTextFrame
Definition: websocket.h:104
WebSocketWorker::SendBinary
bool SendBinary(const QByteArray &data)
Definition: websocket.cpp:878
WebSocketWorkerThread::m_connectionType
PoolServerType m_connectionType
Definition: websocket.h:173
WebSocketWorker::CloseConnection
void CloseConnection()
Definition: websocket.cpp:151
WebSocketWorker::m_connectionType
PoolServerType m_connectionType
Definition: websocket.h:277
WebSocketFrame::kOpPong
@ kOpPong
Definition: websocket.h:109
WebSocketWorker::m_heartBeat
QTimer * m_heartBeat
Definition: websocket.h:286
mythlogging.h
WebSocketWorker::SendPing
bool SendPing(const QByteArray &payload)
Definition: websocket.cpp:885
WebSocketWorker::SetupSocket
void SetupSocket()
Definition: websocket.cpp:160
WebSocketFrame::kOpContinuation
@ kOpContinuation
Definition: websocket.h:103
WebSocketWorker::WebSocketWorker
WebSocketWorker(WebSocketServer &webSocketServer, qt_socket_fd_t sock, PoolServerType type, const QSslConfiguration &sslConfig)
Definition: websocket.cpp:101
PrivTcpServer
Definition: serverpool.h:40
WebSocketWorker::HandleControlFrame
void HandleControlFrame(const WebSocketFrame &frame)
Returns false if an error occurs.
Definition: websocket.cpp:670
WebSocketWorker::SendFrame
bool SendFrame(const QByteArray &frame)
Definition: websocket.cpp:843
WebSocketWorkerThread
The thread in which WebSocketWorker does it's thing.
Definition: websocket.h:158
WebSocketWorker::m_isRunning
bool m_isRunning
Definition: websocket.h:284
WebSocketWorker::SendClose
bool SendClose(ErrorCode errCode, const QString &message=QString())
Definition: websocket.cpp:899
PoolServerType
PoolServerType
Definition: serverpool.h:30
WebSocketFrame::m_payload
QByteArray m_payload
Definition: websocket.h:114
WebSocketWorker::RegisterExtension
void RegisterExtension(WebSocketExtension *extension)
Definition: websocket.cpp:928
WebSocketWorker::m_webSocketMode
bool m_webSocketMode
Definition: websocket.h:280
WebSocketWorker::SendPong
bool SendPong(const QByteArray &payload)
Definition: websocket.cpp:892
WebSocketWorker::kCloseUnexpectedErr
@ kCloseUnexpectedErr
Definition: websocket.h:236
WebSocketWorkerThread::WebSocketWorkerThread
WebSocketWorkerThread(WebSocketServer &webSocketServer, qt_socket_fd_t sock, PoolServerType type, const QSslConfiguration &sslConfig)
Definition: websocket.cpp:69
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
WebSocketServer::m_threadPool
MThreadPool m_threadPool
Definition: websocket.h:58
WebSocketServer::m_rwlock
QReadWriteLock m_rwlock
Definition: websocket.h:57
WebSocketServer::IsRunning
bool IsRunning(void) const
Definition: websocket.h:45
WebSocketWorker::doRead
void doRead()
Definition: websocket.cpp:283
WebSocketFrame::m_payloadSize
uint64_t m_payloadSize
Definition: websocket.h:115
WebSocketFrame::reset
void reset(void)
Definition: websocket.h:89
WebSocketFrame::kOpClose
@ kOpClose
Definition: websocket.h:107
WebSocketServer::m_running
bool m_running
Definition: websocket.h:59
WebSocketWorker
Performs all the protocol-level work for a single websocket connection.
Definition: websocket.h:198
mythcorecontext.h
WebSocketWorker::m_readFrame
WebSocketFrame m_readFrame
Definition: websocket.h:281
WebSocketServer::WebSocketServer
WebSocketServer()
Definition: websocket.cpp:24
WebSocketWorker::kCloseTooLarge
@ kCloseTooLarge
Definition: websocket.h:234
WebSocketServer::~WebSocketServer
~WebSocketServer() override
Definition: websocket.cpp:39
WebSocketWorkerThread::run
void run(void) override
Definition: websocket.cpp:84
WebSocketWorker::SendHeartBeat
void SendHeartBeat()
Definition: websocket.cpp:923
WebSocketFrame::OpCode
OpCode
Definition: websocket.h:102
WebSocketWorker::Exec
void Exec()
Definition: websocket.cpp:146
WebSocketWorker::HandleCloseConnection
void HandleCloseConnection(const QByteArray &payload)
Definition: websocket.cpp:722
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:1
WebSocketFrame::m_mask
QByteArray m_mask
Definition: websocket.h:118
MThreadPool::setMaxThreadCount
void setMaxThreadCount(int maxThreadCount)
Definition: mthreadpool.cpp:516
WebSocketWorker::m_socket
QTcpSocket * m_socket
Definition: websocket.h:276
WebSocketServer
The WebSocket server, which listens for connections.
Definition: websocket.h:38
WebSocketWorker::DeregisterExtension
void DeregisterExtension(WebSocketExtension *extension)
Definition: websocket.cpp:941
websocket_mythevent.h
WebSocketWorker::CreateFrame
static QByteArray CreateFrame(WebSocketFrame::OpCode type, const QByteArray &payload)
Definition: websocket.cpp:767
WebSocketWorker::m_errorCount
uint8_t m_errorCount
Definition: websocket.h:283
WebSocketWorkerThread::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:175
MythCoreContext::CheckSubnet
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
Definition: mythcorecontext.cpp:1274
WebSocketFrame::m_fragmented
bool m_fragmented
Definition: websocket.h:119
WebSocketFrame::kOpPing
@ kOpPing
Definition: websocket.h:108
qt_socket_fd_t
qintptr qt_socket_fd_t
Definition: mythqtcompat.h:4
WebSocketServer::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:62
MThreadPool::startReserved
void startReserved(QRunnable *runnable, const QString &debugName, int waitForAvailMS=0)
Definition: mthreadpool.cpp:361
WebSocketFrame::kOpBinaryFrame
@ kOpBinaryFrame
Definition: websocket.h:105
WebSocketWorker::kCloseNormal
@ kCloseNormal
Definition: websocket.h:221
WebSocketWorker::CleanupSocket
void CleanupSocket()
Definition: websocket.cpp:222
WebSocketWorker::kCloseProtocolError
@ kCloseProtocolError
Definition: websocket.h:223
kTCPServer
@ kTCPServer
Definition: serverpool.h:31
WebSocketWorker::m_socketFD
qt_socket_fd_t m_socketFD
Definition: websocket.h:275
WebSocketMythEvent
Extension for sending MythEvents over WebSocketServer.
Definition: websocket_mythevent.h:15
WebSocketWorker::SendText
bool SendText(const QString &message)
Definition: websocket.cpp:858
WebSocketWorker::m_fuzzTesting
bool m_fuzzTesting
Definition: websocket.h:292
WebSocketWorker::ProcessHandshake
bool ProcessHandshake(QTcpSocket *socket)
Definition: websocket.cpp:311